Skip to content
18 changes: 15 additions & 3 deletions docs/IEC_COMPLIANCE.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,11 @@ STruC++ implements the Structured Text (ST) language from IEC 61131-3. This docu
| TYPE ... END_TYPE | Supported | Type aliases |
| STRUCT ... END_STRUCT | Supported | With nested structs |
| Enumerations | Supported | With optional base type |
| Initialized type declarations | Supported | A type may carry its own default (`Setpoint : REAL := 25.0;`, `Origin : Point := (x := 0.0);`), inherited by every declaration of the type that has no initializer |
| ARRAY (1D) | Supported | Arbitrary bounds: ARRAY[1..10] OF INT |
| ARRAY (2D) | Supported | ARRAY[1..3, 1..4] OF REAL |
| ARRAY (3D) | Supported | ARRAY[1..3, 1..4, 1..5] OF INT |
| ARRAY OF function block | Supported | Declaration, member access, and element invocation (`units[i](step := 1.0)`). A *method* call on an element (`units[0].M()`) is not yet parsed |
| ARRAY[*] (VLA) | Supported | Variable-length array parameters |
| Subranges | Supported | Runtime validation |
| REF_TO | Supported | IEC reference type (explicit dereference) |
Expand Down Expand Up @@ -59,14 +61,24 @@ STruC++ implements the Structured Text (ST) language from IEC 61131-3. This docu
| VAR_INPUT | Supported | Input parameters |
| VAR_OUTPUT | Supported | Output parameters |
| VAR_IN_OUT | Supported | Pass-by-reference parameters |
| VAR_EXTERNAL | Supported | External references to VAR_GLOBAL |
| VAR_GLOBAL | Supported | Global variables |
| VAR_EXTERNAL | Supported | References either a CONFIGURATION or a file-level VAR_GLOBAL |
| VAR_GLOBAL | Supported | Global variables (CONFIGURATION-scoped or file-level) |
| CONSTANT | Supported | Compile-time constants |
| RETAIN | Supported | Tracked in retain variable table |
| NON_RETAIN | Supported | |
| AT %IX0.0 | Supported | Located variables (I/Q/M areas, X/B/W/D/L sizes) |
| Multiple names | Supported | `a, b, c : INT := 0;` |
| Initialization | Supported | `:= expression` |
| Array initialization | Supported | `:= [1, 2, 3]` and the bracket-less `:= 1, 2, 3`. Multi-dimensional arrays take either a flat row-major list or a nested one (`:= [[1, 2], [3, 4]]`), where each inner list fills one row from its own bound. Nesting depth and value count are validated against the declared dimensions |
| Array repetition | Supported | `:= [10(0)]`, `:= [3(1), 2(5)]`, `:= [7, 4(2), 9]`. The repeated value may be a structure initializer. Max count 65536 |
| Structure initialization | Supported | `:= (x := 1.0, y := 2.0)`; nested, in array literals, and for FB instances. Omitted elements keep their own declared default |
| STRUCT element defaults | Supported | Scalar, array-literal and structure-initializer defaults on a STRUCT element all carry their values |

### Initialization gaps

| Form | Notes |
|------|-------|
| Repetition with no value | `:= [10()]` (ten copies of the element default) — write `:= [10(0)]`, or omit the elements entirely. Matches matiec and CODESYS, which also require a value |

## Operators and Expressions

