Skip to content

Verified: Structured Text

Each claim the Structured Text page makes, with the tests that prove it in the last green CI run: 57 of 57 verified. The claims live in docs/claims/structured-text.yaml.

Last green CI run 16 Oct 2026, 14:48 UTC on c7618d3 · all runtime claims

ST-001 verified

Statements evaluate top to bottom, so a value assigned on one line is what the next line reads in the same scan.

where the docs say so · Both run one scan whose later lines read values assigned on earlier lines (sum := pt.x + pt.y after the field writes; Out := n after n := n + 1), so a reordering would change the asserted result. The earlier refs (TestLowerRealArithmetic, ir TestIfElse, ir TestForSum) were single-statement or IR-level and asserted nothing of ordering.

  • pass TestLowerUDTFieldAccess in lang/st
  • pass TestVarTempResetsEveryScan in lang/st

ST-002 verified

Program-local VAR keeps its value from scan to scan.

where the docs say so · TestVarTempResetsEveryScan's plain VAR kept counts to 5 over 5 scans. Dropped: ir TestRetainedAccumulator (hand-built IR, not an ST VAR) and TestLowerRetainedAccumulator (declares VAR RETAIN, so it passes even if plain VAR stopped persisting).

ST-003 verified

In a manifest project every tag in nautilus.yaml is in scope in every program without a VAR_EXTERNAL declaration, typed by its type: (or its init:).

where the docs say so

ST-004 verified

A VAR_EXTERNAL that names a manifest tag must agree with the tag's type:. For a tag typed only by its init:, the declaration sets the type and the init: must be a value of it (init: TRUE against a REAL declaration is an error).

where the docs say so

ST-005 verified

Naming a tag binds it in the tag store rather than creating it, and reading a tag nothing has ever written faults the scan with "undefined tag".

where the docs say so · A program reading a declared but never-written tag faults with "undefined tag Level", and runs once the tag is written.

  • pass TestScanReadOfUnwrittenTagFaults in runtime

ST-006 verified

A FUNCTION_BLOCK sees no tag it does not declare in its own VAR_EXTERNAL.

where the docs say so

ST-007 verified

The declaration sections VAR, VAR_INPUT, VAR_OUTPUT, VAR_IN_OUT, VAR_TEMP, VAR_GLOBAL and VAR_EXTERNAL parse, each closed by END_VAR, with RETAIN or CONSTANT allowed after the section keyword.

where the docs say so · TestExternalStructFieldAssignment supplies VAR_EXTERNAL and VAR_GLOBAL, which the two parser tests do not include.

  • pass TestParseVarBlockKinds in lang/st
  • pass TestParseVarRetainConstant in lang/st
  • pass TestExternalStructFieldAssignment in lang/st

ST-008 verified

VAR_GLOBAL and VAR_EXTERNAL resolve identically: both name a tag in the store.

where the docs say so

  • pass TestLowerGlobalVar in lang/st
  • pass TestVarGlobalAndVarExternalResolveIdentically in lang/st (3 of 3 passed)

ST-009 verified

A declared initial value (Gain : REAL := 2.0;) is where the variable starts, in a PROGRAM, in every FUNCTION_BLOCK instance, and on every FUNCTION call.

where the docs say so · TestConstantInBoundsAndInitialValues asserts a PROGRAM VAR's initial value (start : INT := N + 1) in its result.

  • pass TestConstantInBoundsAndInitialValues in lang/st
  • pass TestUserFB_InitialValues in lang/st
  • pass TestUserFunc_InitialValues in lang/st
  • pass TestUserFB_UnboundInputKeepsInitial in lang/st

ST-010 verified

An initial value (:=) on a VAR_GLOBAL or VAR_EXTERNAL is a compile error; the tag's init: in the manifest sets it instead.

where the docs say so · TestGlobalConstantRules carries the VAR_GLOBAL case; TestManifestTagsAreImplicit shows init: seeding the tag a program reads.

ST-011 verified

VAR_TEMP starts every scan of a program, and every call of a function block or function, at its initial value (or zero), never at what the last execution left.

where the docs say so

  • pass TestConformance/st-var-temp in lang/conformance (3 of 3 passed)
  • pass TestVarTempResetsEveryScan in lang/st
  • pass TestVarTempResetsEveryFBCall in lang/st
  • pass TestVarTempInFunction in lang/st

