The template language
Grammar
The whole syntax, in EBNF.
This is the whole syntax of a template, in EBNF. { x } repeats x zero or more times, [ x ] makes it optional, ( a | b ) is a choice, and quoted text is literal. The lexical structure defines name, number and string.
Templates
template = { declaration | node } ;Declarations and nodes can be mixed at the top level, though declarations conventionally come first. Only the top level holds declarations.
Declarations
See Declarations and Data and effects.
declaration = state | let | on | load | timer ;
state = "state" name "=" expr ;
let = "let" name "=" expr ;
on = "on" name [ "(" [ name { "," name } ] ")" ] "=" steps ;
load = "load" name "=" request ;
timer = ( "every" ( "frame" | expr ) | "after" expr )
[ "when" expr ] "=" steps ;
request = "get" "(" expr [ "," expr ] ")"
| "post" "(" expr "," expr [ "," expr ] ")" ;
steps = step { "," step } ;
step = name "<-" expr | command ;
command = "emit" "(" expr [ "," expr ] ")"
| "navigate" "(" expr ")"
| ( "get" | "delete" ) "(" expr [ "," name [ "," name ] ] ")"
| ( "post" | "put" | "patch" ) "(" expr "," expr [ "," name [ "," name ] ] ")"
| "reload" "(" name ")" ;Nodes
See Nodes and Attributes and values.
node = element | string | if | for ;
element = name [ "(" [ attribute { [ "," ] attribute } [ "," ] ] ")" ]
[ string | block ] ;
block = "{" { node } "}" ;
attribute = name [ ":" expr ]
| "@" name ":" name [ "(" [ expr { "," expr } [ "," ] ] ")" ] ;
if = "if" expr block [ "else" ( if | block ) ] ;
for = "for" name "in" expr block ;- An element's
stringmust start on the element's line (the line rule). - An element has text or children, not both.
- In an
iforforhead, a{starts the block, so a record literal there needs parentheses.
Expressions
See Expressions. Each level binds tighter than the one above it, and every binary operator groups from the left: a - b - c is (a - b) - c.
expr = conditional ;
conditional = "if" expr "then" expr "else" expr | coalesce ;
coalesce = or { "??" or } ;
or = and { "||" and } ;
and = compare { "&&" compare } ;
compare = pipe { ( "==" | "!=" | "<" | "<=" | ">" | ">=" ) pipe } ;
pipe = additive { "|>" call } ;
additive = term { ( "+" | "-" ) term } ;
term = unary { ( "*" | "/" | "%" ) unary } ;
unary = ( "!" | "-" ) unary | postfix ;
postfix = primary { "." name } ;
primary = number | string | "true" | "false" | "null"
| name (* a path: post.title *)
| call | list | record | update
| conditional
| "(" expr ")" ;
call = name "(" [ argument { "," argument } [ "," ] ] ")" ;
argument = expr | lambda ;
lambda = name "->" expr
| "(" [ name { "," name } ] ")" "->" expr ;
list = "[" [ expr { "," expr } [ "," ] ] "]" ;
record = "{" [ field { "," field } [ "," ] ] "}" ;
field = name [ ":" expr ] ; (* { title } is { title: title } *)
update = "{" expr "|" field { "," field } [ "," ] "}" ;.nameafter aprimaryis a field. A path's dots are part of its name.- A lambda is only allowed as a call's argument.
- An
ifexpression extends as far right as it can:1 + if c then 1 else 2 + 3adds2 + 3in itselse. !a == bis rejected: writea != b, or(!a) == b.