Math Expression Language
The complete reference for Race Platform's math expression language. See Math Expressions for the conceptual intro.
The language is implemented twice with the same test corpus:
- TypeScript:
packages/math-engine/ - Dart:
apps/client/lib/src/math/
Grammar (informal)
expression := primary ( binop primary )*
primary := literal
| identifier
| identifier '(' arg-list? ')'
| identifier '.' identifier # member access
| unop primary
| '(' expression ')'
literal := number | string | bool
identifier := /[A-Za-z_][A-Za-z0-9_]*/
arg-list := expression ( ',' expression )*
binop := '+' | '-' | '*' | '/' | '%'
| '==' | '!=' | '<' | '<=' | '>' | '>='
| '&&' | '||'
unop := '-' | '!'
Operator precedence (low to high):
||&&==,!=<,<=,>,>=+,-*,/,%- Unary
-,!
Built-in functions
The current set, exhaustive against
packages/math-engine/src/functions.ts. 35 functions, all
deterministic, all pure.
Aggregations
| Function | Signature | Description |
|---|---|---|
Min | Min(a, b, ...) or Min(list) | Minimum value |
Max | Max(a, b, ...) or Max(list) | Maximum value |
Sum | Sum(a, b, ...) or Sum(list) | Sum of inputs |
Avg | Avg(list) | Arithmetic mean |
Mean | Mean(list) | Same as Avg |
Median | Median(list) | Median value |
StdDev | StdDev(list) | Sample standard deviation |
Count | Count(list) | Number of elements |
First | First(list) | First element |
Last | Last(list) | Last element |
BestX | BestX(items, n, prop, ascending?, average?) | Best n of items by prop (defaults: ascending=true, average=true → mean of the n lowest) |
Numeric
| Function | Description |
|---|---|
Round(x) / Round(x, n) | Round to nearest integer or n decimals |
Floor(x) | Round down |
Ceil(x) | Round up |
Abs(x) | Absolute value |
Sqrt(x) | Square root |
Pow(x, y) | x to the yth power |
Log(x) / Log(x, base) | Natural log or arbitrary base |
Logic
| Function | Description |
|---|---|
If(cond, thenVal, elseVal) | Conditional |
And(a, b, ...) | All truthy |
Or(a, b, ...) | Any truthy |
Not(x) | Logical negation |
IfNull(x, fallback) | Returns fallback if x is null / undefined / NaN |
Collection / lookup / interpolation
| Function | Description |
|---|---|
Filter(items, prop) | Subset of items where prop is truthy |
Lookup(items, prop, target) | First item whose prop equals target |
LookupClosest(items, prop, target) | Item whose numeric prop is closest to target |
Interpolate(items, xProp, yProp, x, extrapolate?) | Linear interpolation across (x,y) pairs sorted by x |
LinFit(items, xProp, yProp, x) | Linear regression fit through (x,y) pairs evaluated at x |
Text
| Function | Description |
|---|---|
Concatenate(a, b, ...) | String concatenation |
Substring(s, start, length) | Substring extraction |
Length(s) | String length |
ToUpper(s) / ToLower(s) | Case conversion |
Contains(s, needle) | Substring check |
Split(s, sep) | Returns a list |
TextJoin(sep, list) | Joins a list into a single string |
Conversion
| Function | Description |
|---|---|
ToNumber(x) | Parse to number |
ToText(x) | Stringify |
FormatNumber(x, dp?) | x.toFixed(dp). dp defaults to 2. |
Scope resolution
When an identifier doesn't match a function name, the evaluator looks it up in the scope:
- Local entity parameters
- Math parameters on the same entity
- Parent entity scope (e.g.
Session.*,Event.*)
Member access (Session.BestLapTimeMs) walks the scope chain
from the named root.
Plugin functions
A plugin's math interface contributes additional functions.
Plugin function names share the global namespace with built-ins —
the evaluator resolves them by trying built-ins first, then any
loaded plugin's listFunctions().
Determinism
The engine intentionally omits Now(), Date.now(), random
number generators, and IO. Math expressions are pure functions of
their inputs. If you need a clock value, inject it as a scope
field at evaluation time.
Testing
Both engines pass the same 113-test corpus
(packages/math-engine/tests/*). The Dart port mirrors the same
function table; the parity tests in
apps/client/test/math_parity_test.dart validate they don't
drift.