Expressions

Expressions are the main building block of IDML, they evaluate into values.

Literals

If you want to assign a literal value to a field you can write something like this:

my.text = "twitter"
my.int = 123
my.float = 123.4
my.bool = true

Coalesce

The Coalesce expression is a pair of rounded brackets, and expressions with bars between them, it evaluates as the first expression which wasn’t null.

my.sender = email.(from | sender).name    # this will attempt to use email.from, and otherwise use email.sender

If Expressions

These are used to branch on a predicate.

result = if a > b then c else d      # if a is bigger than b, set result to c, otherwise set it to d
result1 = if foo exists then bar     # if the variable foo exists, then set result1 to bar

Pattern Matching

This lets you inspect a value with a list of predicates and expressions, and run the first one which matches.

result = match value
| this < 5  => "less than 5"
| this > 5  => "more than 5"
| this == 5 => "five"
| _ => "fallthrough"

Of course it’s an expression so you can do something complex like this:

result = ("the answer is: " + match value
| this < 5 => "less than 5"
| this > 5 => "more than 5"
| this == 5 => "five"
).capitalize()

Relative Paths

These are used to evaluate a path from the input within the current context, they can start with this, or just be a variable path.

this      # the current scope
this.a    # traverse into a inside the current scope
a         # as above
this.a.b  # get the b field from the a object in the current scope
a.b       # as above
this.*    # get every object inside the current scope
*         # as above
this.*.b  # get every b field inside every object in the current scope
*.b       # as above

Absolute Paths

These have the prefix of root and also address the input object.

root.a    # get a from the root scope
root.a.b  # get b from a from the root scope

Variable Paths

These are used to address the output object and have the @ prefix

foo = "hello"
bar = @foo    # set the `bar` field to whatever we've already put in `foo`

Temporary Variable Paths

These are used to address temporary variables and have the $ prefix

let foo = "hello"
bar = $foo     # set the `bar` field to the expression stored in the temporary variable `foo`

Array Traversal

Array elements can be referred to with a numerical index.

The indexes begin at zero and a negative number will wrap around, so the first item can be retrieved with 0 and the final item can be retrieved with -1

first = a[0]
last = a[-1]

Array Slices

A range of array elements can be sliced with two numerical values.

In the below examples we demonstrate how you can select the first four items, select everything except the first item and select the second, third and fourth item.

first_four         = input[:3]
tail               = input[1:]
two_three_and_four = input[1:3]

Functions

Functions are either provided as a globally available function, or are available on certain data types as a suffix, see the next few sections for full listings.

my.id      = any.number.int().max(100)
my.string  = "%s %s".format(a, b).capitalize()

Filters

Filters allow you to filter an array or object using a predicate:

twitter.type        = "retweet" [ retweeted_status exists ]
interaction.content = status [ type == "status" ].message

See them as an if statement; nothing will happen if the expression evaluates to false.

These are evaluated inside a relative scope of the item being traversed over, setting this to the current item, and evaluating all paths inside it, which is useful for filtering arrays:

 people_in_paris = people[location == "Paris"].extract(name)

NB: the left-hand-side provides scope for the square brackets, so given this input:

{"a": "1", "b": "2", "c": {"d": "3", "e": "4"}}

this is the expected behaviour:

x1 = [b == "2"] "test"      # invalid, as the conditional is missing a LHS, so the predicate will be ignored and by pure luck (aka undefined behaviour) will generate this result: {"x1": "test"}
x2 = "test" [b == "2"]      # invalid, as the LHS is a literal value, without a child called "b"
x3 = "test" [root.b == "2"] # use keyword `root` to break out of the current scope; result: {"x3": "test"}
x4 = c [d == "3"].e         # result: {"x4": "4"}
x5 = c [this.d == "3"]      # use keyword `this` to reference object in scope; result: {"x5": {"d": "3", "e": "4"}}
x6 = c.e [root.a == "1"]    # use keyword `root` to break out of the current scope; result: {"x6": "4"}

Maths

The operators +, -, / and * are available for numeric operations, they take an expression as their left and right and resolve them.

a = 1 + 2
b = 2 / 3
c = d * e

See the Maths section for full documentation.