Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Appendix D: Grammar Guide

This guide shows the main source forms. It does not replace the parser. The chapters linked from each section define the checks and runtime rules.

The notation uses:

  • | for a choice;
  • ? for an optional part;
  • * for zero or more parts;
  • + for one or more parts;
  • quoted text for source tokens.
attributes      := attribute-list+
attribute-list  := "#[" attribute ("," attribute)* ","? "]"
file-attribute-list
                := "#![" attribute ("," attribute)* ","? "]"
attribute       := qualified-name call-arguments?

qualified-name  := "\\"? identifier ("\\" identifier)*
variable        := "$" identifier
literal         := "null" | "true" | "false"
                 | integer-literal | float-literal | string-literal
signed-integer-literal
                := "-"? integer-literal

The lexical chapter defines identifier and literal bytes. The bare identifier The grammar reserves _ even where this guide says identifier.

Source file

source-file     := shebang? source-item*

source-item     := file-attribute-list
                 | namespace-declaration
                 | use-declaration
                 | function-declaration
                 | class-declaration
                 | interface-declaration
                 | enum-declaration
                 | type-alias
                 | newtype-declaration
                 | constant-declaration
                 | statement

A file may mix declarations and statements. A namespace declaration can apply to the rest of the file or hold a braced source body. Declarations, imports, and file attributes belong at the top level of a file or namespace.

namespace-declaration
                := "namespace" qualified-name ";"
                 | "namespace" qualified-name "{" source-item* "}"

use-declaration := "use" use-items ";"
use-items       := use-item ("," use-item)*
                 | qualified-name "\\" "{" use-item
                   ("," use-item)* ","? "}"
use-item        := qualified-name ("as" identifier)?

See Namespaces and Imports and Loading Files.

Functions and parameters

function-declaration
                := attributes? "function" function-name type-parameters?
                   parameter-list return-type? where-clause? block

parameter-list  := "(" (parameter ("," parameter)* ","?)? ")"

parameter       := attributes? parameter-modifier* type? variable
                   ("=" expression)?
parameter-modifier
                := visibility | "readonly"

return-type     := ":" type

type-parameters := "<" type-parameter ("," type-parameter)* ","? ">"

type-parameter  := variance? identifier bounds? default-type?
variance        := "in" | "out"
bounds          := ":" type ("+" type)*
default-type    := "=" type

A closure uses fn and captures outer variables by value. Its body is one expression or a block.

closure         := attributes? "fn" type-parameters? parameter-list
                   return-type? where-clause? closure-body

closure-body    := "=>" expression | block

Visibility and readonly on a parameter promote it to a property and are valid only in a class constructor.

See Functions and Closures.

Classes

class-declaration
                := attributes? class-modifier* "class" identifier
                   type-parameters? class-parent? class-interfaces?
                   sealed-family? class-body

class-modifier  := "abstract" | "final" | "readonly"
class-parent    := "extends" named-type
class-interfaces
                := "implements" named-type ("," named-type)*
sealed-family   := "for" named-type ("," named-type)*

class-body      := "{" class-member* "}"
class-member    := property | class-constant | method

Properties include $ in their names. Methods and class constants do not.

property        := attributes? property-modifier+ type?
                   variable ("=" expression)? ";"

property-modifier
                := visibility | "static" | "readonly"

class-constant  := attributes? constant-modifier+ "const" type? identifier
                   "=" expression ";"
constant-modifier
                := visibility | "final"

method          := attributes? method-modifier+ "function"
                   member-name type-parameters? parameter-list
                   return-type? where-clause? (block | ";")

where-clause    := "where" where-constraint ("," where-constraint)* ","?
where-constraint
                := identifier ":" type

method-modifier := visibility | "abstract" | "final" | "static"
visibility      := "public" | "protected" | "private"

A constructor parameter with a visibility word declares a promoted property. readonly may appear before or after the visibility word. A property, constant, or method has exactly one visibility word. The other modifiers may appear in any order, but each may appear only once.

A callable’s where clause requires each named type parameter to fit its bound.

See Classes and Properties and Inheritance and Visibility.

Interfaces and enums

interface-declaration
                := attributes? "interface" identifier type-parameters?
                   interface-parents? sealed-family? interface-body

interface-parents
                := "extends" named-type ("," named-type)*

interface-body  := "{" interface-member* "}"
interface-member
                := property | class-constant | method

enum-declaration
                := attributes? "enum" identifier enum-backing?
                   enum-interfaces? "{" enum-member* "}"

enum-backing    := ":" type
enum-interfaces := "implements" named-type ("," named-type)*
enum-member     := enum-case | class-constant | method
enum-case       := attributes? "case" member-name
                   ("=" expression)? ";"

