Okyline Annex C Quick Reference - Expression Language

Version: 1.7.0 Date: May 2026 Status: Draft

Companion to the Okyline Core Language Quick Reference. Covers the $compute expression language for cross-field business rules and computed validations.


C - Expression Language

Okyline includes a pure, deterministic expression language for computed validations. It lets you express cross-field business rules - e.g. total == subtotal * (1 + taxRate) - that cannot be captured by per-field constraints alone. Expressions are declared in a $compute block and attached to fields with (%Name).

C.1 $compute Block

Declared at root level, alongside $oky:

{
  "$compute": {
    "TaxAmount": "subtotal * taxRate",
    "ValidTotal": "total == subtotal + %TaxAmount"
  },
  "$oky": {
    "invoice": {
      "subtotal": 100.0,
      "taxRate": 0.2,
      "total|(%ValidTotal)": 120.0
    }
  }
}

%TaxAmount references another expression; (%ValidTotal) validates the field. %Name.field.deep accesses members of the compute’s result.

Rules: - Expression names: start with letter, then letters/digits/underscores - Circular dependencies → parsing error - Expressions are evaluated lazily when referenced

Parameterized computes - suffix the name with a formal parameter list; call with matching arguments:

"$compute": {
  "CategoryCheck(cat)": "category != cat || rate > 0",
  "InRange(lo, hi)": "it >= lo && it <= hi"
},
"$oky": {
  "age|(%InRange(18, 120))": 30,
  "rateS|(%CategoryCheck('S'))": 20
}

↳ Params shadow sibling fields inside the body; use this.field to unshadow. Args on a field constraint must be literals or dotted paths; full expressions are allowed in compute bodies. Arity mismatch = load error. Params must not collide with special variables (it, parent, index, …). A parameter bound to an object exposes its members via p.field; a list parameter composes with firstOf/at/findFirst/sum/…


C.2 Field Constraint - (%Name)

"fieldName|(%ExpressionName)": exampleValue
Result Validation
true Pass
false Fail - Compute error
Non-boolean / null Fail - Compute error

Constraint exclusivity: (%Name) cannot combine with other (...) constraints (range, comparison). Include range checks inside the expression instead.

Evaluation order: Type → Standard constraints (@, {...}, ~...~) → Computed expression

Container vs element compute:

"field|@ [size] (%ContainerCheck) -> (%ElementCheck)": [10, 20]

↳ Before ->: evaluated once on collection (it = collection). After ->: evaluated per element (it = element).


C.3 Evaluation Context

Prefix Resolves from
(none) Current object
this. Current object (explicit)
parent. Parent of the current context
root. Document root
origin. Origin element (lambda only)
prev. / next. Adjacent elements (lambda only)
first. / last. Collection endpoints (lambda only)

Field resolution: direct name access, dot notation for nested (address.city), missing fields → null, null-safe navigation.

The it variable: - In field constraint: value of the field being validated - In aggregation lambda on scalars: current element - In aggregation lambda on objects: current object (rarely needed - use property names) - In aggregation lambda on nested collections: the inner collection (composable: sum(map(matrix, sum(it)))) - Outside any context: null


C.4 List Iteration Context (Lambda Only)

Variable Description Null when
origin Element whose validation triggered the call Never
prev / next Adjacent elements First / Last
first / last Collection endpoints Empty collection
index 0-based iteration index Never
size Collection size Never
Predicate True when
isOrigin Current element is the origin
isFirst Current element is first
isLast Current element is last
"$compute": {
  "IsUnique": "countIf(parent.items, id == origin.id) == 1"
}

↳ Inside the lambda: id = iteration element’s id, origin.id = validated element’s id


C.5 Operators

Operator Description Null behavior
?? Null coalescing (high precedence, short-circuit) Returns right if left is null
+ - * / Arithmetic Null propagates. Exception: string + null → treats null as ""
> < >= <= Comparison Returns null
== != Equality null == null → true
=== !== Strict equality (IEEE-754 bit-exact) Strict
&& \|\| ! Logical Null → false; !null → true
? : Ternary Condition null → false branch

