Grammar Rules
The grammar aims to be context-free and LALR(1) parsable. This is to ensure that parsers can be fast and simple, as well as making the code easier to read by avoiding context-dependent ambiguities. Despite of this goal, the grammar does contain a few ambiguities in favor of a more concise inline function syntax and a more C-like pointer type declaration syntax. However, the parser can still be implemented as a simple recursive descent parser.
Definitions
Module:
ModuleStatement (ScopeAttributes | AttributedDeclDef)*
ModuleStatement:
Attributes 'module' QualifiedIdent ';'
AttributedDeclDef:
Attributes DeclDef
DeclDef:
'import' Import
'~' 'this' ModuleCtorDtor
'this' ModuleCtorDtor
'struct' StructDef
'class' ClassDef
'interface' InterfaceDef
'enum' EnumDef
'mixin' '!' MixinInst
'mixin' MixinDef
'unittest' UnittestDef
'function' FunctionDef
'method' FunctionDef
'operator' OperatorDef
'alias' Alias
'var' (Property|Vars)
'let' Lets
'meta' 'if' GlobalMetaIf
'meta' 'for' GlobalMetaFor
Attributes:
Attribute*
Attribute:
Protection | '@' PrimaryExpr
Protection:
'public' | 'private' | 'module' | 'protected'
ScopeAttributes:
Attribute+ ':'
Import:
QualifiedIdent 'public'? (':' Ident (',' Ident)*)? ';'
ModuleCtorDtor:
'(' ')' (thread|shared)? BlockStmt
StructDef:
IdentExpr '(' MetaParameters ')' '{' DeclDef* '}'
ClassDef:
IdentExpr '(' MetaParameters ')' (':' TypeExprList)? '{' DeclDef* '}'
InterfaceDef:
IdentExpr '(' MetaParameters ')' (':' TypeExprList)? '{' DeclDef* '}'
EnumDef:
IdentExpr (':' TypeExpr)? '{' EnumMembers '}'
EnumMembers:
EnumMember (',' EnumMember)* ','?
MixinDef:
('(' MetaParameters ')')? '{' DeclDefOrStmt* '}'
UnittestDef:
BlockStmt
FunctionDef:
IdentExpr FunctionPrototype OptionalFunctionBody
OperatorDef:
'[' OperatorIdent ']' FunctionPrototype OptionalFunctionBody
OperatorIdent:
'++' | '--' | '+' | '-' | '*' | '/' | '%' | '^^' | '&' | '|' | '^' | '~'
'<<' | '>>' | '=' | '+=' | '-=' | '*=' | '/=' | '%=' | '^^=' | '&='
'|=' | '^=' | '~=' | '<<=' | '>>=' | '==' | '<>' |
'call', 'index', 'copy' | 'move', 'cast', 'implicit_cast'
FunctionPrototype:
'(' Parameters ')' ('ref' | TypeQualifier)* (':' TypeExpr)? ('if' '(' Expr ')')?
OptionalFunctionBody:
';' | FunctionBody
FunctionBody:
'=>' Expr ';'
BlockStmt
Parameters:
(Parameter (',' Parameter)*)?
Parameter:
(meta|ref|out)? IdentExpr (':' TypeExpr)? ('=' Expr)?
MetaParameters:
(MetaParameter (',' MetaParameter)*)?
MetaParameter:
IdentExpr '...'? (':' TypeExpr)? ('=' Expr)?
QualifiedIdent:
Ident ('.' Ident)*
Alias:
IdentExpr '=' Expr ';'
Property:
IdentExpr (':' TypeExpr)? '{' PropertyAccessor* '}'
IdentExpr (':' TypeExpr)? => Expr ';'
PropertyAccessor:
'get' FunctionBody
'set' '(' Ident ')' FunctionBody
Vars:
Var (',' Var)* ';'
Var:
IdentExpr (':' TypeExpr)? ('=' Expr)?
Lets:
Let (',' Let)*
Let:
IdentExpr (':' TypeExpr)? '=' Expr ';'
GlobalMetaIf:
'(' Expr ')' DeclDefBlock ('else' DeclDefBlock)?
GlobalMetaFor:
'(' Ident (',' Ident)? ';' Expr ')' DeclDefBlock
DeclDefBlock:
AttributedDeclDef
'{' AttributedDeclDef* '}'
Statements
DeclDefOrStmt:
ScopeAttributes | Attributes (DeclDef | Stmt)
Stmt:
BlockStmt
'loop' Stmt
'for' ForStmt
'if' IfStmt
'switch' SwitchStmt
'case' CastStmt
'default' DefaultCaseStmt
'return' ReturnStmt
'break' ';'
'continue' ';'
Expr ';'
BlockStmt:
'{' DeclDefOrStmt* '}'
ForStmt:
'(' Vars Expr (';' ExprList ')' Attributes Stmt
IfStmt:
'(' Expr ')' Attributes Stmt ('else' Attributes Stmt)?
SwitchStmt:
'(' Expr ')' Attributes Stmt
CaseStmt:
ExprList ':' Attributes Stmt
DefaultCaseStmt:
':' Attributes Stmt
ReturnStmt:
Expr? ';'
Expressions
ExprList:
(Expr (',' Expr)*)?
Expr:
RangeExpr (AssignmentOp RangeExpr)*
TypeExpr:
OrExpr[true]
TypeExprList:
TypeExpr (',' TypeExpr)*
AssignmentOp:
'=' | '+=' | '-=' | '*=' | '/=' | '%=' | '&=' | '|=' | '^=' | '~=' | '^^='
RangeExpr:
ConditionalExpr ('..' ConditionalExpr)?
ConditionalExpr:
OrOrExpr ('?' Expr? ':' ConditionalExpr)?
OrOrExpr:
AndAndExpr ('||' AndAndExpr)*
AndAndExpr:
OrExpr[false] ('&&' OrExpr[false])*
OrExpr[nofunc]:
AndExpr[nofunc] ('|' AndExpr[nofunc])*
AndExpr[nofunc]:
CmpExpr[nofunc] ('&' CmpExpr[nofunc])*
CmpExpr[nofunc]:
ShiftExpr[nofunc] (CmpOp ShiftExpr[nofunc])?
CmpOp:
'<' | '<=' | '==' | '!=' | '>=' | '>' | 'is' | '!is'
ShiftExpr[nofunc]:
AddExpr[nofunc] (ShiftOp AddExpr[nofunc])*
ShiftOp:
'<<' | '>>'
AddExpr[nofunc]:
MulExpr[nofunc] (AddOp MulExpr[nofunc])*
AddOp:
'+' | '-' | '~'
MulExpr[nofunc]:
UnaryExpr[nofunc] (MulOp UnaryExpr[nofunc])*
MulOp:
'*' | '/' | '%'
UnaryExpr[nofunc]:
UnaryOp* PowExpr[nofunc]
UnaryOp:
'&' | '*' | '++' | '--' | '+' | '-' | '!' | '~'
'cast' '(' TypeExpr ')'
PowExpr[nofunc]:
PostfixExpr[nofunc] ('^^' UnaryExpr[nofunc])*
PostfixExpr[nofunc]:
PrimaryExpr[nofunc] PostfixOp*
PostfixOp[nofunc]:
'++' | '--'
'(' NamedArgumentList ')'
'[' ExprList ']'
'!'
'.' Ident
'*'
PrimaryExpr[nofunc]:
'(' Expr ')'
'(' NamedArgumentList ')'
'(' Parameters ')' FunctionBody ; only if nofunc = false
'.' IdentExpr
'[' ExprList ']'
IdentExpr
Ident '=>' Expr ; only if nofunc = false
BuiltinConstant
'import' '(' Expr ')'
'typeof' '(' Expr ')'
'function' FunctionPrototype
Type
NamedArgumentList:
(NamedArgument (',' NamedArgument)*)?
NamedArgument:
IdentExpr ':' Expr
Expr
BuiltinConstant:
'this' | 'super' | 'null' | 'true' | 'false'
IdentExpr:
'#' PrimaryExpr[true]
Ident
Types
Type:
BasicType
QualifiedType
BasicType:
'noreturn' | 'type' | 'void' | 'bool' | 'byte' | 'ubyte'
'short' | 'ushort' | 'int' | 'uint' | 'long' | 'ulong'
'__size' | '__sizediff' | 'float' | 'double' | 'real'
'char' | 'wchar' | 'dchar' | 'const'
QualifiedType:
TypeQualifier '(' TypeExpr ')'
TypeQualifier+ TypeExpr
TypeQualifier:
'immutable' | 'escape' | 'synchronized' | 'shared' | 'isolated'
Ambiguities
The following ambiguities in the grammar need to be resolved manually by the parser:
Nested if/else
When encountering a nested if/else directly inside of an if branch, the
parser reports an error that advises to enclose the nested if inside braces.
if (cond1)
if (cond2)
stmt1;
else // Error: Dangling `else`, use `{}` to clarify.
stmt2;
Nested Lambda Function Syntax
Nesting lambda expressions or function declaration shortcut syntaxes results
in a parsing ambiguity. To resolve this, there are two kinds of expression
grammar rule sets - one with nofunc = false and one with nofunc = true.
Only the nofunc = false primary expression matches the possible inline
function expressions. This effectively results in a bare inline function
expression not being allowed where a TypeExpr is expected and means that the
first '=>' encountered is always associated with the preceding function
declaration instead.
// parsed as "bar" return type and "baz" body expression
function foo(): bar => baz;
// parsed as "bar | bar" return type and "baz" body expression
function foo(): bar | bar => baz;
// parsed as "bar => baz" return type and "bam" body expression
function foo(): (bar => baz) => bam;
// parsed as "bar" return type and "{ return 12; }" body
function foo(): (bar) { return 12; }
* as Pre-, Post- and Infix Operator
Since the * token can occur in all three places, the basic implied precedence
order of postfix > prefix > infix is not sufficient for disambiguating between
a postfix and an infix expression. To resolve the ambiguity, when matching a
* in a postfix position, it may be counted only as a postfix operator if
there is another PostfixOp folowing or if no token follows that leads to an
unary expression. The candidate tokens are UnaryOp, Ident, '(', '[', '#',
BuiltinConstant, 'import', 'typeof', 'function', BasicType and
TypeQualifier.
Parameter Lists, Argument Lists, Clamped Expression, Tuple Expression
In order to distinguish between '(' Parameters | Expr | NamedArgumentList ')',
they may be instead parsed with a combined ParametersOrNamedArgument rule. The
type of list can then be determined retrospecticely by looking at which parts
of the rule have been matched.
Parameters:
ParameterOrNamedArgumentList
NamedArgumentList:
ParametersOrNamedArgumentList
ParametersOrNamedArgumentList:
ParameterOrNamedArgument (',' ParameterOrNamedArgument)* ','?
ParameterOrNamedArgument:
'...'
Attributes ('ref' | 'out' | 'meta')? RangeExpr '...'? (':' RangeExpr)? ('=' Expr)?