Regexp expressions are used in this document to describe specific formats.
\d is used for decimal digits and is equivalent to [0-9].
When describing definitions, other definitions are referenced like {definition-name} and expressions which must result in a specific type are referred like {expression -> type}.
This form is not part of the language but is only used for this documentation.
A program is built by one or more expressions, separated by the pipe operator.
Each expression takes multiple inputs, processes them one by one, and can also generate any number of outputs afterwards. All outputs are concatenated and passed into the next expression. Expressions that reference variables also run for each single value of each variable. Array constructors can be used to combine multiple outputs into a single array.
JPL expects all expressions (including (native) function calls) to be idempotent, meaning that an expression should always produce the same output when run in the same environment (input, variables). This allows subsequent calls to be cached which has large impact on the overall performance.
Indices are zero based, meaning that the first item in an array has index 0, the second item has index 1, and so on.
. | .: {expression} | {expression}The pipe operator separates two expressions. Each output from the expression on the left side is fed into the expression on the right side.
Variables are referenced by variable identifiers. A variable can be one of the following:
. (identity selector)var1_name: [a-zA-Z_$][a-zA-Z_$\d]* (variable selector)
true, false, null can not be used as variable names.Variables can be defined by using the following syntax:
var1_name = .: {variable-selector} = {expression}
The identity selector references the input of the current expression.
.|([a-zA-Z_$][a-zA-Z_$\d]*)A special form is the identity selector ., where duplicate . are condensed into a single ., e.g. when using a field access operator: .a, which accesses field a from the identity variable, instead of ..a
\d, as this would collide with the number syntax.var1_name.some_field: {expression -> object}.{field-name}var1_name["special% field"]: {expression -> object}[{expression -> string}] (generic index)
[a-zA-Z_$][a-zA-Z_$\d]*. The generic index form is to be used for complex or dynamic field names.var1_name[1]: {expression -> array}[{expression -> number}] (generic index)
var1_name[1:2]: {expression -> array|string}[{expression -> number}:{expression -> number}]
.[10:15] will be of length 5, containing the elements from index 10 (inclusive) to index 15 (exclusive). Either index may be negative (in which case it counts backwards from the end of the array) or omitted (in which case it refers to the start or end of the array). Omitting an index is equivalent to setting it to null.var1_name[]: {expression -> array|object}[]
var1_name.some_field?: {object-field-access}?var1_name[1]?: {generic-field-access}?
., ., {expression}, {expression}
(.): ({expression})
Used for logically grouping expressions together.
"some text with \"quotes\"", 'some text with "quotes"'" or ' (single quote) as boundaries\ (backslash) as escape sequence, e.g. \' => ', \\ => \ (see String escape sequences)>= 0x20, excluding CR, LF and TAB (among others). Escape sequences can be used as a replacement.`a string which can contain
literal newlines and tabs`
` (backtick) as boundariesCR, LF and TAB.In multiline strings, newline symbols (CR, LF or CRLF) can be escaped to ignore them.
`a multiline string \
without newline`
"this \(.) is interpolated": "\({expression})"
The following sequences are valid inside string literals:
| HEX Code | Escape sequence | Char |
|---|---|---|
| 0x08 | \b |
BS (backspace) |
| 0x09 | \t |
TAB |
| 0x0a | \n |
LF (line feed) |
| 0x0c | \f |
FF (form feed) |
| 0x0d | \r |
CR (carriage return) |
| 0x22 | \" |
" |
| 0x27 | \' |
' |
| 0x2f | \/ |
/ (forward slash) |
| 0x5c | \\ |
\ (backslash) |
| 0x60 | \` |
` (backtick) |
| 0x00 - 0xFFFF | \u<hex><hex><hex><hex> |
Unicode sequence, e.g. \u0040 equals @ |
\(expression) |
See String interpolation |
-3.7: -?\d+(\.\d*)?([eE][-+]?\d+)?truefalsenull{ key: . }: { {field-name}: {expression}, ... }{ key }: { {field-name}, ... }{ key? }: { {field-name}?, ... }{ "key": . }: { {string}: {expression}, ... }{ (.key): . }: { ({expression}): {expression}, ... }{ (.key)?: . }: { ({expression})?: {expression}, ... }
? can be appended to omit errors that occur if the variable does not exist.? can be appended to omit errors that occur when the result of the expression is not a string.[ . ]: [ {expression} ]
1 + 2: {expression} + {expression}
Objects are added by merging, that is, inserting all key-value pairs from both objects into a single combined object. If both objects contain a value for the same key, the object on the right wins. (For recursive merge use the * operator)
null can be added to any value, and returns the other value unchanged.1 - 2: {expression} - {expression}
1 * 2: {expression} * {expression}1 / 2: {expression} / {expression}1 % 2: {expression} % {expression}
"x" * 0 produces null.==, !=
== returns true, if both values are equal in terms of type and value (like JavaScript’s === comparator).!= is “not equal” and returns the opposite of ==.. ?? true: {expression} ?? {expression}
Returns the first value if it is not null, otherwise the second value.
.a.b = 1: {expression}.path = {expression} (set).a.b |= . + 1: {expression}.path |= {expression} (update).a.b += 1: {expression}.path += {expression}.a.b -= 1: {expression}.path -= {expression}.a.b *= 1: {expression}.path *= {expression}.a.b /= 1: {expression}.path /= {expression}.a.b %= 1: {expression}.path %= {expression}.a.b ?= 1: {expression}.path ?= {expression} (null coalescence).a[1:2][] |= . * .
|= differs from the set operator (=) in that the value that is to be updated is used as the right operands input. This means, that for instance {a:1,b:3} | .a = .b produces {a:3,b:3}, whereas {a:1,b:3} | .a |= .b produces an error, because the program attempts to access .b from the value of a, which is a number.(.a.b).c.d = 1 can be used to extract the value at .a.b and then run an assignment .c.d = 1 on the result. This is equivalent to .a.b | .c.d = 1, but allows for accessing the whole input in the right operand, like e.g. (.a.b).c.d = .a.a.b = 1: {variable-selector}.path = {expression}
a.b = 1 is a shorthand for a = (a).b = 1if A then B endif A then B else C endif A then B elif C then D else E end
B if A produces a value other than false or null, but act like C otherwise.if A then B end is the same as if A then B else . end.A produces multiple results, then B is evaluated once for each result that is not false or null, and C is evaluated once for each false or null.>, >=, <=, <
nullfalsetrueobjects
and, or, not
false and null are considered as “false values”, anything else is a “true value”)try .test: try {expression}try .test catch . : try {expression} catch {expression}
catch is omitted, no outputs are returned if an error is caughttrue + 1 ?: {expression}?
The ? operator is shorthand for try {expression}
func (a, b): a + b: func (arg1, arg2, ...): {expression} (anonymous function definition)func add(a, b): a + b: {variable-selector} = func {variable-selector}(arg1, arg2, ...): {expression} (named function definition)add = func (a, b): a + b: {variable-selector} = func (arg1, arg2, ...): {expression}
add(1, 2): {variable-selector}({expression}, {expression})add->(input, 1, 2): {variable-selector}->({expression}, {expression}, {expression}) (bound function call)
(func (x): x + 1)(1)->, the leftmost argument is bound to the function input, and the remaining arguments become the function’s arguments. The following example returns true: func plus(x): . + x | (1 | plus(2)) == plus->(1, 2)/* comment */# line comment