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}, '"';
BOOL = "true" | "false";
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 = "@";
LPAREN = "("; RPAREN = ")";
LBRACE = "{"; RBRACE = "}";
LBRACKET = "["; RBRACKET = "]";
Comments
line_comment = "//", {any_char}, newline;
block_comment = "/*", {any_char}, "*/";
Block comments may nest.
Operator Precedence
Operators are listed from lowest to highest precedence.
| Precedence | Operators | Associativity |
|---|---|---|
| Logical OR | || |
Left |
| Logical AND | && |
Left |
| Comparison | ==, !=, <, <=, >, >=, is Type |
Left |
| Sum | +, - |
Left |
| Product | *, /, % |
Left |
| Prefix | -x, !x |
Right |
| Call | f(args) |
Left |
| Member | expr.field |
Left |
See Expressions § Operators for semantic details.
Source File Structure
A source file begins with an optional shebang line and module declaration, followed by top-level declarations.
SourceFile = [shebang], [Module], {TopLevelDeclaration};
shebang = "#!", {any_inline_char}, newline;
Module = "mod", 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 = 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;
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;
Calls and Member Access
CallExpr = Expression, "(", [Arguments], ")";
MemberExpr = Expression, ".", Identifier;
IndexExpr = Expression, "[", Expression, "]";
Arguments = Expression, {",", Expression};
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. 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.