13b3ebbe14
把川大 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 运行通过。
911 lines
51 KiB
Python
911 lines
51 KiB
Python
#!/usr/bin/env python3
|
||
# ==============================================================================
|
||
# gen_part1.py — 生成 Part1(C/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 隐式 int:C99 起非法,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 隐式 int(int fun1(i)、g(){...})及无类型参数 fun3() 接受任意实参",
|
||
"显式 int(int 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 成员 mAge(mID 是
|
||
# 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 成员 mAge(mAge = 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()→noexcept,throw(类型...)
|
||
# →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() → noexcept;throw(类型...) → 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",
|
||
"补 noexcept(virtual 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()
|