Structured Text Compiler.
stc is an executable IEC 61131-3 Structured Text compiler front-end written in
Python. The current milestone turns the original grammar prototype into a
usable command-line tool that can parse ST files and emit an AST, C, or Rust for
the supported subset.
- IEC 61131-3 POU parsing for functions, function blocks, and programs.
VAR_INPUTand function-localVARdeclarations.- Elementary types including
BOOL, signed/unsigned integers,REAL,LREAL, and bit-string integer families. - Derived type and declaration parsing for common arrays, structures, and strings in AST output.
- Integer, real, boolean, typed numeric literals such as
INT#10, strings, and date/time literals. - Assignments,
IF/ELSIF/ELSE,CASE,FOR,WHILE, andREPEAT. - Standard function call parsing for common IEC functions in AST output.
- C and Rust code generation for the supported function subset.
- JSON AST output for downstream tooling and regression tests.
- Minimal semantic checks for undeclared variables before code generation.
- Structural AST output for every parser production currently present in the grammar.
python3 src/main.py examples/inter.st -g ast
python3 src/main.py examples/inter.st -g c
python3 src/main.py examples/inter.st -g rust
python3 src/main.py examples/inter.st -g c -o build/inter.c
python3 src/main.py examples/inter.st -g c --no-semantic-checkUse - or omit the source path to read from stdin.
Library clients can use the same result-based pipeline as the CLI:
from compiler import compile_source
result = compile_source(source, "c", source_name="example.st")
if result.success:
c_source = result.output
else:
for diagnostic in result.diagnostics:
print(diagnostic.code, diagnostic.message)CompilationResult preserves the parse tree, AST, semantic context, source
map, diagnostics, and generated output when they are available. Syntax and
semantic failures are returned as data; invalid API usage and unexpected
compiler faults still raise exceptions.
The parser output is converted through an injectable AstBuilder. The current
AST is still dictionary-based, but it is detached from parser-owned objects so
typed nodes can replace it incrementally without changing the compilation API.
Add library search directories with -L and import either every export or one
selected symbol:
python3 src/main.py application.st -g c -L ./libraries --import math
python3 src/main.py application.st -g c -L ./libraries --import math:NativeAddEach library is described by stc-library.json:
{
"schema": 1,
"name": "math",
"exports": {
"NativeAdd": {
"source": "NativeAdd.st"
}
}
}Target-native implementations use matiec-style pragmas inside the IEC source.
A function uses a body section:
{#native c body}
NativeAdd = lhs + rhs;
{#end_native}
A function block uses Arduino-style setup and loop sections:
{#native c setup}
self->output_value = false;
{#end_native}
{#native c loop}
if (self->set_value) self->output_value = true;
{#end_native}
The C backend emits Block_setup(Block *self) and Block_loop(Block *self).
Native function code can access parameters, locals, and the generated return
variable by their IEC names. Function-block code accesses state through
self->field_name. A valid ST body remains in the declaration as a portable
fallback for targets without a matching native pragma.
Syntax errors include the unexpected token, line/column, source line, and a caret:
stc: syntax error: unexpected token at line 6, column 9 near IDENTIFIER('broken')
broken := value_in;
^
python3 -m unittest discover -s testsInteresting Structured Text examples live under tests/fixtures/:
valid_ast/: syntax that must parse to JSON AST.valid_codegen/: syntax that must also emit C and Rust.invalid_semantic/: syntax that parses but must fail semantic code generation.
For example:
python3 src/main.py tests/fixtures/valid_ast/case_and_for.st -g ast
python3 src/main.py tests/fixtures/valid_codegen/typed_literals.st -g c
python3 src/main.py tests/fixtures/invalid_semantic/undeclared_variable.st -g cThe long-term target is to grow toward the practical compiler quality of STruC++ and matiec:
- STruC++: readable generated C++17, reusable libraries, integrated testing, and debugging/REPL workflows.
- matiec: broad IEC 61131-3 compiler coverage and mature PLC-oriented code generation.
This repository is not at that level yet. The next steps are deliberately foundation-first so compatibility can be expanded without repeatedly rewriting the frontend.
The current AST is structurally complete for every production currently present
in IECParser: no parser production is left as a placeholder. It is still a
dictionary-based parse tree, not yet a typed compiler IR. The next maturity step
is typed nodes with source spans, semantic symbols, and deterministic
diagnostics.
Use the parser coverage audit to track that work:
python3 tools/ast_coverage.py
python3 tools/ast_coverage.py --listWhen an Annex B text extraction is available at tmp/pdfs/annex_b.txt, the
parser can also be checked against the IEC 61131-3:2003 production names:
python3 tools/parser_doc_audit.py- Replace dict-based AST nodes with typed nodes carrying source spans.
- Add deterministic diagnostics with line/column ranges and recovery tests.
- Split parsing, semantic analysis, and backend generation into separate compiler phases.
- Implement a symbol table and type checker for functions, function blocks, programs, arrays, structs, direct variables, and configurations.
- Continue expanding grammar coverage for located declarations, array repeat initializers, positional calls, and broader standard function signatures.
- Add a compatibility corpus with accepted and rejected IEC 61131-3 programs.
- Add generated-code compile tests for C and Rust on CI.
- Add a runtime/library layer for standard IEC functions and function blocks.
- Add C++17 generation once the typed IR is stable.
- Add ST-level tests and an interactive execution/debugging loop.
- IEC 61131-3:2013, Programmable controllers, Part 3.
- Autonomy-Logic/STruCpp
- beremiz/matiec
During normal operation, the compiler hides known SLY/PLY warnings about unused tokens and productions, unreachable symbols, and grammar conflicts. Actual grammar construction errors remain enabled.
To enable the complete grammar audit and generate parser.out:
STC_PARSER_DIAGNOSTICS=1 python3 src/main.py input.st -g cSemantic and syntax diagnostics include the file, line, column, source code, and a caret range:
example.st:13:9: error: Cannot assign ['REAL'] to ['INT']. [incompatible-assignment]
13 | b_1 := 10.5;
| ^~~~~~~~~~~
stc: 1 error generated.
Color is enabled automatically on interactive terminals and can be controlled explicitly:
python3 src/main.py input.st -g c --diagnostic-color=always
python3 src/main.py input.st -g c --diagnostic-color=neverThe C generator receives the semantic context and emits FOR loops and #line
directives so C compiler diagnostics can refer back to the Structured Text
source.
Semantic checks are organized under src/semantic_checks/. Each check is a
small registered class with explicit phase and dependencies. See
docs/ADDING_SEMANTIC_CHECKS.md for a complete
example.