Skip to content

Grammar

This annex uses ABNF (RFC 5234). WSP, DIGIT, HEXDIG, and DQUOTE are the core rules of that document.

interpolation = "{{" *WSP expression *WSP "}}"

The typing rules state how a scalar that holds interpolations yields a value.

expression = conditional
conditional = or-expr [ 1*WSP "if" 1*WSP or-expr 1*WSP "else" 1*WSP conditional ]
or-expr = and-expr *( 1*WSP "or" 1*WSP and-expr )
and-expr = equality *( 1*WSP "and" 1*WSP equality )
equality = relational [ ( *WSP eq-op *WSP / 1*WSP "in" 1*WSP ) relational ]
eq-op = "==" / "!="
relational = additive [ *WSP rel-op *WSP additive ]
rel-op = "<=" / ">=" / "<" / ">"
additive = multiplicative *( *WSP add-op *WSP multiplicative )
add-op = "+" / "-"
multiplicative = unary *( *WSP mul-op *WSP unary )
mul-op = "*" / "/" / "%"
unary = ( "not" 1*WSP unary ) / ( "-" unary ) / postfix
postfix = primary *( member / index / filter )
member = "." name
index = "[" *WSP expression *WSP "]"
filter = *WSP "|" *WSP filter-name [ "(" *WSP [ arg-list ] *WSP ")" ]
filter-name = name [ "." name ]
arg-list = expression *( *WSP "," *WSP expression )
primary = literal / name / list-lit / map-lit / group
group = "(" *WSP expression *WSP ")"
list-lit = "[" *WSP [ expression *( *WSP "," *WSP expression ) ] *WSP "]"
map-lit = "{" *WSP [ map-entry *( *WSP "," *WSP map-entry ) ] *WSP "}"
map-entry = ( string-lit / name ) *WSP ":" *WSP expression

equality and relational are non-associative: a < b < c is a syntax error, and parentheses express the intended grouping.

A filter-name with one component names a core filter. A filter-name with two components names an extension filter.

literal = string-lit / duration-lit / number-lit / bool-lit / "null"
bool-lit = "true" / "false"
name = %x61-7A *( %x61-7A / DIGIT / "_" )
string-lit = DQUOTE *dq-char DQUOTE / "'" *sq-char "'"
dq-char = %x20-21 / %x23-5B / %x5D-10FFFF / escape
sq-char = %x20-26 / %x28-5B / %x5D-10FFFF / escape
escape = "\" ( "\" / DQUOTE / "'" / "n" / "r" / "t" / unicode-esc )
unicode-esc = "u{" 1*6HEXDIG "}"
number-lit = float-lit / int-lit
int-lit = 1*DIGIT
float-lit = 1*DIGIT "." 1*DIGIT [ exponent ] / 1*DIGIT exponent
exponent = ( "e" / "E" ) [ "+" / "-" ] 1*DIGIT

A term is matched by the longest alternative that succeeds. 1 is an int-lit, and 1d is a duration-lit.

A negative number is the unary - operator applied to a literal. There is no negative literal.

duration-lit = 1*duration-part
duration-part = 1*DIGIT duration-unit
duration-unit = "ms" / "s" / "m" / "h" / "d"

Units MUST appear in strictly descending order of magnitude, and each unit MUST appear at most once. 1h30m and 500ms are valid. 30m1h and 1h1h are not.

A day is exactly 86400000 milliseconds, and has no calendar or daylight-saving meaning. A duration literal is never negative.

A timestamp is written as an RFC 3339 date-time with a mandatory time offset. A second value of 60 MUST be rejected.

There is no timestamp term in the expression grammar. Where a value of type timestamp is expected, implementations MUST accept a string in this form and convert it.

type-expr = "string" / "int" / "float" / "bool" / "timestamp" / "duration"
/ "file" / "json"
/ enum-type / list-type / map-type
enum-type = "enum[" member *( "," *WSP member ) "]"
member = 1*( %x61-7A / DIGIT / "_" )
list-type = "list[" type-expr "]"
map-type = "map[string," *WSP type-expr "]"

An object type is expressed as JSON Schema rather than as a type expression. The serialization rules require that a type expression is always quoted.