Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
51850d0
feat(validation): check arithmetic on date and time operands
ghaith Sep 25, 2026
bc951c1
feat(resolver): carry out date and time operators in the stdlib
ghaith Sep 28, 2026
e4d0a67
fix(stdlib): give the TIME monomorphs of MUL_TIME and DIV_TIME the TI…
ghaith Sep 28, 2026
951c0c9
fix(stdlib): wrap LTIME_OF_DAY arithmetic at midnight
ghaith Sep 28, 2026
8154276
test(stdlib): assert that date and time arithmetic does not allocate
ghaith Sep 25, 2026
813a0ec
test(lit): cover the operators on date and time operands
ghaith Sep 28, 2026
7a964e0
docs: describe arithmetic on date and time operands
ghaith Sep 28, 2026
8115a7e
feat(builtins): expand ADD, SUB, MUL, DIV for date and time arguments
ghaith Sep 28, 2026
a600388
refactor(stdlib)!: drop the ADD, SUB, MUL, DIV monomorph shims
ghaith Sep 28, 2026
03a664e
test(lit): cover the builtin forms of date and time arithmetic
ghaith Sep 28, 2026
9148d08
docs: describe the builtins on date and time arguments
ghaith Sep 28, 2026
e317eb5
fix(codegen): cast replaced expressions to the expected type
volsa Sep 30, 2026
ee2c909
Merge branch 'master' into feat/prg-4854
volsa Sep 30, 2026
3a9ca89
docs: align the builtin and date and time pages with the behavior
volsa Sep 30, 2026
163b6d5
fix(codegen): evaluate a by-reference argument once
volsa Oct 1, 2026
0d7a218
refactor(builtins): drop redundant work in arithmetic annotation
volsa Oct 1, 2026
29bec6a
fix(validation): report a zero divisor of the DIV builtin
volsa Oct 1, 2026
a7e32a3
fix(validation): underline builtin arithmetic arguments in source order
volsa Oct 1, 2026
efacd33
Merge branch 'master' into feat/prg-4854
volsa Oct 1, 2026
1273553
refactor(builtins): pass the DIV parameter names to the divisor check
volsa Oct 1, 2026
587631f
refactor(builtins): let the compiler infer the span closure types
volsa Oct 1, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions book/technical/internals/08-annotated-ast.md
Original file line number Diff line number Diff line change
Expand Up @@ -349,7 +349,7 @@ ReplacementAst {
}
```

`ReplacementAst` attaches a replacement expression without changing the original node. String equality becomes a `STRING_EQUAL` call. Other string comparisons combine `_EQUAL`, `_LESS`, and `_GREATER` calls with `NOT` and `OR`. A call to an arithmetic or comparison built-in becomes a binary expression over its arguments: `ADD(a, b, c)` becomes `(a + b) + c`, and `GT(a, b, c)` becomes `(a > b) AND (b > c)`. An arithmetic operator on date and time operands becomes the call of the standard library function that carries it out, `ADD_DT_TIME(stamp, cycle)` for `stamp + cycle`. The replacement has its own annotations.
`ReplacementAst` attaches a replacement expression without changing the original node. String equality becomes a `STRING_EQUAL` call. Other string comparisons combine `_EQUAL`, `_LESS`, and `_GREATER` calls with `NOT` and `OR`. A call to an arithmetic or comparison built-in becomes a binary expression over its arguments: `ADD(a, b, c)` becomes `(a + b) + c`, and `GT(a, b, c)` becomes `(a > b) AND (b > c)`. An arithmetic operator on a combination of date and time operands that the standard defines becomes the call of the standard library function that carries it out, `ADD_DT_TIME(stamp, cycle)` for `stamp + cycle`, when that function is declared. The replacement has its own annotations.

```
same := text = 'hello';
Expand All @@ -358,7 +358,7 @@ ReplacementAst {
^^^^^^^ Value "__STRING_5", hint Argument "STRING", position 1, pou STRING_EQUAL
```

Codegen checks every expression for this kind first and generates the replacement instead of the node. The type of the node is the type of its replacement; see [Deriving a type](#deriving-a-type).
Codegen checks every expression for this kind first and generates the replacement instead of the node, also inside parentheses. The type of the node is the type of its replacement; see [Deriving a type](#deriving-a-type). The value of the replacement is then converted to the hint of the node like any other value, so `x := cycle * 1.5` with an `LREAL` target converts the `TIME` result of `MUL_TIME__REAL` to `LREAL`.

### Label

Expand Down Expand Up @@ -420,7 +420,7 @@ Codegen compares the annotated type with the expected type from the hint. The an
| `Program` | references to programs, classes, actions | qualified name | codegen (instance address), validator (E095) |
| `Argument` (hint only) | every call argument | type, position, depth, declaring POU | codegen (parameter slot), aggregate-return lowerer, conversion like any hint |
| `Property` | property references before lowering | accessor name | property lowerer |
| `ReplacementAst` | string and other library-compared comparisons, arithmetic on date and time operands | the replacement statement | codegen |
| `ReplacementAst` | string and other library-compared comparisons, calls of the arithmetic and comparison built-ins, arithmetic on date and time operands | the replacement statement | codegen |
| `Label` | jump statements from CFC | label name | codegen |
| `MethodDeclarations` | block, class, and interface declarations (by declaration ID) | method name to declarations | validator (E111, E112) |
| `Override` | method declarations that override (by declaration ID) | overridden methods | validator (E112, E118) |
2 changes: 1 addition & 1 deletion book/technical/participants/08-generic.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ Nested calls need more than one pass. In `times_two(times_two(a))` the inner cal

The third pass changes nothing and ends the loop. A call whose type parameter gets no offer at all, because no argument is bound to a parameter of that type, is skipped in every pass and stays a generic call.

Two kinds of call are never touched. Built-in generics such as `MUX`, `SEL`, `ADD`, or `SHL` are resolved by the resolver and generated inline by codegen, so there is no implementation to pick. A call inside the body of a generic template works on `T` itself, and a template is never generated; such a call stays as written, and the validator reports it as an unresolved generic type (E064).
Two kinds of call are never touched. Built-in generics such as `MUX`, `SEL`, `ADD`, or `SHL` are resolved by the resolver and generated inline by codegen, so there is no implementation to pick. With a date or time argument, `ADD`, `SUB`, `MUL`, and `DIV` call standard library functions such as `ADD_DT_TIME` instead, which are not generic either. A call inside the body of a generic template works on `T` itself, and a template is never generated; such a call stays as written, and the validator reports it as an unresolved generic type (E064).


## Interactions
Expand Down
2 changes: 1 addition & 1 deletion book/technical/pipeline/04-validation.md
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ POU checks depend on the kind. A program cannot return a value, and a class cann

Variable checks cover declared types, initializer compatibility, constant array bounds, and names that shadow base members. They also check restrictions on constant function block instances. The statement visitor checks initializers because they are expressions.

An implementation is walked statement by statement. The visitor is recursive and mirrors the tree: it visits the children of a node first, then applies the checks for the node itself. A reference is checked for resolution, visibility, and pointer access. An assignment compares the type of the value with the hint the resolver attached to it. A call is matched against the parameters of the callee from the index: argument count, direction of `:=` and `=>`, by-reference arguments, and required `VAR_IN_OUT` arguments. A binary expression with a date or time operand is checked against the table of combinations the standard defines: an undefined one is E156, a defined one whose standard library function is not declared is E073, and a duration combined with a bare integer warns with E157.
An implementation is walked statement by statement. The visitor is recursive and mirrors the tree: it visits the children of a node first, then applies the checks for the node itself. A reference is checked for resolution, visibility, and pointer access. An assignment compares the type of the value with the hint the resolver attached to it. A call is matched against the parameters of the callee from the index: argument count, direction of `:=` and `=>`, by-reference arguments, and required `VAR_IN_OUT` arguments. A binary expression with a date or time operand is checked against the table of combinations the standard defines: an undefined one is E156, a defined one whose standard library function is not declared is E073, and a duration combined with a bare integer warns with E157; the builtins `ADD`, `SUB`, `MUL`, and `DIV` apply the same check to their arguments, folded from the left, and report E156 as well for an argument that is neither a number nor a date or time value.

That match takes the parameter at the place of the argument in the call. The [resolver](03-resolver.md#calls) and codegen take the first parameter that no name claims, which is a different parameter as soon as a call mixes named and positional arguments. The by-reference checks therefore read a mixed call of a POU with a by-reference parameter against the wrong parameter: they report an argument that the resolver binds to an input, and they pass a value that the resolver binds to a `VAR_IN_OUT`. Control statements check their conditions and walk their bodies. For

Expand Down
2 changes: 1 addition & 1 deletion book/user/language/generics.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ END_FUNCTION

When nothing in the project defines the name, the compiler writes an `{external}` declaration for it and leaves the symbol to the linker. If the linker finds nothing either, the build fails with an undefined symbol.

The generic functions that the compiler knows itself work differently. A call to `ABS`, `ADD`, or `SEL` gets no version of its own, because the compiler writes the code at the place of the call. [Built-in Functions](../reference/built-in-functions.md) lists them all.
The generic functions that the compiler knows itself work differently. A call to `ABS`, `ADD`, or `SEL` gets no version of its own, because the compiler writes the code at the place of the call. With date and time arguments, `ADD`, `SUB`, `MUL`, and `DIV` expand to the operators, which call the standard library as the [time and date](time.md) chapter describes. [Built-in Functions](../reference/built-in-functions.md) lists them all.


## Constraints
Expand Down
3 changes: 3 additions & 0 deletions book/user/language/time.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,9 +73,12 @@ A duration shifts a moment of a day or a point in time, and two of those subtrac
| `DATE` | `-` | `DATE` | `TIME` |
| `DATE_AND_TIME` | `-` | `DATE_AND_TIME` | `TIME` |
| `TIME` | `*`, `/` | a number | `TIME` |
| a number | `*` | `TIME` | `TIME` |

The long family has the same table with `LTIME`, `LTIME_OF_DAY`, `LDATE`, and `LDATE_AND_TIME`. A moment of a day wraps around midnight, so `TOD#23:59:50 + T#20s` is `TOD#00:00:10`. Any other combination, such as two points in time added together or a short type mixed with a long one, is rejected (E156); convert first, for example with `TIME_TO_LTIME`.

The functions `ADD`, `SUB`, `MUL`, and `DIV` take the same combinations, and `ADD` and `MUL` take any number of arguments, folded from the left: `ADD(stamp, T#1s, T#2s)` is `stamp + T#1s + T#2s`.

The compiler carries these operations out with the standard library, `ADD_DT_TIME` for `DATE_AND_TIME + TIME`, `MUL_TIME__LINT` for `TIME * n`, and so on, so a project that calculates with time values must link `iec61131std` (see [Linking and Libraries](../building/linking.md)). Each of these functions can also be called directly.

A duration plus or minus a bare integer compiles too, and reads the integer as what the type counts: `cycle + 5` adds five milliseconds to a `TIME` and five nanoseconds to an `LTIME`. The compiler warns about it (E157), because the unit is only implied; `cycle + T#5ms` says the same without the warning.
Expand Down
12 changes: 7 additions & 5 deletions book/user/reference/built-in-functions.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Built-in Functions

The compiler knows these functions itself. You declare nothing, you include no file, and you link no library: the compiler writes the code at the place of the call. This is what separates them from the [standard library](standard-library.md), which is a set of ordinary declarations and an object file that the linker needs.
The compiler knows these functions itself. You declare nothing, you include no file, and you link no library: the compiler writes the code at the place of the call. This is what separates them from the [standard library](standard-library.md), which is a set of ordinary declarations and an object file that the linker needs. The one exception is arithmetic on date and time values, see [Arithmetic](#arithmetic).

Their names are reserved. A function of your own that takes one of them is rejected; see [Source Files](../language/source-files.md).

Expand Down Expand Up @@ -39,10 +39,12 @@ The parameter names in the second column are the names a call can use.
| Call | Parameters | Result | Gives |
|---|---|---|---|
| `ABS(IN)` | `IN: ANY_NUM` | The type of `IN` | The value without its sign |
| `ADD(...)` | Two or more `ANY_NUM` | The biggest argument type | The sum |
| `MUL(...)` | Two or more `ANY_NUM` | The biggest argument type | The product |
| `SUB(IN1, IN2)` | `IN1` and `IN2: ANY` | The biggest argument type | `IN1 - IN2` |
| `DIV(IN1, IN2)` | `IN1` and `IN2: ANY` | The biggest argument type | `IN1 / IN2` |
| `ADD(...)` | Two or more numbers or date and time values | The biggest argument type, or the date and time result | The sum |
| `MUL(...)` | Two or more numbers or durations | The biggest argument type, or the date and time result | The product |
| `SUB(IN1, IN2)` | `IN1` and `IN2: ANY` | The biggest argument type, or the date and time result | `IN1 - IN2` |
| `DIV(IN1, IN2)` | `IN1` and `IN2: ANY` | The biggest argument type, or the date and time result | `IN1 / IN2` |

With a date or time argument, these four take the combinations of [Time and Date](../language/time.md#calculating), folded from the left, and the result has the type that table gives: `ADD(stamp, T#1s, T#2s)` is `stamp + T#1s + T#2s`, a `DATE_AND_TIME`, and `SUB(day1, day2)` is a `TIME`. The compiler carries these operations out with the standard library, such as `ADD_DT_TIME`, so such a call needs `iec61131std` linked. An argument that is neither a number nor part of a defined combination is rejected (E156).

### Comparison

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -257,7 +257,7 @@ lazy_static! {
E153, Error, include_str!("./error_codes/E153.md"), // CFC ENO cycle
E154, Error, include_str!("./error_codes/E154.md"), // Negated CFC reference assignment
E155, Error, include_str!("./error_codes/E155.md"), // Duplicate CFC return pin
E156, Error, include_str!("./error_codes/E156.md"), // Operator not defined for these date or time operands
E156, Error, include_str!("./error_codes/E156.md"), // Operator not defined for these operand types
E157, Warning, include_str!("./error_codes/E157.md"), // Bare integer combined with a duration
);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# E123: Division by zero error

This error occurs when a literal or constant on the right side of a division operation is zero.
This error occurs when a literal or constant on the right side of a division operation is zero. The divisor `IN2` of the `DIV` function, as in `DIV(x, 0)`, is checked the same way.

## Examples

Expand Down
6 changes: 3 additions & 3 deletions compiler/plc_diagnostics/src/diagnostics/error_codes/E156.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# E156 - Operator not defined for these date or time operands
# E156 - Operator not defined for these operand types

Arithmetic on date and time types is only defined for the combinations of IEC 61131-3:
The functions `ADD`, `SUB`, `MUL`, and `DIV` report this error for an argument no arithmetic is defined for, such as a string or a bit string in `ADD(1, 'x')`. The operators and the same functions report it for a combination of date and time operands the standard does not define. Arithmetic on date and time types is only defined for the combinations of IEC 61131-3:

| Left | Operator | Right | Result |
|---|---|---|---|
Expand All @@ -14,7 +14,7 @@ Arithmetic on date and time types is only defined for the combinations of IEC 61
| any number | `*` | `TIME` | `TIME` |

The long types `LTIME`, `LTIME_OF_DAY`, `LDATE`, and `LDATE_AND_TIME` follow the same table
among themselves. The two families do not mix. A duration combined with a bare integer is
among themselves. The two families do not mix. A duration plus or minus a bare integer is
accepted with warning E157.

Erroneous code example:
Expand Down
4 changes: 2 additions & 2 deletions libs/stdlib/iec61131-st/arithmetic_functions.st
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ END_VAR
(********************
*
* This operator returns the value of adding up the operands.
* It overloads the variadic builtin implementation of ADD, which is implemented for ANY_NUM
* It overloads the variadic builtin ADD with a named first parameter IN1
*
*********************)
FUNCTION ADD < T1: ANY, T2: ANY >: T1
Expand All @@ -32,7 +32,7 @@ END_FUNCTION
(********************
*
* This operator produces the multiplication of the operands.
* It overloads the variadic builtin implementation of MUL, which is implemented for ANY_NUM
* It overloads the variadic builtin MUL with a named first parameter IN1
*
*********************)
FUNCTION MUL < T1: ANY, T2: ANY >: T1
Expand Down
81 changes: 0 additions & 81 deletions libs/stdlib/iec61131-st/date_time_numeric_functions.st
Original file line number Diff line number Diff line change
@@ -1,12 +1,3 @@
(* Specialized implementation of ADD for TIME *)
FUNCTION ADD__TIME__TIME: TIME
VAR_INPUT
IN1: TIME;
IN2: TIME;
END_VAR
ADD__TIME__TIME := ADD_TIME(IN1, IN2);
END_FUNCTION

(********************
*
* This operator returns the value of adding up two TIME operands.
Expand All @@ -33,15 +24,6 @@ VAR_INPUT
END_VAR
END_FUNCTION

(* Specialized implementation of ADD for TOD *)
FUNCTION ADD__TIME_OF_DAY__TIME: TOD
VAR_INPUT
IN1: TOD;
IN2: TIME;
END_VAR
ADD__TIME_OF_DAY__TIME := ADD_TOD_TIME(IN1, IN2);
END_FUNCTION

(********************
*
* This operator returns the value of adding up TOD and TIME.
Expand Down Expand Up @@ -70,15 +52,6 @@ VAR_INPUT
END_VAR
END_FUNCTION

(* Specialized implementation of ADD for DT *)
FUNCTION ADD__DATE_AND_TIME__TIME: DT
VAR_INPUT
IN1: DT;
IN2: TIME;
END_VAR
ADD__DATE_AND_TIME__TIME := ADD_DT_TIME(IN1, IN2);
END_FUNCTION

(********************
*
* This operator returns the value of adding up DT and TIME.
Expand Down Expand Up @@ -107,15 +80,6 @@ VAR_INPUT
END_VAR
END_FUNCTION

(* Specialized implementation of SUB for TIME *)
FUNCTION SUB__TIME__TIME: TIME
VAR_INPUT
IN1: TIME;
IN2: TIME;
END_VAR
SUB__TIME__TIME := SUB_TIME(IN1, IN2);
END_FUNCTION

(********************
*
* This operator produces the subtraction of two TIME operands
Expand All @@ -142,15 +106,6 @@ VAR_INPUT
END_VAR
END_FUNCTION

(* Specialized implementation of SUB for DATE *)
FUNCTION SUB__DATE__DATE: TIME
VAR_INPUT
IN1: DATE;
IN2: DATE;
END_VAR
SUB__DATE__DATE := SUB_DATE_DATE(IN1, IN2);
END_FUNCTION

(********************
*
* This operator produces the subtraction of two DATE operands returning TIME
Expand Down Expand Up @@ -179,15 +134,6 @@ VAR_INPUT
END_VAR
END_FUNCTION

(* Specialized implementation of SUB for TOD and TIME *)
FUNCTION SUB__TIME_OF_DAY__TIME: TOD
VAR_INPUT
IN1: TOD;
IN2: TIME;
END_VAR
SUB__TIME_OF_DAY__TIME := SUB_TOD_TIME(IN1, IN2);
END_FUNCTION

(********************
*
* This operator produces the subtraction of TOD and TIME returning TOD
Expand Down Expand Up @@ -216,15 +162,6 @@ VAR_INPUT
END_VAR
END_FUNCTION

(* Specialized implementation of SUB for TOD *)
FUNCTION SUB__TIME_OF_DAY__TIME_OF_DAY: TIME
VAR_INPUT
IN1: TOD;
IN2: TOD;
END_VAR
SUB__TIME_OF_DAY__TIME_OF_DAY := SUB_TOD_TOD(IN1, IN2);
END_FUNCTION

(********************
*
* This operator produces the subtraction of two TOD operands returning TIME
Expand Down Expand Up @@ -253,15 +190,6 @@ VAR_INPUT
END_VAR
END_FUNCTION

(* Specialized implementation of SUB for DT and TIME *)
FUNCTION SUB__DATE_AND_TIME__TIME: DT
VAR_INPUT
IN1: DT;
IN2: TIME;
END_VAR
SUB__DATE_AND_TIME__TIME := SUB_DT_TIME(IN1, IN2);
END_FUNCTION

(********************
*
* This operator produces the subtraction of DT and TIME returning DT
Expand Down Expand Up @@ -290,15 +218,6 @@ VAR_INPUT
END_VAR
END_FUNCTION

(* Specialized implementation of SUB for DT *)
FUNCTION SUB__DATE_AND_TIME__DATE_AND_TIME: TIME
VAR_INPUT
IN1: DT;
IN2: DT;
END_VAR
SUB__DATE_AND_TIME__DATE_AND_TIME := SUB_DT_DT(IN1, IN2);
END_FUNCTION

(********************
*
* This operator produces the subtraction of two DT operands returning TIME
Expand Down
Loading
Loading