See Interfaces and Sealed Families and Enums.

Aliases, newtypes, and constants

type-alias      := attributes? "type" identifier type-parameters?
                   "=" type ";"

newtype-declaration
                := attributes? "newtype" identifier type-parameters?
                   "=" type ";"

constant-declaration
                := attributes? "const" identifier "=" expression ";"

See Aliases and Newtypes.

Statements

statement       := expression ";"
                 | if-statement
                 | while-statement
                 | do-while-statement
                 | for-statement
                 | foreach-statement
                 | try-statement
                 | using-statement
                 | final-local-statement

block           := "{" statement* "}"

if-statement    := "if" "(" expression ")" block
                   ("else" (if-statement | block))?

while-statement := "while" "(" expression ")" block

do-while-statement
                := "do" block "while" "(" expression ")" ";"

for-statement   := "for" "(" expression-list? ";"
                   expression-list? ";" expression-list? ")" block

foreach-statement
                := "foreach" "(" expression "as" foreach-target
                   ("=>" foreach-target)? ")" block

foreach-target  := assignment-target
assignment-target
                := variable | property-access | static-property-access
                 | array-index | array-append
                 | tuple-destructure | dict-destructure

tuple-destructure
                := "(" destructure-item ","
                   (destructure-item ("," destructure-item)*)? ","? ")"
destructure-item
                := assignment-target
                 | assignment-target "=" expression
                 | "..." assignment-target?
dict-destructure
                := "dict" "[" (expression "=>" assignment-target
                   ("," expression "=>" assignment-target)* ","?)? "]"

expression-list := expression ("," expression)*

final-local-statement
                := "final" variable "=" expression ";"

See Statements and Loops.

Error handling and cleanup

try-statement   := "try" block catch-clause* else-clause? finally-clause?

catch-clause    := "catch" "(" type variable? ")" guard? block
guard           := "if" "(" expression ")"
else-clause     := "else" block
finally-clause  := "finally" block

using-statement := "using" "(" using-binding
                   ("," using-binding)* ","? ")" block
using-binding   := bind-target "=" expression

A try statement needs at least one catch, else, or finally clause.

See Throwing and Catching and Resources and Cleanup.

Expressions

expression      := literal
                 | interpolated-string
                 | variable
                 | constant-name
                 | "(" expression ")"
                 | tuple-literal
                 | vec-literal
                 | vec-fill
                 | dict-literal
                 | closure
                 | match-expression
                 | "new" class-expression call-arguments?
                 | "break" integer-literal?
                 | "continue" integer-literal?
                 | "return" expression?
                 | "throw" expression
                 | unary-expression
                 | binary-expression
                 | conditional-expression
                 | assignment-expression
                 | call-expression
                 | partial-call
                 | member-expression
                 | index-expression
                 | construct-expression

conditional-expression
                := expression "?" expression ":" expression

class-expression is a named class, self, parent, static, a type parameter, or an expression that yields a class-name string. The operator appendix gives the precedence that turns the broad forms above into one tree.

Calls may use positional or named arguments. ? in an argument place creates a partial call. ... in a call place creates a first-class callable or leaves later partial-call parameters open.

call-arguments  := "(" (call-argument ("," call-argument)* ","?)? ")"
call-argument   := (parameter-name ":")? (expression | "?" | "...")

... must be the sole or final placeholder. Ordinary calls do not accept placeholders.

The sequence! construct accepts one or more expressions, with an optional trailing comma:

sequence-expression
                := "sequence" "!" "(" expression ("," expression)* ","? ")"

fresh-expression := "fresh" "!" "(" ")"

Both are construct expressions. See Language Constructs for evaluation and discard rules.

Collection literals use these forms:

tuple-literal   := "(" expression "," ")"
                 | "(" expression "," expression
                   ("," expression)* ","? ")"

vec-literal     := "vec" "[" (vec-item ("," vec-item)* ","?)? "]"
vec-item        := expression | "..." expression
vec-fill        := "vec" "[" expression ";" expression "]"

dict-literal    := "dict" "[" (dict-item ("," dict-item)* ","?)? "]"
dict-item       := expression "=>" expression | "..." expression

See Expressions, Operators and Arithmetic, and First-Class Callables and Partial Function Application.

Match patterns

match-expression
                := "match" "(" expression ")" "{"
                   match-arm ("," match-arm)* ","? "}"

match-arm       := pattern "=>" expression