List literals: [a, b, c] - inline list, usable anywhere a value is expected.

Precedence (high to low): ??* /+ - → comparisons → equality → &&||? :


C.6 Date Functions

Function Description Example
date(s, pattern?) Parse to LocalDate (default yyyy-MM-dd) date("2024-03-15")
formatDate(d, pattern?) Format date to string formatDate(d, "dd/MM/yy")"15/03/24"
today() Current system date today()
daysBetween(start, end) Days difference daysBetween("2024-03-15","2024-03-18") → 3
plusDays(d, n) Add days plusDays("2024-02-28", 1)"2024-02-29"
minusDays(d, n) Subtract days
plusMonths(d, n) Add months plusMonths("2024-01-31", 1)"2024-02-29"
minusMonths(d, n) Subtract months
plusYears(d, n) Add years
minusYears(d, n) Subtract years
year(d) month(d) day(d) Extract component month("2024-03-15") → 3
dayOfWeek(d) MON=1 … SUN=7 dayOfWeek("2024-03-15") → 5
dayOfYear(d) 1-366
weekOfYear(d) ISO week 1-53
quarter(d) 1-4 quarter("2024-09-15") → 3
semester(d) 1-2
isWeekend(d) True if Sat/Sun
isLeapYear(d) True if leap year
before(d1, d2) d1 < d2
after(d1, d2) d1 > d2
equals(d1, d2) d1 == d2

C.7 String Functions

Function Description Example
length(s) String length (null → 0) length("hey") → 3
isEmpty(s) Length == 0
isNullOrEmpty(s) Null or empty
isNull(v) v is null
hasValue(v) v is not null (opposite of isNull) hasValue(code) → presence check
substring(s, start, len) Extract substring (len = length, not end index) substring("Hello",1,3)"ell"
substringBefore(s, delim) Before first occurrence substringBefore("a:b:c",":")"a"
substringAfter(s, delim) After first occurrence substringAfter("a:b:c",":")"b:c"
substringBeforeLast(s, d) Before last occurrence
substringAfterLast(s, d) After last occurrence substringAfterLast("a.b.txt",".")"txt"
replace(s, target, repl) Replace all
replaceFirst(s, t, r) Replace first
replaceLast(s, t, r) Replace last
trim(s) ltrim(s) rtrim(s) Trim whitespace
toUpperCase(s) toLowerCase(s) Case conversion
capitalize(s) decapitalize(s) First char case capitalize("hello")"Hello"
startsWith(s, prefix) Check prefix
endsWith(s, suffix) Check suffix
contains(s, search) Substring found
removePrefix(s, p) Remove prefix if present
removeSuffix(s, sf) Remove suffix if present
removeRange(s, start, len) Remove len chars from start removeRange("Hello",1,3)"Ho"
padStart(s, len, ch) Left pad padStart("7",3,"0")"007"
padEnd(s, len, ch) Right pad
repeat(times, ch) Repeat char repeat(5,"*")"*****"
indexOf(s, sub) First index (-1 if not found)
indexOfFirst(s, sub) Alias for indexOf
indexOfLast(s, sub) Last index

String index handling: negative start → clamped to 0, negative length → 0, start > length → "", end > length → clamped. Never throws.

String ↔︎ List:

Function Description Example
chars(s) String → list of characters chars("abc")["a","b","c"]
split(s, sep) Split by literal separator split("a,b,c", ",")["a","b","c"]
join(coll, sep) Join list into string join(["a","b"], "-")"a-b"
countIf(chars(name), in(it, ["a","e","i","o","u"]))  // count vowels
join(filter(split(csv, ","), it != ""), ",")           // clean CSV

C.8 Numeric Functions

