Reflect

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)?