compiler 模块注释完善:Doxygen 风格(Lexer/Parser/Linker/Typecheck/Project/MachineConfig/TypeInfo/Codec/Codegen/Stb/main 共 21 个文件)
This commit is contained in:
@@ -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
@@ -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 成功;false(err 已写)
|
||||
* @details 支持 IF / WHILE / FB 调用 / 赋值;其他语句报
|
||||
* "statement not supported"。内联 FB 体内左值可能是实例字段
|
||||
* (走 STORE_GLOBAL);写 FUNCTION 输入报 "cannot write function input"。
|
||||
*/
|
||||
bool compile_stmt(FuncCtx& f, const Stmt& st) {
|
||||
if (st.kind == StmtKind::If) {
|
||||
return compile_if(f, st);
|
||||
@@ -283,6 +328,15 @@ namespace {
|
||||
return compile_expr(f, *st.value, it->second);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 编译 FB 调用语句。
|
||||
* @param f 函数编译态
|
||||
* @param st FB 调用语句 AST
|
||||
* @return true 成功;false(err 已写)
|
||||
* @details 实参逐项编译后 STORE_GLOBAL 写实例字段;内建 FB
|
||||
* 发 CAL_<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 成功;false(err 已写)
|
||||
* @details 临时置 inline_fields_ 使体内变量引用改查实例字段
|
||||
* (编译结束后恢复)。
|
||||
*/
|
||||
bool compile_fb_inline(FuncCtx& f, const POU& fb, const InstFields& inst) {
|
||||
const InstFields* saved = inline_fields_;
|
||||
inline_fields_ = &inst;
|
||||
@@ -336,6 +400,14 @@ namespace {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 编译 WHILE 循环。
|
||||
* @param f 函数编译态
|
||||
* @param st WHILE 语句 AST
|
||||
* @return true 成功;false(err 已写)
|
||||
* @details 结构:条件 → JF 跳出 → 体 → JMP 回条件;偏移经
|
||||
* patch_jump 回填(相对下一条)。
|
||||
*/
|
||||
bool compile_while(FuncCtx& f, const Stmt& st) {
|
||||
const size_t loop = f.code.size();
|
||||
const uint8_t t = alloc_temp(f);
|
||||
@@ -357,6 +429,14 @@ namespace {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 编译 IF / ELSIF / ELSE。
|
||||
* @param f 函数编译态
|
||||
* @param st IF 语句 AST
|
||||
* @return true 成功;false(err 已写)
|
||||
* @details 每条分支:条件 → JF 跳下一条 → 体;分支间 JMP 跳
|
||||
* 公共结束点;偏移经 patch_jump 回填。
|
||||
*/
|
||||
bool compile_if(FuncCtx& f, const Stmt& st) {
|
||||
std::vector<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 成功;false(err 已写)
|
||||
* @details 目标槽号在 link_.global_index 中查;I/O 输入禁止写
|
||||
* ("cannot write to input");存储操作码经 store_name 选择
|
||||
* (I/O 输出 STORE_Q,其余 STORE_GLOBAL)。
|
||||
* 错误:"no storage for ..." / "cannot write to input ..."。
|
||||
*/
|
||||
bool store_target(FuncCtx& f, const std::string& target, const Expr& value) {
|
||||
const auto git = link_.global_index.find(target);
|
||||
if (git == link_.global_index.end()) {
|
||||
@@ -417,6 +508,18 @@ namespace {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 编译表达式到目标寄存器。
|
||||
* @param f 函数编译态
|
||||
* @param e 表达式 AST
|
||||
* @param rd 目标寄存器号
|
||||
* @return true 成功;false(err 已写)
|
||||
* @details 支持:字面量(LOADK,tag 来自配置类型表)、变量/字段引用、
|
||||
* 四则、比较(cmp_name)、NOT、短路 AND/OR(JF/JT)、函数调用
|
||||
* (实参 MOVE 到 r1..r7 后 CALL,结果 MOVE 回 rd)。
|
||||
* 错误:"no register or slot" / "no instance" / "unknown field" /
|
||||
* "too many arguments (max 7)" / "no fn_id" / "expression not supported"。
|
||||
*/
|
||||
bool compile_expr(FuncCtx& f, const Expr& e, uint8_t rd) {
|
||||
if (e.kind == ExprKind::LitBool || e.kind == ExprKind::LitInt ||
|
||||
e.kind == ExprKind::LitTime) {
|
||||
@@ -533,6 +636,11 @@ namespace {
|
||||
return fail("expression not supported");
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 按名查 fn_id(函数表下标)。
|
||||
* @param name 函数名
|
||||
* @return 找到返回下标;否则 -1
|
||||
*/
|
||||
int find_fn_id(const std::string& name) const {
|
||||
for (size_t i = 0; i < link_.scopes.size(); ++i) {
|
||||
if (link_.scopes[i].name == name) {
|
||||
@@ -542,6 +650,14 @@ namespace {
|
||||
return -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 回填跳转偏移。
|
||||
* @param f 函数编译态
|
||||
* @param idx 跳转指令下标
|
||||
* @param target_idx 目标指令下标
|
||||
* @details 偏移相对下一条:目标 = 当前 + 1 + off(off 为
|
||||
* int16,重写指令的低 16 位)。
|
||||
*/
|
||||
void patch_jump(FuncCtx& f, size_t idx, size_t target_idx) {
|
||||
const int16_t off = static_cast<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 成功;false(err 已写)
|
||||
* @details 每槽 8 字节定宽小端,槽号 × 8 定位;初值按类型元数据
|
||||
* 写(宽度 2 写 u16、8 写 u64、否则写 0/1;别名先经 type_meta 解析)。
|
||||
* 错误:"data area exceeds slot range" / "no type in machine.toml" /
|
||||
* "float initializer not supported yet"。
|
||||
*/
|
||||
bool layout_data() {
|
||||
for (const Symbol& s : link_.globals) {
|
||||
if (s.address > 0xFFFF) {
|
||||
@@ -614,6 +742,12 @@ namespace {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 布局 FB 实例块(全局区之后顺序追加)。
|
||||
* @return true 成功;false(err 已写)
|
||||
* @details 实例 key 为 "POU名/实例名";字段槽号 = 基槽 + 字段序号。
|
||||
* 错误:"no layout for instance ..."。
|
||||
*/
|
||||
bool layout_fb_instances() {
|
||||
uint32_t cur = static_cast<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 成功;false(err 已写)
|
||||
* @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 成功;false(err 前缀 "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
@@ -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
|
||||
|
||||
@@ -96,7 +96,7 @@ namespace {
|
||||
return k == TypeKind::Bool || k == TypeKind::Int || k == TypeKind::Time;
|
||||
}
|
||||
|
||||
// 内建 FB 布局(冻结,见 Doc/compiler/符号表与链接.md;12.11 扩为 8 个)
|
||||
/// 内建 FB 布局(冻结,见 Doc/compiler/符号表与链接.md;12.11 扩为 8 个)
|
||||
const FbLayout kTonLayout{"ton", {{"in", TypeKind::Bool}, {"pt", TypeKind::Time},
|
||||
{"q", TypeKind::Bool}, {"et", TypeKind::Time}}};
|
||||
const FbLayout kTofLayout{"tof", {{"in", TypeKind::Bool}, {"pt", TypeKind::Time},
|
||||
@@ -691,12 +691,12 @@ namespace {
|
||||
}
|
||||
|
||||
// ---- 成员 ----
|
||||
const Project& proj_; // 工程定义(toml)
|
||||
const std::vector<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
|
||||
|
||||
@@ -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 成功;false(err 已写,前缀 "machine error")
|
||||
* @details 校验顺序与文件头清单 1~5 一致:
|
||||
* 1. TOML 语法与字段完整性(meta/type/op/fb 全属性必填)
|
||||
* 2. type.base 命中内建基元;range 合法(min ≤ max)
|
||||
* 3. tag 与 .stb 常量表契约一致(BOOL=0、INT=1、TIME=2,唯一)
|
||||
* 4. op.opcode 唯一、0..255;class/format 合法;类别-格式互锁(INSTANCE ↔ CAL);
|
||||
* params 数量与 format 一致
|
||||
* 5. fb.opcode 与 op 行(instance 类)一致;字段类型命中配置类型表;字段名唯一
|
||||
*/
|
||||
bool MachineConfig::load(const std::string& path, std::string* err) {
|
||||
ok_ = false;
|
||||
model_name_.clear();
|
||||
@@ -352,6 +409,11 @@ bool MachineConfig::load(const std::string& path, std::string* err) {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 按配置类型名查类型行。
|
||||
* @param name 配置类型名
|
||||
* @return 命中指针;未找到返回 nullptr
|
||||
*/
|
||||
const ConfigType* MachineConfig::find_type(const std::string& name) const {
|
||||
for (const ConfigType& t : types_) {
|
||||
if (t.name == name) {
|
||||
@@ -361,6 +423,11 @@ const ConfigType* MachineConfig::find_type(const std::string& name) const {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 按助记符查指令行。
|
||||
* @param name 助记符(如 "MOVE")
|
||||
* @return 命中指针;未找到返回 nullptr
|
||||
*/
|
||||
const ConfigOp* MachineConfig::find_op(const std::string& name) const {
|
||||
for (const ConfigOp& o : ops_) {
|
||||
if (o.name == name) {
|
||||
@@ -370,6 +437,11 @@ const ConfigOp* MachineConfig::find_op(const std::string& name) const {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 按 opcode 查指令行。
|
||||
* @param opcode 操作码(0..255)
|
||||
* @return 命中指针;未找到返回 nullptr
|
||||
*/
|
||||
const ConfigOp* MachineConfig::find_op_by_code(uint32_t opcode) const {
|
||||
for (const ConfigOp& o : ops_) {
|
||||
if (o.opcode == opcode) {
|
||||
@@ -379,11 +451,21 @@ const ConfigOp* MachineConfig::find_op_by_code(uint32_t opcode) const {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 查询某 opcode 是否启用。
|
||||
* @param opcode 操作码
|
||||
* @return 指令存在且 enabled 为 true;未知 opcode 返回 false
|
||||
*/
|
||||
bool MachineConfig::op_enabled(uint32_t opcode) const {
|
||||
const ConfigOp* op = find_op_by_code(opcode);
|
||||
return op != nullptr && op->enabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 按名称查内建 FB。
|
||||
* @param name FB 名(小写,如 "ton")
|
||||
* @return 命中指针;未找到返回 nullptr
|
||||
*/
|
||||
const ConfigFb* MachineConfig::find_fb(const std::string& name) const {
|
||||
for (const ConfigFb& f : fbs_) {
|
||||
if (f.name == name) {
|
||||
|
||||
+11
-8
@@ -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
@@ -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 相同校验,但允许 0(channel / bit 可为 0)。
|
||||
* @param tbl 当前 toml 表
|
||||
* @param sec 段名(报错用)
|
||||
* @param key 字段名
|
||||
* @param out 输出整数值
|
||||
* @param err 错误输出(可空)
|
||||
* @return true 成功;false 缺失或非法(err 已写)
|
||||
*/
|
||||
bool req_uint(const toml::table& tbl, const char* sec, const char* key,
|
||||
uint32_t* out, std::string* err) {
|
||||
if (const auto nv = tbl[key]) {
|
||||
@@ -104,7 +172,16 @@ namespace {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 表内键白名单;返回 false 并报首个未知键
|
||||
/**
|
||||
* @brief 表内键白名单校验。
|
||||
* @details 发现首个未知键即失败,报 "unknown key 'key' in [sec]" + 行号。
|
||||
* @param tbl 当前 toml 表
|
||||
* @param sec 段名(报错用)
|
||||
* @param allowed 允许的键名数组
|
||||
* @param n_allowed 键名数量
|
||||
* @param err 错误输出(可空)
|
||||
* @return true 全部合法;false 有未知键(err 已写)
|
||||
*/
|
||||
bool check_keys(const toml::table& tbl, const char* sec,
|
||||
const char* const* allowed, size_t n_allowed,
|
||||
std::string* err) {
|
||||
@@ -127,8 +204,14 @@ namespace {
|
||||
|
||||
// ---- [project] ----
|
||||
|
||||
// [project] 段:name/entry/cycle_limit/dt_ms 全必填;
|
||||
// 第一版 entry 只接受 "program MAIN"
|
||||
/**
|
||||
* @brief 解析 [project] 段。
|
||||
* @details name/entry/cycle_limit/dt_ms 全必填;第一版 entry 只接受 "program MAIN"。
|
||||
* @param root 根表
|
||||
* @param out 输出 Project(填充 name/entry/cycle_limit/dt_ms)
|
||||
* @param err 错误输出(可空)
|
||||
* @return true 成功;false(err 已写)
|
||||
*/
|
||||
bool parse_project_section(const toml::table& root, Project* out, std::string* err) {
|
||||
static const char* const allowed[] = {"name", "entry", "cycle_limit", "dt_ms"};
|
||||
|
||||
@@ -166,7 +249,14 @@ namespace {
|
||||
|
||||
// ---- [files] ----
|
||||
|
||||
// [files] 段:st 必填、非空数组,元素必须全为字符串,按声明顺序收集
|
||||
/**
|
||||
* @brief 解析 [files] 段。
|
||||
* @details st 必填、非空数组,元素必须全为字符串,按声明顺序收集。
|
||||
* @param root 根表
|
||||
* @param out 输出 Project(填充 files_st)
|
||||
* @param err 错误输出(可空)
|
||||
* @return true 成功;false(err 已写)
|
||||
*/
|
||||
bool parse_files_section(const toml::table& root, Project* out, std::string* err) {
|
||||
static const char* const allowed[] = {"st"};
|
||||
|
||||
@@ -208,7 +298,14 @@ namespace {
|
||||
|
||||
// ---- [gvl](可选)----
|
||||
|
||||
// [gvl] 段:可选;存在时 file 必填字符串,缺失整段不报错
|
||||
/**
|
||||
* @brief 解析 [gvl] 段(可选)。
|
||||
* @details 存在时 file 必填字符串;整段缺失不报错。
|
||||
* @param root 根表
|
||||
* @param out 输出 Project(填充 gvl_file)
|
||||
* @param err 错误输出(可空)
|
||||
* @return true 成功;false(err 已写)
|
||||
*/
|
||||
bool parse_gvl_section(const toml::table& root, Project* out, std::string* err) {
|
||||
static const char* const allowed[] = {"file"};
|
||||
|
||||
@@ -228,7 +325,16 @@ namespace {
|
||||
|
||||
// ---- [[io.input]] / [[io.output]](可选)----
|
||||
|
||||
// 单个 I/O 条目:var/channel/bit 全必填、全非负整数;slot 留待 12.6 链接阶段
|
||||
/**
|
||||
* @brief 解析单个 I/O 条目([[io.input]] / [[io.output]] 数组元素)。
|
||||
* @details var/channel/bit 全必填,channel/bit 为非负整数;类型非表报
|
||||
* "bad entry in [[io.xxx]] (expect table)";slot 留待 12.6 链接阶段解析。
|
||||
* @param el 条目节点
|
||||
* @param is_input true = [[io.input]],false = [[io.output]]
|
||||
* @param out 输出 Project(追加 io 条目)
|
||||
* @param err 错误输出(可空)
|
||||
* @return true 成功;false(err 已写)
|
||||
*/
|
||||
bool parse_io_entry(const toml::node& el, bool is_input, Project* out, std::string* err) {
|
||||
static const char* const allowed[] = {"var", "channel", "bit"};
|
||||
|
||||
@@ -257,7 +363,14 @@ namespace {
|
||||
return true;
|
||||
}
|
||||
|
||||
// [io] 段:可选;input/output 子表为数组时逐条解析,两数组都可缺省
|
||||
/**
|
||||
* @brief 解析 [io] 段(可选)。
|
||||
* @details input/output 子表为数组时逐条解析,两数组都可缺省;整段缺失不报错。
|
||||
* @param root 根表
|
||||
* @param out 输出 Project(填充 io)
|
||||
* @param err 错误输出(可空)
|
||||
* @return true 成功;false(err 已写)
|
||||
*/
|
||||
bool parse_io_section(const toml::table& root, Project* out, std::string* err) {
|
||||
static const char* const allowed[] = {"input", "output"};
|
||||
|
||||
@@ -292,8 +405,15 @@ namespace {
|
||||
|
||||
// ---- 顶层 ----
|
||||
|
||||
// 顶层:root 键白名单 + 按 project → files → gvl → io 顺序解析,
|
||||
// 任一段失败立即返回 false(首个错误优先,不做恢复)
|
||||
/**
|
||||
* @brief 顶层解析入口。
|
||||
* @details root 键白名单 + 按 project → files → gvl → io 顺序解析,
|
||||
* 任一段失败立即返回 false(首个错误优先,不做恢复)。
|
||||
* @param root 根表
|
||||
* @param out 输出 Project
|
||||
* @param err 错误输出(可空)
|
||||
* @return true 成功;false(err 已写)
|
||||
*/
|
||||
bool parse_root(const toml::table& root, Project* out, std::string* err) {
|
||||
static const char* const allowed[] = {"project", "files", "gvl", "io"};
|
||||
if (!check_keys(root, "root", allowed, 4, err)) {
|
||||
@@ -311,7 +431,12 @@ namespace {
|
||||
return parse_io_section(root, out, err);
|
||||
}
|
||||
|
||||
// 取路径的目录部分:最后一个分隔符之前;无分隔符返回 "."(相对当前目录)
|
||||
/**
|
||||
* @brief 取路径的目录部分。
|
||||
* @details 最后一个分隔符之前;无分隔符返回 "."(相对当前目录)。
|
||||
* @param path 文件路径
|
||||
* @return 目录部分
|
||||
*/
|
||||
std::string dir_of(const std::string& path) {
|
||||
const size_t slash = path.find_last_of("/\\");
|
||||
return (slash == std::string::npos) ? "." : path.substr(0, slash);
|
||||
@@ -319,6 +444,16 @@ namespace {
|
||||
|
||||
} // namespace
|
||||
|
||||
/**
|
||||
* @brief 解析并校验 project.toml(对外入口)。
|
||||
* @details 先定 base_dir(toml 所在目录),再 parse_file;toml++ 默认 TOML_EXCEPTIONS=1,
|
||||
* parse_file 失败直接抛 parse_error,捕获后报 "parse error: <描述>" + 行号
|
||||
* (位置空时不带),随后交由 parse_root 按段解析。
|
||||
* @param toml_path project.toml 路径
|
||||
* @param out 输出 Project
|
||||
* @param err 错误输出;可为 nullptr(静默)
|
||||
* @return true 成功;false 失败(err 已写)
|
||||
*/
|
||||
bool parse_project(const std::string& toml_path, Project* out, std::string* err) {
|
||||
out->base_dir = dir_of(toml_path);
|
||||
|
||||
@@ -336,6 +471,12 @@ bool parse_project(const std::string& toml_path, Project* out, std::string* err)
|
||||
|
||||
// ---- 文件集合与工程哈希 ----
|
||||
|
||||
/**
|
||||
* @brief 编译文件集合:files.st ∪ gvl.file,去重(gvl 已在 files.st 则跳过)。
|
||||
* @details 保持 files.st 顺序,gvl 追加在后;相对路径以 base_dir 为基准解析。
|
||||
* @param p 工程定义
|
||||
* @return 编译文件路径集合
|
||||
*/
|
||||
std::vector<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 增量;
|
||||
* 空集合 = basis(kFnvBasis);缺文件报错前缀 "file missing"。
|
||||
* @param p 工程定义
|
||||
* @param hash 输出工程哈希
|
||||
* @param err 错误输出;可为 nullptr(静默)
|
||||
* @return true 成功;false 文件缺失(err 已写)
|
||||
*/
|
||||
bool compute_project_hash(const Project& p, uint64_t* hash, std::string* err) {
|
||||
std::vector<std::string> files = compile_files(p);
|
||||
|
||||
|
||||
+107
-4
@@ -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-256(FIPS 180-4)----
|
||||
|
||||
/// SHA-256 轮常量 K[0..63]。
|
||||
const uint32_t kShaK[64] = {
|
||||
0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1,
|
||||
0x923f82a4, 0xab1c5ed5, 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3,
|
||||
@@ -60,15 +81,26 @@ namespace {
|
||||
0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2,
|
||||
};
|
||||
|
||||
/// 循环右移。
|
||||
inline uint32_t rotr(uint32_t x, uint32_t n) { return (x >> n) | (x << (32 - n)); }
|
||||
|
||||
/**
|
||||
* @brief SHA-256 增量状态机。
|
||||
* @details 标准 FIPS 180-4 实现:update() 吸收任意长度字节流,final() 输出
|
||||
* 32 字节大端摘要。按 64 字节块 process()。
|
||||
*/
|
||||
struct Sha256 {
|
||||
uint32_t h[8] = {0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a,
|
||||
0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19};
|
||||
uint64_t total = 0;
|
||||
uint8_t block[64];
|
||||
size_t block_len = 0;
|
||||
0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19}; ///< 初始哈希值
|
||||
uint64_t total = 0; ///< 已吸收字节数(final 时编码进长度域)
|
||||
uint8_t block[64]; ///< 当前块缓冲
|
||||
size_t block_len = 0; ///< 块缓冲已用字节数
|
||||
|
||||
/**
|
||||
* @brief 吸收数据。
|
||||
* @param data 输入字节
|
||||
* @param len 字节数
|
||||
*/
|
||||
void update(const uint8_t* data, size_t len) {
|
||||
total += len;
|
||||
while (len > 0) {
|
||||
@@ -91,6 +123,7 @@ namespace {
|
||||
}
|
||||
}
|
||||
|
||||
/// 压缩一个满块(64 字节:w[0..63] 展开 + 64 轮)。
|
||||
void process() {
|
||||
uint32_t w[64];
|
||||
for (int i = 0; i < 16; ++i) {
|
||||
@@ -123,6 +156,10 @@ namespace {
|
||||
h[4] += e; h[5] += f; h[6] += g; h[7] += hh;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 结束并输出摘要。
|
||||
* @param out 32 字节输出缓冲(大端)
|
||||
*/
|
||||
void final(uint8_t out[32]) {
|
||||
const uint64_t bitlen = total * 8;
|
||||
const uint8_t pad = 0x80;
|
||||
@@ -146,6 +183,7 @@ namespace {
|
||||
}
|
||||
}
|
||||
|
||||
/// 大端读 32 位。
|
||||
static uint32_t get_be32(const uint8_t* p) {
|
||||
return (static_cast<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 成功;false(err "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 成功;false(err "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 成功;false(err "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");
|
||||
|
||||
@@ -3,6 +3,9 @@
|
||||
* @brief 语言类型元数据(machine.toml 别名 + 内建基元解析)
|
||||
* @author
|
||||
* @date 2026-08-21
|
||||
*
|
||||
* @details type_meta 实现:先按名称(大小写不敏感)命中配置类型行,
|
||||
* 再由 base 解析出内建基元,合成 TypeMeta。
|
||||
*/
|
||||
|
||||
#include "compiler/TypeInfo.h"
|
||||
@@ -13,6 +16,11 @@ namespace compiler {
|
||||
|
||||
namespace {
|
||||
|
||||
/**
|
||||
* @brief 转小写。
|
||||
* @param s 输入串
|
||||
* @return 全小写副本(逐字符 tolower,逐字节安全)
|
||||
*/
|
||||
std::string lower(const std::string& s) {
|
||||
std::string out = s;
|
||||
for (char& ch : out) {
|
||||
@@ -23,6 +31,14 @@ namespace {
|
||||
|
||||
} // namespace
|
||||
|
||||
/**
|
||||
* @brief 按语言类型名查元数据。
|
||||
* @param cfg 机器配置(类型表来源)
|
||||
* @param name 语言类型名
|
||||
* @return 对应 TypeMeta;未找到返回空 TypeMeta(operator bool 为 false)
|
||||
* @details 比较前两侧都转小写(配置名大写 BOOL/INT/TIME,Linker type_name 小写);
|
||||
* 命中配置行后再用 base 解析内建基元,两者都命中才算有效。
|
||||
*/
|
||||
TypeMeta type_meta(const MachineConfig& cfg, const std::string& name) {
|
||||
TypeMeta m;
|
||||
const std::string key = lower(name);
|
||||
|
||||
@@ -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
@@ -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)→ sidecar(I/O 绑定 var → 槽号)。
|
||||
*/
|
||||
int main(int argc, char** argv) {
|
||||
if (argc < 2) {
|
||||
std::printf("STCompiler 0.1\n");
|
||||
|
||||
Reference in New Issue
Block a user