ST-012 verified

VAR CONSTANT is a named value folded at compile time; assigning one is a compile error.

where the docs say so

  • pass TestConstantCannotBeWritten in lang/st
  • pass TestConstantInBoundsAndInitialValues in lang/st

ST-013 verified

VAR_GLOBAL CONSTANT in a library file declares project constants every POU sees; a constant is not a tag.

where the docs say so · TestGlobalConstantCannotBeWritten was dropped: the page does not say a VAR_GLOBAL CONSTANT cannot be written.

  • pass TestGlobalConstantsVisibleEverywhere in lang/st
  • pass TestGlobalConstantsLibraryAlone in lang/st
  • pass TestConformance/st-global-constants in lang/conformance (4 of 4 passed)

ST-014 verified

A standalone VAR_GLOBAL block at the top of a library file (a Codesys gvl.st) declares globals for every program; one the manifest does not declare becomes a state tag starting at the zero of its type.

where the docs say so

ST-015 verified

The elementary types BOOL, SINT..LINT/USINT..ULINT/BYTE..LWORD, REAL, LREAL, TIME, LTIME, STRING, WSTRING, CHAR and WCHAR are accepted.

where the docs say so

  • pass TestKeywordAndTypeNames in lang/st
  • pass TestResolveTypeName in lang/st
  • pass TestWideAndCharTypesAreAcceptedAsString in lang/st

ST-016 verified

All integer kinds share one 64-bit representation, so a declared width documents intent without narrowing (INT does not wrap at 16 bits, DINT not at 32).

where the docs say so

  • pass TestConformance/st-int-width in lang/conformance (6 of 6 passed)
  • pass TestConformance/st-bitwise/bits above 32 survive - the width is 64 bits in lang/conformance

ST-017 verified

TIME counts milliseconds.

where the docs say so

  • pass TestConformance/fn-conversions/TIME is milliseconds in both directions in lang/conformance
  • pass TestConformance/st-conversions-matrix/TIME is milliseconds, and wraps like any integer when narrowed in lang/conformance
  • pass TestParseDuration in lang/ir

ST-018 verified

ARRAY[lo..hi] OF T takes several comma-separated dimensions and any element type, including a UDT or a function block.

where the docs say so

  • pass TestLowerArrayOneBasedIndexing in lang/st
  • pass TestLowerArrayArbitraryLowerBound in lang/st
  • pass TestLowerMultiDimArray in lang/st
  • pass TestParseMultiDimArray in lang/st
  • pass TestLowerNestedAggregateAssign in lang/st
  • pass TestFBInstanceArray in lang/st

ST-019 verified

Array bounds are integer constants: a literal, a named constant, or arithmetic on them (ARRAY[1..N_TANKS]).

where the docs say so

  • pass TestConstantInBoundsAndInitialValues in lang/st
  • pass TestConformance/st-global-constants/an INT constant from a constant expression sizes an array and bounds a loop in lang/conformance
  • pass TestArrayBoundArithmeticInTheBound in lang/st

ST-020 verified

TYPE ... END_TYPE declares a STRUCT, an alias for another type, or an enumeration; enumeration members are used as Mode#Run or plain Run and shown by name in live values.

where the docs say so

  • pass TestParseStructTypeDecl in lang/st
  • pass TestEnumBasics in lang/st
  • pass TestEnumAmbiguousMemberResolvedByContext in lang/st
  • pass TestConformance/st-enum in lang/conformance (3 of 3 passed)
  • pass TestEnumTagsRenderByName in runtime
  • pass TestTypeAliasOfAnotherType in lang/st

ST-021 verified

A struct-typed VAR_EXTERNAL tag is written one field at a time (P101.Running := TRUE) or whole, and each field write reaches the tag store as a member write.

where the docs say so · Asserted by behaviour: a field write changes that member and leaves every other member as it was, including one an operator set between scans, and a whole write copies every member. The VM does a field write as a read-modify-write of the whole tag within the one statement, not as a store-level member call.

  • pass TestExternalStructFieldAssignment in lang/st
  • pass TestVarInOutExternalStructField in lang/st
  • pass TestStructTagWrittenFieldwiseAndWhole in runtime

ST-022 verified

