#!/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 \n" "#include \n" "#include \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; # 补上 。 if "EXIT_SUCCESS" in code and "#include " not in code: fixed = code.replace('#include"my_module.h"', '#include"my_module.h"\n#include ') record_erratum(pid, idx, "missing-include", '"my_module.h" 引用 EXIT_SUCCESS 但未含 ', "补 #include ", "EXIT_SUCCESS 定义在 。") 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(a); 末尾误带了函数闭合花括号 }, # 导致 test01() 未闭合,test02() 被嵌套进 test01() 内 → 函数定义不允许在此。 # 把那个误入注释的 } 移出注释作为 test01() 的正常闭合。 bad = "//int b = const_cast(a); }" good = ("//int b = const_cast(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(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 myArrayInt(10); 及 for 循环、Push_back 调用等) # 直接铺在类定义之后,没有任何函数包裹 → 全局变量初始化里含 for 循环非法, # 且中文散文词非法。重写为:保留 MyArray 模板 + Person 类 + 打印函数,把测试 # 驱动语句包进 main(),使示例可编译运行并演示类模板。 fixed = ( "#include \n" "using std::string;\n\n" "template\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& 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& 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 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 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 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 \n" "#include \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 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::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 \n" "#include \n" "#include \n" ) CPP_PREAMBLE = ( "#include \n" "using namespace std;\n" ) # 便携 pause() 壳(仅当某段用到 pause() 时注入;Windows 下原意是等待用户回车) PAUSE_HELPER = ( "#include \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 _ 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 _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 "" not in stl_hdrs: stl_hdrs.append("") 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 ') + "\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()