This guide walks you through writing, compiling, and validating a PLC program using plc-toolkit. No GUI, no vendor IDE — just your terminal.
- Nix (for the dev environment)
- Or: Rust toolchain + LLVM 21 installed manually
git clone https://github.com/Adjoint-uk/plc-toolkit.git
cd plc-toolkit
nix developYou should see:
PLC Toolkit dev shell (Rust + LLVM 21)
cargo build — build all crates
cargo test — run tests
cargo run -- compile examples/blink.st
Build the toolkit:
cargo buildCreate a file called my_program.st:
PROGRAM TrafficLight
VAR
state : INT := 0; (* 0=red, 1=green, 2=yellow *)
timer_count : INT := 0;
red_duration : INT := 30;
green_duration : INT := 25;
yellow_duration : INT := 5;
red_light : BOOL := TRUE;
green_light : BOOL := FALSE;
yellow_light : BOOL := FALSE;
END_VAR
timer_count := timer_count + 1;
CASE state OF
0: (* Red *)
red_light := TRUE;
green_light := FALSE;
yellow_light := FALSE;
IF timer_count >= red_duration THEN
state := 1;
timer_count := 0;
END_IF;
1: (* Green *)
red_light := FALSE;
green_light := TRUE;
yellow_light := FALSE;
IF timer_count >= green_duration THEN
state := 2;
timer_count := 0;
END_IF;
2: (* Yellow *)
red_light := FALSE;
green_light := FALSE;
yellow_light := TRUE;
IF timer_count >= yellow_duration THEN
state := 0;
timer_count := 0;
END_IF;
END_CASE;
END_PROGRAM
This is a simple traffic light controller — a state machine that cycles through red, green, and yellow phases. It's the kind of program that runs on real PLCs controlling real intersections.
cargo run -- validate my_program.stIf the code is correct you'll see:
my_program.st: ok
cargo run -- compile my_program.stThis outputs JSON with:
success: whether compilation succeededdiagnostics: any errors or warnings, each with a fix suggestionir: the generated LLVM IR (the compiled output)
To save the LLVM IR to a file:
cargo run -- compile my_program.st -o traffic_light.llThe LLVM IR is a low-level representation of your program. It defines:
- A struct for your program's variables (
%TrafficLightwith fields for state, timer_count, lights, etc.) - A function that implements the program logic
- A constructor that sets initial values
From here, LLVM can compile this to native machine code for any target: x86 servers, ARM embedded boards, RISC-V microcontrollers, or even WASM for running in a browser.
Try compiling something broken:
echo 'PROGRAM Bad
VAR
x : NONEXISTENT_TYPE;
END_VAR
x := 42;
END_PROGRAM' > bad.st
cargo run -- compile bad.stYou'll get structured JSON errors:
{
"success": false,
"diagnostics": [
{
"severity": "error",
"message": "Unknown type: NONEXISTENT_TYPE",
"suggestion": "Check spelling of the type name. Standard types: BOOL, INT, DINT, REAL, LREAL, STRING, TIME"
}
]
}Every error includes a suggestion — designed so both humans and LLMs can fix the problem without guessing.
This is the best part. Instead of just compiling, you can execute your program in a simulated scan loop and watch the variables change:
cargo run -- run examples/traffic_light.st -n 65 --interval 0Output:
running TrafficLight — 65 cycles, 0ms interval
(Ctrl+C to stop)
cycle | state | timer_count | red_duration | green_duration | yellow_duration | red_light | green_light | yellow_light
------+--------+-------------+--------------+----------------+-----------------+-----------+-------------+-------------
0 | 0 | 1 | 30 | 25 | 5 | 1 | 0 | 0
...
29 | 1 | 0 | 30 | 25 | 5 | 1 | 0 | 0
30 | 1 | 1 | 30 | 25 | 5 | 0 | 1 | 0
...
54 | 2 | 0 | 30 | 25 | 5 | 0 | 1 | 0
55 | 2 | 1 | 30 | 25 | 5 | 0 | 0 | 1
...
59 | 0 | 0 | 30 | 25 | 5 | 0 | 0 | 1
60 | 0 | 1 | 30 | 25 | 5 | 1 | 0 | 0
You can see the state machine cycling: red (30 cycles) → green (25) → yellow (5) → red again. The lights columns show which output is active.
Under the hood, plc run compiles your ST to native machine code and executes it directly. No interpreter, no VM — this is the same code that would run on a real PLC.
With --interval 100 (the default), it pauses 100ms between cycles, simulating a real PLC scan time.
cargo run -- run examples/counter.st -n 15 # watch a counter increment
cargo run -- compile examples/counter.st # see the JSON output
cargo run -- validate examples/motor_control.st # industrial motor control- MCP server (coming soon) — expose compile/validate as tools for Claude, GPT, or any LLM agent
- LSP server (Phase 2) — autocomplete and diagnostics in VS Code
- Multi-vendor import (Phase 3) — read Rockwell L5X and Siemens SimaticML projects
See the roadmap for the full plan.