Syntax

Syntax

This page defines the formal grammar of Zirric. It covers every syntactic form the parser accepts. For the meaning of each construct, see the corresponding semantic pages: Declarations, Expressions, and Type System.

The grammar is written in EBNF notation. Terminal symbols are quoted. {x} means zero or more repetitions of x, and [x] means x is optional.

Tokens

Identifiers

An identifier starts with a letter or underscore and continues with letters, digits, or underscores. Keywords take precedence over identifiers.

IDENT = (letter | "_"), {letter | digit | "_"};

Keywords

The following words are reserved and cannot be used as identifiers:

mod    import   fn      const   var
data   union    extern  type    attr
if     else     for     switch  case
is     return   break   continue
true   false    _

Literal Tokens

INT    = "0" | ("1"..."9"), {digit}
       | "0x", hex_digit, {hex_digit}
       | "0o", octal_digit, {octal_digit}
       | "0b", ("0" | "1"), {("0" | "1")};

FLOAT  = digits, ".", digits, [("e" | "E"), ["-"], digits]
       | digits, ("e" | "E"), ["-"], digits;

STRING = '"', {string_char}, '"';
CHAR   = "'", char_char, "'";

string_char = ? any character except '"' or backslash ? | escape | backslash, '"';
char_char   = ? any character except "'" or backslash ? | escape | backslash, "'";

escape = backslash, ( "a" | "b" | "f" | "n" | "r" | "t" | "v" | backslash
                    | "x", 2 * hex_digit
                    | "u", 4 * hex_digit
                    | "U", 8 * hex_digit
                    | 3 * octal_digit );

backslash = ? the "\" character ?;

BOOL   = "true" | "false";

String and char literals accept the same escapes, except that each escapes only its own delimiter: \" belongs to a string and \' to a char. \u and \U name a code point, which a STRING holds UTF-8 encoded, while \x and the three-digit octal form name a single byte. Any other escape is an error rather than literal text.

Operator and Punctuation Tokens

PLUS     = "+";     MINUS    = "-";     ASTERISK = "*";
SLASH    = "/";     PERCENT  = "%";     BANG     = "!";

EQ       = "==";    NEQ      = "!=";
LT       = "<";     GT       = ">";
LTE      = "<=";    GTE      = ">=";
AND      = "&&";    OR       = "||";

ASSIGN   = "=";     ARROW    = "->";    LARROW   = "<-";
COLON    = ":";     DOT      = ".";     COMMA    = ",";
AT       = "@";

QDOT     = "?.";    BANGDOT  = "!.";
QQ       = "??";    BANGBANG = "!!";
QUESTION = "?";

LPAREN   = "(";     RPAREN   = ")";
LBRACE   = "{";     RBRACE   = "}";
LBRACKET = "[";     RBRACKET = "]";

Comments

line_comment = ("//" | "#"), {any_char}, newline;

Comments run to the end of the line. Both markers are equivalent to the compiler; // is conventional, and # additionally allows a #! shebang on the first line of an executable script.

A comment immediately preceding a declaration documents it, by convention written with //. The compiler keeps that comment and hands it back through reflect.docs — see Declarations § Documentation comments.

There are no block comments.

Operator Precedence

Operators are listed from lowest to highest precedence.

Precedence Operators Associativity
Logical OR || Left
Logical AND && Left
Comparison ==, !=, <, <=, >, >=, is Type Left
Fallback ??, !! Right
Sum +, - Left
Product *, /, % Left
Prefix -x, !x Right
Call f(args) Left
Member expr.field, expr?.field, expr!.field Left

See Expressions § Operators for semantic details.

Source File Structure

A source file begins with an optional shebang line, then the module declaration, followed by top-level declarations.

SourceFile = [shebang], Module, {TopLevelDeclaration};

shebang = "#!", {any_inline_char}, newline;

Module     = "mod", [Identifier, "="], ModulePath;
ModulePath = Identifier, {".", Identifier};

Declarations

See Declarations for semantic rules.

Variable and Constant Declarations

Const = "const", Identifier, [":", TypeExpr], "=", Expression;
Var   = "var",   Identifier, [":", TypeExpr], "=", Expression;

Function Declaration

Function = "fn", Identifier, "(", [Parameters], ")", ["->", TypeExpr], "{", Block, "}";

Data Declaration

Data = "data", Identifier, ["{", {DataField}, "}"];

DataField = {Attribute}, Identifier, ([":", TypeExpr] | "(", [Parameters], ")", ["->", TypeExpr]), [","];

A data declaration without a body (no braces) declares a zero-field type. Fields are either simple values with an optional type annotation, or function-style fields with parameters and an optional return type.

Union Declaration

Union = "union", Identifier, "{", {UnionMember}, "}";

UnionMember = {Attribute}, (Data | StaticReference), [","];

Union members are either inline data declarations or references to existing types. See Type System § Union Types for membership semantics.

Attribute Declaration

AttributeDecl = "attr", Identifier, ["{", {DataField}, "}"];

Attribute declarations follow the same field syntax as data. See Declarations § Attribute Declarations for application rules.

Extern Declarations

ExternType  = "extern", "type", Identifier, ["{", {DataField}, "}"];
ExternFunc  = "extern", "fn",   Identifier, "(", [Parameters], ")", ["->", TypeExpr];
ExternConst = "extern", "const", Identifier, [":", TypeExpr];

