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.