FS FormScript
Package@formscript/evaluator
Specification6, 6.2, 6.4, 6.5, 6.6
Depends onast, decimal, errors, types

@formscript/evaluator

Expression evaluation — the FormScript Language Specification 1.0.0 , §6.

Evaluation is total: every expression returns a value. There is no exception, no undefined, and no partial result. An operation that cannot succeed returns the error value or null, as Spec-6.1 and Spec-6.2 prescribe.

The three-valued logic

FormScript has three truth values, not two, and two kinds of absence:

Value Meaning
true / false the ordinary truth values
unknown (N) the value is not known yet (a field with no content)
inapplicable (I) the question was not asked (the entity is not active)
error (E) an operation failed (division by zero, an out-of-range index)

and, or and ! are Kleene strong three-valued logic extended so that the two absences are the "undetermined" value and the error value is absorbed by the dominant value (Spec-6.12). The order of the rules matters: in and a false dominates an error, and in or a true does; otherwise the error dominates both absences.

ts
import { Evaluator, toDisplayString } from '@formscript/evaluator';

const evaluator = new Evaluator({
  resolve: (path) => (path[0] === 'age' ? 36 : null),
  resolveNamespace: () => null,
  now: () => new Date(0),
  subject: () => null,
  locale: () => 'en',
});

toDisplayString(evaluator.evaluate(expression));  // the canonical text

Specification sections implemented

Reference What this package implements
Spec-6.1, Spec-6.2 Totality and purity: every operation returns a value, and the error value replaces an exception
Spec-6.3 The error value, and the rules that keep it out of the data plane
Spec-6.5 Structural, order-sensitive equality for array and object
§6.2, Spec-6.8 The operators, and absence propagation: a strict operation yields the error value for an error operand and the dominant absence for an absent one
Spec-6.12 The three-valued logic of and, or and !
§6.4, Spec-6.27 The builtin library — all 67 builtins, side-effect free and total
§6.5 The comprehension forms: map, filter, sum, any, all, reduce
Spec-6.40 The RE2-subset regular-expression dialect
§6.6 The cost of an expression is read from the document, not computed here

Public API

ts
class Evaluator {
  constructor(environment: EvaluationEnvironment);
  readonly diagnostics: Diagnostic[];
  evaluate(expression: Expression): unknown;
}

interface EvaluationEnvironment {
  resolve(path: readonly string[]): unknown;
  resolveNamespace(namespace: string, path: readonly string[]): unknown;
  now(): Date;
  subject(): unknown;
  locale(): string;
  text?(key: string, args: readonly unknown[]): string | null;
  filledFields?(): readonly string[];
  isActive?(path: readonly string[]): boolean;
  shadow?(path: readonly string[]): unknown;
  isOverridden?(path: readonly string[]): boolean;
  formId?(): string;
}

Also exported: ERROR, isError, truthOf, valueOfTruth, strictEquals, compareValues, toDisplayString, typeNameOf, and, or, negate, dominantAbsence, builtinsFor and BUILTIN_NAMES.

Judgement calls

  1. The environment is the only way in. The evaluator holds no state of its own beyond the diagnostics it produced, so the same expression evaluated against the same environment always yields the same value — which is what Spec-9.1 requires of the engine that calls it.
  2. Absence is a value, not a special case. UNKNOWN and INAPPLICABLE are distinct sentinels, so x is unknown and x is inapplicable are told apart. A field that was never answered is unknown; a field whose entity is not active is inapplicable (Spec-5.5, Spec-8.2). When a strict operation has two absent operands, inapplicable dominates (Spec-6.8).
  3. The error value is not a number. It is its own sentinel, so it cannot be compared with == and cannot be added to anything. An error that reaches a non-field node aborts the transaction with E-EVAL-07 (Spec-6.3), which is the engine's job, not this package's.
  4. A builtin never throws. A builtin that would fail — substring with a negative index, 1 / 0 — returns the error value or null, as §6.4 documents for that builtin (Spec-6.27).
  5. Numbers are exact decimals. Arithmetic goes through @formscript/decimal, so 0.1 + 0.2 is exactly 0.3 and the canonical decimal text of Spec-5.32 is what toString yields (Spec-5.33).
  6. The regular-expression dialect is enforced at compile time. The evaluator compiles a pattern with the platform engine and normalises the subject to NFC (Spec-6.40); the restriction to RE2 — no backreferences, no lookaround — is checked by the analyzer's checkPattern, which reports E-BOUND-04 for a pattern outside the dialect. The evaluator therefore trusts a pattern that has passed the analyzer.

Public API

31 exported symbols.

ABSENCE_AWARE_BUILTINS const

The builtins that handle absence themselves (Spec-6.28). Every other builtin is strict (Spec-6.8), so the evaluator propagates an absent argument as absence …

ABSENCE_TESTS re-export
and re-export
Builtin type
BUILTIN_NAMES const
BuiltinContext interface
builtinsFor function
compareValues re-export
decimalOperation function

Runs a decimal operation; a value outside the profile's range yields the error value.

dominantAbsence re-export
ERROR re-export
ERROR_RESULT const

The result of a strict operation when an operand is the error value.

EvaluationEnvironment interface
Evaluator class
FormValue type
isAbsence function

Whether a value is one of the two absences.

isEmptyValue function

Whether a value counts as empty for `??` and `isEmpty` (Spec-5.4).

isError re-export
isTruthy function
LogicOutcome interface
matchesPattern function

A regular expression compiled under the RE2 subset (Spec-6.40).

negate re-export
normalizeValue function

Converts a host-supplied number into the profile's decimal128 (Spec-5.28).

numberLiteral function

The value of a numeric literal. The lexer rejects a literal the profile's number domain cannot represent (Spec-5.30), so this only has to fail if it is hande…

or re-export
strictEquals re-export
toDisplayString re-export
Truth type

The five logical states of the three-valued logic (Spec-6.12).

truthOf re-export
typeNameOf re-export
valueOfTruth re-export