pattern         := union-pattern
union-pattern   := as-pattern ("|" as-pattern)*
as-pattern      := intersection-pattern ("@" union-pattern)?
intersection-pattern
                := primary-pattern ("&" primary-pattern)*
primary-pattern := variable
                 | prefix-type
                 | "(" pattern ")"
                 | tuple-pattern
                 | vec-pattern
                 | dict-pattern
                 | object-pattern
                 | named-object-pattern

named-object-pattern
                := qualified-name ("<" type-list ">")? object-pattern

tuple-pattern   := "(" pattern ("," pattern)*
                   ("," trailing-pattern)? ","? ")"
vec-pattern     := "vec" "[" (pattern ("," pattern)*)?
                   ("," trailing-pattern)? ","? "]"
dict-pattern    := "dict" "[" (dict-pattern-entry
                   ("," dict-pattern-entry)*)?
                   ("," trailing-pattern)? ","? "]"
dict-pattern-entry
                := (string-literal | signed-integer-literal | "true" | "false")
                   "=>" pattern
object-pattern  := "#{" object-pattern-items? "}"
object-pattern-items
                := object-pattern-entry ("," object-pattern-entry)*
                   ("," "...")? ","? | "..." ","?
object-pattern-entry
                := identifier ":" pattern | variable
trailing-pattern
                := "..." pattern?

Variables bind. Types and literals check. @ requires both patterns to match the same value, as does &. Tuple, vec, dict, and object patterns may nest. Tuple, vec, and dict patterns may end with ... to accept, check, or bind the rest. Object patterns use a bare ... to permit additional public properties.

A pattern after a collection’s ... cannot contain bindings, except for a whole-remainder capture written as $rest or $rest @ pattern, where the pattern on the right contains no bindings. The compiler enforces this rule recursively; parentheses preserve the same meaning.

See Match and Destructuring.

Types

type            := union-type
union-type      := intersection-type ("|" intersection-type)*
intersection-type
                := prefix-type ("&" prefix-type)*
prefix-type     := "!" prefix-type
                 | "=" prefix-type
                 | primary-type

primary-type    := object-shape-type
                 | named-shape-type
                 | built-in-type
                 | named-type
                 | literal-type
                 | range-type
                 | string-length-type
                 | tuple-type
                 | vec-type
                 | dict-type
                 | array-type
                 | callable-type
                 | classname-type
                 | "(" type ")"

named-type      := qualified-name ("<" type-list ">")?
                   ("::" identifier ("<" type-list ">")?)?
                 | "self" ("::" identifier ("<" type-list ">")?)?
                 | "parent"
                 | "static"
named-shape-type := qualified-name ("<" type-list ">")? object-shape-type
vec-type        := "vec" ("<" type ">")? | "vec" "[" shape-items? "]"
dict-type       := "dict" ("<" type "," type ">")?
                 | "dict" "[" dict-shape-items? "]"
array-type      := "array" ("<" type "," type ">")?
callable-type   := "fn" | "fn" "(" callable-parameters? ")" ":" type
classname-type  := "classname" "<" type ">"

type-list       := type ("," type)* ","?
built-in-type   := "null" | "bool" | "int" | "uint" | "float" | "string"
                 | "fresh" | "object" | "mixed" | "never" | "void"
literal-type    := literal | "-" (integer-literal | float-literal)
range-type      := signed-integer-literal (".." | "..=")
                   signed-integer-literal?
                 | (".." | "..=") signed-integer-literal
string-length-type
                := "string" "[" (integer-literal | string-length-range) "]"
string-length-range
                := integer-literal (".." | "..=") integer-literal?
                 | (".." | "..=") integer-literal

tuple-type      := "(" type "," ")"
                 | "(" type "," tuple-type-tail ","? ")"
                 | "(" trailing-type ")"
tuple-type-tail := type ("," type)* ("," trailing-type)?
                 | trailing-type
trailing-type   := "..." type?

shape-items     := type ("," type)* ("," trailing-type)? ","?
dict-shape-items
                := dict-shape-entry ("," dict-shape-entry)*
                   ("," dict-shape-rest)? ","?
dict-shape-entry
                := (string-literal | integer-literal | "true" | "false") "=>" type
dict-shape-rest := "..." ("<" type "," type ">")?
object-shape-type
                := "#{" object-shape-items? "}"
object-shape-items
                := object-shape-entry ("," object-shape-entry)*
                   ("," "...")? ","? | "..." ","?
object-shape-entry
                := identifier ":" type

callable-parameters
                := callable-parameter ("," callable-parameter)* ","?
callable-parameter
                := "="? type

See Runtime Type Checks, Unions, Intersections, and Ranges, and Collection and Callable Types.