Literals cover decimals and exponents (1.5e3), based integers (16#FF, 2#1010, 8#777), typed forms (INT#42, BOOL#TRUE, STRING#'hi') and durations (T#5s, T#500ms, T#1h30m).

where the docs say so

  • pass TestParseBasedNumbers in lang/st
  • pass TestParseTypedLiterals in lang/st
  • pass TestParseTimeLiterals in lang/st
  • pass TestLowerBasedLiteral in lang/st
  • pass TestParseDuration in lang/ir
  • pass TestExponentLiteral in lang/st

ST-023 verified

Block comments (* ... *) nest, and // comments to end of line.

where the docs say so

  • pass TestBlockComment_NestedTwoLevels in lang/st
  • pass TestBlockComment_NestedThreeLevels in lang/st
  • pass TestLineComment_StillWorks in lang/st

ST-024 verified

RETAIN parses and changes nothing at runtime: every program VAR and function block instance already keeps its value scan to scan.

where the docs say so

  • pass TestParseVarRetainConstant in lang/st
  • pass TestRetainChangesNothingAtRuntime in lang/st

ST-025 verified

A warm swap carries program VAR and FB instance state across an online edit by name and type.

where the docs say so · Program VARs, a TON and a CTU, and a user FUNCTION_BLOCK instance's internal Sum carry across the swap (TestSwapWarmCarriesRetainedState); a type change resets (TestSwapWarmTypeChangeResets).

  • pass TestSwapWarmCarriesRetainedState in runtime
  • pass TestSwapWarmTypeChangeResets in runtime

ST-026 verified

IF/ELSIF/ELSE, CASE, FOR ... BY, WHILE, REPEAT ... UNTIL, EXIT, CONTINUE and RETURN execute with their standard meaning.

where the docs say so

  • pass TestLowerIfElsif in lang/st
  • pass TestLowerCaseRanges in lang/st
  • pass TestLowerForLoopSum in lang/st
  • pass TestLowerWhileWithExit in lang/st
  • pass TestLowerContinueSkipsEven in lang/st
  • pass TestLowerRepeatUntil in lang/st
  • pass TestReturnEarly in lang/ir
  • pass TestForByStepAndReturnFromST in lang/st

ST-027 verified

A CASE label is a constant (literal, named constant or enumeration member), a comma list, or an inclusive lo..hi range; a value outside every label falls to ELSE.

where the docs say so

  • pass TestLowerCaseMultipleValues in lang/st
  • pass TestLowerCaseRanges in lang/st
  • pass TestCaseConstantLabelForms in lang/st
  • pass TestCaseNamedConstantLabels in lang/st
  • pass TestConformance/st-case-constants in lang/conformance (4 of 4 passed)
  • pass TestConformance/st-enum/CASE labels and comparisons on members, qualified and not in lang/conformance

ST-028 verified

Two CASE labels with the same value are a compile error.

where the docs say so

ST-029 verified

Assignment is :=, equality is =, and inequality is <>.

where the docs say so

  • pass TestLowerScalarAssign in lang/st
  • pass TestLowerIfElsif in lang/st
  • pass TestEqualityAndInequalityOperators in lang/st

ST-030 verified

REGION name ... END_REGION only groups statements (for folding and the Outline); it opens no scope.

where the docs say so · TestRegionOpensNoScope reads and writes an outer variable inside a region and sees the value after END_REGION, and the region's name is not a variable. TestRegionErrors (REGION syntax errors) is not a promise of the page.

  • pass TestRegionIsANoOpGrouping in lang/st
  • pass TestRegionInFBAndCase in lang/st
  • pass TestConformance/st-region in lang/conformance (3 of 3 passed)
  • pass TestRegionOpensNoScope in lang/st

ST-031 verified

Operator precedence, tightest first: unary NOT and -; * / MOD; + -; the comparisons; AND; XOR; OR.

where the docs say so

  • pass TestOperatorPrecedence in lang/st (14 of 14 passed)

ST-032 verified

An INT widens to REAL where one is expected.

where the docs say so

  • pass TestLowerIntToRealPromotion in lang/st
  • pass TestIntWidensToRealParameter in lang/st

ST-033 verified

The other direction (REAL into an INT target) is a compile error; narrowing goes through a conversion.

where the docs say so

  • pass TestConversionCompileErrors in lang/st
  • pass TestRealIntoIntAssignmentIsACompileError in lang/st (4 of 4 passed)

ST-034 verified

REAL_TO_DINT (or TO_DINT) rounds to nearest with ties to even (2.5 becomes 2, 3.5 becomes 4); TRUNC_DINT truncates toward zero.

where the docs say so

  • pass TestConformance/st-conversions-matrix/REAL to integer rounds half to even; TRUNC goes toward zero in lang/conformance

ST-035 verified

Every IEC <A>_TO_<B> conversion between BOOL, the integer kinds, REAL, LREAL, TIME, LTIME and STRING exists; there are no WSTRING_TO_, CHAR_TO_ or WCHAR_TO_ functions.

where the docs say so

  • pass TestConversionMatrixShape in lang/ir
  • pass TestConversionMatrixThroughST in lang/st
  • pass TestConformance/st-conversions-matrix in lang/conformance (9 of 9 passed)
  • pass TestNoWideOrCharConversionFunctions in lang/st (9 of 9 passed)

ST-036 verified

Integer narrowing wraps to the target width (DINT_TO_INT(70000) is 4464).

where the docs say so

  • pass TestConformance/st-conversions-matrix/integer narrowing wraps at the target width (DINT to INT, UINT, BYTE) in lang/conformance
  • pass TestConversionSemantics in lang/ir

ST-037 verified

A REAL or a STRING with no value in the target type faults the scan.

where the docs say so

ST-038 verified

A function block instance is declared like a variable (dwell : TON;) and invoked as its own statement with named arguments; => copies an output into a target at the call site and dwell.Q reads one anywhere.

where the docs say so

  • pass TestFBTONReachesQAfterPT in lang/st
  • pass TestUserFB_OutputBinding in lang/st

ST-039 verified

Struct fields and array elements use .field and [i] on either side of an assignment.

where the docs say so

  • pass TestLowerUDTFieldAccess in lang/st
  • pass TestLowerNestedAggregateAssign in lang/st
  • pass TestParseArrayIndexAssignmentTarget in lang/st

ST-040 verified

An array index outside the declared bounds faults the scan.

where the docs say so · One past the end and a negative index both fault the scan with "out of bounds"; the last valid index runs.

  • pass TestScanArrayIndexOutOfBoundsFaults in runtime (4 of 4 passed)

ST-041 verified

FUNCTION is stateless and returns one value assigned to the function's own name; FUNCTION_BLOCK is stateful and each instance owns its slots.

where the docs say so

  • pass TestUserFunc_DeclareAndCall in lang/st
  • pass TestUserFunc_NoState in lang/st
  • pass TestUserFB_DeclareAndCall in lang/st
  • pass TestUserFB_RetainsState in lang/st
  • pass TestUserFBInstancesOwnTheirSlots in lang/st

ST-042 verified

FUNCTION_BLOCK pins may be any type, a user TYPE included.

where the docs say so

  • pass TestFBStructPins in lang/st
  • pass TestFBStructPinFromLibraryFile in lang/st
  • pass TestEnumInStructsPinsArrays in lang/st
  • pass TestFBArrayAndAliasPins in lang/st

ST-043 verified

A VAR_IN_OUT pin is a reference: the caller binds a variable with := and the block's writes land in that variable when the call returns.

where the docs say so

  • pass TestVarInOutScalar in lang/st
  • pass TestVarInOutStructLocal in lang/st
  • pass TestUserFB_VarConstant_InOutWriteBack in lang/st

ST-044 verified

A VAR_EXTERNAL UDT bound to a VAR_IN_OUT pin round-trips through the tag store as one whole-struct write.

where the docs say so

  • pass TestVarInOutStructTagRoundTrip in runtime
  • pass TestVarInOutExternalStructTag in lang/st
  • pass TestVarInOutExternalStructWritesBackAsOneWholeStruct in lang/st

ST-045 verified

Every VAR_IN_OUT must be bound at every call, to an assignable of exactly the pin's type.

where the docs say so

  • pass TestVarInOutMustBeBound in lang/st
  • pass TestVarInOutTypeMustMatch in lang/st
  • pass TestVarInOutRejectsNonLValue in lang/st (4 of 4 passed)

ST-046 verified

A .st file with no PROGRAM keyword is a library of TYPE, FUNCTION and FUNCTION_BLOCK declarations in scope for every program in the directory; blocks may also be written in .ld/.fbd libraries, composed after the ST libraries.

where the docs say so

  • pass TestLibrariesUserFBFromFBD in runtime
  • pass TestFBStructPinFromLibraryFile in lang/st
  • pass TestLadderFunctionBlockFromST in lang/ld
  • pass TestGlobalConstantsLibraryAlone in lang/st
  • pass TestLibrariesComposeSTFirstThenGraphical in internal/stproject

ST-047 verified

naut check prints gcc-style file:line:col: message diagnostics and exits non-zero when it finds any.

where the docs say so

  • pass TestCheckReportsIdentifierColumn in cmd/naut
  • pass TestParseErrorPos in lang/st
  • pass TestLowerErrorPinsOffendingName in lang/st (13 of 13 passed)

ST-048 verified

Downloading a program warm-swaps it into a running controller with state carried across, and a failed compile leaves the running program untouched.

where the docs say so

  • pass TestSwapWarmCarriesRetainedState in runtime
  • pass TestSwapWarmCompileErrorLeavesRunning in runtime
  • pass TestProgramOnlineEditFlow in server
  • pass TestProgramDownloadCarriesStateAndFailedDownloadChangesNothing in server

ST-049 verified

A program in a *_test.yaml runs on a virtual clock and tests assert on tags over virtual time.

where the docs say so · TestRunsAreReproducible was dropped: run-to-run determinism is not what the page promises here.

  • pass TestConformance/fb-ton/Q false at PT-1ms, true at PT, ET clamps at PT in lang/conformance
  • pass TestVirtualDtIsExact in acceptance

ST-050 verified

Pointers and references (REF_TO, dereference) are not supported.

where the docs say so

  • pass TestCheckRejects/REF_TO in cmd/naut
  • pass TestCheckRejectsDereference/read in cmd/naut
  • pass TestCheckRejectsDereference/write in cmd/naut

ST-051 verified

VAR_ACCESS, CONFIGURATION, RESOURCE and TASK declarations are not supported; tasks come from nautilus.yaml or Go composition.

where the docs say so

  • pass TestCheckRejects/VAR_ACCESS in cmd/naut
  • pass TestCheckRejects/CONFIGURATION in cmd/naut
  • pass TestCheckRejects/RESOURCE in cmd/naut
  • pass TestCheckRejects/TASK in cmd/naut

ST-052 verified

Subrange types are not supported; TYPE declares only a STRUCT, an enumeration, or an alias.

where the docs say so

  • pass TestCheckRejects/subrange in cmd/naut

ST-053 verified

DATE, TOD, TIME_OF_DAY, DT and DATE_AND_TIME are rejected as unknown scalar types, and their literals do not evaluate.

where the docs say so · All five type names are refused as unknown scalar types, and the D#, DATE#, TOD#, TIME_OF_DAY#, DT# and DATE_AND_TIME# literals are refused with a diagnostic naming the construct (#307).

  • pass TestCheckRejects/DATE in cmd/naut
  • pass TestCheckRejects/TOD in cmd/naut
  • pass TestCheckRejects/TIME_OF_DAY in cmd/naut
  • pass TestCheckRejects/DT in cmd/naut
  • pass TestCheckRejects/DATE_AND_TIME in cmd/naut
  • pass TestCheckRejects/DATE literal in cmd/naut
  • pass TestCheckRejects/D literal in cmd/naut
  • pass TestCheckRejects/D literal date in cmd/naut
  • pass TestCheckRejects/DT literal in cmd/naut
  • pass TestCheckRejects/DATE_AND_TIME literal in cmd/naut
  • pass TestCheckRejects/TOD literal in cmd/naut
  • pass TestCheckRejects/TIME_OF_DAY literal in cmd/naut

ST-054 verified

Exponentiation with ** is not supported; EXPT(base, exp) is used instead.

where the docs say so · EXPT is run by fn-trig-log; `2.0 ** 3.0` in ST is refused by naut check.

ST-055 verified

Length-qualified strings (STRING[80]) are a parse error.

where the docs say so

  • pass TestCheckRejects/STRING[80] in cmd/naut

ST-056 verified

Aggregate initializers (:= [1, 2, 3]) in a declaration are a parse error.

where the docs say so

  • pass TestCheckRejects/array initializer in cmd/naut

ST-057 verified

SCL's # local prefix is rejected with a message saying to drop the #.

where the docs say so

  • pass TestHashPrefixRejectedEverywhere in lang/st
  • pass TestHashPrefixDiagnosed in lang/conformance (3 of 3 passed)