Files
Interpreter/Doc/初步计划.md
T
2026-08-18 16:14:28 +08:00

330 lines
8.6 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.
# 子集 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 推进 TON / TOF / CTU(不用 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. 寄存器机与指令
- 局部 / `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_CTU
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. 目录
```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、回放
```
---
## 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. 实现顺序
1. 冻 toml 字段:`files` / `entry` / `cycle_limit` / `dt_ms` / `gvl.file` / 可选 `io.*`
2. 冻关键字表 + 约 20 个样例程序
3. 冻约 20 条指令编码
4. 冻符号表字段:名字、种类、类型、地址、源文件
5. 递归下降解析 → 符号表与链接 → 最后写 VM 循环