Skip to content

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 ​

condition

The condition is always evaluated. True → the rule passes. False → the rule fails.

Guarded rule ​

IF condition THEN condition

The 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 THEN condition 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:

FormShape
ComparisonarithExpr op arithExpr
MembershiparithExpr IN ( list ) or arithExpr NOT IN ( list )
QuantifiedEVERY name IN iterable : condition or SOME name IN iterable : condition
Conjunctioncondition AND condition
Disjunctioncondition OR condition
NegationNOT 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.

LevelOperator(s)Associativity
1 (lowest)ORLeft
2ANDLeft
3NOTPrefix (right)
4Comparison: =, !=, <, <=, >, >=Non-associative
4Membership: IN, NOT INNon-associative
4Quantifiers: EVERY, SOMEPrefix
5Arithmetic: +, -Left
6 (highest)Primary: literals, variables, function calls, ( expr )—

Consequences worth stating explicitly:

  • a OR b AND c is a OR (b AND c).
  • NOT a = b is NOT (a = b) — NOT takes the whole comparison, never just its left operand.
  • a + b > c is (a + b) > c.
  • a + b - c is (a + b) - c.
  • Comparison and membership are non-associative, so a < b < c is 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).

KeywordRole
IFGuard clause opener
THENGuard clause separator
ANDLogical conjunction
ORLogical disjunction
NOTLogical negation; first half of NOT IN
INMembership operator; quantifier separator
EVERYUniversal quantifier
SOMEExistential 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 : condition

name 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.

VariableType
prev_sumnumber
current_valuenumber
current_sumnumber
step_indexnumber
last_step_indexnumber
isLastStepboolean
placenumber or null
formulanumber or null
digitBeforenumber 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:

FunctionArity
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:

AttemptedStatus
prev_sum * 2, prev_sum / 10Multiplication and division are not in the grammar. Only + and - exist.
3.14, 1.0Decimal numbers are not valid literals. Integers only.
1e5Scientific notation is not valid.
"abc"Strings do not exist — no string values, literals, or operators.
0 < current_value < 10Chained 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 = 9ANY is not a keyword. The quantifiers are EVERY and SOME.
current_value > 0 -- noteComments 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.

Reference and cookbook for mental-math rule and recipe authoring.