compiler 模块注释完善:Doxygen 风格(Lexer/Parser/Linker/Typecheck/Project/MachineConfig/TypeInfo/Codec/Codegen/Stb/main 共 21 个文件)

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