Skip to content

Standard Functions

Function lookup for shipped IEC function families, signatures, overload families, coverage status, per-function IEC references, and documented truST extensions such as assertion helpers.

Related: Standard Function Blocks, Data Types

IEC 61131-3 Edition 3.0 (2013) - Section 6.6.2.5

This specification defines standard-function signatures for trust-hir and the bounded runtime results explicitly written below.

1. Overview

Standard functions are predefined functions available in all IEC 61131-3 implementations.

Function Index

Name / Group Category Signature Shape IEC ref Status
*_TO_*, TO_*, TRUNC_*, *_BCD_TO_*, *_TO_BCD_* Conversion fixed or overloaded Tables 22-27 Implemented with documented extensions
ABS, SQRT, LN, LOG, EXP, SIN, COS, TAN, ASIN, ACOS, ATAN, ATAN2 Numerical fixed arity Table 28 Implemented
ADD, SUB, MUL, DIV, MOD, EXPT, MOVE Arithmetic fixed/extensible Table 29 Implemented
SHL, SHR, ROL, ROR Bit shift / rotate fixed arity Table 30 Implemented
AND, OR, XOR, NOT Bitwise boolean fixed/extensible Table 31 Implemented
SEL, MAX, MIN, LIMIT, MUX Selection fixed/extensible Table 32 Implemented
GT, GE, EQ, LE, LT, NE Comparison fixed/extensible Table 33 Implemented
LEN, LEFT, RIGHT, MID, CONCAT, INSERT, DELETE, REPLACE, FIND String fixed/extensible Table 34 Implemented
ADD_*, SUB_*, MUL_*, DIV_*, CONCAT_*, SPLIT_*, DAY_OF_WEEK Date / time fixed or overloaded Tables 35-36 Implemented
IS_VALID, IS_VALID_BCD Validate fixed arity Table 39 Implemented
REF Reference fixed arity Table 12 Implemented
LOWER_BOUND, UPPER_BOUND Array bound fixed arity IEC extension set Implemented
ASSERT_* Test assertions fixed arity non-IEC truST test-workflow extension

Function Characteristics

  • No internal state (stateless)
  • For deterministic stateless operations, identical inputs under the same fixed execution/numeric contract produce the same specified result. Host-clock extensions have their own environmental input contract.
  • Standard function results follow their input semantics under the declared numerical accuracy contract; this is not a promise of identical floating-point bits for unspecified-precision operations, even within one build. IEC 61131-3:2013 §6.6.2.5.8 requires implementation-specific accuracy dependencies to be stated. The planned shared runtime contract and its exact/tolerance-qualified claims are defined in specification 34 §12.
  • Can be overloaded for different types
  • Some have extensible inputs (e.g., ADD can take 2+ arguments)

2. Type Conversion Functions (Tables 22-27)

Conversion functions use the SRC_TO_DST form (Table 22). The overloaded TO_DST form exists but is deprecated. Truncation forms: - TRUNC (deprecated overloaded) - TRUNC_<DST> (overloaded, e.g., TRUNC_INT) - <SRC>_TRUNC_<DST> (typed, deprecated, e.g., REAL_TRUNC_INT)

When STRING/WSTRING is an input or output, the string shall conform to the external representation of the corresponding data type.

2.1 Numeric Conversions

Integer to Integer

Function From To Notes
*_TO_SINT ANY_INT SINT Truncation may occur
*_TO_INT ANY_INT INT Truncation may occur
*_TO_DINT ANY_INT DINT
*_TO_LINT ANY_INT LINT
*_TO_USINT ANY_INT USINT Unsigned conversion
*_TO_UINT ANY_INT UINT
*_TO_UDINT ANY_INT UDINT
*_TO_ULINT ANY_INT ULINT

Real to Integer

Function Notes
REAL_TO_INT IEC 60559 round-to-nearest, ties to even
LREAL_TO_INT IEC 60559 round-to-nearest, ties to even
TRUNC Deprecated overloaded truncation toward zero
TRUNC_* Overloaded truncation toward zero (e.g., TRUNC_INT)
*_TRUNC_* Typed truncation toward zero (deprecated)

Integer to Real

Function Notes
INT_TO_REAL Exact for small integers
*_TO_LREAL More precision

Real to Real

Function Notes
REAL_TO_LREAL Widening
LREAL_TO_REAL Narrowing and precision loss; a finite source that overflows basic-single range returns RuntimeError::Overflow

For an explicit numeric or binary-transfer conversion whose target is REAL or LREAL, truST returns RuntimeError::Overflow if the computed result would be NaN or either infinity. This includes LREAL_TO_REAL narrowing and the IEC Table 25 binary transfers DWORD_TO_REAL and LWORD_TO_LREAL. Finite binary transfers retain their exact bit-defined value. Text parsing rejects the non-finite spellings specified in section 2.7, but that rejection does not promise an exact runtime-error variant. The rejection policy is implementer-specific under IEC 61131-3 section 6.6.2.5.2 and Table 23; see docs/IEC_DECISIONS.md#2026-07-22---non-finite-real-result-and-explicit-conversion-policy.

For conversion into SINT, INT, DINT, or LINT, a representable integer value is preserved exactly. A value outside the destination's IEC Table 10 range returns RuntimeError::Overflow; truST does not wrap, saturate, truncate, or substitute an integer. This is the reviewed implementer-specific conversion fault policy specified by docs/specs/10-runtime-semantics.md#6-8-signed-integer-result-materialization; it is not an IEC deviation.

2.2 Bit Data Type Conversions (Table 24)

