Version 2 of 6 · · Current version

tags
#help #language

Help: expressions

Expressions compute values: in rules, in computed fields (@calc), in object queries (?query=), in ```table fences, and in the Fiddle, where you can try every line below. Each example shows the expression, then its value.

Values

  • 42 → 42
  • 2.5 → 2.5
  • "hello" → "hello"
  • 'single quotes too' → "single quotes too"
  • true → true
  • [1, 2, 3] → [1, 2, 3]
  • {a: 1, b: "x"} → {a: 1, b: "x"}
  • null ?? "none" → "none"

Numbers are Int (whole, 64-bit, wraps like Java's long) or Float. Number accepts either. Text is String. Dates and durations have their own types, below.

Arithmetic

  • 1 + 2 * 3 → 7
  • (1 + 2) * 3 → 9
  • 99 / 2 → 49.5
  • 4 / 2 → 2.0
  • 7 % 3 → 1
  • 1 + 2.5 → 3.5

/ always gives a Float. Dividing by zero is an error (E_ARITH), never infinity.

Text

  • "n=${1 + 2}" → "n=3"
  • "a" + "b" → "ab"
  • "ell" in "hello" → true
  • toUpper("abc") → "ABC"
  • split(s = "a,b,c", by = ",") → ["a", "b", "c"]

Write \${ for a literal ${. Triple quotes ("""…""") hold text over several lines.

Comparing and logic

  • 1 is 1 → true
  • 1 is not 2 → true
  • 5 is "5" → false
  • 1 < 2 and 3 > 2 → true
  • false or true → true
  • not false → true
  • 2 in [1, 2] → true
  • 3 not in [1, 2] → true
  • "a" in {a: 1} → true

is and == are the same, as are and/&&, or/||, not/!. and and or take Booleans only: for a default value use ??. Comparisons don't chain: 1 < 2 < 3 is an error.

Missing values

  • x ?? 7 → 7
  • x is defined → false
  • x is empty → true
  • [] is empty → true
  • {a: null}.a ?? "none" → "none"

Reading a name that has no value is an error (E_UNDEFINED) unless you test it or give it a default. ?. reads a field of something that may be null.

Choosing

  • if 2 > 1 then "yes" else "no" → "yes"
  • (if true then 1 else 2) + 5 → 6

An if in an expression always has an else.

Types and casts

  • 5 is a Int → true
  • 5.5 is a Int → false
  • "x" is a String → true
  • "42" as Int → 42
  • 4.9 as Int → 4
  • 12 as String → "12"
  • typeOf(1 + 1) → "Int"

Objects and lists

  • {a: 1} + {b: 2} → {a: 1, b: 2}
  • {a: {x: 1}} ++ {a: {y: 2}} → {a: {x: 1, y: 2}}
  • {a: 1, b: 2} - {b: ""} → {a: 1}
  • [1, 2] + 3 → [1, 2, 3]
  • [1] ++ [2] → [1, 2]
  • [1, 2, 3][-1] → 3
  • {"a-b": 2}["a-b"] → 2
  • {a: 5}.a → 5

+ merges objects one level deep (the right side wins); ++ merges all the way down and joins lists. A negative index counts from the end.

Working with lists

  • [1, 2, 3] map (x => x * 2) → [2, 4, 6]
  • [1, 2, 3] filter (x => x > 1) → [2, 3]
  • [1, 2, 3] exists (x => x > 2) → true
  • [[1], [2, 3]] flatMap (x => x) → [1, 2, 3]
  • [1, 2, 3] fold (acc = 0) (x => acc + x) → 6
  • [1, 2, 3] mkString ", " → "1, 2, 3"
  • [{id: "a"}, {id: "b"}] indexBy "id" → {a: {id: "a"}, b: {id: "b"}}
  • {a: 1} map (p => p.key + p.value) → ["a1"]
  • [1, 2, 3] filter (x => x > 1) map (x => x * 10) → [20, 30]

The part in parentheses is a small function: x => … takes each item as x. On an object, map, filter and exists see {key, value} pairs.

Patterns

  • "/acct/42" matches /\/acct\/(?<id>\d+)/ → true
  • "ab" ~= /a./ → true

A pattern is written between slashes, not quotes. In a rule, named groups like (?<id>…) become variables after a successful match.

Dates and durations

  • ("2026-09-24" as Date) < ("2026-09-25" as Date) → true
  • ("2026-09-24T00:00:00Z" as Date) + "2h30m" → "2026-09-24T02:30:00.000Z"
  • ("2026-09-25" as Date) - ("2026-09-24" as Date) → "1d"
  • "90s" as Duration → "1m30s"
  • isoDate("2026-09-24T10:00:00Z" as Date) → "2026-09-24"

A duration is text like "5s", "5 seconds", "2h30m", "1d"; next to a date it's read as a duration. Dates are ISO text when stored.

Core functions

Functions take named arguments, split(s = "a,b", by = ","); one that takes a single argument also takes it bare, sizeOf([1, 2]); a variable with the argument's name can stand for it, split(s, by). f(obj...) passes an object's fields as the arguments.

  • sizeOf(x): length of a String or list, number of fields of an object.
  • typeOf(x): the type's name.
  • now(): the current date and time. today(): today at 00:00 UTC.
  • isoDate(d): a Date as YYYY-MM-DD.
  • toMillis(x): a Date or Duration as milliseconds.
  • uuid(): a random id.
  • trim(s), toUpper(s), toLower(s).
  • split(s, by): text to a list.
  • replaceAll(s, regex, with), replaceFirst(s, regex, with): the regex given as text.
  • slice(x, from, until): part of a String or list; until optional, negatives count from the end.
  • range(from, until): the Ints from from up to until - 1.
  • flatten(xs): one level of nested lists.
  • math.sum(xs), math.min(xs), math.max(xs), math.average(xs): over a list of numbers; average is always a Float.
  • nicej(x): pretty JSON text.
  • hashcode(x): a sha1 of the value.
  • urlencode(s), base64encode(s), base64decode(s).
  • json.parse(text): JSON text to a value.
  • csv.parse(text): CSV with a header row to a list of rows; numeric cells become numbers.
  • http.json(url, headers), http.text(url, headers): fetch a web page (https, public sites, 10 s, 2 MB), in rules only.

In rules there are also the engine's own messages: log(msg), diesel.throw(code, msg), dom.upsert(cls, entity), dom.find(cls, key), dom.list(cls), dom.remove(cls, key).

When it goes wrong

Errors are never silent: they say what went wrong and where. The common ones are E_UNDEFINED (a name with no value: use ?? or is defined), E_TYPE (the wrong kind of value, like "a" or true), E_PARSE (the text isn't an expression), E_ARITH (division by zero), E_ARG (a function's arguments) and E_NO_TOOL (no such function).