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.