Skip to main content

Grammar

  • NL is a line break. Consecutive line breaks, empty lines and lines holding only a comment count as one NL; line breaks inside parentheses are ignored.
  • /// comments do not appear in the grammar; they belong to the declaration right after them.
  • Name and name are the same lexical name; Name marks script, host type and state names (PascalCase). Keyword is any reserved word.
  • Rules such as what each side allows, where enter/exit/ready/migrate go and the single auto state are checked by the compiler, not by the grammar.
script = header { member } ;
header = side "script" Name "extends" Name NL
{ ( "label" String | "category" String | "version" Int ) NL } ;
side = "server" | "client" ;
member = group | property | memory | field | input | timer
| handler | function | state ;

group = "group" String NL { property } "end" NL ;
property = "property" name ":" type "=" expr [ String ] { option } NL ;
option = "range" expr "to" expr | "step" expr | "hidden" | "was" String ;
memory = "memory" name ":" type "=" expr [ String ] [ "was" String ] NL ;
field = ( "reads" | "writes" ) dotted ":" type NL ;
input = "input" name ":" type "=" expr [ String ] NL ;
timer = "timer" name ( "daily" "at" expr | "every" expr ) NL ;

state = [ "auto" ] "state" Name NL { handler } "end" NL ;
handler = "on" trigger NL block "end" NL ;
trigger = "ready" | "enter" | "exit" | "migrate" "(" param ")"
| "timer" name { "," name }
| dotted [ "(" [ binding { "," binding } ] ")" ] ;
binding = name [ ":" type ] ;
function = "function" name "(" [ param { "," param } ] ")" [ ":" type ] NL
block "end" NL ;
param = name ":" type ;

block = { statement } ;
statement = ( "let" | "var" ) name [ ":" type ] "=" expr NL
| place assign expr NL
| "if" expr "then" simple NL
| "if" expr NL block { "elseif" expr NL block } [ "else" NL block ] "end" NL
| "while" expr NL block "end" NL
| "for" name "in" expr "to" expr NL block "end" NL
| simple NL ;
simple = "goto" Name
| "start" "timer" name ( "after" expr | "at" expr )
| "stop" "timer" name
| "break" | "continue" | "return" [ expr ]
| postfix ; (* must be a call *)
place = name | dotted ;
assign = "=" | "+=" | "-=" | "*=" | "/=" ;

expr = "if" expr "then" expr "else" expr | or ;
or = and { "or" and } ;
and = not { "and" not } ;
not = "not" not | comparison ;
comparison = sum [ ( "==" | "!=" | "<" | "<=" | ">" | ">=" ) sum ] ;
sum = product { ( "+" | "-" ) product } ;
product = unary { ( "*" | "/" | "%" ) unary } ;
unary = "-" unary | cast ;
cast = postfix [ "as" type ] ;
postfix = primary { "." member | "(" [ expr { "," expr } ] ")" } ;
primary = literal | name | "(" expr ")" ;
dotted = name "." member { "." member } ;
member = name | Keyword ; (* reserved words are allowed after a dot *)
type = "bool" | "int" | "float" | "string" | "time" | "duration" | "color" ;
literal = Int | Float | String | Time | Duration | Color | "true" | "false" ;