Expressions

The one expression language behind variables and alarm conditions, what it may reference, the read surface of every kind of point, the rules the evaluator enforces, the messages validation gives, and how the script editor and its Browse… picker work.

View as Markdown

One expression language serves two places on the Logic page: what an expression variable computes and the condition an alarm watches. Both are one JavaScript expression evaluated in strict mode inside a sandbox with no access to the machine, and both are edited in the same script editor. Action scripts use the same vocabulary but are statement blocks; their differences are on Actions.

What an expression is

An expression is exactly one JavaScript expression: literals, arithmetic, comparisons, logical operators, the conditional a ? b : c, string operations, template strings, and calls to the JavaScript built-ins Math, Date and JSON. Statements (let, if, loops) belong to actions: a second statement is refused with "A variable holds a single expression. Statements belong to Actions." Assignments and ++/-- anywhere in an expression are refused with "Expressions are read-only. Writes belong to Actions."

The expression references points by their rooted address. What each root hands to the expression:

Root What crosses into the expression
Connector tags, Variables, Alarms, the Channels of a Process unit A live object with the typed read surface below. A bare reference (Connector.Simulated.Pump.Flow alone) evaluates to the point's value; a bare alarm to its IsActive.
System, Server, Assets, and the run state, productivity, commands and staging of a Process unit The current value itself: a number, a text, a Boolean or an instant. Use it directly; there is no member to append.

System.Now is the evaluation instant: every reference to it within one evaluation reads the same time. It crosses as a JavaScript Date, like every timestamp, so instants compare and subtract naturally: (System.Now - Connector.Simulated.Pump.Run.SourceTimeStamp()) / 1000 is seconds.

An expression variable may reference tags, memory variables, alarms, Process points, Assets points and the System and Server diagnostics, but never another expression variable: the editor refuses it ("'Variables.X' is an expression variable. Expressions cannot reference other expression variables; reference memory variables or the underlying points instead.") so results never depend on evaluation order. An alarm condition may reference everything but Alarms.* ("Alarm conditions cannot reference other alarms.") and must produce a Boolean.

The read surface of a point

A live tag offers:

Member Returns Meaning
.Value typed value The engineering value, after the tag's whole conversion pipeline
.ValueRaw typed value The raw value normalized to the tag's declared type, before scaling
.ValueObject anything The value exactly as the driver produced it
.ValueScaled, .ValueCalibrated, .ValueLimited, .ValueRounded number The value after the Scale, Calibration, Limits and Decimals stages
.ValueToBool(), .ValueToInteger(), .ValueToReal(), .ValueToString(), .ValueToDateTime() converted value The engineering value converted to that category; no value stays no value
.State() Boolean Quality: true while the reading is good
.SourceTimeStamp() Date When the device produced the reading
.ApplicationTimeStamp() Date When the embedded server received it
.Name, .EngineeringUnit text The tag's name and unit

A variable offers .Value, the five .ValueTo*() conversions, .State(), .ApplicationTimeStamp(), .Name and .EngineeringUnit. An alarm offers .IsActive, .IsAcked, .CurrentState, .State() (whether its condition could be evaluated on the last sweep), .ActiveSince(), .LastTransitionAt() and .Name. A Process channel offers the tag surface when it is bound to a tag and the variable surface when bound to a variable; an unbound channel offers nothing. Nothing else on a live object is reachable: a member outside this surface reads as undefined, and calling one fails at validation. The Browse… picker lists these same members under each point, so none of them has to be typed from memory.

The rules the evaluator enforces

Both in the editor as you type and again on every 100 ms sweep:

  • The quality gate. Before evaluating, every referenced point is resolved and refreshed. Any point missing, Bad, or holding no value makes the result a Bad-quality gap without running the expression, so an offline tag can never fabricate a sample and null never reaches the engine to become 0.
  • Non-finite results are gaps. NaN or infinity (division by zero, undefined arithmetic) never becomes a sample.
  • Runaway protection. An evaluation is aborted past 250 ms, 5,000 statements or 4 MB of script memory, and counts as a Bad-quality gap. One engine is kept per expression text, so nothing leaks from another expression.
  • Coercion. A successful result is coerced to the variable's output type; an alarm condition is coerced to Boolean, and a result that cannot convert holds the alarm's state.
  • Errors are gaps, not exceptions. A runtime error at sweep time yields a gap and a journal entry (the first failure, summaries while it repeats, one line when it recovers); the sweep goes on with the next variable.

What validation refuses

The editor validates the draft every half second and shows the first problem under the value or the condition:

Message Cause
An expression is required. / A condition is required. The editor is empty.
Expression error: … The text does not parse.
A variable holds a single expression. Statements belong to Actions. More than one statement, or a statement that is not an expression.
Expressions are read-only. Writes belong to Actions. An assignment or ++/--.
Addresses must be static: 'Root' is accessed with a computed segment. Variables[name] or any non-literal segment.
Incomplete address 'Connector.Simulated'. A chain too short to name a point under its root.
'X' was not found. The address resolves to nothing.
'X' is an expression variable. … A reference to another expression variable.
Alarm conditions cannot reference other alarms. An Alarms.* reference in a condition.
The condition must evaluate to a Bool, but it produced 'x'. The trial evaluation of a condition produced a present value that does not convert to Boolean.
Expression error: a live point cannot be used directly here. Read it through its .Value member (e.g. Connector.Driver.Device.Tag.Value). A live object used where a primitive is needed (Tag + 1).
Expression error: <engine message> The trial evaluation threw: an unknown identifier, a blocked member called as a function.

The trial evaluation runs with the current values regardless of quality, so a message reflects the expression, not a Bad input; a Bad input shows in the REFERENCED POINTS card instead.

The script editor

Every expression, condition and script on the page is edited in the same card: an eyebrow (EXPRESSION, CONDITION or SCRIPT), a Browse… button, the editor, a hint and the validation message. The editor is CodeMirror with JavaScript highlighting, an undo history, and autocomplete for address tokens: root names for a bare token, then the children of the path typed so far, members included once the token names a point (entering an address). The text is pushed to the draft debounced and autosaved. The expression and condition boxes are 56 px high at least, the action script box 160 px. When the editor engine cannot start, the box degrades to a plain text area that still reads and writes, without highlighting, completion or history; Browse… then appends the picked address at the end instead of the caret.

Browse… opens a dialog ("Browse: insert into EXPRESSION") with the member-level tree of the whole address space; select a row and press Insert, or double-click it, to put its address at the caret. Each editor owns its own dialog, so two editors on one page never share a selection.

What expressions do not do

They do not write anything, do not call Process run commands, keep no state between evaluations, cannot define functions shared across expressions, and are not evaluated on demand: the sweep publishes them every 100 ms and the editor previews the draft twice a second.