Expressions and operators
You write relations (expr) and conditions (applicable_when) in a small expression language.
Tickmarks type-checks every expression when it compiles the schema, so adding a date to a currency
amount, say, fails straight away as a compile error that
points at the problem, rather than producing a wrong answer on some document months later.
This page is the complete reference. If you’re writing your first relation, Relations is a gentler start.
Operators
Section titled “Operators”From lowest to highest precedence:
| Precedence | Operators | Operand types | Result |
|---|---|---|---|
| 1 | or |
bool | bool |
| 2 | and |
bool | bool |
| 3 | not |
bool | bool |
| 4 | == != < <= > >= |
see comparisons | bool |
| 5 | + - |
see arithmetic | |
| 6 | * / |
see arithmetic | |
| 7 | unary - |
int, decimal, money, duration | same type |
| 8 | function calls, field names, literals, ( … ) |
A few things trip people up who are used to other languages. Logic is written in words, and,
or and not, with no &&, || or !. Equality is ==, and a single = is a syntax error.
Comparisons can’t be chained, so write a < b and b < c instead of a < b < c. Binary operators
are left-associative: a - b - c means (a - b) - c.
Literals
Section titled “Literals”| Literal | Example | Type |
|---|---|---|
| Integer | 12, 0 |
int (64-bit) |
| Decimal | 1.21, 0.05 |
decimal (a digit is required after the point) |
| String | 'terminated', 'it''s' |
string; double a quote to escape it |
| Date | date'2027-03-01' |
date |
| Duration | dur'P12M', dur'P1Y2M10D', dur'PT2H' |
duration (ISO 8601) |
| Boolean | true, false |
bool |
| Null | null |
only in field == null / field != null |
There is no negative literal: -5 is unary minus applied to 5.
You don’t need to write a currency on a number used with money. A numeric literal next to a money
value takes that value’s currency, so rent >= 0 and total == net + 25 work on a money field. A
string literal compared with an enum must be one of the enum’s values.
Comparisons
Section titled “Comparisons”| Operands | == != |
< <= > >= |
|---|---|---|
| int, decimal (mixed freely) | yes | yes |
| money, same currency | yes | yes |
| date with date, datetime with datetime | yes | yes |
| duration, same basis | yes | yes, see comparing durations |
| string with string | yes | no |
| bool with bool | yes | no |
| enum with its own labels or the same enum field | yes | no |
field with null |
yes | no |
Equality is exact, with no tolerance: 1.50 == 1.5 is true, but 21.00 == 20.999 is false. If a
document might print a rounded figure, round your side of the comparison with
round.
Only == derives values. endDate == startDate + duration can compute any one of the three
fields from the other two. <, <=, >, >= and != only check candidates.
Arithmetic
Section titled “Arithmetic”| Expression | Result | Notes |
|---|---|---|
int + - * int |
int | Overflow is an error, never a wrap. |
int/decimal + - * int/decimal |
decimal | Exact, never floating point. |
money + - money |
money | Same currency only. |
money * int/decimal, int/decimal * money |
money | |
money * money |
error | |
number / number |
decimal | Only inside round. |
money / int/decimal |
money | Only inside round. |
money / money |
decimal | Same currency; only inside round. |
date + duration, duration + date, date - duration |
date | Duration basis calendar, months or days. |
date - date |
duration | Takes the basis of the duration it is compared or combined with; calendar otherwise. |
datetime + duration, datetime - duration |
datetime | Duration basis exact. |
duration + - duration |
duration | Same basis. |
Anything not in the table is refused. In particular, you can’t mix money with a plain number
field (rent + units), combine different currencies, subtract one datetime from another, multiply
a duration by a number, or do arithmetic on strings, bools or enums.
Functions
Section titled “Functions”round(x, scale) and round(x, scale, mode)
Section titled “round(x, scale) and round(x, scale, mode)”Rounds an int, decimal or money value to scale digits after the point. scale must be an
integer literal from 0 to 28, and mode is written as a bare word, without quotes:
mode |
Rule | 2.5 |
-2.5 |
2.1 |
|---|---|---|---|---|
half_even (default) |
nearest; ties to the even digit | 2 |
-2 |
2 |
half_up |
nearest; ties away from zero | 3 |
-3 |
2 |
down |
towards zero | 2 |
-2 |
2 |
up |
away from zero | 3 |
-3 |
3 |
Division is only allowed inside round. That’s because most divisions, such as 10 / 3, have no
exact decimal result, and Tickmarks would otherwise have to pick a rounding for you. Inside round,
the quotient is computed to 28 significant digits and rounded once:
vat == round(net * rate / 100, 2)vat == round(net * rate / 100, 2, half_up)monthly == round(annual / 12, 2)when(condition, rule)
Section titled “when(condition, rule)”rule must hold only when condition is true. If condition is false, or Tickmarks doesn’t know
its value, the relation is satisfied. when must be the whole relation, or the rule of another
when. It can’t appear inside not, and, a comparison or applicable_when.
when(status == 'terminated', terminationDate <= endDate)when(hasDeposit == true, when(currencyKnown == true, deposit <= rent * 3))is_null(field) and is_present(field)
Section titled “is_null(field) and is_present(field)”Test whether a field has a value. The argument must be a field name. field == null is the same as
is_null(field) and field != null the same as is_present(field).
when(status == 'terminated', is_present(terminationDate))Fields in expressions
Section titled “Fields in expressions”Refer to a field by its name; a name that isn’t a field is E-NAME-UNKNOWN. Array fields can’t
be used in expressions at all (E-ARRAY-RELATION), and an applicable_when can’t refer to the
field it belongs to.
Common type errors
Section titled “Common type errors”When an expression fails to compile, the cause is usually one of these:
| Expression | Code | Why |
|---|---|---|
rate / 2 == x |
E-DIV-UNROUNDED |
Division outside round. |
eurTotal == usdTotal |
E-CURRENCY |
Different currencies. |
rent * rent == x |
E-TYPE |
Money times money. |
status < 'draft' |
E-TYPE |
Enums have no order. |
startDate < rate |
E-TYPE |
Date compared with a number. |
amount > null |
E-TYPE |
null is only allowed with == and !=. |
status == 'bogus' |
E-ENUM-VALUE |
Not a declared label. |
monthsDur < daysDur |
E-BASIS |
Durations of different bases. |
startDate + dur'PT2H' == endDate |
E-BASIS |
A date takes whole days only. |
not when(a, b) |
E-WHEN-NESTED |
when must be outermost. |
round(x, 40) |
E-ROUND-ARG |
Scale above 28. |
startDate + duration |
E-REL-TYPE |
A relation must be true or false. |
a < b < c |
E-EXPR-SYNTAX |
Comparisons cannot be chained. |