diff --git a/analysis/constructors.d b/analysis/constructors.d index 2d38e82..023d69b 100644 --- a/analysis/constructors.d +++ b/analysis/constructors.d @@ -30,7 +30,7 @@ class ConstructorCheck : BaseAnalyzer addErrorMessage(classDeclaration.name.line, classDeclaration.name.column, "This class has a zero-argument" ~ " constructor as well as a constructor with one default" - ~ " argument. This can be confusing"); + ~ " argument. This can be confusing."); } hasDefaultArgConstructor = oldHasDefault; hasNoArgConstructor = oldHasNoArg; @@ -91,7 +91,7 @@ private: unittest { assertAnalyzerWarnings(q{ - class Cat // [warn]: This class has a zero-argument constructor as well as a constructor with one default argument. This can be confusing + class Cat // [warn]: This class has a zero-argument constructor as well as a constructor with one default argument. This can be confusing. { this() {} this(string name = "kittie") {} diff --git a/analysis/del.d b/analysis/del.d index d6120f9..c564265 100644 --- a/analysis/del.d +++ b/analysis/del.d @@ -12,7 +12,7 @@ import analysis.base; import analysis.helpers; /** - * Checks for use of the deprecated "delete" keyword + * Checks for use of the deprecated 'delete' keyword */ class DeleteCheck : BaseAnalyzer { @@ -25,7 +25,7 @@ class DeleteCheck : BaseAnalyzer override void visit(const DeleteExpression d) { - addErrorMessage(d.line, d.column, "Avoid using the delete keyword"); + addErrorMessage(d.line, d.column, "Avoid using the 'delete' keyword."); d.accept(this); } } @@ -36,10 +36,10 @@ unittest void testDelete() { int[int] data = [1 : 2]; - delete data[1]; // [warn]: Avoid using the delete keyword + delete data[1]; // [warn]: Avoid using the 'delete' keyword. auto a = new Class(); - delete a; // [warn]: Avoid using the delete keyword + delete a; // [warn]: Avoid using the 'delete' keyword. } }c, analysis.run.AnalyzerCheck.delete_check); diff --git a/analysis/duplicate_attribute.d b/analysis/duplicate_attribute.d index c36041e..d958221 100644 --- a/analysis/duplicate_attribute.d +++ b/analysis/duplicate_attribute.d @@ -90,7 +90,7 @@ class DuplicateAttributeCheck : BaseAnalyzer // Already has that attribute if (hasAttribute) { - string message = "The attribute '%s' is duplicated.".format(attributeName); + string message = "Attribute '%s' is duplicated.".format(attributeName); addErrorMessage(line, column, message); } @@ -169,25 +169,25 @@ unittest } // Duplicate before - @property @property bool aaa() // [warn]: The attribute 'property' is duplicated. + @property @property bool aaa() // [warn]: Attribute 'property' is duplicated. { return false; } // Duplicate after - bool bbb() @safe @safe // [warn]: The attribute 'safe' is duplicated. + bool bbb() @safe @safe // [warn]: Attribute 'safe' is duplicated. { return false; } // Duplicate before and after - @system bool ccc() @system // [warn]: The attribute 'system' is duplicated. + @system bool ccc() @system // [warn]: Attribute 'system' is duplicated. { return false; } // Duplicate before and after - @trusted bool ddd() @trusted // [warn]: The attribute 'trusted' is duplicated. + @trusted bool ddd() @trusted // [warn]: Attribute 'trusted' is duplicated. { return false; } @@ -200,24 +200,24 @@ unittest return false; } - pure pure bool bbb() // [warn]: The attribute 'pure' is duplicated. + pure pure bool bbb() // [warn]: Attribute 'pure' is duplicated. { return false; } // FIXME: There is no way to get the line/column number of the attribute like this - bool ccc() pure pure // FIXME: [warn]: The attribute 'pure' is duplicated. + bool ccc() pure pure // FIXME: [warn]: Attribute 'pure' is duplicated. { return false; } - nothrow nothrow bool ddd() // [warn]: The attribute 'nothrow' is duplicated. + nothrow nothrow bool ddd() // [warn]: Attribute 'nothrow' is duplicated. { return false; } // FIXME: There is no way to get the line/column number of the attribute like this - bool eee() nothrow nothrow // FIXME: [warn]: The attribute 'nothrow' is duplicated. + bool eee() nothrow nothrow // FIXME: [warn]: Attribute 'nothrow' is duplicated. { return false; } diff --git a/analysis/fish.d b/analysis/fish.d index c81fc9e..1802e8f 100644 --- a/analysis/fish.d +++ b/analysis/fish.d @@ -34,7 +34,7 @@ class FloatOperatorCheck : BaseAnalyzer || r.operator == tok!"!>=" || r.operator == tok!"!<=") { - addErrorMessage(r.line, r.column, "Avoid using the deprecated floating-point operators"); + addErrorMessage(r.line, r.column, "Avoid using the deprecated floating-point operators."); } r.accept(this); } @@ -47,14 +47,14 @@ unittest { float z = 1.5f; bool a; - a = z !<>= z; // [warn]: Avoid using the deprecated floating-point operators - a = z !<> z; // [warn]: Avoid using the deprecated floating-point operators - a = z <> z; // [warn]: Avoid using the deprecated floating-point operators - a = z <>= z; // [warn]: Avoid using the deprecated floating-point operators - a = z !> z; // [warn]: Avoid using the deprecated floating-point operators - a = z !>= z; // [warn]: Avoid using the deprecated floating-point operators - a = z !< z; // [warn]: Avoid using the deprecated floating-point operators - a = z !<= z; // [warn]: Avoid using the deprecated floating-point operators + a = z !<>= z; // [warn]: Avoid using the deprecated floating-point operators. + a = z !<> z; // [warn]: Avoid using the deprecated floating-point operators. + a = z <> z; // [warn]: Avoid using the deprecated floating-point operators. + a = z <>= z; // [warn]: Avoid using the deprecated floating-point operators. + a = z !> z; // [warn]: Avoid using the deprecated floating-point operators. + a = z !>= z; // [warn]: Avoid using the deprecated floating-point operators. + a = z !< z; // [warn]: Avoid using the deprecated floating-point operators. + a = z !<= z; // [warn]: Avoid using the deprecated floating-point operators. } }c, analysis.run.AnalyzerCheck.float_operator_check); diff --git a/analysis/ifelsesame.d b/analysis/ifelsesame.d index 078d946..c454343 100644 --- a/analysis/ifelsesame.d +++ b/analysis/ifelsesame.d @@ -29,7 +29,7 @@ class IfElseSameCheck : BaseAnalyzer { if (ifStatement.thenStatement == ifStatement.elseStatement) addErrorMessage(ifStatement.line, ifStatement.column, - "\"Else\" branch is identical to \"Then\" branch."); + "'Else' branch is identical to 'Then' branch."); ifStatement.accept(this); } @@ -40,7 +40,7 @@ class IfElseSameCheck : BaseAnalyzer && e.ternaryExpression == assignExpression.ternaryExpression) { addErrorMessage(assignExpression.line, assignExpression.column, - "Left side of assignment operatior is identical to the right side"); + "Left side of assignment operatior is identical to the right side."); } assignExpression.accept(this); } @@ -52,7 +52,7 @@ unittest void testSizeT() { string person = "unknown"; - if (person == "unknown") // [warn]: "Else" branch is identical to "Then" branch. + if (person == "unknown") // [warn]: 'Else' branch is identical to 'Then' branch. person = "bobrick"; // same else person = "bobrick"; // same diff --git a/analysis/linespan.d b/analysis/linespan.d index fd5541e..91a605a 100644 --- a/analysis/linespan.d +++ b/analysis/linespan.d @@ -76,6 +76,6 @@ unittest l.addLine(35); foreach (i; 33 .. 43) assert (l.containsLine(i)); - stderr.writeln("Unittest for LineSpans passed"); + stderr.writeln("Unittest for LineSpans passed."); } diff --git a/analysis/numbers.d b/analysis/numbers.d index 6791f92..0e16fb6 100644 --- a/analysis/numbers.d +++ b/analysis/numbers.d @@ -32,7 +32,7 @@ class NumberStyleCheck : BaseAnalyzer || !t.text.matchFirst(badDecimalRegex).empty)) { addErrorMessage(t.line, t.column, - "Use underscores to improve number constant readability"); + "Use underscores to improve number constant readability."); } } @@ -50,9 +50,9 @@ unittest a = 10; // ok a = 100; // ok a = 1000; // FIXME: boom - a = 10000; // [warn]: Use underscores to improve number constant readability - a = 100000; // [warn]: Use underscores to improve number constant readability - a = 1000000; // [warn]: Use underscores to improve number constant readability + a = 10000; // [warn]: Use underscores to improve number constant readability. + a = 100000; // [warn]: Use underscores to improve number constant readability. + a = 1000000; // [warn]: Use underscores to improve number constant readability. } }c, analysis.run.AnalyzerCheck.number_style_check); diff --git a/analysis/objectconst.d b/analysis/objectconst.d index ea4cb25..70f117a 100644 --- a/analysis/objectconst.d +++ b/analysis/objectconst.d @@ -38,8 +38,8 @@ class ObjectConstCheck : BaseAnalyzer && !hasConst(d.functionDeclaration.memberFunctionAttributes))) { addErrorMessage(d.functionDeclaration.name.line, - d.functionDeclaration.name.column, "opCmp, toHash, opEquals," - ~ " and toString should be declared const"); + d.functionDeclaration.name.column, "Methods 'opCmp', 'toHash'," + ~ " 'opEquals', and 'toString' should be declared const."); } d.accept(this); } @@ -101,22 +101,22 @@ unittest // Will warn, because none are const class Dog { - bool opEquals(Object a, Object b) // [warn]: opCmp, toHash, opEquals, and toString should be declared const + bool opEquals(Object a, Object b) // [warn]: Methods 'opCmp', 'toHash', 'opEquals', and 'toString' should be declared const. { return true; } - int opCmp(Object o) // [warn]: opCmp, toHash, opEquals, and toString should be declared const + int opCmp(Object o) // [warn]: Methods 'opCmp', 'toHash', 'opEquals', and 'toString' should be declared const. { return 1; } - hash_t toHash() // [warn]: opCmp, toHash, opEquals, and toString should be declared const + hash_t toHash() // [warn]: Methods 'opCmp', 'toHash', 'opEquals', and 'toString' should be declared const. { return 0; } - string toString() // [warn]: opCmp, toHash, opEquals, and toString should be declared const + string toString() // [warn]: Methods 'opCmp', 'toHash', 'opEquals', and 'toString' should be declared const. { return "Dog"; } diff --git a/analysis/opequals_without_tohash.d b/analysis/opequals_without_tohash.d index fe27ca1..8e844f1 100644 --- a/analysis/opequals_without_tohash.d +++ b/analysis/opequals_without_tohash.d @@ -66,13 +66,13 @@ class OpEqualsWithoutToHashCheck : BaseAnalyzer // Warn if has opEquals, but not toHash if (hasOpEquals && !hasToHash) { - string message = "Has method opEquals, but not toHash"; + string message = "Has method 'opEquals', but not 'toHash'."; addErrorMessage(name.line, name.column, message); } // Warn if has toHash, but not opEquals else if (!hasOpEquals && hasToHash) { - string message = "Has method toHash, but not opEquals"; + string message = "Has method 'toHash', but not 'opEquals'."; addErrorMessage(name.line, name.column, message); } } @@ -96,7 +96,7 @@ unittest } // Fail on class opEquals - class Rabbit // [warn]: Has method opEquals, but not toHash + class Rabbit // [warn]: Has method 'opEquals', but not 'toHash'. { const bool opEquals(Object a, Object b) { @@ -105,7 +105,7 @@ unittest } // Fail on class toHash - class Kangaroo // [warn]: Has method toHash, but not opEquals + class Kangaroo // [warn]: Has method 'toHash', but not 'opEquals'. { override const hash_t toHash() { @@ -114,7 +114,7 @@ unittest } // Fail on struct opEquals - struct Tarantula // [warn]: Has method opEquals, but not toHash + struct Tarantula // [warn]: Has method 'opEquals', but not 'toHash'. { const bool opEquals(Object a, Object b) { @@ -123,7 +123,7 @@ unittest } // Fail on struct toHash - struct Puma // [warn]: Has method toHash, but not opEquals + struct Puma // [warn]: Has method 'toHash', but not 'opEquals'. { const nothrow @safe hash_t toHash() { diff --git a/analysis/pokemon.d b/analysis/pokemon.d index 9a8bf17..7c66924 100644 --- a/analysis/pokemon.d +++ b/analysis/pokemon.d @@ -25,7 +25,7 @@ import analysis.helpers; */ class PokemonExceptionCheck : BaseAnalyzer { - enum message = "Catching Error or Throwable is almost always a bad idea"; + enum message = "Catching Error or Throwable is almost always a bad idea."; alias visit = BaseAnalyzer.visit; @@ -81,15 +81,15 @@ unittest { } - catch (Error err) // [warn]: Catching Error or Throwable is almost always a bad idea + catch (Error err) // [warn]: Catching Error or Throwable is almost always a bad idea. { } - catch (Throwable err) // [warn]: Catching Error or Throwable is almost always a bad idea + catch (Throwable err) // [warn]: Catching Error or Throwable is almost always a bad idea. { } - catch // [warn]: Catching Error or Throwable is almost always a bad idea + catch // [warn]: Catching Error or Throwable is almost always a bad idea. { } } diff --git a/analysis/style.d b/analysis/style.d index 24fd1af..66d3225 100644 --- a/analysis/style.d +++ b/analysis/style.d @@ -36,7 +36,7 @@ class StyleChecker : BaseAnalyzer { if (part.text.matchFirst(moduleNameRegex).length == 0) addErrorMessage(part.line, part.column, "Module/package name '" - ~ part.text ~ "' does not match style guidelines"); + ~ part.text ~ "' does not match style guidelines."); } } @@ -53,8 +53,8 @@ class StyleChecker : BaseAnalyzer void checkLowercaseName(string type, ref const Token name) { if (name.text.matchFirst(varFunNameRegex).length == 0) - addErrorMessage(name.line, name.column, type ~ " name " - ~ name.text ~ " does not match style guidelines"); + addErrorMessage(name.line, name.column, type ~ " name '" + ~ name.text ~ "' does not match style guidelines."); } override void visit(const ClassDeclaration dec) @@ -87,24 +87,24 @@ class StyleChecker : BaseAnalyzer { if (name.text.matchFirst(aggregateNameRegex).length == 0) addErrorMessage(name.line, name.column, aggregateType - ~ " name '" ~ name.text ~ "' does not match style guidelines"); + ~ " name '" ~ name.text ~ "' does not match style guidelines."); } } unittest { assertAnalyzerWarnings(q{ - module AMODULE; // [warn]: Module/package name 'AMODULE' does not match style guidelines + module AMODULE; // [warn]: Module/package name 'AMODULE' does not match style guidelines. bool A_VARIABLE; // FIXME: bool a_variable; // ok bool aVariable; // ok void A_FUNCTION() {} // FIXME: - class cat {} // [warn]: Class name 'cat' does not match style guidelines - interface puma {} // [warn]: Interface name 'puma' does not match style guidelines - struct dog {} // [warn]: Struct name 'dog' does not match style guidelines - enum racoon {} // [warn]: Enum name 'racoon' does not match style guidelines + class cat {} // [warn]: Class name 'cat' does not match style guidelines. + interface puma {} // [warn]: Interface name 'puma' does not match style guidelines. + struct dog {} // [warn]: Struct name 'dog' does not match style guidelines. + enum racoon {} // [warn]: Enum name 'racoon' does not match style guidelines. }c, analysis.run.AnalyzerCheck.style_check); stderr.writeln("Unittest for StyleChecker passed."); diff --git a/ctags.d b/ctags.d index 883f6bf..41bbebf 100644 --- a/ctags.d +++ b/ctags.d @@ -156,6 +156,16 @@ class CTagsPrinter : ASTVisitor dec.accept(this); } + override void visit(const AutoDeclaration dec) + { + foreach (i; dec.identifiers) + { + tagLines ~= "%s\t%s\t%d;\"\tv%s\n".format(i.text, fileName, + i.line, context); + } + dec.accept(this); + } + override void visit(const Invariant dec) { tagLines ~= "invariant\t%s\t%d;\"\tv%s\n".format(fileName, dec.line, context); diff --git a/std/d/parser.d b/std/d/parser.d new file mode 100644 index 0000000..f626162 --- /dev/null +++ b/std/d/parser.d @@ -0,0 +1,7067 @@ +// Written in the D programming language + +/** + * MACROS: + * GRAMMAR =
$0+ * RULEDEF = $(B $(DDOC_ANCHOR $0) $0) + * RULE = $(LINK2 #$0, $0) + * LITERAL = $(D_STRING $(I $0)) + */ + +module std.d.parser; + +import std.d.lexer; +import std.d.ast; +import std.allocator; +import std.conv; +import std.algorithm; +import std.array; +import std.stdio; +import std.string : format; + +// Uncomment this if you want ALL THE OUTPUT +// Caution: generates 180 megabytes of logging for std.datetime +//version = std_parser_verbose; + +alias ParseAllocator = CAllocatorImpl!(Mallocator); + +/** + * Params: + * tokens = the tokens parsed by std.d.lexer + * fileName = the name of the file being parsed + * messageFunction = a function to call on error or warning messages. + * The parameters are the file name, line number, column number, + * the error or warning message, and a boolean (true means error, false + * means warning). + * Returns: the parsed module + */ +Module parseModule(const(Token)[] tokens, string fileName, CAllocator allocator = null, + void function(string, size_t, size_t, string, bool) messageFunction = null) +{ + auto parser = new Parser(); + parser.fileName = fileName; + parser.tokens = tokens; + parser.messageFunction = messageFunction; + parser.allocator = allocator; + auto mod = parser.parseModule(); + // writefln("Parsing finished with %d errors and %d warnings.", + // parser.errorCount, parser.warningCount); + return mod; +} + +/** + * Parser structure + */ +class Parser +{ + /** + * Parses an AddExpression. + * + * $(GRAMMAR $(RULEDEF addExpression): + * $(RULE mulExpression) + * | $(RULE addExpression) $(LPAREN)$(LITERAL '+') | $(LITERAL'-') | $(LITERAL'~')$(RPAREN) $(RULE mulExpression) + * ;) + */ + ExpressionNode parseAddExpression() + { + mixin(traceEnterAndExit!(__FUNCTION__)); + return parseLeftAssocBinaryExpression!(AddExpression, MulExpression, + tok!"+", tok!"-", tok!"~")(); + } + + /** + * Parses an AliasDeclaration. + * + * $(GRAMMAR $(RULEDEF aliasDeclaration): + * $(LITERAL 'alias') $(RULE aliasInitializer) $(LPAREN)$(LITERAL ',') $(RULE aliasInitializer)$(RPAREN)* $(LITERAL ';') + * | $(LITERAL 'alias') $(RULE storageClass)* $(RULE type) $(LITERAL identifier) $(LITERAL ';') + * ;) + */ + AliasDeclaration parseAliasDeclaration() + { + mixin(traceEnterAndExit!(__FUNCTION__)); + auto node = allocate!AliasDeclaration; + if (expect(tok!"alias") is null) return null; + node.comment = comment; + comment = null; + + if (startsWith(tok!"identifier", tok!"=") || startsWith(tok!"identifier", tok!"(")) + { + AliasInitializer[] initializers; + do + { + auto initializer = parseAliasInitializer(); + if (initializer is null) return null; + initializers ~= initializer; + if (currentIs(tok!",")) + advance(); + else + break; + } + while (moreTokens()); + node.initializers = ownArray(initializers); + } + else + { + StorageClass[] storageClasses; + while (moreTokens() && isStorageClass()) + storageClasses ~= parseStorageClass(); + if (storageClasses.length > 0) + node.storageClasses = ownArray(storageClasses); + warn("Prefer the new \"'alias' identifier '=' type ';'\" syntax" + ~ " to the old \"'alias' type identifier ';'\" syntax"); + if ((node.type = parseType()) is null) return null; + auto ident = expect(tok!"identifier"); + if (ident is null) + return null; + node.name = *ident; + } + if (expect(tok!";") is null) return null; + return node; + } + + unittest + { + auto sourceCode = +q{ +alias core.sys.posix.stdio.fileno fileno; +}c; + + Parser p = getParserForUnittest(sourceCode, "parseAliasDeclaration"); + + AliasDeclaration d = p.parseAliasDeclaration(); + assert (d !is null); + assert (p.errorCount == 0); + + stderr.writeln("Unittest for parseAliasDeclaration passed."); + } + + /** + * Parses an AliasInitializer + * $(GRAMMAR $(RULEDEF aliasInitializer): + * $(LITERAL Identifier) $(RULE templateParameters)? $(LITERAL '=') $(RULE storageClass)* $(RULE type) + * ;) + */ + AliasInitializer parseAliasInitializer() + { + mixin(traceEnterAndExit!(__FUNCTION__)); + auto node = allocate!AliasInitializer; + auto i = expect(tok!"identifier"); + if (i is null) return null; + node.name = *i; + if (currentIs(tok!"(")) + node.templateParameters = parseTemplateParameters(); + if (expect(tok!"=") is null) return null; + StorageClass[] storageClasses; + while (moreTokens() && isStorageClass()) + storageClasses ~= parseStorageClass(); + if (storageClasses.length > 0) + node.storageClasses = ownArray(storageClasses); + node.type = parseType(); + return node; + } + + unittest + { + auto sourceCode = q{a = abcde!def}; + Parser p = getParserForUnittest(sourceCode, "parseAliasInitializer"); + auto initializer = p.parseAliasInitializer(); + assert (initializer !is null); + assert (p.errorCount == 0); + stderr.writeln("Unittest for parseAliasInitializer passed."); + } + + /** + * Parses an AliasThisDeclaration + * $(GRAMMAR $(RULEDEF aliasThisDeclaration): + * $(LITERAL 'alias') $(LITERAL Identifier) $(LITERAL 'this') $(LITERAL ';') + * ;) + */ + AliasThisDeclaration parseAliasThisDeclaration() + { + mixin(traceEnterAndExit!(__FUNCTION__)); + auto node = allocate!AliasThisDeclaration; + if (expect(tok!"alias") is null) return null; + auto ident = expect(tok!"identifier"); + if (ident is null) return null; + node.identifier = *ident; + if (expect(tok!"this") is null) return null; + if (expect(tok!";") is null) return null; + return node; + } + + unittest + { + auto sourceCode = q{alias oneTwoThree this;}; + Parser p = getParserForUnittest(sourceCode, "parseAliasThisDeclaration"); + auto aliasThis = p.parseAliasThisDeclaration(); + assert (aliasThis !is null); + assert (p.errorCount == 0); + stderr.writeln("Unittest for parseAliasThisDeclaration passed."); + } + + /** + * Parses an AlignAttribute. + * $(GRAMMAR $(RULEDEF alignAttribute): + * $(LITERAL 'align') ($(LITERAL '$(LPAREN)') $(LITERAL IntegerLiteral) $(LITERAL '$(RPAREN)'))? + * ;) + */ + AlignAttribute parseAlignAttribute() + { + mixin(traceEnterAndExit!(__FUNCTION__)); + auto node = allocate!AlignAttribute; + expect(tok!"align"); + if (currentIs(tok!"(")) + { + if (expect(tok!"(") is null) return null; + auto intLit = expect(tok!"intLiteral"); + if (intLit is null) return null; + node.intLiteral = *intLit; + if (expect(tok!")") is null) return null; + } + return node; + } + + unittest + { + auto sourceCode = q{align(42) align}; + Parser p = getParserForUnittest(sourceCode, "parseAlignAttribute"); + auto attribute = p.parseAlignAttribute(); + assert (attribute !is null); + attribute = p.parseAlignAttribute(); + assert (attribute !is null); + assert (p.errorCount == 0); + stderr.writeln("Unittest for parseAlignAttribute passed."); + } + + /** + * Parses an AndAndExpression + * $(GRAMMAR $(RULEDEF andAndExpression): + * $(RULE orExpression) + * | $(RULE andAndExpression) $(LITERAL '&&') $(RULE orExpression) + * ;) + */ + ExpressionNode parseAndAndExpression() + { + mixin(traceEnterAndExit!(__FUNCTION__)); + return parseLeftAssocBinaryExpression!(AndAndExpression, OrExpression, + tok!"&&")(); + } + + /** + * Parses an AndExpression + * + * $(GRAMMAR $(RULEDEF andExpression): + * $(RULE cmpExpression) + * | $(RULE andExpression) $(LITERAL '&') $(RULE cmpExpression) + * ;) + */ + ExpressionNode parseAndExpression() + { + mixin(traceEnterAndExit!(__FUNCTION__)); + return parseLeftAssocBinaryExpression!(AndExpression, CmpExpression, + tok!"&")(); + } + + /** + * Parses an ArgumentList + * + * $(GRAMMAR $(RULEDEF argumentList): + * $(RULE assignExpression) ($(LITERAL ',') $(RULE assignExpression)?)* + * ;) + */ + ArgumentList parseArgumentList() + { + mixin(traceEnterAndExit!(__FUNCTION__)); + size_t startLocation = current().index; + auto ret= parseCommaSeparatedRule!(ArgumentList, AssignExpression)(true); + ret.startLocation = startLocation; + ret.endLocation = current().index; + return ret; + } + + /** + * Parses Arguments + * + * $(GRAMMAR $(RULEDEF arguments): + * $(LITERAL '$(LPAREN)') $(RULE argumentList)? $(LITERAL '$(RPAREN)') + * ;) + */ + Arguments parseArguments() + { + mixin(traceEnterAndExit!(__FUNCTION__)); + auto node = allocate!Arguments; + if (expect(tok!"(") is null) return null; + if (!currentIs(tok!")")) + node.argumentList = parseArgumentList(); + if (expect(tok!")") is null) return null; + return node; + } + + /** + * Parses an ArrayInitializer + * + * $(GRAMMAR $(RULEDEF arrayInitializer): + * $(LITERAL '[') $(LITERAL ']') + * | $(LITERAL '[') $(RULE arrayMemberInitialization) ($(LITERAL ',') $(RULE arrayMemberInitialization)?)* $(LITERAL ']') + * ;) + */ + ArrayInitializer parseArrayInitializer() + { + mixin(traceEnterAndExit!(__FUNCTION__)); + auto node = allocate!ArrayInitializer; + if (expect(tok!"[") is null) return null; + ArrayMemberInitialization[] arrayMemberInitializations; + while (moreTokens()) + { + if (currentIs(tok!"]")) + break; + arrayMemberInitializations ~= parseArrayMemberInitialization(); + if (currentIs(tok!",")) + advance(); + else + break; + } + node.arrayMemberInitializations = ownArray(arrayMemberInitializations); + if (expect(tok!"]") is null) return null; + return node; + } + + /** + * Parses an ArrayLiteral + * + * $(GRAMMAR $(RULEDEF arrayLiteral): + * $(LITERAL '[') $(RULE argumentList)? $(LITERAL ']') + * ;) + */ + ArrayLiteral parseArrayLiteral() + { + mixin(traceEnterAndExit!(__FUNCTION__)); + auto node = allocate!ArrayLiteral; + if (expect(tok!"[") is null) return null; + if (!currentIs(tok!"]")) + node.argumentList = parseArgumentList(); + if (expect(tok!"]") is null) return null; + return node; + } + + /** + * Parses an ArrayMemberInitialization + * + * $(GRAMMAR $(RULEDEF arrayMemberInitialization): + * ($(RULE assignExpression) $(LITERAL ':'))? $(RULE nonVoidInitializer) + * ;) + */ + ArrayMemberInitialization parseArrayMemberInitialization() + { + mixin(traceEnterAndExit!(__FUNCTION__)); + auto node = allocate!ArrayMemberInitialization; + switch (current.type) + { + case tok!"{": + case tok!"[": + node.nonVoidInitializer = parseNonVoidInitializer(); + if (node.nonVoidInitializer is null) return null; + break; + default: + auto assignExpression = parseAssignExpression(); + if (assignExpression is null) return null; + if (currentIs(tok!":")) + { + node.assignExpression = assignExpression; + advance(); + node.nonVoidInitializer = parseNonVoidInitializer(); + if (node.nonVoidInitializer is null) return null; + } + else + { + node.nonVoidInitializer = allocate!NonVoidInitializer; + node.nonVoidInitializer.assignExpression = assignExpression; + } + } + return node; + } + + /** + * Parses an AsmAddExp + * + * $(GRAMMAR $(RULEDEF asmAddExp): + * $(RULE asmMulExp) + * | $(RULE asmAddExp) ($(LITERAL '+') | $(LITERAL '-')) $(RULE asmMulExp) + * ;) + */ + ExpressionNode parseAsmAddExp() + { + return parseLeftAssocBinaryExpression!(AsmAddExp, AsmMulExp, + tok!"+", tok!"-")(); + } + + /** + * Parses an AsmAndExp + * + * $(GRAMMAR $(RULEDEF asmAndExp): + * $(RULE asmEqualExp) ($(LITERAL '&') $(RULE asmEqualExp))? + * ;) + */ + AsmAndExp parseAsmAndExp() + { +// auto node = allocate!AsmAndExp; + assert (false, "asm"); // TODO asm + } + + /** + * Parses an AsmBrExp + * + * $(GRAMMAR $(RULEDEF asmBrExp): + * $(RULE asmUnaExp) + * | $(RULE asmBrExp) $(LITERAL '[') $(RULE asmExp) $(LITERAL ']') + * ;) + */ + AsmBrExp parseAsmBrExp() + { +// auto node = allocate!AsmBrExp; + assert (false, "asm"); // TODO asm + } + + /** + * Parses an AsmEqualExp + * + * $(GRAMMAR $(RULEDEF asmEqualExp): + * $(RULE asmRelExp) (('==' | '!=') $(RULE asmRelExp))? + * ;) + */ + AsmEqualExp parseAsmEqualExp() + { +// auto node = allocate!AsmEqualExp; + assert (false, "asm"); // TODO asm + } + + /** + * Parses an AsmExp + * + * $(GRAMMAR $(RULEDEF asmExp): + * $(RULE asmLogOrExp) ($(LITERAL '?') $(RULE asmExp) $(LITERAL ':') $(RULE asmExp))? + * ;) + */ + AsmExp parseAsmExp() + { +// auto node = allocate!AsmExp; + assert (false, "asm"); // TODO asm + } + + /** + * Parses an AsmInstruction + * + * $(GRAMMAR $(RULEDEF asmInstruction): + * $(LITERAL Identifier) + * | $(LITERAL 'align') $(LITERAL IntegerLiteral) + * | $(LITERAL 'align') $(LITERAL Identifier) + * | $(LITERAL Identifier) $(LITERAL ':') $(RULE asmInstruction) + * | $(LITERAL Identifier) $(RULE asmExp) + * | $(LITERAL Identifier) $(RULE operands) + * ;) + */ + AsmInstruction parseAsmInstruction() + { +// auto node = allocate!AsmInstruction; + assert (false, "asm"); // TODO asm + } + + /** + * Parses an AsmLogAndExp + * + * $(GRAMMAR $(RULEDEF asmLogAndExp): + * $(RULE asmOrExp) ('&&' $(RULE asmOrExp))? + * ;) + */ + AsmLogAndExp parseAsmLogAndExp() + { +// auto node = allocate!AsmLogAndExp; + assert (false, "asm"); // TODO asm + } + + /** + * Parses an AsmLogOrExp + * + * $(GRAMMAR $(RULEDEF asmLogOrExp): + * $(RULE asmLogAndExp) ('||' $(RULE asmLogAndExp))? + * ;) + */ + AsmLogOrExp parseAsmLogOrExp() + { +// auto node = allocate!AsmLogOrExp; + assert (false, "asm"); // TODO asm + } + + /** + * Parses an AsmMulExp + * + * $(GRAMMAR $(RULEDEF asmMulExp): + * $(RULE asmBrExp) (($(LITERAL '*') | $(LITERAL '/') | $(LITERAL '%')) $(RULE asmBrExp))? + * ;) + */ + AsmMulExp parseAsmMulExp() + { +// auto node = allocate!AsmMulExp; + assert (false, "asm"); // TODO asm + } + + /** + * Parses an AsmOrExp + * + * $(GRAMMAR $(RULEDEF asmOrExp): + * $(RULE asmXorExp) ($(LITERAL '|') $(RULE asmXorExp))? + * ;) + */ + AsmOrExp parseAsmOrExp() + { +// auto node = allocate!AsmOrExp; + assert (false, "asm"); // TODO asm + } + + /** + * Parses an AsmPrimaryExp + * + * $(GRAMMAR $(RULEDEF asmPrimaryExp): + * $(LITERAL IntegerLiteral) + * | $(LITERAL FloatLiteral) + * | $(RULE register) + * | $(RULE identifierChain) + * | $(LITERAL '$') + * ;) + */ + AsmPrimaryExp parseAsmPrimaryExp() + { +// auto node = allocate!AsmPrimaryExp; + assert (false, "asm"); // TODO asm + } + + /** + * Parses an AsmRelExp + * + * $(GRAMMAR $(RULEDEF asmRelExp): + * $(RULE asmShiftExp) (($(LITERAL '<') | $(LITERAL '<=') | $(LITERAL '>') | $(LITERAL '>=')) $(RULE asmShiftExp))? + * ;) + */ + AsmRelExp parseAsmRelExp() + { +// auto node = allocate!AsmRelExp; + assert (false, "asm"); // TODO asm + } + + /** + * Parses an AsmShiftExp + * + * $(GRAMMAR $(RULEDEF asmShiftExp): + * $(RULE asmAddExp) (($(LITERAL '<<') | $(LITERAL '>>') | $(LITERAL '>>>')) $(RULE asmAddExp))? + * ;) + */ + AsmShiftExp parseAsmShiftExp() + { +// auto node = allocate!AsmShiftExp; + assert (false, "asm"); // TODO asm + } + + /** + * Parses an AsmStatement + * + * $(GRAMMAR $(RULEDEF asmStatement): + * $(LITERAL 'asm') $(LITERAL '{') $(RULE asmInstruction)+ $(LITERAL '}') + * ;) + */ + AsmStatement parseAsmStatement() + { + // TODO asm + auto node = allocate!AsmStatement; + warn("Skipping assembly statement. Not supported."); + advance(); + skipBraces(); + return node; + } + + /** + * Parses an AsmTypePrefix + * + * $(GRAMMAR $(RULEDEF asmTypePrefix): + * $(LITERAL Identifier) $(LITERAL Identifier) + * | $(LITERAL 'byte') $(LITERAL Identifier) + * | $(LITERAL 'short') $(LITERAL Identifier) + * | $(LITERAL 'int') $(LITERAL Identifier) + * | $(LITERAL 'float') $(LITERAL Identifier) + * | $(LITERAL 'double') $(LITERAL Identifier) + * | $(LITERAL 'real') $(LITERAL Identifier) + * ;) + */ + AsmTypePrefix parseAsmTypePrefix() + { +// auto node = allocate!AsmTypePrefix; + assert (false, "asm"); // TODO asm + } + + /** + * Parses an AsmUnaExp + * + * $(GRAMMAR $(RULEDEF asmUnaExp): + * $(RULE asmTypePrefix) $(RULE asmExp) + * | $(LITERAL Identifier) $(RULE asmExp) + * | $(LITERAL '+') $(RULE asmUnaExp) + * | $(LITERAL '-') $(RULE asmUnaExp) + * | $(LITERAL '!') $(RULE asmUnaExp) + * | $(LITERAL '~') $(RULE asmUnaExp) + * | $(RULE asmPrimaryExp) + * ;) + */ + AsmUnaExp parseAsmUnaExp() + { +// auto node = allocate!AsmUnaExp; + assert (false, "asm"); // TODO asm + } + + /** + * Parses an AsmXorExp + * + * $(GRAMMAR $(RULEDEF asmXorExp): + * $(RULE asmAndExp) ($(LITERAL '^') $(RULE asmAndExp))? + * ;) + */ + AsmXorExp parseAsmXorExp() + { +// auto node = allocate!AsmXorExp; + assert (false, "asm"); // TODO asm + } + + /** + * Parses an AssertExpression + * + * $(GRAMMAR $(RULEDEF assertExpression): + * $(LITERAL 'assert') $(LITERAL '$(LPAREN)') $(RULE assignExpression) ($(LITERAL ',') $(RULE assignExpression))? $(LITERAL '$(RPAREN)') + * ;) + */ + AssertExpression parseAssertExpression() + { + mixin(traceEnterAndExit!(__FUNCTION__)); + auto node = allocate!AssertExpression; + expect(tok!"assert"); + if (expect(tok!"(") is null) return null; + node.assertion = parseAssignExpression(); + if (currentIs(tok!",")) + { + advance(); + node.message = parseAssignExpression(); + } + if (expect(tok!")") is null) return null; + return node; + } + + /** + * Parses an AssignExpression + * + * $(GRAMMAR $(RULEDEF assignExpression): + * $(RULE ternaryExpression) ($(RULE assignOperator) $(RULE assignExpression))? + * ; + *$(RULEDEF assignOperator): + * $(LITERAL '=') + * | $(LITERAL '>>>=') + * | $(LITERAL '>>=') + * | $(LITERAL '<<=') + * | $(LITERAL '+=') + * | $(LITERAL '-=') + * | $(LITERAL '*=') + * | $(LITERAL '%=') + * | $(LITERAL '&=') + * | $(LITERAL '/=') + * | $(LITERAL '|=') + * | $(LITERAL '^^=') + * | $(LITERAL '^=') + * | $(LITERAL '~=') + * ;) + */ + AssignExpression parseAssignExpression() + { + mixin(traceEnterAndExit!(__FUNCTION__)); + auto node = allocate!AssignExpression; + node.line = current().line; + node.column = current().column; + node.ternaryExpression = parseTernaryExpression(); + if (currentIsOneOf(tok!"=", tok!">>>=", + tok!">>=", tok!"<<=", + tok!"+=", tok!"-=", tok!"*=", + tok!"%=", tok!"&=", tok!"/=", + tok!"|=", tok!"^^=", tok!"^=", + tok!"~=")) + { + node.operator = advance().type; + node.assignExpression = parseAssignExpression(); + } + return node; + } + + /** + * Parses an AssocArrayLiteral + * + * $(GRAMMAR $(RULEDEF assocArrayLiteral): + * $(LITERAL '[') $(RULE keyValuePairs) $(LITERAL ']') + * ;) + */ + AssocArrayLiteral parseAssocArrayLiteral() + { + mixin(traceEnterAndExit!(__FUNCTION__)); + mixin (simpleParse!(AssocArrayLiteral, tok!"[", + "keyValuePairs|parseKeyValuePairs", tok!"]")); + } + + /** + * Parses an AtAttribute + * + * $(GRAMMAR $(RULEDEF atAttribute): + * $(LITERAL '@') ($(LITERAL Identifier) | $(LITERAL '$(LPAREN)') $(RULE argumentList) $(LITERAL '$(RPAREN)') | $(RULE functionCallExpression)) + * ;) + */ + AtAttribute parseAtAttribute() + { + mixin(traceEnterAndExit!(__FUNCTION__)); + auto node = allocate!AtAttribute; + auto start = expect(tok!"@"); + if (start is null) return null; + node.startLocation = start.index; + switch (current.type) + { + case tok!"identifier": + if (peekIsOneOf(tok!"(", tok!".", tok!"!")) + node.functionCallExpression = parseFunctionCallExpression(); + else + node.identifier = advance(); + break; + case tok!"(": + advance(); + node.argumentList = parseArgumentList(); + expect(tok!")"); + break; + default: + error(`"(", or identifier expected`); + return null; + } + node.endLocation = current().index; + return node; + } + + /** + * Parses an Attribute + * + * $(GRAMMAR $(RULEDEF attribute): + * | $(RULE pragmaExpression) + * | $(RULE storageClass) + * | $(LITERAL 'export') + * | $(LITERAL 'package') + * | $(LITERAL 'private') + * | $(LITERAL 'protected') + * | $(LITERAL 'public') + * ;) + */ + Attribute parseAttribute() + { + mixin(traceEnterAndExit!(__FUNCTION__)); + auto node = allocate!Attribute; + switch (current.type) + { + case tok!"pragma": + node.pragmaExpression = parsePragmaExpression(); + break; + case tok!"private": + case tok!"package": + case tok!"protected": + case tok!"public": + case tok!"export": + node.attribute = advance().type; + break; + default: + node.storageClass = parseStorageClass(); + break; + } + return node; + } + + /** + * Parses an AttributeDeclaration + * + * $(GRAMMAR $(RULEDEF attributeDeclaration): + * $(RULE attribute) $(LITERAL ':') + * ;) + */ + AttributeDeclaration parseAttributeDeclaration(Attribute attribute = null) + { + auto node = allocate!AttributeDeclaration; + node.attribute = attribute is null ? parseAttribute() : attribute; + expect(tok!":"); + return node; + } + + /** + * Parses an AutoDeclaration + * + * $(GRAMMAR $(RULEDEF autoDeclaration): + * $(RULE storageClass) $(LITERAL Identifier) $(LITERAL '=') $(RULE initializer) ($(LITERAL ',') $(LITERAL Identifier) $(LITERAL '=') $(RULE initializer))* $(LITERAL ';') + * ;) + */ + AutoDeclaration parseAutoDeclaration() + { + mixin(traceEnterAndExit!(__FUNCTION__)); + auto node = allocate!AutoDeclaration; + Token[] identifiers; + Initializer[] initializers; + do + { + auto ident = expect(tok!"identifier"); + if (ident is null) return null; + identifiers ~= *ident; + if (expect(tok!"=") is null) return null; + auto init = parseInitializer(); + if (init is null) return null; + initializers ~= init; + if (currentIs(tok!",")) + advance(); + else + break; + } while (moreTokens()); + node.identifiers = ownArray(identifiers); + node.initializers = ownArray(initializers); + if (expect(tok!";") is null) return null; + return node; + } + + /** + * Parses a BlockStatement + * + * $(GRAMMAR $(RULEDEF blockStatement): + * $(LITERAL '{') $(RULE declarationsAndStatements)? $(LITERAL '}') + * ;) + */ + BlockStatement parseBlockStatement() + { + mixin(traceEnterAndExit!(__FUNCTION__)); + auto node = allocate!BlockStatement; + auto openBrace = expect(tok!"{"); + if (openBrace is null) return null; + node.startLocation = openBrace.index; + if (!currentIs(tok!"}")) + node.declarationsAndStatements = parseDeclarationsAndStatements(); + auto closeBrace = expect(tok!"}"); + if (closeBrace !is null) + node.endLocation = closeBrace.index; + else + node.endLocation = size_t.max; + return node; + } + + /** + * Parses a BodyStatement + * + * $(GRAMMAR $(RULEDEF bodyStatement): + * $(LITERAL 'body') $(RULE blockStatement) + * ;) + */ + BodyStatement parseBodyStatement() + { + mixin(traceEnterAndExit!(__FUNCTION__)); + mixin (simpleParse!(BodyStatement, tok!"body", + "blockStatement|parseBlockStatement")); + } + + /** + * Parses a BreakStatement + * + * $(GRAMMAR $(RULEDEF breakStatement): + * $(LITERAL 'break') $(LITERAL Identifier)? $(LITERAL ';') + * ;) + */ + BreakStatement parseBreakStatement() + { + mixin(traceEnterAndExit!(__FUNCTION__)); + expect(tok!"break"); + auto node = allocate!BreakStatement; + switch (current.type) + { + case tok!"identifier": + node.label = advance(); + if (expect(tok!";") is null) return null; + break; + case tok!";": + advance(); + break; + default: + error("Identifier or semicolon expected following \"break\""); + return null; + } + return node; + } + + /** + * Parses a BaseClass + * + * $(GRAMMAR $(RULEDEF baseClass): + * ($(RULE typeofExpression) $(LITERAL '.'))? $(RULE identifierOrTemplateChain) + * ;) + */ + BaseClass parseBaseClass() + { + mixin(traceEnterAndExit!(__FUNCTION__)); + auto node = allocate!BaseClass; + if (current.type.isProtection()) + { + warn("Use of base class protection is deprecated."); + advance(); + } + if (currentIs(tok!"typeof")) + { + node.typeofExpression = parseTypeofExpression(); + if (expect(tok!".") is null) return null; + } + node.identifierOrTemplateChain = parseIdentifierOrTemplateChain(); + return node; + } + + /** + * Parses a BaseClassList + * + * $(GRAMMAR $(RULEDEF baseClassList): + * $(RULE baseClass) ($(LITERAL ',') $(RULE baseClass))* + * ;) + */ + BaseClassList parseBaseClassList() + { + mixin(traceEnterAndExit!(__FUNCTION__)); + return parseCommaSeparatedRule!(BaseClassList, BaseClass)(); + } + + /** + * Parses an BuiltinType + * + * $(GRAMMAR $(RULEDEF builtinType): + * $(LITERAL 'bool') + * | $(LITERAL 'byte') + * | $(LITERAL 'ubyte') + * | $(LITERAL 'short') + * | $(LITERAL 'ushort') + * | $(LITERAL 'int') + * | $(LITERAL 'uint') + * | $(LITERAL 'long') + * | $(LITERAL 'ulong') + * | $(LITERAL 'char') + * | $(LITERAL 'wchar') + * | $(LITERAL 'dchar') + * | $(LITERAL 'float') + * | $(LITERAL 'double') + * | $(LITERAL 'real') + * | $(LITERAL 'ifloat') + * | $(LITERAL 'idouble') + * | $(LITERAL 'ireal') + * | $(LITERAL 'cfloat') + * | $(LITERAL 'cdouble') + * | $(LITERAL 'creal') + * | $(LITERAL 'void') + * ;) + */ + IdType parseBasicType() + { + mixin(traceEnterAndExit!(__FUNCTION__)); + if (isBasicType(current.type)) + return advance().type; + error("Basic type expected"); + return tok!""; + } + + /** + * Parses a CaseRangeStatement + * + * $(GRAMMAR $(RULEDEF caseRangeStatement): + * $(LITERAL 'case') $(RULE assignExpression) $(LITERAL ':') $(LITERAL '...') $(LITERAL 'case') $(RULE assignExpression) $(LITERAL ':') $(RULE declarationsAndStatements) + * ;) + */ + CaseRangeStatement parseCaseRangeStatement(AssignExpression low = null) + { + mixin(traceEnterAndExit!(__FUNCTION__)); + auto node = allocate!CaseRangeStatement; + if (low is null) + { + expect(tok!"case"); + node.low = parseAssignExpression(); + } + else + node.low = low; + if (expect(tok!":") is null) return null; + if (expect(tok!"..") is null) return null; + expect(tok!"case"); + node.high = parseAssignExpression(); + if (expect(tok!":") is null) return null; + node.declarationsAndStatements = parseDeclarationsAndStatements(); + return node; + } + + /** + * Parses an CaseStatement + * + * $(GRAMMAR $(RULEDEF caseStatement): + * $(LITERAL 'case') $(RULE argumentList) $(LITERAL ':') $(RULE declarationsAndStatements) + * ;) + */ + CaseStatement parseCaseStatement(ArgumentList argumentList = null) + { + mixin(traceEnterAndExit!(__FUNCTION__)); + auto node = allocate!CaseStatement; + if (argumentList is null) + { + expect(tok!"case"); + node.argumentList = parseArgumentList(); + } + else + node.argumentList = argumentList; + if (expect(tok!":") is null) return null; + node.declarationsAndStatements = parseDeclarationsAndStatements(); + return node; + } + + /** + * Parses a CastExpression + * + * $(GRAMMAR $(RULEDEF castExpression): + * $(LITERAL 'cast') $(LITERAL '$(LPAREN)') ($(RULE type) | $(RULE castQualifier))? $(LITERAL '$(RPAREN)') $(RULE unaryExpression) + * ;) + */ + CastExpression parseCastExpression() + { + mixin(traceEnterAndExit!(__FUNCTION__)); + auto node = allocate!CastExpression; + expect(tok!"cast"); + if (expect(tok!"(") is null) return null; + if (!currentIs(tok!")")) + { + if (isCastQualifier()) + node.castQualifier = parseCastQualifier(); + else + node.type = parseType(); + } + if (expect(tok!")") is null) return null; + node.unaryExpression = parseUnaryExpression(); + return node; + } + + /** + * Parses a CastQualifier + * + * $(GRAMMAR $(RULEDEF castQualifier): + * $(LITERAL 'const') + * | $(LITERAL 'const') $(LITERAL 'shared') + * | $(LITERAL 'immutable') + * | $(LITERAL 'inout') + * | $(LITERAL 'inout') $(LITERAL 'shared') + * | $(LITERAL 'shared') + * | $(LITERAL 'shared') $(LITERAL 'const') + * | $(LITERAL 'shared') $(LITERAL 'inout') + * ;) + */ + CastQualifier parseCastQualifier() + { + mixin(traceEnterAndExit!(__FUNCTION__)); + auto node = allocate!CastQualifier; + switch (current.type) + { + case tok!"inout": + case tok!"const": + node.first = advance(); + if (currentIs(tok!"shared")) + node.second = advance(); + break; + case tok!"shared": + node.first = advance(); + if (currentIsOneOf(tok!"const", tok!"inout")) + node.second = advance(); + break; + case tok!"immutable": + node.first = advance(); + break; + default: + error("const, immutable, inout, or shared expected"); + return null; + } + return node; + } + + unittest + { + auto sourceCode = q{ +const; +const shared; +immutable; +inout; +inout shared; +shared; +shared const; +shared inout; +incorrect; + }; + + Parser p = getParserForUnittest(sourceCode, "parseCastQualifier"); + + CastQualifier one = p.parseCastQualifier(); + assert (one.first == tok!"const"); + assert (one.second == tok!""); + p.expect(tok!";"); + + CastQualifier two = p.parseCastQualifier(); + assert (two.first == tok!"const"); + assert (two.second == tok!"shared"); + p.expect(tok!";"); + + CastQualifier three = p.parseCastQualifier(); + assert (three.first == tok!"immutable"); + assert (three.second == tok!""); + p.expect(tok!";"); + + CastQualifier four = p.parseCastQualifier(); + assert (four.first == tok!"inout"); + assert (four.second == tok!""); + p.expect(tok!";"); + + CastQualifier five = p.parseCastQualifier(); + assert (five.first == tok!"inout"); + assert (five.second == tok!"shared"); + p.expect(tok!";"); + + CastQualifier six = p.parseCastQualifier(); + assert (six.first == tok!"shared"); + assert (six.second == tok!""); + p.expect(tok!";"); + + CastQualifier seven = p.parseCastQualifier(); + assert (seven.first == tok!"shared"); + assert (seven.second == tok!"const"); + p.expect(tok!";"); + + CastQualifier eight = p.parseCastQualifier(); + assert (eight.first == tok!"shared"); + assert (eight.second == tok!"inout"); + p.expect(tok!";"); + + CastQualifier nine = p.parseCastQualifier(); + assert (nine is null); + assert (p.errorCount > 0); + + stderr.writeln("Unittest for parseCastQualifier passed."); + } + + /** + * Parses a Catch + * + * $(GRAMMAR $(RULEDEF catch): + * $(LITERAL 'catch') $(LITERAL '$(LPAREN)') $(RULE type) $(LITERAL Identifier)? $(LITERAL '$(RPAREN)') $(RULE declarationOrStatement) + * ;) + */ + Catch parseCatch() + { + mixin(traceEnterAndExit!(__FUNCTION__)); + auto node = allocate!Catch; + expect(tok!"catch"); + if (expect(tok!"(") is null) return null; + node.type = parseType(); + if (currentIs(tok!"identifier")) + node.identifier = advance(); + if (expect(tok!")") is null) return null; + node.declarationOrStatement = parseDeclarationOrStatement(); + return node; + } + + /** + * Parses a Catches + * + * $(GRAMMAR $(RULEDEF catches): + * $(RULE catch)+ + * | $(RULE catch)* $(RULE lastCatch) + * ;) + */ + Catches parseCatches() + { + mixin(traceEnterAndExit!(__FUNCTION__)); + auto node = allocate!Catches; + Catch[] catches; + while (moreTokens()) + { + if (!currentIs(tok!"catch")) + break; + if (peekIs(tok!"(")) + catches ~= parseCatch(); + else + { + node.lastCatch = parseLastCatch(); + break; + } + } + node.catches = ownArray(catches); + return node; + } + + /** + * Parses a ClassDeclaration + * + * $(GRAMMAR $(RULEDEF classDeclaration): + * $(LITERAL 'class') $(LITERAL Identifier) ($(LITERAL ':') $(RULE baseClassList))? $(LITERAL ';') + * | $(LITERAL 'class') $(LITERAL Identifier) ($(LITERAL ':') $(RULE baseClassList))? $(RULE structBody) + * | $(LITERAL 'class') $(LITERAL Identifier) $(RULE templateParameters) $(RULE constraint)? ($(LITERAL ':') $(RULE baseClassList))? $(RULE structBody) + * | $(LITERAL 'class') $(LITERAL Identifier) $(RULE templateParameters) ($(LITERAL ':') $(RULE baseClassList))? $(RULE constraint)? $(RULE structBody) + * ;) + */ + ClassDeclaration parseClassDeclaration() + { + mixin(traceEnterAndExit!(__FUNCTION__)); + auto node = allocate!ClassDeclaration; + expect(tok!"class"); + auto ident = expect(tok!"identifier"); + if (ident is null) return null; + node.name = *ident; + node.comment = comment; + comment = null; + if (currentIs(tok!";")) + { + advance(); + return node; + } + templateStuff: if (currentIs(tok!"(")) + { + node.templateParameters = parseTemplateParameters(); + constraint: if (currentIs(tok!"if")) + node.constraint = parseConstraint(); + if (node.baseClassList !is null) + goto structBody; + if (currentIs(tok!":")) + { + advance(); + node.baseClassList = parseBaseClassList(); + } + if (currentIs(tok!"if")) + goto constraint; + } + if (currentIs(tok!":")) + { + advance(); + node.baseClassList = parseBaseClassList(); + } + structBody: + node.structBody = parseStructBody(); + return node; + } + + unittest + { + string sourceCode = +q{class ClassZero; +class ClassOne {} +class ClassTwo : Super {} +class ClassThree(A, B) : Super {} +class ClassFour(A, B) if (someTest()) : Super {} +class ClassFive(A, B) : Super if (someTest()) {}}c; + + Parser p = getParserForUnittest(sourceCode, "parseClassDeclaration"); + + auto classZero = p.parseClassDeclaration(); + assert (classZero.name.text == "ClassZero"); + assert (classZero.structBody is null); + + auto classOne = p.parseClassDeclaration(); + assert (classOne.name.text == "ClassOne"); + assert (classOne.structBody.declarations.length == 0); + assert (classOne.baseClassList is null); + assert (classOne.constraint is null); + assert (classOne.templateParameters is null); + + auto classTwo = p.parseClassDeclaration(); + assert (classTwo.name.text == "ClassTwo", classTwo.name.text); + assert (classTwo.baseClassList !is null); + assert (classTwo.baseClassList.items.length == 1, + to!string(classTwo.baseClassList.items.length)); + assert (classTwo.structBody.declarations.length == 0, + to!string(classTwo.structBody.declarations.length)); + + auto classThree = p.parseClassDeclaration(); + assert (classThree.name.text == "ClassThree", classThree.name.text); + assert (classThree.templateParameters !is null); + assert (classThree.templateParameters.templateParameterList.items.length == 2); + assert (classThree.baseClassList !is null); + assert (classThree.baseClassList.items.length == 1); + assert (classThree.structBody.declarations.length == 0, + to!string(classThree.structBody.declarations.length)); + + auto classFour = p.parseClassDeclaration(); + assert (classFour.name.text == "ClassFour", classFour.name.text); + assert (classFour.templateParameters !is null); + assert (classFour.baseClassList !is null); + assert (classFour.constraint !is null); + assert (classFour.baseClassList.items.length == 1); + assert (classFour.structBody.declarations.length == 0, + to!string(classFour.structBody.declarations.length)); + + stderr.writeln("Unittest for parseClassDeclaration passed."); + } + + /** + * Parses a CmpExpression + * + * $(GRAMMAR $(RULEDEF cmpExpression): + * $(RULE shiftExpression) + * | $(RULE equalExpression) + * | $(RULE identityExpression) + * | $(RULE relExpression) + * | $(RULE inExpression) + * ;) + */ + ExpressionNode parseCmpExpression() + { + mixin(traceEnterAndExit!(__FUNCTION__)); + auto node = allocate!CmpExpression; + auto shift = parseShiftExpression(); + if (!moreTokens()) + return shift; + switch (current.type) + { + case tok!"is": + node.identityExpression = parseIdentityExpression(shift); + break; + case tok!"in": + node.inExpression = parseInExpression(shift); + break; + case tok!"!": + if (peekIs(tok!"is")) + node.identityExpression = parseIdentityExpression(shift); + else if (peekIs(tok!"in")) + node.inExpression = parseInExpression(shift); + break; + case tok!"<": + case tok!"<=": + case tok!">": + case tok!">=": + case tok!"!<>=": + case tok!"!<>": + case tok!"<>": + case tok!"<>=": + case tok!"!>": + case tok!"!>=": + case tok!"!<": + case tok!"!<=": + node.relExpression = parseRelExpression(shift); + break; + case tok!"==": + case tok!"!=": + node.equalExpression = parseEqualExpression(shift); + break; + default: + node.shiftExpression = shift; + break; + } + return node; + } + + /** + * Parses a CompileCondition + * + * $(GRAMMAR $(RULEDEF compileCondition): + * $(RULE versionCondition) + * | $(RULE debugCondition) + * | $(RULE staticIfCondition) + * ;) + */ + CompileCondition parseCompileCondition() + { + mixin(traceEnterAndExit!(__FUNCTION__)); + auto node = allocate!CompileCondition; + switch (current.type) + { + case tok!"version": + node.versionCondition = parseVersionCondition(); + break; + case tok!"debug": + node.debugCondition = parseDebugCondition(); + break; + case tok!"static": + node.staticIfCondition = parseStaticIfCondition(); + break; + default: + error(`"version", "debug", or "static" expected`); + return null; + } + return node; + } + + /** + * Parses a ConditionalDeclaration + * + * $(GRAMMAR $(RULEDEF conditionalDeclaration): + * $(RULE compileCondition) $(RULE declaration) + * | $(RULE compileCondition) $(LITERAL ':') $(RULE declaration)+ + * | $(RULE compileCondition) $(RULE declaration) ($(LITERAL 'else') $(RULE declaration))? + * ;) + */ + ConditionalDeclaration parseConditionalDeclaration() + { + mixin(traceEnterAndExit!(__FUNCTION__)); + auto node = allocate!ConditionalDeclaration; + node.compileCondition = parseCompileCondition(); + + Declaration[] trueDeclarations; + if (currentIs(tok!":")) + { + advance(); + while (isDeclaration()) + trueDeclarations ~= parseDeclaration(); + node.trueDeclarations = ownArray(trueDeclarations); + return node; + } + + auto dec = parseDeclaration(); + if (dec is null) return null; + trueDeclarations ~= dec; + node.trueDeclarations = ownArray(trueDeclarations); + + if (currentIs(tok!"else")) + advance(); + else + return node; + + auto elseDec = parseDeclaration(); + if (elseDec is null) return null; + node.falseDeclaration = elseDec; + return node; + } + + /** + * Parses a ConditionalStatement + * + * $(GRAMMAR $(RULEDEF conditionalStatement): + * $(RULE compileCondition) $(RULE declarationOrStatement) ($(LITERAL 'else') $(RULE declarationOrStatement))? + * ;) + */ + ConditionalStatement parseConditionalStatement() + { + mixin(traceEnterAndExit!(__FUNCTION__)); + auto node = allocate!ConditionalStatement; + node.compileCondition = parseCompileCondition(); + node.trueStatement = parseDeclarationOrStatement(); + if (currentIs(tok!"else")) + { + advance(); + node.falseStatement = parseDeclarationOrStatement(); + } + return node; + } + + /** + * Parses a Constraint + * + * $(GRAMMAR $(RULEDEF constraint): + * $(LITERAL 'if') $(LITERAL '$(LPAREN)') $(RULE expression) $(LITERAL '$(RPAREN)') + * ;) + */ + Constraint parseConstraint() + { + mixin(traceEnterAndExit!(__FUNCTION__)); + auto node = allocate!Constraint; + if (expect(tok!"if") is null) return null; + if (expect(tok!"(") is null) return null; + node.expression = parseExpression(); + if (expect(tok!")") is null) return null; + return node; + } + + /** + * Parses a Constructor + * + * $(GRAMMAR $(RULEDEF constructor): + * $(LITERAL 'this') $(RULE templateParameters) $(RULE parameters) $(RULE memberFunctionAttribute)* $(RULE constraint)? ($(RULE functionBody) | $(LITERAL ';')) + * ;) + */ + Constructor parseConstructor() + { + mixin(traceEnterAndExit!(__FUNCTION__)); + Constructor node = allocate!Constructor; + node.comment = comment; + comment = null; + auto t = expect(tok!"this"); + if (t is null) return null; + node.location = t.index; + node.line = t.line; + node.column = t.column; + auto p = peekPastParens(); + bool isTemplate = false; + if (p !is null && p.type == tok!"(") + { + isTemplate = true; + node.templateParameters = parseTemplateParameters(); + } + node.parameters = parseParameters(); + if (node.parameters is null) return null; + + MemberFunctionAttribute[] memberFunctionAttributes; + while (moreTokens() && currentIsMemberFunctionAttribute()) + memberFunctionAttributes ~= parseMemberFunctionAttribute(); + node.memberFunctionAttributes = ownArray(memberFunctionAttributes); + + if (isTemplate && currentIs(tok!"if")) + node.constraint = parseConstraint(); + + if (currentIs(tok!";")) + advance(); + else + { + node.functionBody = parseFunctionBody(); + if (node.functionBody is null) return null; + } + + return node; + } + + /** + * Parses an ContinueStatement + * + * $(GRAMMAR $(RULEDEF continueStatement): + * $(LITERAL 'continue') $(LITERAL Identifier)? $(LITERAL ';') + * ;) + */ + ContinueStatement parseContinueStatement() + { + mixin(traceEnterAndExit!(__FUNCTION__)); + if (expect(tok!"continue") is null) return null; + auto node = allocate!ContinueStatement; + switch (current.type) + { + case tok!"identifier": + node.label = advance(); + if (expect(tok!";") is null) return null; + break; + case tok!";": + advance(); + break; + default: + error(`Identifier or semicolon expected following "continue"`); + return null; + } + return node; + } + + /** + * Parses a DebugCondition + * + * $(GRAMMAR $(RULEDEF debugCondition): + * $(LITERAL 'debug') ($(LITERAL '$(LPAREN)') ($(LITERAL IntegerLiteral) | $(LITERAL Identifier)) $(LITERAL '$(RPAREN)'))? + * ;) + */ + DebugCondition parseDebugCondition() + { + mixin(traceEnterAndExit!(__FUNCTION__)); + auto node = allocate!DebugCondition; + if (expect(tok!"debug") is null) return null; + if (currentIs(tok!"(")) + { + advance(); + if (currentIsOneOf(tok!"intLiteral", tok!"identifier")) + node.identifierOrInteger = advance(); + else + { + error(`Integer literal or identifier expected`); + return null; + } + if (expect(tok!")") is null) return null; + } + return node; + } + + /** + * Parses a DebugSpecification + * + * $(GRAMMAR $(RULEDEF debugSpecification): + * $(LITERAL 'debug') $(LITERAL '=') ($(LITERAL Identifier) | $(LITERAL IntegerLiteral)) $(LITERAL ';') + * ;) + */ + DebugSpecification parseDebugSpecification() + { + mixin(traceEnterAndExit!(__FUNCTION__)); + auto node = allocate!DebugSpecification; + if (expect(tok!"debug") is null) return null; + if (expect(tok!"=") is null) return null; + if (currentIsOneOf(tok!"identifier", tok!"intLiteral")) + node.identifierOrInteger = advance(); + else + { + error("Integer literal or identifier expected"); + return null; + } + if (expect(tok!";") is null) return null; + return node; + } + + /** + * Parses a Declaration + * + * $(GRAMMAR $(RULEDEF declaration): + * $(RULE attribute)* $(declaration2) + * ; + * $(RULEDEF declaration2): + * $(RULE aliasDeclaration) + * | $(RULE aliasThisDeclaration) + * | $(RULE classDeclaration) + * | $(RULE conditionalDeclaration) + * | $(RULE constructor) + * | $(RULE destructor) + * | $(RULE enumDeclaration) + * | $(RULE functionDeclaration) + * | $(RULE importDeclaration) + * | $(RULE interfaceDeclaration) + * | $(RULE mixinDeclaration) + * | $(RULE mixinTemplateDeclaration) + * | $(RULE pragmaDeclaration) + * | $(RULE sharedStaticConstructor) + * | $(RULE sharedStaticDestructor) + * | $(RULE staticAssertDeclaration) + * | $(RULE staticConstructor) + * | $(RULE staticDestructor) + * | $(RULE structDeclaration) + * | $(RULE templateDeclaration) + * | $(RULE unionDeclaration) + * | $(RULE unittest) + * | $(RULE variableDeclaration) + * | $(RULE attributeDeclaration) + * | $(RULE invariant) + * | $(LITERAL '{') $(RULE declaration)+ $(LITERAL '}') + * ;) + */ + Declaration parseDeclaration() + { + mixin(traceEnterAndExit!(__FUNCTION__)); + auto node = allocate!Declaration; + comment = current.comment; + Attribute[] attributes; + do + { + if (!isAttribute()) + break; + auto attr = parseAttribute(); + if (attr is null) + { + error("attribute is null"); + break; + } + if (currentIs(tok!":")) + { + node.attributeDeclaration = parseAttributeDeclaration(attr); + node.attributes = ownArray(attributes); + return node; + } + else + attributes ~= attr; + } while (moreTokens()); + node.attributes = ownArray(attributes); + + switch (current.type) + { + case tok!";": + // http://d.puremagic.com/issues/show_bug.cgi?id=4559 + warn("Empty declaration"); + advance(); + break; + case tok!"{": + advance(); + Declaration[] declarations; + while (moreTokens() && !currentIs(tok!"}")) + { + auto declaration = parseDeclaration(); + if (declaration !is null) + declarations ~= declaration; + } + node.declarations = ownArray(declarations); + if (expect(tok!"}") is null) return null; + break; + case tok!"alias": + if (startsWith(tok!"alias", tok!"identifier", tok!"this")) + node.aliasThisDeclaration = parseAliasThisDeclaration(); + else + node.aliasDeclaration = parseAliasDeclaration(); + break; + case tok!"class": + node.classDeclaration = parseClassDeclaration(); + break; + case tok!"this": + if (startsWith(tok!"this", tok!"(", tok!"this")) + { + node.postblit = parsePostblit(); + if (node.postblit is null) return null; + } + else + { + node.constructor = parseConstructor(); + if (node.constructor is null) return null; + } + break; + case tok!"~": + node.destructor = parseDestructor(); + if (node.destructor is null) return null; + break; + case tok!"enum": + if (startsWith(tok!"enum", tok!"identifier", tok!"(")) + goto case tok!"template"; + node.enumDeclaration = parseEnumDeclaration(); + if (node.enumDeclaration is null) return null; + break; + case tok!"import": + node.importDeclaration = parseImportDeclaration(); + if (node.importDeclaration is null) return null; + break; + case tok!"interface": + node.interfaceDeclaration = parseInterfaceDeclaration(); + if (node.interfaceDeclaration is null) return null; + break; + case tok!"mixin": + if (peekIs(tok!"template")) + node.mixinTemplateDeclaration = parseMixinTemplateDeclaration(); + else + { + auto b = setBookmark(); + advance(); + if (currentIs(tok!"(")) + { + auto t = peekPastParens(); + if (t !is null && t.type == tok!";") + { + goToBookmark(b); + node.mixinDeclaration = parseMixinDeclaration(); + } + else + { + goToBookmark(b); + error("Declaration expected"); + advance(); + return null; + } + } + else + { + goToBookmark(b); + node.mixinDeclaration = parseMixinDeclaration(); + } + } + break; + case tok!"pragma": + node.pragmaDeclaration = parsePragmaDeclaration(); + break; + case tok!"shared": + if (startsWith(tok!"shared", tok!"static", tok!"this")) + node.sharedStaticConstructor = parseSharedStaticConstructor(); + else if (startsWith(tok!"shared", tok!"static", tok!"~")) + node.sharedStaticDestructor = parseSharedStaticDestructor(); + else + goto type; + break; + case tok!"static": + if (peekIs(tok!"this")) + node.staticConstructor = parseStaticConstructor(); + else if (peekIs(tok!"~")) + node.staticDestructor = parseStaticDestructor(); + else if (peekIs(tok!"if")) + node.conditionalDeclaration = parseConditionalDeclaration(); + else if (peekIs(tok!"assert")) + node.staticAssertDeclaration = parseStaticAssertDeclaration(); + else + goto type; + break; + case tok!"struct": + node.structDeclaration = parseStructDeclaration(); + break; + case tok!"template": + node.templateDeclaration = parseTemplateDeclaration(); + break; + case tok!"union": + node.unionDeclaration = parseUnionDeclaration(); + break; + case tok!"invariant": + node.invariant_ = parseInvariant(); + break; + case tok!"unittest": + node.unittest_ = parseUnittest(); + break; + case tok!"identifier": + if (node.attributes.length > 0 + && node.attributes[$ - 1].storageClass !is null) + { + if (peekIs(tok!"=")) + node.variableDeclaration = parseVariableDeclaration(null, true); + else if (peekIs(tok!"(")) + node.functionDeclaration = parseFunctionDeclaration(null, true, node.attributes); + else + goto type; + } + else + goto type; + break; + case tok!"const": + case tok!"immutable": + case tok!"inout": + case tok!"scope": + case tok!"typeof": + mixin (BASIC_TYPE_CASES); + type: + Type type = parseType(); + if (!currentIs(tok!"identifier")) + { + error("Identifier expected"); + return null; + } + if (peekIs(tok!"(")) + node.functionDeclaration = parseFunctionDeclaration(type, false, node.attributes); + else + node.variableDeclaration = parseVariableDeclaration(type); + break; + case tok!"version": + if (peekIs(tok!"(")) + node.conditionalDeclaration = parseConditionalDeclaration(); + else if (peekIs(tok!"=")) + node.versionSpecification = parseVersionSpecification(); + else + { + error(`"=" or "(" expected following "version"`); + return null; + } + break; + case tok!"debug": + node.conditionalDeclaration = parseConditionalDeclaration(); + if (node.conditionalDeclaration is null) return null; + break; + default: + error("Declaration expected"); + if (moreTokens()) + advance(); + return null; + } + return node; + } + + /** + * Parses DeclarationsAndStatements + * + * $(GRAMMAR $(RULEDEF declarationsAndStatements): + * $(RULE declarationOrStatement)+ + * ;) + */ + DeclarationsAndStatements parseDeclarationsAndStatements() + { + mixin(traceEnterAndExit!(__FUNCTION__)); + auto node = allocate!DeclarationsAndStatements; + DeclarationOrStatement[] declarationsAndStatements; + while (!currentIsOneOf(tok!"}") && moreTokens()) + { + auto dos = parseDeclarationOrStatement(); + if (dos !is null) + declarationsAndStatements ~= dos; + } + node.declarationsAndStatements = ownArray(declarationsAndStatements); + return node; + } + + /** + * Parses a DeclarationOrStatement + * + * $(GRAMMAR $(RULEDEF declarationOrStatement): + * $(RULE declaration) + * | $(RULE statement) + * ;) + */ + DeclarationOrStatement parseDeclarationOrStatement() + { + mixin(traceEnterAndExit!(__FUNCTION__)); + auto node = allocate!DeclarationOrStatement; + // "Any ambiguities in the grammar between Statements and + // Declarations are resolved by the declarations taking precedence." + if (isDeclaration()) + { + trace("\033[01;36mparsing declaration\033[0m"); + node.declaration = parseDeclaration(); + } + else + { + trace("\033[01;36mparsing statement\033[0m"); + node.statement = parseStatement(); + } + + if (node.statement is null && node.declaration is null) + { + error("Could not parse declaration or statement"); + return null; + } + return node; + } + + /** + * Parses a Declarator + * + * $(GRAMMAR $(RULEDEF declarator): + * $(LITERAL Identifier) ($(LITERAL '=') $(RULE initializer))? + * ;) + */ + Declarator parseDeclarator() + { + mixin(traceEnterAndExit!(__FUNCTION__)); + auto node = allocate!Declarator; + auto id = expect(tok!"identifier"); + if (id is null) return null; + node.name = *id; + if (currentIs(tok!"[")) // dmd doesn't accept pointer after identifier + { + warn("C-style array declaration."); + TypeSuffix[] typeSuffixes; + while (moreTokens() && currentIs(tok!"[")) + { + auto suffix = parseTypeSuffix(); + if (suffix !is null) + typeSuffixes ~= suffix; + else + return null; + } + node.cstyle = ownArray(typeSuffixes); + } + if (currentIs(tok!"=")) + { + advance(); + node.initializer = parseInitializer(); + } + return node; + } + + /** + * Parses a DefaultStatement + * + * $(GRAMMAR $(RULEDEF defaultStatement): + * $(LITERAL 'default') $(LITERAL ':') $(RULE declarationsAndStatements) + * ;) + */ + DefaultStatement parseDefaultStatement() + { + mixin(traceEnterAndExit!(__FUNCTION__)); + auto node = allocate!DefaultStatement; + if (expect(tok!"default") is null) return null; + if (expect(tok!":") is null) return null; + node.declarationsAndStatements = parseDeclarationsAndStatements(); + return node; + } + + /** + * Parses a DeleteExpression + * + * $(GRAMMAR $(RULEDEF deleteExpression): + * $(LITERAL 'delete') $(RULE unaryExpression) + * ;) + */ + DeleteExpression parseDeleteExpression() + { + mixin(traceEnterAndExit!(__FUNCTION__)); + auto node = allocate!DeleteExpression; + node.line = current.line; + node.column = current.column; + if (expect(tok!"delete") is null) return null; + node.unaryExpression = parseUnaryExpression(); + return node; + } + + /** + * Parses a Deprecated attribute + * + * $(GRAMMAR $(RULEDEF deprecated): + * $(LITERAL 'deprecated') ($(LITERAL '$(LPAREN)') $(RULE assignExpression) $(LITERAL '$(RPAREN)'))? + * ;) + */ + Deprecated parseDeprecated() + { + mixin(traceEnterAndExit!(__FUNCTION__)); + auto node = allocate!Deprecated; + if (expect(tok!"deprecated") is null) return null; + if (currentIs(tok!"(")) + { + advance(); + node.assignExpression = parseAssignExpression(); + if (expect(tok!")") is null) return null; + } + return node; + } + + /** + * Parses a Destructor + * + * $(GRAMMAR $(RULEDEF destructor): + * $(LITERAL '~') $(LITERAL 'this') $(LITERAL '$(LPAREN)') $(LITERAL '$(RPAREN)') $(RULE memberFunctionAttribute)* ($(RULE functionBody) | $(LITERAL ';')) + * ;) + */ + Destructor parseDestructor() + { + mixin(traceEnterAndExit!(__FUNCTION__)); + auto node = allocate!Destructor; + node.comment = comment; + comment = null; + if (expect(tok!"~") is null) return null; + node.index = current.index; + node.line = current.line; + node.column = current.column; + if (expect(tok!"this") is null) return null; + if (expect(tok!"(") is null) return null; + if (expect(tok!")") is null) return null; + if (currentIs(tok!";")) + advance(); + else + { + MemberFunctionAttribute[] memberFunctionAttributes; + while (moreTokens() && currentIsMemberFunctionAttribute()) + memberFunctionAttributes ~= parseMemberFunctionAttribute(); + node.memberFunctionAttributes = ownArray(memberFunctionAttributes); + node.functionBody = parseFunctionBody(); + } + return node; + } + + unittest + { + auto sourceCode = q{~this(){}}c; + Parser p = getParserForUnittest(sourceCode, "parseDestructor"); + Destructor d = p.parseDestructor(); + assert (d !is null); + assert (d.functionBody !is null); + assert (p.errorCount == 0); + stderr.writeln("Unittest for parseDestructor passed."); + } + + /** + * Parses a DoStatement + * + * $(GRAMMAR $(RULEDEF doStatement): + * $(LITERAL 'do') $(RULE statementNoCaseNoDefault) $(LITERAL 'while') $(LITERAL '$(LPAREN)') $(RULE expression) $(LITERAL '$(RPAREN)') $(LITERAL ';') + * ;) + */ + DoStatement parseDoStatement() + { + mixin(traceEnterAndExit!(__FUNCTION__)); + auto node = allocate!DoStatement; + if (expect(tok!"do") is null) return null; + node.statementNoCaseNoDefault = parseStatementNoCaseNoDefault(); + if (expect(tok!"while") is null) return null; + if (expect(tok!"(") is null) return null; + node.expression = parseExpression(); + if (expect(tok!")") is null) return null; + if (expect(tok!";") is null) return null; + return node; + } + + /** + * Parses an EnumBody + * + * $(GRAMMAR $(RULEDEF enumBody): + * $(LITERAL ';') + * | $(LITERAL '{') $(RULE enumMember) ($(LITERAL ',') $(RULE enumMember)?)* $(LITERAL '}') + * ;) + */ + EnumBody parseEnumBody() + { + mixin(traceEnterAndExit!(__FUNCTION__)); + EnumBody node = allocate!EnumBody; + if (!currentIs(tok!";")) + { + auto open = expect (tok!"{"); + if (open is null) goto ret; + node.startLocation = open.index; + EnumMember[] enumMembers; + while (moreTokens()) + { + if (!currentIsOneOf(tok!",", tok!"}")) + enumMembers ~= parseEnumMember(); + else if (currentIs(tok!",")) + { + advance(); + continue; + } + else if (currentIs(tok!"}")) + break; + else + { + error(`",", "}", or enum member expected`); + goto ret; + } + } + node.enumMembers = ownArray(enumMembers); + auto close = expect (tok!"}"); + if (close !is null) + node.endLocation = close.index; + } + ret: + return node; + } + + /** + * Parses an EnumDeclaration + * + * $(GRAMMAR $(RULEDEF enumDeclaration): + * $(LITERAL 'enum') $(LITERAL Identifier)? ($(LITERAL ':') $(RULE type))? $(RULE enumBody) + * ;) + */ + EnumDeclaration parseEnumDeclaration() + { + mixin(traceEnterAndExit!(__FUNCTION__)); + auto node = allocate!EnumDeclaration; + if (expect(tok!"enum") is null) return null; + if (currentIs(tok!"identifier")) + node.name = advance(); + else + node.name.line = tokens[index - 1].line; // preserve line number if anonymous + node.comment = comment; + comment = null; + if (currentIs(tok!":")) + { + advance(); + node.type = parseType(); + } + node.enumBody = parseEnumBody(); + return node; + } + + /** + * Parses an EnumMember + * + * $(GRAMMAR $(RULEDEF enumMember): + * $(LITERAL Identifier) + * | ($(LITERAL Identifier) | $(RULE type)) $(LITERAL '=') $(RULE assignExpression) + * ;) + */ + EnumMember parseEnumMember() + { + mixin(traceEnterAndExit!(__FUNCTION__)); + auto node = allocate!EnumMember; + node.comment = current.comment; + if (currentIs(tok!"identifier")) + { + if (peekIsOneOf(tok!",", tok!"}")) + node.name = advance(); + else if (peekIs(tok!"=")) + { + node.name = advance(); + goto assign; + } + else + goto type; + } + else + { + type: + node.type = parseType(); + assign: + expect(tok!"="); + node.assignExpression = parseAssignExpression(); + } + return node; + } + + /** + * Parses an EqualExpression + * + * $(GRAMMAR $(RULEDEF equalExpression): + * $(RULE shiftExpression) ($(LITERAL '==') | $(LITERAL '!=')) $(RULE shiftExpression) + * ;) + */ + EqualExpression parseEqualExpression(ExpressionNode shift = null) + { + mixin(traceEnterAndExit!(__FUNCTION__)); + auto node = allocate!EqualExpression; + node.left = shift is null ? parseShiftExpression() : shift; + if (currentIsOneOf(tok!"==", tok!"!=")) + node.operator = advance().type; + node.right = parseShiftExpression(); + return node; + } + + /** + * Parses an Expression + * + * $(GRAMMAR $(RULEDEF expression): + * $(RULE assignExpression) ($(LITERAL ',') $(RULE assignExpression))* + * ;) + */ + Expression parseExpression() + { + mixin(traceEnterAndExit!(__FUNCTION__)); + return parseCommaSeparatedRule!(Expression, AssignExpression)(); + } + + /** + * Parses an ExpressionStatement + * + * $(GRAMMAR $(RULEDEF expressionStatement): + * $(RULE expression) $(LITERAL ';') + * ;) + */ + ExpressionStatement parseExpressionStatement(Expression expression = null) + { + mixin(traceEnterAndExit!(__FUNCTION__)); + auto node = allocate!ExpressionStatement; + node.expression = expression is null ? parseExpression() : expression; + if (expect(tok!";") is null) return null; + return node; + } + + /** + * Parses a FinalSwitchStatement + * + * $(GRAMMAR $(RULEDEF finalSwitchStatement): + * $(LITERAL 'final') $(RULE switchStatement) + * ;) + */ + FinalSwitchStatement parseFinalSwitchStatement() + { + mixin(traceEnterAndExit!(__FUNCTION__)); + auto node = allocate!FinalSwitchStatement; + if (expect(tok!"final") is null) return null; + node.switchStatement = parseSwitchStatement(); + if (node.switchStatement is null) return null; + return node; + } + + /** + * Parses a Finally + * + * $(GRAMMAR $(RULEDEF finally): + * $(LITERAL 'finally') $(RULE declarationOrStatement) + * ;) + */ + Finally parseFinally() + { + mixin(traceEnterAndExit!(__FUNCTION__)); + auto node = allocate!Finally; + if (expect(tok!"finally") is null) return null; + node.declarationOrStatement = parseDeclarationOrStatement(); + return node; + } + + /** + * Parses a ForStatement + * + * $(GRAMMAR $(RULEDEF forStatement): + * $(LITERAL 'for') $(LITERAL '$(LPAREN)') $(RULE declarationOrStatement) $(RULE expression)? $(LITERAL ';') $(RULE expression)? $(LITERAL '$(RPAREN)') $(RULE declarationOrStatement) + * ;) + */ + ForStatement parseForStatement() + { + mixin(traceEnterAndExit!(__FUNCTION__)); + auto node = allocate!ForStatement; + if (expect(tok!"for") is null) return null; + node.startIndex = current().index; + if (expect(tok!"(") is null) return null; + + if (currentIs(tok!";")) + advance(); + else + node.initialization = parseDeclarationOrStatement(); + + if (currentIs(tok!";")) + advance(); + else + node.test = parseExpressionStatement(); + + if (!currentIs(tok!")")) + node.increment = parseExpression(); + + if (expect(tok!")") is null) return null; + if (currentIs(tok!"}")) + { + error("Statement expected", false); + return node; // this line makes DCD better + } + node.declarationOrStatement = parseDeclarationOrStatement(); + if (node.declarationOrStatement is null) return null; + return node; + } + + /** + * Parses a ForeachStatement + * + * $(GRAMMAR $(RULEDEF foreachStatement): + * ($(LITERAL 'foreach') | $(LITERAL 'foreach_reverse')) $(LITERAL '$(LPAREN)') $(RULE foreachTypeList) $(LITERAL ';') $(RULE expression) $(LITERAL '$(RPAREN)') $(RULE declarationOrStatement) + * | ($(LITERAL 'foreach') | $(LITERAL 'foreach_reverse')) $(LITERAL '$(LPAREN)') $(RULE foreachType) $(LITERAL ';') $(RULE expression) $(LITERAL '..') $(RULE expression) $(LITERAL '$(RPAREN)') $(RULE declarationOrStatement) + * ;) + */ + ForeachStatement parseForeachStatement() + { + mixin(traceEnterAndExit!(__FUNCTION__)); + ForeachStatement node = allocate!ForeachStatement; + if (currentIsOneOf(tok!"foreach", tok!"foreach_reverse")) + node.type = advance().type; + else + { + error(`"foreach" or "foreach_reverse" expected`); + return null; + } + node.startIndex = current().index; + if (expect(tok!"(") is null) return null; + ForeachTypeList feType = parseForeachTypeList(); + bool canBeRange = feType.items.length == 1; + + if (expect(tok!";") is null) return null; + node.low = parseExpression(); + if (node.low is null) return null; + if (currentIs(tok!"..")) + { + if (!canBeRange) + { + error(`Cannot have more than one foreach variable for a foreach range statement`); + return null; + } + advance(); + node.high = parseExpression(); + node.foreachType = feType.items[0]; + if (node.high is null) return null; + } + else + { + node.foreachTypeList = feType; + } + if (expect(tok!")") is null) return null; + if (currentIs(tok!"}")) + { + error("Statement expected", false); + return node; // this line makes DCD better + } + node.declarationOrStatement = parseDeclarationOrStatement(); + if (node.declarationOrStatement is null) return null; + return node; + } + + /** + * Parses a ForeachType + * + * $(GRAMMAR $(RULEDEF foreachType): + * $(RULE typeConstructors)? $(RULE type)? $(LITERAL Identifier) + * ;) + */ + ForeachType parseForeachType() + { + mixin(traceEnterAndExit!(__FUNCTION__)); + auto node = allocate!ForeachType; + if (currentIsOneOf(tok!"ref", tok!"const", tok!"immutable", + tok!"shared", tok!"inout")) + { + trace("\033[01;36mType constructor"); + if ((node.typeConstructors = parseTypeConstructors()) is null) + return null; + } + if (currentIs(tok!"identifier") && peekIsOneOf(tok!",", tok!";")) + { + node.identifier = advance(); + return node; + } + if ((node.type = parseType()) is null) return null; + auto ident = expect(tok!"identifier"); + if (ident is null) return null; + node.identifier = *ident; + return node; + } + + /** + * Parses a ForeachTypeList + * + * $(GRAMMAR $(RULEDEF foreachTypeList): + * $(RULE foreachType) ($(LITERAL ',') $(RULE foreachType))* + * ;) + */ + ForeachTypeList parseForeachTypeList() + { + mixin(traceEnterAndExit!(__FUNCTION__)); + return parseCommaSeparatedRule!(ForeachTypeList, ForeachType)(); + } + + /** + * Parses a FunctionAttribute + * + * $(GRAMMAR $(RULEDEF functionAttribute): + * $(RULE atAttribute) + * | $(LITERAL 'pure') + * | $(LITERAL 'nothrow') + * ;) + */ + FunctionAttribute parseFunctionAttribute(bool validate = true) + { + auto node = allocate!FunctionAttribute; + switch (current.type) + { + case tok!"@": + node.atAttribute = parseAtAttribute(); + break; + case tok!"pure": + case tok!"nothrow": + node.token = advance(); + break; + default: + if (validate) + error(`@attribute, "pure", or "nothrow" expected`); + return null; + } + return node; + } + + /** + * Parses a FunctionBody + * + * $(GRAMMAR $(RULEDEF functionBody): + * $(RULE blockStatement) + * | ($(RULE inStatement) | $(RULE outStatement) | $(RULE outStatement) $(RULE inStatement) | $(RULE inStatement) $(RULE outStatement))? $(RULE bodyStatement) + * ;) + */ + FunctionBody parseFunctionBody() + { + auto node = allocate!FunctionBody; + if (currentIs(tok!";")) + { + advance(); + return node; + } + else if (currentIs(tok!"{")) + node.blockStatement = parseBlockStatement(); + else + { + if (currentIs(tok!"in")) + { + node.inStatement = parseInStatement(); + if (currentIs(tok!"out")) + node.outStatement = parseOutStatement(); + } + else if (currentIs(tok!"out")) + { + node.outStatement = parseOutStatement(); + if (currentIs(tok!"in")) + node.inStatement = parseInStatement(); + } + node.bodyStatement = parseBodyStatement(); + } + return node; + } + + unittest + { + auto sourceCode = q{ +{} // one +in {} body{} // two +out {} body{} // three +in {} out {} body {} // four +out {} in {} body {} // five +body {} // six + }; + + Parser p = getParserForUnittest(sourceCode, "parseFunctionBody"); + + FunctionBody functionBodyOne = p.parseFunctionBody(); + assert (functionBodyOne.blockStatement !is null); + + FunctionBody functionBodyTwo = p.parseFunctionBody(); + assert (functionBodyTwo.blockStatement is null); + assert (functionBodyTwo.inStatement !is null); + assert (functionBodyTwo.outStatement is null); + assert (functionBodyTwo.bodyStatement !is null); + + FunctionBody functionBodyThree = p.parseFunctionBody(); + assert (functionBodyThree.blockStatement is null); + assert (functionBodyThree.inStatement is null); + assert (functionBodyThree.outStatement !is null); + assert (functionBodyThree.bodyStatement !is null); + + FunctionBody functionBodyFour = p.parseFunctionBody(); + assert (functionBodyFour.blockStatement is null); + assert (functionBodyFour.inStatement !is null); + assert (functionBodyFour.outStatement !is null); + assert (functionBodyFour.bodyStatement !is null); + + FunctionBody functionBodyFive = p.parseFunctionBody(); + assert (functionBodyFive.blockStatement is null); + assert (functionBodyFive.inStatement !is null); + assert (functionBodyFive.outStatement !is null); + assert (functionBodyFive.bodyStatement !is null); + + FunctionBody functionBodySix = p.parseFunctionBody(); + assert (functionBodySix.blockStatement is null); + assert (functionBodySix.inStatement is null); + assert (functionBodySix.outStatement is null); + assert (functionBodySix.bodyStatement !is null); + + stderr.writeln("Unittest for parseFunctionBody passed."); + } + + /** + * Parses a FunctionCallExpression + * + * $(GRAMMAR $(RULEDEF functionCallExpression): + * $(RULE unaryExpression) $(RULE templateArguments)? $(RULE arguments) + * | $(RULE type) $(RULE arguments) + * ;) + */ + FunctionCallExpression parseFunctionCallExpression(UnaryExpression unary = null) + { + mixin(traceEnterAndExit!(__FUNCTION__)); + auto node = allocate!FunctionCallExpression; + switch (current.type) + { + case tok!"const": + case tok!"immutable": + case tok!"inout": + case tok!"shared": + case tok!"scope": + case tok!"pure": + case tok!"nothrow": + node.type = parseType(); + node.arguments = parseArguments(); + break; + default: + if (unary !is null) + node.unaryExpression = unary; + else + node.unaryExpression = parseUnaryExpression(); + if (currentIs(tok!"!")) + node.templateArguments = parseTemplateArguments(); + if (unary !is null) + node.arguments = parseArguments(); + } + return node.arguments is null ? null : node; + } + + /** + * Parses a FunctionCallStatement + * + * $(GRAMMAR $(RULEDEF functionCallStatement): + * $(RULE functionCallExpression) $(LITERAL ';') + * ;) + */ + FunctionCallStatement parseFunctionCallStatement() + { + mixin(traceEnterAndExit!(__FUNCTION__)); + auto node = allocate!FunctionCallStatement; + node.functionCallExpression = parseFunctionCallExpression(); + if (expect(tok!";") is null) return null; + return node; + } + + /** + * Parses a FunctionDeclaration + * + * $(GRAMMAR $(RULEDEF functionDeclaration): + * ($(RULE storageClass) | $(RULE _type)) $(LITERAL Identifier) $(RULE templateParameters) $(RULE parameters) $(RULE memberFunctionAttribute)* $(RULE constraint)? ($(RULE functionBody) | $(LITERAL ';')) + * ;) + */ + FunctionDeclaration parseFunctionDeclaration(Type type = null, bool isAuto = false, Attribute[] attributes = null) + { + mixin(traceEnterAndExit!(__FUNCTION__)); + auto node = allocate!FunctionDeclaration; + node.comment = comment; + comment = null; + MemberFunctionAttribute[] memberFunctionAttributes; + + node.attributes = attributes; + + if (isAuto) + { + foreach (a; node.attributes) + { + if (a.storageClass is null) + continue; + if (a.storageClass.token == tok!"auto") + node.hasAuto = true; + if (a.storageClass.token == tok!"ref") + node.hasRef = true; + } + goto functionName; + } + + while (moreTokens() && currentIsMemberFunctionAttribute()) + memberFunctionAttributes ~= parseMemberFunctionAttribute(); + + node.returnType = type is null ? parseType() : type; + + functionName: + auto ident = expect(tok!"identifier"); + if (ident is null) return null; + + node.name = *ident; + + if (!currentIs(tok!"(")) + { + error(`"(" expected`); + return null; + } + + assert (currentIs(tok!"(")); + auto p = peekPastParens(); + bool isTemplate = p !is null && p.type == tok!"("; + + if (isTemplate) + node.templateParameters = parseTemplateParameters(); + + node.parameters = parseParameters(); + if (node.parameters is null) return null; + + while (moreTokens() && currentIsMemberFunctionAttribute()) + memberFunctionAttributes ~= parseMemberFunctionAttribute(); + + if (isTemplate && currentIs(tok!"if")) + node.constraint = parseConstraint(); + + if (currentIs(tok!";")) + advance(); + else + node.functionBody = parseFunctionBody(); + node.memberFunctionAttributes = ownArray(memberFunctionAttributes); + return node; + } + + /** + * Parses a FunctionLiteralExpression + * + * $(GRAMMAR $(RULEDEF functionLiteralExpression): + * (($(LITERAL 'function') | $(LITERAL 'delegate')) $(RULE type)?)? ($(RULE parameters) $(RULE functionAttribute)*)? $(RULE functionBody) + * ;) + */ + FunctionLiteralExpression parseFunctionLiteralExpression() + { + auto node = allocate!FunctionLiteralExpression; + if (currentIsOneOf(tok!"function", tok!"delegate")) + { + node.functionOrDelegate = advance().type; + if (!currentIsOneOf(tok!"(", tok!"in", tok!"body", + tok!"out", tok!"}")) + { + node.type = parseType(); + if (node.type is null) return null; + } + } + if (currentIs(tok!"(")) + { + node.parameters = parseParameters(); + if (node.parameters is null) return null; + FunctionAttribute[] functionAttributes; + do + { + auto attr = parseFunctionAttribute(false); + if (attr is null) + break; + else + functionAttributes ~= attr; + } while (moreTokens()); + node.functionAttributes = ownArray(functionAttributes); + } + node.functionBody = parseFunctionBody(); + if (node.functionBody is null) return null; + return node; + } + + /** + * Parses a GotoStatement + * + * $(GRAMMAR $(RULEDEF gotoStatement): + * $(LITERAL 'goto') ($(LITERAL Identifier) | $(LITERAL 'default') | $(LITERAL 'case') $(RULE expression)?) $(LITERAL ';') + * ;) + */ + GotoStatement parseGotoStatement() + { + auto node = allocate!GotoStatement; + if (expect(tok!"goto") is null) return null; + switch (current.type) + { + case tok!"identifier": + case tok!"default": + node.label = advance(); + break; + case tok!"case": + node.label = advance(); + if (!currentIs(tok!";")) + node.expression = parseExpression(); + break; + default: + error(`Identifier, "default", or "case" expected`); + return null; + } + if (expect(tok!";") is null) return null; + return node; + } + + /** + * Parses an IdentifierChain + * + * $(GRAMMAR $(RULEDEF identifierChain): + * $(LITERAL Identifier) ($(LITERAL '.') $(LITERAL Identifier))* + * ;) + */ + IdentifierChain parseIdentifierChain() + { + auto node = allocate!IdentifierChain; + Token[] identifiers; + while (moreTokens()) + { + auto ident = expect(tok!"identifier"); + if (ident is null) return null; + identifiers ~= *ident; + if (currentIs(tok!".")) + { + advance(); + continue; + } + else + break; + } + node.identifiers = ownArray(identifiers); + return node; + } + + /** + * Parses an IdentifierList + * + * $(GRAMMAR $(RULEDEF identifierList): + * $(LITERAL Identifier) ($(LITERAL ',') $(LITERAL Identifier))* + * ;) + */ + IdentifierList parseIdentifierList() + { + auto node = allocate!IdentifierList; + Token[] identifiers; + do + { + auto ident = expect(tok!"identifier"); + if (ident is null) return null; + identifiers ~= *ident; + if (currentIs(tok!",")) + { + advance(); + continue; + } + else + break; + } while (moreTokens()); + node.identifiers = ownArray(identifiers); + return node; + } + + /** + * Parses an IdentifierOrTemplateChain + * + * $(GRAMMAR $(RULEDEF identifierOrTemplateChain): + * $(RULE identifierOrTemplateInstance) ($(LITERAL '.') $(RULE identifierOrTemplateInstance))* + * ;) + */ + IdentifierOrTemplateChain parseIdentifierOrTemplateChain() + { + auto node = allocate!IdentifierOrTemplateChain; + IdentifierOrTemplateInstance[] identifiersOrTemplateInstances; + while (moreTokens()) + { + identifiersOrTemplateInstances ~= parseIdentifierOrTemplateInstance(); + if (!currentIs(tok!".")) + break; + else + advance(); + } + node.identifiersOrTemplateInstances = ownArray(identifiersOrTemplateInstances); + return node; + } + + /** + * Parses an IdentifierOrTemplateInstance + * + * $(GRAMMAR $(RULEDEF identifierOrTemplateInstance): + * $(LITERAL Identifier) + * | $(RULE templateInstance) + * ;) + */ + IdentifierOrTemplateInstance parseIdentifierOrTemplateInstance() + { + mixin(traceEnterAndExit!(__FUNCTION__)); + auto node = allocate!IdentifierOrTemplateInstance; + if (peekIs(tok!"!") && !startsWith(tok!"identifier", + tok!"!", tok!"is") + && !startsWith(tok!"identifier", tok!"!", tok!"in")) + { + node.templateInstance = parseTemplateInstance(); + } + else + { + auto ident = expect(tok!"identifier"); + if (ident is null) return null; + node.identifier = *ident; + } + return node; + } + + /** + * Parses an IdentityExpression + * + * $(GRAMMAR $(RULEDEF identityExpression): + * $(RULE shiftExpression) ($(LITERAL 'is') | $(LITERAL '!') $(LITERAL 'is')) $(RULE shiftExpression) + * ;) + */ + ExpressionNode parseIdentityExpression(ExpressionNode shift = null) + { + mixin(traceEnterAndExit!(__FUNCTION__)); + auto node = allocate!IdentityExpression; + node.left = shift is null ? parseShiftExpression() : shift; + if (currentIs(tok!"!")) + { + advance(); + node.negated = true; + } + if (expect(tok!"is") is null) return null; + node.right = parseShiftExpression(); + return node; + } + + /** + * Parses an IfStatement + * + * $(GRAMMAR $(RULEDEF ifStatement): + * $(LITERAL 'if') $(LITERAL '$(LPAREN)') $(RULE ifCondition) $(LITERAL '$(RPAREN)') $(RULE declarationOrStatement) ($(LITERAL 'else') $(RULE declarationOrStatement))? + * $(RULEDEF ifCondition): + * $(LITERAL 'auto') $(LITERAL Identifier) $(LITERAL '=') $(RULE expression) + * | $(RULE type) $(LITERAL Identifier) $(LITERAL '=') $(RULE expression) + * | $(RULE expression) + * ;) + */ + IfStatement parseIfStatement() + { + mixin(traceEnterAndExit!(__FUNCTION__)); + auto node = allocate!IfStatement; + node.line = current().line; + node.column = current().column; + if (expect(tok!"if") is null) return null; + node.startIndex = current().index; + if (expect(tok!"(") is null) return null; + + if (currentIs(tok!"auto")) + { + advance(); + auto i = expect(tok!"identifier"); + if (i !is null) + node.identifier = *i; + expect(tok!"="); + node.expression = parseExpression(); + } + else + { + auto b = setBookmark(); + auto t = parseType(); + if (t is null || !currentIs(tok!"identifier") + || !peekIs(tok!"=")) + { + goToBookmark(b); + node.expression = parseExpression(); + } + else + { + goToBookmark(b); + node.type = parseType(); + auto i = expect(tok!"identifier"); + if (i !is null) + node.identifier = *i; + expect(tok!"="); + node.expression = parseExpression(); + } + } + + if (expect(tok!")") is null) return null; + if (currentIs(tok!"}")) + { + error("Statement expected", false); + return node; // this line makes DCD better + } + node.thenStatement = parseDeclarationOrStatement(); + if (currentIs(tok!"else")) + { + advance(); + node.elseStatement = parseDeclarationOrStatement(); + } + return node; + } + + /** + * Parses an ImportBind + * + * $(GRAMMAR $(RULEDEF importBind): + * $(LITERAL Identifier) ($(LITERAL '=') $(LITERAL Identifier))? + * ;) + */ + ImportBind parseImportBind() + { + auto node = allocate!ImportBind; + auto ident = expect(tok!"identifier"); + if (ident is null) return null; + node.left = *ident; + if (currentIs(tok!"=")) + { + advance(); + auto id = expect(tok!"identifier"); + if (id is null) return null; + node.right = *id; + } + return node; + } + + /** + * Parses ImportBindings + * + * $(GRAMMAR $(RULEDEF importBindings): + * $(RULE singleImport) $(LITERAL ':') $(RULE importBind) ($(LITERAL ',') $(RULE importBind))* + * ;) + */ + ImportBindings parseImportBindings(SingleImport singleImport) + { + auto node = allocate!ImportBindings; + node.singleImport = singleImport is null ? parseSingleImport() : singleImport; + if (expect(tok!":") is null) return null; + ImportBind[] importBinds; + while (moreTokens()) + { + importBinds ~= parseImportBind(); + if (currentIs(tok!",")) + advance(); + else + break; + } + node.importBinds = ownArray(importBinds); + return node; + } + + /** + * Parses an ImportDeclaration + * + * $(GRAMMAR $(RULEDEF importDeclaration): + * $(LITERAL 'import') $(RULE singleImport) ($(LITERAL ',') $(RULE singleImport))* ($(LITERAL ',') $(RULE importBindings))? $(LITERAL ';') + * | $(LITERAL 'import') $(RULE importBindings) $(LITERAL ';') + * ;) + */ + ImportDeclaration parseImportDeclaration() + { + auto node = allocate!ImportDeclaration; + if (expect(tok!"import") is null) return null; + SingleImport si = parseSingleImport(); + if (currentIs(tok!":")) + node.importBindings = parseImportBindings(si); + else + { + SingleImport[] singleImports; + singleImports ~= si; + if (currentIs(tok!",")) + { + advance(); + while (moreTokens()) + { + auto single = parseSingleImport(); + if (single is null) + return null; + if (currentIs(tok!":")) + { + node.importBindings = parseImportBindings(single); + break; + } + else + { + singleImports ~= single; + if (currentIs(tok!",")) + advance(); + else + break; + } + } + } + node.singleImports = ownArray(singleImports); + } + if (expect(tok!";") is null) return null; + return node; + } + + unittest + { + auto sourceCode = +q{import std.stdio; +import foo, bar; +import io = std.stdio; +import std.stdio: writefln, foo = writef; +import io = std.stdio : foo = writefln; +import foo, bar, baz; +import core.stdc.stdio, std.string : KeepTerminator; +}c; + + Parser p = getParserForUnittest(sourceCode, "parseImportDeclaration"); + + ImportDeclaration one = p.parseImportDeclaration(); + assert (one !is null); + assert (one.singleImports.length == 1); + assert (p.errorCount == 0); + + ImportDeclaration two = p.parseImportDeclaration(); + assert (two !is null); + assert (two.singleImports.length == 2); + assert (p.errorCount == 0); + + ImportDeclaration three = p.parseImportDeclaration(); + assert (three !is null); + assert (three.singleImports.length == 1); + assert (p.errorCount == 0); + + ImportDeclaration four = p.parseImportDeclaration(); + assert (four !is null); + assert (four.importBindings !is null); + assert (four.importBindings.importBinds.length == 2); + assert (p.errorCount == 0); + + ImportDeclaration five = p.parseImportDeclaration(); + assert (five !is null); + assert (p.errorCount == 0); + + ImportDeclaration six = p.parseImportDeclaration(); + assert (six !is null); + assert (six.singleImports.length == 3); + assert (p.errorCount == 0); + + ImportDeclaration seven = p.parseImportDeclaration(); + assert (seven !is null); + assert (seven.singleImports.length == 1); + assert (seven.importBindings !is null); + assert (p.errorCount == 0); + + stderr.writeln("Unittest for parseImportDeclaration passed."); + } + + /** + * Parses an ImportExpression + * + * $(GRAMMAR $(RULEDEF importExpression): + * $(LITERAL 'import') $(LITERAL '$(LPAREN)') $(RULE assignExpression) $(LITERAL '$(RPAREN)') + * ;) + */ + ImportExpression parseImportExpression() + { + auto node = allocate!ImportExpression; + if (expect(tok!"import") is null) return null; + if (expect(tok!"(") is null) return null; + node.assignExpression = parseAssignExpression(); + if (expect(tok!")") is null) return null; + return node; + } + + /** + * Parses an IndexExpression + * + * $(GRAMMAR $(RULEDEF indexExpression): + * $(RULE unaryExpression) $(LITERAL '[') $(RULE argumentList) $(LITERAL ']') + * ;) + */ + IndexExpression parseIndexExpression(UnaryExpression unaryExpression = null) + { + mixin(traceEnterAndExit!(__FUNCTION__)); + auto node = allocate!IndexExpression; + node.unaryExpression = unaryExpression is null ? parseUnaryExpression() : unaryExpression; + if (expect(tok!"[") is null) return null; + node.argumentList = parseArgumentList(); + if (expect(tok!"]") is null) return null; + return node; + } + + /** + * Parses an InExpression + * + * $(GRAMMAR $(RULEDEF inExpression): + * $(RULE shiftExpression) ($(LITERAL 'in') | $(LITERAL '!') $(LITERAL 'in')) $(RULE shiftExpression) + * ;) + */ + ExpressionNode parseInExpression(ExpressionNode shift = null) + { + auto node = allocate!InExpression; + node.left = shift is null ? parseShiftExpression() : shift; + if (currentIs(tok!"!")) + { + node.negated = true; + advance(); + } + if (expect(tok!"in") is null) return null; + node.right = parseShiftExpression(); + return node; + } + + /** + * Parses an InStatement + * + * $(GRAMMAR $(RULEDEF inStatement): + * $(LITERAL 'in') $(RULE blockStatement) + * ;) + */ + InStatement parseInStatement() + { + auto node = allocate!InStatement; + if (expect(tok!"in") is null) return null; + node.blockStatement = parseBlockStatement(); + return node; + } + + /** + * Parses an Initialize + * + * $(GRAMMAR $(RULEDEF initialize): + * $(LITERAL ';') + * | $(RULE statementNoCaseNoDefault) + * ;) + */ + Initialize parseInitialize() + { + auto node = allocate!Initialize; + if (!currentIs(tok!";")) + { + node.statementNoCaseNoDefault = parseStatementNoCaseNoDefault(); + if (node.statementNoCaseNoDefault is null) return null; + } + else if (expect(tok!";") is null) + return null; + return node; + } + + /** + * Parses an Initializer + * + * $(GRAMMAR $(RULEDEF initializer): + * $(LITERAL 'void') + * | $(RULE nonVoidInitializer) + * ;) + */ + Initializer parseInitializer() + { + auto node = allocate!Initializer; + if (currentIs(tok!"void")) + advance(); + else + node.nonVoidInitializer = parseNonVoidInitializer(); + return node; + } + + /** + * Parses an InterfaceDeclaration + * + * $(GRAMMAR $(RULEDEF interfaceDeclaration): + * $(LITERAL 'interface') $(LITERAL Identifier) ($(LITERAL ';') | ($(RULE templateParameters) $(RULE constraint)?)? ($(LITERAL ':') $(RULE baseClassList))? $(RULE structBody)) + * ;) + */ + InterfaceDeclaration parseInterfaceDeclaration() + { + auto node = allocate!InterfaceDeclaration; + if (expect(tok!"interface") is null) return null; + auto ident = expect(tok!"identifier"); + if (ident is null) return null; + node.name = *ident; + node.comment = comment; + comment = null; + if (currentIs(tok!";")) + { + advance(); + return node; + } + if (currentIs(tok!"(")) + { + node.templateParameters = parseTemplateParameters(); + if (currentIs(tok!"if")) + node.constraint = parseConstraint(); + } + if (currentIs(tok!":")) + { + advance(); + node.baseClassList = parseBaseClassList(); + } + node.structBody = parseStructBody(); + return node; + } + + unittest + { + auto sourceCode = +q{interface One {} +interface Two : Number {} +interface Three(T) if (someTest(T)) {} +interface "Four" +}c; + + Parser p = getParserForUnittest(sourceCode, "parseInterfaceDeclaration"); + + InterfaceDeclaration one = p.parseInterfaceDeclaration(); + assert (one !is null); + assert (one.name.text == "One"); + assert (one.constraint is null); + assert (one.templateParameters is null); + assert (one.structBody !is null); + assert (one.baseClassList is null); + assert (p.errorCount == 0); + + InterfaceDeclaration two = p.parseInterfaceDeclaration(); + assert (two !is null); + assert (two.name.text == "Two"); + assert (two.constraint is null); + assert (two.templateParameters is null); + assert (two.structBody !is null); + assert (two.baseClassList !is null); + assert (p.errorCount == 0); + + InterfaceDeclaration three = p.parseInterfaceDeclaration(); + assert (three !is null); + assert (three.name.text == "Three"); + assert (three.constraint !is null); + assert (three.templateParameters !is null); + assert (three.structBody !is null); + assert (three.baseClassList is null); + assert (p.errorCount == 0); + + InterfaceDeclaration four = p.parseInterfaceDeclaration(); + assert (four is null); + assert (p.errorCount > 0); + + stderr.writeln("Unittest for parseInterfaceDeclaration passed."); + } + + /** + * Parses an Invariant + * + * $(GRAMMAR $(RULEDEF invariant): + * $(LITERAL 'invariant') ($(LITERAL '$(LPAREN)') $(LITERAL '$(RPAREN)'))? $(RULE blockStatement) + * ;) + */ + Invariant parseInvariant() + { + auto node = allocate!Invariant; + node.index = current.index; + node.line = current.line; + if (expect(tok!"invariant") is null) return null; + if (currentIs(tok!"(")) + { + advance(); + if (expect(tok!")") is null) return null; + } + if ((node.blockStatement = parseBlockStatement()) is null) return null; + return node; + } + + unittest + { + auto sourceCode = +q{invariant() {} +invariant{} +invariant() foo(); +}c; + + Parser p = getParserForUnittest(sourceCode, "parseInvariant"); + + auto inv1 = p.parseInvariant(); + assert (inv1 !is null); + assert (inv1.blockStatement !is null); + assert (p.errorCount == 0); + + auto inv2 = p.parseInvariant(); + assert (inv2 !is null); + assert (inv2.blockStatement !is null); + assert (p.errorCount == 0); + + auto inv3 = p.parseInvariant(); + assert (inv3 is null); + assert (p.errorCount > 0); + } + + /** + * Parses an IsExpression + * + * $(GRAMMAR $(RULEDEF isExpression): + * $(LITERAL'is') $(LITERAL '$(LPAREN)') $(RULE type) $(LITERAL Identifier)? (($(LITERAL ':') | $(LITERAL '==')) $(RULE typeSpecialization) ($(LITERAL ',') $(RULE templateParameterList))?)? $(LITERAL '$(RPAREN)') + * ;) + */ + IsExpression parseIsExpression() + { + mixin(traceEnterAndExit!(__FUNCTION__)); + auto node = allocate!IsExpression; + if (expect(tok!"is") is null) return null; + if (expect(tok!"(") is null) return null; + node.type = parseType(); + if (node.type is null) return null; + if (currentIs(tok!"identifier")) + node.identifier = advance(); + if (currentIsOneOf(tok!"==", tok!":")) + { + node.equalsOrColon = advance().type; + node.typeSpecialization = parseTypeSpecialization(); + if (currentIs(tok!",")) + { + advance(); + node.templateParameterList = parseTemplateParameterList(); + } + } + if (expect(tok!")") is null) return null; + return node; + } + + unittest + { + auto sourceCode = q{is ( x : uybte)}c; + Parser p = getParserForUnittest(sourceCode, "parseIsExpression"); + auto isExp1 = p.parseIsExpression(); + assert (isExp1 !is null); + assert (p.errorCount == 0); + stderr.writeln("Unittest for parseIsExpression passed."); + } + + /** + * Parses a KeyValuePair + * + * $(GRAMMAR $(RULEDEF keyValuePair): + * $(RULE assignExpression) $(LITERAL ':') $(RULE assignExpression) + * ;) + */ + KeyValuePair parseKeyValuePair() + { + mixin(traceEnterAndExit!(__FUNCTION__)); + auto node = allocate!KeyValuePair; + node.key = parseAssignExpression(); + if (expect(tok!":") is null) return null; + node.value = parseAssignExpression(); + return node; + } + + /** + * Parses KeyValuePairs + * + * $(GRAMMAR $(RULEDEF keyValuePairs): + * $(RULE keyValuePair) ($(LITERAL ',') $(RULE keyValuePair))* $(LITERAL ',')? + * ;) + */ + KeyValuePairs parseKeyValuePairs() + { + mixin(traceEnterAndExit!(__FUNCTION__)); + auto node = allocate!KeyValuePairs; + KeyValuePair[] keyValuePairs; + while (moreTokens()) + { + auto kvPair = parseKeyValuePair(); + if (kvPair !is null) + keyValuePairs ~= kvPair; + if (currentIs(tok!",")) + { + advance(); + if (currentIs(tok!"]")) + break; + } + else + break; + } + node.keyValuePairs = ownArray(keyValuePairs); + return node; + } + + /** + * Parses a LabeledStatement + * + * $(GRAMMAR $(RULEDEF labeledStatement): + * $(LITERAL Identifier) $(LITERAL ':') $(RULE declarationOrStatement) + * ;) + */ + LabeledStatement parseLabeledStatement() + { + mixin(traceEnterAndExit!(__FUNCTION__)); + auto node = allocate!LabeledStatement; + auto ident = expect(tok!"identifier"); + if (ident is null) return null; + node.identifier = *ident; + expect(tok!":"); + node.declarationOrStatement = parseDeclarationOrStatement(); + return node; + } + + /** + * Parses a LambdaExpression + * + * $(GRAMMAR $(RULEDEF lambdaExpression): + * $(LITERAL Identifier) $(LITERAL '=>') $(RULE assignExpression) + * | $(LITERAL 'function') $(RULE parameters) $(RULE functionAttribute)* $(LITERAL '=>') $(RULE assignExpression) + * | $(LITERAL 'delegate') $(RULE parameters) $(RULE functionAttribute)* $(LITERAL '=>') $(RULE assignExpression) + * | $(RULE parameters) $(RULE functionAttribute)* $(LITERAL '=>') $(RULE assignExpression) + * ;) + */ + LambdaExpression parseLambdaExpression() + { + mixin(traceEnterAndExit!(__FUNCTION__)); + auto node = allocate!LambdaExpression; + if (currentIsOneOf(tok!"function", tok!"delegate")) + { + node.functionType = advance().type; + goto lParen; + } + else if (currentIs(tok!"identifier")) + node.identifier = advance(); + else if (currentIs(tok!"(")) + { + lParen: + node.parameters = parseParameters(); + FunctionAttribute[] functionAttributes; + do + { + auto attribute = parseFunctionAttribute(false); + if (attribute is null) + break; + functionAttributes ~= attribute; + } + while (moreTokens()); + node.functionAttributes = ownArray(functionAttributes); + } + else + { + error(`Identifier or argument list expected`); + return null; + } + + if (expect(tok!"=>") is null) return null; + + if ((node.assignExpression = parseAssignExpression()) is null) + return null; + + return node; + } + + /** + * Parses a LastCatch + * + * $(GRAMMAR $(RULEDEF lastCatch): + * $(LITERAL 'catch') $(RULE statementNoCaseNoDefault) + * ;) + */ + LastCatch parseLastCatch() + { + mixin(traceEnterAndExit!(__FUNCTION__)); + auto node = allocate!LastCatch; + auto t = expect(tok!"catch"); + if (t is null) return null; + node.line = t.line; + node.column = t.column; + if ((node.statementNoCaseNoDefault = parseStatementNoCaseNoDefault()) is null) + return null; + return node; + } + + /** + * Parses a LinkageAttribute + * + * $(GRAMMAR $(RULEDEF linkageAttribute): + * $(LITERAL 'extern') $(LITERAL '$(LPAREN)') $(LITERAL Identifier) ($(LITERAL '++') ($(LITERAL ',') $(RULE identifierChain))?)? $(LITERAL '$(RPAREN)') + * ;) + */ + LinkageAttribute parseLinkageAttribute() + { + mixin(traceEnterAndExit!(__FUNCTION__)); + auto node = allocate!LinkageAttribute; + expect(tok!"extern"); + expect(tok!"("); + auto ident = expect(tok!"identifier"); + if (ident is null) return null; + node.identifier = *ident; + if (currentIs(tok!"++")) + { + advance(); + node.hasPlusPlus = true; + version (DIP61) if (currentIs(tok!",")) + { + advance(); + node.identifierChain = parseIdentifierChain(); + } + } + expect(tok!")"); + return node; + } + + /** + * Parses a MemberFunctionAttribute + * + * $(GRAMMAR $(RULEDEF memberFunctionAttribute): + * $(RULE functionAttribute) + * | $(LITERAL 'immutable') + * | $(LITERAL 'inout') + * | $(LITERAL 'shared') + * | $(LITERAL 'const') + * ;) + */ + MemberFunctionAttribute parseMemberFunctionAttribute() + { + mixin(traceEnterAndExit!(__FUNCTION__)); + auto node = allocate!MemberFunctionAttribute; + switch (current.type) + { + case tok!"@": + node.atAttribute = parseAtAttribute(); + break; + case tok!"immutable": + case tok!"inout": + case tok!"shared": + case tok!"const": + case tok!"pure": + case tok!"nothrow": + node.tokenType = advance().type; + break; + default: + error(`Member funtion attribute expected`); + } + return node; + } + + /** + * Parses a MixinDeclaration + * + * $(GRAMMAR $(RULEDEF mixinDeclaration): + * $(RULE mixinExpression) $(LITERAL ';') + * | $(RULE templateMixinExpression) $(LITERAL ';') + * ;) + */ + MixinDeclaration parseMixinDeclaration() + { + mixin(traceEnterAndExit!(__FUNCTION__)); + auto node = allocate!MixinDeclaration; + if (peekIs(tok!"identifier") || peekIs(tok!"typeof")) + node.templateMixinExpression = parseTemplateMixinExpression(); + else if (peekIs(tok!"(")) + node.mixinExpression = parseMixinExpression(); + else + { + error(`"(" or identifier expected`); + return null; + } + expect(tok!";"); + return node; + } + + /** + * Parses a MixinExpression + * + * $(GRAMMAR $(RULEDEF mixinExpression): + * $(LITERAL 'mixin') $(LITERAL '$(LPAREN)') $(RULE assignExpression) $(LITERAL '$(RPAREN)') + * ;) + */ + MixinExpression parseMixinExpression() + { + mixin(traceEnterAndExit!(__FUNCTION__)); + auto node = allocate!MixinExpression; + expect(tok!"mixin"); + expect(tok!"("); + node.assignExpression = parseAssignExpression(); + expect(tok!")"); + return node; + } + + /** + * Parses a MixinTemplateDeclaration + * + * $(GRAMMAR $(RULEDEF mixinTemplateDeclaration): + * $(LITERAL 'mixin') $(RULE templateDeclaration) + * ;) + */ + MixinTemplateDeclaration parseMixinTemplateDeclaration() + { + mixin(traceEnterAndExit!(__FUNCTION__)); + auto node = allocate!MixinTemplateDeclaration; + if (expect(tok!"mixin") is null) return null; + node.templateDeclaration = parseTemplateDeclaration(); + if (node.templateDeclaration is null) return null; + return node; + } + + /** + * Parses a MixinTemplateName + * + * $(GRAMMAR $(RULEDEF mixinTemplateName): + * $(RULE symbol) + * | $(RULE typeofExpression) $(LITERAL '.') $(RULE identifierOrTemplateChain) + * ;) + */ + MixinTemplateName parseMixinTemplateName() + { + mixin(traceEnterAndExit!(__FUNCTION__)); + auto node = allocate!MixinTemplateName; + if (currentIs(tok!"typeof")) + { + node.typeofExpression = parseTypeofExpression(); + expect(tok!"."); + node.identifierOrTemplateChain = parseIdentifierOrTemplateChain(); + } + else + node.symbol = parseSymbol(); + return node; + } + + unittest + { + } + + /** + * Parses a Module + * + * $(GRAMMAR $(RULEDEF module): + * $(RULE moduleDeclaration)? $(RULE declaration)* + * ;) + */ + Module parseModule() + { + mixin(traceEnterAndExit!(__FUNCTION__)); + Module m = allocate!Module; + if (currentIs(tok!"scriptLine")) + m.scriptLine = advance(); + if (currentIs(tok!"module")) + m.moduleDeclaration = parseModuleDeclaration(); + Declaration[] declarations; + while (moreTokens()) + { + auto declaration = parseDeclaration(); + if (declaration !is null) + declarations ~= declaration; + } + m.declarations = ownArray(declarations); + return m; + } + + /** + * Parses a ModuleDeclaration + * + * $(GRAMMAR $(RULEDEF moduleDeclaration): + * $(LITERAL 'module') $(RULE identifierChain) $(LITERAL ';') + * ;) + */ + ModuleDeclaration parseModuleDeclaration() + { + auto node = allocate!ModuleDeclaration; + auto start = expect(tok!"module"); + node.moduleName = parseIdentifierChain(); + node.comment = start.comment; + comment = null; + auto end = expect(tok!";"); + node.startLocation = start.index; + node.endLocation = end.index; + return node; + } + + /** + * Parses a MulExpression + * $(GRAMMAR $(RULEDEF mulExpression): + * $(RULE powExpression) + * | $(RULE mulExpression) ($(LITERAL '*') | $(LITERAL '/') | $(LITERAL '%')) $(RULE powExpression) + * ;) + */ + ExpressionNode parseMulExpression() + { + mixin(traceEnterAndExit!(__FUNCTION__)); + return parseLeftAssocBinaryExpression!(MulExpression, PowExpression, + tok!"*", tok!"/", tok!"%")(); + } + + /** + * Parses a NewAnonClassExpression + * + * $(GRAMMAR $(RULEDEF newAnonClassExpression): + * $(LITERAL 'new') $(RULE arguments)? $(LITERAL 'class') $(RULE arguments)? $(RULE baseClassList)? $(RULE structBody) + * ;) + */ + NewAnonClassExpression parseNewAnonClassExpression() + { + auto node = allocate!NewAnonClassExpression; + expect(tok!"new"); + if (currentIs(tok!"(")) + node.allocatorArguments = parseArguments(); + expect(tok!"class"); + if (currentIs(tok!"(")) + node.constructorArguments = parseArguments(); + if (!currentIs(tok!"{")) + node.baseClassList = parseBaseClassList(); + node.structBody = parseStructBody(); + return node; + } + + /** + * Parses a NewExpression + * + * $(GRAMMAR $(RULEDEF newExpression): + * $(LITERAL 'new') $(RULE type) ($(LITERAL '[') $(RULE assignExpression) $(LITERAL ']') | $(RULE arguments))? + * | $(RULE newAnonClassExpression) + * ;) + */ + NewExpression parseNewExpression() + { + auto node = allocate!NewExpression; + if (peekIsOneOf(tok!"class", tok!"(")) + node.newAnonClassExpression = parseNewAnonClassExpression(); + else + { + expect(tok!"new"); + if (!moreTokens()) + return null; + node.type = parseType(); + if (currentIs(tok!"[")) + { + advance(); + node.assignExpression = parseAssignExpression(); + expect(tok!"]"); + } + else if (currentIs(tok!"(")) + node.arguments = parseArguments(); + } + return node; + } + + /** + * Parses a StatementNoCaseNoDefault + * + * $(GRAMMAR $(RULEDEF statementNoCaseNoDefault): + * $(RULE labeledStatement) + * | $(RULE blockStatement) + * | $(RULE ifStatement) + * | $(RULE whileStatement) + * | $(RULE doStatement) + * | $(RULE forStatement) + * | $(RULE foreachStatement) + * | $(RULE switchStatement) + * | $(RULE finalSwitchStatement) + * | $(RULE continueStatement) + * | $(RULE breakStatement) + * | $(RULE returnStatement) + * | $(RULE gotoStatement) + * | $(RULE withStatement) + * | $(RULE synchronizedStatement) + * | $(RULE tryStatement) + * | $(RULE throwStatement) + * | $(RULE scopeGuardStatement) + * | $(RULE asmStatement) + * | $(RULE conditionalStatement) + * | $(RULE staticAssertStatement) + * | $(RULE versionSpecification) + * | $(RULE debugSpecification) + * | $(RULE expressionStatement) + * ;) + */ + StatementNoCaseNoDefault parseStatementNoCaseNoDefault() + { + auto node = allocate!StatementNoCaseNoDefault; + switch (current.type) + { + case tok!"{": + node.blockStatement = parseBlockStatement(); + break; + case tok!"if": + node.ifStatement = parseIfStatement(); + break; + case tok!"while": + node.whileStatement = parseWhileStatement(); + break; + case tok!"do": + node.doStatement = parseDoStatement(); + break; + case tok!"for": + node.forStatement = parseForStatement(); + break; + case tok!"foreach": + case tok!"foreach_reverse": + node.foreachStatement = parseForeachStatement(); + break; + case tok!"switch": + node.switchStatement = parseSwitchStatement(); + break; + case tok!"continue": + node.continueStatement = parseContinueStatement(); + break; + case tok!"break": + node.breakStatement = parseBreakStatement(); + break; + case tok!"return": + node.returnStatement = parseReturnStatement(); + break; + case tok!"goto": + node.gotoStatement = parseGotoStatement(); + break; + case tok!"with": + node.withStatement = parseWithStatement(); + break; + case tok!"synchronized": + node.synchronizedStatement = parseSynchronizedStatement(); + break; + case tok!"try": + node.tryStatement = parseTryStatement(); + break; + case tok!"throw": + node.throwStatement = parseThrowStatement(); + break; + case tok!"scope": + node.scopeGuardStatement = parseScopeGuardStatement(); + break; + case tok!"asm": + node.asmStatement = parseAsmStatement(); + break; + case tok!"final": + if (peekIs(tok!"switch")) + { + node.finalSwitchStatement = parseFinalSwitchStatement(); + break; + } + else + { + error(`"switch" expected`); + return null; + } + case tok!"debug": + if (peekIs(tok!"=")) + node.debugSpecification = parseDebugSpecification(); + else + node.conditionalStatement = parseConditionalStatement(); + break; + case tok!"version": + if (peekIs(tok!"=")) + node.versionSpecification = parseVersionSpecification(); + else + node.conditionalStatement = parseConditionalStatement(); + break; + case tok!"static": + if (peekIs(tok!"if")) + node.conditionalStatement = parseConditionalStatement(); + else if (peekIs(tok!"assert")) + node.staticAssertStatement = parseStaticAssertStatement(); + break; + case tok!"identifier": + if (peekIs(tok!":")) + { + node.labeledStatement = parseLabeledStatement(); + break; + } + goto default; + case tok!"delete": + case tok!"assert": + default: + node.expressionStatement = parseExpressionStatement(); + break; + } + return node; + } + + /** + * Parses a NonVoidInitializer + * + * $(GRAMMAR $(RULEDEF nonVoidInitializer): + * $(RULE assignExpression) + * | $(RULE arrayInitializer) + * | $(RULE structInitializer) + * | $(RULE functionBody) + * ;) + */ + NonVoidInitializer parseNonVoidInitializer() + { + mixin(traceEnterAndExit!(__FUNCTION__)); + auto node = allocate!NonVoidInitializer; + if (currentIs(tok!"{")) + { + auto b = peekPastBraces(); + if (b !is null && (b.type == tok!"(")) + node.assignExpression = parseAssignExpression(); + else if (hasMagicDelimiter!(tok!"{", tok!";")()) + node.functionBody = parseFunctionBody(); + else + node.structInitializer = parseStructInitializer(); + } + else if (currentIs(tok!"[")) + { + auto b = peekPastBrackets(); + if (b !is null && (b.type == tok!"," + || b.type == tok!")" + || b.type == tok!"]" + || b.type == tok!"}" + || b.type == tok!";")) + { + node.arrayInitializer = parseArrayInitializer(); + } + else + node.assignExpression = parseAssignExpression(); + } + else if (currentIsOneOf(tok!"in", tok!"out", tok!"body")) + node.functionBody = parseFunctionBody(); + else + node.assignExpression = parseAssignExpression(); + if (node.assignExpression is null && node.arrayInitializer is null + && node.structInitializer is null && node.functionBody is null) + { + return null; + } + return node; + } + + /** + * Parses Operands + * + * $(GRAMMAR $(RULEDEF operands): + * $(RULE asmExp)+ + * ;) + */ + Operands parseOperands() + { +// auto node = allocate!Operands; + assert (false, "asm"); // TODO asm + } + + /** + * Parses an OrExpression + * + * $(GRAMMAR $(RULEDEF orExpression): + * $(RULE xorExpression) + * | $(RULE orExpression) $(LITERAL '|') $(RULE xorExpression) + * ;) + */ + ExpressionNode parseOrExpression() + { + mixin(traceEnterAndExit!(__FUNCTION__)); + return parseLeftAssocBinaryExpression!(OrExpression, XorExpression, + tok!"|")(); + } + + /** + * Parses an OrOrExpression + * + * $(GRAMMAR $(RULEDEF orOrExpression): + * $(RULE andAndExpression) + * | $(RULE orOrExpression) $(LITERAL '||') $(RULE andAndExpression) + * ;) + */ + ExpressionNode parseOrOrExpression() + { + mixin(traceEnterAndExit!(__FUNCTION__)); + return parseLeftAssocBinaryExpression!(OrOrExpression, AndAndExpression, + tok!"||")(); + } + + /** + * Parses an OutStatement + * + * $(GRAMMAR $(RULEDEF outStatement): + * $(LITERAL 'out') ($(LITERAL '$(LPAREN)') $(LITERAL Identifier) $(LITERAL '$(RPAREN)'))? $(RULE blockStatement) + * ;) + */ + OutStatement parseOutStatement() + { + auto node = allocate!OutStatement; + expect(tok!"out"); + if (currentIs(tok!"(")) + { + advance(); + auto ident = expect(tok!"identifier"); + if (ident is null) return null; + node.parameter = *ident; + expect(tok!")"); + } + node.blockStatement = parseBlockStatement(); + return node; + } + + /** + * Parses a Parameter + * + * $(GRAMMAR $(RULEDEF parameter): + * $(RULE parameterAttribute)* $(RULE type) (($(LITERAL Identifier) $(RULE typeSuffix)*)? $(LITERAL '...') | ($(LITERAL Identifier)? ($(LITERAL '=') $(RULE assignExpression))?))? + * ;) + */ + Parameter parseParameter() + { + mixin(traceEnterAndExit!(__FUNCTION__)); + auto node = allocate!Parameter; + IdType[] parameterAttributes; + while (moreTokens()) + { + IdType type = parseParameterAttribute(false); + if (type == tok!"") + break; + else + parameterAttributes ~= type; + } + node.parameterAttributes = ownArray(parameterAttributes); + node.type = parseType(); + if (node.type is null) return null; + if (currentIs(tok!"identifier")) + { + node.name = advance(); + if (currentIs(tok!"...")) + { + advance(); + node.vararg = true; + } + else if (currentIs(tok!"=")) + { + advance(); + node.default_ = parseAssignExpression(); + } + else if (currentIs(tok!"[")) + { + TypeSuffix[] typeSuffixes; + while(moreTokens() && currentIs(tok!"[")) + { + auto suffix = parseTypeSuffix(); + if (suffix !is null) + typeSuffixes ~= suffix; + else + return null; + } + node.cstyle = ownArray(typeSuffixes); + } + } + else if (currentIs(tok!"...")) + { + node.vararg = true; + advance(); + } + else if (currentIs(tok!"=")) + { + advance(); + node.default_ = parseAssignExpression(); + } + return node; + } + + /** + * Parses a ParameterAttribute + * + * $(GRAMMAR $(RULEDEF parameterAttribute): + * $(RULE typeConstructor) + * | $(LITERAL 'final') + * | $(LITERAL 'in') + * | $(LITERAL 'lazy') + * | $(LITERAL 'out') + * | $(LITERAL 'ref') + * | $(LITERAL 'scope') + * | $(LITERAL 'auto') + * ;) + */ + IdType parseParameterAttribute(bool validate = false) + { + mixin(traceEnterAndExit!(__FUNCTION__)); + switch (current.type) + { + case tok!"immutable": + case tok!"shared": + case tok!"const": + case tok!"inout": + if (peekIs(tok!"(")) + return tok!""; + else + goto _auto; + case tok!"final": + case tok!"in": + case tok!"lazy": + case tok!"out": + case tok!"ref": + case tok!"scope": + _auto: + case tok!"auto": + return advance().type; + default: + if (validate) + error("Parameter attribute expected"); + return tok!""; + } + } + + /** + * Parses Parameters + * + * $(GRAMMAR $(RULEDEF parameters): + * $(LITERAL '$(LPAREN)') $(RULE parameter) ($(LITERAL ',') $(RULE parameter))* ($(LITERAL ',') $(LITERAL '...'))? $(LITERAL '$(RPAREN)') + * | $(LITERAL '$(LPAREN)') $(LITERAL '...') $(LITERAL '$(RPAREN)') + * | $(LITERAL '$(LPAREN)') $(LITERAL '$(RPAREN)') + * ;) + */ + Parameters parseParameters() + { + mixin(traceEnterAndExit!(__FUNCTION__)); + auto node = allocate!Parameters; + if (expect(tok!"(") is null) return null; + Parameter[] parameters; + if (currentIs(tok!")")) + goto end; + if (currentIs(tok!"...")) + { + advance(); + node.hasVarargs = true; + goto end; + } + while (moreTokens()) + { + if (currentIs(tok!"...")) + { + advance(); + node.hasVarargs = true; + break; + } + if (currentIs(tok!")")) + break; + auto param = parseParameter(); + if (param is null) + return null; + parameters ~= param; + if (currentIs(tok!",")) + advance(); + else + break; + } + node.parameters = ownArray(parameters); + end: + if (expect(tok!")") is null) + return null; + return node; + } + + unittest + { + string sourceCode = +q{(int a, ...) +(double ...) +(Range r)}c; + + Parser p = getParserForUnittest(sourceCode, "parseParameters"); + + Parameters params1 = p.parseParameters(); + assert (params1.hasVarargs); + assert (params1.parameters.length == 1); + assert (params1.parameters[0].name.text == "a"); + + Parameters params2 = p.parseParameters(); + assert (params2.parameters.length == 1); + assert (params2.parameters[0].vararg); + assert (params2.parameters[0].type !is null); + + Parameters params3 = p.parseParameters(); + assert (params3.parameters.length == 1); + assert (!params3.parameters[0].vararg); + assert (params3.parameters[0].type !is null); + + stderr.writeln("Unittest for parseParameters passed."); + } + + /** + * Parses a Postblit + * + * $(GRAMMAR $(RULEDEF postblit): + * $(LITERAL 'this') $(LITERAL '$(LPAREN)') $(LITERAL 'this') $(LITERAL '$(RPAREN)') $(RULE memberFunctionAttribute)* ($(RULE functionBody) | $(LITERAL ';')) + * ;) + */ + Postblit parsePostblit() + { + auto node = allocate!Postblit; + expect(tok!"this"); + expect(tok!"("); + expect(tok!"this"); + expect(tok!")"); + MemberFunctionAttribute[] memberFunctionAttributes; + while (currentIsMemberFunctionAttribute()) + memberFunctionAttributes ~= parseMemberFunctionAttribute(); + node.memberFunctionAttributes = ownArray(memberFunctionAttributes); + if (currentIs(tok!";")) + advance(); + else + node.functionBody = parseFunctionBody(); + return node; + } + + /** + * Parses a PostIncDecExpression + * + * $(GRAMMAR $(RULEDEF postIncDecExpression): + * $(RULE unaryExpression) ($(LITERAL '++') | $(LITERAL '--')) + * ;) + */ + PostIncDecExpression parsePostIncDecExpression(UnaryExpression unary = null) + { + auto node = allocate!PostIncDecExpression; + node.unaryExpression = unary is null ? parseUnaryExpression() : unary; + node.operator = advance().type; + return node; + } + + /** + * Parses a PowExpression + * + * $(GRAMMAR $(RULEDEF powExpression): + * $(RULE unaryExpression) + * | $(RULE powExpression) $(LITERAL '^^') $(RULE unaryExpression) + * ;) + */ + ExpressionNode parsePowExpression() + { + return parseLeftAssocBinaryExpression!(PowExpression, UnaryExpression, + tok!"^^")(); + } + + /** + * Parses a PragmaDeclaration + * + * $(GRAMMAR $(RULEDEF pragmaDeclaration): + * $(RULE pragmaExpression) $(LITERAL ';') + * ;) + */ + PragmaDeclaration parsePragmaDeclaration() + { + mixin (traceEnterAndExit!(__FUNCTION__)); + auto node = allocate!PragmaDeclaration; + node.pragmaExpression = parsePragmaExpression(); + expect(tok!";"); + return node; + } + + /** + * Parses a PragmaExpression + * + * $(GRAMMAR $(RULEDEF pragmaExpression): + * $(RULE 'pragma') $(LITERAL '$(LPAREN)') $(LITERAL Identifier) ($(LITERAL ',') $(RULE argumentList))? $(LITERAL '$(RPAREN)') + * ;) + */ + PragmaExpression parsePragmaExpression() + { + mixin (traceEnterAndExit!(__FUNCTION__)); + auto node = allocate!PragmaExpression; + expect(tok!"pragma"); + expect(tok!"("); + auto ident = expect(tok!"identifier"); + if (ident is null) return null; + node.identifier = *ident; + if (currentIs(tok!",")) + { + advance(); + node.argumentList = parseArgumentList(); + } + expect(tok!")"); + return node; + } + + /** + * Parses a PreIncDecExpression + * + * $(GRAMMAR $(RULEDEF preIncDecExpression): + * ($(LITERAL '++') | $(LITERAL '--')) $(RULE unaryExpression) + * ;) + */ + PreIncDecExpression parsePreIncDecExpression() + { + mixin(traceEnterAndExit!(__FUNCTION__)); + auto node = allocate!PreIncDecExpression; + if (currentIsOneOf(tok!"++", tok!"--")) + advance(); + else + { + error(`"++" or "--" expected`); + return null; + } + node.unaryExpression = parseUnaryExpression(); + return node; + } + + /** + * Parses a PrimaryExpression + * + * $(GRAMMAR $(RULEDEF primaryExpression): + * $(RULE identifierOrTemplateInstance) + * | $(LITERAL '.') $(RULE identifierOrTemplateInstance) + * | $(RULE typeConstructor) $(LITERAL '(') $(RULE basicType) $(LITERAL ')') $(LITERAL '.') $(LITERAL Identifier) + * | $(RULE basicType) $(LITERAL '.') $(LITERAL Identifier) + * | $(RULE basicType) $(RULE arguments) + * | $(RULE typeofExpression) + * | $(RULE typeidExpression) + * | $(RULE vector) + * | $(RULE arrayLiteral) + * | $(RULE assocArrayLiteral) + * | $(LITERAL '$(LPAREN)') $(RULE expression) $(LITERAL '$(RPAREN)') + * | $(RULE isExpression) + * | $(RULE lambdaExpression) + * | $(RULE functionLiteralExpression) + * | $(RULE traitsExpression) + * | $(RULE mixinExpression) + * | $(RULE importExpression) + * | $(LITERAL '$') + * | $(LITERAL 'this') + * | $(LITERAL 'super') + * | $(LITERAL '_null') + * | $(LITERAL '_true') + * | $(LITERAL '_false') + * | $(LITERAL '___DATE__') + * | $(LITERAL '___TIME__') + * | $(LITERAL '___TIMESTAMP__') + * | $(LITERAL '___VENDOR__') + * | $(LITERAL '___VERSION__') + * | $(LITERAL '___FILE__') + * | $(LITERAL '___LINE__') + * | $(LITERAL '___MODULE__') + * | $(LITERAL '___FUNCTION__') + * | $(LITERAL '___PRETTY_FUNCTION__') + * | $(LITERAL IntegerLiteral) + * | $(LITERAL FloatLiteral) + * | $(LITERAL StringLiteral)+ + * | $(LITERAL CharacterLiteral) + * ;) + */ + PrimaryExpression parsePrimaryExpression() + { + mixin(traceEnterAndExit!(__FUNCTION__)); + auto node = allocate!PrimaryExpression; + if (!moreTokens()) + { + error("Expected primary statement instead of EOF"); + return null; + } + switch (current.type) + { + case tok!".": + node.dot = advance(); + goto case; + case tok!"identifier": + if (peekIs(tok!"=>")) + node.lambdaExpression = parseLambdaExpression(); + else + node.identifierOrTemplateInstance = parseIdentifierOrTemplateInstance(); + break; + case tok!"immutable": + case tok!"const": + case tok!"inout": + case tok!"shared": + advance(); + expect(tok!"("); + node.type = parseType(); + expect(tok!")"); + expect(tok!"."); + auto ident = expect(tok!"identifier"); + if (ident !is null) + node.primary = *ident; + break; + mixin (BASIC_TYPE_CASES); + node.basicType = advance(); + if (currentIs(tok!".")) + { + advance(); + auto t = expect(tok!"identifier"); + if (t !is null) + node.primary = *t; + } + else if (currentIs(tok!"(")) + node.arguments = parseArguments(); + break; + case tok!"function": + case tok!"delegate": + if (peekIs(tok!"(")) + { + auto b = setBookmark(); + advance(); // function | delegate + skipParens(); + while (isAttribute()) + parseAttribute(); + if (currentIs(tok!"=>")) + { + goToBookmark(b); + node.lambdaExpression = parseLambdaExpression(); + break; + } + else + goToBookmark(b); + } + goto case; + case tok!"{": + case tok!"in": + case tok!"out": + case tok!"body": + node.functionLiteralExpression = parseFunctionLiteralExpression(); + break; + case tok!"typeof": + node.typeofExpression = parseTypeofExpression(); + break; + case tok!"typeid": + node.typeidExpression = parseTypeidExpression(); + break; + case tok!"__vector": + node.vector = parseVector(); + break; + case tok!"[": + if (isAssociativeArrayLiteral()) + node.assocArrayLiteral = parseAssocArrayLiteral(); + else + node.arrayLiteral = parseArrayLiteral(); + break; + case tok!"(": + auto b = setBookmark(); + skipParens(); + while (isAttribute()) + parseAttribute(); + if (currentIs(tok!"=>")) + { + goToBookmark(b); + node.lambdaExpression = parseLambdaExpression(); + } + else if (currentIs(tok!"{")) + { + goToBookmark(b); + node.functionLiteralExpression = parseFunctionLiteralExpression(); + } + else + { + goToBookmark(b); + advance(); + node.expression = parseExpression(); + expect(tok!")"); + } + break; + case tok!"is": + node.isExpression = parseIsExpression(); + break; + case tok!"__traits": + node.traitsExpression = parseTraitsExpression(); + break; + case tok!"mixin": + node.mixinExpression = parseMixinExpression(); + break; + case tok!"import": + node.importExpression = parseImportExpression(); + break; + case tok!"$": + case tok!"this": + case tok!"super": + case tok!"null": + case tok!"true": + case tok!"false": + mixin (SPECIAL_CASES); + mixin (LITERAL_CASES); + if (currentIsOneOf(tok!"stringLiteral", tok!"wstringLiteral", tok!"dstringLiteral")) + { + node.primary = advance(); + bool alreadyWarned = false; + while (currentIsOneOf(tok!"stringLiteral", tok!"wstringLiteral", + tok!"dstringLiteral")) + { + if (!alreadyWarned) + { + warn("Implicit concatenation of string literals"); + alreadyWarned = true; + } + node.primary.text~= advance().text; + } + } + else + node.primary = advance(); + break; + default: + error(`Primary expression expected`); + return null; + } + return node; + } + + /** + * Parses a Register + * + * $(GRAMMAR $(RULEDEF register): + * $(LITERAL Identifier) + * | $(LITERAL Identifier) $(LITERAL '$(LPAREN)') $(LITERAL IntegerLiteral) $(LITERAL '$(RPAREN)') + * ;) + */ + Register parseRegister() + { + mixin(traceEnterAndExit!(__FUNCTION__)); + auto node = allocate!Register; + auto ident = expect(tok!"identifier"); + if (ident is null) return null; + node.identifier = *ident; + if (currentIs(tok!"(")) + { + advance(); + auto intLit = expect(tok!"intLiteral"); + if (intLit is null) return null; + node.intLiteral = *intLit; + expect(tok!")"); + } + return node; + } + + /** + * Parses a RelExpression + * + * $(GRAMMAR $(RULEDEF relExpression): + * $(RULE shiftExpression) + * | $(RULE relExpression) $(RULE relOperator) $(RULE shiftExpression) + * ; + *$(RULEDEF relOperator): + * $(LITERAL '<') + * | $(LITERAL '<=') + * | $(LITERAL '>') + * | $(LITERAL '>=') + * | $(LITERAL '!<>=') + * | $(LITERAL '!<>') + * | $(LITERAL '<>') + * | $(LITERAL '<>=') + * | $(LITERAL '!>') + * | $(LITERAL '!>=') + * | $(LITERAL '!<') + * | $(LITERAL '!<=') + * ;) + */ + ExpressionNode parseRelExpression(ExpressionNode shift) + { + mixin (traceEnterAndExit!(__FUNCTION__)); + return parseLeftAssocBinaryExpression!(RelExpression, ShiftExpression, + tok!"<", tok!"<=", tok!">", tok!">=", tok!"!<>=", tok!"!<>", + tok!"<>", tok!"<>=", tok!"!>", tok!"!>=", tok!"!>=", tok!"!<", + tok!"!<=")(shift); + } + + /** + * Parses a ReturnStatement + * + * $(GRAMMAR $(RULEDEF returnStatement): + * $(LITERAL 'return') $(RULE expression)? $(LITERAL ';') + * ;) + */ + ReturnStatement parseReturnStatement() + { + mixin(traceEnterAndExit!(__FUNCTION__)); + auto node = allocate!ReturnStatement; + auto start = expect(tok!"return"); + if (start is null) return null; + node.startLocation = start.index; + if (!currentIs(tok!";")) + node.expression = parseExpression(); + auto semicolon = expect(tok!";"); + if (semicolon is null) return null; + node.endLocation = semicolon.index; + return node; + } + + /** + * Parses a ScopeGuardStatement + * + * $(GRAMMAR $(RULEDEF scopeGuardStatement): + * $(LITERAL 'scope') $(LITERAL '$(LPAREN)') $(LITERAL Identifier) $(LITERAL '$(RPAREN)') $(RULE statementNoCaseNoDefault) + * ;) + */ + ScopeGuardStatement parseScopeGuardStatement() + { + mixin(traceEnterAndExit!(__FUNCTION__)); + auto node = allocate!ScopeGuardStatement; + expect(tok!"scope"); + expect(tok!"("); + auto ident = expect(tok!"identifier"); + if (ident is null) return null; + node.identifier = *ident; + expect(tok!")"); + node.statementNoCaseNoDefault = parseStatementNoCaseNoDefault(); + return node; + } + + /** + * Parses a SharedStaticConstructor + * + * $(GRAMMAR $(RULEDEF sharedStaticConstructor): + * $(LITERAL 'shared') $(LITERAL 'static') $(LITERAL 'this') $(LITERAL '$(LPAREN)') $(LITERAL '$(RPAREN)') $(RULE functionBody) + * ;) + */ + SharedStaticConstructor parseSharedStaticConstructor() + { + mixin(traceEnterAndExit!(__FUNCTION__)); + mixin (simpleParse!(SharedStaticConstructor, tok!"shared", tok!"static", + tok!"this", tok!"(", tok!")", "functionBody|parseFunctionBody")); + } + + /** + * Parses a SharedStaticDestructor + * + * $(GRAMMAR $(RULEDEF sharedStaticDestructor): + * $(LITERAL 'shared') $(LITERAL 'static') $(LITERAL '~') $(LITERAL 'this') $(LITERAL '$(LPAREN)') $(LITERAL '$(RPAREN)') $(RULE functionBody) + * ;) + */ + SharedStaticDestructor parseSharedStaticDestructor() + { + mixin(traceEnterAndExit!(__FUNCTION__)); + mixin (simpleParse!(SharedStaticDestructor, tok!"shared", tok!"static", + tok!"~", tok!"this", tok!"(", tok!")", + "functionBody|parseFunctionBody")); + } + + /** + * Parses a ShiftExpression + * + * $(GRAMMAR $(RULEDEF shiftExpression): + * $(RULE addExpression) + * | $(RULE shiftExpression) ($(LITERAL '<<') | $(LITERAL '>>') | $(LITERAL '>>>')) $(RULE addExpression) + * ;) + */ + ExpressionNode parseShiftExpression() + { + mixin(traceEnterAndExit!(__FUNCTION__)); + return parseLeftAssocBinaryExpression!(ShiftExpression, AddExpression, + tok!"<<", tok!">>", tok!">>>")(); + } + + /** + * Parses a SingleImport + * + * $(GRAMMAR $(RULEDEF singleImport): + * ($(LITERAL Identifier) $(LITERAL '='))? $(RULE identifierChain) + * ;) + */ + SingleImport parseSingleImport() + { + mixin(traceEnterAndExit!(__FUNCTION__)); + auto node = allocate!SingleImport; + if (startsWith(tok!"identifier", tok!"=")) + { + node.rename = advance(); + advance(); // = + } + node.identifierChain = parseIdentifierChain(); + if (node.identifierChain is null) + return null; + return node; + } + + /** + * Parses a SliceExpression + * + * $(GRAMMAR $(RULEDEF sliceExpression): + * $(RULE unaryExpression) $(LITERAL '[') $(RULE assignExpression) $(LITERAL '..') $(RULE assignExpression) $(LITERAL ']') + * | $(RULE unaryExpression) $(LITERAL '[') $(LITERAL ']') + * ;) + */ + SliceExpression parseSliceExpression(UnaryExpression unary = null) + { + mixin(traceEnterAndExit!(__FUNCTION__)); + auto node = allocate!SliceExpression; + node.unaryExpression = unary is null ? parseUnaryExpression() : unary; + if (expect(tok!"[") is null) return null; + if (!currentIs(tok!"]")) + { + node.lower = parseAssignExpression(); + if (node.lower is null) + { + error("assignExpression expected"); + return null; + } + expect(tok!".."); + node.upper = parseAssignExpression(); + if (node.upper is null) + { + error("assignExpression expected"); + return null; + } + } + if (expect(tok!"]") is null) return null; + return node; + } + + /** + * Parses a Statement + * + * $(GRAMMAR $(RULEDEF statement): + * $(RULE statementNoCaseNoDefault) + * | $(RULE caseStatement) + * | $(RULE caseRangeStatement) + * | $(RULE defaultStatement) + * ;) + */ + Statement parseStatement() + { + mixin(traceEnterAndExit!(__FUNCTION__)); + auto node = allocate!Statement; + if (!moreTokens()) + { + error("Expected statement instead of EOF"); + return null; + } + switch (current.type) + { + case tok!"case": + advance(); + auto argumentList = parseArgumentList(); + if (argumentList.items.length == 1 && startsWith(tok!":", tok!"..")) + node.caseRangeStatement = parseCaseRangeStatement(argumentList.items[0]); + else + node.caseStatement = parseCaseStatement(argumentList); + break; + case tok!"default": + node.defaultStatement = parseDefaultStatement(); + break; + default: + node.statementNoCaseNoDefault = parseStatementNoCaseNoDefault(); + break; + } + return node; + } + + /** + * Parses a StaticAssertDeclaration + * + * $(GRAMMAR $(RULEDEF staticAssertDeclaration): + * $(RULE staticAssertStatement) + * ;) + */ + StaticAssertDeclaration parseStaticAssertDeclaration() + { + mixin(traceEnterAndExit!(__FUNCTION__)); + mixin (simpleParse!(StaticAssertDeclaration, + "staticAssertStatement|parseStaticAssertStatement")); + } + + + /** + * Parses a StaticAssertStatement + * + * $(GRAMMAR $(RULEDEF staticAssertStatement): + * $(LITERAL 'static') $(RULE assertExpression) $(LITERAL ';') + * ;) + */ + StaticAssertStatement parseStaticAssertStatement() + { + mixin(traceEnterAndExit!(__FUNCTION__)); + mixin (simpleParse!(StaticAssertStatement, + tok!"static", "assertExpression|parseAssertExpression", tok!";")); + } + + /** + * Parses a StaticConstructor + * + * $(GRAMMAR $(RULEDEF staticConstructor): + * $(LITERAL 'static') $(LITERAL 'this') $(LITERAL '$(LPAREN)') $(LITERAL '$(RPAREN)') $(RULE functionBody) + * ;) + */ + StaticConstructor parseStaticConstructor() + { + mixin(traceEnterAndExit!(__FUNCTION__)); + mixin (simpleParse!(StaticConstructor, tok!"static", tok!"this", + tok!"(", tok!")", "functionBody|parseFunctionBody")); + } + + /** + * Parses a StaticDestructor + * + * $(GRAMMAR $(RULEDEF staticDestructor): + * $(LITERAL 'static') $(LITERAL '~') $(LITERAL 'this') $(LITERAL '$(LPAREN)') $(LITERAL '$(RPAREN)') $(RULE functionBody) + * ;) + */ + StaticDestructor parseStaticDestructor() + { + mixin(traceEnterAndExit!(__FUNCTION__)); + mixin (simpleParse!(StaticDestructor, tok!"static", tok!"~", tok!"this", + tok!"(", tok!")", "functionBody|parseFunctionBody")); + } + + /** + * Parses an StaticIfCondition + * + * $(GRAMMAR $(RULEDEF staticIfCondition): + * $(LITERAL 'static') $(LITERAL 'if') $(LITERAL '$(LPAREN)') $(RULE assignExpression) $(LITERAL '$(RPAREN)') + * ;) + */ + StaticIfCondition parseStaticIfCondition() + { + mixin(traceEnterAndExit!(__FUNCTION__)); + mixin (simpleParse!(StaticIfCondition, tok!"static", tok!"if", tok!"(", + "assignExpression|parseAssignExpression", tok!")")); + } + + /** + * Parses a StorageClass + * + * $(GRAMMAR $(RULEDEF storageClass): + * $(RULE alignAttribute) + * | $(RULE linkageAttribute) + * | $(RULE atAttribute) + * | $(RULE typeConstructor) + * | $(RULE deprecated) + * | $(LITERAL 'abstract') + * | $(LITERAL 'auto') + * | $(LITERAL 'enum') + * | $(LITERAL 'extern') + * | $(LITERAL 'final') + * | $(LITERAL 'virtual') + * | $(LITERAL 'nothrow') + * | $(LITERAL 'override') + * | $(LITERAL 'pure') + * | $(LITERAL 'ref') + * | $(LITERAL '___gshared') + * | $(LITERAL 'scope') + * | $(LITERAL 'static') + * | $(LITERAL 'synchronized') + * ;) + */ + StorageClass parseStorageClass() + { + auto node = allocate!StorageClass; + switch (current.type) + { + case tok!"@": + node.atAttribute = parseAtAttribute(); + if (node.atAttribute is null) return null; + break; + case tok!"deprecated": + node.deprecated_ = parseDeprecated(); + break; + case tok!"align": + node.alignAttribute = parseAlignAttribute(); + break; + case tok!"extern": + if (peekIs(tok!"(")) + { + node.linkageAttribute = parseLinkageAttribute(); + break; + } + else goto case; + case tok!"const": + case tok!"immutable": + case tok!"inout": + case tok!"shared": + case tok!"abstract": + case tok!"auto": + case tok!"enum": + case tok!"final": + case tok!"virtual": + case tok!"nothrow": + case tok!"override": + case tok!"pure": + case tok!"ref": + case tok!"__gshared": + case tok!"scope": + case tok!"static": + case tok!"synchronized": + node.token = advance(); + break; + default: + error(`Storage class expected`); + return null; + } + return node; + } + + /** + * Parses a StructBody + * + * $(GRAMMAR $(RULEDEF structBody): + * $(LITERAL '{') $(RULE declaration)* $(LITERAL '}') + * ;) + */ + StructBody parseStructBody() + { + mixin(traceEnterAndExit!(__FUNCTION__)); + auto node = allocate!StructBody; + auto start = expect(tok!"{"); + if (start !is null) node.startLocation = start.index; + Declaration[] declarations; + while (!currentIs(tok!"}") && moreTokens()) + { + auto dec = parseDeclaration(); + if (dec !is null) + declarations ~= dec; + } + node.declarations = ownArray(declarations); + auto end = expect(tok!"}"); + if (end !is null) node.endLocation = end.index; + return node; + } + + /** + * Parses a StructDeclaration + * + * $(GRAMMAR $(RULEDEF structDeclaration): + * $(LITERAL 'struct') $(LITERAL Identifier)? ($(RULE templateParameters) $(RULE constraint)? $(RULE structBody) | ($(RULE structBody) | $(LITERAL ';'))) + * ;) + */ + StructDeclaration parseStructDeclaration() + { + mixin(traceEnterAndExit!(__FUNCTION__)); + auto node = allocate!StructDeclaration; + expect(tok!"struct"); + if (currentIs(tok!"identifier")) + { + node.name = advance(); + } + node.comment = comment; + comment = null; + + if (currentIs(tok!"(")) + { + node.templateParameters = parseTemplateParameters(); + if (tokens[index] == tok!"if") + node.constraint = parseConstraint(); + node.structBody = parseStructBody(); + } + else if (currentIs(tok!"{")) + { + node.structBody = parseStructBody(); + } + else if (currentIs(tok!";")) + advance(); + else + { + error("Template Parameters, Struct Body, or Semicolon expected"); + return null; + } + return node; + } + + /** + * Parses an StructInitializer + * + * $(GRAMMAR $(RULEDEF structInitializer): + * $(LITERAL '{') $(RULE structMemberInitializers)? $(LITERAL '}') + * ;) + */ + StructInitializer parseStructInitializer() + { + mixin(traceEnterAndExit!(__FUNCTION__)); + auto node = allocate!StructInitializer; + expect(tok!"{"); + if (currentIs(tok!"}")) + advance(); + else + { + node.structMemberInitializers = parseStructMemberInitializers(); + expect(tok!"}"); + } + return node; + } + + /** + * Parses a StructMemberInitializer + * + * $(GRAMMAR $(RULEDEF structMemberInitializer): + * ($(LITERAL Identifier) $(LITERAL ':'))? $(RULE nonVoidInitializer) + * ;) + */ + StructMemberInitializer parseStructMemberInitializer() + { + mixin(traceEnterAndExit!(__FUNCTION__)); + auto node = allocate!StructMemberInitializer; + if (startsWith(tok!"identifier", tok!":")) + { + node.identifier = tokens[index++]; + index++; + } + node.nonVoidInitializer = parseNonVoidInitializer(); + return node; + } + + /** + * Parses StructMemberInitializers + * + * $(GRAMMAR $(RULEDEF structMemberInitializers): + * $(RULE structMemberInitializer) ($(LITERAL ',') $(RULE structMemberInitializer)?)* + * ;) + */ + StructMemberInitializers parseStructMemberInitializers() + { + mixin(traceEnterAndExit!(__FUNCTION__)); + auto node = allocate!StructMemberInitializers; + StructMemberInitializer[] structMemberInitializers; + do + { + auto structMemberInitializer = parseStructMemberInitializer(); + if (structMemberInitializer !is null) + structMemberInitializers ~= structMemberInitializer; + if (currentIs(tok!",")) + { + advance(); + if (!currentIs(tok!"}")) + continue; + else + break; + } + else + break; + } while (moreTokens()); + node.structMemberInitializers = ownArray(structMemberInitializers); + return node; + } + + /** + * Parses a SwitchStatement + * + * $(GRAMMAR $(RULEDEF switchStatement): + * $(LITERAL 'switch') $(LITERAL '$(LPAREN)') $(RULE expression) $(LITERAL '$(RPAREN)') $(RULE statement) + * ;) + */ + SwitchStatement parseSwitchStatement() + { + mixin(traceEnterAndExit!(__FUNCTION__)); + auto node = allocate!SwitchStatement; + expect(tok!"switch"); + expect(tok!"("); + node.expression = parseExpression(); + expect(tok!")"); + node.statement = parseStatement(); + return node; + } + + /** + * Parses a Symbol + * + * $(GRAMMAR $(RULEDEF symbol): + * $(LITERAL '.')? $(RULE identifierOrTemplateChain) + * ;) + */ + Symbol parseSymbol() + { + mixin(traceEnterAndExit!(__FUNCTION__)); + auto node = allocate!Symbol; + if (currentIs(tok!".")) + { + node.dot = true; + advance(); + } + node.identifierOrTemplateChain = parseIdentifierOrTemplateChain(); + return node; + } + + /** + * Parses a SynchronizedStatement + * + * $(GRAMMAR $(RULEDEF synchronizedStatement): + * $(LITERAL 'synchronized') ($(LITERAL '$(LPAREN)') $(RULE expression) $(LITERAL '$(RPAREN)'))? $(RULE statementNoCaseNoDefault) + * ;) + */ + SynchronizedStatement parseSynchronizedStatement() + { + mixin(traceEnterAndExit!(__FUNCTION__)); + auto node = allocate!SynchronizedStatement; + expect(tok!"synchronized"); + if (currentIs(tok!"(")) + { + expect(tok!"("); + node.expression = parseExpression(); + expect(tok!")"); + } + node.statementNoCaseNoDefault = parseStatementNoCaseNoDefault(); + return node; + } + + /** + * Parses a TemplateAliasParameter + * + * $(GRAMMAR $(RULEDEF templateAliasParameter): + * $(LITERAL 'alias') $(RULE type)? $(LITERAL Identifier) ($(LITERAL ':') ($(RULE type) | $(RULE assignExpression)))? ($(LITERAL '=') ($(RULE type) | $(RULE assignExpression)))? + * ;) + */ + TemplateAliasParameter parseTemplateAliasParameter() + { + mixin(traceEnterAndExit!(__FUNCTION__)); + auto node = allocate!TemplateAliasParameter; + expect(tok!"alias"); + if (currentIs(tok!"identifier")) + { + if (peekIsOneOf(tok!",", tok!")", tok!"=", + tok!":")) + { + node.identifier = advance(); + } + } + else + { + if ((node.type = parseType()) is null) return null; + auto ident = expect(tok!"identifier"); + if (ident is null) return null; + node.identifier = *ident; + } + + if (currentIs(tok!":")) + { + advance(); + if (isType()) + node.colonType = parseType(); + else + node.colonExpression = parseAssignExpression(); + } + if (currentIs(tok!"=")) + { + advance(); + if (isType()) + node.assignType = parseType(); + else + node.assignExpression = parseAssignExpression(); + } + return node; + } + + /** + * Parses a TemplateArgument + * + * $(GRAMMAR $(RULEDEF templateArgument): + * $(RULE type) + * | $(RULE assignExpression) + * ;) + */ + TemplateArgument parseTemplateArgument() + { + mixin(traceEnterAndExit!(__FUNCTION__)); + auto node = allocate!TemplateArgument; + auto b = setBookmark(); + auto t = parseType(); + if (t !is null && currentIsOneOf(tok!",", tok!")")) + { + goToBookmark(b); + node.type = parseType(); + } + else + { + goToBookmark(b); + if ((node.assignExpression = parseAssignExpression()) is null) return null; + } + return node; + } + + /** + * Parses a TemplateArgumentList + * + * $(GRAMMAR $(RULEDEF templateArgumentList): + * $(RULE templateArgument) ($(LITERAL ',') $(RULE templateArgument)?)* + * ;) + */ + TemplateArgumentList parseTemplateArgumentList() + { + mixin(traceEnterAndExit!(__FUNCTION__)); + return parseCommaSeparatedRule!(TemplateArgumentList, TemplateArgument)(true); + } + + /** + * Parses TemplateArguments + * + * $(GRAMMAR $(RULEDEF templateArguments): + * $(LITERAL '!') ($(LITERAL '$(LPAREN)') $(RULE templateArgumentList)? $(LITERAL '$(RPAREN)') | $(RULE templateSingleArgument)) + * ;) + */ + TemplateArguments parseTemplateArguments() + { + mixin(traceEnterAndExit!(__FUNCTION__)); + auto node = allocate!TemplateArguments; + expect(tok!"!"); + if (currentIs(tok!"(")) + { + advance(); + if (!currentIs(tok!")")) + node.templateArgumentList = parseTemplateArgumentList(); + expect(tok!")"); + } + else + node.templateSingleArgument = parseTemplateSingleArgument(); + return node; + } + + /** + * Parses a TemplateDeclaration + * + * $(GRAMMAR $(RULEDEF templateDeclaration): + * $(LITERAL 'template') $(LITERAL Identifier) $(RULE templateParameters) $(RULE constraint)? $(LITERAL '{') $(RULE declaration)* $(LITERAL '}') + * | $(RULE eponymousTemplateDeclaration) + * ;) + */ + TemplateDeclaration parseTemplateDeclaration() + { + mixin(traceEnterAndExit!(__FUNCTION__)); + auto node = allocate!TemplateDeclaration; + node.comment = comment; + comment = null; + if (currentIs(tok!"enum")) + { + node.eponymousTemplateDeclaration = parseEponymousTemplateDeclaration(); + return node; + } + expect(tok!"template"); + auto ident = expect(tok!"identifier"); + if (ident is null) return null; + node.name = *ident; + node.templateParameters = parseTemplateParameters(); + if (currentIs(tok!"if")) + node.constraint = parseConstraint(); + if (expect(tok!"{") is null) return null; + Declaration[] declarations; + while (moreTokens() && !currentIs(tok!"}")) + { + auto decl = parseDeclaration(); + if (decl !is null) + declarations ~= decl; + } + node.declarations = ownArray(declarations); + expect(tok!"}"); + return node; + } + + /** + * Parses an EponymousTemplateDeclaration + * + * $(GRAMMAR $(RULEDEF eponymousTemplateDeclaration): + * $(LITERAL 'enum') $(LITERAL Identifier) $(RULE templateParameters) $(LITERAL '=') $(RULE assignExpression) $(LITERAL ';') + * $(LITERAL 'enum') $(LITERAL Identifier) $(RULE templateParameters) $(LITERAL '=') $(RULE type) $(LITERAL ';') + * ;) + */ + EponymousTemplateDeclaration parseEponymousTemplateDeclaration() + { + mixin(traceEnterAndExit!(__FUNCTION__)); + auto node = allocate!EponymousTemplateDeclaration; + if (currentIsOneOf(tok!"enum", tok!"alias")) + advance(); + auto ident = expect(tok!"identifier"); + if (ident is null) return null; + node.name = *ident; + node.templateParameters = parseTemplateParameters(); + expect(tok!"="); + if (isExpression()) + node.assignExpression = parseAssignExpression(); + else + node.type = parseType(); + expect(tok!";"); + return node; + } + + /** + * Parses a TemplateInstance + * + * $(GRAMMAR $(RULEDEF templateInstance): + * $(LITERAL Identifier) $(RULE templateArguments) + * ;) + */ + TemplateInstance parseTemplateInstance() + { + mixin(traceEnterAndExit!(__FUNCTION__)); + auto node = allocate!TemplateInstance; + auto ident = expect(tok!"identifier"); + if (ident is null) return null; + node.identifier = *ident; + node.templateArguments = parseTemplateArguments(); + if (node.templateArguments is null) + return null; + return node; + } + + /** + * Parses a TemplateMixinExpression + * + * $(GRAMMAR $(RULEDEF templateMixinExpression): + * $(LITERAL 'mixin') $(RULE mixinTemplateName) $(RULE templateArguments)? $(LITERAL Identifier)? + * ;) + */ + TemplateMixinExpression parseTemplateMixinExpression() + { + mixin(traceEnterAndExit!(__FUNCTION__)); + auto node = allocate!TemplateMixinExpression; + if (expect(tok!"mixin") is null) return null; + node.mixinTemplateName = parseMixinTemplateName(); + if (currentIs(tok!"!")) + node.templateArguments = parseTemplateArguments(); + if (currentIs(tok!"identifier")) + node.identifier = advance(); + return node; + } + + /** + * Parses a TemplateParameter + * + * $(GRAMMAR $(RULEDEF templateParameter): + * $(RULE templateTypeParameter) + * | $(RULE templateValueParameter) + * | $(RULE templateAliasParameter) + * | $(RULE templateTupleParameter) + * | $(RULE templateThisParameter) + * ;) + */ + TemplateParameter parseTemplateParameter() + { + mixin(traceEnterAndExit!(__FUNCTION__)); + auto node = allocate!TemplateParameter; + switch (current.type) + { + case tok!"alias": + node.templateAliasParameter = parseTemplateAliasParameter(); + break; + case tok!"identifier": + if (peekIs(tok!"...")) + node.templateTupleParameter = parseTemplateTupleParameter(); + else if (peekIsOneOf(tok!":", tok!"=", tok!",", tok!")")) + node.templateTypeParameter = parseTemplateTypeParameter(); + else + node.templateValueParameter = parseTemplateValueParameter(); + break; + case tok!"this": + node.templateThisParameter = parseTemplateThisParameter(); + break; + default: + node.templateValueParameter = parseTemplateValueParameter(); + break; + } + return node; + } + + /** + * Parses an TemplateParameterList + * + * $(GRAMMAR $(RULEDEF templateParameterList): + * $(RULE templateParameter) ($(LITERAL ',') $(RULE templateParameter)?)* + * ;) + */ + TemplateParameterList parseTemplateParameterList() + { + mixin(traceEnterAndExit!(__FUNCTION__)); + return parseCommaSeparatedRule!(TemplateParameterList, TemplateParameter)(); + } + + /** + * Parses TemplateParameters + * + * $(GRAMMAR $(RULEDEF templateParameters): + * $(LITERAL '$(LPAREN)') $(RULE templateParameterList)? $(LITERAL '$(RPAREN)') + * ;) + */ + TemplateParameters parseTemplateParameters() + { + mixin(traceEnterAndExit!(__FUNCTION__)); + auto node = allocate!TemplateParameters; + if (expect(tok!"(") is null) return null; + if (!currentIs(tok!")")) + node.templateParameterList = parseTemplateParameterList(); + if (expect(tok!")") is null) return null; + return node; + } + + /** + * Parses a TemplateSingleArgument + * + * $(GRAMMAR $(RULEDEF templateSingleArgument): + * $(RULE builtinType) + * | $(LITERAL Identifier) + * | $(LITERAL CharacterLiteral) + * | $(LITERAL StringLiteral) + * | $(LITERAL IntegerLiteral) + * | $(LITERAL FloatLiteral) + * | $(LITERAL '_true') + * | $(LITERAL '_false') + * | $(LITERAL '_null') + * | $(LITERAL 'this') + * | $(LITERAL '___DATE__') + * | $(LITERAL '___TIME__') + * | $(LITERAL '___TIMESTAMP__') + * | $(LITERAL '___VENDOR__') + * | $(LITERAL '___VERSION__') + * | $(LITERAL '___FILE__') + * | $(LITERAL '___LINE__') + * | $(LITERAL '___MODULE__') + * | $(LITERAL '___FUNCTION__') + * | $(LITERAL '___PRETTY_FUNCTION__') + * ;) + */ + TemplateSingleArgument parseTemplateSingleArgument() + { + mixin(traceEnterAndExit!(__FUNCTION__)); + auto node = allocate!TemplateSingleArgument; + switch (current.type) + { + case tok!"true": + case tok!"false": + case tok!"null": + case tok!"this": + case tok!"identifier": + mixin (SPECIAL_CASES); + mixin (LITERAL_CASES); + mixin (BASIC_TYPE_CASES); + node.token = advance(); + break; + default: + error(`Invalid template argument. (Try enclosing in parenthesis?)`); + return null; + } + return node; + } + + /** + * Parses a TemplateThisParameter + * + * $(GRAMMAR $(RULEDEF templateThisParameter): + * $(LITERAL 'this') $(RULE templateTypeParameter) + * ;) + */ + TemplateThisParameter parseTemplateThisParameter() + { + mixin(traceEnterAndExit!(__FUNCTION__)); + auto node = allocate!TemplateThisParameter; + expect(tok!"this"); + node.templateTypeParameter = parseTemplateTypeParameter(); + return node; + } + + /** + * Parses an TemplateTupleParameter + * + * $(GRAMMAR $(RULEDEF templateTupleParameter): + * $(LITERAL Identifier) $(LITERAL '...') + * ;) + */ + TemplateTupleParameter parseTemplateTupleParameter() + { + mixin(traceEnterAndExit!(__FUNCTION__)); + auto node = allocate!TemplateTupleParameter; + auto i = expect(tok!"identifier"); + if (i is null) + return null; + node.identifier = *i; + if (expect(tok!"...") is null) return null; + return node; + } + + /** + * Parses a TemplateTypeParameter + * + * $(GRAMMAR $(RULEDEF templateTypeParameter): + * $(LITERAL Identifier) ($(LITERAL ':') $(RULE type))? ($(LITERAL '=') $(RULE type))? + * ;) + */ + TemplateTypeParameter parseTemplateTypeParameter() + { + mixin(traceEnterAndExit!(__FUNCTION__)); + auto node = allocate!TemplateTypeParameter; + auto ident = expect(tok!"identifier"); + if (ident is null) return null; + node.identifier = *ident; + if (currentIs(tok!":")) + { + advance(); + node.colonType = parseType(); + } + if (currentIs(tok!"=")) + { + advance(); + node.assignType = parseType(); + } + return node; + } + + /** + * Parses a TemplateValueParameter + * + * $(GRAMMAR $(RULEDEF templateValueParameter): + * $(RULE type) $(LITERAL Identifier) ($(LITERAL ':') $(RULE expression))? $(RULE templateValueParameterDefault)? + * ;) + */ + TemplateValueParameter parseTemplateValueParameter() + { + mixin(traceEnterAndExit!(__FUNCTION__)); + auto node = allocate!TemplateValueParameter; + if ((node.type = parseType()) is null) return null; + auto ident = expect(tok!"identifier"); + if (ident is null) return null; + node.identifier = *ident; + if (currentIs(tok!":")) + { + advance(); + if ((node.expression = parseExpression()) is null) return null; + } + if (currentIs(tok!"=")) + { + if ((node.templateValueParameterDefault = parseTemplateValueParameterDefault()) is null) + return null; + } + return node; + } + + /** + * Parses a TemplateValueParameterDefault + * + * $(GRAMMAR $(RULEDEF templateValueParameterDefault): + * $(LITERAL '=') ($(LITERAL '___FILE__') | $(LITERAL '___MODULE__') | $(LITERAL '___LINE__') | $(LITERAL '___FUNCTION__') | $(LITERAL '___PRETTY_FUNCTION__') | $(RULE assignExpression)) + * ;) + */ + TemplateValueParameterDefault parseTemplateValueParameterDefault() + { + mixin(traceEnterAndExit!(__FUNCTION__)); + auto node = allocate!TemplateValueParameterDefault; + expect(tok!"="); + switch (current.type) + { + case tok!"__FILE__": + case tok!"__MODULE__": + case tok!"__LINE__": + case tok!"__FUNCTION__": + case tok!"__PRETTY_FUNCTION__": + node.token = advance(); + break; + default: + node.assignExpression = parseAssignExpression(); + break; + } + return node; + } + + /** + * Parses a TernaryExpression + * + * $(GRAMMAR $(RULEDEF ternaryExpression): + * $(RULE orOrExpression) ($(LITERAL '?') $(RULE expression) $(LITERAL ':') $(RULE ternaryExpression))? + * ;) + */ + ExpressionNode parseTernaryExpression() + { + mixin(traceEnterAndExit!(__FUNCTION__)); + auto node = allocate!TernaryExpression; + node.orOrExpression = parseOrOrExpression(); + if (currentIs(tok!"?")) + { + advance(); + node.expression = parseExpression(); + if (expect(tok!":") is null) return null; + node.ternaryExpression = parseTernaryExpression(); + } + return node; + } + + /** + * Parses a ThrowStatement + * + * $(GRAMMAR $(RULEDEF throwStatement): + * $(LITERAL 'throw') $(RULE expression) $(LITERAL ';') + * ;) + */ + ThrowStatement parseThrowStatement() + { + mixin(traceEnterAndExit!(__FUNCTION__)); + auto node = allocate!ThrowStatement; + expect(tok!"throw"); + node.expression = parseExpression(); + expect(tok!";"); + return node; + } + + /** + * Parses an TraitsExpression + * + * $(GRAMMAR $(RULEDEF traitsExpression): + * $(LITERAL '___traits') $(LITERAL '$(LPAREN)') $(LITERAL Identifier) $(LITERAL ',') $(RULE TemplateArgumentList) $(LITERAL '$(RPAREN)') + * ;) + */ + TraitsExpression parseTraitsExpression() + { + mixin(traceEnterAndExit!(__FUNCTION__)); + auto node = allocate!TraitsExpression; + if (expect(tok!"__traits") is null) return null; + if (expect(tok!"(") is null) return null; + auto ident = expect(tok!"identifier"); + if (ident is null) return null; + node.identifier = *ident; + if (currentIs(tok!",")) + { + advance(); + if ((node.templateArgumentList = parseTemplateArgumentList()) is null) return null; + } + if (expect(tok!")") is null) return null; + return node; + } + + /** + * Parses a TryStatement + * + * $(GRAMMAR $(RULEDEF tryStatement): + * $(LITERAL 'try') $(RULE declarationOrStatement) ($(RULE catches) | $(RULE catches) $(RULE finally) | $(RULE finally)) + * ;) + */ + TryStatement parseTryStatement() + { + mixin(traceEnterAndExit!(__FUNCTION__)); + auto node = allocate!TryStatement; + expect(tok!"try"); + node.declarationOrStatement = parseDeclarationOrStatement(); + if (currentIs(tok!"catch")) + node.catches = parseCatches(); + if (currentIs(tok!"finally")) + node.finally_ = parseFinally(); + return node; + } + + /** + * Parses a Type + * + * $(GRAMMAR $(RULEDEF type): + * $(RULE attribute)? $(RULE type2) $(RULE typeSuffix)* + * ;) + */ + Type parseType() + { + mixin(traceEnterAndExit!(__FUNCTION__)); + auto node = allocate!Type; + switch (current.type) + { + case tok!"const": + case tok!"immutable": + case tok!"inout": + case tok!"shared": + case tok!"scope": + case tok!"pure": + case tok!"nothrow": + if (!peekIs(tok!"(")) + node.typeConstructors = parseTypeConstructors(); + break; + default: + break; + } + node.type2 = parseType2(); + if (node.type2 is null) + return null; + TypeSuffix[] typeSuffixes; + loop: while (moreTokens()) switch (current.type) + { + case tok!"*": + case tok!"[": + case tok!"delegate": + case tok!"function": + auto suffix = parseTypeSuffix(); + if (suffix !is null) + typeSuffixes ~= suffix; + else + return null; + break; + default: + break loop; + } + node.typeSuffixes = ownArray(typeSuffixes); + return node; + } + + /** + * Parses a Type2 + * + * $(GRAMMAR $(RULEDEF type2): + * $(RULE builtinType) + * | $(RULE symbol) + * | $(RULE typeofExpression) ($(LITERAL '.') $(RULE identifierOrTemplateChain))? + * | $(RULE typeConstructor) $(LITERAL '$(LPAREN)') $(RULE type) $(LITERAL '$(RPAREN)') + * ;) + */ + Type2 parseType2() + { + mixin(traceEnterAndExit!(__FUNCTION__)); + auto node = allocate!Type2; + switch (current.type) + { + case tok!"identifier": + case tok!".": + if ((node.symbol = parseSymbol()) is null) + return null; + break; + mixin (BASIC_TYPE_CASES); + if ((node.builtinType = parseBasicType()) == tok!"") + return null; + break; + case tok!"typeof": + if ((node.typeofExpression = parseTypeofExpression()) is null) + return null; + if (currentIs(tok!".")) + { + advance(); + node.identifierOrTemplateChain = parseIdentifierOrTemplateChain(); + if (node.identifierOrTemplateChain is null) + return null; + } + break; + case tok!"const": + case tok!"immutable": + case tok!"inout": + case tok!"shared": + node.typeConstructor = advance().type; + if (expect(tok!"(") is null) return null; + if ((node.type = parseType()) is null) return null; + if (expect(tok!")") is null) return null; + break; + default: + error("Basic type, type constructor, symbol, or typeof expected"); + return null; + } + return node; + } + + /** + * Parses a TypeConstructor + * + * $(GRAMMAR $(RULEDEF typeConstructor): + * $(LITERAL 'const') + * | $(LITERAL 'immutable') + * | $(LITERAL 'inout') + * | $(LITERAL 'shared') + * | $(LITERAL 'scope') + * ;) + */ + IdType parseTypeConstructor(bool validate = true) + { + mixin(traceEnterAndExit!(__FUNCTION__)); + switch (current.type) + { + case tok!"const": + case tok!"immutable": + case tok!"inout": + case tok!"shared": + case tok!"scope": + case tok!"ref": + case tok!"pure": + case tok!"nothrow": + if (!peekIs(tok!"(")) + return advance().type; + goto default; + default: + if (validate) + error(`"const", "immutable", "inout", "shared", or "scope" expected`); + return tok!""; + } + } + + /** + * Parses TypeConstructors + * + * $(GRAMMAR $(RULEDEF typeConstructors): + * $(RULE typeConstructor)+ + * ;) + */ + IdType[] parseTypeConstructors() + { + mixin(traceEnterAndExit!(__FUNCTION__)); + IdType[] r; + while (moreTokens()) + { + IdType type = parseTypeConstructor(false); + if (type == tok!"") + break; + else + r ~= type; + } + return r; + } + + /** + * Parses a TypeSpecialization + * + * $(GRAMMAR $(RULEDEF typeSpecialization): + * $(RULE type) + * | $(LITERAL 'struct') + * | $(LITERAL 'union') + * | $(LITERAL 'class') + * | $(LITERAL 'interface') + * | $(LITERAL 'enum') + * | $(LITERAL 'function') + * | $(LITERAL 'delegate') + * | $(LITERAL 'super') + * | $(LITERAL 'const') + * | $(LITERAL 'immutable') + * | $(LITERAL 'inout') + * | $(LITERAL 'shared') + * | $(LITERAL 'return') + * | $(LITERAL 'typedef') + * | $(LITERAL '___parameters') + * ;) + */ + TypeSpecialization parseTypeSpecialization() + { + mixin(traceEnterAndExit!(__FUNCTION__)); + auto node = allocate!TypeSpecialization; + switch (current.type) + { + case tok!"struct": + case tok!"union": + case tok!"class": + case tok!"interface": + case tok!"enum": + case tok!"function": + case tok!"delegate": + case tok!"super": + case tok!"return": + case tok!"typedef": + case tok!"__parameters": + case tok!"const": + case tok!"immutable": + case tok!"inout": + case tok!"shared": + if (peekIsOneOf(tok!")", tok!",")) + { + node.token = advance(); + break; + } + goto default; + default: + node.type = parseType(); + break; + } + return node; + } + + /** + * Parses a TypeSuffix + * + * $(GRAMMAR $(RULEDEF typeSuffix): + * $(LITERAL '*') + * | $(LITERAL '[') $(RULE type)? $(LITERAL ']') + * | $(LITERAL '[') $(RULE assignExpression) $(LITERAL ']') + * | $(LITERAL '[') $(RULE assignExpression) $(LITERAL '..') $(RULE assignExpression) $(LITERAL ']') + * | ($(LITERAL 'delegate') | $(LITERAL 'function')) $(RULE parameters) $(RULE memberFunctionAttribute)* + * ;) + */ + TypeSuffix parseTypeSuffix() + { + mixin(traceEnterAndExit!(__FUNCTION__)); + auto node = allocate!TypeSuffix; + switch (current.type) + { + case tok!"*": + node.star = true; + advance(); + return node; + case tok!"[": + node.array = true; + advance(); + if (currentIs(tok!"]")) + { + advance(); + return node; + } + auto bookmark = setBookmark(); + auto type = parseType(); + if (type !is null && currentIs(tok!"]")) + { + goToBookmark(bookmark); + type = parseType(); + node.type = type; + } + else + { + goToBookmark(bookmark); + node.low = parseAssignExpression(); + if (node.low is null) return null; + if (currentIs(tok!"..")) + { + advance(); + node.high = parseAssignExpression(); + if (node.high is null) return null; + } + } + if (expect(tok!"]") is null) return null; + return node; + case tok!"delegate": + case tok!"function": + node.delegateOrFunction = advance(); + node.parameters = parseParameters(); + MemberFunctionAttribute[] memberFunctionAttributes; + while (currentIsMemberFunctionAttribute()) + memberFunctionAttributes ~= parseMemberFunctionAttribute(); + node.memberFunctionAttributes = ownArray(memberFunctionAttributes); + return node; + default: + error(`"*", "[", "delegate", or "function" expected.`); + return null; + } + } + + /** + * Parses a TypeidExpression + * + * $(GRAMMAR $(RULEDEF typeidExpression): + * $(LITERAL 'typeid') $(LITERAL '$(LPAREN)') ($(RULE type) | $(RULE expression)) $(LITERAL '$(RPAREN)') + * ;) + */ + TypeidExpression parseTypeidExpression() + { + mixin(traceEnterAndExit!(__FUNCTION__)); + auto node = allocate!TypeidExpression; + expect(tok!"typeid"); + expect(tok!"("); + auto b = setBookmark(); + auto t = parseType(); + if (t is null || !currentIs(tok!")")) + { + goToBookmark(b); + node.expression = parseExpression(); + if (node.expression is null) return null; + } + else + { + goToBookmark(b); + node.type = parseType(); + if (node.type is null) return null; + } + expect(tok!")"); + return node; + } + + /** + * Parses a TypeofExpression + * + * $(GRAMMAR $(RULEDEF typeofExpression): + * $(LITERAL 'typeof') $(LITERAL '$(LPAREN)') ($(RULE expression) | $(LITERAL 'return')) $(LITERAL '$(RPAREN)') + * ;) + */ + TypeofExpression parseTypeofExpression() + { + mixin(traceEnterAndExit!(__FUNCTION__)); + auto node = allocate!TypeofExpression; + expect(tok!"typeof"); + expect(tok!"("); + if (currentIs(tok!"return")) + node.return_ = advance(); + else + node.expression = parseExpression(); + expect(tok!")"); + return node; + } + + /** + * Parses a UnaryExpression + * + * $(GRAMMAR $(RULEDEF unaryExpression): + * $(RULE primaryExpression) + * | $(LITERAL '&') $(RULE unaryExpression) + * | $(LITERAL '!') $(RULE unaryExpression) + * | $(LITERAL '*') $(RULE unaryExpression) + * | $(LITERAL '+') $(RULE unaryExpression) + * | $(LITERAL '-') $(RULE unaryExpression) + * | $(LITERAL '~') $(RULE unaryExpression) + * | $(LITERAL '++') $(RULE unaryExpression) + * | $(LITERAL '--') $(RULE unaryExpression) + * | $(RULE newExpression) + * | $(RULE deleteExpression) + * | $(RULE castExpression) + * | $(RULE assertExpression) + * | $(RULE functionCallExpression) + * | $(RULE sliceExpression) + * | $(RULE indexExpression) + * | $(LITERAL '$(LPAREN)') $(RULE type) $(LITERAL '$(RPAREN)') $(LITERAL '.') $(RULE identifierOrTemplateInstance) + * | $(RULE unaryExpression) $(LITERAL '.') $(RULE identifierOrTemplateInstance) + * | $(RULE unaryExpression) $(LITERAL '--') + * | $(RULE unaryExpression) $(LITERAL '++') + * ;) + */ + UnaryExpression parseUnaryExpression() + { + mixin(traceEnterAndExit!(__FUNCTION__)); + if (!moreTokens()) + return null; + auto node = allocate!UnaryExpression; + switch (current.type) + { + case tok!"const": + case tok!"immutable": + case tok!"inout": + case tok!"shared": + auto b = setBookmark(); + if (peekIs(tok!"(")) + { + advance(); + auto past = peekPastParens(); + if (past !is null && past.type == tok!".") + { + goToBookmark(b); + goto default; + } + } + goToBookmark(b); + goto case; + case tok!"scope": + case tok!"pure": + case tok!"nothrow": + node.functionCallExpression = parseFunctionCallExpression(); + break; + case tok!"&": + case tok!"!": + case tok!"*": + case tok!"+": + case tok!"-": + case tok!"~": + case tok!"++": + case tok!"--": + node.prefix = advance(); + node.unaryExpression = parseUnaryExpression(); + break; + case tok!"new": + node.newExpression = parseNewExpression(); + break; + case tok!"delete": + node.deleteExpression = parseDeleteExpression(); + break; + case tok!"cast": + node.castExpression = parseCastExpression(); + break; + case tok!"assert": + node.assertExpression = parseAssertExpression(); + break; + case tok!"(": + // handle (type).identifier + auto b = setBookmark(); + skipParens(); + if (startsWith(tok!".", tok!"identifier")) + { + // go back to the ( + index = b; + advance(); + auto t = parseType(); + if (t is null || !currentIs(tok!")")) + { + goToBookmark(b); + goto default; + } + node.type = t; + advance(); // ) + advance(); // . + node.identifierOrTemplateInstance = parseIdentifierOrTemplateInstance(); + break; + } + else + { + // not (type).identifier, so treat as primary expression + goToBookmark(b); + goto default; + } + default: + node.primaryExpression = parsePrimaryExpression(); + break; + } + + loop: while (moreTokens()) switch (current.type) + { + case tok!"!": + if (peekIs(tok!"is")) + break loop; + index++; + bool jump = (currentIs(tok!"(") && peekPastParens().type == tok!"(") + || peekIs(tok!"("); + index--; + if (jump) + goto case tok!"("; + else + break loop; + case tok!"(": + auto newUnary = allocate!UnaryExpression(); + newUnary.functionCallExpression = parseFunctionCallExpression(node); + node = newUnary; + break; + case tok!"++": + case tok!"--": + auto n = allocate!UnaryExpression(); + n.unaryExpression = node; + n.suffix = advance(); + node = n; + break; + case tok!"[": + auto n = allocate!UnaryExpression; + if (isSliceExpression()) + n.sliceExpression = parseSliceExpression(node); + else + n.indexExpression = parseIndexExpression(node); + node = n; + break; + case tok!".": + advance(); + auto n = allocate!UnaryExpression(); + n.unaryExpression = node; + n.identifierOrTemplateInstance = parseIdentifierOrTemplateInstance(); + node = n; + break; + default: + break loop; + } + return node; + } + + unittest + { + auto sourceCode = +q{doStuff(5)}c; + Parser p = getParserForUnittest(sourceCode, "parseUnaryExpression"); + auto unary = p.parseUnaryExpression(); + assert (unary !is null); + assert (unary.functionCallExpression !is null); + stderr.writeln("Unittest for parseUnaryExpression passed."); + } + + /** + * Parses an UnionDeclaration + * + * $(GRAMMAR $(RULEDEF unionDeclaration): + * $(LITERAL 'union') $(LITERAL Identifier) $(RULE templateParameters) $(RULE constraint)? $(RULE structBody) + * | $(LITERAL 'union') $(LITERAL Identifier) ($(RULE structBody) | $(LITERAL ';')) + * | $(LITERAL 'union') $(RULE structBody) + * ;) + */ + UnionDeclaration parseUnionDeclaration() + { + mixin(traceEnterAndExit!(__FUNCTION__)); + auto node = allocate!UnionDeclaration; + // grab line number even if it's anonymous + auto l = expect(tok!"union").line; + if (currentIs(tok!"identifier")) + { + node.name = advance(); + if (currentIs(tok!"(")) + { + node.templateParameters = parseTemplateParameters(); + if (currentIs(tok!"if")) + node.constraint = parseConstraint(); + node.structBody = parseStructBody(); + } + else + goto semiOrStructBody; + } + else + { + node.name.line = l; + semiOrStructBody: + if (currentIs(tok!";")) + advance(); + else + node.structBody = parseStructBody(); + } + return node; + } + + /** + * Parses a Unittest + * + * $(GRAMMAR $(RULEDEF unittest): + * $(LITERAL 'unittest') $(RULE blockStatement) + * ;) + */ + Unittest parseUnittest() + { + mixin(traceEnterAndExit!(__FUNCTION__)); + mixin (simpleParse!(Unittest, tok!"unittest", "blockStatement|parseBlockStatement")); + } + + /** + * Parses a VariableDeclaration + * + * $(GRAMMAR $(RULEDEF variableDeclaration): + * $(RULE _type) $(RULE declarator) ($(LITERAL ',') $(RULE declarator))* $(LITERAL ';') + * | $(RULE _type) $(RULE declarator) $(LITERAL '=') $(RULE functionBody) + * | $(RULE autoDeclaration) + * ;) + */ + VariableDeclaration parseVariableDeclaration(Type type = null, bool isAuto = false) + { + mixin (traceEnterAndExit!(__FUNCTION__)); + auto node = allocate!VariableDeclaration; + if (isAuto) + { + node.autoDeclaration = parseAutoDeclaration(); + return node; + } + + node.type = type is null ? parseType() : type; + node.comment = comment; + + Declarator[] declarators; + while (true) + { + auto declarator = parseDeclarator(); + if (declarator is null) return null; + declarators ~= declarator; + if (moreTokens() && currentIs(tok!",")) + advance(); + else + break; + } + node.declarators = ownArray(declarators); +// if (node.declarators.length == 1 +// && node.declarators[0].initializer !is null +// && node.declarators[0].initializer.nonVoidInitializer !is null +// && node.declarators[0].initializer.nonVoidInitializer.functionBody !is null) +// { +// return node; +// } +// else + { + if (expect(tok!";") is null) return null; + return node; + } + } + + /** + * Parses a Vector + * + * $(GRAMMAR $(RULEDEF vector): + * $(LITERAL '___vector') $(LITERAL '$(LPAREN)') $(RULE type) $(LITERAL '$(RPAREN)') + * ;) + */ + Vector parseVector() + { + mixin(traceEnterAndExit!(__FUNCTION__)); + mixin (simpleParse!(Vector, tok!"__vector", tok!"(", "type|parseType", tok!")")); + } + + /** + * Parses a VersionCondition + * + * $(GRAMMAR $(RULEDEF versionCondition): + * $(LITERAL 'version') $(LITERAL '$(LPAREN)') ($(LITERAL IntegerLiteral) | $(LITERAL Identifier) | $(LITERAL 'unittest') | $(LITERAL 'assert')) $(LITERAL '$(RPAREN)') + * ;) + */ + VersionCondition parseVersionCondition() + { + mixin(traceEnterAndExit!(__FUNCTION__)); + auto node = allocate!VersionCondition; + mixin (expectSequence!(tok!"version", tok!"(")); + if (currentIsOneOf(tok!"intLiteral", tok!"identifier", + tok!"unittest", tok!"assert")) + { + node.token = advance(); + } + else + { + error(`Expected an integer literal, an identifier, "assert", or "unittest"`); + return null; + } + expect(tok!")"); + return node; + } + + /** + * Parses a VersionSpecification + * + * $(GRAMMAR $(RULEDEF versionSpecification): + * $(LITERAL 'version') $(LITERAL '=') ($(LITERAL Identifier) | $(LITERAL IntegerLiteral)) $(LITERAL ';') + * ;) + */ + VersionSpecification parseVersionSpecification() + { + mixin(traceEnterAndExit!(__FUNCTION__)); + auto node = allocate!VersionSpecification; + mixin (expectSequence!(tok!"version", tok!"=")); + if (!currentIsOneOf(tok!"identifier", tok!"intLiteral")) + { + error("Identifier or integer literal expected"); + return null; + } + node.token = advance(); + expect(tok!";"); + return node; + } + + /** + * Parses a WhileStatement + * + * $(GRAMMAR $(RULEDEF whileStatement): + * $(LITERAL 'while') $(LITERAL '$(LPAREN)') $(RULE expression) $(LITERAL '$(RPAREN)') $(RULE declarationOrStatement) + * ;) + */ + WhileStatement parseWhileStatement() + { + mixin(traceEnterAndExit!(__FUNCTION__)); + auto node = allocate!WhileStatement; + expect(tok!"while"); + node.startIndex = current().index; + expect(tok!"("); + node.expression = parseExpression(); + expect(tok!")"); + if (currentIs(tok!"}")) + { + error("Statement expected", false); + return node; // this line makes DCD better + } + node.declarationOrStatement = parseDeclarationOrStatement(); + return node; + } + + /** + * Parses a WithStatement + * + * $(GRAMMAR $(RULEDEF withStatement): + * $(LITERAL 'with') $(LITERAL '$(LPAREN)') $(RULE expression) $(LITERAL '$(RPAREN)') $(RULE statementNoCaseNoDefault) + * ;) + */ + WithStatement parseWithStatement() + { + mixin(traceEnterAndExit!(__FUNCTION__)); + mixin (simpleParse!(WithStatement, tok!"with", tok!"(", + "expression|parseExpression", tok!")", + "statementNoCaseNoDefault|parseStatementNoCaseNoDefault")); + } + + /** + * Parses an XorExpression + * + * $(GRAMMAR $(RULEDEF xorExpression): + * $(RULE andExpression) + * | $(RULE xorExpression) $(LITERAL '^') $(RULE andExpression) + * ;) + */ + ExpressionNode parseXorExpression() + { + mixin(traceEnterAndExit!(__FUNCTION__)); + return parseLeftAssocBinaryExpression!(XorExpression, AndExpression, + tok!"^")(); + } + + /** + * Current error count + */ + uint errorCount; + + /** + * Current warning count + */ + uint warningCount; + + /** + * Name used when reporting warnings and errors + */ + string fileName; + + /** + * Allocator used for creating AST nodes + */ + CAllocator allocator; + + /** + * Function that is called when a warning or error is encountered. + * The parameters are the file name, line number, column number, + * and the error or warning message. + */ + void function(string, size_t, size_t, string, bool) messageFunction; + + bool isSliceExpression() + { + mixin(traceEnterAndExit!(__FUNCTION__)); + if (startsWith(tok!"[", tok!"]")) + return true; + return hasMagicDelimiter!(tok!"[", tok!"..")(); + } + + void setTokens(const(Token)[] tokens) + { + this.tokens = tokens; + } + + /** + * Returns: true if there are more tokens + */ + bool moreTokens() const nothrow pure @safe + { + return index < tokens.length; + } + +protected: + + T[] ownArray(T)(T[] from) + { + if (allocator is null) + return from; + T[] to = cast(T[]) allocator.allocate(T.sizeof * from.length); + assert (to.length == from.length, format("from.length = %d, to.length = %d", from.length, to.length)); + to[] = from[]; + return to; + } + + T allocate(T, Args...)(auto ref Args args) + { + import std.stdio; + if (allocator is null) + return new T(args); + enum numBytes = __traits(classInstanceSize, T); + void[] mem = allocator.allocate(numBytes); + assert (mem.length == numBytes, format("%d", mem.length)); + T t = emplace!T(mem, args); + assert (cast(void*) t == mem.ptr, "%x, %x".format(cast(void*) t, mem.ptr)); + return t; + } + + bool isCastQualifier() const + { + switch (current.type) + { + case tok!"const": + return peekIsOneOf(tok!"shared", tok!")"); + case tok!"immutable": + return peekIs(tok!")"); + case tok!"inout": + return peekIsOneOf(tok!"shared", tok!")"); + case tok!"shared": + return peekIsOneOf(tok!"const", tok!"inout", tok!")"); + default: + return false; + } + } + + bool isAssociativeArrayLiteral() + { + auto b = setBookmark(); + scope(exit) goToBookmark(b); + advance(); + return !currentIs(tok!"]") && parseExpression() !is null && currentIs(tok!":"); + } + + bool hasMagicDelimiter(alias L, alias T)() + { + mixin(traceEnterAndExit!(__FUNCTION__)); + auto i = index; + scope(exit) index = i; + assert (currentIs(L)); + advance(); + while (moreTokens()) switch (current.type) + { + case tok!"{": skipBraces(); break; + case tok!"(": skipParens(); break; + case tok!"[": skipBrackets(); break; + case tok!"]": case tok!"}": return false; + case T: return true; + default: advance(); break; + } + return false; + } + + bool isDeclaration() + { + mixin(traceEnterAndExit!(__FUNCTION__)); + if (!moreTokens()) return false; + switch (current.type) + { + case tok!"final": + return !peekIs(tok!"switch"); + case tok!"debug": + case tok!"version": + { + if (peekIs(tok!"=")) + return false; + + auto b = setBookmark(); + scope (exit) goToBookmark(b); + advance(); + return isDeclaration(); + } + case tok!"synchronized": + if (peekIs(tok!"(")) + return false; + else + { + auto b = setBookmark(); + scope (exit) goToBookmark(b); + advance(); + return isDeclaration(); + } + case tok!"static": + if (peekIs(tok!"if")) + return false; + goto case; + case tok!"scope": + if (peekIs(tok!"(")) + return false; + goto case; + case tok!"const": + case tok!"immutable": + case tok!"inout": + case tok!"shared": + case tok!"__gshared": + case tok!"alias": + case tok!"class": + case tok!"enum": + case tok!"interface": + case tok!"struct": + case tok!"union": + case tok!"unittest": + case tok!"auto": + case tok!"ref": + case tok!"@": + case tok!"align": + case tok!"deprecated": + case tok!"private": + case tok!"package": + case tok!"protected": + case tok!"public": + case tok!"export": + case tok!"extern": + case tok!"override": + case tok!"abstract": + case tok!"pure": + case tok!"nothrow": + case tok!"virtual": + return true; + mixin(BASIC_TYPE_CASES); + return !peekIs(tok!"."); + case tok!"case": + case tok!"default": + case tok!"return": + case tok!"if": + case tok!"while": + case tok!"do": + case tok!"for": + case tok!"foreach": + case tok!"switch": + case tok!"continue": + case tok!"break": + case tok!"goto": + case tok!"try": + case tok!"throw": + case tok!"asm": + case tok!"foreach_reverse": + case tok!"{": + case tok!"assert": + return false; + default: + break; + } + auto b = setBookmark(); + scope(exit) goToBookmark(b); + + return parseDeclaration() !is null; + } + + bool isStatement() + { + if (!moreTokens()) return false; + auto b = setBookmark(); + scope (exit) goToBookmark(b); + return parseStatement() !is null; + } + + bool isExpression() + { + if (!moreTokens()) return false; + auto b = setBookmark(); + scope (exit) goToBookmark(b); + return parseExpression() !is null; + } + + bool isType() + { + if (!moreTokens()) return false; + auto b = setBookmark(); + scope (exit) goToBookmark(b); + if (parseType() is null) return false; + if (currentIsOneOf(tok!",", tok!")")) return true; + return false; + } + + bool isStorageClass() + { + if (!moreTokens()) return false; + switch (current.type) + { + case tok!"const": + case tok!"immutable": + case tok!"inout": + case tok!"shared": + return !peekIs(tok!"("); + case tok!"@": + case tok!"deprecated": + case tok!"abstract": + case tok!"align": + case tok!"auto": + case tok!"enum": + case tok!"extern": + case tok!"final": + case tok!"virtual": + case tok!"nothrow": + case tok!"override": + case tok!"pure": + case tok!"ref": + case tok!"__gshared": + case tok!"scope": + case tok!"static": + case tok!"synchronized": + return true; + default: + return false; + } + } + + bool isAttribute() + { + if (!moreTokens()) return false; + switch (current.type) + { + case tok!"const": + case tok!"immutable": + case tok!"inout": + case tok!"scope": + return !peekIs(tok!"("); + case tok!"static": + return !peekIsOneOf(tok!"assert", tok!"this", tok!"if", tok!"~"); + case tok!"shared": + return !(startsWith(tok!"shared", tok!"static", tok!"this") + || startsWith(tok!"shared", tok!"static", tok!"~") + || peekIs(tok!"(")); + case tok!"enum": + if (peekIsOneOf(tok!"{", tok!":", tok!";")) + return false; + else if (peekIs(tok!"identifier")) + { + if (startsWith(tok!"enum", tok!"identifier", tok!"(")) + return false; + auto b = setBookmark(); + scope(exit) goToBookmark(b); + advance(); + if (peekIsOneOf(tok!"{", tok!":", tok!";")) + return false; + return true; + } + return true; + case tok!"pragma": + auto b = setBookmark(); + scope(exit) goToBookmark(b); + advance(); + auto past = peekPastParens(); + if (past is null || *past == tok!";") + return false; + return true; + case tok!"deprecated": + case tok!"private": + case tok!"package": + case tok!"protected": + case tok!"public": + case tok!"export": + case tok!"final": + case tok!"virtual": + case tok!"synchronized": + case tok!"override": + case tok!"abstract": + case tok!"auto": + case tok!"__gshared": + case tok!"pure": + case tok!"nothrow": + case tok!"@": + case tok!"ref": + case tok!"extern": + case tok!"align": + return true; + default: + return false; + } + } + + bool currentIsMemberFunctionAttribute() const + { + switch (current.type) + { + case tok!"const": + case tok!"immutable": + case tok!"inout": + case tok!"shared": + case tok!"@": + case tok!"pure": + case tok!"nothrow": + return true; + default: + return false; + } + } + + ExpressionNode parseLeftAssocBinaryExpression(alias ExpressionType, + alias ExpressionPartType, Operators ...)(ExpressionNode part = null) + { + ExpressionNode node; + mixin ("node = part is null ? parse" ~ ExpressionPartType.stringof ~ "() : part;"); + while (currentIsOneOf(Operators)) + { + auto n = allocate!ExpressionType; + static if (__traits(hasMember, ExpressionType, "operator")) + { + n.line = current.line; + n.column = current.column; + n.operator = advance().type; + } + else + advance(); + n.left = node; + mixin ("n.right = parse" ~ ExpressionPartType.stringof ~ "();"); + node = n; + } + return node; + } + + ListType parseCommaSeparatedRule(alias ListType, alias ItemType)(bool allowTrailingComma = false) + { + auto node = allocate!ListType; + ItemType[] items; + while (moreTokens()) + { + mixin ("items ~= parse" ~ ItemType.stringof ~ "();"); + if (currentIs(tok!",")) + { + advance(); + if (allowTrailingComma && currentIsOneOf(tok!")", + tok!"}", tok!"]")) + { + break; + } + else + continue; + } + else + break; + } + node.items = ownArray(items); + return node; + } + + void warn(lazy string message) + { + if (suppressMessages > 0) + return; + ++warningCount; + auto column = index < tokens.length ? tokens[index].column : 0; + auto line = index < tokens.length ? tokens[index].line : 0; + if (messageFunction is null) + stderr.writefln("%s(%d:%d)[warn]: %s", fileName, line, column, message); + else + messageFunction(fileName, line, column, message, false); + } + + void error(lazy string message, bool shouldAdvance = true) + { + import std.stdio; + if (suppressMessages <= 0) + { + ++errorCount; + auto column = index < tokens.length ? tokens[index].column : tokens[$ - 1].column; + auto line = index < tokens.length ? tokens[index].line : tokens[$ - 1].line; + if (messageFunction is null) + { + stderr.writefln("%s(%d:%d)[error]: %s", fileName, line, column, message); + } + else + messageFunction(fileName, line, column, message, true); + } + while (shouldAdvance && moreTokens()) + { + if (currentIsOneOf(tok!";", tok!"}", + tok!")", tok!"]")) + { + advance(); + break; + } + else + advance(); + } + } + + void skip(alias O, alias C)() + { + assert (currentIs(O), current().text); + advance(); + int depth = 1; + while (moreTokens()) + { + switch (tokens[index].type) + { + case C: + advance(); + depth--; + if (depth <= 0) + return; + break; + case O: + depth++; + advance(); + break; + default: + advance(); + break; + } + } + } + + void skipBraces() + { + skip!(tok!"{", tok!"}")(); + } + + void skipParens() + { + skip!(tok!"(", tok!")")(); + } + + void skipBrackets() + { + skip!(tok!"[", tok!"]")(); + } + + const(Token)* peek() const + { + return index + 1 < tokens.length ? &tokens[index + 1] : null; + } + + const(Token)* peekPast(alias O, alias C)() const nothrow + { + if (index >= tokens.length) + return null; + int depth = 1; + size_t i = index; + ++i; + while (i < tokens.length) + { + if (tokens[i] == O) + { + ++depth; + ++i; + } + else if (tokens[i] == C) + { + --depth; + ++i; + if (depth <= 0) + break; + } + else + ++i; + } + return i >= tokens.length ? null : depth == 0 ? &tokens[i] : null; + } + + const(Token)* peekPastParens() const nothrow + { + return peekPast!(tok!"(", tok!")")(); + } + + const(Token)* peekPastBrackets() const nothrow + { + return peekPast!(tok!"[", tok!"]")(); + } + + const(Token)* peekPastBraces() const nothrow + { + return peekPast!(tok!"{", tok!"}")(); + } + + bool peekIs(IdType t) const nothrow + { + return index + 1 < tokens.length && tokens[index + 1].type == t; + } + + bool peekIsOneOf(IdType[] types...) const nothrow + { + if (index + 1 >= tokens.length) return false; + return canFind(types, tokens[index + 1].type); + } + + /** + * Returns a token of the specified type if it was the next token, otherwise + * calls the error function and returns null. + */ + const(Token)* expect(IdType type) + { + if (index < tokens.length && tokens[index].type == type) + return &tokens[index++]; + else + { + string tokenString = str(type) is null + ? to!string(type) : str(type); + bool shouldNotAdvance = index < tokens.length + && (tokens[index].type == tok!")" + || tokens[index].type == tok!";" + || tokens[index].type == tok!"}"); + auto token = (index < tokens.length + ? (tokens[index].text is null ? str(tokens[index].type) : tokens[index].text) + : "EOF"); + error("Expected " ~ tokenString ~ " instead of " ~ token, + !shouldNotAdvance); + return null; + } + } + + /** + * Returns: the _current token + */ + Token current() const @property + { + return tokens[index]; + } + + /** + * Advances to the next token and returns the current token + */ + Token advance() + { + return tokens[index++]; + } + + /** + * Returns: true if the current token has the given type + */ + bool currentIs(IdType type) const + { + return index < tokens.length && tokens[index] == type; + } + + /** + * Returns: true if the current token is one of the given types + */ + bool currentIsOneOf(IdType[] types...) const + { + if (index >= tokens.length) + return false; + return canFind(types, current.type); + } + + bool startsWith(IdType[] types...) const nothrow + { + if (index + types.length >= tokens.length) + return false; + for (size_t i = 0; (i < types.length) && ((index + i) < tokens.length); ++i) + { + if (tokens[index + i].type != types[i]) + return false; + } + return true; + } + + size_t setBookmark() + { +// mixin(traceEnterAndExit!(__FUNCTION__)); + suppressMessages++; + auto i = index; + return i; + } + + void goToBookmark(size_t i) nothrow + { + --suppressMessages; + index = i; + } + + version (unittest) static void doNothingErrorFunction(string, + size_t, size_t, string, bool) {} + + version (unittest) static Parser getParserForUnittest(string sourceCode, + string testName) + { + auto r = byToken(cast(ubyte[]) sourceCode); + + CAllocator allocator = new ParseAllocator(); + enum numBytes = __traits(classInstanceSize, Parser); + void[] mem = allocator.allocate(numBytes); + assert (mem.length == numBytes, format("%d", mem.length)); + Parser p = emplace!Parser(mem); + assert (cast(void*) p == mem.ptr, "%x, %x".format(cast(void*) p, mem.ptr)); + + p.messageFunction = &doNothingErrorFunction; + p.fileName = testName ~ ".d"; + p.tokens = r.array(); + return p; + } + + template simpleParse(NodeType, parts ...) + { + static if (__traits(hasMember, NodeType, "comment")) + enum simpleParse = "auto node = allocate!" ~ NodeType.stringof ~ ";\n" + ~ "node.comment = comment;\n" + ~ "comment = null;\n" + ~ simpleParseItems!(parts) + ~ "\nreturn node;\n"; + else + enum simpleParse = "auto node = allocate!" ~ NodeType.stringof ~ ";\n" + ~ simpleParseItems!(parts) + ~ "\nreturn node;\n"; + } + + template simpleParseItems(items ...) + { + static if (items.length > 1) + enum simpleParseItems = simpleParseItem!(items[0]) ~ "\n" + ~ simpleParseItems!(items[1 .. $]); + else static if (items.length == 1) + enum simpleParseItems = simpleParseItem!(items[0]); + else + enum simpleParseItems = ""; + } + + template simpleParseItem(alias item) + { + static if (is (typeof(item) == string)) + enum simpleParseItem = "if ((node." ~ item[0 .. item.countUntil("|")] + ~ " = " ~ item[item.countUntil("|") + 1 .. $] ~ "()) is null) return null;"; + else + enum simpleParseItem = "if (expect(" ~ item.stringof ~ ") is null) return null;"; + } + + template expectSequence(sequence ...) + { + static if (sequence.length == 1) + enum expectSequence = "if (expect(" ~ sequence[0].stringof ~ ") is null) return null;"; + else + enum expectSequence = "if (expect(" ~ sequence[0].stringof ~ ") is null) return null;\n" + ~ expectSequence!(sequence[1..$]); + } + + template traceEnterAndExit(string fun) + { + enum traceEnterAndExit = `version (std_parser_verbose) { _traceDepth++; trace("` + ~ `\033[01;32m` ~ fun ~ `\033[0m"); }` + ~ `version (std_parser_verbose) scope(exit) { trace("` + ~ `\033[01;31m` ~ fun ~ `\033[0m"); _traceDepth--; }`; + } + + version (std_parser_verbose) + { + void trace(lazy string message) + { + auto depth = format("%4d ", _traceDepth); + if (suppressMessages > 0) + return; + if (index < tokens.length) + writeln(depth, message, "(", current.line, ":", current.column, ")"); + else + writeln(depth, message, "(EOF:0)"); + } + } + else + { + void trace(lazy string) {} + } + + enum string BASIC_TYPE_CASES = q{ + case tok!"int": + case tok!"uint": + case tok!"double": + case tok!"idouble": + case tok!"float": + case tok!"ifloat": + case tok!"short": + case tok!"ushort": + case tok!"long": + case tok!"ulong": + case tok!"char": + case tok!"wchar": + case tok!"dchar": + case tok!"bool": + case tok!"void": + case tok!"cent": + case tok!"ucent": + case tok!"real": + case tok!"ireal": + case tok!"byte": + case tok!"ubyte": + case tok!"cdouble": + case tok!"cfloat": + case tok!"creal": + }; + enum string LITERAL_CASES = q{ + case tok!"doubleLiteral": + case tok!"floatLiteral": + case tok!"idoubleLiteral": + case tok!"ifloatLiteral": + case tok!"intLiteral": + case tok!"longLiteral": + case tok!"realLiteral": + case tok!"irealLiteral": + case tok!"uintLiteral": + case tok!"ulongLiteral": + case tok!"stringLiteral": + case tok!"wstringLiteral": + case tok!"dstringLiteral": + case tok!"characterLiteral": + }; + enum string SPECIAL_CASES = q{ + case tok!"__DATE__": + case tok!"__EOF__": + case tok!"__FILE__": + case tok!"__FUNCTION__": + case tok!"__LINE__": + case tok!"__MODULE__": + case tok!"__PRETTY_FUNCTION__": + case tok!"__TIME__": + case tok!"__TIMESTAMP__": + case tok!"__VENDOR__": + case tok!"__VERSION__": + }; + const(Token)[] tokens; + int suppressMessages; + size_t index; + int _traceDepth; + string comment; +} +