A script can fail in two ways: it may not compile, or it may fail while it runs. And a value can be missing in two
ways: it may be null, or the variable may never have been written. This chapter says what each place does with a
failure, how to guard a value with evl, nvl, ivl and inn, and which names to keep clear of. The examples use
the sample variables.
Compile errors
A script is compiled before it runs. It does not compile when its text breaks the syntax, when
a literal is refused (a whole number out of range, an unknown escape), when it calls a function that does not exist,
or when it gives a function the wrong number of arguments. A script that does not compile never runs at all. The
message starts with Syntax error at and marks where compiling stopped; see
Syntax.
Variable names are not checked when compiling. A name that is not a variable is found only when the script reads it.
- In the formula editor, the status line shows the message as you type.
- In a variable's formula, the engine logs an error with the variable's code and the message when the configuration loads, and that variable is not calculated.
Run-time errors
A script that compiles can still fail while it runs:
| Cause | Example |
|---|---|
| Reading a name that is not a variable, or one never written | NOPE |
| Reading a field the record does not hold | SETPOINTS.mid |
| Reading past the end of a list | REGISTERS[4] |
| A condition that is not a boolean | if(COUNT, 1, 2) |
| A value of the wrong kind | 1 + "a", "1" == 1, year(TAG) |
null where a value is needed |
LEVEL + null |
| Text that does not convert | to_double("1,5") |
| A date that does not exist | date(2026, 2, 30) |
| A gradient the engine does not provide | gradient(nope, 0.5) |
An error goes up through every operator and function around it, and the whole script fails, unless
evl catches it on the way. &&, ||, if and case evaluate only what
they need, so an error in a part they skip never happens.
Some results look wrong but are not errors: a division by zero gives Infinity or NaN; +, -, * or / with
null, a boolean or a date on its left gives null; and a comparison with null gives true or false.
NOPE → error
SETPOINTS.mid → error
REGISTERS[4] → error
if(COUNT, 1, 2) → error
year(TAG) → error
LEVEL + null → error
to_double("1,5") → error
date(2026, 2, 30) → error
gradient(nope, 0.5) → error
round(NOPE) + 1 → error
if(PUMP_ON, LEVEL, NOPE) → 42.5
1 / 0 → Infinity
null + 1 → null
COUNT == null → false
Where errors go
| Place | When the script fails | When it does not compile |
|---|---|---|
| Variable formula | A warning in the log, with the variable's code and the message. The value stays as it was | An error in the log when the configuration loads. The variable is not calculated |
| Initial value | An error in the log. The variable starts without a value | The same |
| Merger filter | A warning in the Merger's messages. Nothing is written | The Merger does not run: Filter does not compile |
| Mailer enabler | The Mailer reports it in its status and messages | The same |
| WebService Out | No request is sent, and the module reports it in its status and messages | The module does not open: Configuration invalid |
| Dashboard widget property | Nothing is reported. The property falls back: a blank label, a visible widget, zero offsets | Nothing is reported, and the widget is not updated |
| Action button script | An error dialog | The same, on the click |
Peer script.execute |
The peer receives an error | The same |
A dashboard shows no error, so a widget that stays blank or does not move is often a failing formula. Open its formula in the editor to see whether it compiles, and try it in a variable if it does.
The module pages list the exact messages: Logic modules, Mailer and WebService Out.
Guarding a value
Four functions give a default in place of a problem. Each takes the value to guard and the default:
| Function | Gives the default when the value | Does not help with |
|---|---|---|
| evl | Fails to evaluate, for any reason: a never-written variable, a missing field, a bad conversion | null, Infinity and NaN: they are values, not errors |
| nvl | Is null |
Errors, such as reading a never-written variable |
| ivl | Is Infinity, -Infinity or NaN. Any other number comes back as a decimal |
Text or null: both are errors |
| inn | Is not a number (text, null, a boolean) or is NaN |
Infinity |
The default is evaluated only when it is needed, and an error in the default itself is not caught.
| To guard | Write |
|---|---|
| A reading that may not have arrived yet | evl(PT_101, 0.0) |
| A field that may be missing | evl(STATE.o, 0.0) |
A value that may be null |
nvl(MODE, "auto") |
| A division whose divisor can reach zero | ivl(FLOW / AREA, 0.0) |
| A square root or logarithm of a reading that can go negative | inn(sqrt(DP), 0.0) |
| A condition over a reading that may not exist yet | evl(PT_101 > 4.5, false) |
evl hides every error, a misspelt variable name included. Keep the expression it guards small, so that it catches
only the failure you expect.
evl(NOPE, 0) → 0
nvl(NOPE, 0) → error
nvl(null, 0) → 0
evl(null, 0) → null
nvl(COUNT, 0) → 3
evl(SETPOINTS.mid, 50) → 50
evl(REGISTERS[4], 0) → 0
evl(to_double("1,5"), 0.0) → 0.0
evl(NOPE > 10, false) → false
evl(LEVEL / 0, 0.0) → Infinity
ivl(LEVEL / 0, 0.0) → 0.0
ivl(0 / 0, 0.0) → 0.0
ivl(COUNT, 0) → 3.0
ivl(null, 0) → error
inn(sqrt(-1), 0.0) → 0.0
inn(TAG, 0.0) → 0.0
inn(null, 0.0) → 0.0
inn(COUNT, 0) → 3
inn(1 / 0, 0.0) → Infinity
evl(NOPE, NOPE_TOO) → error
Never-written variables
In the engine, a variable exists from the moment it is first written: by its initial value, its formula, a module or
a dashboard's input widget. Until then it is not null: it does not exist, and reading it fails with
Variable 'CODE' not found, exactly as a name that is no variable at all.
- After every start, a variable that is neither persistent nor given an initial value does not exist until something writes it.
nulldoes not create it. A formula or a module that writesnullto a variable that does not exist yet leaves it not existing.nvlcannot guard it: the read fails beforenvlsees a value. Useevl.- A formula that reads it fails each time it runs, with a warning in the log, and a Merger filter that reads it fails the same way. Give the variable an initial value, or guard the read.
- A formula that reads its own code reads its previous value, which does not exist on the first calculation.
Formula of RUN_MINUTES, recalculated every minute by a Scheduler:
evl(RUN_MINUTES, 0) + if(PUMP_ON, 1, 0)
Loop and binding names
for, with, forEach, arrayFilter and tsFilter bind a name, such as the counter of a loop. They do so by writing it into the table that holds the plant's variables, shared by every formula and dashboard of the instance, and they put the previous value back when they finish.
- While the call runs, the name hides a variable of the same code. Inside
forEach(LIST, COUNT, …),COUNTis the current item, not the variable. - A name that was not a variable is left behind as a variable holding
null. Afterwards, reading it givesnullinstead of an error. - The name is also a trigger of the formula that binds it, so the log reports it when the configuration loads, as it does any name that is not a variable.
Choose binding names that no variable of the plant uses.
evl(i, "missing") → "missing"
(for(i, 0, i < 3, i * 2, i + 1), evl(i, "missing")) → null
forEach([1, 2, 3], COUNT, COUNT * 10) → [10.0, 20.0, 30.0]
(forEach([1, 2], COUNT, COUNT), COUNT) → 3
Names to avoid
Every variable shares one table with names the engine and the dashboards use. Do not give a variable any of these codes:
| Name | Why |
|---|---|
ORCHESTER |
The engine's own reference, through which the history functions reach the loggers. A variable with this code replaces it, and they fail |
E, PI |
The preset constants. A variable with either code hides it |
DEBUG |
The text "true" makes every write of a variable print to the server's standard output. Any value that is not text makes every write of every variable fail |
_VALUE_ |
The value a Merger filter is given. A filter cannot read a variable with this code |
_VIEW_, _CALLER_ |
The view and the button an Action button's script is given |
The names that loops and bindings use belong on this list too: see Loop and binding names.
Next steps
This page describes Amtiri Script 5.4.1 as shipped with Data Orchester engine 6.12.1.