细化第一版实现顺序,并与仓库目录对齐。
把第 12 节拆成可验收阶段,目录改为 isa/compiler/vm/host。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
+1
-1
@@ -4,6 +4,6 @@
|
||||
|
||||
| 文件 | 内容 |
|
||||
|---|---|
|
||||
| [`初步计划.md`](初步计划.md) | 定案:子集 ST、toml 工程、寄存器 VM、执行模型、ST 子集、实现顺序 |
|
||||
| [`初步计划.md`](初步计划.md) | 定案:子集 ST、toml 工程、寄存器 VM、执行模型、ST 子集;第 12 节为分阶段实现顺序 |
|
||||
|
||||
仓库根目录 [`README.md`](../README.md) 只说明目录和模块边界;语言规则、映像与扫描周期的细节以本目录为准。
|
||||
|
||||
+260
-13
@@ -1,4 +1,4 @@
|
||||
# 子集 ST + toml 工程 + 寄存器 VM
|
||||
# StScan:子集 ST + toml 工程 + 寄存器 VM
|
||||
|
||||
独立方案说明。调研全文见 `PLC-DSL.md`。
|
||||
|
||||
@@ -269,14 +269,15 @@ RET
|
||||
|
||||
## 10. 目录
|
||||
|
||||
仓库布局见根目录 `README.md`。实现时按四个 CMake 目标拆,样例不放仓库根:
|
||||
|
||||
```text
|
||||
project.toml # files / gvl.file / 可选 io
|
||||
globals.st # 第一版唯一 GVL
|
||||
motor.st
|
||||
main.st
|
||||
compiler/ # lexer parser symbols checker codegen linker
|
||||
vm/ # 扫描周期、寄存器、映像、FB
|
||||
host/ # 喂 I、收 Q、Δt、回放
|
||||
isa/ # 指令编码、映像头、定宽类型(compiler 与 vm 的合同)
|
||||
compiler/ # lexer parser symbols checker codegen linker
|
||||
vm/ # 扫描周期、寄存器、映像、FB
|
||||
host/ # 喂 I、收 Q、Δt、回放
|
||||
examples/line1/ # 第 11 节最小样例(给人看)
|
||||
tests/ # 冻规则用的正例 / 负例
|
||||
```
|
||||
|
||||
---
|
||||
@@ -322,8 +323,254 @@ END_PROGRAM
|
||||
|
||||
## 12. 实现顺序
|
||||
|
||||
1. 冻 toml 字段:`files` / `entry` / `cycle_limit` / `dt_ms` / `gvl.file` / 可选 `io.*`
|
||||
2. 冻关键字表 + 约 20 个样例程序
|
||||
3. 冻约 20 条指令编码
|
||||
4. 冻符号表字段:名字、种类、类型、地址、源文件
|
||||
5. 递归下降解析 → 符号表与链接 → 最后写 VM 循环
|
||||
原则:**先冻合同,再解析,再链接,最后写 VM**。`compiler` 与 `vm` 不互链,合同只进 `isa`。每一阶段先有可运行的测试,再进入下一阶段。一人约 4~8 周做到第 11 节 `line1`。
|
||||
|
||||
第一版不做:图染色、SSA、WHILE 有界证明、异常表、闭包、优化、`REAL`、独立 `stc.exe`。
|
||||
|
||||
---
|
||||
|
||||
### 12.0 骨架(约 1 天)
|
||||
|
||||
CMake 四个目标能空编译:`isa`、`compiler`、`vm`、`host`。`host` 先打印版本即可。
|
||||
|
||||
- 落下 `examples/line1/`:第 3 节 toml + 第 11 节三个 `.st`。
|
||||
- `tests/` 接上 CTest,先放一个必过的空测试。
|
||||
- 溢出策略当场选定并写进 `isa` 注释:**饱和**或**报错**,只留一种。
|
||||
|
||||
完成:`cmake --build` 过;`ctest` 能跑。
|
||||
|
||||
---
|
||||
|
||||
### 12.1 冻合同(约 2~3 天,先于任何解析器)
|
||||
|
||||
四张表写进代码或 `Doc/`,改表要同步改测试。未冻完不要写递归下降。
|
||||
|
||||
**toml 字段(只这些)**
|
||||
|
||||
| 字段 | 含义 | 缺省 / 约束 |
|
||||
|---|---|---|
|
||||
| `project.name` | 工程名 | 必填 |
|
||||
| `project.entry` | 入口 | 第一版必须是 `program MAIN` |
|
||||
| `project.cycle_limit` | 每周期指令上限 | 如 `100000`;超时是故障 |
|
||||
| `project.dt_ms` | 本周期 Δt | 给 TON / TOF / CTU,不是 wall clock |
|
||||
| `files.st` | 源文件列表 | 编译前全部已知 |
|
||||
| `gvl.file` | 唯一允许 `VAR_GLOBAL` 的文件 | 已在 `files.st` 则不重复收录 |
|
||||
| `[[io.input]]` / `[[io.output]]` | `var` + `channel` + `bit` | 可选;`var` 必须已在 GVL |
|
||||
|
||||
禁止 toml 里用 `[[globals]]` 再声明变量。
|
||||
|
||||
**关键字(第一版只认这些)**
|
||||
|
||||
`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`
|
||||
`AND` `OR` `NOT`
|
||||
`TRUE` `FALSE`
|
||||
`TON` `TOF` `CTU`
|
||||
|
||||
其余一律当标识符或直接拒绝(`VAR_IN_OUT`、`REF`、`CLASS` 等出明确错误)。
|
||||
|
||||
**符号表一行**
|
||||
|
||||
`name` / `kind` / `type` / `address` / `source_file`
|
||||
`kind` 至少:`global` / `external` / `local` / `input` / `output` / `pou` / `fb_instance` / `const`。
|
||||
|
||||
**指令(进 `isa`,约 20 条)**
|
||||
|
||||
第 7 节那张表:编码宽度、立即数格式、`fn_id` 是立即数、跳转是相对 offset。
|
||||
映像头:函数表、常量表、全局槽数、FB 槽布局、`cycle_limit`、`dt_ms`、工程哈希。
|
||||
工程哈希:收录的源文件排序后算,写入映像,回放时槽位必须一致。
|
||||
|
||||
完成:`isa` 能编解码一条指令;头文件即规范,不再另写一份互相打架的文档。
|
||||
|
||||
---
|
||||
|
||||
### 12.2 约 20 个样例(与 12.1 同时列出,随后逐个点亮)
|
||||
|
||||
每个用例一个子目录(`project.toml` + `.st`)。先写文件和期望(通过 / 报哪类错),再实现。`examples/line1` 给人看;负例和切片放 `tests/`。
|
||||
|
||||
| # | 用例 | 期望 |
|
||||
|---|---|---|
|
||||
| 1 | 空 `PROGRAM MAIN` + `RET` | 通过 |
|
||||
| 2 | `BOOL` 赋值、`TRUE`/`FALSE` | 通过 |
|
||||
| 3 | `AND`/`OR` 短路(右侧有副作用时不执行) | 通过 |
|
||||
| 4 | `IF` / `ELSIF` / `ELSE` | 通过 |
|
||||
| 5 | `WHILE` 正常结束 | 通过 |
|
||||
| 6 | `WHILE` 打满 `cycle_limit` | 运行时故障 |
|
||||
| 7 | `INT` 四则 + 比较 | 通过 |
|
||||
| 8 | `TIME` 字面量 | 通过 |
|
||||
| 9 | GVL + `VAR_EXTERNAL` | 通过,同一槽 |
|
||||
| 10 | 非 GVL 文件写 `VAR_GLOBAL` | 编译错误 |
|
||||
| 11 | 同名 `VAR_GLOBAL` | 链接失败 |
|
||||
| 12 | `io.var` 未在 GVL | 编译错误 |
|
||||
| 13 | `FUNCTION` 纯计算 + `CALL` | 通过 |
|
||||
| 14 | `FUNCTION` 写全局 | 编译错误 |
|
||||
| 15 | FB 实例 + `fb.Q` + 命名输入 | 通过 |
|
||||
| 16 | 未声明实例就调 FB | 编译错误 |
|
||||
| 17 | `TON` + 固定 Δt | 到时 `Q` 为真 |
|
||||
| 18 | `TOF` / `CTU` | 行为与 Δt 一致 |
|
||||
| 19 | 函数体循环调用 / 无界递归 | 编译错误 |
|
||||
| 20 | `line1`(第 11 节) | I 组合 → `Q0_0` 与急停语义一致 |
|
||||
|
||||
完成:目录和期望表齐;此时大部分还编不过,属正常。
|
||||
|
||||
---
|
||||
|
||||
### 12.3 toml 读取(`compiler`,约 2 天)
|
||||
|
||||
还不碰 `.st` 正文。
|
||||
|
||||
- 解析第 3 节字段;未知键报错。
|
||||
- 拼出编译文件集合:`files.st` ∪ `gvl.file`,去重、记绝对/相对路径。
|
||||
- 校验:缺文件、`entry` 不是 `program MAIN`、`io` 缺 `var`/`channel`/`bit`。
|
||||
- 算出工程哈希(文件集合排序后);先对源文件算,映像还没有也没关系。
|
||||
|
||||
完成:对 `examples/line1/project.toml` 打出文件列表和哈希;缺文件的负例过。
|
||||
|
||||
---
|
||||
|
||||
### 12.4 词法(`compiler`,约 2 天)
|
||||
|
||||
- `(* *)` 注释、标识符、关键字、整数、`TIME` 字面量、`:=`、比较符、括号。
|
||||
- 关键字与标识符分词;大小写规则写死(建议与 IEC 一样不敏感,实现时统一折成一种内部形)。
|
||||
- 每个 token 带 `source_file` + 行列,供以后报错。
|
||||
|
||||
完成:把第 11 节三个文件 lex 成稳定 token 流;非法字符报错。
|
||||
|
||||
---
|
||||
|
||||
### 12.5 递归下降语法(`compiler`,约 4~6 天)
|
||||
|
||||
只出 AST,不分配寄存器、不写映像。
|
||||
|
||||
顺序:
|
||||
|
||||
1. POU 外壳:`PROGRAM` / `FUNCTION` / `FUNCTION_BLOCK` 与对应 `END_*`
|
||||
2. 变量段:`VAR` / `VAR_INPUT` / `VAR_OUTPUT` / `VAR_GLOBAL` / `VAR_EXTERNAL`
|
||||
3. 类型名只接受 `BOOL` `INT` `TIME`;内置 FB 名 `TON` `TOF` `CTU`
|
||||
4. 语句:赋值、`IF`、`WHILE`、FB 调用 `fb(in := …)`、字段 `fb.Q`
|
||||
5. 表达式:`NOT`、`AND`/`OR`(AST 里保留短路语义)、比较、加减乘除
|
||||
|
||||
拒绝时给明确错误,不要 silently 忽略:`VAR_IN_OUT`、指针/`REF`、`CLASS`、`ANY`、用户类型套娃。
|
||||
|
||||
完成:`line1` 三个文件能 parse;用例 10、14、19 若已能在语法/结构层拒绝则先红后绿。
|
||||
|
||||
---
|
||||
|
||||
### 12.6 符号表与链接(`compiler`,约 3~4 天)
|
||||
|
||||
```text
|
||||
收集导出 → 校验 GVL → 解析 VAR_EXTERNAL → 定地址 → 校验 io.*
|
||||
```
|
||||
|
||||
- 先扫完全部 POU 再解析引用,禁止边解析边执行。
|
||||
- 仅 `gvl.file` 可出现 `VAR_GLOBAL`;声明顺序 = 全局槽顺序。
|
||||
- `VAR_EXTERNAL` 接已有槽,种类/类型必须一致,不是新变量。
|
||||
- `FUNCTION` 标成无状态;`FUNCTION_BLOCK` 必须先有实例才能调用。
|
||||
- `CALL` 目标解析成 `fn_id`(立即数),无函数指针。
|
||||
- 同名全局、未声明 `io.var`、重复 POU 名 → 失败。
|
||||
- FB 实例:按类型算出固定布局(输入/输出/内部/内置 TON 等字段偏移)。
|
||||
|
||||
完成:用例 9~12、16 按表变绿;符号表能打印 `name kind type address file`。
|
||||
|
||||
---
|
||||
|
||||
### 12.7 类型检查(`compiler`,约 2 天)
|
||||
|
||||
- `:=`、比较、算术的类型规则;`AND`/`OR`/`NOT` 只吃 `BOOL`。
|
||||
- `FUNCTION` 禁止写全局(含经 `VAR_EXTERNAL`)。
|
||||
- FB 调用:命名输入类型匹配;`fb.Q` 等字段存在且类型对。
|
||||
- 第一版无隐式宽化;`INT` 与 `TIME` 不混用。
|
||||
|
||||
完成:类型错误有稳定报文;合法 `line1` AST 带类型。
|
||||
|
||||
---
|
||||
|
||||
### 12.8 寄存器码(`compiler`,约 5~7 天)
|
||||
|
||||
局部和 `VAR` 固定寄存器;表达式临时值往后编号;`nregs` 写进函数头。跳转先留洞,再回填。`AND`/`OR` **必须编成跳转**,禁止两边都算。
|
||||
|
||||
建议按指令面切片,每片都能 `isa` 编解码、必要时用临时 VM 桩执行:
|
||||
|
||||
1. `MOVE` `LOADK` `RET`,空 `MAIN` 出映像(对应用例 1~2)
|
||||
2. `LOAD_I` `STORE_Q` `LOAD_GLOBAL` `STORE_GLOBAL` `LOAD_M` `STORE_M`
|
||||
3. `NOT`,短路 `AND`/`OR`(用例 3)
|
||||
4. `CMP_*` `JMP` `JT`/`JF` → `IF`(用例 4)
|
||||
5. `WHILE` + 映像里带上 `cycle_limit`(用例 5;超限等 VM)
|
||||
6. `ADD`/`SUB`/`MUL`/`DIV`(用例 7~8)
|
||||
7. `CALL fn_id` + 有界帧(用例 13)
|
||||
8. FB 实例块 + 字段偏移;`CAL_TON` / `CAL_TOF` / `CAL_CTU`(用例 15、17、18)
|
||||
|
||||
完成:`line1` 能写出映像(函数字节码 + 全局/FB 槽 + 工程哈希);跳转回填无悬空。
|
||||
|
||||
---
|
||||
|
||||
### 12.9 VM 循环(`vm`,约 3~4 天)
|
||||
|
||||
此时才写执行循环。只认 `isa` 映像。
|
||||
|
||||
```text
|
||||
采样 I → pc=0 执行到 RET → 写回 Q → 用本周期 Δt 推进 TON/TOF/CTU
|
||||
```
|
||||
|
||||
- 译码 + 大 `switch`;寄存器文件按函数头 `nregs` 分配。
|
||||
- 每周期计指令数,超过 `cycle_limit` 报故障(用例 6)。
|
||||
- 调用栈有界;`CALL`/`RET` 恢复 `pc` 与帧。
|
||||
- 无 GC、无堆、无线程。同一映像 + 同一组 `I` + 同一 `Δt` → `Q`/`M`/FB 一致。
|
||||
- 内置 FB:`CAL_TON`/`CAL_TOF`/`CAL_CTU` 只用映像里的 Δt,不读系统时钟。
|
||||
|
||||
完成:对手工拼的小映像能跑赋值和跳转;再接编译器产出的映像。
|
||||
|
||||
---
|
||||
|
||||
### 12.10 host(约 2~3 天)
|
||||
|
||||
一条命令:读 toml → `compiler` → `vm` 跑 N 个周期。不拆第二个可执行文件。
|
||||
|
||||
- 按 `[[io.*]]` 从宿主缓冲采样 / 写回,不创造变量。
|
||||
- 把 `dt_ms` 每周期传给 VM。
|
||||
- 最小可观测:打印本周期 I/Q,或把寄存器/映像 dump 出来。
|
||||
- 回放:读预先录制的 I 序列(文本即可),不接真实硬件。
|
||||
|
||||
完成:`host examples/line1/project.toml` 能编译并跑若干周期。
|
||||
|
||||
---
|
||||
|
||||
### 12.11 闭环与冻测试(约 1 周)
|
||||
|
||||
- 点亮第 12.2 节 20 个用例;负例检查报错类别,不查英文原文。
|
||||
- `line1` 固定输入表:急停、start/stop 组合 → 期望 `Q0_0`。
|
||||
- 回放:同一 I 序列跑两遍,槽位与哈希一致。
|
||||
- 单步:能停在指令边界,看到寄存器和 I/Q/M(可先做打印,不必做 UI)。
|
||||
|
||||
完成标准(第一版可交付):**`MAIN` + 一个 Motor FB + I/O + 全局**,扫描周期可重复。
|
||||
|
||||
---
|
||||
|
||||
### 12.12 建议日历
|
||||
|
||||
| 周 | 做完 |
|
||||
|---|---|
|
||||
| 1 | 12.0~12.4:骨架、合同、样例清单、toml、词法 |
|
||||
| 2 | 12.5:AST,能 parse `line1`,拒绝多余特性 |
|
||||
| 3 | 12.6~12.7:链接、定址、类型、io 校验 |
|
||||
| 4 | 12.8 前半 + 12.9 前半:赋值/短路/IF 出映像,VM 能跑 |
|
||||
| 5 | 12.8 后半 + 12.10:CALL、FB、host 跑起来 |
|
||||
| 6 | 定时器/计数器、io、Δt、`line1` 语义 |
|
||||
| 7~8 | 20 用例、回放、单步、补漏 |
|
||||
|
||||
周 4 结束时就应能跑一个「读 I、写 Q」的假 MAIN。不要等 FB 和 TON 齐了才第一次执行。
|
||||
|
||||
---
|
||||
|
||||
### 12.13 依赖方向(全程遵守)
|
||||
|
||||
```text
|
||||
host → compiler → isa
|
||||
host → vm → isa
|
||||
```
|
||||
|
||||
- 指令或映像布局有变:先改 `isa` 和编解码测试,再改两端。
|
||||
- 新 ST 特性:先加 12.2 的用例行,再改关键字 / AST / 检查 / 码 / VM。
|
||||
- 不要为了一个 pass 新建 CMake 库。
|
||||
|
||||
Reference in New Issue
Block a user