Skip to main content

Operators and precedence

Part of the function and operator catalog. Every example on the catalog is executed as an assertion by the engine's own test suite (examples.test.ts).

Operators​

Every condition operator ENG-08 enumerates. An operator with a symbol is callable either way — by name (add(1, 2)) or by its infix/prefix symbol (1 + 2) — and both forms dispatch through the exact same implementation.

OperatorSymbolSignatureDescription
equal==equal(left: any value, right: any value) -> true/falseAnswers whether two values are equal. Cross-type-tolerant: a number and text are simply not equal — never an error.
notEqual!=notEqual(left: any value, right: any value) -> true/falseThe negation of equal — cross-type-tolerant in the same way.
approxEqual—approxEqual(left: a number, right: a number, tolerance: a number) -> true/falseAnswers whether two numbers are equal within an explicit tolerance. There is no default tolerance — omitting it is a wrong-arity error, not a fallback. A difference exactly equal to the tolerance is equal.
lessThan<lessThan(left: any value, right: any value) -> true/falseAnswers whether the left value orders before the right value. Strings order by exact UTF-16 code-unit sequence, not locale collation, so an uppercase letter sorts before a lowercase one. Ordering across incomparable types (e.g. text against a number) is a coded error, never a silent false.
lessThanInclusive<=lessThanInclusive(left: any value, right: any value) -> true/falseLike lessThan, inclusive of equality. Same code-unit ordering rule and the same incomparable-type error.
greaterThan>greaterThan(left: any value, right: any value) -> true/falseAnswers whether the left value orders after the right value. Same code-unit ordering rule and the same incomparable-type error.
greaterThanInclusive>=greaterThanInclusive(left: any value, right: any value) -> true/falseLike greaterThan, inclusive of equality. Same code-unit ordering rule and the same incomparable-type error.
and—and(operand: any value, ...) -> true/falseAnswers true only if every operand is true. Short-circuits left to right: stops at the first operand that resolves false, and never evaluates the operands after it. Each operand must be a boolean or null (null counts as false); a string or a number is a coded error.
or—or(operand: any value, ...) -> true/falseAnswers true if any operand is true. Short-circuits left to right: stops at the first operand that resolves true, and never evaluates the operands after it. Each operand must be a boolean or null (null counts as false); a string or a number is a coded error.
not—not(operand: any value) -> true/falseNegates a boolean or null (null counts as false). A string or a number is a coded error — there is nothing to skip, so this parameter is eager.
add+add(left: a number, right: a number) -> a numberAdds two numbers. The '+' operator only adds numbers in rule-line — it never joins strings; use CONCAT for that.
sub-sub(left: a number, right: a number) -> a numberSubtracts the right number from the left number.
mul*mul(left: a number, right: a number) -> a numberMultiplies two numbers.
div/div(left: a number, right: a number) -> a numberDivides the left number by the right number. Division by zero is a coded error, never Infinity.
contains—contains(subject: text, needle: text) -> true/falseAnswers whether the subject text contains the needle text, anywhere. Exact UTF-16 code-unit comparison: no case folding, no trimming, no Unicode normalization. An empty needle is always found, so this is true.
doesNotContain—doesNotContain(subject: text, needle: text) -> true/falseThe negation of contains. An empty needle is always found, so this is false.
startsWith—startsWith(subject: text, needle: text) -> true/falseAnswers whether the subject text begins with the needle text. Exact UTF-16 code-unit comparison: no case folding, no trimming, no Unicode normalization. An empty needle is always true.
endsWith—endsWith(subject: text, needle: text) -> true/falseAnswers whether the subject text ends with the needle text. Exact UTF-16 code-unit comparison: no case folding, no trimming, no Unicode normalization. An empty needle is always true.
in—in(value: any value, set: an array of any value) -> true/falseAnswers whether a value is a member of an array, deciding membership through the one equality rule — a decimal 2 is not in an array holding the text "2". An empty array is always false. A second argument that is not an array is a coded type error, not false.
notIn—notIn(value: any value, set: an array of any value) -> true/falseThe negation of in. An empty array is always true.
matches—matches(subject: text, pattern: text, flags?: text) -> true/falseAnswers whether the subject text matches the pattern over its WHOLE length. The pattern is checked against rule-line's supported regular-expression subset before it is ever matched, and is then run on a linear-time matcher, so a pattern cannot make a match take long on long text. A pattern that already anchors itself with ^ or $ is refused — rule-line cannot safely add its own whole-subject anchoring around one — use regexMatch instead for match-anywhere semantics.
regexMatch—regexMatch(subject: text, pattern: text, flags?: text) -> true/falseAnswers whether the subject text matches the pattern ANYWHERE in it — unlike matches, which requires the whole subject to match. Same supported-subset check, the same linear-time matcher and the same input cap.
exists—exists(operand: any value) -> true/falseAnswers whether a data-access path is present, whether or not its value is null: true for a present value (including an explicit null — the key is there), false for an absent path. Only valid over a path access; asking whether a call failed is not what this operator does — use IF or COALESCE for that.
notExists—notExists(operand: any value) -> true/falseThe exact negation of exists on every input: false for a present value (including explicit null), true for an absent path.
isNull—isNull(operand: any value) -> true/falseAnswers whether a data-access path is present AND explicitly null. True only for a present null value; false for a present non-null value; false for an absent path — absence is not null.

Text pattern limits​

matches and regexMatch hold every pattern to the limits below. The Text Patterns page explains the pattern language and what each refusal means. These values are read from the engine itself, so they are the values it enforces.

LimitValueWhen it is exceeded
Permitted flagsi, m, sAny other flag, or a flags value longer than 8 characters, is refused with RE_DISALLOWED_FLAG.
Longest pattern1024 charactersThe pattern is refused with RE_PATTERN_TOO_LONG before it is read.
Largest compiled pattern4096 matcher instructionsThe pattern is refused with RE_PATTERN_TOO_LONG. Repeat counts expand a short pattern into many instructions, so a pattern of a few hundred characters can reach it.
Largest pattern written out in full65536 matcher instructionsA pattern whose alternatives and repeats, written out in full without sharing their common beginnings, are larger is refused with RE_PATTERN_TOO_LONG before it is built, even when sharing would bring the compiled pattern under the limit above. Patterns of ordinary shape are far below it.
Most characters compared for upper and lower case16384 charactersUnder the i flag the matcher compares every character a character class covers, one by one, for its upper and lower case. A pattern whose classes cover more than this many such characters in total is refused with RE_PATTERN_TOO_LONG before it is built. ., [\s\S] and [^] cost nothing, [A-Za-z] costs 52 and \w costs 53, so patterns of ordinary shape are far below it. A very wide range, such as a whole script block, is refused under i; removing the i flag or narrowing the range accepts it.
Largest repeat count, alone or nested1000A count such as {1001}, or counts that multiply past it such as (?:a{100}){11}, is refused with RE_DISALLOWED_CONSTRUCT.
Longest text a pattern is applied to4096 UTF-16 code unitsThe match is not attempted and the step fails with STEP_EXECUTION_FAILED, and its details carry RE_INPUT_TOO_LONG. This is data, so it is checked when the run reaches the pattern, never when the ruleset is saved.

Operator precedence​

The parser's one declared precedence and associativity table, loosest to tightest. This table is generated directly from PRECEDENCE_TABLE — the hand-written Expression Language page explains why the levels are ordered this way and links back here rather than restating this table in prose.

PrecedenceOperatorAssociativity
1orleft
2andleft
3notright
4==left
4!=left
5<left
5<=left
5>left
5>=left
6+left
6-left
7*left
7/left