Structured Text Editor

Overview

Structured Text (ST) is the Pascal-like high-level language of IEC 61131-3 and the default editor for PROGRAM, FUNCTION_BLOCK, and FUNCTION POUs in ForgeIEC. The editor is a QWidget-based composition of a variable table and a code area, coupled through a vertical splitter.

+----------------------------------------+
| Variable table                         |  <- FVariablesPanel
| (VAR/VAR_INPUT/VAR_OUTPUT/VAR_INST)    |
+========================================+  <- QSplitter (vertical)
| Code area                              |  <- FStCodeEdit
| (Tree-sitter highlighting + folding +  |
|  Ctrl-Space completion)                |
+----------------------------------------+

Editor layout

AreaContent
Variable table (top)Declarations with columns Name, Type, Initial value, Address, Comment. Edits sync live into the VAR ... END_VAR block of the code.
Code area (bottom)ST source between the variable sections. Line folding driven by the tree-sitter AST, line numbers, cursor-line highlight.
Search bar (Ctrl-F / Ctrl-H)Shown above the code area, with replace mode for find-and-replace.

The splitter remembers its position per POU in the layout state.

Tree-sitter syntax highlighting

Instead of a regex-based QSyntaxHighlighter, ForgeIEC parses ST source with Tree-sitter into an AST and colours via capture queries:

  • Keywords (IF, THEN, FOR, FUNCTION_BLOCK, …): magenta
  • Data types (BOOL, INT, REAL, TIME, …): cyan
  • Strings + time literals ('abc', T#20ms): green
  • Comments ((* ... *), // ...): grey, italic
  • PUBLISH / SUBSCRIBE: Anvil extension keywords, dedicated style

Benefit: highlighting stays correct on complex constructs (nested comments, time literals, qualified references), and the same AST drives the foldable ranges for code folding.

Code completion (Ctrl-Space)

Pressing Ctrl-Space or typing two matching characters opens the completion popup. The completer knows:

  • IEC keywords (IF, CASE, FOR, WHILE, RETURN, …)
  • Data types (BOOL, INT, DINT, REAL, STRING, TIME, …)
  • Local variables of the current POU
  • POU names in the project (PROGRAM, FUNCTION_BLOCK, FUNCTION)
  • Library blocks (TON, R_TRIG, JK_FF, DEBOUNCE, …)
  • Standard functions (ABS, SQRT, LIMIT, LEN, …)

Changes to the variable pool (poolChanged signal) propagate into the completion model with 100 ms debounce — new pool entries become available almost instantly, without every keystroke triggering a full rescan.

Language fundamentals (IEC 61131-3)

Statements

StatementForm
Assignmentvar := expression;
IF / ELSIF / ELSEIF cond THEN ... ELSIF cond THEN ... ELSE ... END_IF;
CASECASE x OF 1: ... ; 2,3: ... ; ELSE ... END_CASE;
FORFOR i := 1 TO 10 BY 1 DO ... END_FOR;
WHILEWHILE cond DO ... END_WHILE;
REPEATREPEAT ... UNTIL cond END_REPEAT;
EXIT / RETURNLeave loop / leave POU

Expressions

Standard operators with IEC precedence: **, unary +/-/NOT, * / MOD, + -, comparisons, AND / &, XOR, OR. Parentheses as in Pascal. Implicit numeric type conversions are not allowed — INT_TO_DINT, REAL_TO_INT etc. must be called explicitly.

Bit access on ANY_BIT types

var.<bit> extracts or sets a single bit, directly on BYTE/WORD/DWORD/LWORD variables:

status.0 := TRUE;             (* set bit 0 *)
alarm := flags.7 OR flags.3;  (* read bits *)

The compiler translates this into clean bit masking with AND/OR/shift, without helper variables.

3-level qualified references

<Category>.<Group>.<Variable> accesses pool entries directly, without having to declare GVLs explicitly:

PrefixSource
Anvil.X.YPool entry with anvilGroup="X"
Bellows.X.YPool entry with hmiGroup="X"
GVL.X.YPool entry with gvlNamespace="X"
HMI.X.YSynonym for Bellows.X.Y

Anvil.X.Y and Bellows.X.Y may independently point to different pool entries — the compiler emits separate C symbols as soon as the IEC addresses differ.

Located variables (AT %...)

Located variables bind a declaration to an IEC address:

button_raw    AT %IX0.0  : BOOL;
motor_speed   AT %QW1    : INT;
flag_persist  AT %MX10.3 : BOOL;

Address is the primary key in the pool — see Project file format.

Code examples

Example 1 — TON call with library block

PROGRAM PLC_PRG
VAR
    start_button   AT %IX0.0  : BOOL;
    motor_run      AT %QX0.0  : BOOL;
    fbDelay        : TON;
END_VAR

fbDelay(IN := start_button, PT := T#3s);
motor_run := fbDelay.Q;
END_PROGRAM

fbDelay is an instance of the library FB TON. After 3 seconds of held start_button, motor_run switches to TRUE.

Example 2 — Bellows read driving an output

PROGRAM Lampen
VAR
    relay_lamp  AT %QX0.1 : BOOL;
END_VAR

(* HMI panel can write Bellows.Pfirsich.T_1 *)
relay_lamp := Bellows.Pfirsich.T_1 OR Anvil.Sensors.contact_door;
END_PROGRAM

Bellows.Pfirsich.T_1 and Anvil.Sensors.contact_door are 3-level references the compiler resolves without a GVL declaration — provided both tags are kept in the address pool and the HMI export for the group Pfirsich is active.