Skip to content
Grammar

Grammar

This is the complete syntax of qdx. Square brackets mean optional, braces mean repeated zero or more times, a bar separates alternatives, and text in quotes is written as shown.

Text

Source files are bytes. Spaces, tabs and line ends separate tokens and otherwise mean nothing, with one exception: a bytes literal cannot contain a line end.

letter  = "a" ... "z" | "A" ... "Z" | "_" ;
digit   = "0" ... "9" ;
name    = letter { letter | digit } ;
integer = digit { digit } ;
bytes   = '"' { any byte except '"', "\" and line end | escape } '"' ;
escape  = "\n" | "\t" | "\\" | '\"' | "\x" hex hex ;
hex     = digit | "A" ... "F" ;

A name may not be one of the reserved words. An integer may not be followed directly by a letter. Where two symbols could be read as one, the longer is taken, so <= is one symbol.

Declarations

program     = { function | record | union } ;
function    = "fn" name "(" [ typed-names ] ")" "->" type block ;
record      = "record" name "{" [ typed-names ] "}" ;
union       = "union" name "{" case { "," case } "}" ;
case        = name "(" [ typed-names ] ")" ;
typed-names = name ":" type { "," name ":" type } ;
type        = "int" | "bool" | "bytes" | "never" | name ;

Blocks

block     = "{" { statement } expression "}" ;
statement = "let" name [ ":" type ] "=" expression ";"
          | expression ";" ;

Expressions

expression     = or ;
or             = and { "or" and } ;
and            = not { "and" not } ;
not            = "not" not | comparison ;
comparison     = additive [ compare additive ] ;
compare        = "==" | "!=" | "<" | "<=" | ">" | ">=" ;
additive       = multiplicative { ( "+" | "-" ) multiplicative } ;
multiplicative = unary { ( "*" | "/" | "%" ) unary } ;
unary          = "-" unary | access ;
access         = primary { "." name } ;
primary        = integer | bytes | "true" | "false" | name
               | call | make | if | match | block
               | "(" expression ")" ;
call           = name "(" [ expression { "," expression } ] ")" ;
make           = name "(" name ":" expression { "," name ":" expression } ")" ;
if             = "if" expression block "else" ( if | block ) ;
match          = "match" expression "{" arm { "," arm } "}" ;
arm            = name "(" [ name { "," name } ] ")" "=>" expression ;

Notes

  • Lists never end with a comma.
  • call and make look alike when there is nothing between the parentheses. point() makes a record or union case if point is one, and calls a function otherwise.
  • A name used as a type must be a declared record or union. Type names are known across all files before any file is read, so a type can be used before its declaration.
  • The condition of an if and the value of a match are followed by {. Because making a record uses parentheses and not braces, there is no ambiguity.