IEC 61131-3 Structured Text to C++14 compiler.
STruC++ compiles PLC programs written in Structured Text into clean, readable C++14. It ships with a built-in unit testing framework, an interactive REPL for program debugging, and a reusable library system.
The name STruC++ comes from ST (Structured Text) + stru (Latin root meaning "to build") + C++ (the target language).
ST-to-C++ makes sense. Other tools target C (MatIEC) or proprietary bytecode. STruC++ generates idiomatic C++14 with classes for function blocks, virtual methods for interfaces, and templates for generics, producing code you can actually read, debug, and integrate with existing C++ projects.
Built-in unit testing. Every PLC testing solution today requires a separate IDE add-on, an external library, or PLC hardware to run tests. STruC++ has a test runner built into the compiler itself. Users can write tests in ST, run them on any machine with strucpp source.st --test tests.st. No PLC needed, perfect for automated build pipelines. See the Testing Guide.
Interactive REPL. Build your ST program into a standalone binary with --build and step through it interactively to check correctness. The interactive REPL allows users to print ST and C++ code side-by-side, set inputs, advance cycles, inspect variables, and force values. See the REPL Guide.
Zero runtime dependencies. The compiler is a single binary. The C++ runtime is header-only. Generated code and the runtime compile at C++14, exercised with g++ 7.3 and newer (avr-g++ 7.3 is the oldest toolchain in the board matrix); clang++ and MSVC are not exercised by CI. C++14 rather than C++17 because the generated code is compiled by the Arduino core's own toolchain, and the mbed-based cores hard-code -std=gnu++14. See the CLI Reference and C++ Runtime.
Download the latest release for your platform from GitHub Releases, extract it, and add it to your PATH:
tar -xzf strucpp-linux-x64.tar.gz # or unzip on macOS/Windows
export PATH="$PWD/strucpp:$PATH"
strucpp --versionstrucpp counter.st -o counter.cppThis generates counter.cpp and counter.hpp. To compile the C++ output:
g++ -std=c++14 -Istrucpp/runtime/include counter.cpp -o counterstrucpp adder.st --test test_adder.stSTruC++ Test Runner v1.0
test_adder.st
[PASS] Addition works
[PASS] Addition with negatives
-----------------------------------------
2 tests, 2 passed, 0 failed
Interactive REPL (full guide)
strucpp program.st -o program.cpp --build
./programSTruC++ Interactive PLC Test REPL
Programs: Main(3 vars)
strucpp[0]> set Main.enable true
strucpp[0]> run 5
Executed 5 cycle(s). Total: 5
strucpp[5]> vars Main
Main.enable : BOOL = TRUE
Main.count : INT = 5
Unit Testing (full guide)
Write test files in Structured Text using TEST blocks with assertions. No need for IDE plugins, no PLC hardware required:
Source (adder.st):
FUNCTION_BLOCK Adder
VAR_INPUT a, b : INT; END_VAR
VAR_OUTPUT sum : INT; END_VAR
sum := a + b;
END_FUNCTION_BLOCK
Test (test_adder.st):
TEST 'Addition works'
VAR uut : Adder; END_VAR
uut(a := 3, b := 7);
ASSERT_EQ(uut.sum, 10);
END_TEST
TEST 'Timer reaches preset'
VAR t : TON; END_VAR
t(IN := TRUE, PT := T#100ms);
ADVANCE_TIME(T#100ms);
t(IN := TRUE, PT := T#100ms);
ASSERT_TRUE(t.Q);
END_TEST
The framework supports SETUP/TEARDOWN blocks, MOCK/MOCK_FUNCTION for dependency isolation, MOCK_VERIFY_CALLED/MOCK_VERIFY_CALL_COUNT for interaction verification, and ADVANCE_TIME for timer and scheduling tests.
Assertions: ASSERT_EQ, ASSERT_NEQ, ASSERT_TRUE, ASSERT_FALSE, ASSERT_GT, ASSERT_LT, ASSERT_GE, ASSERT_LE, ASSERT_NEAR.
Input (counter.st):
FUNCTION_BLOCK Counter
VAR_INPUT
enable : BOOL;
reset : BOOL;
END_VAR
VAR_OUTPUT
count : INT;
END_VAR
IF reset THEN
count := 0;
ELSIF enable THEN
count := count + 1;
END_IF;
END_FUNCTION_BLOCK
Generated C++ header:
namespace strucpp {
class Counter {
public:
IEC_BOOL enable;
IEC_BOOL reset;
IEC_INT count;
Counter();
void operator()();
virtual ~Counter() = default;
};
} // namespace strucppGenerated C++ implementation:
namespace strucpp {
Counter::Counter() { /* variable initialization */ }
void Counter::operator()() {
if (reset) {
count = 0;
} else if (enable) {
count = count + 1;
}
}
} // namespace strucppSTruC++ implements a broad subset of IEC 61131-3 Edition 3 plus common CODESYS extensions:
| Category | Features |
|---|---|
| Data types | BOOL, BYTE/WORD/DWORD/LWORD, SINT/INT/DINT/LINT, USINT/UINT/UDINT/ULINT, REAL/LREAL, STRING/WSTRING, TIME/DATE/DT/TOD (+ L-variants), arrays (1D/2D/3D/VLA), structs, enums, subranges |
| POUs | PROGRAM, FUNCTION, FUNCTION_BLOCK, INTERFACE |
| Variables | VAR, VAR_INPUT, VAR_OUTPUT, VAR_IN_OUT, VAR_EXTERNAL, VAR_GLOBAL, CONSTANT, RETAIN, AT (located) |
| Control flow | IF/ELSIF/ELSE, CASE, FOR, WHILE, REPEAT, EXIT, RETURN |
| OOP | Methods, properties (GET/SET), inheritance (EXTENDS), interfaces (IMPLEMENTS), ABSTRACT, FINAL, OVERRIDE, access modifiers |
| Pointers | POINTER TO, REF_TO, REFERENCE_TO, ADR, ^ dereference, __NEW/__DELETE |
| Standard functions | 80+ functions: math, trig, string, selection, comparison, bitwise, type conversion |
| Standard FBs | TON, TOF, TP, CTU, CTD, CTUD, R_TRIG, F_TRIG, SR, RS (compiled ST library) |
| Project model | CONFIGURATION, RESOURCE, TASK, program scheduling |
See IEC Compliance for the full feature matrix.
STruC++ can also be used as a JavaScript/TypeScript library for embedding in browser-based IDEs and web applications:
import { compile } from "strucpp";
const result = compile(`
FUNCTION_BLOCK Counter
VAR_INPUT enable : BOOL; END_VAR
VAR_OUTPUT count : INT; END_VAR
IF enable THEN count := count + 1; END_IF;
END_FUNCTION_BLOCK
`);
// result.success, result.cppCode, result.headerCode, result.errorsThe compiler has zero native dependencies and runs in any JavaScript environment (Node.js, browsers, Deno, Bun).
Requires Node.js 18+:
git clone https://github.com/Autonomy-Logic/STruCpp.git
cd STruCpp
npm ci
npm run build # Compile TypeScript to dist/
npm test # Run all 1400+ testsBuild standalone binaries (no Node.js required to run):
npm run build:pkg # Produces binaries for Linux, macOS, Windowsnpm run dev # Watch mode
npm run test:coverage # Coverage report (75% branch minimum)
npm run lint # ESLint
npm run typecheck # Type-check without emit| Document | Description |
|---|---|
| CLI Reference | All compiler modes, flags, and options |
| Testing Guide | Test syntax, assertions, mocking, time advancement |
| REPL Guide | Interactive commands and program debugging |
| Architecture | Compiler pipeline, module map, design decisions |
| C++ Runtime | Runtime library types, IECVar wrapper, standard functions |
| IEC Compliance | Full feature matrix with implementation status |
The compiler is licensed under GPL-3.0. The C++ runtime and standard libraries use GPL-3.0 with the STruC++ Runtime Library Exception, allowing you to distribute compiled programs under any license. See COPYING for details.
STruC++ is part of the OpenPLC ecosystem, whose runtimes are dual-licensed under the GPLv3 and a commercial licence. The runtime library exception above means programs compiled with STruC++ carry no licence obligation from STruC++ itself; see the OpenPLC Runtime licensing page for how the runtimes that execute them are licensed. Contributions require agreeing to the Contributor License Agreement by ticking its checkbox on the pull request; see CONTRIBUTING.md.
- OpenPLC Project -- The ecosystem that motivated this compiler
- MatIEC -- The original IEC 61131-3 compiler
- Chevrotain -- Parser framework
- isocline -- REPL line editor (MIT)