Expand All @@ -84,7 +96,7 @@ STruC++ implements the Structured Text (ST) language from IEC 61131-3. This docu
| Parentheses | `( )` | Supported |
| Function call | `name(args)` | Supported (positional + named) |
| Method call | `obj.method(args)` | Supported |
| Array access | `arr[i]`, `arr[i, j]` | Supported |
| Array access | `arr[i]`, `arr[i, j]` | Supported — the index count is validated against the declared rank |
| Field access | `struct.field` | Supported |
| Typed literals | `INT#5`, `DINT#42`, `REAL#3.14` | Supported |
| NEW | `__NEW(type)`, `__NEW(type, size)` | Supported |
Expand Down
17 changes: 17 additions & 0 deletions src/ast-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,8 @@ import type {
DrefExpression,
NewExpression,
ArrayLiteralExpression,
StructInitializerExpression,
StructElementInitializer,
AssertCall,
MockFunctionStatement,
MockVerifyCallCountStatement,
Expand Down Expand Up @@ -344,6 +346,7 @@ const EXPRESSION_KINDS = new Set([
"DrefExpression",
"NewExpression",
"ArrayLiteralExpression",
"StructInitializerExpression",
]);

function isExpression(node: ASTNode): boolean {
Expand Down Expand Up @@ -455,6 +458,7 @@ function getChildren(node: ASTNode): ASTNode[] {
case "TypeDeclaration": {
const td = node as TypeDeclaration;
children.push(td.definition);
if (td.defaultValue) children.push(td.defaultValue);
break;
}

Expand Down Expand Up @@ -592,6 +596,7 @@ function getChildren(node: ASTNode): ASTNode[] {

case "FunctionCallExpression": {
const fce = node as FunctionCallExpression;
if (fce.instance) children.push(fce.instance);
children.push(...fce.arguments);
break;
}
Expand Down Expand Up @@ -652,6 +657,18 @@ function getChildren(node: ASTNode): ASTNode[] {
break;
}

case "StructInitializerExpression": {
const sie = node as StructInitializerExpression;
children.push(...sie.elements);
break;
}

case "StructElementInitializer": {
const sei = node as StructElementInitializer;
children.push(sei.value);
break;
}

// --- Test framework ---
case "AssertCall": {
const ac = node as AssertCall;
Expand Down
98 changes: 98 additions & 0 deletions src/backend/codegen-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,3 +52,101 @@ export function formatArrayType(
}
return result;
}

/**
* Append an unchecked element access for one full set of array indices,
* matching the container {@link formatArrayType} picked for that rank.
*
* `Array1D` subscripts with `operator[]`; `Array2D` / `Array3D` take all indices
* at once through `operator()`; 4+ dimensions are nested `Array1D`, so they
* subscript once per dimension. Getting this wrong doesn't just read the wrong
* element — `arr[i][j]` on an `Array2D` has no matching operator and fails to
* compile.
*
* Unchecked (rather than `.at()`) because these accessors are `constexpr`, which
* is what lets `&arr[i]` be a constant expression — required for the debug
* pointer table's PROGMEM placement on AVR.
*/
export function formatArrayElementAccess(
base: string,
indices: number[],
): string {
if (indices.length === 2 || indices.length === 3) {
return `${base}(${indices.join(", ")})`;
}
return base + indices.map((i) => `[${i}]`).join("");
}

/**
* Translate IEC 61131-3 `$`-escape sequences in a string literal's body to C++
* escape sequences, and escape what C++ needs escaped.
*
* Handles `$N`/`$n` (newline), `$L`/`$l` (line feed), `$R`/`$r` (CR), `$T`/`$t`
* (tab), `$P`/`$p` (form feed), `$$` (literal `$`), `$'` (single quote), `$XX`
* (hex byte) and `''` (doubled single quote), then escapes backslash and
* double-quote so the result is safe inside a C++ `"…"` literal.
*
* Shared by the expression emitter and the type generator: a STRING literal has
* to lower identically whether it appears in a statement, a variable
* initialiser, or a STRUCT element default.
*/
export function translateIECString(inner: string): string {
let result = "";
for (let i = 0; i < inner.length; i++) {
const ch = inner[i]!;
if (ch === "$" && i + 1 < inner.length) {
const next = inner[i + 1]!;
switch (next.toUpperCase()) {
case "N":
case "L":
result += "\\n";
i++;
break;
case "R":
result += "\\r";
i++;
break;
case "T":
result += "\\t";
i++;
break;
case "P":
result += "\\f";
i++;
break;
case "$":
result += "$";
i++;
break;
case "'":
result += "'";
i++;
break;
default:
// $XX hex escape: two hex digits
if (
i + 2 < inner.length &&
/^[0-9A-Fa-f]{2}$/.test(inner.substring(i + 1, i + 3))
) {
result += "\\x" + inner.substring(i + 1, i + 3);
i += 2;
} else {
// Unknown $-escape, pass through
result += "\\\\$";
}
break;
}
} else if (ch === "'" && i + 1 < inner.length && inner[i + 1] === "'") {
// ST doubled-quote → single quote
result += "'";
i++;
} else if (ch === "\\") {
result += "\\\\";
} else if (ch === '"') {
result += '\\"';
} else {
result += ch;
}
}
return result;
}
Loading