Import Declaration

Import = "import", [Identifier, "="], ImportPath, ["{", {Identifier, [","]}, "}"];

ImportPath = Identifier, {".", Identifier};

Attribute Application

Attribute = "@", StaticReference, "(", [Arguments], ")";

Attributes precede the declaration or field they annotate. Parentheses are always required, even when empty. Multiple attributes can be stacked.

TopLevelDeclaration = Import
                    | {Attribute}, (Union | Data | ExternType | ExternFunc | ExternConst | AttributeDecl | Function | Const | Var)
                    | Expression;

Parameters

Parameters = Parameter, {",", Parameter};
Parameter  = {Attribute}, (Identifier | "_"), [":", TypeExpr];

Parameters appear in fn declarations, closures, extern fn, and function-style data fields. See Declarations § Parameters.

Type Expressions

Type expressions annotate declarations and parameters with type information. They appear after : on fields, parameters, and variables, and after -> on function return types.

TypeExpr      = TypeExprPrimary, {"?" | "!"};
TypeExprPrimary = TypeExprRef | TypeExprArray | TypeExprDict | TypeExprFunc | TypeExprAttrs;

TypeExprRef   = StaticReference;
TypeExprArray = "[", TypeExpr, "]";
TypeExprDict  = "[", TypeExpr, ":", TypeExpr, "]";
TypeExprFunc  = "fn", "(", [TypeExprParams], ")", ["->", TypeExpr];
TypeExprAttrs = "@", StaticReference, {"@", StaticReference};

TypeExprParams = TypeExprParam, {",", TypeExprParam};
TypeExprParam  = [{Attribute}, Identifier, ":"], TypeExpr;

A ? or ! suffix must sit directly on the type it qualifies, with no whitespace or comment between them. That is what keeps a return type from running on into the next line: after extern fn ready() -> Bool, a following statement that begins with ! is a statement of its own. See Type System § Optional and Result Shorthands.

See Type System § Type Hints for what type expressions express and how they interact with type checking.

Expressions

See Expressions for semantic details.

Literals

IntLiteral    = INT;
FloatLiteral  = FLOAT;
StringLiteral = STRING;
BoolLiteral   = "true" | "false";
ArrayLiteral  = "[", [Expression, {",", Expression}], "]";
DictLiteral   = "[", [DictEntry, {",", DictEntry}], "]";

DictEntry = Expression, ":", Expression;

Identifiers and References

Identifier      = IDENT;
StaticReference = Identifier, {".", Identifier};

A StaticReference is a dot-separated path used for qualified names in imports, attributes, and type hints.

Operators

InfixExpr    = Expression, operator, Expression;
PrefixExpr   = ("-" | "!"), Expression;
IsExpr       = Expression, "is", TypeExpr;
FallbackExpr = Expression, ("??" | "!!"), Expression;

A ! directly before an expression negates it; one directly after a type is the T! shorthand. See Type Expressions.

Calls and Member Access

CallExpr   = Expression, "(", [Arguments], ")";
MemberExpr = Expression, ("." | "?." | "!."), Identifier;
IndexExpr  = Expression, "[", Expression, "]";

Arguments = Expression, {",", Expression};

?. and !. read a field the same way . does, but only once the value they sit on turns out to be readable. See Expressions § Guarded Member Access.

Closures

Closure = "fn", "(", [Parameters], ")", ["->", TypeExpr], "{", Block, "}";

Closures are anonymous functions. They share syntax with fn declarations but omit the name. See Expressions § Closures.

Switch Expression

SwitchExpr = "switch", Expression, "{", {SwitchCase}, "}";
SwitchCase = "case", ("is", TypeExpr | "_" | Expression), ":", Expression;

Type matching requires the is keyword (case is Type:). Value matching uses a plain expression (case value:). The default case uses _. Switch expressions require a _ fallback case. See Expressions § Switch.

Assignment

Assignment      = Identifier, "=", Expression;
FieldAssignment = Expression, ".", Identifier, "=", Expression;
IndexAssignment = Expression, "[", Expression, "]", "=", Expression;

Only var bindings and mutable locations accept assignment. A guarded access (?. or !.) is not an assignment target, since it may produce no field to assign to. See Expressions § Assignment.

Statements

Statements appear inside blocks. Most expressions can also be used as statements.

Block = {Statement};

Statement = ScopeLevelDeclaration | Return | If | For | SwitchStmt | Expression;

ScopeLevelDeclaration = Function | Const | Var | Union | Data;

Return

Return = "return", [Expression];

If

If = "if", Expression, "{", Block, "}", {"else", "if", Expression, "{", Block, "}"}, ["else", "{", Block, "}"];

See Expressions § If for the distinction between if expressions and if statements.

For

For = "for", [ForBinding], "{", Block, "}";

ForBinding = Identifier, "<-", Expression    (* collection iteration *)
           | Expression;                      (* boolean or infinite loop *)

When the binding and expression are both omitted, the loop runs indefinitely. See Expressions § For.

Switch Statement

SwitchStmt     = "switch", Expression, "{", {StmtSwitchCase}, "}";
StmtSwitchCase = "case", ("is", TypeExpr | "_" | Expression), ":", Block;

Switch statements do not require a _ fallback.