- Linker.h/cpp:收集导出(仅 gvl 文件顶层 VAR_GLOBAL、声明顺序定槽、重复名拒绝) - VAR_EXTERNAL 接同一槽、类型一致;FB 实例必须先声明;字段存在性校验;函数调用图 DFS 环检测 - io.var 必须在 GVL;内建 TON/TOF/CTU 布局冻结;用户 FB 字段 = input/output/var(external 不是字段) - 函数名赋值 = 结果值写入(用例 13 约定);dump_symbols 打印 name kind type address file - linker_test:51 断言(用例 09/10/11/12/16/19 + line1/17 正例),ctest 7/7;20 用例扫描 15 绿 5 按期望拒
5.1 KiB
5.1 KiB
符号表与链接(12.6)
compiler 模块的符号收集与链接。输入:Project(toml)+ 各 .st 文件的 Unit(AST,见 语法.md);输出:链接结果(全局槽表、POU 符号表、函数表、FB 实例布局)。全工程定案见 初步计划.md 12.6。
做 / 不做
做
- 先扫完全部文件再解析引用(禁止边解析边执行)
- 全局收集:仅
gvl.file顶层VAR_GLOBAL,声明顺序 = 全局槽顺序 VAR_EXTERNAL接已有槽,种类/类型一致,不是新变量FUNCTION标无状态;FUNCTION_BLOCK必须先有实例才能调用CALL目标解析成fn_id(立即数),无函数指针- 校验:同名全局、未声明
io.var、重复 POU 名、未声明 FB 实例、函数循环调用 → 失败 - FB 实例布局:按类型算字段清单(输入/输出/内部/内建 TON/TOF/CTU 固定字段)
不做(第一版)
- 寄存器分配(12.8)、类型检查(12.7)
- 多 GVL 文件、I/Q/M 与全局分槽(v1 单一数据区,见下)
数据区定址(v1 定案)
- 单一数据区:全部全局(含 I/Q/M)按
globals.st声明顺序编槽号 0..n-1 - 映像头
n_globals= 声明总数;n_i/n_q/n_m= 0(第一版不拆,见初步计划.md5.1) LOAD_I/STORE_Q/LOAD_GLOBAL/STORE_GLOBAL操作码不同,槽号指向同一数据区- io 绑定(toml)只负责 channel/bit → 槽号映射(sidecar),不创造变量
符号表一行
enum class SymbolKind { Global, External, Local, Input, Output, Pou, FbInstance, Const };
struct Symbol {
SymbolKind kind;
std::string name; // 小写
std::string type_name; // bool / int / time / fb 类型名(小写)
uint32_t address; // 全局槽号(Global/External);POU 局部暂 0(12.8 分配)
std::string source_file;
uint32_t line, col;
};
kind 与 12.1 冻结表一致:global / external / local / input / output / pou / fb_instance / const(const 本期未用,保留)。
链接流程
1. 收集导出
遍历全部文件:
- 顶层 globals:仅 gvl.file 允许;同名 → duplicate global;声明顺序编槽号
- POU 导出:program / function / fb 名登记;重复 → duplicate POU
2. 逐 POU 建局部符号
VAR / VAR_INPUT / VAR_OUTPUT → local / input / output
VAR_EXTERNAL → 查全局表,必须存在、类型一致 → External(同一槽)
POU 内 VAR_GLOBAL → error(仅 gvl 顶层允许)
3. 语句引用解析
- FB 调用 instance(...) → 局部表查实例,类型必须是 FB → 否则 undeclared FB instance
- 字段读 fb.field → 实例类型有该字段(用户 FB 的 input/output;内建固定字段)
- VarRef / 赋值左值 → 局部表或全局表查得到 → 否则 undeclared identifier
- 函数调用 fn(...) → POU 表查得到且是 FUNCTION → 记依赖边;FUNCTION 不得调用 PROGRAM/FB
4. 循环调用检测
函数依赖图 DFS 找环 → recursive call(用例 19)
5. io 校验
io.var 必须在全局表 → 否则 io.var not in GVL(用例 12)
6. 输出
全局表 / POU 符号表 / 函数表(fn_id 按收集序)/ FB 实例布局
FB 实例布局
- 用户 FB:字段 = 全部
VAR_INPUT+VAR_OUTPUT+VAR(内部),按声明段序排 - 内建(冻结,isa 规格):
| FB | 字段(名 → 类型) |
|---|---|
| TON | in BOOL, pt TIME, q BOOL, et TIME |
| TOF | in BOOL, pt TIME, q BOOL, et TIME |
| CTU | cu BOOL, r BOOL, pv INT, q BOOL, cv INT |
- 实例布局 = 类型字段表的一份拷贝;字段偏移 12.8 按对齐规则算
错误
稳定前缀 link error:
link error: duplicate global 'g1' (globals.st, line 4, col 1)
link error: VAR_GLOBAL only allowed in gvl file (other.st, line 2, col 1)
link error: io.var 'Nope' not in GVL (project.toml)
link error: undeclared FB instance 'starter' (main.st, line 4, col 5)
link error: recursive call involving 'a' (main.st, line 2, col 4)
link error: undeclared identifier 'x' (main.st, line 3, col 3)
完成标准
- 用例 9(同一槽)、10(非 GVL 写全局)、11(同名全局)、12(io.var 未声明)、16(未声明实例)、19(循环调用)按表变绿
- 符号表能打印
name kind type address file - 全部构建 +
ctest无回归
执行计划(单步确认)
Linker.h(新建compiler/include/compiler/Linker.h):Symbol/SymbolKind+ 链接结果结构 +link_project声明Linker.cpp前半:收集导出(gvl 校验 / 全局定槽 / POU 登记)+ 逐 POU 局部符号Linker.cpp后半:引用解析(FB 实例 / 字段 / 变量 / 函数)+ 循环检测 + io 校验linker_test(tests/src/linker_test.cpp+ CMake):用例 09/10/11/12/16/19 + 符号表打印断言- 验证:
cmake --build+ctest(新增linker_links后 7/7 全绿) - 提交:
Linker.h/cpp+linker_test+ 文档