diff --git a/compiler/include/compiler/Codec.h b/compiler/include/compiler/Codec.h index f62abc7..3a38dfc 100644 --- a/compiler/include/compiler/Codec.h +++ b/compiler/include/compiler/Codec.h @@ -18,19 +18,42 @@ namespace compiler { - // 指令字:[op:8 | rd:8 | a:8 | b:8],小端 u32 + /// 指令字:`[ op:8 | rd:8 | a:8 | b:8 ]`,小端 u32。 typedef uint32_t Instr; + /** + * @brief 打包:opcode + 三个 8 位字段拼成一条指令字。 + * @param opcode 操作码(低 8 位) + * @param rd 目标寄存器 / 条件寄存器(第 8..15 位) + * @param a 源寄存器 / 立即数低 8 位 / 偏移低 8 位(第 16..23 位) + * @param b 源寄存器 / 立即数高 8 位 / 偏移高 8 位(第 24..31 位) + * @return 打包后的指令字(小端 u32) + * @details 布局与执行器 isa 一致,保证编译器产出的字节可被执行。 + */ Instr pack(uint8_t opcode, uint8_t rd, uint8_t a, uint8_t b); + /// @brief 取操作码(低 8 位)。 uint8_t op_of(Instr w); + /// @brief 取 rd 字段(第 8..15 位)。 uint8_t rd_of(Instr w); + /// @brief 取 a 字段(第 16..23 位)。 uint8_t a_of(Instr w); + /// @brief 取 b 字段(第 24..31 位)。 uint8_t b_of(Instr w); - uint16_t imm16_of(Instr w); // a|b 拼 u16(const_id / slot / fn_id) - int16_t off16_of(Instr w); // a|b 有符号偏移(相对下一条) + /// @brief a|b 拼 16 位无符号数(const_id / slot / fn_id)。 + uint16_t imm16_of(Instr w); + /// @brief a|b 为有符号相对偏移(单位:指令条数,相对下一条指令)。 + int16_t off16_of(Instr w); - // 配置驱动反汇编:按 machine.toml 的 format 输出一行文本。 - // 未知操作码 → "??? 0x"。输出格式与 Doc/isa/指令与映像.md 一致。 + /** + * @brief 配置驱动反汇编:按 machine.toml 的 format 输出一行文本。 + * @param cfg 机器配置(opcode 名 / format / 参数名来源) + * @param w 指令字 + * @param out 输出缓冲 + * @param cap 缓冲容量(含 '\0');cap==0 时直接返回、不写 out + * @details 文本格式与 Doc/isa/指令与映像.md 一致(如 `MOVE r1, r2`、 + * `LOADK r0, 3`、`JMP +4`、`JT r0, -1`、`CALL 1`、`CAL_TON 0`、`RET`); + * 未知操作码输出 `??? 0x`。 + */ void disasm(const MachineConfig& cfg, Instr w, char* out, size_t cap); } diff --git a/compiler/include/compiler/Codegen.h b/compiler/include/compiler/Codegen.h index 1312f20..1a52a78 100644 --- a/compiler/include/compiler/Codegen.h +++ b/compiler/include/compiler/Codegen.h @@ -3,6 +3,9 @@ * @brief 寄存器码生成(12.8,切片 1:MOVE / LOADK / RET) * @author * @date 2026-08-21 + * + * @details 代码生成:输入 Project + Unit(AST)+ LinkResult,输出 .stb 映像字节。 + * 寄存器分配:r0 结果、r1..r7 参数、r8+ 变量/临时;跳转偏移相对下一条。 */ #pragma once @@ -18,9 +21,17 @@ namespace compiler { - // 编译工程为 .stb 映像字节(在链接 + 类型检查成功后调用)。 - // 指令 opcode / 类型 tag / FB 布局全部来自 machine.toml(cfg)。 - // 失败返回 false,err 前缀 "codegen error"。 + /** + * @brief 编译工程为 .stb 映像字节(在链接 + 类型检查成功后调用)。 + * @param proj 工程定义(cycle_limit / dt_ms / 哈希) + * @param units 全部源文件的 AST + * @param link 链接结果(POU 顺序 / 全局符号 / FB 布局) + * @param cfg 机器定义(machine.toml;指令 opcode / 类型 tag / FB 布局) + * @param image 输出映像字节 + * @param err 错误输出;可为 nullptr(静默) + * @return true 成功;false 失败(err 前缀 "codegen error") + * @details 指令 opcode / 类型 tag / FB 布局全部来自 machine.toml(cfg)。 + */ bool codegen_project(const Project& proj, const std::vector& units, const LinkResult& link, const MachineConfig& cfg, std::vector* image, std::string* err); diff --git a/compiler/include/compiler/Lexer.h b/compiler/include/compiler/Lexer.h index d66517f..371e085 100644 --- a/compiler/include/compiler/Lexer.h +++ b/compiler/include/compiler/Lexer.h @@ -1,6 +1,9 @@ /** * @file Lexer.h * @brief ST 子集词法分析 + * @details 本文件是词法层对外接口:定义 token 类型(Tok)、token 载体 + * (Token)与两个入口(lex / lex_file)。扫描器实现见 Lexer.cpp, + * 关键字与 token 集见 Doc/compiler/词法.md。 * @author * @date 2026-08-21 */ @@ -13,84 +16,105 @@ namespace compiler { - // token 类型。关键字与 token 集见 Doc/compiler/词法.md。 + /** + * @brief token 类型枚举。 + * @details 关键字与 token 集见 Doc/compiler/词法.md;关键字共 25 个, + * 与 12.1 冻结表一致(12.5 修订补入 THEN/DO)。顺序冻结后 + * 不得插值改动,只能追加到末尾。 + */ enum class Tok { // 关键字(25 个,与 12.1 冻结表一致) - PROGRAM, - FUNCTION, - FUNCTION_BLOCK, - END_PROGRAM, - END_FUNCTION, - END_FUNCTION_BLOCK, - VAR, - VAR_INPUT, - VAR_OUTPUT, - VAR_GLOBAL, - VAR_EXTERNAL, - END_VAR, - BOOL, - INT, - TIME, - IF, - ELSIF, - ELSE, - END_IF, - WHILE, - END_WHILE, - THEN, // 12.5 修订:IF/WHILE 语法需要,补入关键字表 - DO, // 同上 - AND, - OR, - NOT, - TRUE, - FALSE, - TON, - TOF, - TP, - CTU, - CTD, - CTUD, - R_TRIG, - F_TRIG, + PROGRAM, ///< PROGRAM 声明起始 + FUNCTION, ///< FUNCTION 声明起始 + FUNCTION_BLOCK, ///< FUNCTION_BLOCK 声明起始 + END_PROGRAM, ///< PROGRAM 结束 + END_FUNCTION, ///< FUNCTION 结束 + END_FUNCTION_BLOCK, ///< FUNCTION_BLOCK 结束 + VAR, ///< 局部变量段(VAR)起始 + VAR_INPUT, ///< 输入变量段起始 + VAR_OUTPUT, ///< 输出变量段起始 + VAR_GLOBAL, ///< 全局变量段起始 + VAR_EXTERNAL, ///< 外部变量段起始 + END_VAR, ///< 变量段结束 + BOOL, ///< 布尔类型 + INT, ///< 整数类型 + TIME, ///< 时间类型 + IF, ///< IF 关键字 + ELSIF, ///< ELSIF 关键字 + ELSE, ///< ELSE 关键字 + END_IF, ///< IF 结束 + WHILE, ///< WHILE 关键字 + END_WHILE, ///< WHILE 结束 + THEN, ///< IF/ELSIF 分支引导(12.5 修订补入关键字表) + DO, ///< WHILE 循环体引导(12.5 修订补入关键字表) + AND, ///< 逻辑与 + OR, ///< 逻辑或 + NOT, ///< 逻辑非(前缀) + TRUE, ///< 布尔真字面量 + FALSE, ///< 布尔假字面量 + TON, ///< 内建 FB 类型名 + TOF, ///< 内建 FB 类型名 + TP, ///< 内建 FB 类型名 + CTU, ///< 内建 FB 类型名 + CTD, ///< 内建 FB 类型名 + CTUD, ///< 内建 FB 类型名 + R_TRIG, ///< 内建 FB 类型名 + F_TRIG, ///< 内建 FB 类型名 // 字面量 - IDENT, // text = 小写名 - INT_LIT, // int_value - TIME_LIT, // int_value = 毫秒 + IDENT, ///< 标识符(text = 小写名) + INT_LIT, ///< 整数字面量(int_value) + TIME_LIT, ///< 时间字面量(int_value = 毫秒) // 符号 - ASSIGN, // := - EQ, // = - NE, // <> - LT, // < - LE, // <= - GT, // > - GE, // >= - PLUS, // + - MINUS, // - - STAR, // * - SLASH, // / - LPAREN, // ( - RPAREN, // ) - COMMA, // , - SEMI, // ; - COLON, // : - DOT, // . - END, // 文件结束 + ASSIGN, ///< :=(赋值) + EQ, ///< =(等于) + NE, ///< <>(不等) + LT, ///< <(小于) + LE, ///< <=(小于等于) + GT, ///< >(大于) + GE, ///< >=(大于等于) + PLUS, ///< +(加) + MINUS, ///< -(减) + STAR, ///< *(乘) + SLASH, ///< /(除) + LPAREN, ///< ((左括号) + RPAREN, ///< )(右括号) + COMMA, ///< ,(逗号) + SEMI, ///< ;(分号) + COLON, ///< :(冒号) + DOT, ///< .(点,FB 字段访问) + END, ///< 文件结束哨兵 }; + /** + * @brief 单个 token:类型 + 文本/字面量值 + 源位置。 + * @details line/col 从 1 起,记录 token 起始字符的位置。 + */ struct Token { - Tok type; - std::string text; // 标识符/关键字的小写文本;字面量原文 - int64_t int_value; // INT_LIT / TIME_LIT(毫秒) - std::string source_file; - uint32_t line; // 从 1 起 - uint32_t col; // 从 1 起 + Tok type; ///< token 类型 + std::string text; ///< 标识符/关键字的小写文本;字面量原文 + int64_t int_value; ///< INT_LIT / TIME_LIT(毫秒)的字面量值 + std::string source_file; ///< 来源文件名 + uint32_t line; ///< 起始行(从 1 起) + uint32_t col; ///< 起始列(从 1 起) }; - // 对源文本 lex 成 token 流(含末尾 END)。失败返回 false, - // err 前缀 "lex error",带文件与行列。 + /** + * @brief 对源文本做词法分析,产出 token 流(含末尾 END)。 + * @param source_file 源文件名(写入每个 token 的 source_file 并用于报错) + * @param content 源文本 + * @param out 输出 token 流 + * @param err 错误输出;可为 nullptr(静默) + * @return true 成功;false 失败(err 前缀 "lex error",带文件与行列) + */ bool lex(const std::string& source_file, const std::string& content, std::vector* out, std::string* err); - // 读文件后 lex(文件不存在 / 读取失败也算 lex error) + /** + * @brief 读取文件后做词法分析。 + * @param path 文件路径 + * @param out 输出 token 流 + * @param err 错误输出;可为 nullptr(静默) + * @return true 成功;false 失败(文件不存在 / 读取失败也算 lex error) + */ bool lex_file(const std::string& path, std::vector* out, std::string* err); } diff --git a/compiler/include/compiler/Linker.h b/compiler/include/compiler/Linker.h index 34b4976..65338f1 100644 --- a/compiler/include/compiler/Linker.h +++ b/compiler/include/compiler/Linker.h @@ -1,6 +1,13 @@ /** * @file Linker.h * @brief 符号表与链接 + * @details 本文件定义链接阶段的输入 / 输出数据结构与对外接口(实现见 Linker.cpp): + * - Symbol / FbField / FbLayout / SourceUnit / LinkResult:符号与布局数据结构 + * - link_project:链接主入口(校验 GVL、编全局槽号、解析 VAR_EXTERNAL、 + * FB 实例与字段、函数循环调用检测、io 校验),失败 err 前缀 "link error" + * - load_unit:读取并解析一个 .st 文件成 SourceUnit + * - dump_symbols:符号表文本输出(全局表 + 各 POU 局部表) + * 流程与规则详见 Doc/compiler/符号表与链接.md。 * @author * @date 2026-08-21 */ @@ -17,66 +24,106 @@ namespace compiler { - // 符号种类(与 Doc/初步计划.md 12.1 一致) + /** + * @brief 符号种类(与 Doc/初步计划.md 12.1 一致)。 + * @details 各成员含义: + * - Global:全局变量(仅 gvl 顶层,编全局槽号) + * - External:VAR_EXTERNAL 引用(接全局同一槽,类型必须一致) + * - Local:POU 内部局部变量(12.8 分配槽) + * - Input / Output:POU 输入 / 输出参数 + * - Pou:POU 登记(PROGRAM / FUNCTION / FUNCTION_BLOCK) + * - FbInstance:FB 实例(带布局,内建或用户类型) + * - Const:常量 + */ enum class SymbolKind { Global, External, Local, Input, Output, Pou, FbInstance, Const }; + /** + * @brief 一个符号(全局槽表项或 POU 局部符号)。 + */ struct Symbol { - SymbolKind kind = SymbolKind::Local; - std::string name; // 小写 - std::string type_name; // bool / int / time / fb 类型名(小写) - uint32_t address = 0; // 全局槽号(Global/External);局部暂 0(12.8 分配) - std::string source_file; - uint32_t line = 0; - uint32_t col = 0; + SymbolKind kind = SymbolKind::Local; ///< 符号种类 + std::string name; ///< 符号名(小写) + std::string type_name; ///< 类型名:bool / int / time / fb 类型名(小写) + uint32_t address = 0; ///< 全局槽号(Global/External);局部暂 0(12.8 分配) + std::string source_file; ///< 声明所在源文件 + uint32_t line = 0; ///< 声明行号 + uint32_t col = 0; ///< 声明列号 }; - // FB 类型字段(实例布局用;v1 字段类型仅 BOOL/INT/TIME) + /** + * @brief FB 类型字段(实例布局用;v1 字段类型仅 BOOL/INT/TIME)。 + */ struct FbField { - std::string name; - TypeKind type = TypeKind::Bool; + std::string name; ///< 字段名 + TypeKind type = TypeKind::Bool; ///< 字段类型(仅标量) }; + /** + * @brief 一个 FB 类型的字段布局(供实例分配与字段引用解析)。 + */ struct FbLayout { - std::string type_name; // 类型名(小写) - std::vector fields; // 段序:input → output → 内部 var + std::string type_name; ///< 类型名(小写) + std::vector fields; ///< 段序:input → output → 内部 var }; - // 一个源文件的 AST(供链接输入) + /** + * @brief 一个源文件的 AST(供链接输入)。 + */ struct SourceUnit { - std::string path; - Unit ast; + std::string path; ///< 源文件路径 + Unit ast; ///< 解析出的 AST }; - // 链接结果 + /** + * @brief 链接结果:全局槽表 + FB 类型布局 + 各 POU 作用域 + 函数表顺序。 + */ struct LinkResult { - // 全局槽表:下标即槽号(声明顺序) - std::vector globals; - std::map global_index; // 名 → 槽号 + std::vector globals; ///< 全局槽表:下标即槽号(声明顺序) + std::map global_index; ///< 名 → 槽号 - // 用户 FB 类型布局(按类型名) - std::map fb_types; + std::map fb_types; ///< 用户 FB 类型布局(按类型名) - // 每 POU 的局部符号与 FB 实例 + /** + * @brief 每 POU 的局部符号与 FB 实例。 + */ struct PouScope { - std::string name; - PouKind kind = PouKind::Program; - std::vector syms; // local/input/output/external/fb_instance - std::map fb_instances; // 实例名 → 布局 + std::string name; ///< POU 名 + PouKind kind = PouKind::Program; ///< POU 种类 + std::vector syms; ///< local/input/output/external/fb_instance + std::map fb_instances; ///< 实例名 → 布局 }; - std::vector scopes; + std::vector scopes; ///< 全部 POU 作用域 - // 函数表顺序(PROGRAM / FUNCTION / FUNCTION_BLOCK 名,收集序) - std::vector fn_order; + std::vector fn_order; ///< 函数表顺序(PROGRAM / FUNCTION / FUNCTION_BLOCK 名,收集序) }; - // 链接一个工程:校验 GVL、定全局槽、解析 VAR_EXTERNAL、FB 实例与字段、 - // 函数循环调用检测、io 校验。失败返回 false,err 前缀 "link error"。 + /** + * @brief 链接一个工程。 + * @details 校验 GVL、定全局槽、解析 VAR_EXTERNAL、FB 实例与字段、函数循环调用检测、 + * io 校验。失败返回 false,err 前缀 "link error"。 + * @param proj 工程定义(toml 解析结果) + * @param units 全部源文件的 AST(compile_files 集合逐一 load_unit 得到) + * @param out 链接结果(各字段先清空后填充) + * @param err 错误输出;可为 nullptr(静默) + * @return true 成功;false 失败(err 已写) + */ bool link_project(const Project& proj, const std::vector& units, LinkResult* out, std::string* err); - // 读取并解析一个 .st 文件成 SourceUnit + /** + * @brief 读取并解析一个 .st 文件成 SourceUnit。 + * @param path .st 文件路径 + * @param out 输出 SourceUnit(path + ast) + * @param err 错误输出;可为 nullptr(静默) + * @return true 成功;false 失败(透传 lex/syntax error) + */ bool load_unit(const std::string& path, SourceUnit* out, std::string* err); - // 符号表文本:每行 "name kind type address file"(全局表 + 各 POU 局部表) + /** + * @brief 符号表文本。 + * @details 每行 "name kind type address file"(全局表 + 各 POU 局部表)。 + * @param r 链接结果 + * @return 多行文本:全局表逐行;每个 POU 以 "[名字]" 开头后接其局部符号行 + */ std::string dump_symbols(const LinkResult& r); } diff --git a/compiler/include/compiler/MachineConfig.h b/compiler/include/compiler/MachineConfig.h index 52f7600..5bf8fce 100644 --- a/compiler/include/compiler/MachineConfig.h +++ b/compiler/include/compiler/MachineConfig.h @@ -19,74 +19,146 @@ namespace compiler { - // 内建基元(编译器内建,C 语义) + /** + * @brief 内建基元(编译器内建,C 语义)。 + * @details 元数据写在代码而非 machine.toml;配置类型的 base 必须命中本表。 + */ struct PrimType { - const char* name; - uint32_t width; // 字节 - bool is_signed; - bool is_float; + const char* name; ///< 基元名(bit / int8..uint64 / float32 / float64) + uint32_t width; ///< 宽度(字节) + bool is_signed; ///< 是否带符号 + bool is_float; ///< 是否浮点 }; - // 配置类型 = 基元别名(+ 值域约束 + .stb 契约 tag) + /** + * @brief 配置类型 = 基元别名(+ 值域约束 + .stb 契约 tag)。 + * @details 对应 machine.toml [[type]] 行:base 必为内建基元名;range 可选且 + * min ≤ max;tag 与 .stb 常量表契约一致(0=BOOL、1=INT、2=TIME,唯一)。 + */ struct ConfigType { - std::string name; - std::string base; - bool has_range = false; - int64_t range_min = 0; - int64_t range_max = 0; - uint32_t tag = 0; + std::string name; ///< 配置类型名(大写,如 BOOL/INT/TIME) + std::string base; ///< 基元名(命中内建基元表) + bool has_range = false; ///< 是否有值域约束 + int64_t range_min = 0; ///< 值域下界(含) + int64_t range_max = 0; ///< 值域上界(含) + uint32_t tag = 0; ///< .stb 常量表契约 tag }; - // 配置指令 + /** + * @brief 配置指令。 + * @details 对应 machine.toml [[op]] 行:opcode 唯一且 0..255;class 与 format + * 互锁(INSTANCE ↔ CAL);params 数量与 format 期望一致。 + */ struct ConfigOp { - std::string name; - uint32_t opcode = 0; - bool is_instance = false; // class: instance / plain - std::string format; // RR/RRR/IMM/SLOT/JMP/JC/CALL/CAL/NONE - std::vector params; - bool enabled = false; + std::string name; ///< 助记符(与 .stb / isa 一致) + uint32_t opcode = 0; ///< 操作码(0..255,表内唯一) + bool is_instance = false; ///< class:instance / plain + std::string format; ///< 操作数形态:RR/RRR/IMM/SLOT/JMP/JC/CALL/CAL/NONE + std::vector params; ///< 参数名(数量与 format 一致) + bool enabled = false; ///< 是否启用 }; + /** + * @brief 内建 FB 的一个字段。 + */ struct ConfigFbField { - std::string name; - std::string type; // 配置类型名(BOOL/INT/TIME) + std::string name; ///< 字段名(如 in/pt/q/et/cu/cv) + std::string type; ///< 配置类型名(BOOL/INT/TIME) }; + /** + * @brief 内建功能块(FB)布局登记。 + * @details 对应 machine.toml [[fb]] 行;opcode 必须命中 instance 类 op 行。 + */ struct ConfigFb { - std::string name; - uint32_t opcode = 0; - std::vector fields; + std::string name; ///< FB 名(小写,如 ton/ctu) + uint32_t opcode = 0; ///< 对应 op 行 opcode(CAL_*) + std::vector fields; ///< 字段列表(名称唯一,类型命中 type 表) }; + /** + * @brief 机器定义:加载 machine.toml 并做强校验。 + * @details 编译器指令集 / 类型的唯一事实来源;校验失败时 ok() 为 false, + * err 前缀 "machine error"。vm 不读配置(内建全指令集)。 + */ class MachineConfig { public: - // 加载 + 强校验;失败返回 false 并写 err(前缀 "machine error") + /** + * @brief 加载 + 强校验。 + * @param path machine.toml 路径 + * @param err 错误输出;可为 nullptr(静默) + * @return true 成功;false(err 已写,前缀 "machine error") + * @details 强校验项:字段必填 / type.base 命中内建基元·range 合法·tag 契约 / + * op opcode 唯一·类别-格式互锁 / fb.opcode 与 op 一致·字段类型命中 + * type 表(清单见文件头)。 + */ bool load(const std::string& path, std::string* err); + /// @brief 加载是否成功(校验通过)。 bool ok() const { return ok_; } + /// @brief 型号名(meta.name,参与 .stb 型号标识)。 const std::string& model_name() const { return model_name_; } + /// @brief 指令集版本(meta.version,参与 .stb 型号标识)。 uint32_t version() const { return version_; } + /// @brief 配置类型表([[type]])。 const std::vector& types() const { return types_; } + /// @brief 配置指令表([[op]])。 const std::vector& ops() const { return ops_; } + /// @brief 内建 FB 表([[fb]])。 const std::vector& fbs() const { return fbs_; } + /** + * @brief 按配置类型名查类型行。 + * @param name 配置类型名 + * @return 命中指针;未找到返回 nullptr + */ const ConfigType* find_type(const std::string& name) const; + + /** + * @brief 按助记符查指令行。 + * @param name 助记符(如 "MOVE") + * @return 命中指针;未找到返回 nullptr + */ const ConfigOp* find_op(const std::string& name) const; + + /** + * @brief 按 opcode 查指令行。 + * @param opcode 操作码(0..255) + * @return 命中指针;未找到返回 nullptr + */ const ConfigOp* find_op_by_code(uint32_t opcode) const; + + /** + * @brief 查询某 opcode 是否启用。 + * @param opcode 操作码 + * @return 指令存在且 enabled 为 true;未知 opcode 返回 false + */ bool op_enabled(uint32_t opcode) const; + + /** + * @brief 按名称查内建 FB。 + * @param name FB 名(小写,如 "ton") + * @return 命中指针;未找到返回 nullptr + */ const ConfigFb* find_fb(const std::string& name) const; - // 内建基元表 + /// @brief 内建基元表(bit / int8..uint64 / float32 / float64)。 static const std::vector& prims(); + + /** + * @brief 按名称查内建基元。 + * @param name 基元名 + * @return 命中指针;未找到返回 nullptr + */ static const PrimType* find_prim(const std::string& name); private: - bool ok_ = false; - std::string model_name_; - uint32_t version_ = 0; - std::vector types_; - std::vector ops_; - std::vector fbs_; + bool ok_ = false; ///< 加载成功标志 + std::string model_name_; ///< 型号名(meta.name) + uint32_t version_ = 0; ///< 指令集版本(meta.version) + std::vector types_; ///< 配置类型表 + std::vector ops_; ///< 配置指令表 + std::vector fbs_; ///< 内建 FB 表 }; } diff --git a/compiler/include/compiler/Parser.h b/compiler/include/compiler/Parser.h index 7295b86..24b7d8c 100644 --- a/compiler/include/compiler/Parser.h +++ b/compiler/include/compiler/Parser.h @@ -1,6 +1,9 @@ /** * @file Parser.h * @brief ST 子集递归下降语法分析(AST 定义 + 入口) + * @details 本文件定义语法层全部公开类型(AST 节点与相关枚举)与两个 + * 解析入口(parse_pous / parse_pous_file)。解析器实现见 + * Parser.cpp,文法详见 Doc/compiler/语法.md。 * @author * @date 2026-08-21 */ @@ -16,118 +19,196 @@ namespace compiler { // ---- 类型引用 ---- + /** + * @brief 类型种类。 + * @details 枚举值:Bool=内建 BOOL;Int=内建 INT;Time=内建 TIME; + * FbBuiltin=内建 FB 类型(TON/TOF/TP/CTU/CTD/CTUD/R_TRIG/F_TRIG); + * FbUser=用户 FB 类型(存在性由 12.6 链接层校验)。 + */ enum class TypeKind { Bool, Int, Time, FbBuiltin, FbUser }; + /** + * @brief 类型引用:种类 + (用户 FB 的)类型名。 + */ struct TypeRef { - TypeKind kind = TypeKind::Bool; - std::string name; // FbUser 时为用户 FB 类型名(小写) + TypeKind kind = TypeKind::Bool; ///< 类型种类 + std::string name; ///< FbUser 时为用户 FB 类型名(小写) }; // ---- 变量段 ---- + /** + * @brief 单个变量声明。 + * @details 初值可选(v1 仅字面量);line/col 记录声明处位置(报错用)。 + */ struct VarDecl { - std::string name; - TypeRef type; - bool has_init = false; // 可选初值(v1 仅字面量) - int64_t init_value = 0; // BOOL 0/1、INT 值、TIME 毫秒 - uint32_t line = 0; - uint32_t col = 0; + std::string name; ///< 变量名(小写) + TypeRef type; ///< 类型引用 + bool has_init = false; ///< 是否有可选初值(v1 仅字面量) + int64_t init_value = 0; ///< BOOL 0/1、INT 值、TIME 毫秒 + uint32_t line = 0; ///< 声明起始行 + uint32_t col = 0; ///< 声明起始列 }; + /** + * @brief 变量段种类。 + * @details 枚举值:Local=VAR、Input=VAR_INPUT、Output=VAR_OUTPUT、 + * Global=VAR_GLOBAL、External=VAR_EXTERNAL。 + */ enum class VarSection { Local, Input, Output, Global, External }; + /** + * @brief 一段 VAR_* … END_VAR:段种类 + 声明列表。 + */ struct VarBlock { - VarSection section = VarSection::Local; - std::vector vars; + VarSection section = VarSection::Local; ///< 段种类 + std::vector vars; ///< 段内声明(按出现顺序) }; // ---- 表达式 ---- + /** + * @brief 表达式节点种类。 + * @details 二目节点(And/Or/Cmp/Add/Sub/Mul/Div)用 lhs/rhs/op; + * 一元节点(Not/Neg)用 operand;AND/OR 的短路语义保留在 + * AST 中,由 12.8 编译为跳转。 + */ enum class ExprKind { - LitBool, // int_value 0/1 - LitInt, // int_value - LitTime, // int_value 毫秒 - VarRef, // name - Field, // name.field - Not, // operand - And, Or, // lhs, rhs(短路语义保留到 12.8) - Cmp, Add, Sub, Mul, Div, // lhs, rhs,op - Neg, // operand(一元负号) - Call, // name + args(函数调用,有返回值) + LitBool, ///< 布尔字面量(int_value 0/1) + LitInt, ///< 整数字面量(int_value) + LitTime, ///< 时间字面量(int_value 毫秒) + VarRef, ///< 变量引用(name) + Field, ///< FB 字段访问(name.field) + Not, ///< 逻辑非(operand) + And, Or, ///< 逻辑与 / 逻辑或(lhs, rhs;短路语义保留到 12.8) + Cmp, Add, Sub, Mul, Div, ///< 二目:比较 / 加减 / 乘除(lhs, rhs,op 为运算符) + Neg, ///< 一元负号(operand) + Call, ///< 函数调用(name + args,有返回值) }; + /** + * @brief 二目运算符种类。 + * @details 枚举值:Eq/Ne/Lt/Le/Gt/Ge=比较符(= <> < <= > >=); + * Add/Sub/Mul/Div=算术符(+ - * /)。 + */ enum class BinOp { Eq, Ne, Lt, Le, Gt, Ge, Add, Sub, Mul, Div }; + /** + * @brief 表达式 AST 节点(带 kind 标签的联合式结构)。 + * @details 按 kind 解释字段:字面量用 int_value;VarRef/Field/Call 用 + * name(+field、args);二目用 lhs/rhs/op;一元 Not/Neg 用 + * operand。 + */ struct Expr { - ExprKind kind = ExprKind::LitBool; - BinOp op = BinOp::Eq; // 二目运算 - std::string name; // VarRef / Field 对象 / Call 函数名 - std::string field; // Field 字段名 - int64_t int_value = 0; // 字面量值 - std::unique_ptr lhs; - std::unique_ptr rhs; - std::unique_ptr operand; // Not / Neg - std::vector> args; // Call 实参 + ExprKind kind = ExprKind::LitBool; ///< 节点种类 + BinOp op = BinOp::Eq; ///< 二目运算(Cmp/Add/Sub/Mul/Div 用) + std::string name; ///< VarRef / Field 对象 / Call 函数名 + std::string field; ///< Field 字段名 + int64_t int_value = 0; ///< 字面量值(BOOL 0/1、INT、TIME 毫秒) + std::unique_ptr lhs; ///< 二目左操作数 + std::unique_ptr rhs; ///< 二目右操作数 + std::unique_ptr operand; ///< Not / Neg 的操作数 + std::vector> args; ///< Call 实参 }; // ---- 语句 ---- + /** + * @brief 语句节点种类。 + * @details 枚举值:Assign=赋值;If=IF 语句(含 ELSIF/ELSE); + * While=WHILE 语句;FbCall=FB 调用语句(无返回值)。 + */ enum class StmtKind { Assign, If, While, FbCall }; - struct Stmt; // 前置声明(IfBranch / Stmt 相互引用) + /** @brief 语句节点(前置声明;Stmt 与 IfBranch 相互引用)。 */ + struct Stmt; + /** + * @brief FB 调用实参:命名参数(名 := 表达式)。 + */ struct FbArg { - std::string name; - std::unique_ptr value; + std::string name; ///< 形参名(小写) + std::unique_ptr value; ///< 实参表达式 }; + /** + * @brief IF 语句的一个 ELSIF 分支:条件 + 分支体。 + */ struct IfBranch { - std::unique_ptr cond; - std::vector body; + std::unique_ptr cond; ///< 分支条件 + std::vector body; ///< 分支体(条件成立时执行) }; - // 前置声明:Stmt 内部自引用 + /** + * @brief 语句 AST 节点:按 kind 解释各字段组。 + * @details Assign 用 target(+field);If/While 用 cond/body + * (+elsifs、else_body);FbCall 用 instance/args。 + * fb.field 左值 v1 禁止赋值,在语法层拒绝。 + */ struct Stmt { - StmtKind kind = StmtKind::Assign; + StmtKind kind = StmtKind::Assign; ///< 语句种类 // Assign - std::string target; // 左值标识符 - bool target_is_field = false; // 左值 fb.field(v1 禁止赋值,语法层拒绝) - std::string field; - std::unique_ptr value; + std::string target; ///< 左值标识符 + bool target_is_field = false; ///< 左值 fb.field(v1 禁止赋值,语法层拒绝) + std::string field; ///< 左值字段名 + std::unique_ptr value; ///< 右值表达式 // If / While - std::unique_ptr cond; - std::vector body; - std::vector elsifs; - std::vector else_body; + std::unique_ptr cond; ///< 条件表达式 + std::vector body; ///< 主分支体 + std::vector elsifs; ///< ELSIF 分支列表 + std::vector else_body; ///< ELSE 分支体 // FbCall - std::string instance; - std::vector args; + std::string instance; ///< FB 实例名 + std::vector args; ///< 命名实参列表 }; // ---- POU ---- + /** + * @brief POU 种类。 + * @details 枚举值:Program=PROGRAM;Function=FUNCTION(可有返回类型); + * FunctionBlock=FUNCTION_BLOCK。 + */ enum class PouKind { Program, Function, FunctionBlock }; + /** + * @brief 程序组织单元(POU):外壳 + 变量段 + 语句体。 + */ struct POU { - PouKind kind = PouKind::Program; - std::string name; - TypeRef result_type; // FUNCTION 才有 - std::vector blocks; - std::vector body; - std::string source_file; + PouKind kind = PouKind::Program; ///< POU 种类 + std::string name; ///< POU 名(小写) + TypeRef result_type; ///< 返回类型(仅 FUNCTION 才有) + std::vector blocks; ///< 变量段(按声明顺序) + std::vector body; ///< 语句体 + std::string source_file; ///< 来源文件名 }; - // 一个 .st 文件的解析结果:GVL 文件形态(无 POU)时只有 globals 非空 + /** + * @brief 一个 .st 文件的解析结果。 + * @details GVL 文件形态(无 POU)时只有 globals 非空。 + */ struct Unit { - std::vector globals; // 顶层 VAR_GLOBAL 段(仅 GVL 文件) - std::vector pous; + std::vector globals; ///< 顶层 VAR_GLOBAL 段(仅 GVL 文件) + std::vector pous; ///< POU 列表(按出现顺序) }; - // 解析一个 .st 文件的全部内容(顶层 VAR_GLOBAL 段或 POU)。 - // 失败返回 false,err 前缀 "syntax error",带文件与行列。 + /** + * @brief 解析一个 .st 文件的全部内容(顶层 VAR_GLOBAL 段或 POU)。 + * @param source_file 源文件名(报错用) + * @param content 源文本 + * @param out 输出 Unit(globals / pous) + * @param err 错误输出;可为 nullptr(静默) + * @return true 成功;false 失败(err 前缀 "syntax error",带文件与行列) + */ bool parse_pous(const std::string& source_file, const std::string& content, Unit* out, std::string* err); - // 读文件后解析 + /** + * @brief 读取文件后解析。 + * @param path 文件路径 + * @param out 输出 Unit(globals / pous) + * @param err 错误输出;可为 nullptr(静默) + * @return true 成功;false 失败(文件打不开也算 syntax error) + */ bool parse_pous_file(const std::string& path, Unit* out, std::string* err); } diff --git a/compiler/include/compiler/Project.h b/compiler/include/compiler/Project.h index 81bbe4e..fe6882e 100644 --- a/compiler/include/compiler/Project.h +++ b/compiler/include/compiler/Project.h @@ -1,6 +1,13 @@ /** * @file Project.h * @brief 工程定义与 project.toml 解析(schema 校验) + * @details 本文件定义工程数据结构与解析接口(实现见 Project.cpp): + * - IoBinding / Project:project.toml 的解析结果(I/O 绑定不创造变量, + * slot 由链接结果解析) + * - parse_project:解析并校验 project.toml,错误带稳定类别前缀 + 行号 + * - compile_files:编译文件集合(files.st ∪ gvl.file,去重) + * - compute_project_hash:校验文件存在并计算工程哈希,缺文件前缀 "file missing" + * 字段约定与 Doc/初步计划.md 12.1 的 toml 字段表一一对应。 * @author * @date 2026-08-21 */ @@ -13,37 +20,61 @@ namespace compiler { - // I/O 绑定(project.toml [[io.*]];不创造变量,slot 由链接结果解析) + /** + * @brief I/O 绑定(project.toml [[io.*]];不创造变量,slot 由链接结果解析)。 + */ struct IoBinding { - std::string var; - uint32_t slot = 0; - uint32_t channel = 0; - uint32_t bit = 0; - bool is_input = false; // true = [[io.input]],false = [[io.output]] + std::string var; ///< 绑定的全局变量名(须已在 GVL 声明) + uint32_t slot = 0; ///< 全局槽号(12.6 链接阶段解析,此处填 0) + uint32_t channel = 0; ///< 通道号 + uint32_t bit = 0; ///< 位号 + bool is_input = false; ///< true = [[io.input]],false = [[io.output]] }; - // 与 Doc/初步计划.md 12.1 的 toml 字段表一一对应。 - // 字段白名单 / 必填 / io 完整性在 parse_project 内校验。 + /** + * @brief 工程定义。 + * @details 与 Doc/初步计划.md 12.1 的 toml 字段表一一对应; + * 字段白名单 / 必填 / io 完整性在 parse_project 内校验。 + */ struct Project { - std::string name; // [project] 必填 - std::string entry; // 必填,第一版必须 "program MAIN" - uint32_t cycle_limit = 0; // 必填 > 0 - uint32_t dt_ms = 0; // 必填 > 0 - std::vector files_st; // [files] 必填非空 - std::string gvl_file; // [gvl] 可选;无则空串 - std::vector io; // [[io.*]] 可选;slot 12.6 才解析,此处填 0 - std::string base_dir; // toml 所在目录(解析相对路径用) + std::string name; ///< [project] 必填 + std::string entry; ///< 必填,第一版必须 "program MAIN" + uint32_t cycle_limit = 0; ///< 必填 > 0 + uint32_t dt_ms = 0; ///< 必填 > 0 + std::vector files_st; ///< [files] 必填非空 + std::string gvl_file; ///< [gvl] 可选;无则空串 + std::vector io; ///< [[io.*]] 可选;slot 12.6 才解析,此处填 0 + std::string base_dir; ///< toml 所在目录(解析相对路径用) }; - // 解析并校验 project.toml。成功返回 true;失败返回 false 并写 err, - // err 带稳定类别前缀(unknown key / missing field / invalid value / bad entry)+ 行号。 + /** + * @brief 解析并校验 project.toml。 + * @details 成功返回 true;失败返回 false 并写 err,err 带稳定类别前缀 + * (parse error / unknown key / missing field / invalid value / bad entry) + * + 行号。 + * @param toml_path project.toml 路径 + * @param out 输出 Project(base_dir 先填,其余字段由各段解析填充) + * @param err 错误输出;可为 nullptr(静默) + * @return true 成功;false 失败(err 已写) + */ bool parse_project(const std::string& toml_path, Project* out, std::string* err); - // 编译文件集合:files.st ∪ gvl.file,去重(gvl 已在 files.st 则跳过)。 - // 保持 files.st 顺序,gvl 追加在后;相对路径以 base_dir 为基准解析。 + /** + * @brief 编译文件集合:files.st ∪ gvl.file,去重(gvl 已在 files.st 则跳过)。 + * @details 保持 files.st 顺序,gvl 追加在后;相对路径以 base_dir 为基准解析。 + * @param p 工程定义 + * @return 编译文件路径集合 + */ std::vector compile_files(const Project& p); - // 校验全部文件存在并计算工程哈希:集合按路径排序,对内容做 FNV-1a 64 增量; - // 空集合 = basis(Stb::kFnvBasis)。缺文件报错前缀 "file missing"。 + /** + * @brief 校验全部文件存在并计算工程哈希。 + * @details 集合按路径排序,对内容做 FNV-1a 64 增量;空集合 = basis(Stb::kFnvBasis)。 + * 缺文件报错前缀 "file missing"。 + * @param p 工程定义 + * @param hash 输出工程哈希 + * @param err 错误输出;可为 nullptr(静默) + * @return true 成功;false 文件缺失(err 已写) + */ bool compute_project_hash(const Project& p, uint64_t* hash, std::string* err); } diff --git a/compiler/include/compiler/Stb.h b/compiler/include/compiler/Stb.h index 6ee618f..0143493 100644 --- a/compiler/include/compiler/Stb.h +++ b/compiler/include/compiler/Stb.h @@ -5,7 +5,8 @@ * @date 2026-08-21 * * @details 执行器(vm)各有自己的读写实现;格式契约见 Doc/isa/指令与映像.md。 - * 12.13 修订(型号标识[32] 头 104 字节 + SHA-256 文件尾)在后续步骤落地。 + * 12.13 修订:头 104 = 原 72 + 型号标识[32] @72,文件尾 SHA-256[32]。 + * 常量表 tag 契约:0=BOOL、1=INT、2=TIME;工程哈希用 FNV-1a 64。 */ #pragma once @@ -20,97 +21,186 @@ namespace compiler { // ---- .stb 格式常量(契约;12.13 修订:头 104 = 原 72 + 型号标识[32],文件尾 SHA-256[32])---- - static const uint32_t kMagic = 0x43545353u; // "STSC" 小端 + /// 映像魔数:"STSC" 小端。 + static const uint32_t kMagic = 0x43545353u; + /// 映像格式版本。 static const uint32_t kVersion = 1; + /// 映像头字节数(原 72 字段 + 型号标识[32] @72)。 static const size_t kHeaderSize = 104; + /// 常量表一行字节数(tag:4 + value:8)。 static const size_t kConstEntrySize = 12; + /// 函数表一行字节数(nregs / code_offset / code_len 各 4)。 static const size_t kFuncRowSize = 12; + /// SHA-256 摘要长度(文件尾)。 static const size_t kSha256Size = 32; + /// 型号标识长度(头内 @72,不足补 '\0')。 static const size_t kModelIdSize = 32; + /// FNV-1a 64 哈希基值。 static const uint64_t kFnvBasis = 0xcbf29ce484222325ull; + /// FNV-1a 64 哈希素数。 static const uint64_t kFnvPrime = 0x100000001b3ull; - // 常量表一行(tag:0=BOOL、1=INT、2=TIME,契约) + /** + * @brief 常量表一行。 + * @details tag 契约:0=BOOL、1=INT、2=TIME。 + */ struct ConstEntry { - uint32_t tag = 0; - uint64_t value = 0; + uint32_t tag = 0; ///< 类型 tag(0=BOOL、1=INT、2=TIME) + uint64_t value = 0; ///< 常量值 }; - // FNV-1a 64(工程哈希,非密码学) + /** + * @brief FNV-1a 64 增量哈希更新(工程哈希,非密码学)。 + * @param h 当前哈希值(首轮传 kFnvBasis) + * @param data 输入字节 + * @param len 字节数 + * @return 更新后的哈希值 + */ uint64_t fnv1a64_update(uint64_t h, const uint8_t* data, size_t len); + /** + * @brief FNV-1a 64 一次性哈希(工程哈希)。 + * @param data 输入字节 + * @param len 字节数 + * @return 哈希值 + */ uint64_t fnv1a64(const uint8_t* data, size_t len); - // SHA-256(文件完整性校验,非密码学用途之外的完整性) + /** + * @brief 一次性 SHA-256(文件完整性校验)。 + * @param data 输入字节 + * @param len 字节数 + * @param out 32 字节摘要输出(大端) + */ void sha256(const uint8_t* data, size_t len, uint8_t out[kSha256Size]); - // 型号标识:name + version 拼 32 字节 ASCII(如 "STATOR1"),不足补 '\0',超长截断 + /** + * @brief 型号标识:name + version 拼 32 字节 ASCII(如 "STATOR1")。 + * @param name 型号名 + * @param version 版本号 + * @param out 32 字节输出缓冲 + * @details 不足补 '\0',超长截断。 + */ void fill_model_id(const std::string& name, uint32_t version, char out[kModelIdSize]); - // 只读视图(--disasm 用;校验魔数/版本/段边界) + /** + * @brief .stb 映像只读视图(--disasm 用)。 + * @details from() 时校验魔数/版本/段边界,之后各访问器只读; + * 失败时 ok() == false,error() 取原因。 + */ class StbView { public: + /// @brief 函数表一行(nregs / code_offset / code_len)。 struct FuncRow { - uint32_t nregs = 0; - uint32_t code_offset = 0; - uint32_t code_len = 0; + uint32_t nregs = 0; ///< 帧寄存器数(含变量与临时) + uint32_t code_offset = 0; ///< 字节码段内偏移(4 字节对齐) + uint32_t code_len = 0; ///< 指令条数 }; + /// 从原始字节构造(不拷贝,调用方保证生命周期)。 static StbView from(const uint8_t* buf, size_t len); + /// 从 vector 构造(转发 from(buf.data(), buf.size()))。 static StbView from(const std::vector& buf); + /// 解析是否成功。 bool ok() const { return ok_; } + /// 失败原因(成功时为空串)。 const std::string& error() const { return err_; } + /// 周期上限(头 @8)。 uint32_t cycle_limit() const { return cycle_limit_; } + /// 扫描周期 dt_ms(头 @12)。 uint32_t dt_ms() const { return dt_ms_; } + /// 工程哈希(FNV-1a 64,头 @16)。 uint64_t project_hash() const { return project_hash_; } + /// 入口函数 fn_id(头 @24)。 uint32_t entry_fn_id() const { return entry_fn_id_; } + /// 全局槽数(头 @28)。 uint32_t n_globals() const { return n_globals_; } + /// 常量表行数(头 @44)。 uint32_t n_consts() const { return n_consts_; } + /// 函数表行数(头 @48)。 uint32_t n_funcs() const { return n_funcs_; } + /// 字节码段偏移(头 @60)。 uint32_t offset_code() const { return offset_code_; } + /// 数据段(FB 区)偏移(头 @64;即字节码段终点)。 uint32_t offset_fb() const { return offset_fb_; } + /// 数据段偏移(头 @68)。 uint32_t offset_data() const { return offset_data_; } + /// 读常量表一行;越界时返回全 0。 ConstEntry const_entry(size_t i) const; + /// 读函数表一行;越界时返回全 0。 FuncRow func_row(size_t i) const; + /// 字节码段起点。 const uint8_t* code_bytes() const; + /// 字节码段字节数。 size_t code_len() const; + /// 数据段起点。 const uint8_t* data_bytes() const; + /// 数据段字节数(不含文件尾 SHA-256)。 size_t data_len() const; - // 12.13:型号标识与 SHA-256 校验 + /// 型号标识字符串(头 72..103,截断到首个 '\0')。 std::string model_id() const; // 头 72..103(补 '\0' 后字符串) + /// 型号标识是否匹配 name + version。 bool model_matches(const std::string& name, uint32_t version) const; + /// 文件尾 32 字节 SHA-256 校验(对文件尾之前全部内容重算)。 bool sha_ok() const; // 文件尾 32 字节 SHA-256 校验 private: + /// 私有构造(只能经 from() 创建)。 StbView() : buf_(0), len_(0) {} + /// 常量表偏移(from 已校验的段起点)。 uint32_t offs_of_const() const; // offset_const(from 已校验段起点) - const uint8_t* buf_; - size_t len_; - bool ok_ = false; - std::string err_; - uint32_t cycle_limit_ = 0; - uint32_t dt_ms_ = 0; - uint64_t project_hash_ = 0; - uint32_t entry_fn_id_ = 0; - uint32_t n_globals_ = 0; - uint32_t n_consts_ = 0; - uint32_t n_funcs_ = 0; - uint32_t offset_code_ = 0; - uint32_t offset_fb_ = 0; - uint32_t offset_data_ = 0; + const uint8_t* buf_; ///< 映像缓冲(不拥有) + size_t len_; ///< 缓冲字节数 + bool ok_ = false; ///< 解析成功标志 + std::string err_; ///< 失败原因 + uint32_t cycle_limit_ = 0; ///< 周期上限 + uint32_t dt_ms_ = 0; ///< 扫描周期 + uint64_t project_hash_ = 0; ///< 工程哈希 + uint32_t entry_fn_id_ = 0; ///< 入口函数 fn_id + uint32_t n_globals_ = 0; ///< 全局槽数 + uint32_t n_consts_ = 0; ///< 常量表行数 + uint32_t n_funcs_ = 0; ///< 函数表行数 + uint32_t offset_code_ = 0; ///< 字节码段偏移 + uint32_t offset_fb_ = 0; ///< 数据段(FB 区)偏移 + uint32_t offset_data_ = 0; ///< 数据段偏移 }; - // 文件读写(纯字节;校验交给 StbView) + /** + * @brief 读整个文件为字节(纯字节;校验交给 StbView)。 + * @param path 文件路径 + * @param out 输出字节 + * @param err 错误输出;可为 nullptr(静默) + * @return true 成功;false(err "cannot open for read: ") + */ bool read_stb_file(const char* path, std::vector* out, std::string* err); + /** + * @brief 写字节为文件(纯字节)。 + * @param path 文件路径 + * @param img 映像字节 + * @param err 错误输出;可为 nullptr(静默) + * @return true 成功;false(err "cannot open for write: " / "write failed: ") + */ bool write_stb_file(const char* path, const std::vector& img, std::string* err); - // sidecar 生成与写文件(I/O 绑定 var → 槽号 → channel/bit) + /** + * @brief 生成 sidecar 文本(TOML)。 + * @param bindings I/O 绑定列表 + * @return TOML 文本(每个绑定一节 [[io.input]] / [[io.output]]) + * @details I/O 绑定 var → 槽号 → channel/bit(不进映像,执行器采样用)。 + */ std::string make_sidecar(const std::vector& bindings); + /** + * @brief 生成并写 sidecar 文件。 + * @param path 文件路径 + * @param bindings I/O 绑定列表 + * @param err 错误输出;可为 nullptr(静默) + * @return true 成功;false(err "cannot open for write: " / "write failed: ") + */ bool write_sidecar_file(const char* path, const std::vector& bindings, std::string* err); } diff --git a/compiler/include/compiler/TypeInfo.h b/compiler/include/compiler/TypeInfo.h index 45157a1..f71d912 100644 --- a/compiler/include/compiler/TypeInfo.h +++ b/compiler/include/compiler/TypeInfo.h @@ -18,24 +18,45 @@ namespace compiler { - // 语言类型元数据 + /** + * @brief 语言类型元数据。 + * @details 由配置类型行 + 内建基元合成;config 或 prim 未命中时 + * 对应访问器取安全默认值,operator bool 为 false。 + */ struct TypeMeta { - const ConfigType* config = nullptr; // 配置类型行(别名 + range + tag) - const PrimType* prim = nullptr; // 内建基元(宽度/符号/浮点) + const ConfigType* config = nullptr; ///< 配置类型行(别名 + range + tag) + const PrimType* prim = nullptr; ///< 内建基元(宽度/符号/浮点) + /// @brief 宽度(字节);无基元返回 0。 uint32_t width() const { return prim ? prim->width : 0; } + /// @brief 是否带符号;无基元返回 false。 bool is_signed() const { return prim ? prim->is_signed : false; } + /// @brief 是否浮点;无基元返回 false。 bool is_float() const { return prim ? prim->is_float : false; } + /// @brief .stb 契约 tag;无配置返回 0。 uint32_t tag() const { return config ? config->tag : 0; } + /// @brief 是否有值域约束;无配置返回 false。 bool has_range() const { return config ? config->has_range : false; } + /** + * @brief 值是否落在配置值域内。 + * @param v 待查值 + * @return 无配置或无 range 时为 true;否则 min ≤ v ≤ max + */ bool value_in_range(int64_t v) const { return !config || !config->has_range || (v >= config->range_min && v <= config->range_max); } + /// @brief 是否有效(config 与 prim 都已命中)。 explicit operator bool() const { return config != nullptr && prim != nullptr; } }; - // 按语言类型名查元数据(大小写不敏感;未找到返回空 TypeMeta) + /** + * @brief 按语言类型名查元数据。 + * @param cfg 机器配置(类型表来源) + * @param name 语言类型名 + * @return 对应 TypeMeta;未找到返回空 TypeMeta(operator bool 为 false) + * @details 查询大小写不敏感(配置名大写,Linker type_name 小写)。 + */ TypeMeta type_meta(const MachineConfig& cfg, const std::string& name); } diff --git a/compiler/include/compiler/Typecheck.h b/compiler/include/compiler/Typecheck.h index 0f40ce9..c8dfca4 100644 --- a/compiler/include/compiler/Typecheck.h +++ b/compiler/include/compiler/Typecheck.h @@ -1,6 +1,10 @@ /** * @file Typecheck.h * @brief 类型检查 + * @details 本文件定义类型检查的求值类型与对外入口(实现见 Typecheck.cpp): + * - TType:表达式求值类型(v1 三型,无隐式宽化) + * - check_project:对工程做类型检查(在链接成功后调用),失败 err 前缀 "type error" + * 规则详见 Doc/compiler/类型检查.md。 * @author * @date 2026-08-21 */ @@ -16,11 +20,24 @@ namespace compiler { - // 表达式求值类型(v1 三型,无隐式宽化) + /** + * @brief 表达式求值类型(v1 三型,无隐式宽化)。 + * @details 各成员含义: + * - Bool:布尔量(逻辑运算 / 条件表达式) + * - Int:整数(算术运算) + * - Time:时间量(仅比较,无算术);INT 与 TIME 不混用 + */ enum class TType { Bool, Int, Time }; - // 对工程做类型检查(在链接成功后调用)。 - // 规则见 Doc/compiler/类型检查.md。失败返回 false,err 前缀 "type error"。 + /** + * @brief 对工程做类型检查(在链接成功后调用)。 + * @details 规则见 Doc/compiler/类型检查.md。失败返回 false,err 前缀 "type error"。 + * @param proj 工程定义 + * @param units 全部源文件的 AST + * @param link 链接结果(符号 / 布局已解析,只读) + * @param err 错误输出;可为 nullptr(静默) + * @return true 全部通过;false 失败(err 已写) + */ bool check_project(const Project& proj, const std::vector& units, const LinkResult& link, std::string* err); } diff --git a/compiler/src/Codec.cpp b/compiler/src/Codec.cpp index 0947b4c..96f7c65 100644 --- a/compiler/src/Codec.cpp +++ b/compiler/src/Codec.cpp @@ -3,6 +3,9 @@ * @brief 指令字编解码 + 配置驱动反汇编(编译器侧) * @author * @date 2026-08-21 + * + * @details pack / op_of / imm16_of / off16_of 与执行器 isa 字节布局一致; + * disasm 由 MachineConfig 驱动(opcode 名 / format / 参数名来自配置)。 */ #include "compiler/Codec.h" @@ -11,6 +14,14 @@ namespace compiler { +/** + * @brief 打包:四个字段拼成一条指令字。 + * @param opcode 操作码(低 8 位) + * @param rd 目标寄存器 / 条件寄存器(第 8..15 位) + * @param a 源寄存器 / 立即数低 8 位 / 偏移低 8 位(第 16..23 位) + * @param b 源寄存器 / 立即数高 8 位 / 偏移高 8 位(第 24..31 位) + * @return 打包后的指令字(小端 u32) + */ Instr pack(uint8_t opcode, uint8_t rd, uint8_t a, uint8_t b) { return static_cast(opcode) | (static_cast(rd) << 8) @@ -18,17 +29,40 @@ Instr pack(uint8_t opcode, uint8_t rd, uint8_t a, uint8_t b) { | (static_cast(b) << 24); } +/// @brief 取操作码(低 8 位)。 uint8_t op_of(Instr w) { return static_cast(w & 0xFFu); } +/// @brief 取 rd 字段(第 8..15 位)。 uint8_t rd_of(Instr w) { return static_cast((w >> 8) & 0xFFu); } +/// @brief 取 a 字段(第 16..23 位)。 uint8_t a_of(Instr w) { return static_cast((w >> 16) & 0xFFu); } +/// @brief 取 b 字段(第 24..31 位)。 uint8_t b_of(Instr w) { return static_cast((w >> 24) & 0xFFu); } +/** + * @brief a|b 拼成 16 位无符号数。 + * @param w 指令字 + * @return 小端拼出的 16 位值(const_id / slot / fn_id) + */ uint16_t imm16_of(Instr w) { return static_cast(a_of(w) | (static_cast(b_of(w)) << 8)); } +/** + * @brief a|b 为有符号相对偏移。 + * @param w 指令字 + * @return 偏移量(单位:指令条数,相对下一条指令) + */ int16_t off16_of(Instr w) { return static_cast(imm16_of(w)); } +/** + * @brief 配置驱动反汇编:按 machine.toml 的 format 输出一行文本。 + * @param cfg 机器配置(opcode 名 / format / 参数名来源) + * @param w 指令字 + * @param out 输出缓冲 + * @param cap 缓冲容量(含 '\0');cap==0 时直接返回、不写 out + * @details 未知操作码输出 `??? 0x%08x`;format 分支 RR/RRR/IMM/SLOT/JMP/JC/CALL/CAL/NONE + * 输出文本与 Doc/isa/指令与映像.md 一致。 + */ void disasm(const MachineConfig& cfg, Instr w, char* out, size_t cap) { if (cap == 0) { return; diff --git a/compiler/src/Codegen.cpp b/compiler/src/Codegen.cpp index a3b6584..721a6d2 100644 --- a/compiler/src/Codegen.cpp +++ b/compiler/src/Codegen.cpp @@ -48,7 +48,11 @@ namespace compiler { namespace { /** - * @brief 代码生成器 + * @brief 代码生成器(寄存器码)。 + * @details 流程:布局数据区 → 布局 FB 实例 → 逐 POU 建函数 → 拼映像。 + * 寄存器分配:r0 结果、r1..r7 参数(调用约定区,输入只读)、r8+ 变量/临时; + * 跳转偏移相对下一条(目标 = 当前 + 1 + off)。失败统一经 fail() 写 + * err(前缀 "codegen error")。 */ class Builder { public: @@ -107,6 +111,11 @@ namespace { } private: + /** + * @brief 记录错误并返回失败。 + * @param msg 错误消息(自动加前缀 "codegen error: ") + * @return 恒 false(便于 return fail(...) 连写) + */ bool fail(const std::string& msg) { if (err_) { *err_ = "codegen error: " + msg; @@ -125,32 +134,45 @@ namespace { } // ---- 指令发射(配置 opcode)---- + /// 双寄存器指令(rd, rs)。 Instr E_rr(const char* name, uint8_t rd, uint8_t rs) { return pack(opc(name), rd, rs, 0); } + /// 三寄存器指令(rd, ra, rb)。 Instr E_rrr(const char* name, uint8_t rd, uint8_t ra, uint8_t rb) { return pack(opc(name), rd, ra, rb); } + /// 立即数指令(imm 拆低/高字节入字段)。 Instr E_imm(const char* name, uint8_t rd, uint16_t imm) { return pack(opc(name), rd, static_cast(imm & 0xFFu), static_cast((imm >> 8) & 0xFFu)); } + /// 槽号指令(slot 复用 imm 字段,u16)。 Instr E_slot(const char* name, uint8_t rd, uint16_t slot) { return E_imm(name, rd, slot); } + /// 无条件跳转(off 相对下一条指令)。 Instr E_jmp(int16_t off) { const uint16_t u = static_cast(off); return pack(opc("JMP"), 0, static_cast(u & 0xFFu), static_cast((u >> 8) & 0xFFu)); } + /// 条件跳转(寄存器 r 为真则跳;off 相对下一条指令)。 Instr E_jc(const char* name, uint8_t r, int16_t off) { const uint16_t u = static_cast(off); return pack(opc(name), r, static_cast(u & 0xFFu), static_cast((u >> 8) & 0xFFu)); } + /// CALL 指令(fn_id 为函数表下标)。 Instr E_call(uint16_t fn_id) { return E_imm("CALL", 0, fn_id); } + /// RET 指令。 Instr E_ret() { return pack(opc("RET"), 0, 0, 0); } + /** + * @brief 按名查 POU AST。 + * @param name POU 名 + * @return 找到返回指针;否则 nullptr + */ const POU* find_pou(const std::string& name) const { for (const SourceUnit& u : units_) { for (const POU& p : u.ast.pous) { @@ -163,27 +185,41 @@ namespace { } // 每函数的编译态 + /** + * @brief 单个函数的编译状态。 + * @details 寄存器分配(调用约定):结果 r0、输入 r1..r7、变量与临时 r8+。 + */ struct FuncCtx { - std::string name; - std::string pou_name; // 所属 POU 名(实例查找键) - std::vector code; // 字节码 - std::map regs; // 变量名 → 帧寄存器 - uint8_t nlocals = 0; // 变量区终点 = 临时寄存器起始 - uint8_t nregs = 0; // 峰值(变量 + 临时) - uint8_t temp_used = 0; // 本语句已用临时数(语句结束清零) - bool is_function = false; // FUNCTION(结果 r0 / 输入只读) - std::string result_name; // FUNCTION 名(结果寄存器映射) + std::string name; ///< 函数名(函数表行用) + std::string pou_name; ///< 所属 POU 名(实例查找键) + std::vector code; ///< 字节码 + std::map regs; ///< 变量名 → 帧寄存器 + uint8_t nlocals = 0; ///< 变量区终点 = 临时寄存器起始 + uint8_t nregs = 0; ///< 寄存器峰值(变量 + 临时) + uint8_t temp_used = 0; ///< 本语句已用临时数(语句结束清零) + bool is_function = false; ///< FUNCTION(结果 r0 / 输入只读) + std::string result_name; ///< FUNCTION 名(结果寄存器映射) }; // FB 实例:字段名 → 数据区槽号 + /** + * @brief FB 实例布局。 + * @details 字段槽号 = 实例基槽 + 字段序号(跨周期持久)。 + */ struct InstFields { - std::map field_addr; - uint32_t base = 0; - std::string type_name; // 实例的 FB 类型名(内建 / 用户) + std::map field_addr; ///< 字段名 → 数据区槽号 + uint32_t base = 0; ///< 实例基槽 + std::string type_name; ///< 实例的 FB 类型名(内建 / 用户) }; + /// 语句开始:清零临时寄存器计数。 void begin_stmt(FuncCtx& f) { f.temp_used = 0; } + /** + * @brief 分配一个临时寄存器。 + * @param f 函数编译态 + * @return 临时寄存器号(nlocals + 已用数;随用抬高 nregs 峰值) + */ uint8_t alloc_temp(FuncCtx& f) { const uint8_t r = f.nlocals + f.temp_used; ++f.temp_used; @@ -248,6 +284,15 @@ namespace { return true; } + /** + * @brief 编译一条语句。 + * @param f 函数编译态 + * @param st 语句 AST + * @return true 成功;false(err 已写) + * @details 支持 IF / WHILE / FB 调用 / 赋值;其他语句报 + * "statement not supported"。内联 FB 体内左值可能是实例字段 + * (走 STORE_GLOBAL);写 FUNCTION 输入报 "cannot write function input"。 + */ bool compile_stmt(FuncCtx& f, const Stmt& st) { if (st.kind == StmtKind::If) { return compile_if(f, st); @@ -283,6 +328,15 @@ namespace { return compile_expr(f, *st.value, it->second); } + /** + * @brief 编译 FB 调用语句。 + * @param f 函数编译态 + * @param st FB 调用语句 AST + * @return true 成功;false(err 已写) + * @details 实参逐项编译后 STORE_GLOBAL 写实例字段;内建 FB + * 发 CAL_ <实例基槽>;用户 FB 调用点内联展开。 + * 错误:"no instance" / "unknown input" / "no FB type"。 + */ bool compile_fb_call(FuncCtx& f, const Stmt& st) { const InstFields* inst = instance_of(f.pou_name, st.instance); if (inst == nullptr) { @@ -315,6 +369,7 @@ namespace { return compile_fb_inline(f, *fb, *inst); } + /// 转大写(内建 FB 名 → 操作码名)。 static std::string uppercase_of(const std::string& s) { std::string out = s; for (char& ch : out) { @@ -323,6 +378,15 @@ namespace { return out; } + /** + * @brief 用户 FB 调用点内联展开。 + * @param f 函数编译态 + * @param fb 用户 FB 的 POU AST + * @param inst 实例布局 + * @return true 成功;false(err 已写) + * @details 临时置 inline_fields_ 使体内变量引用改查实例字段 + * (编译结束后恢复)。 + */ bool compile_fb_inline(FuncCtx& f, const POU& fb, const InstFields& inst) { const InstFields* saved = inline_fields_; inline_fields_ = &inst; @@ -336,6 +400,14 @@ namespace { return true; } + /** + * @brief 编译 WHILE 循环。 + * @param f 函数编译态 + * @param st WHILE 语句 AST + * @return true 成功;false(err 已写) + * @details 结构:条件 → JF 跳出 → 体 → JMP 回条件;偏移经 + * patch_jump 回填(相对下一条)。 + */ bool compile_while(FuncCtx& f, const Stmt& st) { const size_t loop = f.code.size(); const uint8_t t = alloc_temp(f); @@ -357,6 +429,14 @@ namespace { return true; } + /** + * @brief 编译 IF / ELSIF / ELSE。 + * @param f 函数编译态 + * @param st IF 语句 AST + * @return true 成功;false(err 已写) + * @details 每条分支:条件 → JF 跳下一条 → 体;分支间 JMP 跳 + * 公共结束点;偏移经 patch_jump 回填。 + */ bool compile_if(FuncCtx& f, const Stmt& st) { std::vector*>> branches; branches.push_back({st.cond.get(), &st.body}); @@ -401,6 +481,17 @@ namespace { return true; } + /** + * @brief 编译左值存储(全局槽)。 + * @param f 函数编译态 + * @param target 目标名 + * @param value 表达式 + * @return true 成功;false(err 已写) + * @details 目标槽号在 link_.global_index 中查;I/O 输入禁止写 + * ("cannot write to input");存储操作码经 store_name 选择 + * (I/O 输出 STORE_Q,其余 STORE_GLOBAL)。 + * 错误:"no storage for ..." / "cannot write to input ..."。 + */ bool store_target(FuncCtx& f, const std::string& target, const Expr& value) { const auto git = link_.global_index.find(target); if (git == link_.global_index.end()) { @@ -417,6 +508,18 @@ namespace { return true; } + /** + * @brief 编译表达式到目标寄存器。 + * @param f 函数编译态 + * @param e 表达式 AST + * @param rd 目标寄存器号 + * @return true 成功;false(err 已写) + * @details 支持:字面量(LOADK,tag 来自配置类型表)、变量/字段引用、 + * 四则、比较(cmp_name)、NOT、短路 AND/OR(JF/JT)、函数调用 + * (实参 MOVE 到 r1..r7 后 CALL,结果 MOVE 回 rd)。 + * 错误:"no register or slot" / "no instance" / "unknown field" / + * "too many arguments (max 7)" / "no fn_id" / "expression not supported"。 + */ bool compile_expr(FuncCtx& f, const Expr& e, uint8_t rd) { if (e.kind == ExprKind::LitBool || e.kind == ExprKind::LitInt || e.kind == ExprKind::LitTime) { @@ -533,6 +636,11 @@ namespace { return fail("expression not supported"); } + /** + * @brief 按名查 fn_id(函数表下标)。 + * @param name 函数名 + * @return 找到返回下标;否则 -1 + */ int find_fn_id(const std::string& name) const { for (size_t i = 0; i < link_.scopes.size(); ++i) { if (link_.scopes[i].name == name) { @@ -542,6 +650,14 @@ namespace { return -1; } + /** + * @brief 回填跳转偏移。 + * @param f 函数编译态 + * @param idx 跳转指令下标 + * @param target_idx 目标指令下标 + * @details 偏移相对下一条:目标 = 当前 + 1 + off(off 为 + * int16,重写指令的低 16 位)。 + */ void patch_jump(FuncCtx& f, size_t idx, size_t target_idx) { const int16_t off = static_cast( static_cast(target_idx) - (static_cast(idx) + 1)); @@ -552,6 +668,7 @@ namespace { } // ---- 操作码名(MachineConfig 已强校验存在)---- + /// 四则运算操作码名(Add→"ADD" 等;未知回退 "ADD")。 static const char* arith_name(ExprKind k) { switch (k) { case ExprKind::Add: return "ADD"; @@ -561,6 +678,7 @@ namespace { default: return "ADD"; } } + /// 比较操作码名(Eq→"CMP_EQ" 等;未知回退 "CMP_EQ")。 static const char* cmp_name(BinOp op) { switch (op) { case BinOp::Eq: return "CMP_EQ"; @@ -572,13 +690,23 @@ namespace { default: return "CMP_EQ"; } } + /// 加载操作码选择:I/O 输入走 LOAD_I,其余 LOAD_GLOBAL。 const char* load_name(const std::string& name) const { return io_input_.count(name) ? "LOAD_I" : "LOAD_GLOBAL"; } + /// 存储操作码选择:I/O 输出走 STORE_Q,其余 STORE_GLOBAL。 const char* store_name(const std::string& name) const { return io_output_.count(name) ? "STORE_Q" : "STORE_GLOBAL"; } + /** + * @brief 布局全局数据区。 + * @return true 成功;false(err 已写) + * @details 每槽 8 字节定宽小端,槽号 × 8 定位;初值按类型元数据 + * 写(宽度 2 写 u16、8 写 u64、否则写 0/1;别名先经 type_meta 解析)。 + * 错误:"data area exceeds slot range" / "no type in machine.toml" / + * "float initializer not supported yet"。 + */ bool layout_data() { for (const Symbol& s : link_.globals) { if (s.address > 0xFFFF) { @@ -614,6 +742,12 @@ namespace { return true; } + /** + * @brief 布局 FB 实例块(全局区之后顺序追加)。 + * @return true 成功;false(err 已写) + * @details 实例 key 为 "POU名/实例名";字段槽号 = 基槽 + 字段序号。 + * 错误:"no layout for instance ..."。 + */ bool layout_fb_instances() { uint32_t cur = static_cast(data_.size() / 8); bool any = false; @@ -643,12 +777,19 @@ namespace { return true; } + /// 按 "POU名/实例名" 查实例布局;找不到返回 nullptr。 const InstFields* instance_of(const std::string& pou, const std::string& name) const { const auto it = instances_.find(pou + "/" + name); return it == instances_.end() ? nullptr : &it->second; } + /** + * @brief 查全局变量初值(源文件 globals 段)。 + * @param name 变量名 + * @param has_init 输出:是否有初值 + * @param init 输出:初值 + */ void init_of(const std::string& name, bool* has_init, int64_t* init) const { for (const SourceUnit& u : units_) { for (const VarBlock& b : u.ast.globals) { @@ -683,6 +824,13 @@ namespace { return static_cast(consts_.size() - 1); } + /** + * @brief 拼装最终映像。 + * @return true 成功;false(err 已写) + * @details 布局:头(104) + 常量表 + 函数表 + 字节码 + 数据段 + + * SHA-256 文件尾;型号标识 @72;头内各段偏移(52..68)与函数表 + * code_offset 小端写入;SHA-256 对文件尾之前全部内容计算。 + */ bool assemble_image() { const uint32_t off_const = static_cast(kHeaderSize); const uint32_t off_funcs = off_const + @@ -774,12 +922,14 @@ namespace { return true; } + /// 小端写 32 位。 static void put_le32(std::vector& b, size_t off, uint32_t v) { b[off + 0] = static_cast(v & 0xFFu); b[off + 1] = static_cast((v >> 8) & 0xFFu); b[off + 2] = static_cast((v >> 16) & 0xFFu); b[off + 3] = static_cast((v >> 24) & 0xFFu); } + /// 小端写 64 位。 static void put_le64(std::vector& b, size_t off, uint64_t v) { for (int i = 0; i < 8; ++i) { b[off + i] = static_cast((v >> (8 * i)) & 0xFFu); @@ -787,23 +937,33 @@ namespace { } // ---- 成员 ---- - const Project& proj_; - const std::vector& units_; - const LinkResult& link_; - const MachineConfig& cfg_; - std::vector* image_; - std::string* err_; - std::vector funcs_; - std::vector consts_; - std::map io_input_; - std::map io_output_; - std::vector data_; - std::map instances_; - const InstFields* inline_fields_ = nullptr; + const Project& proj_; ///< 工程定义(cycle_limit / dt_ms / 哈希) + const std::vector& units_; ///< 全部源文件 AST + const LinkResult& link_; ///< 链接结果(POU 顺序 / 全局符号 / FB 布局) + const MachineConfig& cfg_; ///< 机器定义(opcode / tag / FB 布局) + std::vector* image_; ///< 输出映像字节 + std::string* err_; ///< 错误输出(可为 nullptr) + std::vector funcs_; ///< 已编译函数 + std::vector consts_; ///< 常量表(tag + value) + std::map io_input_; ///< I/O 输入变量名集合(小写;只影响操作码选择) + std::map io_output_; ///< I/O 输出变量名集合(小写;只影响操作码选择) + std::vector data_; ///< 全局数据区字节(每槽 8 字节定宽) + std::map instances_; ///< "POU名/实例名" → 实例布局 + const InstFields* inline_fields_ = nullptr; ///< 内联 FB 字段表(非空 = 正在内联 FB 体) }; } // namespace +/** + * @brief 编译工程为 .stb 映像字节(对外入口)。 + * @param proj 工程定义 + * @param units 全部源文件的 AST + * @param link 链接结果 + * @param cfg 机器定义(machine.toml) + * @param image 输出映像字节 + * @param err 错误输出;可为 nullptr + * @return true 成功;false(err 前缀 "codegen error") + */ bool codegen_project(const Project& proj, const std::vector& units, const LinkResult& link, const MachineConfig& cfg, std::vector* image, std::string* err) { diff --git a/compiler/src/Lexer.cpp b/compiler/src/Lexer.cpp index 19ed538..98e3ee9 100644 --- a/compiler/src/Lexer.cpp +++ b/compiler/src/Lexer.cpp @@ -37,9 +37,11 @@ namespace compiler { namespace { - // 关键字表:小写键 → Tok。 - // 与 Doc/compiler/词法.md 的冻结表一致(12.5 修订补入 then/do); - // 数值即 token 类型,只追加不删改。 + /** + * @brief 关键字表:小写键 → Tok。 + * @details 与 Doc/compiler/词法.md 的冻结表一致(12.5 修订补入 + * then/do);数值即 token 类型,只追加不删改。 + */ const struct { const char* key; Tok tok; @@ -433,15 +435,15 @@ namespace { } // ---- 状态 ---- - const std::string& src_; // 源文本(外部所有,不拷贝) - const std::string& sf_; // 源文件名(报错用) - std::vector* out_; // token 流输出 - std::string* err_; // 错误输出(可空) - size_t pos_; // 当前字符下标 - uint32_t line_; // 当前行(1 起) - uint32_t col_; // 当前列(1 起) - uint32_t tok_line_ = 1; // 本 token 起始行(push 时用) - uint32_t tok_col_ = 1; // 本 token 起始列(push 时用) + const std::string& src_; ///< 源文本(外部所有,不拷贝) + const std::string& sf_; ///< 源文件名(报错用) + std::vector* out_; ///< token 流输出 + std::string* err_; ///< 错误输出(可空) + size_t pos_; ///< 当前字符下标 + uint32_t line_; ///< 当前行(1 起) + uint32_t col_; ///< 当前列(1 起) + uint32_t tok_line_ = 1; ///< 本 token 起始行(push 时用) + uint32_t tok_col_ = 1; ///< 本 token 起始列(push 时用) }; } // namespace diff --git a/compiler/src/Linker.cpp b/compiler/src/Linker.cpp index 4bc9969..54a0a30 100644 --- a/compiler/src/Linker.cpp +++ b/compiler/src/Linker.cpp @@ -96,7 +96,7 @@ namespace { return k == TypeKind::Bool || k == TypeKind::Int || k == TypeKind::Time; } - // 内建 FB 布局(冻结,见 Doc/compiler/符号表与链接.md;12.11 扩为 8 个) + /// 内建 FB 布局(冻结,见 Doc/compiler/符号表与链接.md;12.11 扩为 8 个) const FbLayout kTonLayout{"ton", {{"in", TypeKind::Bool}, {"pt", TypeKind::Time}, {"q", TypeKind::Bool}, {"et", TypeKind::Time}}}; const FbLayout kTofLayout{"tof", {{"in", TypeKind::Bool}, {"pt", TypeKind::Time}, @@ -691,12 +691,12 @@ namespace { } // ---- 成员 ---- - const Project& proj_; // 工程定义(toml) - const std::vector& units_; // 全部源文件 AST - LinkResult* out_; // 链接结果 - std::string* err_; // 错误输出(可空) - std::filesystem::path gvl_path_; // 规范化后的 gvl 路径 - std::map> call_edges_; // 调用者 → 被调函数集 + const Project& proj_; ///< 工程定义(toml) + const std::vector& units_; ///< 全部源文件 AST + LinkResult* out_; ///< 链接结果 + std::string* err_; ///< 错误输出(可空) + std::filesystem::path gvl_path_; ///< 规范化后的 gvl 路径 + std::map> call_edges_; ///< 调用者 → 被调函数集 }; } // namespace diff --git a/compiler/src/MachineConfig.cpp b/compiler/src/MachineConfig.cpp index f3c000a..461dece 100644 --- a/compiler/src/MachineConfig.cpp +++ b/compiler/src/MachineConfig.cpp @@ -22,6 +22,10 @@ namespace compiler { +/** + * @brief 内建基元表。 + * @return 静态基元表(bit / int8..uint64 / float32 / float64,元数据在代码) + */ const std::vector& MachineConfig::prims() { static const std::vector kPrims = { {"bit", 1, false, false}, @@ -39,6 +43,11 @@ const std::vector& MachineConfig::prims() { return kPrims; } +/** + * @brief 按名称查内建基元。 + * @param name 基元名 + * @return 命中指针;未找到返回 nullptr + */ const PrimType* MachineConfig::find_prim(const std::string& name) { for (const PrimType& p : prims()) { if (p.name == name) { @@ -50,7 +59,12 @@ const PrimType* MachineConfig::find_prim(const std::string& name) { namespace { - // 稳定前缀 + /** + * @brief 写错误信息(稳定前缀 "machine error")并原样返回 msg。 + * @param err 错误输出;可为 nullptr(静默) + * @param msg 错误描述 + * @return msg(供调用方直接 return) + */ std::string fail(std::string* err, const std::string& msg) { if (err) { *err = "machine error: " + msg; @@ -58,7 +72,11 @@ namespace { return msg; } - // format → 参数数量(解析时校验) + /** + * @brief format → 期望参数数量(解析时校验)。 + * @param format 操作数形态(RR/RRR/IMM/SLOT/JMP/JC/CALL/CAL/NONE) + * @return 参数数量;未知 format 返回 -1 + */ int params_of(const std::string& format) { if (format == "RR") return 2; if (format == "RRR") return 3; @@ -72,15 +90,25 @@ namespace { return -1; } + /// @brief format 是否已知(params_of 返回非负)。 bool is_known_format(const std::string& f) { return params_of(f) >= 0; } + /// @brief tag 是否在 .stb 常量表契约内(0=BOOL、1=INT、2=TIME)。 bool is_known_tag(uint32_t tag) { return tag <= 2; // .stb 常量表契约:0=BOOL 1=INT 2=TIME } - // 必填字符串 + /** + * @brief 读必填字符串字段。 + * @param t 配置表 + * @param what 表归属描述(用于错误信息,如 "[meta]") + * @param key 字段名 + * @param out 输出值 + * @param err 错误输出;可为 nullptr(静默) + * @return true 成功;false(字段缺失或类型不符,err 已写 "machine error: ...") + */ bool req_string(const toml::table& t, const char* what, const std::string& key, std::string* out, std::string* err) { const auto nv = t[key]; @@ -92,7 +120,15 @@ namespace { return true; } - // 必填整数 + /** + * @brief 读必填整数字段。 + * @param t 配置表 + * @param what 表归属描述(用于错误信息,如 "[meta]") + * @param key 字段名 + * @param out 输出值 + * @param err 错误输出;可为 nullptr(静默) + * @return true 成功;false(字段缺失或类型不符,err 已写 "machine error: ...") + */ bool req_int(const toml::table& t, const char* what, const std::string& key, int64_t* out, std::string* err) { const auto nv = t[key]; @@ -104,7 +140,15 @@ namespace { return true; } - // 必填布尔 + /** + * @brief 读必填布尔字段。 + * @param t 配置表 + * @param what 表归属描述(用于错误信息,如 "[meta]") + * @param key 字段名 + * @param out 输出值 + * @param err 错误输出;可为 nullptr(静默) + * @return true 成功;false(字段缺失或类型不符,err 已写 "machine error: ...") + */ bool req_bool(const toml::table& t, const char* what, const std::string& key, bool* out, std::string* err) { const auto nv = t[key]; @@ -118,6 +162,19 @@ namespace { } // namespace +/** + * @brief 加载 machine.toml 并做强校验。 + * @param path machine.toml 路径 + * @param err 错误输出;可为 nullptr(静默) + * @return true 成功;false(err 已写,前缀 "machine error") + * @details 校验顺序与文件头清单 1~5 一致: + * 1. TOML 语法与字段完整性(meta/type/op/fb 全属性必填) + * 2. type.base 命中内建基元;range 合法(min ≤ max) + * 3. tag 与 .stb 常量表契约一致(BOOL=0、INT=1、TIME=2,唯一) + * 4. op.opcode 唯一、0..255;class/format 合法;类别-格式互锁(INSTANCE ↔ CAL); + * params 数量与 format 一致 + * 5. fb.opcode 与 op 行(instance 类)一致;字段类型命中配置类型表;字段名唯一 + */ bool MachineConfig::load(const std::string& path, std::string* err) { ok_ = false; model_name_.clear(); @@ -352,6 +409,11 @@ bool MachineConfig::load(const std::string& path, std::string* err) { return true; } +/** + * @brief 按配置类型名查类型行。 + * @param name 配置类型名 + * @return 命中指针;未找到返回 nullptr + */ const ConfigType* MachineConfig::find_type(const std::string& name) const { for (const ConfigType& t : types_) { if (t.name == name) { @@ -361,6 +423,11 @@ const ConfigType* MachineConfig::find_type(const std::string& name) const { return nullptr; } +/** + * @brief 按助记符查指令行。 + * @param name 助记符(如 "MOVE") + * @return 命中指针;未找到返回 nullptr + */ const ConfigOp* MachineConfig::find_op(const std::string& name) const { for (const ConfigOp& o : ops_) { if (o.name == name) { @@ -370,6 +437,11 @@ const ConfigOp* MachineConfig::find_op(const std::string& name) const { return nullptr; } +/** + * @brief 按 opcode 查指令行。 + * @param opcode 操作码(0..255) + * @return 命中指针;未找到返回 nullptr + */ const ConfigOp* MachineConfig::find_op_by_code(uint32_t opcode) const { for (const ConfigOp& o : ops_) { if (o.opcode == opcode) { @@ -379,11 +451,21 @@ const ConfigOp* MachineConfig::find_op_by_code(uint32_t opcode) const { return nullptr; } +/** + * @brief 查询某 opcode 是否启用。 + * @param opcode 操作码 + * @return 指令存在且 enabled 为 true;未知 opcode 返回 false + */ bool MachineConfig::op_enabled(uint32_t opcode) const { const ConfigOp* op = find_op_by_code(opcode); return op != nullptr && op->enabled; } +/** + * @brief 按名称查内建 FB。 + * @param name FB 名(小写,如 "ton") + * @return 命中指针;未找到返回 nullptr + */ const ConfigFb* MachineConfig::find_fb(const std::string& name) const { for (const ConfigFb& f : fbs_) { if (f.name == name) { diff --git a/compiler/src/Parser.cpp b/compiler/src/Parser.cpp index 5c71730..12d326a 100644 --- a/compiler/src/Parser.cpp +++ b/compiler/src/Parser.cpp @@ -57,9 +57,12 @@ namespace compiler { namespace { - // 明确拒绝的保留名(小写命中即报错,见 Doc/compiler/语法.md)。 - // 这些词在词法层不是关键字(lex 成 IDENT),必须在语法层显式拦截, - // 避免 VAR_IN_OUT / REF / CLASS 等被静默当作用户标识符。 + /** + * @brief 语法层明确拒绝的保留名清单。 + * @details 这些词在词法层不是关键字(lex 成 IDENT),必须在语法层 + * 显式拦截,避免 VAR_IN_OUT / REF / CLASS 等被静默当作 + * 用户标识符(详见 Doc/compiler/语法.md)。 + */ const char* const kForbidden[] = { "var_in_out", "var_temp", "ref", "class", "any", "pointer", "interface", "method", @@ -989,11 +992,11 @@ namespace { // ---- 成员 ---- - std::string* err_; // 错误输出(可空) - std::vector tokens_; // 整段 token 流(构造时一次性 lex 完成) - Unit* out_; // 解析结果 - size_t pos_ = 0; // 当前 token 下标 - bool ok_ = true; // 词法阶段是否成功 + std::string* err_; ///< 错误输出(可空) + std::vector tokens_; ///< 整段 token 流(构造时一次性 lex 完成) + Unit* out_; ///< 解析结果 + size_t pos_ = 0; ///< 当前 token 下标 + bool ok_ = true; ///< 词法阶段是否成功 }; } // namespace diff --git a/compiler/src/Project.cpp b/compiler/src/Project.cpp index af2b20c..3dce331 100644 --- a/compiler/src/Project.cpp +++ b/compiler/src/Project.cpp @@ -3,6 +3,31 @@ * @brief 工程定义与 project.toml 解析(schema 校验) * @author * @date 2026-08-21 + * + * @details 设计说明(schema 规则见 Doc/初步计划.md 12.1 的 toml 字段表): + * - 解析顺序:root 键白名单 → [project] → [files] → [gvl](可选)→ [io](可选), + * 任一段失败立即返回(首个错误优先,不做恢复) + * - 错误:稳定类别前缀(parse error / unknown key / missing field / invalid value / + * bad entry)+ 行号(toml++ 源位置) + * - io 绑定不创造变量:slot 留待 12.6 链接阶段解析,此处填 0 + * + * 函数清单: + * - fail 写错误消息;err 为 nullptr 时静默 + * - at / at_pos toml 节点 / 位置 → " (line N)" 行号后缀 + * - req_string 取字符串(必填校验) + * - req_pos_int 取正整数(必填校验) + * - req_uint 取非负整数(必填校验,channel / bit 允许 0) + * - check_keys 表内键白名单(报首个未知键) + * - parse_project_section [project] 段(name/entry/cycle_limit/dt_ms 全必填) + * - parse_files_section [files] 段(st 必填、非空字符串数组) + * - parse_gvl_section [gvl] 段(可选;file 必填字符串) + * - parse_io_entry [[io.*]] 单个条目(var/channel/bit 全必填) + * - parse_io_section [io] 段(可选;input/output 数组可缺省) + * - parse_root 顶层:root 键白名单 + 按序解析四段 + * - dir_of 取路径的目录部分 + * - parse_project 对外入口:parse_file 捕获 parse_error → parse_root + * - compile_files 编译文件集合(files.st ∪ gvl.file,去重) + * - compute_project_hash 校验文件存在并计算 FNV-1a 64 工程哈希 */ #include "compiler/Project.h" @@ -23,28 +48,52 @@ namespace { // ---- 错误收集:稳定类别前缀 + 行号 ---- - // 写错误消息;err 为 nullptr 时静默忽略(调用方可不关心原因) + /** + * @brief 写错误消息。 + * @details err 为 nullptr 时静默忽略(调用方可不关心原因)。 + * @param err 错误输出(可空) + * @param msg 完整错误消息(已带类别前缀与行号) + */ void fail(std::string* err, const std::string& msg) { if (err) { *err = msg; } } - // 给 toml 节点附加行号后缀 " (line N)",用于定位非法值位置 + /** + * @brief 给 toml 节点附加行号后缀。 + * @param n toml 节点 + * @return " (line N)",用于定位非法值位置 + */ std::string at(const toml::node& n) { char buf[32]; std::snprintf(buf, sizeof buf, " (line %u)", n.source().begin.line); return buf; } - // parse_error 的位置可能是空的(如文件打不开):只在有位置时带行号 + /** + * @brief 给 parse_error 位置附加行号后缀。 + * @details parse_error 的位置可能是空的(如文件打不开):只在有位置时带行号。 + * @param pos toml 源位置 + * @return " (line N)";位置为空时返回空串 + */ std::string at_pos(const toml::source_position& pos) { char buf[32]; std::snprintf(buf, sizeof buf, " (line %u)", pos.line); return static_cast(pos) ? buf : std::string(); } - // 取字符串,必填校验 + /** + * @brief 取字符串字段(必填校验)。 + * @details 缺失报 "missing field 'key' in [sec]";类型非字符串报 + * "invalid value for 'key' in [sec] (expect string)" + 行号。 + * @param tbl 当前 toml 表 + * @param sec 段名(报错用) + * @param key 字段名 + * @param out 输出字符串值 + * @param err 错误输出(可空) + * @return true 成功;false 缺失或类型错误(err 已写) + */ bool req_string(const toml::table& tbl, const char* sec, const char* key, std::string* out, std::string* err) { if (const auto nv = tbl[key]) { @@ -60,7 +109,17 @@ namespace { return false; } - // 取正整数,必填校验 + /** + * @brief 取正整数字段(必填校验)。 + * @details 缺失报 "missing field";非整数报 "invalid value ... (expect integer)"; + * v <= 0 报 "(expect > 0)",均带行号。 + * @param tbl 当前 toml 表 + * @param sec 段名(报错用) + * @param key 字段名 + * @param out 输出整数值 + * @param err 错误输出(可空) + * @return true 成功;false 缺失或非法(err 已写) + */ bool req_pos_int(const toml::table& tbl, const char* sec, const char* key, uint32_t* out, std::string* err) { if (const auto nv = tbl[key]) { @@ -82,7 +141,16 @@ namespace { return false; } - // 取非负整数,必填校验(channel / bit 允许 0) + /** + * @brief 取非负整数字段(必填校验)。 + * @details 与 req_pos_int 相同校验,但允许 0(channel / bit 可为 0)。 + * @param tbl 当前 toml 表 + * @param sec 段名(报错用) + * @param key 字段名 + * @param out 输出整数值 + * @param err 错误输出(可空) + * @return true 成功;false 缺失或非法(err 已写) + */ bool req_uint(const toml::table& tbl, const char* sec, const char* key, uint32_t* out, std::string* err) { if (const auto nv = tbl[key]) { @@ -104,7 +172,16 @@ namespace { return false; } - // 表内键白名单;返回 false 并报首个未知键 + /** + * @brief 表内键白名单校验。 + * @details 发现首个未知键即失败,报 "unknown key 'key' in [sec]" + 行号。 + * @param tbl 当前 toml 表 + * @param sec 段名(报错用) + * @param allowed 允许的键名数组 + * @param n_allowed 键名数量 + * @param err 错误输出(可空) + * @return true 全部合法;false 有未知键(err 已写) + */ bool check_keys(const toml::table& tbl, const char* sec, const char* const* allowed, size_t n_allowed, std::string* err) { @@ -127,8 +204,14 @@ namespace { // ---- [project] ---- - // [project] 段:name/entry/cycle_limit/dt_ms 全必填; - // 第一版 entry 只接受 "program MAIN" + /** + * @brief 解析 [project] 段。 + * @details name/entry/cycle_limit/dt_ms 全必填;第一版 entry 只接受 "program MAIN"。 + * @param root 根表 + * @param out 输出 Project(填充 name/entry/cycle_limit/dt_ms) + * @param err 错误输出(可空) + * @return true 成功;false(err 已写) + */ bool parse_project_section(const toml::table& root, Project* out, std::string* err) { static const char* const allowed[] = {"name", "entry", "cycle_limit", "dt_ms"}; @@ -166,7 +249,14 @@ namespace { // ---- [files] ---- - // [files] 段:st 必填、非空数组,元素必须全为字符串,按声明顺序收集 + /** + * @brief 解析 [files] 段。 + * @details st 必填、非空数组,元素必须全为字符串,按声明顺序收集。 + * @param root 根表 + * @param out 输出 Project(填充 files_st) + * @param err 错误输出(可空) + * @return true 成功;false(err 已写) + */ bool parse_files_section(const toml::table& root, Project* out, std::string* err) { static const char* const allowed[] = {"st"}; @@ -208,7 +298,14 @@ namespace { // ---- [gvl](可选)---- - // [gvl] 段:可选;存在时 file 必填字符串,缺失整段不报错 + /** + * @brief 解析 [gvl] 段(可选)。 + * @details 存在时 file 必填字符串;整段缺失不报错。 + * @param root 根表 + * @param out 输出 Project(填充 gvl_file) + * @param err 错误输出(可空) + * @return true 成功;false(err 已写) + */ bool parse_gvl_section(const toml::table& root, Project* out, std::string* err) { static const char* const allowed[] = {"file"}; @@ -228,7 +325,16 @@ namespace { // ---- [[io.input]] / [[io.output]](可选)---- - // 单个 I/O 条目:var/channel/bit 全必填、全非负整数;slot 留待 12.6 链接阶段 + /** + * @brief 解析单个 I/O 条目([[io.input]] / [[io.output]] 数组元素)。 + * @details var/channel/bit 全必填,channel/bit 为非负整数;类型非表报 + * "bad entry in [[io.xxx]] (expect table)";slot 留待 12.6 链接阶段解析。 + * @param el 条目节点 + * @param is_input true = [[io.input]],false = [[io.output]] + * @param out 输出 Project(追加 io 条目) + * @param err 错误输出(可空) + * @return true 成功;false(err 已写) + */ bool parse_io_entry(const toml::node& el, bool is_input, Project* out, std::string* err) { static const char* const allowed[] = {"var", "channel", "bit"}; @@ -257,7 +363,14 @@ namespace { return true; } - // [io] 段:可选;input/output 子表为数组时逐条解析,两数组都可缺省 + /** + * @brief 解析 [io] 段(可选)。 + * @details input/output 子表为数组时逐条解析,两数组都可缺省;整段缺失不报错。 + * @param root 根表 + * @param out 输出 Project(填充 io) + * @param err 错误输出(可空) + * @return true 成功;false(err 已写) + */ bool parse_io_section(const toml::table& root, Project* out, std::string* err) { static const char* const allowed[] = {"input", "output"}; @@ -292,8 +405,15 @@ namespace { // ---- 顶层 ---- - // 顶层:root 键白名单 + 按 project → files → gvl → io 顺序解析, - // 任一段失败立即返回 false(首个错误优先,不做恢复) + /** + * @brief 顶层解析入口。 + * @details root 键白名单 + 按 project → files → gvl → io 顺序解析, + * 任一段失败立即返回 false(首个错误优先,不做恢复)。 + * @param root 根表 + * @param out 输出 Project + * @param err 错误输出(可空) + * @return true 成功;false(err 已写) + */ bool parse_root(const toml::table& root, Project* out, std::string* err) { static const char* const allowed[] = {"project", "files", "gvl", "io"}; if (!check_keys(root, "root", allowed, 4, err)) { @@ -311,7 +431,12 @@ namespace { return parse_io_section(root, out, err); } - // 取路径的目录部分:最后一个分隔符之前;无分隔符返回 "."(相对当前目录) + /** + * @brief 取路径的目录部分。 + * @details 最后一个分隔符之前;无分隔符返回 "."(相对当前目录)。 + * @param path 文件路径 + * @return 目录部分 + */ std::string dir_of(const std::string& path) { const size_t slash = path.find_last_of("/\\"); return (slash == std::string::npos) ? "." : path.substr(0, slash); @@ -319,6 +444,16 @@ namespace { } // namespace +/** + * @brief 解析并校验 project.toml(对外入口)。 + * @details 先定 base_dir(toml 所在目录),再 parse_file;toml++ 默认 TOML_EXCEPTIONS=1, + * parse_file 失败直接抛 parse_error,捕获后报 "parse error: <描述>" + 行号 + * (位置空时不带),随后交由 parse_root 按段解析。 + * @param toml_path project.toml 路径 + * @param out 输出 Project + * @param err 错误输出;可为 nullptr(静默) + * @return true 成功;false 失败(err 已写) + */ bool parse_project(const std::string& toml_path, Project* out, std::string* err) { out->base_dir = dir_of(toml_path); @@ -336,6 +471,12 @@ bool parse_project(const std::string& toml_path, Project* out, std::string* err) // ---- 文件集合与工程哈希 ---- +/** + * @brief 编译文件集合:files.st ∪ gvl.file,去重(gvl 已在 files.st 则跳过)。 + * @details 保持 files.st 顺序,gvl 追加在后;相对路径以 base_dir 为基准解析。 + * @param p 工程定义 + * @return 编译文件路径集合 + */ std::vector compile_files(const Project& p) { std::vector out = p.files_st; if (!p.gvl_file.empty() && @@ -345,6 +486,15 @@ std::vector compile_files(const Project& p) { return out; } +/** + * @brief 校验全部文件存在并计算工程哈希。 + * @details 集合按路径排序(规格:路径只当排序键),对内容做 FNV-1a 64 增量; + * 空集合 = basis(kFnvBasis);缺文件报错前缀 "file missing"。 + * @param p 工程定义 + * @param hash 输出工程哈希 + * @param err 错误输出;可为 nullptr(静默) + * @return true 成功;false 文件缺失(err 已写) + */ bool compute_project_hash(const Project& p, uint64_t* hash, std::string* err) { std::vector files = compile_files(p); diff --git a/compiler/src/Stb.cpp b/compiler/src/Stb.cpp index 156fc2f..75a85e2 100644 --- a/compiler/src/Stb.cpp +++ b/compiler/src/Stb.cpp @@ -3,6 +3,10 @@ * @brief 编译器自带的 .stb 映像规范(写侧)+ 只读视图 + FNV-1a + sidecar * @author * @date 2026-08-21 + * + * @details 与 vm 侧各一份实现;格式契约见 Doc/isa/指令与映像.md。 + * 12.13 修订已落地:头 104 = 原 72 + 型号标识[32] @72,文件尾 SHA-256[32]; + * 工程哈希为 FNV-1a 64。 */ #include "compiler/Stb.h" @@ -15,6 +19,14 @@ namespace compiler { +/** + * @brief FNV-1a 64 增量哈希更新(工程哈希,非密码学)。 + * @param h 当前哈希(首轮传 kFnvBasis) + * @param data 输入字节 + * @param len 字节数 + * @return 更新后的哈希值 + * @details 每字节:h ^= byte; h *= kFnvPrime。 + */ uint64_t fnv1a64_update(uint64_t h, const uint8_t* data, size_t len) { for (size_t i = 0; i < len; ++i) { h ^= data[i]; @@ -23,12 +35,19 @@ uint64_t fnv1a64_update(uint64_t h, const uint8_t* data, size_t len) { return h; } +/** + * @brief FNV-1a 64 一次性哈希(工程哈希)。 + * @param data 输入字节 + * @param len 字节数 + * @return 哈希值(从 kFnvBasis 起) + */ uint64_t fnv1a64(const uint8_t* data, size_t len) { return fnv1a64_update(kFnvBasis, data, len); } namespace { + /// 小端读 32 位。 uint32_t get_le32(const uint8_t* p) { return static_cast(p[0]) | (static_cast(p[1]) << 8) @@ -36,6 +55,7 @@ namespace { | (static_cast(p[3]) << 24); } + /// 小端读 64 位。 uint64_t get_le64(const uint8_t* p) { uint64_t v = 0; for (int i = 0; i < 8; ++i) { @@ -46,6 +66,7 @@ namespace { // ---- SHA-256(FIPS 180-4)---- + /// SHA-256 轮常量 K[0..63]。 const uint32_t kShaK[64] = { 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5, 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, @@ -60,15 +81,26 @@ namespace { 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2, }; + /// 循环右移。 inline uint32_t rotr(uint32_t x, uint32_t n) { return (x >> n) | (x << (32 - n)); } + /** + * @brief SHA-256 增量状态机。 + * @details 标准 FIPS 180-4 实现:update() 吸收任意长度字节流,final() 输出 + * 32 字节大端摘要。按 64 字节块 process()。 + */ struct Sha256 { uint32_t h[8] = {0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, - 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19}; - uint64_t total = 0; - uint8_t block[64]; - size_t block_len = 0; + 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19}; ///< 初始哈希值 + uint64_t total = 0; ///< 已吸收字节数(final 时编码进长度域) + uint8_t block[64]; ///< 当前块缓冲 + size_t block_len = 0; ///< 块缓冲已用字节数 + /** + * @brief 吸收数据。 + * @param data 输入字节 + * @param len 字节数 + */ void update(const uint8_t* data, size_t len) { total += len; while (len > 0) { @@ -91,6 +123,7 @@ namespace { } } + /// 压缩一个满块(64 字节:w[0..63] 展开 + 64 轮)。 void process() { uint32_t w[64]; for (int i = 0; i < 16; ++i) { @@ -123,6 +156,10 @@ namespace { h[4] += e; h[5] += f; h[6] += g; h[7] += hh; } + /** + * @brief 结束并输出摘要。 + * @param out 32 字节输出缓冲(大端) + */ void final(uint8_t out[32]) { const uint64_t bitlen = total * 8; const uint8_t pad = 0x80; @@ -146,6 +183,7 @@ namespace { } } + /// 大端读 32 位。 static uint32_t get_be32(const uint8_t* p) { return (static_cast(p[0]) << 24) | (static_cast(p[1]) << 16) | (static_cast(p[2]) << 8) | static_cast(p[3]); @@ -154,12 +192,25 @@ namespace { } // namespace +/** + * @brief 一次性 SHA-256(文件完整性校验)。 + * @param data 输入字节 + * @param len 字节数 + * @param out 32 字节摘要输出(大端) + */ void sha256(const uint8_t* data, size_t len, uint8_t out[kSha256Size]) { Sha256 s; s.update(data, len); s.final(out); } +/** + * @brief 按型号标识约定填充 32 字节:name + version(如 "STATOR1")。 + * @param name 型号名 + * @param version 版本号 + * @param out 32 字节输出缓冲 + * @details 不足补 '\0',超长截断。 + */ void fill_model_id(const std::string& name, uint32_t version, char out[kModelIdSize]) { const std::string id = name + std::to_string(version); for (size_t i = 0; i < kModelIdSize; ++i) { @@ -167,6 +218,19 @@ void fill_model_id(const std::string& name, uint32_t version, char out[kModelIdS } } +/** + * @brief 从原始字节构造只读视图(不拷贝,调用方保证生命周期)。 + * @param buf 映像缓冲;可为 nullptr + * @param len 缓冲字节数 + * @return 校验结果;ok() 判成功,error() 取失败原因 + * @details 校验顺序:长度 ≥ 头 → 魔数 → 版本 → 段偏移范围/单调 → + * 常量表/函数表大小 → 代码段 4 字节对齐 → 入口 fn_id 范围; + * 错误消息:"null buffer" / "image too short" / "bad magic" / + * "bad version" / "missing sha256 tail" / "segment offset out of range" / + * "segment offsets not monotonic" / "const table size mismatch" / + * "function table size mismatch" / "code segment not 4-byte aligned" / + * "entry fn_id out of range"。 + */ StbView StbView::from(const uint8_t* buf, size_t len) { StbView v; v.buf_ = buf; @@ -236,10 +300,12 @@ StbView StbView::from(const uint8_t* buf, size_t len) { return v; } +/// 从 vector 构造(转发 from(buf.data(), buf.size()))。 StbView StbView::from(const std::vector& buf) { return from(buf.data(), buf.size()); } +/// 读常量表一行;越界或未 ok() 时返回全 0。 ConstEntry StbView::const_entry(size_t i) const { ConstEntry e; if (ok_ && i < n_consts_) { @@ -250,11 +316,13 @@ ConstEntry StbView::const_entry(size_t i) const { return e; } +/// 常量表段起点(头 @52;由 from() 已校验的段起点)。 uint32_t StbView::offs_of_const() const { // offset_const = offs[0],由 from() 已校验的段起点 return static_cast(get_le32(buf_ + 52)); } +/// 读函数表一行;越界或未 ok() 时返回全 0。 StbView::FuncRow StbView::func_row(size_t i) const { FuncRow r; if (ok_ && i < n_funcs_) { @@ -266,22 +334,27 @@ StbView::FuncRow StbView::func_row(size_t i) const { return r; } +/// 字节码段起点(未 ok() 时返回 nullptr)。 const uint8_t* StbView::code_bytes() const { return ok_ ? buf_ + offset_code_ : nullptr; } +/// 字节码段字节数(未 ok() 时返回 0)。 size_t StbView::code_len() const { return ok_ ? offset_fb_ - offset_code_ : 0; } +/// 数据段起点(未 ok() 时返回 nullptr)。 const uint8_t* StbView::data_bytes() const { return ok_ ? buf_ + offset_data_ : nullptr; } +/// 数据段字节数(不含文件尾 SHA-256;未 ok() 时返回 0)。 size_t StbView::data_len() const { return ok_ ? (len_ - kSha256Size) - offset_data_ : 0; } +/// 型号标识字符串(头 72..103,截断到首个 '\0';未 ok() 时返回空串)。 std::string StbView::model_id() const { if (!ok_) { return ""; @@ -294,12 +367,14 @@ std::string StbView::model_id() const { return s; } +/// 型号标识是否匹配 name + version(未 ok() 时返回 false)。 bool StbView::model_matches(const std::string& name, uint32_t version) const { char want[kModelIdSize]; fill_model_id(name, version, want); return std::memcmp(buf_ + 72, want, kModelIdSize) == 0; } +/// 文件尾 32 字节 SHA-256 校验(对文件尾之前全部内容重算;未 ok() 时返回 false)。 bool StbView::sha_ok() const { if (!ok_) { return false; @@ -310,6 +385,13 @@ bool StbView::sha_ok() const { return std::memcmp(buf_ + content_len, digest, kSha256Size) == 0; } +/** + * @brief 读整个文件为字节(纯字节;校验交给 StbView)。 + * @param path 文件路径 + * @param out 输出字节 + * @param err 错误输出;可为 nullptr(静默) + * @return true 成功;false(err "cannot open for read: ") + */ bool read_stb_file(const char* path, std::vector* out, std::string* err) { std::ifstream in(path, std::ios::binary); if (!in) { @@ -322,6 +404,13 @@ bool read_stb_file(const char* path, std::vector* out, std::string* err return true; } +/** + * @brief 写字节为文件(纯字节)。 + * @param path 文件路径 + * @param img 映像字节 + * @param err 错误输出;可为 nullptr(静默) + * @return true 成功;false(err "cannot open for write: " / "write failed: ") + */ bool write_stb_file(const char* path, const std::vector& img, std::string* err) { std::FILE* f = std::fopen(path, "wb"); if (f == nullptr) { @@ -338,6 +427,13 @@ bool write_stb_file(const char* path, const std::vector& img, std::stri return ok; } +/** + * @brief 生成 sidecar 文本(TOML)。 + * @param bindings I/O 绑定列表 + * @return TOML 文本(每个绑定一节 [[io.input]] / [[io.output]],含 + * var / slot / channel / bit 四字段) + * @details I/O 绑定 var → 槽号 → channel/bit(执行器采样用)。 + */ std::string make_sidecar(const std::vector& bindings) { std::string out; for (const IoBinding& b : bindings) { @@ -357,6 +453,13 @@ std::string make_sidecar(const std::vector& bindings) { return out; } +/** + * @brief 生成并写 sidecar 文件。 + * @param path 文件路径 + * @param bindings I/O 绑定列表 + * @param err 错误输出;可为 nullptr(静默) + * @return true 成功;false(err "cannot open for write: " / "write failed: ") + */ bool write_sidecar_file(const char* path, const std::vector& bindings, std::string* err) { std::FILE* f = std::fopen(path, "wb"); diff --git a/compiler/src/TypeInfo.cpp b/compiler/src/TypeInfo.cpp index 3c43e5c..64f713c 100644 --- a/compiler/src/TypeInfo.cpp +++ b/compiler/src/TypeInfo.cpp @@ -3,6 +3,9 @@ * @brief 语言类型元数据(machine.toml 别名 + 内建基元解析) * @author * @date 2026-08-21 + * + * @details type_meta 实现:先按名称(大小写不敏感)命中配置类型行, + * 再由 base 解析出内建基元,合成 TypeMeta。 */ #include "compiler/TypeInfo.h" @@ -13,6 +16,11 @@ namespace compiler { namespace { + /** + * @brief 转小写。 + * @param s 输入串 + * @return 全小写副本(逐字符 tolower,逐字节安全) + */ std::string lower(const std::string& s) { std::string out = s; for (char& ch : out) { @@ -23,6 +31,14 @@ namespace { } // namespace +/** + * @brief 按语言类型名查元数据。 + * @param cfg 机器配置(类型表来源) + * @param name 语言类型名 + * @return 对应 TypeMeta;未找到返回空 TypeMeta(operator bool 为 false) + * @details 比较前两侧都转小写(配置名大写 BOOL/INT/TIME,Linker type_name 小写); + * 命中配置行后再用 base 解析内建基元,两者都命中才算有效。 + */ TypeMeta type_meta(const MachineConfig& cfg, const std::string& name) { TypeMeta m; const std::string key = lower(name); diff --git a/compiler/src/Typecheck.cpp b/compiler/src/Typecheck.cpp index bcee19f..41cba80 100644 --- a/compiler/src/Typecheck.cpp +++ b/compiler/src/Typecheck.cpp @@ -468,10 +468,10 @@ namespace { } // ---- 成员 ---- - const Project& proj_; // 工程定义(本阶段未用) - const std::vector& units_; // 全部源文件 AST - const LinkResult& link_; // 链接结果(只读) - std::string* err_; // 错误输出(可空) + const Project& proj_; ///< 工程定义(本阶段未用) + const std::vector& units_; ///< 全部源文件 AST + const LinkResult& link_; ///< 链接结果(只读) + std::string* err_; ///< 错误输出(可空) }; } // namespace diff --git a/compiler/src/main.cpp b/compiler/src/main.cpp index 05ed636..c3575de 100644 --- a/compiler/src/main.cpp +++ b/compiler/src/main.cpp @@ -3,6 +3,11 @@ * @brief STCompiler 可执行入口 * @author * @date 2026-08-21 + * + * @details 用法:STCompiler [-o out.stb] --machine ; + * --disasm 模式反汇编已编译映像。编译/反汇编必填 --machine(缺失报 + * "error: missing --machine ");打印模式(无 -o)不需要 + * 机器定义。错误统一打到 stderr,前缀 "error: "。 */ #include @@ -21,6 +26,7 @@ #include "compiler/Typecheck.h" namespace { + /// 打印命令行用法(三种模式 + -o / --machine / --disasm / --help)。 void usage() { std::printf("usage: STCompiler [-o .stb] --machine \n" " STCompiler .stb --disasm --machine \n" @@ -31,8 +37,16 @@ namespace { " --help 打印本帮助\n"); } - // 反汇编一个已编译映像(函数表逐条 + 常量表 + 数据段摘要)。 - // 指令偏移为文件绝对字节偏移;反汇编按 machine.toml 的 format 输出。 + /** + * @brief 反汇编一个已编译映像。 + * @param path .stb 文件路径 + * @param cfg 机器定义(machine.toml;反汇编按 format 输出) + * @return 0 成功;1 失败(错误打到 stderr,前缀 "error: ") + * @details 输出:映像摘要(大小 / 函数数 / 全局数 / 入口 fn_id / + * dt_ms / cycle_limit / hash / model / sha)、常量表、函数表逐条 + * 反汇编、数据段摘要。指令偏移为文件绝对字节偏移。 + * 错误:"cannot open for read" / 解析失败消息(见 StbView::from)。 + */ int dump_image(const char* path, const compiler::MachineConfig& cfg) { std::vector bytes; std::string err; @@ -92,6 +106,16 @@ namespace { } } +/** + * @brief STCompiler 程序入口。 + * @param argc 参数个数 + * @param argv 参数列表 + * @return 0 成功;1 失败(错误 stderr 前缀 "error: ") + * @details 参数:argv[1] 为 project.toml 或 .stb(--disasm 时); + * -o 指定输出(打印模式判定依据);--machine 编译/反汇编必填。 + * 编译管线:读 .st → 链接 → 类型检查 → 寄存器码 → 写映像 → + * 写后自检(型号标识 + SHA-256)→ sidecar(I/O 绑定 var → 槽号)。 + */ int main(int argc, char** argv) { if (argc < 2) { std::printf("STCompiler 0.1\n");