Skip to main content

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

  1. ||
  2. &&
  3. ==, !=
  4. <, <=, >, >=
  5. +, -
  6. *, /, %
  7. Unary -, !

Built-in functions

The current set, exhaustive against packages/math-engine/src/functions.ts. 35 functions, all deterministic, all pure.

Aggregations

FunctionSignatureDescription
MinMin(a, b, ...) or Min(list)Minimum value
MaxMax(a, b, ...) or Max(list)Maximum value
SumSum(a, b, ...) or Sum(list)Sum of inputs
AvgAvg(list)Arithmetic mean
MeanMean(list)Same as Avg
MedianMedian(list)Median value
StdDevStdDev(list)Sample standard deviation
CountCount(list)Number of elements
FirstFirst(list)First element
LastLast(list)Last element
BestXBestX(items, n, prop, ascending?, average?)Best n of items by prop (defaults: ascending=true, average=true → mean of the n lowest)

Numeric

FunctionDescription
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

FunctionDescription
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

FunctionDescription
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

FunctionDescription
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

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

  1. Local entity parameters
  2. Math parameters on the same entity
  3. 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.