Files
scuc-qt-course/tools/gen_part1.py
T
张宗平 13b3ebbe14 课程代码仓库初始化:Part1 (144) + Part3 (29) 全量生成与验证
把川大 wiki「02.Qt方向」课程代码整理为 CMake 管理的可编译运行项目。

Part1 C/C++ 强化 (p01/, 144 目标, host gcc/g++ 构建, 全绿):
- 修复全部编译失败:补 <cstring>/<fstream>/<iomanip>/<vector> 等缺失头
  (common_fix + infer_stl_includes 自动检测注入)、修正 pause() 壳注入
  (改检测已转换后的 pause 调用)、namespace 提升出函数体、const 成员函数
  正确性、dedupe 不再破坏函数重载集、K&R 隐式 int / 动态异常规范等
  C++17 移除特性的可编译等价改写。
- 目录采用深缩写 p01/ch04/s03/ (原 part1_cpp/ch04_class_object/03_ctor_dtor)。

Part3 Qt 桌面 (p03/, 29 目标, 隔离 Qt 5.14.2 构建, 全绿):
- gen_part3.py: Qt 外壳模板 (main+QApplication+show, QTimer 自动退出便于
  offscreen 验证)、AUTOMOC + .moc include、rewrite_* 处理散文混入/重载/
  资源依赖、生成占位 png/gif + .qrc + rundata 样例数据。
- 修正 QLable 笔误、全角分号、.→->、Windows 路径、QApplication 阻塞事件
  循环改为 QCoreApplication 等。

任务1 概念/版本验证:
- tools/std_probe.py: 跨 -std= 编译探针 (--pedantic-errors 让已废除特性硬失败)。
- tools/demo_versions/: K&R 隐式 int、三目左值、void* 转换、enum 赋整、
  const 经指针、动态异常规范 共 6 个跨版本演示。
- docs/VERSION_NOTES.md: 每条「原文说法→C17/C++17 是否成立→何版本成立→已验证」。

文档: 顶层 README、docs/SOURCE_PAGES.md (页面→目录映射)、
docs/ERRATA.md (Part1 修正)、docs/ERRATA_part3.md (Part3 修正)。
另修复 tools/run.sh / run_qt.sh 的 ${1:?...} 引号语法 bug。

隔离 Qt 验证: ldd 0 系统 Qt 引用; 173/173 目标全绿; p03 offscreen 运行通过。
2026-06-30 14:16:55 +08:00