Function Description Example
abs(x) Absolute value abs(-5) → 5
sqrt(x) Square root sqrt(9) → 3.0
floor(x, scale?) Round down floor(3.1415, 2) → 3.14
ceil(x, scale?) Round up ceil(3.1415, 2) → 3.15
round(x, scale?, mode?) Round (default HALF_UP) round(3.5, 0, "HALF_EVEN") → 4.0
mod(a, b) Remainder mod(10, 3) → 1
pow(base, exp) Power pow(2, 3) → 8.0
log(x) Natural log
log10(x) Base-10 log log10(1000) → 3.0
toInt(v) Convert to integer toInt(3.7) → 4
toNum(v) Convert to number toNum("42") → 42.0
toStr(v) Convert to string toStr(42)"42"

Rounding modes: HALF_UP (default), HALF_DOWN, HALF_EVEN, UP, DOWN, CEILING, FLOOR

Precision: Exact decimal arithmetic within 6 decimal places (configurable via $decimalScale, see Core spec §7.3). No IEEE-754 artifacts (0.1 + 0.2 = 0.3 exactly). Division by zero → null.

Type promotion: int + int → int, int + decimal → decimal, int / int → decimal (division always returns decimal).


C.9 Aggregation & Utility Functions

Function Object collection Scalar collection Description
sum sum(coll, expr) sum(coll) Sum
average average(coll, expr) average(coll) Mean
min min(coll, expr) min(coll) Minimum
max max(coll, expr) max(coll) Maximum
count count(coll) count(coll) Non-null elements
countAll countAll(coll) countAll(coll) All elements (incl. null)
countIf countIf(coll, pred) countIf(coll, pred) Count where true
exists exists(coll, pred) exists(coll, pred) Any match
notExists notExists(coll, pred) notExists(coll, pred) No match
sumIf sumIf(coll, pred, expr) sumIf(coll, pred, val) Conditional sum
map map(coll, expr) map(coll, expr) Transform each
filter filter(coll, pred) filter(coll, pred) Keep where true
sum(items, quantity * unitPrice)           // object collection
sum([10, 20, 30])                          // scalar collection
filter([1, 2, 3, 4], it > 2)              // → [3, 4]
map(items, toUpperCase(code))              // transform

Membership - in:

Form Example
List literal in(status, ["DRAFT", "SENT"])
Nomenclature in(status, '$INVOICE_STATUS')
Array field in(code, allowedCodes)

null value → false. List as first arg → containsAll semantics.

Lookup - lookup:

lookup(currency, rates)                    // get value by key from map
lookup(key, src) ?? defaultValue           // with fallback
join(map(chars(code), lookup(it, charMap) ?? it), "")  // char-by-char transform

Compute reference - %Name:

"$compute": {
  "LineTotal": "netAmount * (1 + vat)",
  "InvoiceTotal": "sum(lines, %LineTotal)"
}

%LineTotal evaluates LineTotal for each element in the aggregation

List element access - retrieve a single element (null-safe):

Function Returns
firstOf(coll) First element, null if empty
lastOf(coll) Last element, null if empty
findFirst(coll, pred) First matching element, null otherwise
findLast(coll, pred) Last matching element (reverse scan), null otherwise
at(coll, idx) Element at numeric idx (0-based, list/array, or positional access on map values), null if out of bounds / negative
at(map, "key") Value for string key (map only - at(list, "0") returns null, no implicit String→Integer)

Member access on any expression - chain .field after a function call or parenthesized expression:

firstOf(items).amount
findFirst(lines, cat == 'S').amount
%getParty('S').address.city
at(payments, 0).status

↳ Dotted paths on bare identifiers (parent.x.y) are unchanged - the postfix .field applies after ) or ]. Resolution on null base → null.


C.10 Null Handling Summary

Context Behavior
Arithmetic (+ - * /) Null propagates
String + Null treated as ""
Comparisons (< > >= <=) Returns null
Equality (==) null == null → true; null == x → false
Logical (&& \|\|) Null → false
!null → true
Division by zero → null
Missing field → null
obj.field on null obj → null (null-safe)

Okyline® is a registered trademark of Akwatype.