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

8.6 KiB
Raw Blame History

子集 ST + toml 工程 + 寄存器 VM

独立方案说明。调研全文见 PLC-DSL.md

定案: IEC 风格的子集 ST,用 .toml 组织多个 .st,编译成单一映像,由寄存器虚拟机按扫描周期执行。


1. 做什么、不做什么

多个 .st 源文件
    ↑ 由 project.toml 列出
    ↓
编译器(词法/语法 → 符号表 → 类型检查 → 寄存器码 → 链接)
    ↓
单一映像(函数字节码 + 全局/FB 数据槽 + 工程哈希)
    ↓
寄存器虚拟机,按扫描周期执行

  • 子集 ST(不是完整 IEC
  • .toml 管工程,不声明变量
  • 寄存器 VM + 扫描周期
  • 有界 CALL / RET
  • 全局只在指定 GVL 文件里声明

不做

  • 「很像 ST 的 Python」
  • 完整 IEC:指针、OOP、ANYVAR_IN_OUT、间接寻址
  • CPython 特化 / JIT / 真 Python 模块系统
  • 第一版不做梯形图编辑器(以后编译到同一套 IR)

指令表可作为内部 IR 的文本形态,不必当主语言。


2. 执行模型

每个扫描周期:
  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、(可选)把已声明的全局接到硬件。

[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
类型 BOOLINTTIME(以后再加 DINT / 定点数或 REAL
运算 :=AND OR NOT,比较,算术
控制 IF / ELSIF / ELSE / END_IFWHILE … END_WHILE
功能块 内置 TON / TOF / CTUfb(in := …),字段 fb.Q

WHILE 不做终止证明,靠 cycle_limit 卡死。
AND / OR 必须短路,编成跳转;两边都算会让定时器、FB 多执行。

第一版不做: 指针、REF、接口、类、VAR_IN_OUTANY、间接寻址、用户类型套娃、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,按类别拆,不按功能块拆:

[gvl]
files = ["io.st", "globals.st"]
io.st        只放 I/Q/M          (以后才拆)
globals.st   只放命名全局
motor.st     只有 FB + VAR_EXTERNAL
main.st      只有 PROGRAM

不要 motor_globals.stpump_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 编译器数据流

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_GLOBALI/Q/M 全工程一份,只在 GVL 里声明
常量 进常量表

CALL 目标都是立即数 fn_id,没有函数指针。


7. 寄存器机与指令

  • 局部 / VAR = 固定寄存器;表达式临时值往后编号;nregs 写进函数头。
  • I/Q/M 走映像指令,不是通用寄存器。
  • 每个 FB 实例一块固定布局,字段偏移编译期算死。
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 + 同一 ΔtQM、FB 状态完全一致。
  • 无 GC、无堆、无线程;变量编译期全部分配。
  • 定宽整数;溢出规则写死(饱和或报错,选一个)。
  • 第一版不要 REAL
  • 每周期指令上限(如 cycle_limit = 100000),超时是故障。
  • 可单步、可看寄存器和映像、可按录制的 I 回放。

9. 编译器

中等工程,不是算法难题。直通 pass,不上 LLVM:

读 toml、收齐 .st
  → 词法 / 递归下降语法
  → 收集导出符号
  → 解析 VAR_EXTERNAL、链接、定地址
  → 类型检查
  → 按 PROGRAM / FUNCTION / FB 编寄存器码
  → 回填跳转、写映像头与工程哈希
部分 难度
ST 解析 中低
拒多余特性
多文件链接
类型检查 低~中
寄存器码(短路、IF/WHILE
FB 布局与调用
VM

第一版不做:图染色分配、SSA、证明 WHILE 有界、异常表、闭包、优化。

一人约 48 周 做出 MAIN + 一个 Motor FB + I/O + 全局


10. 目录

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 节。

(* 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 循环