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.
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.
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.