Appearance
Language reference
This page specifies the rule expression language: what a valid rule is, how its parts bind, and what the grammar rejects. It is a reference, not a tutorial — for a worked introduction start at what is a rule.
The language is case-sensitive. Keywords are uppercase only. Whitespace — spaces, tabs, newlines, carriage returns — is insignificant between tokens and required only where it separates two words that would otherwise merge.
Top-level forms
A rule is exactly one of two forms.
Plain condition
conditionThe condition is always evaluated. True → the rule passes. False → the rule fails.
Guarded rule
IF condition THEN conditionThe IF condition is evaluated first.
- If it is false, the rule is skipped — neither passed nor failed. A skipped rule never blocks a move.
- If it is true, the
THENcondition is evaluated: true → passes, false → fails.
IF without THEN, THEN without IF, an empty guard, and an empty consequent are all parse errors.
Conditions
A condition is a boolean expression. It is one of:
| Form | Shape |
|---|---|
| Comparison | arithExpr op arithExpr |
| Membership | arithExpr IN ( list ) or arithExpr NOT IN ( list ) |
| Quantified | EVERY name IN iterable : condition or SOME name IN iterable : condition |
| Conjunction | condition AND condition |
| Disjunction | condition OR condition |
| Negation | NOT condition |
| Grouping | ( condition ) |
Operator precedence
Listed from lowest binding strength to highest. Operators lower in the table bind more tightly and are therefore applied first.
| Level | Operator(s) | Associativity |
|---|---|---|
| 1 (lowest) | OR | Left |
| 2 | AND | Left |
| 3 | NOT | Prefix (right) |
| 4 | Comparison: =, !=, <, <=, >, >= | Non-associative |
| 4 | Membership: IN, NOT IN | Non-associative |
| 4 | Quantifiers: EVERY, SOME | Prefix |
| 5 | Arithmetic: +, - | Left |
| 6 (highest) | Primary: literals, variables, function calls, ( expr ) | — |
Consequences worth stating explicitly:
a OR b AND cisa OR (b AND c).NOT a = bisNOT (a = b)—NOTtakes the whole comparison, never just its left operand.a + b > cis(a + b) > c.a + b - cis(a + b) - c.- Comparison and membership are non-associative, so
a < b < cis a parse error rather than a chained test.
Parentheses override precedence at any level.
A quantifier body extends to the right
Everything after a quantifier's : is parsed as a full condition, so it absorbs any following AND / OR clauses. EVERY d IN digits(x): d < 9 AND current_sum > 0 puts the second clause inside the loop body. Parenthesize the quantifier — (EVERY d IN digits(x): d < 9) AND current_sum > 0 — to keep the clauses independent.
Keywords
The complete reserved list. All are uppercase-only, and each must be bounded by whitespace, (, ), :, ,, or the end of the rule — a keyword adjacent to a letter, digit, or underscore merges into an identifier instead (IFprev_sum is one identifier).
| Keyword | Role |
|---|---|
IF | Guard clause opener |
THEN | Guard clause separator |
AND | Logical conjunction |
OR | Logical disjunction |
NOT | Logical negation; first half of NOT IN |
IN | Membership operator; quantifier separator |
EVERY | Universal quantifier |
SOME | Existential quantifier |
Keywords cannot be used as variable or function names.
ALL_DIGITS and SOME_DIGITS are not keywords. They are function-call patterns recognised by the macro expander and rewritten into EVERY / SOME form before evaluation — see quantifiers.
Operators
Comparison
= (strict equality), !=, <, <=, >, >=. Both operands are arithmetic expressions; the result is boolean.
Equality is strict. This matters against the null-valued place variables: place = 10 is false when place is null, and place != 10 is true. See decimal places.
Membership
arithExpr IN ( item, item, … )
arithExpr NOT IN ( item, item, … )Each item is an arithmetic expression. At least one item is required; the list is comma-separated and parenthesized.
NOT IN is a single two-word operator, not a NOT applied to an IN expression — though the two produce the same result.
When the left-hand side evaluates to an array (only digits(...) does), membership tests whether any element of the array appears in the list.
Arithmetic
+ and - only, left-associative. There is no multiplication, division, modulo, or exponentiation.
Quantifiers
EVERY name IN iterable : condition
SOME name IN iterable : conditionname is an identifier bound as the loop variable for the duration of condition; it shadows any outer variable of the same name and does not leak outside. iterable is an arithmetic expression that should evaluate to an array — in practice always a digits(...) call. The colon is required.
EVERY over an empty iterable is true; SOME over an empty iterable is false. Quantifiers may nest, each with its own independent scope.
Literals
Numbers
An integer, optionally preceded by a minus sign.
Valid: 0, 1, 5, -3, 100, -999
Invalid: 3.14, 1.0, 1e5, 0x1F, 1_000
There are no other literal types
No booleans, no strings, no arrays. true and false are not literals — they parse as ordinary variable names and resolve to nothing. Arrays exist only as the return value of digits(...); they cannot be written out.
Identifiers
An identifier begins with a letter and continues with letters, digits, and underscores. Identifiers are case-sensitive: prev_sum and Prev_Sum are different names.
The grammar also accepts dot-separated paths (a.b.c), resolving the first segment from the evaluation context and the rest as property accesses. No context value in this system exposes properties, so a dotted path is never useful in practice.
Variables
Exactly nine names are in scope. Any other identifier is reported as an unknown variable by the editor's linter, and resolves to nothing at evaluation time.
| Variable | Type |
|---|---|
prev_sum | number |
current_value | number |
current_sum | number |
step_index | number |
last_step_index | number |
isLastStep | boolean |
place | number or null |
formula | number or null |
digitBefore | number or null |
Full descriptions are in variables and the context reference.
Function calls
name ( arg, arg, … )Arguments are arithmetic expressions, comma-separated. At least one argument is required — zero-argument calls are not syntactically valid.
Five functions exist, each with a fixed arity:
| Function | Arity |
|---|---|
digits(x) | 1 |
highest_digit(x) | 1 |
lowest_digit(x) | 1 |
number_of_digits(x) | 1 |
digitAt(x, place) | 2 |
Calling an unknown name, or a known name with the wrong number of arguments, is flagged by the linter while editing; at evaluation time an unknown name raises a runtime error rather than a parse error. Semantics are in the function reference and digit functions.
Not supported
Constructs that are commonly attempted and are not part of the language:
| Attempted | Status |
|---|---|
prev_sum * 2, prev_sum / 10 | Multiplication and division are not in the grammar. Only + and - exist. |
3.14, 1.0 | Decimal numbers are not valid literals. Integers only. |
1e5 | Scientific notation is not valid. |
"abc" | Strings do not exist — no string values, literals, or operators. |
0 < current_value < 10 | Chained comparisons are not supported. Write current_value > 0 AND current_value < 10. |
digits() | Zero-argument calls are not valid syntax. Every function requires at least one argument. |
ANY d IN digits(x): d = 9 | ANY is not a keyword. The quantifiers are EVERY and SOME. |
current_value > 0 -- note | Comments are not part of the grammar. The -- annotations used in this documentation's examples are for the reader only; a rule containing one will not parse. |
Also absent, with no planned equivalent: variable assignment, user-defined functions, string or date handling, and any form of loop other than the two quantifiers.