IEC 61131-3 Soft PLC Runtime and Structured Text Engine in MoonBit
curl -fsSL https://cli.moonbitlang.com/install/unix.sh | bashirm https://cli.moonbitlang.com/install/powershell.ps1 | iexmoon version
# 推荐版本 >= 0.1.20260915# 克隆仓库
git clone https://github.com/lycvvt/Moonplc.git
cd Moonplc
# 执行静态检查与依赖加载
moon check
# 编译项目与测试套件
moon testimport {
"lycvvt/moonplc"
}moon run cmd/main# 使用任意轻量 HTTP 伺服
npx serve web
# 或使用 python
python -m http.server 8080 --directory webimport "lycvvt/moonplc" as plc
fn main {
// 1. 实例化一个软 PLC 控制器
let controller = plc.MoonPlc::new("IndustrialUnit_01")
// 2. 加载 IEC 61131-3 结构化文本 (ST) 程序
let st_program =
#|PROGRAM ConveyorControl
#|VAR
#| Sensor_Item AT %IX0.0 : BOOL;
#| Piston_Push AT %QX0.0 : BOOL;
#|END_VAR
#|IF Sensor_Item THEN
#| Piston_Push := TRUE;
#|ELSE
#| Piston_Push := FALSE;
#|END_IF;
#|END_PROGRAM
controller.load_st(st_program)
// 3. 模拟外部传感器信号输入 (%IX0.0 置为 TRUE)
controller.set_input_bool("%IX0.0", true)
// 4. 执行一个 10ms 扫描周期
controller.cycle(10L)
// 5. 读取物理输出映像寄存器状态
let is_piston_active = controller.get_output_bool("%QX0.0")
println("Piston state: \{is_piston_active}") // true
// 6. 打印 PLC 内部完整 I/O 状态表与时基统计
println(controller.dump_status())
}Motor_Run := (Start_Btn OR Motor_Run) AND NOT Stop_Btn;IF Car_Present OR Pedestrian THEN
Light_Red := FALSE;
Light_Green := TRUE;
Timer_Pass(IN := TRUE, PT := T#3s);
IF Timer_Pass.Q THEN
Light_Green := FALSE;
Light_Amber := TRUE;
END_IF;
ELSE
Light_Red := TRUE;
Light_Green := FALSE;
Light_Amber := FALSE;
END_IF;moon fmt --check || moon fmt
moon checkmoon testTotal tests: 41, passed: 41, failed: 0.涵盖:数据类型编解码测试、ST 词法与语法分析测试、梯形图自锁电路编译测试、过程映像内存映射测试、TON/TOF/TP/TONR/CTU/CTUD 功能块测试、SR/RS 触发器、R_TRIG/F_TRIG 边沿检测、工业闭环 PID 控制器测试、Modbus TCP/RTU 现场总线协议编解码与 CRC16 校验测试、ST-to-IL 字节码编译器与 IL 虚拟机单步与跳转测试、以及 50,000 周期高频扫描基准压力测试,全量 41 项 100% 绿色通过。moon run cmd/main================================================================
IEC 61131-3 Soft PLC Runtime & Structured Text Simulator
Pure MoonBit Implementation | WebAssembly & Edge Ready
================================================================
[SCENARIO 1] Ladder Diagram: Motor Start-Stop Self-Hold Circuit
>> Step 1: Initial state -> Motor_Run = FALSE
>> Step 2: Press [START] Button -> Motor_Run = TRUE [MOTOR STARTED]
>> Step 3: Release [START] Button -> Motor_Run = TRUE [SELF-LATCH MAINTAINED]
>> Step 4: Press [STOP] Button -> Motor_Run = FALSE [MOTOR STOPPED]
[SCENARIO 2] Structured Text: Traffic Light Timing Control
>> Car Arrives! Triggering %IX0.0 = TRUE
[At 50ms] RED = false | GREEN = true | AMBER = false
[At 110ms] RED = false | GREEN = false | AMBER = true
[SCENARIO 3] Process Automation: Closed-Loop PID Temperature Control
>> Target Setpoint: 65.0°C | Initial Tank Temperature: 25.0°C
Step 1 | Heater Output = 81% | Tank Temp = 31°C
Step 2 | Heater Output = 98% | Tank Temp = 39°C
Step 3 | Heater Output = 85% | Tank Temp = 45°C
Step 4 | Heater Output = 76% | Tank Temp = 51°C
Step 5 | Heater Output = 67% | Tank Temp = 56°C
[SCENARIO 4] Fieldbus: Modbus TCP/RTU Protocol Telemetry
>> SCADA Host writes Coil 0 (%QX0.0) via Modbus FC 0x05
Response Error: false | %QX0.0 Status = true
>> SCADA Host writes Holding Register 40001 (%MW0) = 8848 via Modbus FC 0x06
Response Error: false | Written Value = 8848graph TD
subgraph Frontend["编译前端 (IEC 61131-3 Frontend)"]
ST[ST 结构化文本源码] --> Lexer[词法分析器 Lexer]
Lexer --> Parser[递归下降语法解析器 Parser]
LD[梯形图 LD 梯级数据] --> LDTrans[布尔代数转换器 LadderCompiler]
Parser --> AST[AST 抽象语法树]
LDTrans --> AST
end
subgraph Core["运行时与执行引擎 (MoonPLC Core)"]
AST --> Executor[AST 控制流与表达式求值]
FB[标准功能块库 TON/TOF/TP/CTU] <--> Executor
PID[工业闭环 PID 控制器] <--> Executor
MB[Modbus 通信协议栈] <--> Scheduler
IL[IL 字节码虚拟机] <--> Executor
subgraph Scheduler["周期调度器 (Scan Scheduler)"]
P1[1. 输入采样 Input Scan] --> P2[2. 逻辑执行 Execution]
P2 --> P3[3. 输出刷新 Output Scan]
end
Mem[(过程映像存储区 Memory)] <--> Scheduler
Executor <--> Mem
end
subgraph Target["外设与仿真 (Simulation & Targets)"]
Scheduler --> CLI[CLI 命令行测试器]
Scheduler --> WebUI[WebAssembly 工业数字孪生工作台]
endMoonplc/
├── moon.mod # MoonBit 模块定义
├── moon.pkg # 核心库包配置 (零告警选项)
├── pkg.generated.mbti # 自动生成的标准公共接口定义
├── types.mbt # IEC 基础类型与 %IX 寻址表示
├── types_wbtest.mbt # 类型白盒单测
├── ast.mbt # ST 抽象语法树
├── lexer.mbt # 词法分析器
├── lexer_wbtest.mbt # 词法分析白盒单测
├── parser.mbt # 递归下降语法解析器
├── parser_wbtest.mbt # 语法解析白盒单测
├── ladder.mbt # 梯形图数据结构与转 AST 引擎
├── ladder_wbtest.mbt # 梯形图白盒单测
├── memory.mbt # 过程映像区与变量符号表
├── memory_wbtest.mbt # 内存映像白盒单测
├── fblocks.mbt # 工业标准功能块 (TON/TOF/TP/CTU/SR/RS/R_TRIG/F_TRIG)
├── fblocks_wbtest.mbt # 功能块白盒单测
├── pid.mbt # 工业闭环 PID 控制器 (抗饱和/微分先行)
├── pid_wbtest.mbt # PID 闭环恒温仿真测试
├── modbus.mbt # 现场总线 Modbus TCP 协议编解码与寄存器映射
├── modbus_wbtest.mbt # Modbus TCP 协议白盒单测
├── modbus_rtu.mbt # Modbus RTU 物理帧编解码与 CRC16 校验
├── modbus_rtu_wbtest.mbt # Modbus RTU 报文与校验白盒单测
├── compiler.mbt # ST 到 IL 字节码编译器 (表达式/控制流)
├── compiler_wbtest.mbt # 编译器与虚拟机联调白盒单测
├── vm.mbt # IEC 61131-3 指令表 (IL) 字节码虚拟机
├── vm_wbtest.mbt # IL 虚拟机白盒单测
├── runtime.mbt # 周期调度引擎与解释执行器
├── runtime_wbtest.mbt # 周期扫描集成白盒测试
├── bench_wbtest.mbt # 50,000 周期高频扫描与总线基准压测套件
├── moonplc.mbt # 统一 Facade API
├── moonplc_wbtest.mbt # 端到端工作流白盒测试
├── cmd/
│ └── main/
│ ├── main.mbt # CLI 命令行仿真程序 (四大场景)
│ ├── moon.pkg
│ └── pkg.generated.mbti
├── web/
│ └── index.html # 浏览器 SCADA 工业仿真控制台
├── examples/ # 典型工业案例 (电机、红绿灯、流水线、恒温水箱)
├── docs/
│ └── ACCEPTANCE.md # 黑客松结项验收自查报告
├── PROPOSAL.md # 大赛项目申报说明书
├── .github/workflows/ci.yml # GitHub Actions CI
└── LICENSE # Apache-2.0pub(all) enum Coil {
Normal(String)
Set(String)
Reset(String)
DirectCoil(PlcAddress)
} derive(Eq, Debug)pub(all) enum Contact {
NormallyOpen(String)
NormallyClosed(String)
DirectNO(PlcAddress)
DirectNC(PlcAddress)
RisingEdgeContact(String)
FallingEdgeContact(String)
CompareContact(String, BinaryOp, Int)
} derive(Eq, Debug)pub(all) struct CtudCounter {
cu_prev : Bool
cd_prev : Bool
pv : Int
cv : Int
qu : Bool
qd : Bool
} derive(Debug)fn CtudCounter::tick(self : CtudCounter, cu_val : Bool, cd_val : Bool, r_val : Bool, ld_val : Bool, pv_val : Int) -> Unitpub(all) enum FbInstance {
Ton(TonTimer)
Tof(TofTimer)
Tp(TpTimer)
Tonr(TonrTimer)
Ctu(CtuCounter)
Ctud(CtudCounter)
Sr(SrFlipFlop)
Rs(RsFlipFlop)
Rtrig(RTrig)
Ftrig(FTrig)
} derive(Debug)pub(all) enum LadderNode {
Leaf(Contact)
Series(Array[LadderNode])
Parallel(Array[LadderNode])
} derive(Eq, Debug)pub(all) struct ModbusRequest {
slave_id : Int
function_code : ModbusFunction
address : Int
quantity : Int
values : Array[Int]
} derive(Debug)fn ModbusRequest::new(slave_id : Int, fc : ModbusFunction, address : Int, quantity : Int) -> ModbusRequestpub(all) struct ModbusResponse {
slave_id : Int
function_code : ModbusFunction
is_error : Bool
exception_code : ModbusException?
byte_count : Int
data : Array[Int]
} derive(Debug)pub(all) struct PidController {
kp : Double
ti_ms : Double
td_ms : Double
out_min : Double
out_max : Double
deadband : Double
integral_acc : Double
prev_pv : Double
prev_error : Double
manual : Bool
manual_val : Double
output : Double
} derive(Debug)pub(all) struct PlcAddress {
area : PlcArea
size : PlcDataSize
byte_offset : Int
bit_offset : Int
} derive(Eq, Debug)pub(all) struct PlcRuntime {
mem : PlcMemory
fb_instances : Map[String, FbInstance]
program : Program?
cycle_count : Int64
current_time_ms : Int64
scan_time_us : Int64
watchdog_ms : Int64
is_running : Bool
} derive(Debug)fn PlcRuntime::execute_statement_list(self : PlcRuntime, stmts : Array[Stmt], delta_ms : Int64) -> Unitpub(all) enum PlcValue {
Bool(Bool)
Int(Int)
Real(Double)
Time(Int64)
String(String)
Null
} derive(Debug)pub(all) struct PlcVm {
instructions : Array[IlInstruction]
mem : PlcMemory
pc : Int
acc : PlcValue
halted : Bool
} derive(Debug)pub(all) struct Rung {
id : Int
comment : String
condition : LadderNode
coils : Array[Coil]
} derive(Debug)pub(all) enum Stmt {
Assign(String, Expr)
AssignAddress(PlcAddress, Expr)
AssignField(String, String, Expr)
If(Expr, Array[Stmt], Array[(Expr, Array[Stmt])], Array[Stmt])
While(Expr, Array[Stmt])
For(String, Expr, Expr, Expr?, Array[Stmt])
FbCall(String, String, Array[(String, Expr)])
Empty
} derive(Debug)pub(all) enum Token {
KwProgram
KwEndProgram
KwVar
KwEndVar
KwVarInput
KwVarOutput
KwAt
KwIf
KwThen
KwElsif
KwElse
KwEndIf
KwWhile
KwEndWhile
KwDo
KwFor
KwTo
KwBy
KwEndFor
KwAnd
KwOr
KwNot
KwXor
KwMod
Assign
Equal
NotEqual
LessThan
LessThanEqual
GreaterThan
GreaterThanEqual
Plus
Minus
Star
Slash
LParen
RParen
Comma
Semicolon
Colon
Dot
Ident(String)
BoolLit(Bool)
IntLit(Int)
RealLit(Double)
TimeLit(Int64)
StringLit(String)
DirectAddr(PlcAddress)
EOF
} derive(Eq, Debug)pub(all) struct TpTimer {
in_prev : Bool
active : Bool
pt : Int64
et : Int64
q : Bool
} derive(Debug)pub(all) struct VarDecl {
name : String
var_type : String
init_val : PlcValue?
located_at : PlcAddress?
} derive(Debug)Install
Download zipIEC 61131-3 Soft PLC Runtime and Structured Text Engine in MoonBit