Files
Interpreter/Doc/初步计划.md
T
Admin 6873b5ee0b 文档同步:machine.toml 配置驱动架构收尾。
- README.md:依赖方向(STCompiler→compiler 不链 isa)、模块表、isa 归执行器侧、
  数据流图(machine.toml + 型号/SHA)
- isa/README.md:重写——只管指令定义(Types/Op/Instr/Encode),删 Image 部分
- 指令与映像.md:依赖图与 CMake(compiler 不链 isa)
- 编译管线.md:管线首步加载 machine.toml、产物含型号/SHA
- 初步计划.md:12.10/12.13 修订(--machine、依赖方向)
- 扫描周期.md / 指令执行.md:12.9 步骤标注已落地、vm 读校验说明
- compiler/vm/executor README:依赖与边界同步
2026-08-21 22:39:27 +08:00

589 lines
20 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# StScan:子集 ST + toml 工程 + 寄存器 VM
独立方案说明。调研全文见 `PLC-DSL.md`
**定案:** IEC 风格的子集 ST,用 `.toml` 组织多个 `.st`,编译成单一映像,由寄存器虚拟机按扫描周期执行。
---
## 1. 做什么、不做什么
```text
多个 .st 源文件
↑ 由 project.toml 列出
编译器(词法/语法 → 符号表 → 类型检查 → 寄存器码 → 链接)
单一映像(函数字节码 + 全局/FB 数据槽 + 工程哈希)
寄存器虚拟机,按扫描周期执行
```
**做**
- 子集 ST(不是完整 IEC
- `.toml` 管工程,不声明变量
- 寄存器 VM + 扫描周期
- 有界 `CALL` / `RET`
- 全局只在指定 GVL 文件里声明
**不做**
- 「很像 ST 的 Python」
- 完整 IEC:指针、OOP、`ANY``VAR_IN_OUT`、间接寻址
- CPython 特化 / JIT / 真 Python 模块系统
- 第一版不做梯形图编辑器(以后编译到同一套 IR)
指令表可作为内部 IR 的文本形态,不必当主语言。
---
## 2. 执行模型
```text
每个扫描周期:
1. 按 toml 的 io 绑定,采样 → I / 输入全局
2. 从 PROGRAM MAIN 的 pc=0 执行到 RET
3. 写回 Q / 输出全局
4. 用本周期 Δt 推进内置 FBTON/TOF/TP/CTU/CTD/CTUD/R_TRIG/F_TRIG,不用 wall clock
```
运行时是 **一台 VM、一个入口、一份映像**。多文件只影响源码怎么切、怎么链接。
---
## 3. 工程文件(toml
toml **不声明 ST 变量**。只做三件事:列出源文件、指定唯一 GVL、(可选)把已声明的全局接到硬件。
```toml
[project]
name = "line1"
entry = "program MAIN"
cycle_limit = 100000
dt_ms = 10
[files]
st = ["globals.st", "motor.st", "main.st"]
# 全工程只允许这一处出现 VAR_GLOBAL(第一版:单个文件)
[gvl]
file = "globals.st"
# 可选:I/O 绑定。不创造变量,var 必须已在 GVL 里声明
[[io.input]]
var = "EmergencyStop"
channel = 0
bit = 2
[[io.input]]
var = "I0_0"
channel = 0
bit = 0
[[io.output]]
var = "Q0_0"
channel = 0
bit = 0
```
规则:
- 所有源文件编译前已知;无运行时加载。
- `gvl.file` 自动并入编译集合(已在 `files.st` 里则不重复)。
- 文件集合排序后算 **工程哈希**,写入映像,回放时槽位一致。
- 同名 `VAR_GLOBAL` 链接失败,禁止覆盖。
- `[[io.*]]``var` 未在 GVL 中出现 → 编译错误。
- 第一版禁止:模块级副作用、实例/函数体循环调用、函数指针、无界递归。
不要在 toml 里用 `[[globals]] name/type/init` 再声明变量。
名字和类型属于 ST;针脚和通道属于工程。
---
## 4. ST 子集(第一版)
**保留**
| 类别 | 内容 |
|---|---|
| POU | `PROGRAM` / `FUNCTION` / `FUNCTION_BLOCK` |
| 变量段 | `VAR` / `VAR_INPUT` / `VAR_OUTPUT` / `VAR_GLOBAL` / `VAR_EXTERNAL` |
| 类型 | `BOOL``INT``TIME`(以后再加 `DINT` / 定点数或 `REAL` |
| 运算 | `:=``AND` `OR` `NOT`,比较,算术 |
| 控制 | `IF / ELSIF / ELSE / END_IF``WHILE … END_WHILE` |
| 功能块 | 内置 `TON` / `TOF` / `CTU``fb(in := …)`,字段 `fb.Q` |
`WHILE` 不做终止证明,靠 `cycle_limit` 卡死。
`AND` / `OR` **必须短路**,编成跳转;两边都算会让定时器、FB 多执行。
**第一版不做:** 指针、`REF`、接口、类、`VAR_IN_OUT``ANY`、间接寻址、用户类型套娃、`FUNCTION` 改全局。
---
## 5. 全局变量
ST 有全局:`VAR_GLOBAL` 声明,`VAR_EXTERNAL` 引用。`I` / `Q` / `M` 也是全局映像。
`FUNCTION` 第一版禁止写全局。
### 5.1 在哪个文件声明
**第一版:只有 `globals.st` 可以出现 `VAR_GLOBAL`。**
多文件指 PROGRAM / FB / FUNCTION 分文件,不是全局可以散落声明。
| 种类 | 放哪 | 几个文件 |
|---|---|---|
| `VAR_GLOBAL` / `CONSTANT` / `RETAIN` | `globals.st` | **1 个** |
| `I` / `Q` / `M` | 同一份 `globals.st` | 第一版不拆 |
| FB / PROGRAM 的 `VAR` | 各自的 `.st` | 多个(局部 / 实例) |
| `VAR_EXTERNAL` | 各 POU 的 `.st` | 多个(只引用) |
其他文件写 `VAR_GLOBAL` → 编译错误。
单文件的好处:声明顺序 = 地址顺序,不必再规定多 GVL 的排序和回放。
以后要拆,仍须 toml **显式列出全部 GVL**,按类别拆,不按功能块拆:
```toml
[gvl]
files = ["io.st", "globals.st"]
```
```text
io.st 只放 I/Q/M (以后才拆)
globals.st 只放命名全局
motor.st 只有 FB + VAR_EXTERNAL
main.st 只有 PROGRAM
```
不要 `motor_globals.st``pump_globals.st` 各写一套同名点。
### 5.2 寿命
| 种类 | 寿命 |
|---|---|
| `VAR` / `VAR_TEMP` | 本次调用或该 POU 内部 |
| FB 的 `VAR` | 跟着实例(各一份) |
| `VAR_GLOBAL` | 全工程一份 |
| `I/Q/M` | 全局,绑定 I/O 映像 |
`VAR_EXTERNAL` 接到已有槽,不是新变量。
第一版按 `globals.st` 声明顺序定址;以后若 `[gvl] files`:先 toml 列表,再文件内顺序。
### 5.3 编译器数据流
```text
project.toml
files.st → 要编译的源
gvl.file → 唯一允许 VAR_GLOBAL 的文件
io.* → 名字 → 硬件(可选,不创造变量)
globals.st
VAR_GLOBAL / CONSTANT / I/Q/M → 符号表 + 固定槽位
motor.st / main.st
VAR_EXTERNAL → 同一槽
```
---
## 6. 多文件导出什么
| 导出 | 规则 |
|---|---|
| `FUNCTION` | 无内部状态;寄存器帧在有界调用栈上 |
| `FUNCTION_BLOCK` | 必须先声明实例 |
| `VAR_GLOBAL``I/Q/M` | 全工程一份,只在 GVL 里声明 |
| 常量 | 进常量表 |
`CALL` 目标都是立即数 `fn_id`,没有函数指针。
---
## 7. 寄存器机与指令
编码宽度、操作码取值、映像头与定宽类型以 [`isa/指令与映像.md`](isa/指令与映像.md) 为准。本节只列助记符。
- 局部 / `VAR` = 固定寄存器;表达式临时值往后编号;`nregs` 写进函数头。
- `I/Q/M` 走映像指令,不是通用寄存器。
- 每个 FB 实例一块固定布局,字段偏移编译期算死。
```text
MOVE rd, rs
LOADK rd, const_id
NOT rd, rs
AND/OR rd, ra, rb (* 已算好的值;短路用跳转 *)
ADD/SUB/MUL/DIV rd, ra, rb
CMP_xx rd, ra, rb (* 结果 BOOL *)
JMP offset
JT/JF r, offset
LOAD_I / STORE_Q / LOAD_M / STORE_M
LOAD_GLOBAL / STORE_GLOBAL
CAL_TON / CAL_TOF / CAL_TP / CAL_CTU / CAL_CTD / CAL_CTUD / CAL_R_TRIG / CAL_F_TRIG
CALL fn_id
RET
```
寄存器机不会比栈机更确定。确定性来自类型、内存、时间和副作用。
---
## 8. 确定性与可控
- 同一程序 + 同一组 `I` + 同一 `Δt``Q``M`、FB 状态完全一致。
- 无 GC、无堆、无线程;变量编译期全部分配。
- 定宽整数;溢出规则写死(饱和或报错,选一个)。
- 第一版不要 `REAL`
- 每周期指令上限(如 `cycle_limit = 100000`),超时是故障。
- 可单步、可看寄存器和映像、可按录制的 `I` 回放。
---
## 9. 编译器
中等工程,不是算法难题。直通 pass,不上 LLVM:
```text
读 toml、收齐 .st
→ 词法 / 递归下降语法
→ 收集导出符号
→ 解析 VAR_EXTERNAL、链接、定地址
→ 类型检查
→ 按 PROGRAM / FUNCTION / FB 编寄存器码
→ 回填跳转、写映像头与工程哈希
```
| 部分 | 难度 |
|---|---|
| ST 解析 | 中低 |
| 拒多余特性 | 低 |
| 多文件链接 | 中 |
| 类型检查 | 低~中 |
| 寄存器码(短路、IF/WHILE | 中 |
| FB 布局与调用 | 中 |
| VM | 低 |
第一版不做:图染色分配、SSA、证明 WHILE 有界、异常表、闭包、优化。
一人约 **48 周** 做出 `MAIN + 一个 Motor FB + I/O + 全局`
---
## 10. 目录
仓库布局见根目录 `README.md`。实现时按模块拆 CMake 目标(三个库 + 两个可执行),样例不放仓库根:
```text
isa/ # 指令编码、映像头、定宽类型(compiler 与 vm 的合同)
compiler/ # lexer parser symbols checker codegen linkerSTCompiler 入口在 src/main.cpp
vm/ # 扫描周期、寄存器、映像、FB(库)
executor/ # BytecodeExecutor:加载 .stb + sidecar,喂 I、收 Q、Δt、回放
examples/line1/ # 第 11 节最小样例(给人看)
tests/ # 冻规则用的正例 / 负例
```
---
## 11. 最小样例
`project.toml` 见第 3 节。
```st
(* globals.st *)
VAR_GLOBAL
EmergencyStop : BOOL := FALSE;
I0_0 : BOOL;
I0_1 : BOOL;
Q0_0 : BOOL;
END_VAR
(* motor.st *)
FUNCTION_BLOCK MotorStarter
VAR_EXTERNAL
EmergencyStop : BOOL;
END_VAR
VAR_INPUT
start, stop : BOOL;
END_VAR
VAR_OUTPUT
Q : BOOL;
END_VAR
Q := start AND NOT stop AND NOT EmergencyStop;
END_FUNCTION_BLOCK
(* main.st *)
PROGRAM MAIN
VAR
starter : MotorStarter;
END_VAR
starter(start := I0_0, stop := I0_1);
Q0_0 := starter.Q;
END_PROGRAM
```
---
## 12. 实现顺序
原则:**先冻合同,再解析,再链接,最后写 VM**。`compiler``vm` 不互链,合同只进 `isa`。每一阶段先有可运行的测试,再进入下一阶段。一人约 4~8 周做到第 11 节 `line1`
第一版不做:图染色、SSA、WHILE 有界证明、异常表、闭包、优化、`REAL`
---
### 12.0 骨架(约 1 天)
CMake 三个库能空编译:`isa``compiler``vm`;两个可执行 `STCompiler``BytecodeExecutor` 先打印版本即可。
- 落下 `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 | 给内置 FB 定时器,不是 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`
`THEN` `DO` 12.5 修订补入:`IF…THEN` / `WHILE…DO` 语法需要)
`AND` `OR` `NOT`
`TRUE` `FALSE`
`TON` `TOF` `TP` `CTU` `CTD` `CTUD` `R_TRIG` `F_TRIG` (12.11 修订:内置 FB 扩为 8 个)
其余一律当标识符或直接拒绝(`VAR_IN_OUT``REF``CLASS` 等出明确错误)。
**符号表一行**
`name` / `kind` / `type` / `address` / `source_file`
`kind` 至少:`global` / `external` / `local` / `input` / `output` / `pou` / `fb_instance` / `const`
**指令(进 `isa`,约 20 条)**
布局、操作码、映像、哈希、饱和以 [`isa/指令与映像.md`](isa/指令与映像.md) 为准。第 7 节只列助记符。
完成:`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`,约 46 天)
只出 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` `TP` `CTU` `CTD` `CTUD` `R_TRIG` `F_TRIG` (12.11 修订:内置 FB 扩为 8 个)
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`,约 34 天)
```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`,约 57 天)
局部和 `VAR` 固定寄存器;表达式临时值往后编号;`nregs` 写进函数头。跳转先留洞,再回填。`AND`/`OR` **必须编成跳转**,禁止两边都算。
建议按指令面切片,每片都能 `isa` 编解码、必要时用临时 VM 桩执行:
1. `MOVE` `LOADK` `RET`,空 `MAIN` 出映像(对应用例 12
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`(用例 78
7. `CALL fn_id` + 有界帧(用例 13
8. FB 实例块 + 字段偏移;`CAL_TON` / `CAL_TOF` / `CAL_CTU`(用例 15、17、18
完成:`line1` 能写出映像(函数字节码 + 全局/FB 槽 + 工程哈希);跳转回填无悬空。
---
### 12.9 VM 循环(`vm`,约 34 天)
此时才写执行循环。只认 `isa` 映像。
```text
采样 I → pc=0 执行到 RET → 写回 Q → 用本周期 Δt 推进内置 FB
```
- 译码 + 大 `switch`;寄存器文件按函数头 `nregs` 分配。
- 每周期计指令数,超过 `cycle_limit` 报故障(用例 6)。
- 调用栈有界;`CALL`/`RET` 恢复 `pc` 与帧。
- 无 GC、无堆、无线程。同一映像 + 同一组 `I` + 同一 `Δt``Q`/`M`/FB 一致。
- 内置 FB`CAL_TON`/`CAL_TOF`/`CAL_CTU` 只用映像里的 Δt,不读系统时钟。
完成:对手工拼的小映像能跑赋值和跳转;再接编译器产出的映像。
---
### 12.10 双可执行:STCompiler 与 BytecodeExecutor(约 23 天)
编译与执行拆成两个可执行,映像 `.stb` 是中间产物:
```text
STCompiler <project.toml> -o <name>.stb --machine <machine.toml>
BytecodeExecutor <name>.stb [--cycles N]
```
- `STCompiler``compiler`**不链 `isa`**,编码查 `machine.toml`);`BytecodeExecutor``vm` + `isa`**不链 `compiler`**。
- 产物两个文件:`<name>.stb`(头 104 含型号标识 + 段 + SHA-256 文件尾)+ `<name>.runtime.toml`sidecarI/O 绑定 var → 槽号 → channel/bit)。`[[io.*]]` 不进映像,sidecar 由 STCompiler 生成,不创造变量。
- `dt_ms` / `cycle_limit` 从映像头取,sidecar 不重复配置。
- 执行器读取时**型号匹配 + SHA-256 校验**,不匹配/篡改 → 拒绝。
- 最小可观测:BytecodeExecutor 打印本周期 I/Q,或把寄存器/映像 dump 出来。
- 回放:读预先录制的 I 序列(文本即可),不接真实硬件。
完成:`STCompiler examples/line1/project.toml -o line1.stb --machine compiler/machine.toml` 产出两个文件;`BytecodeExecutor line1.stb` 能跑若干周期。
> 12.13 修订:指令 opcode / 类型 / FB 布局的登记移入 `compiler/machine.toml`(见 [`指令配置.md`](isa/指令配置.md)),
> `STCompiler` 编译路径必填 `--machine`。
---
### 12.11 闭环与冻测试(约 1 周)
- 点亮第 12.2 节 20 个用例;负例检查报错类别,不查英文原文。
- `line1` 固定输入表:急停、start/stop 组合 → 期望 `Q0_0`
- 回放:同一 I 序列跑两遍,槽位与哈希一致。
- 单步:能停在指令边界,看到寄存器和 I/Q/M(可先做打印,不必做 UI)。
- TUI 界面**最后做**,v1 只预留接口:执行器(`BytecodeExecutor`)的观察界面,数据走 vm 观察 API(见 `Doc/vm/扫描周期.md`),展示与输出层分离(见 `Doc/executor/执行器入口.md`)。
完成标准(第一版可交付):**`MAIN` + 一个 Motor FB + I/O + 全局**,扫描周期可重复。
---
### 12.12 建议日历
| 周 | 做完 |
|---|---|
| 1 | 12.012.4:骨架、合同、样例清单、toml、词法 |
| 2 | 12.5AST,能 parse `line1`,拒绝多余特性 |
| 3 | 12.6~12.7:链接、定址、类型、io 校验 |
| 4 | 12.8 前半 + 12.9 前半:赋值/短路/IF 出映像,VM 能跑 |
| 5 | 12.8 后半 + 12.10CALL、FB、两个可执行跑起来 |
| 6 | 定时器/计数器、io、Δt、`line1` 语义 |
| 7~8 | 20 用例、回放、单步、补漏 |
周 4 结束时就应能跑一个「读 I、写 Q」的假 MAIN。不要等 FB 和 TON 齐了才第一次执行。
---
### 12.13 依赖方向(全程遵守)
```text
STCompiler → compiler (编码查 machine.toml;自带 .stb 写)
BytecodeExecutor → vm → isa isa 只管指令;vm 自带 .stb 读)
```
- 指令或映像布局有变:先改 `isa` 和编解码测试,再改两端。
- 新 ST 特性:先加 12.2 的用例行,再改关键字 / AST / 检查 / 码 / VM。
- 不要为了一个 pass 新建 CMake 库。