Compare commits

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