911 lines
51 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env python3
# ==============================================================================
# gen_part1.py — 生成 Part1C/C++ 强化)的全部源码与 CMakeLists
#
# 策略:解析每页标题层级得到子话题;同一子话题内的代码块(多为 test() 片段)
# 合并为「一个完整小程序」,按教学顺序嵌入并标注出处(// --- 原文片段 N ---)。
# 语言按内容判定(C 块用 .c,C++ 块用 .cpp)。每段代码先经 common_fix 修补
# 平台/笔误,再走本文件中针对个别页/块的 EXACT_FIX。
# ------------------------------------------------------------------------------
import json, os, re, sys
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
import gen_sources
from gen_sources import (REPO, WIKI, page_outline, classify_lang, is_complete,
has_windows_pause, has_pause_call, needs_cstring,
infer_stl_includes, slug, ascii_slug, write,
record_erratum, common_fix, PAGE_CONFIG, LANG_OVERRIDE)
from file_name_map import stem_for
# ERRATA is a module-level list in gen_sources that record_erratum mutates in place.
ERRATA = gen_sources.ERRATA
# track section titles we could not map, to report at end
UNMAPPED = []
def src_name_for(pid, sec_title, prefix, idx_in_page):
"""English source-file stem: explicit map → chapter-number fallback → counter."""
stem = stem_for(pid, sec_title)
if stem:
return stem
UNMAPPED.append((pid, sec_title))
# fallback: use the leading chapter number from the title (e.g. "4.3.7.1 ...")
m = re.match(r"(\d+(?:\.\d+)*)", sec_title)
if m:
num = m.group(1).replace(".", "_")
return f"{prefix}_sec_{num}"
return f"{prefix}_t{idx_in_page:02d}"
EXTRACTED = json.load(open(os.path.join(WIKI, "extracted_code.json")))
PAGES = {p["id"]: p for p in json.load(open(os.path.join(WIKI, "all_page_ids.json")))}
# ==============================================================================
# 针对单个代码块的精确修正(结构性/逻辑性错误)。返回修正后的代码。
# 仅对确有错误的块做最小改动,并 record_erratum。
# ==============================================================================
def exact_fix(pid, idx, code):
if pid == "58954439" and idx == 14:
# K&R 隐式 intC99 起非法,C17 无法编译。原意是演示「C89 允许省略类型」
# 并可向无类型参数函数传任意实参。C17 下给出显式类型的等价对照:
# fun1(i) → fun1(int i)
# fun2(i) → fun2(char* i)
# fun3() → fun3(void),原调用 fun3(1,2,"abc") 在 C17 非法 → 改 fun3()
# g(){...} → int g(void){...}
# 原始 C89 形式(含无类型参数)仍由 tools/demo_versions/kr_implicit_int.c
# 在 -std=c89 下跨版本演示。
fixed = code.replace("int fun1(i){", "int fun1(int i){")
fixed = fixed.replace("int fun2(i){", "int fun2(char* i){")
fixed = fixed.replace("int fun3(){ ", "int fun3(void){ ")
fixed = re.sub(r"\ng\(\)\{", "\nint g(void){", fixed)
# main 中 fun3(1, 2, "abc") 在 C17 下是参数过多错误;改为 fun3() 并注释说明
fixed = fixed.replace('fun3(1, 2, "abc");',
'fun3(); // 注意:C89 中 fun3() 可接受任意参数;C17 下 fun3(void) 不接受参数')
record_erratum(pid, idx, "removed-feature",
"K&R 隐式 intint fun1(i)、g(){...})及无类型参数 fun3() 接受任意实参",
"显式 intint fun1(int i)、int g(void){...}),fun2 形参显式为 char*"
"fun3() → fun3(void)、调用改为 fun3()C17 不允许向 (void) 函数传参)",
"隐式 int 与无类型/可变参数在 C99 起被废除,C17 不可编译。原始 C89 形式仍可由 "
"tools/std_probe.py 与 tools/demo_versions/kr_implicit_int.c 在 -std=c89 下演示。")
return fixed
if pid == "58954439" and idx == 38:
# 类定义末尾缺分号
if not code.rstrip().endswith(";"):
fixed = code.rstrip() + ";\n"
record_erratum(pid, idx, "syntax",
"class Person{...}(末尾缺分号)", "class Person{...};",
"C++ 类/结构体定义后必须有分号。")
return fixed
if pid == "58954451" and idx == 8:
# 伪代码:中文散文混入语句。提取可编译对照(new vs malloc),散文转注释。
fixed = (
"// 原文为伪代码对照,演示 new 与 malloc 的等价关系:\n"
"// Person* person = new Person;\n"
"// 相当于:\n"
"// Person* person = (Person*)malloc(sizeof(Person));\n"
"// if(person == NULL){ return 0; }\n"
"// person->Init(); // 构造函数\n"
"// 下面给出可编译的对照程序:\n"
"#include <iostream>\n"
"#include <cstdlib>\n"
"#include <cstring>\n"
"class Person {\n"
"public:\n"
" Person() { std::cout << \"构造函数!\" << std::endl; }\n"
" void Init() { std::cout << \"Init\" << std::endl; }\n"
"};\n"
"int main() {\n"
" Person* person = new Person; // C++: new 自动调用构造函数\n"
" Person* p2 = (Person*)malloc(sizeof(Person));\n"
" if (p2) { new (p2) Person(); } // 对齐 new 的行为(placement new\n"
" delete person;\n"
" if (p2) { p2->~Person(); free(p2); }\n"
" return 0;\n"
"}\n"
)
record_erratum(pid, idx, "pseudo-code",
"new Person / malloc / 构造函数(中文散文混入语句,不可编译)",
"可编译对照程序:new vs malloc+placement new,散文转注释",
"原文是对照式伪代码(含「相当于:」「构造函数」等中文散文),无法编译。"
"改为可编译的 new 与 malloc 行为对照(用 placement new 对齐构造语义)。")
return fixed
if pid == "58954439" and idx in (3, 6, 7):
# wiki 把 namespace 定义 / namespace 作用域成员函数定义 写在 void test(){...}
# 函数体内,这在 C++ 中非法(namespace 必须在全局/命名空间作用域)。把
# namespace 块提升到文件作用域,test() 仅保留对命名空间成员的使用,使示例
# 既可编译又保留原教学意图(演示命名空间的声明与定义分离)。
if idx == 3:
fixed = (
"namespace A{\n\tint a = 10;\n}\n"
"namespace B{\n\tint a = 20;\n}\n"
"void test(){\n"
"\tcout << \"A::a : \" << A::a << endl;\n"
"\tcout << \"B::a : \" << B::a << endl;\n"
"}\n"
)
elif idx == 6:
fixed = (
"namespace MySpace{\n\tvoid func1();\n\tvoid func2(int param);\n}\n"
)
else: # idx == 7
fixed = (
"namespace MySpace{\n\tvoid func1();\n\tvoid func2(int param);\n}\n"
"void MySpace::func1(){\n\tcout << \"MySpace::func1\" << endl;\n"
"}\n"
"void MySpace::func2(int param){\n"
"\tcout << \"MySpace::func2 : \" << param << endl;\n"
"}\n"
"void test(){\n\tMySpace::func1();\n\tMySpace::func2(42);\n}\n"
)
record_erratum(pid, idx, "illegal-in-function",
"namespace 定义 / 命名空间作用域成员函数定义 写在函数体内(void test(){ namespace A{...} }",
"把 namespace 块与成员定义提升到文件作用域,test() 仅使用其成员",
"C++ 不允许在函数体内定义 namespace 或其成员函数;标准要求它们处于全局/"
"命名空间作用域。原 wiki 写法无法编译;提升后保留教学意图。")
return fixed
if pid == "58954439" and idx == 45:
# extern_c_module 的实现文件用 EXIT_SUCCESS 但只 include 了 my_module.h
# 补上 <stdlib.h>。
if "EXIT_SUCCESS" in code and "#include <stdlib.h>" not in code:
fixed = code.replace('#include"my_module.h"',
'#include"my_module.h"\n#include <stdlib.h>')
record_erratum(pid, idx, "missing-include",
'"my_module.h" 引用 EXIT_SUCCESS 但未含 <stdlib.h>',
"补 #include <stdlib.h>",
"EXIT_SUCCESS 定义在 <stdlib.h>。")
return fixed
if pid == "58954439" and idx == 24:
# 该块引用 Person(前面小节定义于另一文件),独立编译为缺失符号。补一个最小
# 的 Person 前置定义(仅含本块用到的 age 成员 + 默认成员初始化,使
# const Person person; 可默认初始化、C++17 合法),使其自洽可编译。
pre = ("class Person { public: int age = 0; };\n")
record_erratum(pid, idx, "missing-symbol",
"const Person 演示引用 Person,但 Person 定义在另一小节(拆分为独立文件后丢失)",
"在本块顶部补一个最小 Person 定义(age 带默认成员初始化,使 const 对象可默认构造)",
"原 wiki 把 Person 定义与 const 演示放在同节、本应同文件;按「示例相对隔离」拆开后"
"本块自洽编译所需的最小前置定义。const 聚合对象需默认成员初始化才能默认构造(C++17)。")
return pre + code
if pid == "58954439" and idx == 41:
# wiki 把 namespace A/B 定义(含重载集 MyFunc)写在函数体内(被 block_classify
# 误判为 statements 包进 block_0(){}),namespace 不能在函数内定义。提升到
# 文件作用域,并在 test() 中调用命名空间内的重载演示调用(与原文重载集意图一致)。
fixed = (
"namespace A{\n"
"\tvoid MyFunc(){ cout << \"无参数!\" << endl; }\n"
"\tvoid MyFunc(int a){ cout << \"a: \" << a << endl; }\n"
"\tvoid MyFunc(string b){ cout << \"b: \" << b << endl; }\n"
"\tvoid MyFunc(int a, string b){ cout << \"a: \" << a << \" b:\" << b << endl;}\n"
"\tvoid MyFunc(string b, int a){ cout << \"a: \" << a << \" b:\" << b << endl;}\n"
"}\n"
"namespace B{\n"
"\tvoid MyFunc(string b, int a){}\n"
"\t//int MyFunc(string b, int a){} //无法重载仅按返回值区分的函数\n"
"}\n"
"void test(){\n"
"\tA::MyFunc();\n"
"\tA::MyFunc(10);\n"
"\tA::MyFunc(\"hello\");\n"
"\tA::MyFunc(10, \"world\");\n"
"\tA::MyFunc(\"world\", 10);\n"
"\tB::MyFunc(\"ok\", 1);\n"
"}\n"
)
record_erratum(pid, idx, "illegal-in-function",
"namespace A/B 定义(含重载集 MyFunc)写在函数体内(void test(){ namespace A{...} }",
"把 namespace 块提升到文件作用域,test() 通过 A::/B:: 调用各重载",
"C++ 不允许在函数体内定义 namespace;标准要求其处于全局/命名空间作用域。"
"原 wiki 写法无法编译;提升后保留重载集演示意图。")
return fixed
if pid == "58954439" and idx == 42:
# 该块演示「函数重载碰上默认参数 → 二义性」。原文 main 里 MyFunc("hello")
# 在两个重载间确实二义,编译器拒绝——但教学意图是「展示二义性」,不是产出
# 可运行错误。注意:MyFunc(string) 与 MyFunc(string,int=10) 中,任何只给一个
# string 实参的调用都二义(强转 string 也无济于事,两候选都同等匹配)。故把
# 单参调用注释为「二义不可调用」,main 只显式调用双参重载,使示例可编译运行
# 且保留「二义性」教学信息。
fixed = (
"void MyFunc(string b){\n"
"\tcout << \"b: \" << b << endl;\n"
"}\n"
"//函数重载碰上默认参数\n"
"void MyFunc(string b, int a = 10){\n"
"\tcout << \"a: \" << a << \" b:\" << b << endl;\n"
"}\n"
"int main(){\n"
"\t// MyFunc(\"hello\"); // 二义:MyFunc(string) 与 MyFunc(string,int=10) 同等匹配\n"
"\t// MyFunc(string(\"hello\")); // 仍二义:强转 string 也无法区分两候选\n"
"\tMyFunc(\"hello\", 20); // 显式给第二参 → 明确选双参重载\n"
"\treturn 0;\n"
"}\n"
)
record_erratum(pid, idx, "ambiguous-overload",
'main 中 MyFunc("hello") 在 MyFunc(string) 与 MyFunc(string,int=10) 间二义,无法编译',
"注释保留二义调用作说明,main 只显式调用双参重载(单参形式因二义不可调用)",
"函数重载遇上默认参数时,编译器无法在两个可行候选间抉择(二义性)。即使强转 "
"std::string 也无法消除——两候选对单个 string 实参同等匹配。原文意在演示此现象,"
"作为可运行示例只能保留可调用的双参形式并注释说明二义点。")
return fixed
if pid == "58954456" and idx == 3:
# const 成员函数 ChangePerson() const 内修改非 mutable 成员 mAgemID 是
# mutable 合法)。const 成员函数不得修改对象逻辑状态——原文 mAge=100 非法。
# 修正:把对 mAge 的修改注释为「非法(演示 const 约束)」,仅保留对 mutable
# mID 的合法修改,并在注释中说明 const 成员函数的语义。
fixed = code.replace(
"\tvoid ChangePerson() const{\n"
"\t\tmAge = 100;\n"
"\t\tmID = 100;\n"
"\t}",
"\tvoid ChangePerson() const{\n"
"\t\t//mAge = 100; // 非法:const 成员函数内不能修改非 mutable 成员\n"
"\t\tmID = 100; // 合法:mID 被 mutable 修饰,const 函数内可改\n"
"\t}")
if fixed != code:
record_erratum(pid, idx, "const-correctness",
"const 成员函数 ChangePerson() const 内修改非 mutable 成员 mAgemAge = 100",
"注释掉对 mAge 的修改,保留对 mutable mID 的修改,并加注释说明",
"const 成员函数承诺不修改对象的逻辑状态,故不得修改非 mutable 数据成员;"
"mID 被 mutable 修饰例外。原写法在 C++17 下编译报错。")
return fixed
if pid == "58954461" and idx == 0:
# wiki 在 class IndexPage 前混入了孤立的中文散文标题词「网页类」(非注释、
# 非代码),编译器报 '网页类' does not name a type。把该散文行转为注释。
if re.search(r"(?m)^[ \t]*网页类[ \t]*$", code):
fixed = re.sub(r"(?m)^([ \t]*)网页类([ \t]*)$",
r"\1// 网页类(原文小标题,转为注释)\2", code)
record_erratum(pid, idx, "prose-in-code",
"class IndexPage 前混入孤立中文散文词「网页类」(非注释非代码)",
"把该散文行转为注释",
"wiki 正文标题词被误纳入代码块,C++ 无法解析;转为注释保留教学上下文。")
return fixed
if pid == "58954469" and idx == 5:
# B 有纯虚析构函数(virtual ~B() = 0;),故 B 是抽象类,不可实例化。原文
# test() 里 B b; 试图构造抽象类对象,编译报错。教学意图正是「B 是抽象类不能
# 实例化」——把 B b; 注释为「非法(演示)」并说明,A a; 保留。
if re.search(r"(?m)^[ \t]*B[ \t]+b[ \t]*;", code):
fixed = re.sub(r"(?m)^([ \t]*)B[ \t]+b[ \t]*;",
r"\1//B b; // 非法:B 含纯虚析构函数,是抽象类,不能实例化对象", code)
record_erratum(pid, idx, "abstract-instantiation",
"test() 中 B b; 试图实例化抽象类 B(B 有纯虚析构函数 virtual ~B()=0",
"注释掉 B b; 并加说明(B 是抽象类不可实例化),A a; 保留",
"含纯虚函数(含纯虚析构函数)的类是抽象类,C++ 禁止创建其对象。原文此处"
"正是演示该约束,但作为可编译示例需把非法构造注释化。")
return fixed
if pid == "58954473" and idx == 2:
# 笔误:注释行 //int b = const_cast<int>(a); 末尾误带了函数闭合花括号 },
# 导致 test01() 未闭合,test02() 被嵌套进 test01() 内 → 函数定义不允许在此。
# 把那个误入注释的 } 移出注释作为 test01() 的正常闭合。
bad = "//int b = const_cast<int>(a); }"
good = ("//int b = const_cast<int>(a); // 不能对非指针或非引用进行 const_cast\n"
"} // end of test01()")
if bad in code:
fixed = code.replace(bad, good)
record_erratum(pid, idx, "syntax",
"//int b = const_cast<int>(a); }(闭合花括号误入注释行)",
"把 } 移出注释作为 test01() 的正常闭合花括号",
"原 wiki 把 test01() 的闭合 } 误写进注释,导致函数体未结束、后续 test02() "
"被嵌套在 test01() 内,C++ 禁止函数内定义函数。")
return fixed
if pid == "58954474" and idx == 4:
# 动态异常规范 throw(int,char,char*) 与 throw() 在 C++17 起被废除(throw()
# 等价 noexcept 已在 C++11 弃用,C++17 移除动态规范)。教学意图是展示「异常
# 规范」概念。给出 C++17 可编译的等价形式:throw()→noexceptthrow(类型...)
# →noexcept(false)(无法精确表达「只抛某类型」,因 C++17 不支持),并注释
# 说明旧规范的语义与废除版本。
fixed = (
"//可抛出所有类型异常\n"
"void TestFunction01(){\n"
"\tthrow 10;\n"
"}\n\n"
"// 【C++17 修正】原文为 throw(int,char,char*) 动态异常规范——C++17 起非法\n"
"// C++11 弃用、C++17 移除)。动态规范无法在现代 C++ 中精确表达「只抛这些类型」,\n"
"// 等价改写为 noexcept(false)(表示可能抛异常)。原文语义见 tools/demo_versions/。\n"
"void TestFunction02() noexcept(false){\n"
"\tstring exception = \"error!\";\n"
"\tthrow exception;\n"
"}\n\n"
"// 【C++17 修正】原文为 throw()(不抛任何异常)→ C++17 等价于 noexcept。\n"
"void TestFunction03() noexcept {\n"
"\t//throw 10; // noexcept 函数抛异常会调用 std::terminate\n"
"}\n\n"
"int main(){\n\n"
"\ttry{\n"
"\t\t//TestFunction01();\n"
"\t\t//TestFunction02();\n"
"\t\t//TestFunction03();\n"
"\t}\n"
"\tcatch (...){\n"
"\t\tcout << \"捕获异常!\" << endl;\n"
"\t}\n\n"
"\treturn EXIT_SUCCESS;\n"
"}\n"
)
record_erratum(pid, idx, "removed-feature",
"动态异常规范 throw(int,char,char*) 与 throw()C++17 已移除)",
"throw() → noexceptthrow(类型...) → noexcept(false),并注释说明旧语义",
"动态异常规范在 C++11 弃用、C++17 移除,C++17 下是语法错误。throw() 的等价物是 "
"noexcept;带类型列表的动态规范无法在现代 C++ 中精确表达,用 noexcept(false) 近似"
"(表示可能抛异常)。旧规范演示见 tools/demo_versions/dynamic_exception_spec.cpp"
"(在 -std=c++14 下编译)。")
return fixed
if pid == "58954472" and idx == 11:
# MyArray 类模板应用:wiki 把「测试代码」用例的裸语句(中文标题词「测试代码:」
# + 文件作用域的 MyArray<int> myArrayInt(10); 及 for 循环、Push_back 调用等)
# 直接铺在类定义之后,没有任何函数包裹 → 全局变量初始化里含 for 循环非法,
# 且中文散文词非法。重写为:保留 MyArray 模板 + Person 类 + 打印函数,把测试
# 驱动语句包进 main(),使示例可编译运行并演示类模板。
fixed = (
"#include <string>\n"
"using std::string;\n\n"
"template<class T>\n"
"class MyArray {\n"
"public:\n"
"\texplicit MyArray(int capacity) {\n"
"\t\tthis->m_Capacity = capacity;\n"
"\t\tthis->m_Size = 0;\n"
"\t\t// 如果T是对象,那么这个对象必须提供默认的构造函数\n"
"\t\tpAddress = new T[this->m_Capacity];\n"
"\t}\n"
"\tMyArray(const MyArray& arr) {\n"
"\t\tthis->m_Capacity = arr.m_Capacity;\n"
"\t\tthis->m_Size = arr.m_Size;\n"
"\t\tthis->pAddress = new T[this->m_Capacity];\n"
"\t\tfor (int i = 0; i < this->m_Size; i++) {\n"
"\t\t\tthis->pAddress[i] = arr.pAddress[i];\n"
"\t\t}\n"
"\t}\n"
"\tT& operator[](int index) { return this->pAddress[index]; }\n"
"\tvoid Push_back(const T& val) {\n"
"\t\tif (this->m_Capacity == this->m_Size) return;\n"
"\t\tthis->pAddress[this->m_Size] = val;\n"
"\t\tthis->m_Size++;\n"
"\t}\n"
"\tvoid Pop_back() { if (this->m_Size > 0) this->m_Size--; }\n"
"\tint getSize() { return this->m_Size; }\n"
"\t~MyArray() {\n"
"\t\tif (this->pAddress != NULL) {\n"
"\t\t\tdelete[] this->pAddress;\n"
"\t\t\tthis->pAddress = NULL;\n"
"\t\t\tthis->m_Capacity = 0;\n"
"\t\t\tthis->m_Size = 0;\n"
"\t\t}\n"
"\t}\n"
"private:\n"
"\tT* pAddress;\n"
"\tint m_Capacity;\n"
"\tint m_Size;\n"
"};\n\n"
"class Person {\n"
"public:\n"
"\tPerson() {}\n"
"\tPerson(string name, int age) { this->mName = name; this->mAge = age; }\n"
"public:\n"
"\tstring mName;\n"
"\tint mAge;\n"
"};\n\n"
"void PrintMyArrayInt(MyArray<int>& arr) {\n"
"\tfor (int i = 0; i < arr.getSize(); i++) {\n"
"\t\tcout << arr[i] << \" \";\n"
"\t}\n"
"\tcout << endl;\n"
"}\n\n"
"void PrintMyPerson(MyArray<Person>& personArr) {\n"
"\tfor (int i = 0; i < personArr.getSize(); i++) {\n"
"\t\tcout << \"姓名:\" << personArr[i].mName << \" 年龄:\" << personArr[i].mAge << endl;\n"
"\t}\n"
"}\n\n"
"// --- 测试代码(原文为文件作用域裸语句,已包进 main 使其可编译运行)---\n"
"int main() {\n"
"\tMyArray<int> myArrayInt(10);\n"
"\tfor (int i = 0; i < 9; i++) {\n"
"\t\tmyArrayInt.Push_back(i);\n"
"\t}\n"
"\tmyArrayInt.Push_back(100);\n"
"\tPrintMyArrayInt(myArrayInt);\n\n"
"\tMyArray<Person> myArrayPerson(10);\n"
"\tPerson p1(\"德玛西亚\", 30);\n"
"\tPerson p2(\"提莫\", 20);\n"
"\tPerson p3(\"孙悟空\", 18);\n"
"\tPerson p4(\"赵信\", 15);\n"
"\tPerson p5(\"赵云\", 24);\n"
"\tmyArrayPerson.Push_back(p1);\n"
"\tmyArrayPerson.Push_back(p2);\n"
"\tmyArrayPerson.Push_back(p3);\n"
"\tmyArrayPerson.Push_back(p4);\n"
"\tmyArrayPerson.Push_back(p5);\n"
"\tPrintMyPerson(myArrayPerson);\n"
"\treturn 0;\n"
"}\n"
)
record_erratum(pid, idx, "prose-in-code",
"「测试代码:」中文散文词 + 文件作用域裸语句(MyArray<int> myArrayInt(10);、"
"for 循环、Push_back 调用等)直接铺在类定义之后,无函数包裹",
"删除散文词,把测试驱动语句包进 main(),保留 MyArray 模板 + Person 类 + 打印函数",
"C++ 不允许在文件作用域直接写可执行语句(如 for 循环、函数调用);中文散文词"
"亦非法。原文是把「使用示例」当散文铺排;重写为可编译运行的单文件程序,保留"
"类模板应用的教学意图。")
return fixed
if pid == "58954439" and idx == 46:
# extern "C" 演示:main 调用 func1()/func2(),但这两个函数在本文件中仅有
# extern 声明(定义在配对的 45 块 MyModule.c)。本文件作为独立可执行目标编译
# 时链接报 undefined reference。修正:在本文件内就地提供 func1/func2 的 extern
# "C" 定义(最小实现),使示例自洽可编译运行;保留原文的 extern "C" 声明教学。
fixed = code + (
"\n// --- 为使本示例作为独立目标可链接,就地提供 func1/func2 的 extern \"C\" 定义 ---\n"
'// (原文仅声明;定义在配对的 MyModule.c。此处补最小实现以自洽编译运行。)\n'
'extern "C" void func1(){ printf("hello world!"); }\n'
'extern "C" int func2(int a, int b){ return a + b; }\n'
)
record_erratum(pid, idx, "missing-definition",
"main 调用 func1()/func2(),但本文件仅有 extern \"C\" 声明(定义在配对的 MyModule.c)",
"在本文件就地补 func1/func2 的 extern \"C\" 定义(最小实现)",
"原 wiki 把声明与定义分属两文件;按「示例相对隔离」拆分为独立目标后,本文件"
"缺少定义导致链接失败。补最小实现使其自洽可运行,保留 extern \"C\" 声明的教学意图。")
return fixed
if pid == "58954474" and idx == 8:
# 自定义异常类 MyOutOfRange::what() 重写 exception::what()。C++11 起
# std::exception::what() 声明为 noexcept,重写版本必须也 noexcept(否则
# 「looser exception specification」编译错)。补 noexcept。
if re.search(r"virtual\s+const\s+char\s*\*\s*what\s*\(\s*\)\s*const\s*\{", code):
fixed = re.sub(r"virtual\s+const\s+char\s*\*\s*what\s*\(\s*\)\s*const\s*\{",
"virtual const char* what() const noexcept {", code)
record_erratum(pid, idx, "override-noexcept",
"MyOutOfRange::what() const 重写 exception::what() 但缺 noexcept",
"补 noexceptvirtual const char* what() const noexcept",
"C++11 起 std::exception::what() 声明为 noexcept;重写函数的异常规范不得"
"比基类更宽松,必须同样 noexcept,否则 C++17 编译报「looser exception "
"specification」。原 wiki 写于旧标准,未带 noexcept。")
return fixed
if pid == "58954488" and idx == 5:
# set 排序仿函数 MyCompare02/03 的 operator() 非 const。C++14 起 set 的比较
# 对象要求「可 const 调用」(comparison object must be invocable as const),
# 非 const operator() 触发 static_assert。给两个 operator() 加 const。
fixed = code.replace(
"bool operator()(int v1,int v2){\n\t\treturn v1 > v2;\n\t}",
"bool operator()(int v1, int v2) const {\n\t\treturn v1 > v2;\n\t}")
fixed = fixed.replace(
"bool operator()(const Person& p1,const Person& p2){\n\t\treturn p1.mAge > p2.mAge;\n\t}",
"bool operator()(const Person& p1, const Person& p2) const {\n\t\treturn p1.mAge > p2.mAge;\n\t}")
if fixed != code:
record_erratum(pid, idx, "const-comparator",
"set 的排序仿函数 MyCompare02/MyCompare03 的 operator() 非 const",
"给两个 operator() 加 const 修饰",
"C++14 起,关联容器(set/map)的比较对象必须可 const 调用(标准要求 "
"Compare 对象满足 invocable as const)。原 wiki 的非 const operator() 在 "
"C++17 libstdc++ 下触发 static_assert。")
return fixed
if pid == "58954488" and idx == 4:
# list 节点结构演示:原文直接访问 MSVC 实现私有的 list 内部成员
# _Nodeptr / _Myhead / _Next / _Myval / _Mysize),这些是 MSVC STL 的
# 实现细节,libstdc++(gcc)没有等价物,无法编译。教学意图是「演示 list 的
# 环形双向链表节点结构」。改写为:用标准接口(迭代器)遍历 list,并用注释
# 图示说明环形链表结构(_Myhead 是哨兵头节点、_Next/_Prev 串联、_Myval 存值),
# 保留教学意图且跨实现可移植。
fixed = (
"#include <iostream>\n"
"#include <list>\n"
"using namespace std;\n\n"
"// 【可移植性说明】原文直接访问 MSVC STL 的 list 私有成员\n"
"// _Nodeptr / _Myhead / _Next / _Mysize / _Myval)来「演示 list 的环形\n"
"// 双向链表结构」。这些是 MSVC 实现细节,libstdc++(gcc)无等价物,无法移植。\n"
"// 下面改用标准迭代器接口遍历,并以注释图示说明 list 的环形双向链表结构:\n"
"//\n"
"// _Myhead (哨兵节点) ──┐\n"
"// ↑ ↓ │ list 内部维护一个哨兵头节点 _Myhead,\n"
"// [_Prev|_Myval|_Next] ←──→ [_Prev|_Myval|_Next] ←──→ ... ──┘\n"
"// 末节点.next = _Myhead_Myhead.prev = 末节点;_Mysize 记录元素个数。\n"
"// 遍历从 _Myhead->_Next 开始,绕一圈回到 _Myhead 即结束。\n\n"
"int main(){\n"
"\tlist<int> myList;\n"
"\tfor (int i = 0; i < 10; i++){\n"
"\t\tmyList.push_back(i);\n"
"\t}\n\n"
"\t// 标准(可移植)方式:用迭代器遍历环形双向链表\n"
"\tcout << \"size = \" << myList.size() << endl;\n"
"\tfor (list<int>::iterator it = myList.begin(); it != myList.end(); it++){\n"
"\t\tcout << \"Node:\" << *it << endl;\n"
"\t}\n\n"
"\treturn EXIT_SUCCESS;\n"
"}\n"
)
record_erratum(pid, idx, "implementation-specific",
"直接访问 MSVC STL 私有成员 _Nodeptr/_Myhead/_Next/_Myval/_Mysize(演示 list 节点结构)",
"改用标准迭代器遍历,以注释图示说明环形双向链表结构",
"MSVC STL 的下划线大写成员是实现细节,libstdc++ 无等价物,跨编译器不可移植。"
"C++ 标准不暴露容器内部节点结构;原写法仅在 MSVC 下可编译。改用标准接口 + 图示"
"注释保留教学意图。")
return fixed
if pid == "58954476" and idx == 4:
# 二进制文件读写:Person 只有 Person(char*,int) 构造,无默认构造,但读取时
# 用 Person p3; 默认构造 → 报错。补一个默认构造(成员带默认值),使二进制读
# 入占位对象合法。同时 char* fileName = "person.txt" 在 C++17 是 const char*
# → char* 的弃用转换(-Wall 报警,本例仅作最小修正)。
if "Person(char* name,int age)" in code and "Person(){}" not in code:
fixed = code.replace(
"class Person{\npublic:\n\tPerson(char* name,int age){",
"class Person{\npublic:\n\tPerson(){} // 补默认构造:二进制读入需先默认构造占位对象\n\tPerson(char* name,int age){")
record_erratum(pid, idx, "missing-default-ctor",
"Person 只有 Person(char*,int) 构造,但二进制读取用 Person p3; 默认构造",
"补一个空的默认构造 Person(){}",
"二进制文件读取需先默认构造一个占位对象再 read() 填充;原 Person 无默认"
"构造,C++17 报「no matching function for call to Person::Person()」。")
return fixed
return code
# ==============================================================================
# C/C++ 控制台程序的合并模板
# ==============================================================================
C_PREAMBLE = (
"#include <stdio.h>\n"
"#include <stdlib.h>\n"
"#include <string.h>\n"
)
CPP_PREAMBLE = (
"#include <iostream>\n"
"using namespace std;\n"
)
# 便携 pause() 壳(仅当某段用到 pause() 时注入;Windows 下原意是等待用户回车)
PAUSE_HELPER = (
"#include <cstdio>\n"
"static inline void pause() { printf(\"按回车键继续...\"); (void)getchar(); }\n"
)
# matches a top-level (column-0) function definition header like "void test01(){"
# or "int fun2(int a, int b){" — captures return-type and function name.
_TOPFN_RE = re.compile(
r"^([A-Za-z_][\w:&<>,\s\*]*?\s+)([A-Za-z_]\w*)\s*\(", re.M)
# a real function definition: optional ret-type, name, "(...)", then a body "{"
_REAL_FN_DEF_RE = re.compile(
r"^([A-Za-z_][\w:&<>,\s\*]*?\s+)?([A-Za-z_]\w*)\s*\([^;]*\)\s*\{", re.M)
def block_classify(code):
"""Classify a fragment's shape so we know how to make it compile.
'program' — has int main(...); kept as-is.
'fndef' — has a top-level function definition with body (e.g. void test(){...}).
'groupdecl' — only global declarations (namespace/class/typedef/using/var defs).
'statements'— loose statements with NO enclosing function (copied from inside
a test() body); must be wrapped to compile.
"""
if re.search(r"\bint\s+main\s*\(", code):
return "program"
if _REAL_FN_DEF_RE.search(code):
return "fndef"
stripped = re.sub(r"//[^\n]*", "", code)
stripped = re.sub(r"/\*.*?\*/", "", stripped, flags=re.S)
lines = [l.strip() for l in stripped.splitlines() if l.strip()]
if not lines:
return "statements"
decl_re = re.compile(
r"^(namespace|class|struct|typedef|using|template|enum|extern|#)\b"
r"|^(int|char|double|float|bool|auto|void|const|static|unsigned|long|short|size_t|wchar_t|string|std::)"
r"[\w:&<>,\*\s]*\s*[A-Za-z_]\w*\s*(=\s*[^;{]*|\([^;{]*\)\s*(=\s*\d+)?)?\s*;"
r"|^(int|char|double|float|bool|auto|void|const|static|size_t|wchar_t|string|std::)"
r"[\w:&<>,\*\s]*?\s+\*?[A-Za-z_]\w*\s*\([^;{]*\)\s*;\s*$")
# 'groupdecl' lines: a declaration line OR a namespace/class/struct/enum block
# boundary line (bare '{', bare '}', or '};' on its own). A bare call like
# printf(...); is NOT a declaration (no leading type) → classify as statements.
return "groupdecl" if all(decl_re.match(l) or l in {"{", "}", "};"} for l in lines) else "statements"
def wrap_statements(code, seq):
"""Wrap a loose-statement fragment in static void func_N(){...} so it compiles."""
if block_classify(code) != "statements":
return code, None
body = "\n".join((" " + l) if l.strip() else l for l in code.splitlines())
fn = f"block_{seq}"
return f"static void {fn}() {{\n{body}\n}}\n", fn
def dedupe_functions(blocks_with_meta):
"""Across the merged blocks, rename duplicated top-level function *definitions*
to <name>_<seq> so the file compiles. The wiki commonly shows several
alternative variants of the same `void test(){...}` under one heading; they
are meant to be read individually, not linked together. We keep all variants
in one file (preserving the teaching progression) but give each a unique name
and call each from main(). Recursion/calls within the same block are renamed
consistently.
CRITICAL: two definitions with the same name but DIFFERENT parameter lists
are legal function *overloads* and must NOT be renamed (renaming them all to
<name>_N would collapse the overload set into redefinitions). We only rename
a duplicate whose (name, param-count) signature already appeared — a true
variant. Returns the (possibly renamed) blocks."""
seen = {} # (fn_name, param_count) -> count of definitions seen so far
out = []
for (idx, code, sec_title) in blocks_with_meta:
# find top-level (col 0) function names *defined* in this block
# (a definition has a body on the same or next line; a call is indented)
defined = []
for m in _TOPFN_RE.finditer(code):
name = m.group(2)
# only treat as definition if the header is at column 0 (not indented call)
if m.start() == 0 or code[m.start()-1] == "\n":
# and it's followed by a body `{` within a few lines (def, not decl)
tail = code[m.end():m.end()+200]
if re.match(r"[^{;]*\{", tail):
# capture the parameter list to count params (overload detection).
# _TOPFN_RE ends right after the opening '(', so m.end() is just
# past '('; grab from there to the matching ')'.
rest = code[m.end():]
paren = re.match(r"([^)]*)\)", rest)
params = paren.group(1) if paren else ""
pcount = 0 if params.strip() == "" or params.strip() == "void" \
else params.count(",") + 1
defined.append((name, pcount))
rename = {} # old_name → new_name (applied by token; renames are unique
# per old name, so overloads sharing a name all map together)
name_seq = {} # name → last assigned seq
for (name, pcount) in defined:
if name == "main":
continue # never rename main; a block with its own main stands alone
if name == "operator":
# operator() / operator+ etc. are overloaded operators, not plain
# functions — renaming them to operator_N breaks the operator.
# They live inside classes anyway and don't collide at file scope.
continue
key = (name, pcount)
cnt = seen.get(key, 0)
# rename only if THIS exact signature (name+paramcount) was seen before —
# a same-name/different-paramcount def is a legal overload, left intact.
if cnt > 0:
seq = name_seq.get(name, 0) + 1
name_seq[name] = seq
rename[name] = f"{name}_{seq}"
seen[key] = cnt + 1
if rename:
# word-boundary replace of each old name with new (only the function
# name token; safe enough for these small teaching snippets)
newcode = code
for old, new in rename.items():
newcode = re.sub(r"\b" + re.escape(old) + r"\b", new, newcode)
code = newcode
out.append((idx, code, sec_title))
return out
def emit_console(target_name, blocks_with_meta, lang, out_dir, src_name=None):
"""Emit one .c/.cpp = one add_executable, merging given (idx,code,section_title)
blocks. Returns (rel_source_path, list of block ids used).
target_name: ASCII-only CMake target name (CMake forbids non-ASCII targets).
src_name: human-readable source filename (may contain CJK); defaults to
a transliteration of target_name."""
# First, rename duplicate function definitions across blocks so the merge
# compiles (each variant gets a unique name; all are called from main).
blocks_with_meta = dedupe_functions(blocks_with_meta)
ext = "c" if lang == "c" else "cpp"
if src_name is None:
src_name = target_name
fname = f"{src_name}.{ext}"
parts = []
# header
title_line = blocks_with_meta[0][2]
parts.append(f"// ============================================================================\n")
parts.append(f"// {target_name}{title_line}\n")
parts.append(f"// 来源:川大 C++/LinuxC wiki「02.Qt方向」\n")
parts.append(f"// 说明:本文件由 gen_part1.py 合并页面中的教学片段而成。每个片段用\n")
parts.append(f"// // --- 原文片段 [pageId:blockIdx] --- 标注出处。\n")
parts.append(f"// 已做最小修正(笔误/平台移植),详见 docs/ERRATA.md。\n")
parts.append(f"// ============================================================================\n\n")
# includes
# NOTE: detect pause() on the already-common_fix-ed code (common_fix converts
# system("pause") → pause()), so has_pause_call sees the converted form.
# Detecting the raw 'system("pause")' literal here would always be False and
# miss the pause() helper injection — the root cause of past link failures.
needs_pause = any(has_pause_call(b[1]) for b in blocks_with_meta)
needs_str = any(needs_cstring(b[1]) for b in blocks_with_meta)
# aggregate STL/IO headers inferred across all merged blocks (sorted, deduped)
stl_hdrs = sorted({h for b in blocks_with_meta for h in infer_stl_includes(b[1])})
if needs_str and "<cstring>" not in stl_hdrs:
stl_hdrs.append("<cstring>")
if lang == "c":
parts.append(C_PREAMBLE)
else:
parts.append(CPP_PREAMBLE)
for h in stl_hdrs:
parts.append(f"#include {h}\n")
if needs_pause:
parts.append("\n" + PAUSE_HELPER)
parts.append("\n")
# body: each block is normalized to a compilable unit before emission.
# 'program' → kept as-is (brings its own main).
# 'fndef' → the test()/fun() definitions are emitted as-is; called from main.
# 'groupdecl' → global decls (namespace/class/typedef/var) emitted at file scope.
# 'statements' → loose statements wrapped in static void block_N(){...} and called from main.
call_fns = []
for n, (idx, code, sec_title) in enumerate(blocks_with_meta):
parts.append(f"// --- 原文片段 [{PAGES_META_PID}:{idx}] {sec_title} ---\n")
body = code.rstrip() + "\n"
try:
cls = block_classify(body)
except Exception:
cls = "fndef" # conservative default
if cls == "statements":
wrapped, fn = wrap_statements(body, n)
if fn:
call_fns.append(fn)
parts.append(wrapped)
else:
parts.append(body)
if cls in ("fndef", "program"):
# only call no-arg DEMO entry points (test* / block_*) from main; any
# internal helpers they call (with params) are called by them.
for m in _REAL_FN_DEF_RE.finditer(body):
name = m.group(2)
if name == "main":
continue
if re.match(r"^(test|block_|demo_|run)", name):
call_fns.append(name)
parts.append("\n")
already_has_main = any(is_complete(b[1]) for b in blocks_with_meta)
call_fns = list(dict.fromkeys(call_fns))
parts.append("// --- main ---\n")
if already_has_main:
pass # rely on a block's own main
elif call_fns:
parts.append("int main() {\n")
for fn in call_fns:
parts.append(f" {fn}();\n")
if needs_pause:
parts.append(" pause();\n")
parts.append(" return 0;\n}\n")
else:
parts.append("int main() {\n return 0;\n}\n")
src_rel = os.path.join(out_dir, fname)
write(os.path.join(REPO, src_rel), "".join(parts))
return src_rel, [b[0] for b in blocks_with_meta]
# global set by main()
PAGES_META_PID = "?"
# ==============================================================================
# 主流程
# ==============================================================================
def main():
global PAGES_META_PID
part1_pages = [pid for pid, cfg in PAGE_CONFIG.items() if cfg["part"] == "p01"]
# collect CMake target entries per chapter dir
targets_by_dir = {} # dir -> list of (target_name, src_rel)
for pid in part1_pages:
cfg = PAGE_CONFIG[pid]
PAGES_META_PID = pid
body = json.load(open(os.path.join(WIKI, "bodies", f"{pid}.json"))) \
.get("body", {}).get("storage", {}).get("value", "")
blocks = EXTRACTED.get(pid, [])
sections = page_outline(body)
out_dir = os.path.join("p01", cfg["dir"])
# Decide language per block (override > classify)
def lang_of(i):
if (pid, i) in LANG_OVERRIDE:
return LANG_OVERRIDE[(pid, i)]
return classify_lang(blocks[i]["code"])
# If a page is almost-all one language, emit per-section merged files in that lang.
# Otherwise emit one file per section using the section's dominant language.
all_langs = [lang_of(i) for i in range(len(blocks))]
dominant = "cpp" if all_langs.count("cpp") >= all_langs.count("c") else "c"
used = [False] * len(blocks)
# --- 特殊处理:58954439:44 与 58954439:45 是 header/impl 对,作为一组两文件目标 ---
if pid == "58954439":
# 44 = MyModule.h, 45 = MyModule.c → 单独目标 p1c03_mymodule
code44 = exact_fix(pid, 44, common_fix(blocks[44]["code"], pid, 44))
code45 = exact_fix(pid, 45, common_fix(blocks[45]["code"], pid, 45))
hdr = os.path.join(out_dir, "my_module.h")
impl = os.path.join(out_dir, "extern_c_module_main.c")
write(os.path.join(REPO, hdr), code44 + "\n")
write(os.path.join(REPO, impl),
f"// {PAGES_META_PID}:45 — MyModule 实现文件(与 my_module.h 配对)\n"
f"// 由 gen_part1.py 成对输出。\n\n"
+ code45.replace('"MyModule.h"', '"my_module.h"').replace('#include"my_module.h"',
'#include"my_module.h"\n#include <stdlib.h>')
+ "\nint main(){ func1(); printf(\"func2=%d\\n\", func2(3,4)); return EXIT_SUCCESS; }\n")
used[44] = used[45] = True
targets_by_dir.setdefault(out_dir, []).append(("p1c03_mymodule", "extern_c_module_main.c"))
# --- 58954439:14 K&R 隐式 int 由通用路径处理(exact_fix 给出 C17 修正版)---
# --- 通用:每个代码块 = 一个独立目标(block-per-target---
# 每个 wiki 代码块都是独立的教学示例(常含同名函数/共享符号的多个变体),
# 合并会导致符号重定义;逐块成文件既保持教学隔离,又避免跨块冲突。
# ASCII target names: CMake forbids non-ASCII target names, so we use a
# per-page counter (prefix_NN). Source filenames are English (mapped via
# file_name_map.py); same section repeated → suffix _2, _3...
tgt_counter = [0]
def next_target(prefix):
tgt_counter[0] += 1
return f"{prefix}_{tgt_counter[0]:02d}"
# Map each block to its owning section title (for naming).
block_sec_title = {}
for sec in sections:
for i in sec["code_indices"]:
if i < len(blocks):
block_sec_title[i] = sec["title"]
for i in range(len(blocks)):
if used[i]:
continue
lang = lang_of(i)
sec_title = block_sec_title.get(i, "(导言)")
# Apply fixes
c0 = common_fix(blocks[i]["code"], pid, i)
c1 = exact_fix(pid, i, c0)
tgt = next_target(cfg["prefix"])
# English source filename stem from the explicit map (with fallback)
src_name = src_name_for(pid, sec_title, cfg["prefix"], i)
# ensure uniqueness within dir (same section name → _2, _3...)
cand = src_name; k = 2
existing_names = {os.path.basename(t[1]) for t in targets_by_dir.get(out_dir, [])}
base_ext = ".cpp" if lang == "cpp" else ".c"
while f"{cand}{base_ext}" in existing_names:
cand = f"{src_name}_{k}"; k += 1
src_name = cand
src_rel, _ = emit_console(tgt, [(i, c1, sec_title)], lang, out_dir, src_name=src_name)
targets_by_dir.setdefault(out_dir, []).append((tgt, src_rel))
used[i] = True
leftover = [i for i in range(len(blocks)) if not used[i]]
if leftover:
print(f"[WARN] {pid} unused blocks: {leftover}", file=sys.stderr)
# --- 写每个 dir 的 CMakeLists ---
write_part1_cmakelists(targets_by_dir)
# --- 写 ERRATA ---
write_errata()
print(f"gen_part1: wrote {sum(len(v) for v in targets_by_dir.values())} targets across {len(targets_by_dir)} dirs.")
def write_part1_cmakelists(targets_by_dir):
# per-dir CMakeLists
for d, targets in targets_by_dir.items():
lines = []
lines.append(f"# Auto-generated by tools/gen_part1.py — do not edit by hand.\n")
lines.append(f"# 章节:{d}\n")
for tgt, src_rel in targets:
src = os.path.basename(src_rel)
lang = "C" if src.endswith(".c") else "CXX"
lines.append(f"add_executable({tgt} {src})\n")
lines.append(f"set_target_properties({tgt} PROPERTIES "
f"C_STANDARD 17 C_STANDARD_REQUIRED ON C_EXTENSIONS OFF)\n")
if lang == "C":
lines.append(f"target_compile_options({tgt} PRIVATE -Wall -Wextra)\n")
else:
lines.append(f"set_target_properties({tgt} PROPERTIES "
f"CXX_STANDARD 17 CXX_STANDARD_REQUIRED ON CXX_EXTENSIONS OFF)\n")
lines.append(f"target_compile_options({tgt} PRIVATE -Wall -Wextra)\n")
# a convenience target to build all in this dir
dir_tname = "all_" + d.replace("/", "_").replace("-", "_")
lines.append(f"add_custom_target({dir_tname} DEPENDS {' '.join(t for t,_ in targets)})\n")
write(os.path.join(REPO, d, "CMakeLists.txt"), "".join(lines))
# top-level p01/CMakeLists that includes all sub-dirs
subdirs = sorted(set(d.split("/")[0] if "/" not in d else "/".join(d.split("/")[:2]) for d in targets_by_dir))
# actually include every dir that has targets
seen = set()
includes = []
for d in sorted(targets_by_dir.keys()):
# include the deepest dir that has a CMakeLists we wrote
includes.append(d)
lines = ["# Auto-generated by tools/gen_part1.py\n"]
for d in includes:
rel = os.path.relpath(d, "p01")
lines.append(f"add_subdirectory({rel})\n")
write(os.path.join(REPO, "p01", "CMakeLists.txt"), "".join(lines))
def write_errata():
lines = ["# ERRATA — 代码修正记录\n\n",
"本文件由 `tools/gen_part1.py` 自动生成,记录对 wiki 原始代码块的每一处修正。\n",
"格式:`[pageId:blockIdx]` 类型 → 原内容 → 修正后 → 依据。\n\n"]
if not ERRATA:
lines.append("(无修正)\n")
else:
for (pid, idx, kind, before, after, reason) in ERRATA:
lines.append(f"## [{pid}:{idx}] {kind}\n\n")
lines.append(f"- **原文**`{before}`\n")
lines.append(f"- **修正**`{after}`\n")
lines.append(f"- **依据**{reason}\n\n")
write(os.path.join(REPO, "docs", "ERRATA.md"), "".join(lines))
if __name__ == "__main__":
main()