#!/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()
