实现 12.6 符号表与链接:全局定槽、引用解析、循环检测。
- 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 按期望拒
This commit is contained in:
@@ -0,0 +1,111 @@
|
||||
# 符号表与链接(12.6)
|
||||
|
||||
compiler 模块的符号收集与链接。输入:`Project`(toml)+ 各 `.st` 文件的 `Unit`(AST,见 [`语法.md`](语法.md));输出:链接结果(全局槽表、POU 符号表、函数表、FB 实例布局)。全工程定案见 [`初步计划.md`](../初步计划.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(第一版不拆,见 [`初步计划.md`](../初步计划.md) 5.1)
|
||||
- `LOAD_I` / `STORE_Q` / `LOAD_GLOBAL` / `STORE_GLOBAL` 操作码不同,槽号指向同一数据区
|
||||
- io 绑定(toml)只负责 channel/bit → 槽号映射(sidecar),不创造变量
|
||||
|
||||
## 符号表一行
|
||||
|
||||
```cpp
|
||||
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` 本期未用,保留)。
|
||||
|
||||
## 链接流程
|
||||
|
||||
```text
|
||||
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`:
|
||||
|
||||
```text
|
||||
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)
|
||||
```
|
||||
|
||||
## 完成标准
|
||||
|
||||
1. 用例 9(同一槽)、10(非 GVL 写全局)、11(同名全局)、12(io.var 未声明)、16(未声明实例)、19(循环调用)按表变绿
|
||||
2. 符号表能打印 `name kind type address file`
|
||||
3. 全部构建 + `ctest` 无回归
|
||||
|
||||
---
|
||||
|
||||
## 执行计划(单步确认)
|
||||
|
||||
1. **`Linker.h`(新建 `compiler/include/compiler/Linker.h`)**:`Symbol`/`SymbolKind` + 链接结果结构 + `link_project` 声明
|
||||
2. **`Linker.cpp` 前半**:收集导出(gvl 校验 / 全局定槽 / POU 登记)+ 逐 POU 局部符号
|
||||
3. **`Linker.cpp` 后半**:引用解析(FB 实例 / 字段 / 变量 / 函数)+ 循环检测 + io 校验
|
||||
4. **`linker_test`(`tests/src/linker_test.cpp` + CMake)**:用例 09/10/11/12/16/19 + 符号表打印断言
|
||||
5. **验证**:`cmake --build` + `ctest`(新增 `linker_links` 后 7/7 全绿)
|
||||
6. **提交**:`Linker.h/cpp` + `linker_test` + 文档
|
||||
@@ -10,6 +10,7 @@ doc/
|
||||
compiler/编译管线.md
|
||||
compiler/词法.md
|
||||
compiler/语法.md
|
||||
compiler/符号表与链接.md
|
||||
vm/扫描周期.md
|
||||
executor/执行器入口.md
|
||||
```
|
||||
@@ -21,6 +22,7 @@ doc/
|
||||
| [`compiler/编译管线.md`](compiler/编译管线.md) | 编译器边界与管线 |
|
||||
| [`compiler/词法.md`](compiler/词法.md) | 12.4 词法:关键字表、token、大小写、TIME 字面量 |
|
||||
| [`compiler/语法.md`](compiler/语法.md) | 12.5 语法:AST 结构、文法、拒绝清单 |
|
||||
| [`compiler/符号表与链接.md`](compiler/符号表与链接.md) | 12.6 符号表与链接:数据区定址、FB 布局、错误类别 |
|
||||
| [`vm/扫描周期.md`](vm/扫描周期.md) | 扫描周期与 VM 边界 |
|
||||
| [`executor/执行器入口.md`](executor/执行器入口.md) | 可执行入口:加载 `.stb` + sidecar,跑扫描周期 |
|
||||
|
||||
|
||||
Reference in New Issue
Block a user