Binary transfer between BYTE/WORD/DWORD/LWORD. If the target is wider, the rightmost bits are preserved and the remaining bits are set to zero. If the target is narrower, only the rightmost bits are kept.

  • BYTE_TO_WORD, BYTE_TO_DWORD, BYTE_TO_LWORD
  • WORD_TO_BYTE, WORD_TO_DWORD, WORD_TO_LWORD
  • DWORD_TO_BYTE, DWORD_TO_WORD, DWORD_TO_LWORD
  • LWORD_TO_BYTE, LWORD_TO_WORD, LWORD_TO_DWORD
  • BYTE_TO_BOOL, WORD_TO_BOOL, DWORD_TO_BOOL, LWORD_TO_BOOL: binary transfer of the rightmost bit into the target (Table 24 rows 4, 8, 12 and 16), so BYTE_TO_BOOL(2#1101_0100) = FALSE and WORD_TO_BOOL(16#0003) = TRUE. The overloaded TO_BOOL of a bit string does the same.

2.3 Bit/Numeric Conversions (Table 25)

Binary transfer between bit strings and numeric types as listed in Table 25: - Bit to numeric: BYTE/WORD/DWORD/LWORD to SINT/INT/DINT/LINT/USINT/UINT/UDINT/ULINT, DWORD_TO_REAL and LWORD_TO_LREAL - Numeric to bit: SINT/INT/DINT/LINT/USINT/UINT/UDINT/ULINT to BYTE/WORD/DWORD/LWORD, REAL_TO_DWORD and LREAL_TO_LWORD - BOOL to SINT/INT/DINT/LINT/USINT/UINT/UDINT/ULINT results in 0 or 1 - Table 25 has no other pair of a bit string and a real (no DWORD_TO_LREAL, no WORD_TO_REAL), so a bit string's numeric value reaches a real through an integer: UDINT_TO_LREAL(DWORD_TO_UDINT(d)). - A bit string takes no arithmetic operator: the arithmetic functions take ANY_NUM (§6.6.2.5.8, Table 29), so its value is converted to an integer first, as DWORD_TO_UDINT(TIME_TO_DWORD(t)) is before it is divided.

REAL/LREAL to a bit string of another width and BOOL to REAL/LREAL are not in Table 25; truST provides them as the extensions of section 2.9.

2.4 Date and Time Conversions (Table 26)

Function Description
LTIME_TO_TIME LTIME to TIME
TIME_TO_LTIME TIME to LTIME
LDT_TO_DT LDT to DT
LDT_TO_DATE Extract DATE from LDT
LDT_TO_LTOD Extract LTOD from LDT
LDT_TO_TOD Extract TOD from LDT (precision loss possible)
DT_TO_LDT DT to LDT
DT_TO_DATE Extract DATE from DT
DT_TO_LTOD Extract LTOD from DT
DT_TO_TOD Extract TOD from DT
LTOD_TO_TOD LTOD to TOD
TOD_TO_LTOD TOD to LTOD

Implementer extension note: - truST additionally provides TIME_TO_DWORD and DWORD_TO_TIME as documented non-IEC conversion helpers. - These extensions use milliseconds: TIME_TO_DWORD(T#123ms) = DWORD#123, DWORD_TO_TIME(DWORD#123) = T#123ms. - Under a CODESYS vendor_profile (specification 14), truST additionally provides duration-to-integer and time-of-day-to-dword conversions as documented non-IEC helpers (IEC_DEVIATIONS.md 2026-10-28); the IEC dialect rejects them: TIME_TO_<int> and LTIME_TO_<int> yield the whole millisecond (respectively nanosecond) count, kept to the destination's low-order bits as the other CODESYS integer conversions are (section 2.10): TIME_TO_DINT(T#1500ms) = 1500, TIME_TO_SINT(T#300ms) = 44; TOD_TO_DWORD and LTOD_TO_DWORD yield the milliseconds of the day: TOD_TO_DWORD(TOD#01:00:00) = 3600000. A negative duration rejects with an overflow fault, matching the TIME_TO_DWORD contract. - Round 2 (IEC_DEVIATIONS.md 2026-11-06), under the CODESYS profile only, the remaining inverses as whole families; the IEC dialect rejects them: <ANY_INT>_TO_TIME reads the integer as a millisecond count through its low-order 32 bits (measured: DINT_TO_TIME(DINT#-1) = T#49d17h2m47s295ms, INT_TO_TIME(INT#-1) = T#49d17h2m47s295ms); REAL_TO_TIME/LREAL_TO_TIME read the value as milliseconds truncated toward zero — truncation, not the DEV-024 rounding applied to REAL/LREAL to integer — and a negative value saturates to T#0ms (measured: REAL_TO_TIME(2.5) = T#2ms, REAL_TO_TIME(-1.0) = T#0ms, section 2.10); DWORD_TO_TOD yields the ms-of-day magnitude modulo 24 h (measured: DWORD_TO_TOD(DWORD#86400000) = TOD#0:0:0, DWORD_TO_TOD(DWORD#16#FFFFFFFF) = TOD#17:02:47.295).

2.5 Character Type Conversions (Table 27)

Function Description
WSTRING_TO_STRING Convert WSTRING to STRING
WSTRING_TO_WCHAR First character of WSTRING
STRING_TO_WSTRING Convert STRING to WSTRING
STRING_TO_CHAR First character of STRING
WCHAR_TO_WSTRING Single-character WSTRING
WCHAR_TO_CHAR Convert WCHAR to CHAR
CHAR_TO_STRING Single-character STRING
CHAR_TO_WCHAR Convert CHAR to WCHAR

Implementer extension note: - truST also accepts direct character-to-bitstring conversions such as CHAR_TO_BYTE and WCHAR_TO_WORD as documented vendor extensions. - Other conversions involving STRING/WSTRING (for example, numeric to string) remain implementer-specific. When provided, they shall follow the external literal representation rules in 6.3.3.

2.6 BCD Conversions (Table 22)

Function Description
*_BCD_TO_** Typed BCD conversion from BYTE/WORD/DWORD/LWORD to USINT/UINT/UDINT/ULINT
BCD_TO_** Overloaded BCD conversion (bit string to unsigned integer)
**_TO_BCD_* Typed BCD conversion from USINT/UINT/UDINT/ULINT to BYTE/WORD/DWORD/LWORD
TO_BCD_** Overloaded BCD conversion (unsigned integer to bit string)
// Example
BCDValue := 16#0042;
UIntValue := BCD_TO_UINT(BCDValue);  // UIntValue = 42

2.7 Runtime Text-Conversion Representatives

IEC 61131-3 Ed.3 section 6.6.2.5.2 requires conversions involving STRING/WSTRING to use the external representation of the source or destination type, while conversion-error handling remains implementer-specific. The bounded truST runtime contract contains these exact representatives:

Call Result
DINT_TO_STRING(DINT#42) STRING#'42'
REAL_TO_STRING(REAL#1.25) STRING#'1.25'
DWORD_TO_STRING(DWORD#42) STRING#'42'
STRING_TO_DINT(STRING#'42') DINT#42

Vendor-extension representatives (IEC_DEVIATIONS.md 2026-10-28), under a CODESYS vendor_profile only: TO_STRING and the typed *_TO_STRING spellings accept a BOOL ('TRUE'/'FALSE'), a TIME/LTIME (the vendor's duration text), and an enumerated value (its variant name); the IEC dialect rejects them. The duration text is T# and each unit that is not zero from days down to milliseconds, T#0ms for no time; an LTIME has the prefix LTIME# and units down to nanoseconds, LTIME#0ns for none (measured on a live e!COCKPIT runtime on a WAGO PFC200, 2026-11-06: TO_STRING(T#1500ms) = 'T#1s500ms', TO_STRING(T#2000ms) = 'T#2s', TO_STRING(T#1h2m3s4ms) = 'T#1h2m3s4ms', TO_STRING(T#2d) = 'T#2d', TO_STRING(T#49d17h2m47s295ms) = 'T#49d17h2m47s295ms', TO_STRING(T#0ms) = 'T#0ms', TIME_TO_STRING(T#1500ms) = 'T#1s500ms', TO_STRING(LTIME#1500ms) = 'LTIME#1s500ms', TO_STRING(LTIME#1h2m3s4ms5us6ns) = 'LTIME#1h2m3s4ms5us6ns', TO_STRING(LTIME#0ns) = 'LTIME#0ns'). A TIME's part below a millisecond is left out, as the vendor's TIME counts milliseconds, and a negative duration, which the vendor's unsigned types cannot hold, has its sign after the # as an IEC literal does ('T#-1s500ms'). In every dialect, TO_STRING over a STRING[n] value is an identity copy. Time-of-day and date string forms are deliberately not provided.

Round 2 representatives (IEC_DEVIATIONS.md 2026-11-06), under a CODESYS vendor_profile only: STRING_TO_<int>, STRING_TO_<bit string> and the overloaded TO_<int>/TO_<bit string> spellings accept a STRING, all with one parse rule — leading spaces, an optional sign, a decimal or 2#/8#/16# literal, trailing characters ignored; text with no number yields 0 and an out-of-range value keeps the low-order bits of the destination (measured on a live e!COCKPIT runtime: TO_BYTE('123abc') = 123, TO_BYTE('300') = 44, TO_BYTE('abc') = 0, TO_BYTE('') = 0). The IEC dialect keeps the strict representative parse (STRING_TO_DINT('123abc') faults) and rejects string-to-bit-string conversions.

These representatives do not define canonical output spelling for every numeric or bit-string value. STRING_TO_REAL('NaN') and STRING_TO_LREAL('inf') reject without producing a runtime REAL/LREAL value. This specification intentionally does not freeze the runtime-error variant used for those two text-rejection cases.

The compact text implementation shall preserve the existing conversion contract on every platform. Decimal parsing uses correctly rounded binary64 conversion before the existing destination conversion, including subnormal values and signed zero; lossy parsing is forbidden. Formatting preserves the prior shortest round-tripping binary64 digits expanded into decimal notation, with the existing .0 suffix for integral real conversion output. REAL continues to widen to binary64 before formatting. Normalization, malformed-input errors and non-finite rejection remain unchanged. Compact lookup tables are an implementation choice, not a per-device capability restriction (IEC §6.6.2.5.2, Table 23).

2.8 Runtime Conversion Dispatch and Boundary Contract

The following requirements bind runtime execution of IEC 61131-3 Tables 22-27 and the truST conversion extensions documented above:

  • Conversion names are ASCII case-insensitive. SRC_TO_DST and SRC_TRUNC_DST first normalize the argument to the named source type: an integer or real source accepts a value of its family (ANY_INT or ANY_REAL) when that value is representable in the source type (for a real source: finite after IEC 60559 rounding to the source width), and raises RuntimeError::Overflow otherwise, so SINT_TO_BYTE(INT#256) faults before the BYTE conversion; a BCD source requires exactly its declared type and raises RuntimeError::TypeMismatch for any other. TO_DST, TRUNC_DST, and TRUNC infer the source from the runtime value. Every conversion requires exactly one argument.
  • Ordinary real-to-integer conversions round to nearest with ties to even. TRUNC, TRUNC_DST, and SRC_TRUNC_DST truncate toward zero. Non-finite inputs and results outside the complete destination range raise RuntimeError::Overflow; this includes the ULINT upper boundary.
  • Integer narrowing and signed-to-unsigned conversion are checked numeric conversions. Bit-string transfers are different: widening zero-extends and narrowing preserves the rightmost destination-width bits. Bit-string to a signed integer sign-extends from the destination width, and bit-string to BOOL keeps the rightmost bit.
  • REAL_TO_DWORD, DWORD_TO_REAL, LREAL_TO_LWORD, and LWORD_TO_LREAL are exact binary transfers for finite values. A transferred NaN or infinity is rejected as RuntimeError::Overflow.
  • Text-to-integer conversion trims surrounding whitespace, accepts single _ separators between two digits (the IEC 61131-3 §6.3.2 rule for numeric literals), and accepts base#digits for bases 2 through 36 with an optional sign on digits. An empty value, invalid digit, leading, trailing or repeated _, or base outside 2 through 36 raises RuntimeError::TypeMismatch; malformed input never panics.
  • Text-to-real conversion accepts finite Rust/IEC-compatible decimal and exponent text after whitespace and _ normalization, with the same single-separator rule. NaN, infinity, and a finite decimal whose destination result becomes non-finite raise RuntimeError::Overflow.
  • Numeric and bit-string text output is decimal. Integral finite real output retains a .0 suffix. Character conversion requires exactly one Unicode scalar for text input and checks the target CHAR/WCHAR code range.
  • A text result longer than the implementer maximum of 65,535 bytes (STRING) or UTF-16 code units (WSTRING) raises RuntimeError::StringCapacityExceeded (IEC 61131-3 Ed.3 §6.6.2.5.11), for example a WSTRING_TO_STRING of 30,000 characters that each need three bytes of UTF-8.
  • BCD encoding accepts unsigned integers only and fails with RuntimeError::Overflow when the decimal digits do not fit the destination bit string. BCD decoding rejects every nibble above nine with RuntimeError::TypeMismatch and checks the unsigned destination range.
  • Short DT extraction uses Euclidean day division at the one-millisecond default profile, so instants before the epoch produce the preceding DATE and a non-negative TOD. Long-to-short conversion floors sub-millisecond instants consistently. TIME_TO_DWORD counts whole milliseconds and rejects negative or greater-than-u32::MAX values.

2.9 Vendor Extension Conversions

truST also accepts these conversions, which IEC 61131-3 Tables 22-27 do not list but CODESYS and TwinCAT projects use. They follow the truST conversion rules above (no wrapping or saturation):

Function Result
REAL_TO_BYTE, REAL_TO_WORD, REAL_TO_LWORD, LREAL_TO_BYTE, LREAL_TO_WORD, LREAL_TO_DWORD The number converted like REAL_TO_<unsigned> of the target width (USINT, UINT, UDINT, ULINT): round to nearest, ties to even; a value outside the target range or a non-finite value returns RuntimeError::Overflow. REAL_TO_DWORD and LREAL_TO_LWORD remain the Table 25 binary transfers.
<ANY_INT>_TO_BOOL, <ANY_REAL>_TO_BOOL, and TO_BOOL of such a value TRUE when the value is not zero. A non-finite REAL/LREAL returns RuntimeError::Overflow.
BOOL_TO_REAL, BOOL_TO_LREAL 1.0 for TRUE, 0.0 for FALSE.

Examples: REAL_TO_BYTE(200.4) = BYTE#200, REAL_TO_BYTE(256.0) returns RuntimeError::Overflow, INT_TO_BOOL(-3) = TRUE, INT_TO_BOOL(0) = FALSE. BYTE/WORD/DWORD/LWORD_TO_BOOL are the IEC Table 24 conversions of section 2.2, not extensions.

2.10 CODESYS Conversion Profile

A project selects the conversion rules with the vendor_profile of its manifest, the same setting the language server reads ([project] vendor_profile in trust-lsp.toml, see docs/public/reference/config/vendor-profiles.md):

vendor_profile Conversion rules
codesys, beckhoff, twincat CODESYS rules (this section); TwinCAT is built on CODESYS
absent, iec, siemens, mitsubishi, gxworks3 IEC rules and truST policies (sections 2.1 to 2.9)
any other value refused: the project is not built, run or tested, and the language server reports C006

Every tool that executes a project's code applies the project's rules: trust-runtime build, check and run (from a project folder or a runtime.toml folder), trust-dev test, debug sessions (whose project folder holds the manifest, io.toml or runtime.toml), and trust-harness started with --project DIR. A trust-harness load request may name "vendor_profile" itself, which takes precedence over --project; without either it uses the IEC rules. In the Rust API the rules are CompileSession::with_conversion_profile and TestHarness::from_session. Editor diagnostics do not depend on the conversion rules: the type checker accepts the same conversions under both. A running runtime keeps the rules it started with; an online change does not switch them.

Under the CODESYS rules the conversion functions follow the CODESYS Development System instead of the IEC rules and truST policies above:

Conversion iec (default) codesys
REAL/LREAL to an integer or bit string round to nearest, ties to even round half away from zero (.1-.4 down, .5-.9 up): REAL_TO_INT(2.5) = 3, REAL_TO_INT(-1.5) = -2
a value outside the target range (integer narrowing, signed/unsigned change, REAL to integer) RuntimeError::Overflow the low-order bits of the target width, two's complement: DINT_TO_SINT(128) = -128, INT_TO_UINT(-1) = 65535, REAL_TO_BYTE(300.0) = 44
REAL_TO_DWORD, LREAL_TO_LWORD Table 25 binary transfer the number: REAL_TO_DWORD(3.7) = 4
DWORD_TO_REAL, LWORD_TO_LREAL Table 25 binary transfer the number: DWORD_TO_REAL(5) = 5.0
BYTE/WORD/DWORD/LWORD_TO_BOOL (and TO_BOOL of a bit string) Table 24: the rightmost bit TRUE when the value is not zero: BYTE_TO_BOOL(16#80) = TRUE
<ANY_INT>_TO_TIME (an integer read as a millisecond count) Table 26 lists none the low-order 32 bits of the count: DINT_TO_TIME(DINT#-1) = T#49d17h2m47s295ms (measured)
REAL_TO_TIME, LREAL_TO_TIME (the value read as milliseconds) Table 26 lists none truncated toward zero, negative saturates to T#0ms: REAL_TO_TIME(2.5) = T#2ms, REAL_TO_TIME(-1.0) = T#0ms (measured)
DWORD_TO_TOD Table 26 lists none the ms-of-day magnitude modulo 24 h: DWORD_TO_TOD(DWORD#86400000) = TOD#0:0:0, DWORD_TO_TOD(DWORD#16#FFFFFFFF) = TOD#17:02:47.295 (measured)
STRING_TO_<int>, STRING_TO_<bit string>, TO_<int>(STRING), TO_<bit string>(STRING) STRING_TO_<int> keeps the strict representative parse of section 2.7; string to a bit string rejects one CODESYS parse rule: leading spaces, an optional sign, a decimal or 2#/8#/16# literal, trailing characters ignored, no number yields 0, out of range keeps the low-order bits: TO_BYTE('123abc') = 123, TO_BYTE('300') = 44, TO_BYTE('abc') = 0 (measured)

The round-2 values above (2026-11-06) are measured on a live e!COCKPIT runtime on a WAGO PFC200, not derived from the vendor documentation (IEC_DEVIATIONS.md 2026-11-06). The rounding row does not extend to REAL_TO_TIME/LREAL_TO_TIME: the vendor truncates toward zero there (REAL_TO_TIME(2.5) = T#2ms, measured — despite the DEV-024 rounding of REAL/LREAL to an integer), and a negative REAL/LREAL value saturates to T#0ms (REAL_TO_TIME(-1.0), measured). A negative integer count is different: the vendor TIME is a 32-bit unsigned count, so the integer reads through its low-order 32 bits (DINT_TO_TIME(DINT#-1) = T#49d17h2m47s295ms, measured). The vendor string parse applies to every string-to-integer and string-to-bit-string conversion under the profile, typed and overloaded alike, and consumes _ inside the number as a digit separator (TO_BYTE('1_0') = 10, measured): TO_BYTE('123abc') and STRING_TO_DINT('123abc') are both 123 — one parse rule per profile.

A non-finite REAL/LREAL source still returns RuntimeError::Overflow, and conversions whose CODESYS result is "undefined, target system-dependent" keep the low-order bits. The profile changes conversion functions only; arithmetic overflow, implicit conversions and the set of available conversions are unchanged.

The out-of-range results are implementer-specific under IEC 61131-3 Ed.3 6.6.2.5.3 and Table 23. Three CODESYS rules conflict with normative IEC requirements and are recorded in docs/IEC_DEVIATIONS.md: the rounding of ties (IEC_DEVIATIONS 2026-10-28), the numeric REAL/LREAL and DWORD/LWORD conversions (IEC_DEVIATIONS 2026-10-28) and the bit string to BOOL test (IEC_DEVIATIONS 2026-10-28).

References: CODESYS Development System, REAL_TO / LREAL_TO and integer conversions ("If the number to be converted exceeds the range limit, then the first bytes of the number are ignored"); for TO_BOOL, the CODESYS-based ABB Automation Builder documentation.

3. Numerical Functions (Table 28)

Basic Arithmetic

Function Description Signature
ABS Absolute value ABS(x: ANY_NUM) : ANY_NUM
SQRT Square root SQRT(x: ANY_REAL) : ANY_REAL
LN Natural logarithm LN(x: ANY_REAL) : ANY_REAL
LOG Base 10 logarithm LOG(x: ANY_REAL) : ANY_REAL
EXP Exponential (e^x) EXP(x: ANY_REAL) : ANY_REAL

Trigonometric Functions (Table 28)

Function Description Domain Range
SIN Sine Radians -1.0 to 1.0
COS Cosine Radians -1.0 to 1.0
TAN Tangent Radians Real
ASIN Arc sine -1.0 to 1.0 -π/2 to π/2
ACOS Arc cosine -1.0 to 1.0 0 to π
ATAN Arc tangent Real -π/2 to π/2
ATAN2 Arc tangent (y/x) Real, Real -π to π
// Examples
Y := SIN(X);              // X in radians
Angle := ATAN2(DY, DX);   // Four-quadrant arctangent

Arithmetic Functions (Table 29)

Function Description Signature
ADD Addition ADD(IN1, IN2, ...: ANY_NUM) : ANY_NUM
MUL Multiplication MUL(IN1, IN2, ...: ANY_NUM) : ANY_NUM
SUB Subtraction SUB(IN1, IN2: ANY_NUM) : ANY_NUM
DIV Division DIV(IN1, IN2: ANY_NUM) : ANY_NUM
MOD Modulo MOD(IN1, IN2: ANY_INT) : ANY_INT
EXPT Exponentiation EXPT(IN1: ANY_REAL, IN2: ANY_NUM) : ANY_REAL
MOVE Assignment MOVE(IN: ANY) : ANY

Note: ADD and MUL are extensible (can take more than 2 inputs).

The EXPT standard-function signature remains the IEC ANY_REAL-base contract shown above. The reviewed INT#2 ** INT#3 host-evaluator extension is separate and is recorded in IEC_DEVIATIONS.md; it does not widen the standard-function signature.

For finite REAL or LREAL operands, EXP and EXPT return a value only when the result remains finite at the operand's IEC basic width (single for REAL, double for LREAL). A result outside that finite range raises RuntimeError::Overflow before assignment storage and leaves the target unchanged. The runtime does not clamp or store infinity or NaN. EXPT(0.0, 0.0) is 1.0; a zero base with a negative exponent, and a negative base with a non-integer exponent, have no finite result and raise Overflow. Results keep IEC 60559 signed zeros and subnormal values, and an underflow to a subnormal value or zero is not a fault. See docs/specs/10-runtime-semantics.md and docs/IEC_DECISIONS.md#2026-07-22---non-finite-real-result-and-explicit-conversion-policy.

3.1 Runtime Numerical Conformance Contract

The following requirements bind the runtime implementation of IEC 61131-3 Tables 28 and 29:

  • ABS preserves the operand's elementary numeric type. Unsigned values are returned unchanged. The most-negative value of each signed integer type raises RuntimeError::Overflow; it is never wrapped. ABS(-0.0) is +0.0.
  • MOD returns IN1 - (IN1 / IN2) * IN2 with integer division truncating toward zero (IEC 61131-3 Table 29), so a nonzero result has the sign of IN1. MOD by zero raises RuntimeError::ModuloByZero; IEC Table 29 defines that case as 0, and the deviation is recorded in docs/IEC_DEVIATIONS.md.
  • The named real functions accept REAL or LREAL. A REAL input produces a REAL result and an LREAL input produces an LREAL result. A domain error or non-finite result raises RuntimeError::Overflow.
  • ATAN2(Y, X) accepts matching real widths and the mixed REAL/LREAL pairs. Either mixed pair produces LREAL.
  • Extensible ADD and MUL require at least two inputs and evaluate from left to right. SUB, DIV, MOD, and EXPT require exactly two inputs, while MOVE requires exactly one.
  • Date/time participation makes ADD a two-input operation. Duration multiplication accepts the duration on either side of the scale factor; duration division requires the duration on the left. These operations preserve TIME versus LTIME.
  • Arithmetic errors, including division by zero and an unrepresentable intermediate or result, are returned as runtime errors. No later variadic input is evaluated after an earlier fold step fails.
  • MOVE returns an equal clone of its input value without numeric widening or other conversion.

4. Bit Shift Functions (Table 30)

Function Description Signature
SHL Shift left SHL(IN: ANY_BIT, N: ANY_INT) : ANY_BIT
SHR Shift right SHR(IN: ANY_BIT, N: ANY_INT) : ANY_BIT
ROL Rotate left ROL(IN: ANY_BIT, N: ANY_INT) : ANY_BIT
ROR Rotate right ROR(IN: ANY_BIT, N: ANY_INT) : ANY_BIT
// Examples
X := 2#1100_0000;
Y := SHL(X, 2);    // Y = 2#0000_0000 (bits shifted out)
Z := ROL(X, 2);    // Z = 2#0000_0011 (bits rotated)

IN must be a bit-string value (ANY_BIT); an integer, real or other IN raises RuntimeError::TypeMismatch.

5. Bitwise Boolean Functions (Table 31)

Function Description Signature
AND Bitwise AND AND(IN1, IN2, ...: ANY_BIT) : ANY_BIT
OR Bitwise OR OR(IN1, IN2, ...: ANY_BIT) : ANY_BIT
XOR Bitwise XOR XOR(IN1, IN2, ...: ANY_BIT) : ANY_BIT
NOT Bitwise NOT NOT(IN: ANY_BIT) : ANY_BIT

Note: AND, OR, XOR are extensible.

At runtime, shift and rotate functions preserve the input bit-string type and width. Bitwise Boolean functions return the common participating bit-string width; a narrower input is zero-extended to that width. The reviewed mixed representative is OR(BYTE#16#0F, WORD#16#00F0) = WORD#16#00FF.

Shift counts are non-negative integers. For SHL and SHR, a count greater than or equal to the operand width produces zero at that same width. For ROL and ROR, the count is reduced modulo the operand width; zero and exact multiples of the width are identity operations for every bit-string width, including LWORD. A negative count raises RuntimeError::TypeMismatch.

Extensible AND, OR, and XOR require at least two inputs. Their result width is the widest participating bit-string width, narrower values are zero-extended, and the final result is masked to that width. NOT requires one input, flips only the bits inside its declared width, and preserves that width.

// Examples
Mask := 16#FF00;
Data := 16#1234;
Result := AND(Data, Mask);   // Result = 16#1200
Result := OR(Data, 16#00FF); // Result = 16#12FF

6. Selection Functions (Table 32)

Function Description Signature
SEL Binary selection SEL(G: BOOL, IN0, IN1: ANY) : ANY
MAX Maximum MAX(IN1, IN2, ...: ANY_ELEMENTARY) : ANY_ELEMENTARY
MIN Minimum MIN(IN1, IN2, ...: ANY_ELEMENTARY) : ANY_ELEMENTARY
LIMIT Bounded value LIMIT(MN, IN, MX: ANY_ELEMENTARY) : ANY_ELEMENTARY
MUX Multiplexer MUX(K: ANY_INT, IN0, IN1, ...: ANY) : ANY
// SEL: Returns IN0 if G=FALSE, IN1 if G=TRUE
Result := SEL(Condition, ValueIfFalse, ValueIfTrue);

// MAX/MIN
MaxValue := MAX(A, B, C, D);
MinValue := MIN(A, B, C, D);

// LIMIT: Clamps IN between MN and MX
Output := LIMIT(0, Input, 100);  // 0 <= Output <= 100

// MUX: Returns IN[K]
Selected := MUX(Index, Value0, Value1, Value2, Value3);

The runtime returns the selected input with its runtime value identity intact, including an enumerated value selected by SEL. Named invocation binds the documented formal names; the reviewed executed-ST representative SEL(G := TRUE, IN0 := INT#4, IN1 := INT#7) returns INT#7.

6.1 Runtime Selection Conformance Contract

  • SEL requires a BOOL selector and exactly two data inputs. FALSE selects IN0; TRUE selects IN1.
  • MIN and MAX require at least two inputs and compare every input after resolving one common elementary runtime type.
  • LIMIT first resolves MN, IN, and MX to one common type, then returns MIN(MAX(IN, MN), MX) (IEC 61131-3 Table 32): MN when IN < MN, MX when IN > MX, and IN otherwise. With inverted limits (MN > MX) the result is MX.
  • MUX interprets K as a zero-based input number. A negative or out-of-range selector raises RuntimeError::IndexOutOfBounds. All candidate inputs must belong to one valid common type even when the selected candidate itself would otherwise be usable.
  • Common numeric widening is applied before returning a selection result. Incompatible signed/unsigned, narrow/wide string, time-family, or enum-family inputs raise RuntimeError::TypeMismatch.

7. Comparison Functions (Table 33)

Function Description Signature
GT Greater than GT(IN1, IN2, ...: ANY_ELEMENTARY) : BOOL
GE Greater or equal GE(IN1, IN2, ...: ANY_ELEMENTARY) : BOOL
EQ Equal EQ(IN1, IN2, ...: ANY_ELEMENTARY) : BOOL
LE Less or equal LE(IN1, IN2, ...: ANY_ELEMENTARY) : BOOL
LT Less than LT(IN1, IN2, ...: ANY_ELEMENTARY) : BOOL
NE Not equal NE(IN1, IN2: ANY_ELEMENTARY) : BOOL

Note: For GT, GE, EQ, LE, LT with multiple inputs, checks if sequence is monotonic. NE is not extensible.

For values of the same enumerated type, runtime EQ and NE compare the enumerated value identity. The reviewed representatives are EQ(RED, RED) = TRUE and NE(RED, GREEN) = TRUE.

The runtime comparison contract for IEC 61131-3 Table 33 is pairwise and adjacent: GT(A, B, C) means (A > B) AND (B > C), with equivalent chaining for GE, EQ, LE, and LT. Those functions require at least two inputs; NE requires exactly two. Before comparison, all inputs resolve to one common elementary runtime type. Numeric widths may widen where the conversion is lossless under the normal numeric policy, while bit strings widen by zero-extension. Strings, time values, and enumerations compare only within their compatible family; incompatible families raise RuntimeError::TypeMismatch.

// Examples
InOrder := GT(A, B, C);      // TRUE if A > B > C
AllEqual := EQ(X, Y, Z);     // TRUE if X = Y = Z
Different := NE(A, B);       // TRUE if A <> B

8. String Functions (Table 34)

String declaration syntax (STRING[n], WSTRING[n]) and character indexing are owned by 02-data-types.md. This section owns the callable string functions.

Function Description Signature
LEN Length LEN(IN: ANY_STRING) : INT
LEFT Left substring LEFT(IN: ANY_STRING, L: ANY_INT) : ANY_STRING
RIGHT Right substring RIGHT(IN: ANY_STRING, L: ANY_INT) : ANY_STRING
MID Middle substring MID(IN: ANY_STRING, L, P: ANY_INT) : ANY_STRING
CONCAT Concatenate CONCAT(IN1, IN2, ...: ANY_STRING) : ANY_STRING
INSERT Insert string INSERT(IN1, IN2: ANY_STRING, P: ANY_INT) : ANY_STRING
DELETE Delete substring DELETE(IN: ANY_STRING, L, P: ANY_INT) : ANY_STRING
REPLACE Replace substring REPLACE(IN1, IN2: ANY_STRING, L, P: ANY_INT) : ANY_STRING
FIND Find position FIND(IN1, IN2: ANY_STRING) : INT
// Examples
Str := 'Hello World';
Length := LEN(Str);                    // 11
Left5 := LEFT(Str, 5);                 // 'Hello'
Right5 := RIGHT(Str, 5);               // 'World'
Mid := MID(Str, 5, 7);                 // 'World' (5 chars starting at pos 7)
Full := CONCAT('Hello', ' ', 'World'); // 'Hello World'
Inserted := INSERT('AC', 'B', 1);      // 'ABC'
Deleted := DELETE('ABCD', 2, 2);       // 'AD' (delete 2 chars at pos 2)
Replaced := REPLACE('ABCD', 'XX', 2, 2); // 'AXXD'
Pos := FIND('ABCABC', 'BC');           // 2 (first occurrence)

Position Notes: - Position 1 is the first character - FIND returns 0 if not found - Runtime positions and lengths count Unicode scalar values (characters), not UTF-8 bytes (DEV-017). Thus LEN('ÄBC') = 3, LEFT('ÄBC', 1) = 'Ä', and FIND('ÄBC', 'B') = 2. The same element-count rule applies to the reviewed WSTRING length. - A declared capacity counts storage units instead: STRING[n] holds n bytes of UTF-8 and WSTRING[n] n UTF-16 code units (02-data-types.md §6). 'ÄBC' is three characters in four bytes, so it fits STRING[4] but not STRING[3].

8.1 Runtime String Boundary Contract

  • Every result preserves the input family: STRING operations return STRING, and WSTRING operations return WSTRING. Binary and variadic operations reject mixed narrow/wide inputs.
  • LEFT and RIGHT return the empty string for a non-positive length and the whole string when the requested length exceeds the element count.
  • MID, DELETE, and REPLACE use one-based positions. Positions below one are clamped to the first element. A start beyond the final element returns the empty string for MID and leaves the input unchanged for DELETE and REPLACE. A length that runs past the final element is clamped to it.
  • INSERT treats P as the number of existing elements before the insertion: zero inserts before the first element, P = LEN(IN1) appends, and larger values also append. For example, INSERT('AC', 'B', 1) = 'ABC'.
  • DELETE with a non-positive length leaves the input unchanged. REPLACE with a non-positive length inserts the replacement at its clamped start without deleting an element.
  • FIND returns the one-based position of the first match and zero when no match exists. An empty IN2 matches at position 1. A found position that cannot be represented as INT raises RuntimeError::Overflow.
  • A result longer than the implementer maximum of 65,535 bytes (STRING) or UTF-16 code units (WSTRING) is an error (IEC 61131-3 Ed.3 §6.6.2.5.11, Table 34): CONCAT, INSERT, REPLACE, LEFT, RIGHT, MID and DELETE raise RuntimeError::StringCapacityExceeded (runtime_string_capacity_exceeded) and return no value. The conversions to STRING and WSTRING (§2) apply the same maximum; a WSTRING of 65,535 code units can need up to 196,605 bytes as STRING.
  • The internal __TRUST_LIMIT_STRING(IN, L) assignment helper keeps the longest prefix of whole characters within L bytes of UTF-8 (STRING) or L UTF-16 code units (WSTRING), returns a value that fits unchanged, preserves the string family, and rejects a negative or otherwise non-u32 capacity with RuntimeError::Overflow.

9. Date and Time Functions (Tables 35-36)

Time Arithmetic

Function Description
ADD Overloaded time/date addition (see Table 35): the time of day or date and time first, the duration second, as the + operator (specification 05); the duration first is InvalidArgumentType
ADD_TIME TIME + TIME → TIME
ADD_LTIME LTIME + LTIME → LTIME
ADD_TOD_TIME TOD + TIME → TOD
ADD_LTOD_LTIME LTOD + LTIME → LTOD
ADD_DT_TIME DT + TIME → DT
ADD_LDT_LTIME LDT + LTIME → LDT
SUB Overloaded time/date subtraction (see Table 35)
SUB_TIME TIME - TIME → TIME
SUB_LTIME LTIME - LTIME → LTIME
SUB_DATE_DATE DATE - DATE → TIME
SUB_LDATE_LDATE LDATE - LDATE → LTIME
SUB_TOD_TIME TOD - TIME → TOD
SUB_LTOD_LTIME LTOD - LTIME → LTOD
SUB_TOD_TOD TOD - TOD → TIME
SUB_LTOD_LTOD LTOD - LTOD → LTIME
SUB_DT_TIME DT - TIME → DT
SUB_LDT_LTIME LDT - LTIME → LDT
SUB_DT_DT DT - DT → TIME
SUB_LDT_LDT LDT - LDT → LTIME
MUL_TIME TIME * ANY_NUM → TIME
MUL_LTIME LTIME * ANY_NUM → LTIME
DIV_TIME TIME / ANY_NUM → TIME
DIV_LTIME LTIME / ANY_NUM → LTIME

Notes: - Overloaded ADD/SUB apply only within the TIME/DT/DATE/TOD set or the LTIME/LDT/LDATE/LTOD set. - Result range overflow is an error; output ranges are Implementer specific.

IEC 61131-3 Ed.3 section 6.6.2.5.12 and Table 35 define the accepted operand and result families and require an error when the result exceeds the implementer-specific output range. Within that boundary, truST applies this product contract:

  • TIME and LTIME arithmetic preserves the operand width and uses checked signed nanoseconds; a result outside i64 is RuntimeError::Overflow.
  • Short DATE, TOD, and DT values use the active DateTimeProfile.resolution. A duration is converted to whole profile ticks by truncating toward zero. Short date/time differences convert their signed tick difference back to a checked TIME duration.
  • Long LDATE, LTOD, and LDT values operate directly in signed nanoseconds and produce LTIME for same-family subtraction.
  • Adding a duration to TOD, LTOD, DT, or LDT does not wrap at a day boundary. It returns the checked signed result in the same value family.
  • MUL_TIME, MUL_LTIME, DIV_TIME, and DIV_LTIME are the named forms of the * and / duration operators (IEC 61131-3 Table 35) and compute exactly as the operators do. They accept signed or unsigned integer and REAL/LREAL factors. An integer factor scales the nanosecond count exactly, with integer division truncating toward zero; a real factor scales it in binary64 and the real result truncates toward zero. Zero division returns RuntimeError::DivisionByZero; a non-numeric factor returns RuntimeError::TypeMismatch; a non-finite factor or an out-of-range result returns RuntimeError::Overflow.
  • Ordering compares stored ticks or nanoseconds only when both operands have the same runtime date/time family. Cross-family ordering returns RuntimeError::TypeMismatch.

Runtime Clock Sources (TIME, CURRENT_DT)

TIME() is a zero-argument truST product function that returns the supplied logical elapsed duration exactly. Supplying any actual argument is a compile-time wrong-argument-count error and is also rejected at the runtime dispatch boundary.

CURRENT_DT() is a zero-argument truST product function. IEC 61131-3 Ed.3 §6.4.2, Table 10, footnote b permits an implementer-defined DT range and precision; IEC does not define this host-clock function.

The function has this complete product contract:

  • it samples std::time::SystemTime once per call and interprets that sample as a UTC Unix timestamp; on a device without an operating system it reads the device's clock, which counts from DT#1970-01-01-00:00:00 at power-up until a time source sets it (specification 34, TARGET-06);
  • it returns a timezone-naive DT whose epoch is DT#1970-01-01-00:00:00 and whose fixed resolution is one millisecond;
  • it truncates a positive sub-millisecond remainder toward the preceding millisecond;
  • it accepts Unix timestamps from tick 0 through i64::MAX milliseconds, inclusive;
  • a host value before the Unix epoch or above the representable millisecond tick range returns RuntimeError::Overflow and produces no DT value;
  • local timezone, daylight-saving, and leap-second metadata are neither read nor encoded;
  • the injected runtime/manual clock, scheduler scaling, simulation time, and replay time do not replace or offset the host sample; and
  • samples are not clamped to be monotonic. A host-clock rollback may therefore make a later call return an earlier DT.

Deterministic runtime replay does not claim identical results for a program that calls CURRENT_DT() unless the surrounding environment controls the host clock. Programs that require replay-controlled elapsed time use TIME() instead. CURRENT_DT rejects every argument during HIR validation and at the runtime dispatch boundary.

Declared Clock Sources (reads_host_clock)

A POU can declare that it samples the host wall clock with the pragma attribute reads_host_clock. The flag carries no value:

{attribute 'reads_host_clock'}
FUNCTION FuGetDateAndTime : DT
    FuGetDateAndTime := FuGetDateAndTime;
END_FUNCTION

A declared POU is itself a clock source, on the same terms as CURRENT_DT: deterministic replay does not control it, and a program that calls it needs a controlled host clock for identical results. The declaration makes no claim about how the samples are taken; it declares that the POU reads the host clock, which is what a vendor library documents and what a body-less stub cannot say in code.

The declaration is read from the pragma trivia that precedes the POU's declaration keyword. A pragma inside the POU's body does not declare anything, and a POU whose body also reads CURRENT_DT is one source, not two. A declaration-only stub that carries the flag warns nowhere in its own file: the declaration speaks for the body the stub replaces, and the warnings appear at the call sites, like those of a linked library body (specification 14 §7.6). Specification 28 §5 defines the warning and its message.

Date/Time Component Functions

Function Description
CONCAT_DATE_TOD Combine DATE and TOD into DT
CONCAT_DATE_LTOD Combine DATE and LTOD into LDT
CONCAT_DATE YEAR, MONTH, DAY → DATE
CONCAT_TOD HOUR, MINUTE, SECOND, MILLISECOND → TOD
CONCAT_LTOD HOUR, MINUTE, SECOND, MILLISECOND → LTOD
CONCAT_DT YEAR, MONTH, DAY, HOUR, MINUTE, SECOND, MILLISECOND → DT
CONCAT_LDT YEAR, MONTH, DAY, HOUR, MINUTE, SECOND, MILLISECOND → LDT
SPLIT_DATE DATE → YEAR, MONTH, DAY
SPLIT_TOD TOD → HOUR, MINUTE, SECOND, MILLISECOND
SPLIT_LTOD LTOD → HOUR, MINUTE, SECOND, MILLISECOND
SPLIT_DT DT → YEAR, MONTH, DAY, HOUR, MINUTE, SECOND, MILLISECOND
SPLIT_LDT LDT → YEAR, MONTH, DAY, HOUR, MINUTE, SECOND, MILLISECOND
DAY_OF_WEEK DATE → 0=Sunday..6=Saturday

The CONCAT_* functions accept only valid components, as a date or time literal does: month 1 to 12, a day that exists in that month, hour 0 to 23, minute 0 to 59, second 0 to 59 and millisecond 0 to 999. Any component outside its range, including an invalid calendar date, raises RuntimeError::Overflow; a component never carries into the next field.

Notes: - SPLIT_* output types are ANY_INT; the Implementer specifies concrete types. - Additional inputs/outputs (for example, microsecond/nanosecond) are Implementer specific.

// Examples
NewTime := ADD_TIME(T#1h, T#30m);           // T#1h30m
EndTime := ADD_TOD_TIME(TOD#08:00:00, T#2h); // TOD#10:00:00
Duration := SUB_DT_DT(EndDateTime, StartDateTime);
DoubleTime := MUL_TIME(BaseTime, 2);
HalfTime := DIV_TIME(BaseTime, 2);

10. Validate Functions (Table 39)

IS_VALID requires exactly one REAL or LREAL and returns whether it is finite. IS_VALID_BCD requires exactly one BYTE, WORD, DWORD, or LWORD and checks every nibble in the declared width; it returns FALSE for an invalid nibble rather than raising a BCD decoding error. The type checker rejects an unsupported argument type at compile time; a dynamic call that reaches the runtime with one raises RuntimeError::TypeMismatch. Validation functions do not modify their input.

Function Description Signature
IS_VALID Returns FALSE for invalid real values such as NaN or infinity IS_VALID(IN: REAL/LREAL) : BOOL
IS_VALID_BCD Returns FALSE if any BCD nibble is greater than 9 IS_VALID_BCD(IN: BYTE/WORD/DWORD/LWORD) : BOOL

IEC 61131-3 Ed.3 Figure 5 places BOOL under the broader ANY_BIT hierarchy, but the Table 39 validation-function narrative defines IS_VALID_BCD for BYTE, WORD, DWORD, and LWORD. truST follows that stricter Table 39 domain and rejects BOOL for IS_VALID_BCD.

VAR
  R : REAL;
  W : WORD := WORD#16#1234;
  Ok : BOOL;
END_VAR

Ok := IS_VALID(R);
Ok := IS_VALID_BCD(W);

11. Reference Functions

Function Description Signature
REF Get reference REF(IN: ANY) : REF_TO ANY
VAR
  MyInt: INT := 42;
  pInt: REF_TO INT;
END_VAR

pInt := REF(MyInt);

12. Array Bound Functions

Function Description Signature
LOWER_BOUND Lower array bound LOWER_BOUND(arr: ARRAY, dim: INT) : DINT
UPPER_BOUND Upper array bound UPPER_BOUND(arr: ARRAY, dim: INT) : DINT
VAR
  Data: ARRAY[5..15] OF INT;
  Lo, Hi: DINT;
END_VAR

Lo := LOWER_BOUND(Data, 1);  // Lo = 5
Hi := UPPER_BOUND(Data, 1);  // Hi = 15

13. Error Conditions

Runtime Errors

Function Error Condition
SQRT Negative input
LN, LOG Non-positive input
DIV, MOD Division by zero
ASIN, ACOS Input outside [-1, 1]
STRING_TO_* Invalid string format
Array bound Invalid dimension

Overflow

Numeric functions may overflow. Behavior is Implementer specific: - Saturation to max/min value - Wrap-around - Error flag/exception

Implementation Notes for trust-hir

Function Resolution

  1. Match function name (case-insensitive)
  2. Check argument count (consider extensible functions)
  3. Resolve overloaded variants by argument types
  4. Apply implicit conversions if needed
  5. Determine return type

Type Inference for Overloaded Functions

// ADD is overloaded for all numeric types
A: INT;
B: INT;
C := ADD(A, B);  // C is INT

X: REAL;
Y: REAL;
Z := ADD(X, Y);  // Z is REAL

Extensible Functions

These functions accept variable number of inputs: - ADD, MUL (arithmetic) - AND, OR, XOR (bitwise) - MAX, MIN (selection) - GT, GE, EQ, LE, LT (comparison) - CONCAT (string) - MUX (selection)

Standard Library

The trust-hir should include definitions for all standard functions with: - Name - Parameter types (considering overloading) - Return type - Extensibility flag - Built-in implementation or intrinsic marker

At runtime, standard-function registration and lookup normalize names to ASCII uppercase. Fixed parameter metadata preserves the documented formal order. Extensible metadata preserves fixed leading parameters, the uppercase variadic prefix, its first numeric suffix, and its minimum count. Conversion functions are parsed on demand after registered-function lookup; an unrecognized name raises RuntimeError::UndefinedFunction.

Runtime Shared-Helper Contract

The common runtime path used by numerical, bitwise, selection, comparison, assertion, and timer functions obeys these rules:

  • Exact and minimum arity failures return RuntimeError::InvalidArgumentCount with the required count and observed count.
  • Common-type resolution widens compatible numeric and bit-string values, preserves narrow versus wide string families, and requires an identical date/time or enumeration family. Incompatible signed/unsigned or unrelated elementary families return RuntimeError::TypeMismatch.
  • Common-value coercion is checked. It cannot silently wrap an out-of-range numeric value. CHAR may join STRING and WCHAR may join WSTRING as a one-element string.
  • Common comparison applies the requested relation after coercion. Floating comparison follows finite IEEE ordering; NaN is not equal to itself and satisfies NE.
  • Bit extraction and reconstruction preserve the declared widths 1, 8, 16, 32, and 64. Masks include exactly the requested low bits, with every width of 64 or greater producing the full u64 mask.
  • Duration scaling follows the * and / duration operators exactly (section 9): integer factors scale exactly and a real result truncates toward zero. It rejects division by zero and returns overflow rather than wrapping an unrepresentable result.

The runtime clock dispatcher receives normalized uppercase names. TIME returns the supplied logical elapsed duration exactly. CURRENT_DT follows the host-clock contract above. Any other name returns RuntimeError::UndefinedFunction carrying the original unknown name without normalization or substitution.

Non-IEC Extensions (MP-014)

The following functions are non-IEC additions for the user-facing ST test framework:

Function Signature Behavior
ASSERT_TRUE ASSERT_TRUE(IN: BOOL) : VOID Fails test if IN is not TRUE
ASSERT_FALSE ASSERT_FALSE(IN: BOOL) : VOID Fails test if IN is not FALSE
ASSERT_EQUAL ASSERT_EQUAL(EXPECTED: ANY_ELEMENTARY, ACTUAL: ANY_ELEMENTARY) : VOID Fails test when values are not equal
ASSERT_NOT_EQUAL ASSERT_NOT_EQUAL(EXPECTED: ANY_ELEMENTARY, ACTUAL: ANY_ELEMENTARY) : VOID Fails test when values are equal
ASSERT_GREATER ASSERT_GREATER(VALUE: ANY_ELEMENTARY, BOUND: ANY_ELEMENTARY) : VOID Fails test unless VALUE > BOUND
ASSERT_LESS ASSERT_LESS(VALUE: ANY_ELEMENTARY, BOUND: ANY_ELEMENTARY) : VOID Fails test unless VALUE < BOUND
ASSERT_GREATER_OR_EQUAL ASSERT_GREATER_OR_EQUAL(VALUE: ANY_ELEMENTARY, BOUND: ANY_ELEMENTARY) : VOID Fails test unless VALUE >= BOUND
ASSERT_LESS_OR_EQUAL ASSERT_LESS_OR_EQUAL(VALUE: ANY_ELEMENTARY, BOUND: ANY_ELEMENTARY) : VOID Fails test unless VALUE <= BOUND
ASSERT_NEAR ASSERT_NEAR(EXPECTED: ANY_NUM, ACTUAL: ANY_NUM, DELTA: ANY_NUM) : VOID Fails test when ABS(EXPECTED-ACTUAL) > DELTA

Compatibility notes: - These assertions are truST test-workflow extensions and are not part of IEC 61131-3 Tables 22-36 or Table 39. - They are intended for TEST_PROGRAM / TEST_FUNCTION_BLOCK execution paths. - On success, every ASSERT_* runtime call returns Value::Null, the runtime representation of the IEC-facing VOID result. - The reviewed mixed numeric comparisons are lossless INT/DINT equality and ordering, plus finite REAL/LREAL comparison for ASSERT_NEAR. Other mixed elementary-type pairs are not authorized by these representatives. - A condition failure returns RuntimeError::AssertionFailed; it does not return a normal value. - Failure messages use user-facing value text rather than internal Value debug forms. For the reviewed value families, integers use decimal text, integral REAL values retain .0, Boolean values use TRUE/FALSE, and CHAR values use single quotes. - The stable message forms for the reviewed relational assertions are: - ASSERT_EQUAL failed: expected {expected}, actual {actual} - ASSERT_NOT_EQUAL failed: values should differ, left {left}, right {right} - ASSERT_GREATER failed: value {value} is not greater than bound {bound} - ASSERT_LESS failed: value {value} is not less than bound {bound} - ASSERT_GREATER_OR_EQUAL failed: value {value} is not >= bound {bound} - ASSERT_LESS_OR_EQUAL failed: value {value} is not <= bound {bound} - An ASSERT_NEAR failure message identifies ASSERT_NEAR and includes its delta context. No exact full-string format is promised for that message.

Assertion calls enforce their documented arity and reject unsupported operand families with RuntimeError::TypeMismatch. ASSERT_TRUE and ASSERT_FALSE accept only BOOL. ASSERT_NEAR accepts finite numeric values, succeeds when ABS(EXPECTED - ACTUAL) <= DELTA, rejects a negative DELTA as RuntimeError::AssertionFailed, and rejects non-finite inputs as RuntimeError::Overflow. The comparison runs in binary64 and also succeeds when the difference exceeds DELTA by no more than four binary64 epsilons times the largest of |EXPECTED|, |ACTUAL|, DELTA and 1.0, which absorbs the rounding of decimal operands such as ASSERT_NEAR(1.0, 1.1, 0.1). A successful assertion always returns Value::Null; failed assertions never return a normal value.