/** * Takes a token stream from the lexer, and parses it into an abstract syntax tree. * * Specification: $(LINK2 https://dlang.org/spec/grammar.html, D Grammar) * * Copyright: Copyright (C) 1999-2022 by The D Language Foundation, All Rights Reserved * Authors: $(LINK2 https://www.digitalmars.com, Walter Bright) * License: $(LINK2 https://www.boost.org/LICENSE_1_0.txt, Boost License 1.0) * Source: $(LINK2 https://github.com/dlang/dmd/blob/master/src/dmd/parse.d, _parse.d) * Documentation: https://dlang.org/phobos/dmd_parse.html * Coverage: https://codecov.io/gh/dlang/dmd/src/master/src/dmd/parse.d */ module dmd.parse; import core.stdc.stdio; import core.stdc.string; import dmd.astenums; import dmd.globals; import dmd.id; import dmd.identifier; import dmd.lexer; import dmd.errors; import dmd.root.filename; import dmd.common.outbuffer; import dmd.root.rmem; import dmd.root.rootobject; import dmd.root.string; import dmd.tokens; /*********************************************************** */ class Parser(AST, Lexer = dmd.lexer.Lexer) : Lexer { AST.ModuleDeclaration* md; protected { AST.Module mod; LINK linkage; Loc linkLoc; CPPMANGLE cppmangle; Loc endloc; // set to location of last right curly int inBrackets; // inside [] of array index or slice Loc lookingForElse; // location of lonely if looking for an else } /********************* * Use this constructor for string mixins. * Input: * loc location in source file of mixin */ extern (D) this(const ref Loc loc, AST.Module _module, const(char)[] input, bool doDocComment) { super(_module ? _module.srcfile.toChars() : null, input.ptr, 0, input.length, doDocComment, false); //printf("Parser::Parser()\n"); scanloc = loc; if (!writeMixin(input, scanloc) && loc.filename) { /* Create a pseudo-filename for the mixin string, as it may not even exist * in the source file. */ char* filename = cast(char*)mem.xmalloc(strlen(loc.filename) + 7 + (loc.linnum).sizeof * 3 + 1); sprintf(filename, "%s-mixin-%d", loc.filename, cast(int)loc.linnum); scanloc.filename = filename; } mod = _module; linkage = LINK.d; //nextToken(); // start up the scanner } extern (D) this(AST.Module _module, const(char)[] input, bool doDocComment) { super(_module ? _module.srcfile.toChars() : null, input.ptr, 0, input.length, doDocComment, false); //printf("Parser::Parser()\n"); mod = _module; linkage = LINK.d; //nextToken(); // start up the scanner } /++ + Parse a module, i.e. the optional `module x.y.z` declaration and all declarations + found in the current file. + + Returns: the list of declarations or an empty list in case of malformed declarations, + the module declaration will be stored as `this.md` if found +/ AST.Dsymbols* parseModule() { if (!parseModuleDeclaration()) return errorReturn(); return parseModuleContent(); } /++ + Parse the optional module declaration + + Returns: false if a malformed module declaration was found +/ final bool parseModuleDeclaration() { const comment = token.blockComment; bool isdeprecated = false; AST.Expression msg = null; // Parse optional module attributes parseModuleAttributes(msg, isdeprecated); // ModuleDeclaration leads off if (token.value == TOK.module_) { const loc = token.loc; nextToken(); /* parse ModuleFullyQualifiedName * https://dlang.org/spec/module.html#ModuleFullyQualifiedName */ if (token.value != TOK.identifier) { error("identifier expected following `module`"); return false; } Identifier[] a; Identifier id = token.ident; while (nextToken() == TOK.dot) { a ~= id; nextToken(); if (token.value != TOK.identifier) { error("identifier expected following `package`"); return false; } id = token.ident; } md = new AST.ModuleDeclaration(loc, a, id, msg, isdeprecated); if (token.value != TOK.semicolon) error("`;` expected following module declaration instead of `%s`", token.toChars()); nextToken(); addComment(mod, comment); } return true; } /++ + Parse the content of a module, i.e. all declarations found until the end of file. + + Returns: the list of declarations or an empty list in case of malformed declarations +/ final AST.Dsymbols* parseModuleContent() { AST.Dsymbol lastDecl = mod; AST.Dsymbols* decldefs = parseDeclDefs(0, &lastDecl); if (token.value == TOK.rightCurly) { error(token.loc, "unmatched closing brace"); return errorReturn(); } if (token.value != TOK.endOfFile) { error(token.loc, "unrecognized declaration"); return errorReturn(); } return decldefs; } /++ + Skips to the end of the current declaration - denoted by either `;` or EOF + + Returns: An empty list of Dsymbols +/ private AST.Dsymbols* errorReturn() { while (token.value != TOK.semicolon && token.value != TOK.endOfFile) nextToken(); nextToken(); return new AST.Dsymbols(); } /********************************** * Parse the ModuleAttributes preceding a module declaration. * ModuleDeclaration: * ModuleAttributes(opt) module ModuleFullyQualifiedName ; * https://dlang.org/spec/module.html#ModuleAttributes * Params: * msg = set to the AssignExpression from DeprecatedAttribute https://dlang.org/spec/module.html#DeprecatedAttribute * isdeprecated = set to true if a DeprecatedAttribute is seen */ private void parseModuleAttributes(out AST.Expression msg, out bool isdeprecated) { Token* tk; if (!(skipAttributes(&token, &tk) && tk.value == TOK.module_)) return; // no module attributes AST.Expressions* udas = null; while (token.value != TOK.module_) { switch (token.value) { case TOK.deprecated_: { // deprecated (...) module ... if (isdeprecated) error("there is only one deprecation attribute allowed for module declaration"); isdeprecated = true; nextToken(); if (token.value == TOK.leftParenthesis) { check(TOK.leftParenthesis); msg = parseAssignExp(); check(TOK.rightParenthesis); } break; } case TOK.at: { AST.Expressions* exps = null; const stc = parseAttribute(exps); if (stc & atAttrGroup) { error("`@%s` attribute for module declaration is not supported", token.toChars()); } else { udas = AST.UserAttributeDeclaration.concat(udas, exps); } if (stc) nextToken(); break; } default: { error("`module` expected instead of `%s`", token.toChars()); nextToken(); break; } } } if (udas) { auto a = new AST.Dsymbols(); auto udad = new AST.UserAttributeDeclaration(udas, a); mod.userAttribDecl = udad; } } final: /** * Parses a `deprecated` declaration * * Params: * msg = Deprecated message, if any. * Used to support overriding a deprecated storage class with * a deprecated declaration with a message, but to error * if both declaration have a message. * * Returns: * Whether the deprecated declaration has a message */ private bool parseDeprecatedAttribute(ref AST.Expression msg) { if (peekNext() != TOK.leftParenthesis) return false; nextToken(); check(TOK.leftParenthesis); AST.Expression e = parseAssignExp(); check(TOK.rightParenthesis); if (msg) { error("conflicting storage class `deprecated(%s)` and `deprecated(%s)`", msg.toChars(), e.toChars()); } msg = e; return true; } AST.Dsymbols* parseDeclDefs(int once, AST.Dsymbol* pLastDecl = null, PrefixAttributes!AST* pAttrs = null) { AST.Dsymbol lastDecl = null; // used to link unittest to its previous declaration if (!pLastDecl) pLastDecl = &lastDecl; const linksave = linkage; // save global state //printf("Parser::parseDeclDefs()\n"); auto decldefs = new AST.Dsymbols(); do { // parse result AST.Dsymbol s = null; AST.Dsymbols* a = null; PrefixAttributes!AST attrs; if (!once || !pAttrs) { pAttrs = &attrs; pAttrs.comment = token.blockComment.ptr; } AST.Visibility.Kind prot; StorageClass stc; AST.Condition condition; linkage = linksave; Loc startloc; switch (token.value) { case TOK.enum_: { /* Determine if this is a manifest constant declaration, * or a conventional enum. */ const tv = peekNext(); if (tv == TOK.leftCurly || tv == TOK.colon) s = parseEnum(); else if (tv != TOK.identifier) goto Ldeclaration; else { const nextv = peekNext2(); if (nextv == TOK.leftCurly || nextv == TOK.colon || nextv == TOK.semicolon) s = parseEnum(); else goto Ldeclaration; } break; } case TOK.import_: a = parseImport(); // keep pLastDecl break; case TOK.template_: s = cast(AST.Dsymbol)parseTemplateDeclaration(); break; case TOK.mixin_: { const loc = token.loc; switch (peekNext()) { case TOK.leftParenthesis: { // MixinType if (isDeclaration(&token, NeedDeclaratorId.mustIfDstyle, TOK.reserved, null)) goto Ldeclaration; // mixin(string) nextToken(); auto exps = parseArguments(); check(TOK.semicolon); s = new AST.CompileDeclaration(loc, exps); break; } case TOK.template_: // mixin template nextToken(); s = cast(AST.Dsymbol)parseTemplateDeclaration(true); break; default: s = parseMixin(); break; } break; } case TOK.wchar_: case TOK.dchar_: case TOK.bool_: case TOK.char_: case TOK.int8: case TOK.uns8: case TOK.int16: case TOK.uns16: case TOK.int32: case TOK.uns32: case TOK.int64: case TOK.uns64: case TOK.int128: case TOK.uns128: case TOK.float32: case TOK.float64: case TOK.float80: case TOK.imaginary32: case TOK.imaginary64: case TOK.imaginary80: case TOK.complex32: case TOK.complex64: case TOK.complex80: case TOK.void_: case TOK.alias_: case TOK.identifier: case TOK.super_: case TOK.typeof_: case TOK.dot: case TOK.vector: case TOK.struct_: case TOK.union_: case TOK.class_: case TOK.interface_: case TOK.traits: Ldeclaration: a = parseDeclarations(false, pAttrs, pAttrs.comment); if (a && a.dim) *pLastDecl = (*a)[a.dim - 1]; break; case TOK.this_: if (peekNext() == TOK.dot) goto Ldeclaration; s = parseCtor(pAttrs); break; case TOK.tilde: s = parseDtor(pAttrs); break; case TOK.invariant_: const tv = peekNext(); if (tv == TOK.leftParenthesis || tv == TOK.leftCurly) { // invariant { statements... } // invariant() { statements... } // invariant (expression); s = parseInvariant(pAttrs); break; } error("invariant body expected, not `%s`", token.toChars()); goto Lerror; case TOK.unittest_: /** * Ignore unittests in non-root modules. * * This mainly means that unittests *inside templates* are only * ever instantiated if the module lexically declaring the * template is one of the root modules. * * E.g., compiling some project with `-unittest` does NOT * compile and later run any unittests in instantiations of * templates declared in other libraries. * * Declaring unittests *inside* templates is considered an anti- * pattern. In almost all cases, the unittests don't depend on * the template parameters, but instantiate the template with * fixed arguments (e.g., Nullable!T unittests instantiating * Nullable!int), so compiling and running identical tests for * each template instantiation is hardly desirable. * But adding a unittest right below some function being tested * is arguably good for locality, so unittests end up inside * templates. * To make sure a template's unittests are run, it should be * instantiated in the same module, e.g., some module-level * unittest. * * Another reason for ignoring unittests in templates from non- * root modules is for template codegen culling via * TemplateInstance.needsCodegen(). If the compiler decides not * to emit some Nullable!bool because there's an existing * instantiation in some non-root module, it has no idea whether * that module was compiled with -unittest too, and so whether * Nullable!int (instantiated in some unittest inside the * Nullable template) can be culled too. By ignoring unittests * in non-root modules, the compiler won't consider any * template instantiations in these unittests as candidates for * further codegen culling. */ if (mod.isRoot() && (global.params.useUnitTests || global.params.doDocComments || global.params.doHdrGeneration)) { s = parseUnitTest(pAttrs); if (*pLastDecl) (*pLastDecl).ddocUnittest = cast(AST.UnitTestDeclaration)s; } else { // Skip over unittest block by counting { } Loc loc = token.loc; int braces = 0; while (1) { nextToken(); switch (token.value) { case TOK.leftCurly: ++braces; continue; case TOK.rightCurly: if (--braces) continue; nextToken(); break; case TOK.endOfFile: /* { */ error(loc, "closing `}` of unittest not found before end of file"); goto Lerror; default: continue; } break; } // Workaround 14894. Add an empty unittest declaration to keep // the number of symbols in this scope independent of -unittest. s = new AST.UnitTestDeclaration(loc, token.loc, STC.undefined_, null); } break; case TOK.new_: s = parseNew(pAttrs); break; case TOK.colon: case TOK.leftCurly: error("declaration expected, not `%s`", token.toChars()); goto Lerror; case TOK.rightCurly: case TOK.endOfFile: if (once) error("declaration expected, not `%s`", token.toChars()); return decldefs; case TOK.static_: { const next = peekNext(); if (next == TOK.this_) s = parseStaticCtor(pAttrs); else if (next == TOK.tilde) s = parseStaticDtor(pAttrs); else if (next == TOK.assert_) s = parseStaticAssert(); else if (next == TOK.if_) { const Loc loc = token.loc; condition = parseStaticIfCondition(); AST.Dsymbols* athen; if (token.value == TOK.colon) athen = parseBlock(pLastDecl); else { const lookingForElseSave = lookingForElse; lookingForElse = token.loc; athen = parseBlock(pLastDecl); lookingForElse = lookingForElseSave; } AST.Dsymbols* aelse = null; if (token.value == TOK.else_) { const elseloc = token.loc; nextToken(); aelse = parseBlock(pLastDecl); checkDanglingElse(elseloc); } s = new AST.StaticIfDeclaration(loc, condition, athen, aelse); } else if (next == TOK.import_) { a = parseImport(); // keep pLastDecl } else if (next == TOK.foreach_ || next == TOK.foreach_reverse_) { s = parseForeach!(AST.StaticForeachDeclaration)(token.loc, pLastDecl); } else { stc = STC.static_; goto Lstc; } break; } case TOK.const_: if (peekNext() == TOK.leftParenthesis) goto Ldeclaration; stc = STC.const_; goto Lstc; case TOK.immutable_: if (peekNext() == TOK.leftParenthesis) goto Ldeclaration; stc = STC.immutable_; goto Lstc; case TOK.shared_: { const next = peekNext(); if (next == TOK.leftParenthesis) goto Ldeclaration; if (next == TOK.static_) { TOK next2 = peekNext2(); if (next2 == TOK.this_) { s = parseSharedStaticCtor(pAttrs); break; } if (next2 == TOK.tilde) { s = parseSharedStaticDtor(pAttrs); break; } } stc = STC.shared_; goto Lstc; } case TOK.inout_: if (peekNext() == TOK.leftParenthesis) goto Ldeclaration; stc = STC.wild; goto Lstc; case TOK.final_: stc = STC.final_; goto Lstc; case TOK.auto_: stc = STC.auto_; goto Lstc; case TOK.scope_: stc = STC.scope_; goto Lstc; case TOK.override_: stc = STC.override_; goto Lstc; case TOK.abstract_: stc = STC.abstract_; goto Lstc; case TOK.synchronized_: stc = STC.synchronized_; goto Lstc; case TOK.nothrow_: stc = STC.nothrow_; goto Lstc; case TOK.pure_: stc = STC.pure_; goto Lstc; case TOK.ref_: stc = STC.ref_; goto Lstc; case TOK.gshared: stc = STC.gshared; goto Lstc; case TOK.at: { AST.Expressions* exps = null; stc = parseAttribute(exps); if (stc) goto Lstc; // it's a predefined attribute // no redundant/conflicting check for UDAs pAttrs.udas = AST.UserAttributeDeclaration.concat(pAttrs.udas, exps); goto Lautodecl; } Lstc: pAttrs.storageClass = appendStorageClass(pAttrs.storageClass, stc); nextToken(); Lautodecl: /* Look for auto initializers: * storage_class identifier = initializer; * storage_class identifier(...) = initializer; */ if (token.value == TOK.identifier && hasOptionalParensThen(peek(&token), TOK.assign)) { a = parseAutoDeclarations(getStorageClass!AST(pAttrs), pAttrs.comment); if (a && a.dim) *pLastDecl = (*a)[a.dim - 1]; if (pAttrs.udas) { s = new AST.UserAttributeDeclaration(pAttrs.udas, a); pAttrs.udas = null; } break; } /* Look for return type inference for template functions. */ Token* tk; if (token.value == TOK.identifier && skipParens(peek(&token), &tk) && skipAttributes(tk, &tk) && (tk.value == TOK.leftParenthesis || tk.value == TOK.leftCurly || tk.value == TOK.in_ || tk.value == TOK.out_ || tk.value == TOK.do_ || tk.value == TOK.goesTo || tk.value == TOK.identifier && tk.ident == Id._body)) { // @@@DEPRECATED_2.117@@@ // https://github.com/dlang/DIPs/blob/1f5959abe482b1f9094f6484a7d0a3ade77fc2fc/DIPs/accepted/DIP1003.md // Deprecated in 2.097 - Can be removed from 2.117 // The deprecation period is longer than usual as `body` // was quite widely used. if (tk.value == TOK.identifier && tk.ident == Id._body) deprecation("Usage of the `body` keyword is deprecated. Use `do` instead."); a = parseDeclarations(true, pAttrs, pAttrs.comment); if (a && a.dim) *pLastDecl = (*a)[a.dim - 1]; if (pAttrs.udas) { s = new AST.UserAttributeDeclaration(pAttrs.udas, a); pAttrs.udas = null; } break; } a = parseBlock(pLastDecl, pAttrs); auto stc2 = getStorageClass!AST(pAttrs); if (stc2 != STC.undefined_) { s = new AST.StorageClassDeclaration(stc2, a); } if (pAttrs.udas) { if (s) { a = new AST.Dsymbols(); a.push(s); } s = new AST.UserAttributeDeclaration(pAttrs.udas, a); pAttrs.udas = null; } break; case TOK.deprecated_: { stc |= STC.deprecated_; if (!parseDeprecatedAttribute(pAttrs.depmsg)) goto Lstc; a = parseBlock(pLastDecl, pAttrs); s = new AST.DeprecatedDeclaration(pAttrs.depmsg, a); pAttrs.depmsg = null; break; } case TOK.leftBracket: { if (peekNext() == TOK.rightBracket) error("empty attribute list is not allowed"); error("use `@(attributes)` instead of `[attributes]`"); AST.Expressions* exps = parseArguments(); // no redundant/conflicting check for UDAs pAttrs.udas = AST.UserAttributeDeclaration.concat(pAttrs.udas, exps); a = parseBlock(pLastDecl, pAttrs); if (pAttrs.udas) { s = new AST.UserAttributeDeclaration(pAttrs.udas, a); pAttrs.udas = null; } break; } case TOK.extern_: { if (peekNext() != TOK.leftParenthesis) { stc = STC.extern_; goto Lstc; } const linkLoc = token.loc; auto res = parseLinkage(); if (pAttrs.link != LINK.default_) { if (pAttrs.link != res.link) { error("conflicting linkage `extern (%s)` and `extern (%s)`", AST.linkageToChars(pAttrs.link), AST.linkageToChars(res.link)); } else if (res.idents || res.identExps || res.cppmangle != CPPMANGLE.def) { // Allow: // extern(C++, foo) extern(C++, bar) void foo(); // to be equivalent with: // extern(C++, foo.bar) void foo(); // Allow also: // extern(C++, "ns") extern(C++, class) struct test {} // extern(C++, class) extern(C++, "ns") struct test {} } else error("redundant linkage `extern (%s)`", AST.linkageToChars(pAttrs.link)); } pAttrs.link = res.link; this.linkage = res.link; this.linkLoc = linkLoc; a = parseBlock(pLastDecl, pAttrs); if (res.idents) { assert(res.link == LINK.cpp); assert(res.idents.dim); for (size_t i = res.idents.dim; i;) { Identifier id = (*res.idents)[--i]; if (s) { a = new AST.Dsymbols(); a.push(s); } s = new AST.Nspace(linkLoc, id, null, a); } pAttrs.link = LINK.default_; } else if (res.identExps) { assert(res.link == LINK.cpp); assert(res.identExps.dim); for (size_t i = res.identExps.dim; i;) { AST.Expression exp = (*res.identExps)[--i]; if (s) { a = new AST.Dsymbols(); a.push(s); } s = new AST.CPPNamespaceDeclaration(linkLoc, exp, a); } pAttrs.link = LINK.default_; } else if (res.cppmangle != CPPMANGLE.def) { assert(res.link == LINK.cpp); s = new AST.CPPMangleDeclaration(linkLoc, res.cppmangle, a); } else if (pAttrs.link != LINK.default_) { s = new AST.LinkDeclaration(linkLoc, pAttrs.link, a); pAttrs.link = LINK.default_; } break; } case TOK.private_: prot = AST.Visibility.Kind.private_; goto Lprot; case TOK.package_: prot = AST.Visibility.Kind.package_; goto Lprot; case TOK.protected_: prot = AST.Visibility.Kind.protected_; goto Lprot; case TOK.public_: prot = AST.Visibility.Kind.public_; goto Lprot; case TOK.export_: prot = AST.Visibility.Kind.export_; goto Lprot; Lprot: { if (pAttrs.visibility.kind != AST.Visibility.Kind.undefined) { if (pAttrs.visibility.kind != prot) error("conflicting visibility attribute `%s` and `%s`", AST.visibilityToChars(pAttrs.visibility.kind), AST.visibilityToChars(prot)); else error("redundant visibility attribute `%s`", AST.visibilityToChars(prot)); } pAttrs.visibility.kind = prot; nextToken(); // optional qualified package identifier to bind // visibility to Identifier[] pkg_prot_idents; if (pAttrs.visibility.kind == AST.Visibility.Kind.package_ && token.value == TOK.leftParenthesis) { pkg_prot_idents = parseQualifiedIdentifier("protection package"); if (pkg_prot_idents) check(TOK.rightParenthesis); else { while (token.value != TOK.semicolon && token.value != TOK.endOfFile) nextToken(); nextToken(); break; } } const attrloc = token.loc; a = parseBlock(pLastDecl, pAttrs); if (pAttrs.visibility.kind != AST.Visibility.Kind.undefined) { if (pAttrs.visibility.kind == AST.Visibility.Kind.package_ && pkg_prot_idents) s = new AST.VisibilityDeclaration(attrloc, pkg_prot_idents, a); else s = new AST.VisibilityDeclaration(attrloc, pAttrs.visibility, a); pAttrs.visibility = AST.Visibility(AST.Visibility.Kind.undefined); } break; } case TOK.align_: { const attrLoc = token.loc; nextToken(); AST.Expression e = null; // default if (token.value == TOK.leftParenthesis) { nextToken(); e = parseAssignExp(); check(TOK.rightParenthesis); } if (pAttrs.setAlignment) { if (e) error("redundant alignment attribute `align(%s)`", e.toChars()); else error("redundant alignment attribute `align`"); } pAttrs.setAlignment = true; pAttrs.ealign = e; a = parseBlock(pLastDecl, pAttrs); if (pAttrs.setAlignment) { s = new AST.AlignDeclaration(attrLoc, pAttrs.ealign, a); pAttrs.setAlignment = false; pAttrs.ealign = null; } break; } case TOK.pragma_: { AST.Expressions* args = null; const loc = token.loc; nextToken(); check(TOK.leftParenthesis); if (token.value != TOK.identifier) { error("`pragma(identifier)` expected"); goto Lerror; } Identifier ident = token.ident; nextToken(); if (token.value == TOK.comma && peekNext() != TOK.rightParenthesis) args = parseArguments(); // pragma(identifier, args...) else check(TOK.rightParenthesis); // pragma(identifier) AST.Dsymbols* a2 = null; if (token.value == TOK.semicolon) { /* https://issues.dlang.org/show_bug.cgi?id=2354 * Accept single semicolon as an empty * DeclarationBlock following attribute. * * Attribute DeclarationBlock * Pragma DeclDef * ; */ nextToken(); } else a2 = parseBlock(pLastDecl); s = new AST.PragmaDeclaration(loc, ident, args, a2); break; } case TOK.debug_: startloc = token.loc; nextToken(); if (token.value == TOK.assign) { s = parseDebugSpecification(); break; } condition = parseDebugCondition(); goto Lcondition; case TOK.version_: startloc = token.loc; nextToken(); if (token.value == TOK.assign) { s = parseVersionSpecification(); break; } condition = parseVersionCondition(); goto Lcondition; Lcondition: { AST.Dsymbols* athen; if (token.value == TOK.colon) athen = parseBlock(pLastDecl); else { const lookingForElseSave = lookingForElse; lookingForElse = token.loc; athen = parseBlock(pLastDecl); lookingForElse = lookingForElseSave; } AST.Dsymbols* aelse = null; if (token.value == TOK.else_) { const elseloc = token.loc; nextToken(); aelse = parseBlock(pLastDecl); checkDanglingElse(elseloc); } s = new AST.ConditionalDeclaration(startloc, condition, athen, aelse); break; } case TOK.semicolon: // empty declaration //error("empty declaration"); nextToken(); continue; default: error("declaration expected, not `%s`", token.toChars()); Lerror: while (token.value != TOK.semicolon && token.value != TOK.endOfFile) nextToken(); nextToken(); s = null; continue; } if (s) { if (!s.isAttribDeclaration()) *pLastDecl = s; decldefs.push(s); addComment(s, pAttrs.comment); } else if (a && a.dim) { decldefs.append(a); } } while (!once); linkage = linksave; return decldefs; } /***************************************** * Parse auto declarations of the form: * storageClass ident = init, ident = init, ... ; * and return the array of them. * Starts with token on the first ident. * Ends with scanner past closing ';' */ private AST.Dsymbols* parseAutoDeclarations(StorageClass storageClass, const(char)* comment) { //printf("parseAutoDeclarations\n"); auto a = new AST.Dsymbols(); while (1) { const loc = token.loc; Identifier ident = token.ident; nextToken(); // skip over ident AST.TemplateParameters* tpl = null; if (token.value == TOK.leftParenthesis) tpl = parseTemplateParameterList(); check(TOK.assign); // skip over '=' AST.Initializer _init = parseInitializer(); auto v = new AST.VarDeclaration(loc, null, ident, _init, storageClass); AST.Dsymbol s = v; if (tpl) { auto a2 = new AST.Dsymbols(); a2.push(v); auto tempdecl = new AST.TemplateDeclaration(loc, ident, tpl, null, a2, 0); s = tempdecl; } a.push(s); switch (token.value) { case TOK.semicolon: nextToken(); addComment(s, comment); break; case TOK.comma: nextToken(); if (!(token.value == TOK.identifier && hasOptionalParensThen(peek(&token), TOK.assign))) { error("identifier expected following comma"); break; } addComment(s, comment); continue; default: error("semicolon expected following auto declaration, not `%s`", token.toChars()); break; } break; } return a; } /******************************************** * Parse declarations after an align, visibility, or extern decl. */ private AST.Dsymbols* parseBlock(AST.Dsymbol* pLastDecl, PrefixAttributes!AST* pAttrs = null) { AST.Dsymbols* a = null; //printf("parseBlock()\n"); switch (token.value) { case TOK.semicolon: error("declaration expected following attribute, not `;`"); nextToken(); break; case TOK.endOfFile: error("declaration expected following attribute, not end of file"); break; case TOK.leftCurly: { const lookingForElseSave = lookingForElse; lookingForElse = Loc(); nextToken(); a = parseDeclDefs(0, pLastDecl); if (token.value != TOK.rightCurly) { /* { */ error("matching `}` expected, not `%s`", token.toChars()); } else nextToken(); lookingForElse = lookingForElseSave; break; } case TOK.colon: nextToken(); a = parseDeclDefs(0, pLastDecl); // grab declarations up to closing curly bracket break; default: a = parseDeclDefs(1, pLastDecl, pAttrs); break; } return a; } /** * Provide an error message if `added` contains storage classes which are * redundant with those in `orig`; otherwise, return the combination. * * Params: * orig = The already applied storage class. * added = The new storage class to add to `orig`. * * Returns: * The combination of both storage classes (`orig | added`). */ private StorageClass appendStorageClass(StorageClass orig, StorageClass added) { void checkConflictSTCGroup(bool at = false)(StorageClass group) { if (added & group && orig & group & ((orig & group) - 1)) error( at ? "conflicting attribute `@%s`" : "conflicting attribute `%s`", token.toChars()); } if (orig & added) { OutBuffer buf; AST.stcToBuffer(&buf, added); error("redundant attribute `%s`", buf.peekChars()); return orig | added; } const Redundant = (STC.const_ | STC.scope_ | (global.params.previewIn ? STC.ref_ : 0)); orig |= added; if ((orig & STC.in_) && (added & Redundant)) { if (added & STC.const_) error("attribute `const` is redundant with previously-applied `in`"); else if (global.params.previewIn) { error("attribute `%s` is redundant with previously-applied `in`", (orig & STC.scope_) ? "scope".ptr : "ref".ptr); } else error("attribute `scope` cannot be applied with `in`, use `-preview=in` instead"); return orig; } if ((added & STC.in_) && (orig & Redundant)) { if (orig & STC.const_) error("attribute `in` cannot be added after `const`: remove `const`"); else if (global.params.previewIn) { // Windows `printf` does not support `%1$s` const(char*) stc_str = (orig & STC.scope_) ? "scope".ptr : "ref".ptr; error("attribute `in` cannot be added after `%s`: remove `%s`", stc_str, stc_str); } else error("attribute `in` cannot be added after `scope`: remove `scope` and use `-preview=in`"); return orig; } checkConflictSTCGroup(STC.const_ | STC.immutable_ | STC.manifest); checkConflictSTCGroup(STC.gshared | STC.shared_); checkConflictSTCGroup!true(STC.safeGroup); return orig; } /*********************************************** * Parse attribute(s), lexer is on '@'. * * Attributes can be builtin (e.g. `@safe`, `@nogc`, etc...), * or be user-defined (UDAs). In the former case, we return the storage * class via the return value, while in thelater case we return `0` * and set `pudas`. * * Params: * pudas = An array of UDAs to append to * * Returns: * If the attribute is builtin, the return value will be non-zero. * Otherwise, 0 is returned, and `pudas` will be appended to. */ private StorageClass parseAttribute(ref AST.Expressions* udas) { nextToken(); if (token.value == TOK.identifier) { // If we find a builtin attribute, we're done, return immediately. if (StorageClass stc = isBuiltinAtAttribute(token.ident)) return stc; // Allow identifier, template instantiation, or function call // for `@Argument` (single UDA) form. AST.Expression exp = parsePrimaryExp(); if (token.value == TOK.leftParenthesis) { const loc = token.loc; exp = new AST.CallExp(loc, exp, parseArguments()); } if (udas is null) udas = new AST.Expressions(); udas.push(exp); return 0; } if (token.value == TOK.leftParenthesis) { // Multi-UDAs ( `@( ArgumentList )`) form, concatenate with existing if (peekNext() == TOK.rightParenthesis) error("empty attribute list is not allowed"); udas = AST.UserAttributeDeclaration.concat(udas, parseArguments()); return 0; } if (token.isKeyword()) error("`%s` is a keyword, not an `@` attribute", token.toChars()); else error("`@identifier` or `@(ArgumentList)` expected, not `@%s`", token.toChars()); return 0; } /*********************************************** * Parse const/immutable/shared/inout/nothrow/pure postfix */ private StorageClass parsePostfix(StorageClass storageClass, AST.Expressions** pudas) { while (1) { StorageClass stc; switch (token.value) { case TOK.const_: stc = STC.const_; break; case TOK.immutable_: stc = STC.immutable_; break; case TOK.shared_: stc = STC.shared_; break; case TOK.inout_: stc = STC.wild; break; case TOK.nothrow_: stc = STC.nothrow_; break; case TOK.pure_: stc = STC.pure_; break; case TOK.return_: stc = STC.return_; if (peekNext() == TOK.scope_) stc |= STC.returnScope; // recognize `return scope` break; case TOK.scope_: stc = STC.scope_; break; case TOK.at: { AST.Expressions* udas = null; stc = parseAttribute(udas); if (udas) { if (pudas) *pudas = AST.UserAttributeDeclaration.concat(*pudas, udas); else { // Disallow: // void function() @uda fp; // () @uda { return 1; } error("user-defined attributes cannot appear as postfixes"); } continue; } break; } default: return storageClass; } storageClass = appendStorageClass(storageClass, stc); nextToken(); } } private StorageClass parseTypeCtor() { StorageClass storageClass = STC.undefined_; while (1) { if (peekNext() == TOK.leftParenthesis) return storageClass; StorageClass stc; switch (token.value) { case TOK.const_: stc = STC.const_; break; case TOK.immutable_: stc = STC.immutable_; break; case TOK.shared_: stc = STC.shared_; break; case TOK.inout_: stc = STC.wild; break; default: return storageClass; } storageClass = appendStorageClass(storageClass, stc); nextToken(); } } /************************************** * Parse constraint. * Constraint is of the form: * if ( ConstraintExpression ) */ private AST.Expression parseConstraint() { AST.Expression e = null; if (token.value == TOK.if_) { nextToken(); // skip over 'if' check(TOK.leftParenthesis); e = parseExpression(); check(TOK.rightParenthesis); } return e; } /************************************** * Parse a TemplateDeclaration. */ private AST.TemplateDeclaration parseTemplateDeclaration(bool ismixin = false) { AST.TemplateDeclaration tempdecl; Identifier id; AST.TemplateParameters* tpl; AST.Dsymbols* decldefs; AST.Expression constraint = null; const loc = token.loc; nextToken(); if (token.value != TOK.identifier) { error("identifier expected following `template`"); goto Lerr; } id = token.ident; nextToken(); tpl = parseTemplateParameterList(); if (!tpl) goto Lerr; constraint = parseConstraint(); if (token.value != TOK.leftCurly) { error("members of template declaration expected"); goto Lerr; } decldefs = parseBlock(null); tempdecl = new AST.TemplateDeclaration(loc, id, tpl, constraint, decldefs, ismixin); return tempdecl; Lerr: return null; } /****************************************** * Parse template parameter list. * Input: * flag 0: parsing "( list )" * 1: parsing non-empty "list $(RPAREN)" */ private AST.TemplateParameters* parseTemplateParameterList(int flag = 0) { auto tpl = new AST.TemplateParameters(); if (!flag && token.value != TOK.leftParenthesis) { error("parenthesized template parameter list expected following template identifier"); goto Lerr; } nextToken(); // Get array of TemplateParameters if (flag || token.value != TOK.rightParenthesis) { while (token.value != TOK.rightParenthesis) { AST.TemplateParameter tp; Loc loc; Identifier tp_ident = null; AST.Type tp_spectype = null; AST.Type tp_valtype = null; AST.Type tp_defaulttype = null; AST.Expression tp_specvalue = null; AST.Expression tp_defaultvalue = null; // Get TemplateParameter // First, look ahead to see if it is a TypeParameter or a ValueParameter const tv = peekNext(); if (token.value == TOK.alias_) { // AliasParameter nextToken(); loc = token.loc; // todo AST.Type spectype = null; if (isDeclaration(&token, NeedDeclaratorId.must, TOK.reserved, null)) { spectype = parseType(&tp_ident); } else { if (token.value != TOK.identifier) { error("identifier expected for template `alias` parameter"); goto Lerr; } tp_ident = token.ident; nextToken(); } RootObject spec = null; if (token.value == TOK.colon) // : Type { nextToken(); if (isDeclaration(&token, NeedDeclaratorId.no, TOK.reserved, null)) spec = parseType(); else spec = parseCondExp(); } RootObject def = null; if (token.value == TOK.assign) // = Type { nextToken(); if (isDeclaration(&token, NeedDeclaratorId.no, TOK.reserved, null)) def = parseType(); else def = parseCondExp(); } tp = new AST.TemplateAliasParameter(loc, tp_ident, spectype, spec, def); } else if (tv == TOK.colon || tv == TOK.assign || tv == TOK.comma || tv == TOK.rightParenthesis) { // TypeParameter if (token.value != TOK.identifier) { error("identifier expected for template type parameter"); goto Lerr; } loc = token.loc; tp_ident = token.ident; nextToken(); if (token.value == TOK.colon) // : Type { nextToken(); tp_spectype = parseType(); } if (token.value == TOK.assign) // = Type { nextToken(); tp_defaulttype = parseType(); } tp = new AST.TemplateTypeParameter(loc, tp_ident, tp_spectype, tp_defaulttype); } else if (token.value == TOK.identifier && tv == TOK.dotDotDot) { // ident... loc = token.loc; tp_ident = token.ident; nextToken(); nextToken(); tp = new AST.TemplateTupleParameter(loc, tp_ident); } else if (token.value == TOK.this_) { // ThisParameter nextToken(); if (token.value != TOK.identifier) { error("identifier expected for template `this` parameter"); goto Lerr; } loc = token.loc; tp_ident = token.ident; nextToken(); if (token.value == TOK.colon) // : Type { nextToken(); tp_spectype = parseType(); } if (token.value == TOK.assign) // = Type { nextToken(); tp_defaulttype = parseType(); } tp = new AST.TemplateThisParameter(loc, tp_ident, tp_spectype, tp_defaulttype); } else { // ValueParameter loc = token.loc; // todo tp_valtype = parseType(&tp_ident); if (!tp_ident) { error("identifier expected for template value parameter"); tp_ident = Identifier.idPool("error"); } if (token.value == TOK.colon) // : CondExpression { nextToken(); tp_specvalue = parseCondExp(); } if (token.value == TOK.assign) // = CondExpression { nextToken(); tp_defaultvalue = parseDefaultInitExp(); } tp = new AST.TemplateValueParameter(loc, tp_ident, tp_valtype, tp_specvalue, tp_defaultvalue); } tpl.push(tp); if (token.value != TOK.comma) break; nextToken(); } } check(TOK.rightParenthesis); Lerr: return tpl; } /****************************************** * Parse template mixin. * mixin Foo; * mixin Foo!(args); * mixin a.b.c!(args).Foo!(args); * mixin Foo!(args) identifier; * mixin typeof(expr).identifier!(args); */ private AST.Dsymbol parseMixin() { AST.TemplateMixin tm; Identifier id; AST.Objects* tiargs; //printf("parseMixin()\n"); const locMixin = token.loc; nextToken(); // skip 'mixin' auto loc = token.loc; AST.TypeQualified tqual = null; if (token.value == TOK.dot) { id = Id.empty; } else { if (token.value == TOK.typeof_) { tqual = parseTypeof(); check(TOK.dot); } if (token.value != TOK.identifier) { error("identifier expected, not `%s`", token.toChars()); id = Id.empty; } else id = token.ident; nextToken(); } while (1) { tiargs = null; if (token.value == TOK.not) { tiargs = parseTemplateArguments(); } if (tiargs && token.value == TOK.dot) { auto tempinst = new AST.TemplateInstance(loc, id, tiargs); if (!tqual) tqual = new AST.TypeInstance(loc, tempinst); else tqual.addInst(tempinst); tiargs = null; } else { if (!tqual) tqual = new AST.TypeIdentifier(loc, id); else tqual.addIdent(id); } if (token.value != TOK.dot) break; nextToken(); if (token.value != TOK.identifier) { error("identifier expected following `.` instead of `%s`", token.toChars()); break; } loc = token.loc; id = token.ident; nextToken(); } id = null; if (token.value == TOK.identifier) { id = token.ident; nextToken(); } tm = new AST.TemplateMixin(locMixin, id, tqual, tiargs); if (token.value != TOK.semicolon) error("`;` expected after `mixin`"); nextToken(); return tm; } /****************************************** * Parse template arguments. * Input: * current token is opening '!' * Output: * current token is one after closing '$(RPAREN)' */ private AST.Objects* parseTemplateArguments() { AST.Objects* tiargs; nextToken(); if (token.value == TOK.leftParenthesis) { // ident!(template_arguments) tiargs = parseTemplateArgumentList(); } else { // ident!template_argument tiargs = parseTemplateSingleArgument(); } if (token.value == TOK.not) { TOK tok = peekNext(); if (tok != TOK.is_ && tok != TOK.in_) { error("multiple ! arguments are not allowed"); Lagain: nextToken(); if (token.value == TOK.leftParenthesis) parseTemplateArgumentList(); else parseTemplateSingleArgument(); if (token.value == TOK.not && (tok = peekNext()) != TOK.is_ && tok != TOK.in_) goto Lagain; } } return tiargs; } /****************************************** * Parse template argument list. * Input: * current token is opening '$(LPAREN)', * or ',' for __traits * Output: * current token is one after closing '$(RPAREN)' */ private AST.Objects* parseTemplateArgumentList() { //printf("Parser::parseTemplateArgumentList()\n"); auto tiargs = new AST.Objects(); TOK endtok = TOK.rightParenthesis; assert(token.value == TOK.leftParenthesis || token.value == TOK.comma); nextToken(); // Get TemplateArgumentList while (token.value != endtok) { tiargs.push(parseTypeOrAssignExp()); if (token.value != TOK.comma) break; nextToken(); } check(endtok, "template argument list"); return tiargs; } /*************************************** * Parse a Type or an Expression * Returns: * RootObject representing the AST */ RootObject parseTypeOrAssignExp(TOK endtoken = TOK.reserved) { return isDeclaration(&token, NeedDeclaratorId.no, endtoken, null) ? parseType() // argument is a type : parseAssignExp(); // argument is an expression } /***************************** * Parse single template argument, to support the syntax: * foo!arg * Input: * current token is the arg */ private AST.Objects* parseTemplateSingleArgument() { //printf("parseTemplateSingleArgument()\n"); auto tiargs = new AST.Objects(); AST.Type ta; switch (token.value) { case TOK.identifier: ta = new AST.TypeIdentifier(token.loc, token.ident); goto LabelX; case TOK.vector: ta = parseVector(); goto LabelX; case TOK.void_: ta = AST.Type.tvoid; goto LabelX; case TOK.int8: ta = AST.Type.tint8; goto LabelX; case TOK.uns8: ta = AST.Type.tuns8; goto LabelX; case TOK.int16: ta = AST.Type.tint16; goto LabelX; case TOK.uns16: ta = AST.Type.tuns16; goto LabelX; case TOK.int32: ta = AST.Type.tint32; goto LabelX; case TOK.uns32: ta = AST.Type.tuns32; goto LabelX; case TOK.int64: ta = AST.Type.tint64; goto LabelX; case TOK.uns64: ta = AST.Type.tuns64; goto LabelX; case TOK.int128: ta = AST.Type.tint128; goto LabelX; case TOK.uns128: ta = AST.Type.tuns128; goto LabelX; case TOK.float32: ta = AST.Type.tfloat32; goto LabelX; case TOK.float64: ta = AST.Type.tfloat64; goto LabelX; case TOK.float80: ta = AST.Type.tfloat80; goto LabelX; case TOK.imaginary32: ta = AST.Type.timaginary32; goto LabelX; case TOK.imaginary64: ta = AST.Type.timaginary64; goto LabelX; case TOK.imaginary80: ta = AST.Type.timaginary80; goto LabelX; case TOK.complex32: ta = AST.Type.tcomplex32; goto LabelX; case TOK.complex64: ta = AST.Type.tcomplex64; goto LabelX; case TOK.complex80: ta = AST.Type.tcomplex80; goto LabelX; case TOK.bool_: ta = AST.Type.tbool; goto LabelX; case TOK.char_: ta = AST.Type.tchar; goto LabelX; case TOK.wchar_: ta = AST.Type.twchar; goto LabelX; case TOK.dchar_: ta = AST.Type.tdchar; goto LabelX; LabelX: tiargs.push(ta); nextToken(); break; case TOK.int32Literal: case TOK.uns32Literal: case TOK.int64Literal: case TOK.uns64Literal: case TOK.int128Literal: case TOK.uns128Literal: case TOK.float32Literal: case TOK.float64Literal: case TOK.float80Literal: case TOK.imaginary32Literal: case TOK.imaginary64Literal: case TOK.imaginary80Literal: case TOK.null_: case TOK.true_: case TOK.false_: case TOK.charLiteral: case TOK.wcharLiteral: case TOK.dcharLiteral: case TOK.string_: case TOK.file: case TOK.fileFullPath: case TOK.line: case TOK.moduleString: case TOK.functionString: case TOK.prettyFunction: case TOK.this_: { // Template argument is an expression AST.Expression ea = parsePrimaryExp(); tiargs.push(ea); break; } default: error("template argument expected following `!`"); break; } return tiargs; } /********************************** * Parse a static assertion. * Current token is 'static'. */ private AST.StaticAssert parseStaticAssert() { const loc = token.loc; AST.Expression exp; AST.Expression msg = null; //printf("parseStaticAssert()\n"); nextToken(); nextToken(); check(TOK.leftParenthesis); exp = parseAssignExp(); if (token.value == TOK.comma) { nextToken(); if (token.value != TOK.rightParenthesis) { msg = parseAssignExp(); if (token.value == TOK.comma) nextToken(); } } check(TOK.rightParenthesis); check(TOK.semicolon); return new AST.StaticAssert(loc, exp, msg); } /*********************************** * Parse typeof(expression). * Current token is on the 'typeof'. */ private AST.TypeQualified parseTypeof() { AST.TypeQualified t; const loc = token.loc; nextToken(); check(TOK.leftParenthesis); if (token.value == TOK.return_) // typeof(return) { nextToken(); t = new AST.TypeReturn(loc); } else { AST.Expression exp = parseExpression(); // typeof(expression) t = new AST.TypeTypeof(loc, exp); } check(TOK.rightParenthesis); return t; } /*********************************** * Parse __vector(type). * Current token is on the '__vector'. */ private AST.Type parseVector() { nextToken(); check(TOK.leftParenthesis); AST.Type tb = parseType(); check(TOK.rightParenthesis); return new AST.TypeVector(tb); } /*********************************** * Parse: * extern (linkage) * extern (C++, namespaces) * extern (C++, "namespace", "namespaces", ...) * extern (C++, (StringExp)) * The parser is on the 'extern' token. */ private ParsedLinkage!(AST) parseLinkage() { ParsedLinkage!(AST) result; nextToken(); assert(token.value == TOK.leftParenthesis); nextToken(); ParsedLinkage!(AST) returnLinkage(LINK link) { check(TOK.rightParenthesis); result.link = link; return result; } ParsedLinkage!(AST) invalidLinkage() { error("valid linkage identifiers are `D`, `C`, `C++`, `Objective-C`, `Windows`, `System`"); return returnLinkage(LINK.d); } if (token.value != TOK.identifier) return returnLinkage(LINK.d); Identifier id = token.ident; nextToken(); if (id == Id.Windows) return returnLinkage(LINK.windows); else if (id == Id.D) return returnLinkage(LINK.d); else if (id == Id.System) return returnLinkage(LINK.system); else if (id == Id.Objective) // Looking for tokens "Objective-C" { if (token.value != TOK.min) return invalidLinkage(); nextToken(); if (token.ident != Id.C) return invalidLinkage(); nextToken(); return returnLinkage(LINK.objc); } else if (id != Id.C) return invalidLinkage(); if (token.value != TOK.plusPlus) return returnLinkage(LINK.c); nextToken(); if (token.value != TOK.comma) // , namespaces or class or struct return returnLinkage(LINK.cpp); nextToken(); if (token.value == TOK.rightParenthesis) return returnLinkage(LINK.cpp); // extern(C++,) if (token.value == TOK.class_ || token.value == TOK.struct_) { result.cppmangle = token.value == TOK.class_ ? CPPMANGLE.asClass : CPPMANGLE.asStruct; nextToken(); } else if (token.value == TOK.identifier) // named scope namespace { result.idents = new AST.Identifiers(); while (1) { Identifier idn = token.ident; result.idents.push(idn); nextToken(); if (token.value == TOK.dot) { nextToken(); if (token.value == TOK.identifier) continue; error("identifier expected for C++ namespace"); result.idents = null; // error occurred, invalidate list of elements. } break; } } else // non-scoped StringExp namespace { result.identExps = new AST.Expressions(); while (1) { result.identExps.push(parseCondExp()); if (token.value != TOK.comma) break; nextToken(); // Allow trailing commas as done for argument lists, arrays, ... if (token.value == TOK.rightParenthesis) break; } } return returnLinkage(LINK.cpp); } /*********************************** * Parse ident1.ident2.ident3 * * Params: * entity = what qualified identifier is expected to resolve into. * Used only for better error message * * Returns: * array of identifiers with actual qualified one stored last */ private Identifier[] parseQualifiedIdentifier(const(char)* entity) { Identifier[] qualified; do { nextToken(); if (token.value != TOK.identifier) { error("`%s` expected as dot-separated identifiers, got `%s`", entity, token.toChars()); return qualified; } Identifier id = token.ident; qualified ~= id; nextToken(); } while (token.value == TOK.dot); return qualified; } private AST.DebugSymbol parseDebugSpecification() { AST.DebugSymbol s; nextToken(); if (token.value == TOK.identifier) s = new AST.DebugSymbol(token.loc, token.ident); else if (token.value == TOK.int32Literal || token.value == TOK.int64Literal) s = new AST.DebugSymbol(token.loc, cast(uint)token.unsvalue); else { error("identifier or integer expected, not `%s`", token.toChars()); s = null; } nextToken(); if (token.value != TOK.semicolon) error("semicolon expected"); nextToken(); return s; } /************************************** * Parse a debug conditional */ private AST.Condition parseDebugCondition() { uint level = 1; Identifier id = null; Loc loc = token.loc; if (token.value == TOK.leftParenthesis) { nextToken(); if (token.value == TOK.identifier) id = token.ident; else if (token.value == TOK.int32Literal || token.value == TOK.int64Literal) level = cast(uint)token.unsvalue; else error("identifier or integer expected inside `debug(...)`, not `%s`", token.toChars()); loc = token.loc; nextToken(); check(TOK.rightParenthesis); } return new AST.DebugCondition(loc, mod, level, id); } /************************************** * Parse a version specification */ private AST.VersionSymbol parseVersionSpecification() { AST.VersionSymbol s; nextToken(); if (token.value == TOK.identifier) s = new AST.VersionSymbol(token.loc, token.ident); else if (token.value == TOK.int32Literal || token.value == TOK.int64Literal) s = new AST.VersionSymbol(token.loc, cast(uint)token.unsvalue); else { error("identifier or integer expected, not `%s`", token.toChars()); s = null; } nextToken(); if (token.value != TOK.semicolon) error("semicolon expected"); nextToken(); return s; } /************************************** * Parse a version conditional */ private AST.Condition parseVersionCondition() { uint level = 1; Identifier id = null; Loc loc; if (token.value == TOK.leftParenthesis) { nextToken(); /* Allow: * version (unittest) * version (assert) * even though they are keywords */ loc = token.loc; if (token.value == TOK.identifier) id = token.ident; else if (token.value == TOK.int32Literal || token.value == TOK.int64Literal) level = cast(uint)token.unsvalue; else if (token.value == TOK.unittest_) id = Identifier.idPool(Token.toString(TOK.unittest_)); else if (token.value == TOK.assert_) id = Identifier.idPool(Token.toString(TOK.assert_)); else error("identifier or integer expected inside `version(...)`, not `%s`", token.toChars()); nextToken(); check(TOK.rightParenthesis); } else error("(condition) expected following `version`"); return new AST.VersionCondition(loc, mod, level, id); } /*********************************************** * static if (expression) * body * else * body * Current token is 'static'. */ private AST.Condition parseStaticIfCondition() { AST.Expression exp; AST.Condition condition; const loc = token.loc; nextToken(); nextToken(); if (token.value == TOK.leftParenthesis) { nextToken(); exp = parseAssignExp(); check(TOK.rightParenthesis); } else { error("(expression) expected following `static if`"); exp = null; } condition = new AST.StaticIfCondition(loc, exp); return condition; } /***************************************** * Parse a constructor definition: * this(parameters) { body } * or postblit: * this(this) { body } * or constructor template: * this(templateparameters)(parameters) { body } * Current token is 'this'. */ private AST.Dsymbol parseCtor(PrefixAttributes!AST* pAttrs) { AST.Expressions* udas = null; const loc = token.loc; StorageClass stc = getStorageClass!AST(pAttrs); nextToken(); if (token.value == TOK.leftParenthesis && peekNext() == TOK.this_ && peekNext2() == TOK.rightParenthesis) { // this(this) { ... } nextToken(); nextToken(); check(TOK.rightParenthesis); stc = parsePostfix(stc, &udas); if (stc & STC.immutable_) deprecation("`immutable` postblit is deprecated. Please use an unqualified postblit."); if (stc & STC.shared_) deprecation("`shared` postblit is deprecated. Please use an unqualified postblit."); if (stc & STC.const_) deprecation("`const` postblit is deprecated. Please use an unqualified postblit."); if (stc & STC.static_) error(loc, "postblit cannot be `static`"); auto f = new AST.PostBlitDeclaration(loc, Loc.initial, stc, Id.postblit); AST.Dsymbol s = parseContracts(f); if (udas) { auto a = new AST.Dsymbols(); a.push(f); s = new AST.UserAttributeDeclaration(udas, a); } return s; } /* Look ahead to see if: * this(...)(...) * which is a constructor template */ AST.TemplateParameters* tpl = null; if (token.value == TOK.leftParenthesis && peekPastParen(&token).value == TOK.leftParenthesis) { tpl = parseTemplateParameterList(); } /* Just a regular constructor */ auto parameterList = parseParameterList(null); stc = parsePostfix(stc, &udas); if (parameterList.varargs != VarArg.none || AST.Parameter.dim(parameterList.parameters) != 0) { if (stc & STC.static_) error(loc, "constructor cannot be static"); } else if (StorageClass ss = stc & (STC.shared_ | STC.static_)) // this() { if (ss == STC.static_) error(loc, "use `static this()` to declare a static constructor"); else if (ss == (STC.shared_ | STC.static_)) error(loc, "use `shared static this()` to declare a shared static constructor"); } AST.Expression constraint = tpl ? parseConstraint() : null; AST.Type tf = new AST.TypeFunction(parameterList, null, linkage, stc); // RetrunType -> auto tf = tf.addSTC(stc); auto f = new AST.CtorDeclaration(loc, Loc.initial, stc, tf); AST.Dsymbol s = parseContracts(f); if (udas) { auto a = new AST.Dsymbols(); a.push(f); s = new AST.UserAttributeDeclaration(udas, a); } if (tpl) { // Wrap a template around it auto decldefs = new AST.Dsymbols(); decldefs.push(s); s = new AST.TemplateDeclaration(loc, f.ident, tpl, constraint, decldefs); } return s; } /***************************************** * Parse a destructor definition: * ~this() { body } * Current token is '~'. */ private AST.Dsymbol parseDtor(PrefixAttributes!AST* pAttrs) { AST.Expressions* udas = null; const loc = token.loc; StorageClass stc = getStorageClass!AST(pAttrs); nextToken(); check(TOK.this_); check(TOK.leftParenthesis); check(TOK.rightParenthesis); stc = parsePostfix(stc, &udas); if (StorageClass ss = stc & (STC.shared_ | STC.static_)) { if (ss == STC.static_) error(loc, "use `static ~this()` to declare a static destructor"); else if (ss == (STC.shared_ | STC.static_)) error(loc, "use `shared static ~this()` to declare a shared static destructor"); } auto f = new AST.DtorDeclaration(loc, Loc.initial, stc, Id.dtor); AST.Dsymbol s = parseContracts(f); if (udas) { auto a = new AST.Dsymbols(); a.push(f); s = new AST.UserAttributeDeclaration(udas, a); } return s; } /***************************************** * Parse a static constructor definition: * static this() { body } * Current token is 'static'. */ private AST.Dsymbol parseStaticCtor(PrefixAttributes!AST* pAttrs) { //Expressions *udas = NULL; const loc = token.loc; StorageClass stc = getStorageClass!AST(pAttrs); nextToken(); nextToken(); check(TOK.leftParenthesis); check(TOK.rightParenthesis); stc = parsePostfix(stc & ~STC.TYPECTOR, null) | stc; if (stc & STC.shared_) error(loc, "use `shared static this()` to declare a shared static constructor"); else if (stc & STC.static_) appendStorageClass(stc, STC.static_); // complaint for the redundancy else if (StorageClass modStc = stc & STC.TYPECTOR) { OutBuffer buf; AST.stcToBuffer(&buf, modStc); error(loc, "static constructor cannot be `%s`", buf.peekChars()); } stc &= ~(STC.static_ | STC.TYPECTOR); auto f = new AST.StaticCtorDeclaration(loc, Loc.initial, stc); AST.Dsymbol s = parseContracts(f); return s; } /***************************************** * Parse a static destructor definition: * static ~this() { body } * Current token is 'static'. */ private AST.Dsymbol parseStaticDtor(PrefixAttributes!AST* pAttrs) { AST.Expressions* udas = null; const loc = token.loc; StorageClass stc = getStorageClass!AST(pAttrs); nextToken(); nextToken(); check(TOK.this_); check(TOK.leftParenthesis); check(TOK.rightParenthesis); stc = parsePostfix(stc & ~STC.TYPECTOR, &udas) | stc; if (stc & STC.shared_) error(loc, "use `shared static ~this()` to declare a shared static destructor"); else if (stc & STC.static_) appendStorageClass(stc, STC.static_); // complaint for the redundancy else if (StorageClass modStc = stc & STC.TYPECTOR) { OutBuffer buf; AST.stcToBuffer(&buf, modStc); error(loc, "static destructor cannot be `%s`", buf.peekChars()); } stc &= ~(STC.static_ | STC.TYPECTOR); auto f = new AST.StaticDtorDeclaration(loc, Loc.initial, stc); AST.Dsymbol s = parseContracts(f); if (udas) { auto a = new AST.Dsymbols(); a.push(f); s = new AST.UserAttributeDeclaration(udas, a); } return s; } /***************************************** * Parse a shared static constructor definition: * shared static this() { body } * Current token is 'shared'. */ private AST.Dsymbol parseSharedStaticCtor(PrefixAttributes!AST* pAttrs) { //Expressions *udas = NULL; const loc = token.loc; StorageClass stc = getStorageClass!AST(pAttrs); nextToken(); nextToken(); nextToken(); check(TOK.leftParenthesis); check(TOK.rightParenthesis); stc = parsePostfix(stc & ~STC.TYPECTOR, null) | stc; if (StorageClass ss = stc & (STC.shared_ | STC.static_)) appendStorageClass(stc, ss); // complaint for the redundancy else if (StorageClass modStc = stc & STC.TYPECTOR) { OutBuffer buf; AST.stcToBuffer(&buf, modStc); error(loc, "shared static constructor cannot be `%s`", buf.peekChars()); } stc &= ~(STC.static_ | STC.TYPECTOR); auto f = new AST.SharedStaticCtorDeclaration(loc, Loc.initial, stc); AST.Dsymbol s = parseContracts(f); return s; } /***************************************** * Parse a shared static destructor definition: * shared static ~this() { body } * Current token is 'shared'. */ private AST.Dsymbol parseSharedStaticDtor(PrefixAttributes!AST* pAttrs) { AST.Expressions* udas = null; const loc = token.loc; StorageClass stc = getStorageClass!AST(pAttrs); nextToken(); nextToken(); nextToken(); check(TOK.this_); check(TOK.leftParenthesis); check(TOK.rightParenthesis); stc = parsePostfix(stc & ~STC.TYPECTOR, &udas) | stc; if (StorageClass ss = stc & (STC.shared_ | STC.static_)) appendStorageClass(stc, ss); // complaint for the redundancy else if (StorageClass modStc = stc & STC.TYPECTOR) { OutBuffer buf; AST.stcToBuffer(&buf, modStc); error(loc, "shared static destructor cannot be `%s`", buf.peekChars()); } stc &= ~(STC.static_ | STC.TYPECTOR); auto f = new AST.SharedStaticDtorDeclaration(loc, Loc.initial, stc); AST.Dsymbol s = parseContracts(f); if (udas) { auto a = new AST.Dsymbols(); a.push(f); s = new AST.UserAttributeDeclaration(udas, a); } return s; } /***************************************** * Parse an invariant definition: * invariant { statements... } * invariant() { statements... } * invariant (expression); * Current token is 'invariant'. */ private AST.Dsymbol parseInvariant(PrefixAttributes!AST* pAttrs) { const loc = token.loc; StorageClass stc = getStorageClass!AST(pAttrs); nextToken(); if (token.value == TOK.leftParenthesis) // optional () or invariant (expression); { nextToken(); if (token.value != TOK.rightParenthesis) // invariant (expression); { AST.Expression e = parseAssignExp(), msg = null; if (token.value == TOK.comma) { nextToken(); if (token.value != TOK.rightParenthesis) { msg = parseAssignExp(); if (token.value == TOK.comma) nextToken(); } } check(TOK.rightParenthesis); check(TOK.semicolon); e = new AST.AssertExp(loc, e, msg); auto fbody = new AST.ExpStatement(loc, e); auto f = new AST.InvariantDeclaration(loc, token.loc, stc, null, fbody); return f; } nextToken(); } auto fbody = parseStatement(ParseStatementFlags.curly); auto f = new AST.InvariantDeclaration(loc, token.loc, stc, null, fbody); return f; } /***************************************** * Parse a unittest definition: * unittest { body } * Current token is 'unittest'. */ private AST.Dsymbol parseUnitTest(PrefixAttributes!AST* pAttrs) { const loc = token.loc; StorageClass stc = getStorageClass!AST(pAttrs); nextToken(); const(char)* begPtr = token.ptr + 1; // skip '{' const(char)* endPtr = null; AST.Statement sbody = parseStatement(ParseStatementFlags.curly, &endPtr); /** Extract unittest body as a string. Must be done eagerly since memory will be released by the lexer before doc gen. */ char* docline = null; if (global.params.doDocComments && endPtr > begPtr) { /* Remove trailing whitespaces */ for (const(char)* p = endPtr - 1; begPtr <= p && (*p == ' ' || *p == '\r' || *p == '\n' || *p == '\t'); --p) { endPtr = p; } size_t len = endPtr - begPtr; if (len > 0) { docline = cast(char*)mem.xmalloc_noscan(len + 2); memcpy(docline, begPtr, len); docline[len] = '\n'; // Terminate all lines by LF docline[len + 1] = '\0'; } } auto f = new AST.UnitTestDeclaration(loc, token.loc, stc, docline); f.fbody = sbody; return f; } /***************************************** * Parse a new definition: * @disable new(); * Current token is 'new'. */ private AST.Dsymbol parseNew(PrefixAttributes!AST* pAttrs) { const loc = token.loc; StorageClass stc = getStorageClass!AST(pAttrs); if (!(stc & STC.disable)) { error("`new` allocator must be annotated with `@disabled`"); } nextToken(); /* @@@DEPRECATED_2.108@@@ * After deprecation period (2.108), remove all code in the version(all) block. */ version (all) { auto parameterList = parseParameterList(null); // parameterList ignored if (parameterList.parameters.length > 0 || parameterList.varargs != VarArg.none) deprecation("`new` allocator with non-empty parameter list is deprecated"); auto f = new AST.NewDeclaration(loc, stc); if (token.value != TOK.semicolon) { deprecation("`new` allocator with function definition is deprecated"); parseContracts(f); // body ignored f.fbody = null; f.fensures = null; f.frequires = null; } else nextToken(); return f; } else { check(TOK.leftParenthesis); check(TOK.rightParenthesis); check(TOK.semicolon); return new AST.NewDeclaration(loc, stc); } } /********************************************** * Parse parameter list. */ private AST.ParameterList parseParameterList(AST.TemplateParameters** tpl) { auto parameters = new AST.Parameters(); VarArg varargs = VarArg.none; int hasdefault = 0; StorageClass varargsStc; // Attributes allowed for ... enum VarArgsStc = STC.const_ | STC.immutable_ | STC.shared_ | STC.scope_ | STC.return_ | STC.returnScope; check(TOK.leftParenthesis); while (1) { Identifier ai = null; AST.Type at; StorageClass storageClass = 0; StorageClass stc; AST.Expression ae; AST.Expressions* udas = null; for (; 1; nextToken()) { L3: switch (token.value) { case TOK.rightParenthesis: if (storageClass != 0 || udas !is null) error("basic type expected, not `)`"); break; case TOK.dotDotDot: varargs = VarArg.variadic; varargsStc = storageClass; if (varargsStc & ~VarArgsStc) { OutBuffer buf; AST.stcToBuffer(&buf, varargsStc & ~VarArgsStc); error("variadic parameter cannot have attributes `%s`", buf.peekChars()); varargsStc &= VarArgsStc; } nextToken(); break; case TOK.const_: if (peekNext() == TOK.leftParenthesis) goto default; stc = STC.const_; goto L2; case TOK.immutable_: if (peekNext() == TOK.leftParenthesis) goto default; stc = STC.immutable_; goto L2; case TOK.shared_: if (peekNext() == TOK.leftParenthesis) goto default; stc = STC.shared_; goto L2; case TOK.inout_: if (peekNext() == TOK.leftParenthesis) goto default; stc = STC.wild; goto L2; case TOK.at: { AST.Expressions* exps = null; StorageClass stc2 = parseAttribute(exps); if (stc2 & atAttrGroup) { error("`@%s` attribute for function parameter is not supported", token.toChars()); } else { udas = AST.UserAttributeDeclaration.concat(udas, exps); } if (token.value == TOK.dotDotDot) error("variadic parameter cannot have user-defined attributes"); if (stc2) nextToken(); goto L3; // Don't call nextToken again. } case TOK.in_: if (global.params.vin) message(scanloc, "Usage of 'in' on parameter"); stc = STC.in_; goto L2; case TOK.out_: stc = STC.out_; goto L2; case TOK.ref_: stc = STC.ref_; goto L2; case TOK.lazy_: stc = STC.lazy_; goto L2; case TOK.scope_: stc = STC.scope_; goto L2; case TOK.final_: stc = STC.final_; goto L2; case TOK.auto_: stc = STC.auto_; goto L2; case TOK.return_: stc = STC.return_; if (peekNext() == TOK.scope_) stc |= STC.returnScope; goto L2; L2: storageClass = appendStorageClass(storageClass, stc); continue; version (none) { case TOK.static_: stc = STC.static_; goto L2; case TOK.auto_: storageClass = STC.auto_; goto L4; case TOK.alias_: storageClass = STC.alias_; goto L4; L4: nextToken(); ai = null; if (token.value == TOK.identifier) { ai = token.ident; nextToken(); } at = null; // no type ae = null; // no default argument if (token.value == TOK.assign) // = defaultArg { nextToken(); ae = parseDefaultInitExp(); hasdefault = 1; } else { if (hasdefault) error("default argument expected for `alias %s`", ai ? ai.toChars() : ""); } goto L3; } default: { stc = storageClass & (STC.IOR | STC.lazy_); // if stc is not a power of 2 if (stc & (stc - 1) && !(stc == (STC.in_ | STC.ref_))) error("incompatible parameter storage classes"); //if ((storageClass & STC.scope_) && (storageClass & (STC.ref_ | STC.out_))) //error("scope cannot be ref or out"); if (tpl && token.value == TOK.identifier) { const tv = peekNext(); if (tv == TOK.comma || tv == TOK.rightParenthesis || tv == TOK.dotDotDot) { Identifier id = Identifier.generateId("__T"); const loc = token.loc; at = new AST.TypeIdentifier(loc, id); if (!*tpl) *tpl = new AST.TemplateParameters(); AST.TemplateParameter tp = new AST.TemplateTypeParameter(loc, id, null, null); (*tpl).push(tp); ai = token.ident; nextToken(); } else goto _else; } else { _else: at = parseType(&ai); } ae = null; if (token.value == TOK.assign) // = defaultArg { nextToken(); ae = parseDefaultInitExp(); hasdefault = 1; } else { if (hasdefault) error("default argument expected for `%s`", ai ? ai.toChars() : at.toChars()); } auto param = new AST.Parameter(storageClass | STC.parameter, at, ai, ae, null); if (udas) { auto a = new AST.Dsymbols(); auto udad = new AST.UserAttributeDeclaration(udas, a); param.userAttribDecl = udad; } if (token.value == TOK.at) { AST.Expressions* exps = null; StorageClass stc2 = parseAttribute(exps); if (stc2 & atAttrGroup) { error("`@%s` attribute for function parameter is not supported", token.toChars()); } else { error("user-defined attributes cannot appear as postfixes", token.toChars()); } if (stc2) nextToken(); } if (token.value == TOK.dotDotDot) { /* This is: * at ai ... */ if (storageClass & (STC.out_ | STC.ref_)) error("variadic argument cannot be `out` or `ref`"); varargs = VarArg.typesafe; parameters.push(param); nextToken(); break; } parameters.push(param); if (token.value == TOK.comma) { nextToken(); goto L1; } break; } } break; } break; L1: } check(TOK.rightParenthesis); return AST.ParameterList(parameters, varargs, varargsStc); } /************************************* */ private AST.EnumDeclaration parseEnum() { AST.EnumDeclaration e; Identifier id; AST.Type memtype; auto loc = token.loc; // printf("Parser::parseEnum()\n"); nextToken(); id = null; if (token.value == TOK.identifier) { id = token.ident; nextToken(); } memtype = null; if (token.value == TOK.colon) { nextToken(); int alt = 0; const typeLoc = token.loc; memtype = parseBasicType(); memtype = parseDeclarator(memtype, alt, null); checkCstyleTypeSyntax(typeLoc, memtype, alt, null); } e = new AST.EnumDeclaration(loc, id, memtype); if (token.value == TOK.semicolon && id) nextToken(); else if (token.value == TOK.leftCurly) { bool isAnonymousEnum = !id; TOK prevTOK; //printf("enum definition\n"); e.members = new AST.Dsymbols(); nextToken(); const(char)[] comment = token.blockComment; while (token.value != TOK.rightCurly) { /* Can take the following forms... * 1. ident * 2. ident = value * 3. type ident = value * ... prefixed by valid attributes */ loc = token.loc; AST.Type type = null; Identifier ident = null; AST.Expressions* udas; StorageClass stc; AST.Expression deprecationMessage; enum attributeErrorMessage = "`%s` is not a valid attribute for enum members"; while(token.value != TOK.rightCurly && token.value != TOK.comma && token.value != TOK.assign) { switch(token.value) { case TOK.at: if (StorageClass _stc = parseAttribute(udas)) { if (_stc == STC.disable) stc |= _stc; else { OutBuffer buf; AST.stcToBuffer(&buf, _stc); error(attributeErrorMessage, buf.peekChars()); } prevTOK = token.value; nextToken(); } break; case TOK.deprecated_: stc |= STC.deprecated_; if (!parseDeprecatedAttribute(deprecationMessage)) { prevTOK = token.value; nextToken(); } break; case TOK.identifier: const tv = peekNext(); if (tv == TOK.assign || tv == TOK.comma || tv == TOK.rightCurly) { ident = token.ident; type = null; prevTOK = token.value; nextToken(); } else { goto default; } break; default: if (isAnonymousEnum) { type = parseType(&ident, null); if (type == AST.Type.terror) { type = null; prevTOK = token.value; nextToken(); } else { prevTOK = TOK.identifier; } } else { error(attributeErrorMessage, token.toChars()); prevTOK = token.value; nextToken(); } break; } if (token.value == TOK.comma) { prevTOK = token.value; } } if (type && type != AST.Type.terror) { if (!ident) error("no identifier for declarator `%s`", type.toChars()); if (!isAnonymousEnum) error("type only allowed if anonymous enum and no enum type"); } AST.Expression value; if (token.value == TOK.assign) { if (prevTOK == TOK.identifier) { nextToken(); value = parseAssignExp(); } else { error("assignment must be preceded by an identifier"); nextToken(); } } else { value = null; if (type && type != AST.Type.terror && isAnonymousEnum) error("if type, there must be an initializer"); } AST.DeprecatedDeclaration dd; if (deprecationMessage) { dd = new AST.DeprecatedDeclaration(deprecationMessage, null); stc |= STC.deprecated_; } auto em = new AST.EnumMember(loc, ident, value, type, stc, null, dd); e.members.push(em); if (udas) { auto s = new AST.Dsymbols(); s.push(em); auto uad = new AST.UserAttributeDeclaration(udas, s); em.userAttribDecl = uad; } if (token.value == TOK.rightCurly) { } else { addComment(em, comment); comment = null; check(TOK.comma); } addComment(em, comment); comment = token.blockComment; if (token.value == TOK.endOfFile) { error("premature end of file"); break; } } nextToken(); } else error("enum declaration is invalid"); //printf("-parseEnum() %s\n", e.toChars()); return e; } /******************************** * Parse struct, union, interface, class. */ private AST.Dsymbol parseAggregate() { AST.TemplateParameters* tpl = null; AST.Expression constraint; const loc = token.loc; TOK tok = token.value; //printf("Parser::parseAggregate()\n"); nextToken(); Identifier id; if (token.value != TOK.identifier) { id = null; } else { id = token.ident; nextToken(); if (token.value == TOK.leftParenthesis) { // struct/class template declaration. tpl = parseTemplateParameterList(); constraint = parseConstraint(); } } // Collect base class(es) AST.BaseClasses* baseclasses = null; if (token.value == TOK.colon) { if (tok != TOK.interface_ && tok != TOK.class_) error("base classes are not allowed for `%s`, did you mean `;`?", Token.toChars(tok)); nextToken(); baseclasses = parseBaseClasses(); } if (token.value == TOK.if_) { if (constraint) error("template constraints appear both before and after BaseClassList, put them before"); constraint = parseConstraint(); } if (constraint) { if (!id) error("template constraints not allowed for anonymous `%s`", Token.toChars(tok)); if (!tpl) error("template constraints only allowed for templates"); } AST.Dsymbols* members = null; if (token.value == TOK.leftCurly) { //printf("aggregate definition\n"); const lookingForElseSave = lookingForElse; lookingForElse = Loc(); nextToken(); members = parseDeclDefs(0); lookingForElse = lookingForElseSave; if (token.value != TOK.rightCurly) { /* { */ error("`}` expected following members in `%s` declaration at %s", Token.toChars(tok), loc.toChars()); } nextToken(); } else if (token.value == TOK.semicolon && id) { if (baseclasses || constraint) error("members expected"); nextToken(); } else { error("{ } expected following `%s` declaration", Token.toChars(tok)); } AST.AggregateDeclaration a; switch (tok) { case TOK.interface_: if (!id) error(loc, "anonymous interfaces not allowed"); a = new AST.InterfaceDeclaration(loc, id, baseclasses); a.members = members; break; case TOK.class_: if (!id) error(loc, "anonymous classes not allowed"); bool inObject = md && !md.packages && md.id == Id.object; a = new AST.ClassDeclaration(loc, id, baseclasses, members, inObject); break; case TOK.struct_: if (id) { bool inObject = md && !md.packages && md.id == Id.object; a = new AST.StructDeclaration(loc, id, inObject); a.members = members; } else { /* Anonymous structs/unions are more like attributes. */ assert(!tpl); return new AST.AnonDeclaration(loc, false, members); } break; case TOK.union_: if (id) { a = new AST.UnionDeclaration(loc, id); a.members = members; } else { /* Anonymous structs/unions are more like attributes. */ assert(!tpl); return new AST.AnonDeclaration(loc, true, members); } break; default: assert(0); } if (tpl) { // Wrap a template around the aggregate declaration auto decldefs = new AST.Dsymbols(); decldefs.push(a); auto tempdecl = new AST.TemplateDeclaration(loc, id, tpl, constraint, decldefs); return tempdecl; } return a; } /******************************************* */ private AST.BaseClasses* parseBaseClasses() { auto baseclasses = new AST.BaseClasses(); for (; 1; nextToken()) { auto b = new AST.BaseClass(parseBasicType()); baseclasses.push(b); if (token.value != TOK.comma) break; } return baseclasses; } AST.Dsymbols* parseImport() { auto decldefs = new AST.Dsymbols(); Identifier aliasid = null; int isstatic = token.value == TOK.static_; if (isstatic) nextToken(); //printf("Parser::parseImport()\n"); do { L1: nextToken(); if (token.value != TOK.identifier) { error("identifier expected following `import`"); break; } const loc = token.loc; Identifier id = token.ident; Identifier[] a; nextToken(); if (!aliasid && token.value == TOK.assign) { aliasid = id; goto L1; } while (token.value == TOK.dot) { a ~= id; nextToken(); if (token.value != TOK.identifier) { error("identifier expected following `package`"); break; } id = token.ident; nextToken(); } auto s = new AST.Import(loc, a, id, aliasid, isstatic); decldefs.push(s); /* Look for * : alias=name, alias=name; * syntax. */ if (token.value == TOK.colon) { do { nextToken(); if (token.value != TOK.identifier) { error("identifier expected following `:`"); break; } Identifier _alias = token.ident; Identifier name; nextToken(); if (token.value == TOK.assign) { nextToken(); if (token.value != TOK.identifier) { error("identifier expected following `%s=`", _alias.toChars()); break; } name = token.ident; nextToken(); } else { name = _alias; _alias = null; } s.addAlias(name, _alias); } while (token.value == TOK.comma); break; // no comma-separated imports of this form } aliasid = null; } while (token.value == TOK.comma); if (token.value == TOK.semicolon) nextToken(); else { error("`;` expected"); nextToken(); } return decldefs; } AST.Type parseType(Identifier* pident = null, AST.TemplateParameters** ptpl = null) { /* Take care of the storage class prefixes that * serve as type attributes: * const type * immutable type * shared type * inout type * inout const type * shared const type * shared inout type * shared inout const type */ StorageClass stc = 0; while (1) { switch (token.value) { case TOK.const_: if (peekNext() == TOK.leftParenthesis) break; // const as type constructor stc |= STC.const_; // const as storage class nextToken(); continue; case TOK.immutable_: if (peekNext() == TOK.leftParenthesis) break; stc |= STC.immutable_; nextToken(); continue; case TOK.shared_: if (peekNext() == TOK.leftParenthesis) break; stc |= STC.shared_; nextToken(); continue; case TOK.inout_: if (peekNext() == TOK.leftParenthesis) break; stc |= STC.wild; nextToken(); continue; default: break; } break; } const typeLoc = token.loc; AST.Type t; t = parseBasicType(); int alt = 0; t = parseDeclarator(t, alt, pident, ptpl); checkCstyleTypeSyntax(typeLoc, t, alt, pident ? *pident : null); t = t.addSTC(stc); return t; } private AST.Type parseBasicType(bool dontLookDotIdents = false) { AST.Type t; Loc loc; Identifier id; //printf("parseBasicType()\n"); switch (token.value) { case TOK.void_: t = AST.Type.tvoid; goto LabelX; case TOK.int8: t = AST.Type.tint8; goto LabelX; case TOK.uns8: t = AST.Type.tuns8; goto LabelX; case TOK.int16: t = AST.Type.tint16; goto LabelX; case TOK.uns16: t = AST.Type.tuns16; goto LabelX; case TOK.int32: t = AST.Type.tint32; goto LabelX; case TOK.uns32: t = AST.Type.tuns32; goto LabelX; case TOK.int64: t = AST.Type.tint64; nextToken(); if (token.value == TOK.int64) // if `long long` { error("use `long` for a 64 bit integer instead of `long long`"); nextToken(); } else if (token.value == TOK.float64) // if `long double` { error("use `real` instead of `long double`"); t = AST.Type.tfloat80; nextToken(); } break; case TOK.uns64: t = AST.Type.tuns64; goto LabelX; case TOK.int128: t = AST.Type.tint128; goto LabelX; case TOK.uns128: t = AST.Type.tuns128; goto LabelX; case TOK.float32: t = AST.Type.tfloat32; goto LabelX; case TOK.float64: t = AST.Type.tfloat64; goto LabelX; case TOK.float80: t = AST.Type.tfloat80; goto LabelX; case TOK.imaginary32: t = AST.Type.timaginary32; goto LabelX; case TOK.imaginary64: t = AST.Type.timaginary64; goto LabelX; case TOK.imaginary80: t = AST.Type.timaginary80; goto LabelX; case TOK.complex32: t = AST.Type.tcomplex32; goto LabelX; case TOK.complex64: t = AST.Type.tcomplex64; goto LabelX; case TOK.complex80: t = AST.Type.tcomplex80; goto LabelX; case TOK.bool_: t = AST.Type.tbool; goto LabelX; case TOK.char_: t = AST.Type.tchar; goto LabelX; case TOK.wchar_: t = AST.Type.twchar; goto LabelX; case TOK.dchar_: t = AST.Type.tdchar; goto LabelX; LabelX: nextToken(); break; case TOK.this_: case TOK.super_: case TOK.identifier: loc = token.loc; id = token.ident; nextToken(); if (token.value == TOK.not) { // ident!(template_arguments) auto tempinst = new AST.TemplateInstance(loc, id, parseTemplateArguments()); t = parseBasicTypeStartingAt(new AST.TypeInstance(loc, tempinst), dontLookDotIdents); } else { t = parseBasicTypeStartingAt(new AST.TypeIdentifier(loc, id), dontLookDotIdents); } break; case TOK.mixin_: // https://dlang.org/spec/expression.html#mixin_types loc = token.loc; nextToken(); if (token.value != TOK.leftParenthesis) error("found `%s` when expecting `%s` following `mixin`", token.toChars(), Token.toChars(TOK.leftParenthesis)); auto exps = parseArguments(); t = new AST.TypeMixin(loc, exps); break; case TOK.dot: // Leading . as in .foo t = parseBasicTypeStartingAt(new AST.TypeIdentifier(token.loc, Id.empty), dontLookDotIdents); break; case TOK.typeof_: // typeof(expression) t = parseBasicTypeStartingAt(parseTypeof(), dontLookDotIdents); break; case TOK.vector: t = parseVector(); break; case TOK.traits: if (AST.TraitsExp te = cast(AST.TraitsExp) parsePrimaryExp()) if (te.ident) { t = new AST.TypeTraits(token.loc, te); break; } t = new AST.TypeError; break; case TOK.const_: // const(type) nextToken(); check(TOK.leftParenthesis); t = parseType().addSTC(STC.const_); check(TOK.rightParenthesis); break; case TOK.immutable_: // immutable(type) nextToken(); check(TOK.leftParenthesis); t = parseType().addSTC(STC.immutable_); check(TOK.rightParenthesis); break; case TOK.shared_: // shared(type) nextToken(); check(TOK.leftParenthesis); t = parseType().addSTC(STC.shared_); check(TOK.rightParenthesis); break; case TOK.inout_: // wild(type) nextToken(); check(TOK.leftParenthesis); t = parseType().addSTC(STC.wild); check(TOK.rightParenthesis); break; default: error("basic type expected, not `%s`", token.toChars()); if (token.value == TOK.else_) errorSupplemental(token.loc, "There's no `static else`, use `else` instead."); t = AST.Type.terror; break; } return t; } private AST.Type parseBasicTypeStartingAt(AST.TypeQualified tid, bool dontLookDotIdents) { AST.Type maybeArray = null; // See https://issues.dlang.org/show_bug.cgi?id=1215 // A basic type can look like MyType (typical case), but also: // MyType.T -> A type // MyType[expr] -> Either a static array of MyType or a type (iif MyType is a Ttuple) // MyType[expr].T -> A type. // MyType[expr].T[expr] -> Either a static array of MyType[expr].T or a type // (iif MyType[expr].T is a Ttuple) while (1) { switch (token.value) { case TOK.dot: { nextToken(); if (token.value != TOK.identifier) { error("identifier expected following `.` instead of `%s`", token.toChars()); break; } if (maybeArray) { // This is actually a TypeTuple index, not an {a/s}array. // We need to have a while loop to unwind all index taking: // T[e1][e2].U -> T, addIndex(e1), addIndex(e2) AST.Objects dimStack; AST.Type t = maybeArray; while (true) { if (t.ty == Tsarray) { // The index expression is an Expression. AST.TypeSArray a = cast(AST.TypeSArray)t; dimStack.push(a.dim.syntaxCopy()); t = a.next.syntaxCopy(); } else if (t.ty == Taarray) { // The index expression is a Type. It will be interpreted as an expression at semantic time. AST.TypeAArray a = cast(AST.TypeAArray)t; dimStack.push(a.index.syntaxCopy()); t = a.next.syntaxCopy(); } else { break; } } assert(dimStack.dim > 0); // We're good. Replay indices in the reverse order. tid = cast(AST.TypeQualified)t; while (dimStack.dim) { tid.addIndex(dimStack.pop()); } maybeArray = null; } const loc = token.loc; Identifier id = token.ident; nextToken(); if (token.value == TOK.not) { auto tempinst = new AST.TemplateInstance(loc, id, parseTemplateArguments()); tid.addInst(tempinst); } else tid.addIdent(id); continue; } case TOK.leftBracket: { if (dontLookDotIdents) // workaround for https://issues.dlang.org/show_bug.cgi?id=14911 goto Lend; nextToken(); AST.Type t = maybeArray ? maybeArray : cast(AST.Type)tid; if (token.value == TOK.rightBracket) { // It's a dynamic array, and we're done: // T[].U does not make sense. t = new AST.TypeDArray(t); nextToken(); return t; } else if (isDeclaration(&token, NeedDeclaratorId.no, TOK.rightBracket, null)) { // This can be one of two things: // 1 - an associative array declaration, T[type] // 2 - an associative array declaration, T[expr] // These can only be disambiguated later. AST.Type index = parseType(); // [ type ] maybeArray = new AST.TypeAArray(t, index); check(TOK.rightBracket); } else { // This can be one of three things: // 1 - an static array declaration, T[expr] // 2 - a slice, T[expr .. expr] // 3 - a template parameter pack index expression, T[expr].U // 1 and 3 can only be disambiguated later. //printf("it's type[expression]\n"); inBrackets++; AST.Expression e = parseAssignExp(); // [ expression ] if (token.value == TOK.slice) { // It's a slice, and we're done. nextToken(); AST.Expression e2 = parseAssignExp(); // [ exp .. exp ] t = new AST.TypeSlice(t, e, e2); inBrackets--; check(TOK.rightBracket); return t; } else { maybeArray = new AST.TypeSArray(t, e); inBrackets--; check(TOK.rightBracket); continue; } } break; } default: goto Lend; } } Lend: return maybeArray ? maybeArray : cast(AST.Type)tid; } /****************************************** * Parse suffixes to type t. * * * [] * [AssignExpression] * [AssignExpression .. AssignExpression] * [Type] * delegate Parameters MemberFunctionAttributes(opt) * function Parameters FunctionAttributes(opt) * Params: * t = the already parsed type * Returns: * t with the suffixes added * See_Also: * https://dlang.org/spec/declaration.html#TypeSuffixes */ private AST.Type parseTypeSuffixes(AST.Type t) { //printf("parseTypeSuffixes()\n"); while (1) { switch (token.value) { case TOK.mul: t = new AST.TypePointer(t); nextToken(); continue; case TOK.leftBracket: // Handle []. Make sure things like // int[3][1] a; // is (array[1] of array[3] of int) nextToken(); if (token.value == TOK.rightBracket) { t = new AST.TypeDArray(t); // [] nextToken(); } else if (isDeclaration(&token, NeedDeclaratorId.no, TOK.rightBracket, null)) { // It's an associative array declaration //printf("it's an associative array\n"); AST.Type index = parseType(); // [ type ] t = new AST.TypeAArray(t, index); check(TOK.rightBracket); } else { //printf("it's type[expression]\n"); inBrackets++; AST.Expression e = parseAssignExp(); // [ expression ] if (!e) { inBrackets--; check(TOK.rightBracket); continue; } if (token.value == TOK.slice) { nextToken(); AST.Expression e2 = parseAssignExp(); // [ exp .. exp ] t = new AST.TypeSlice(t, e, e2); } else { t = new AST.TypeSArray(t, e); } inBrackets--; check(TOK.rightBracket); } continue; case TOK.delegate_: case TOK.function_: { // Handle delegate declaration: // t delegate(parameter list) nothrow pure // t function(parameter list) nothrow pure const save = token.value; nextToken(); auto parameterList = parseParameterList(null); StorageClass stc = parsePostfix(STC.undefined_, null); auto tf = new AST.TypeFunction(parameterList, t, linkage, stc); if (stc & (STC.const_ | STC.immutable_ | STC.shared_ | STC.wild | STC.return_)) { if (save == TOK.function_) error("`const`/`immutable`/`shared`/`inout`/`return` attributes are only valid for non-static member functions"); else tf = cast(AST.TypeFunction)tf.addSTC(stc); } t = save == TOK.delegate_ ? new AST.TypeDelegate(tf) : new AST.TypePointer(tf); // pointer to function continue; } default: return t; } assert(0); } assert(0); } /********************** * Parse Declarator * Params: * t = base type to start with * palt = OR in 1 for C-style function pointer declaration syntax, * 2 for C-style array declaration syntax, otherwise don't modify * pident = set to Identifier if there is one, null if not * tpl = if !null, then set to TemplateParameterList * storageClass = any storage classes seen so far * pdisable = set to true if @disable seen * pudas = any user defined attributes seen so far. Merged with any more found * Returns: * type declared * Reference: https://dlang.org/spec/declaration.html#Declarator */ private AST.Type parseDeclarator(AST.Type t, ref int palt, Identifier* pident, AST.TemplateParameters** tpl = null, StorageClass storageClass = 0, bool* pdisable = null, AST.Expressions** pudas = null) { //printf("parseDeclarator(tpl = %p)\n", tpl); t = parseTypeSuffixes(t); AST.Type ts; switch (token.value) { case TOK.identifier: if (pident) *pident = token.ident; else error("unexpected identifier `%s` in declarator", token.ident.toChars()); ts = t; nextToken(); break; case TOK.leftParenthesis: { // like: T (*fp)(); // like: T ((*fp))(); if (peekNext() == TOK.mul || peekNext() == TOK.leftParenthesis) { /* Parse things with parentheses around the identifier, like: * int (*ident[3])[] * although the D style would be: * int[]*[3] ident */ palt |= 1; nextToken(); ts = parseDeclarator(t, palt, pident); check(TOK.rightParenthesis); break; } ts = t; Token* peekt = &token; /* Completely disallow C-style things like: * T (a); * Improve error messages for the common bug of a missing return type * by looking to see if (a) looks like a parameter list. */ if (isParameters(&peekt)) { error("function declaration without return type. (Note that constructors are always named `this`)"); } else error("unexpected `(` in declarator"); break; } default: ts = t; break; } // parse DeclaratorSuffixes while (1) { switch (token.value) { static if (CARRAYDECL) { /* Support C style array syntax: * int ident[] * as opposed to D-style: * int[] ident */ case TOK.leftBracket: { // This is the old C-style post [] syntax. AST.TypeNext ta; nextToken(); if (token.value == TOK.rightBracket) { // It's a dynamic array ta = new AST.TypeDArray(t); // [] nextToken(); palt |= 2; } else if (isDeclaration(&token, NeedDeclaratorId.no, TOK.rightBracket, null)) { // It's an associative array //printf("it's an associative array\n"); AST.Type index = parseType(); // [ type ] check(TOK.rightBracket); ta = new AST.TypeAArray(t, index); palt |= 2; } else { //printf("It's a static array\n"); AST.Expression e = parseAssignExp(); // [ expression ] ta = new AST.TypeSArray(t, e); check(TOK.rightBracket); palt |= 2; } /* Insert ta into * ts -> ... -> t * so that * ts -> ... -> ta -> t */ AST.Type* pt; for (pt = &ts; *pt != t; pt = &(cast(AST.TypeNext)*pt).next) { } *pt = ta; continue; } } case TOK.leftParenthesis: { if (tpl) { Token* tk = peekPastParen(&token); if (tk.value == TOK.leftParenthesis) { /* Look ahead to see if this is (...)(...), * i.e. a function template declaration */ //printf("function template declaration\n"); // Gather template parameter list *tpl = parseTemplateParameterList(); } else if (tk.value == TOK.assign) { /* or (...) =, * i.e. a variable template declaration */ //printf("variable template declaration\n"); *tpl = parseTemplateParameterList(); break; } } auto parameterList = parseParameterList(null); /* Parse const/immutable/shared/inout/nothrow/pure/return postfix */ // merge prefix storage classes StorageClass stc = parsePostfix(storageClass, pudas); AST.Type tf = new AST.TypeFunction(parameterList, t, linkage, stc); tf = tf.addSTC(stc); if (pdisable) *pdisable = stc & STC.disable ? true : false; /* Insert tf into * ts -> ... -> t * so that * ts -> ... -> tf -> t */ AST.Type* pt; for (pt = &ts; *pt != t; pt = &(cast(AST.TypeNext)*pt).next) { } *pt = tf; break; } default: break; } break; } return ts; } private void parseStorageClasses(ref StorageClass storage_class, ref LINK link, ref bool setAlignment, ref AST.Expression ealign, ref AST.Expressions* udas, out Loc linkloc) { StorageClass stc; bool sawLinkage = false; // seen a linkage declaration linkloc = Loc.initial; while (1) { switch (token.value) { case TOK.const_: if (peekNext() == TOK.leftParenthesis) break; // const as type constructor stc = STC.const_; // const as storage class goto L1; case TOK.immutable_: if (peekNext() == TOK.leftParenthesis) break; stc = STC.immutable_; goto L1; case TOK.shared_: if (peekNext() == TOK.leftParenthesis) break; stc = STC.shared_; goto L1; case TOK.inout_: if (peekNext() == TOK.leftParenthesis) break; stc = STC.wild; goto L1; case TOK.static_: stc = STC.static_; goto L1; case TOK.final_: stc = STC.final_; goto L1; case TOK.auto_: stc = STC.auto_; goto L1; case TOK.scope_: stc = STC.scope_; goto L1; case TOK.override_: stc = STC.override_; goto L1; case TOK.abstract_: stc = STC.abstract_; goto L1; case TOK.synchronized_: stc = STC.synchronized_; goto L1; case TOK.deprecated_: stc = STC.deprecated_; goto L1; case TOK.nothrow_: stc = STC.nothrow_; goto L1; case TOK.pure_: stc = STC.pure_; goto L1; case TOK.ref_: stc = STC.ref_; goto L1; case TOK.gshared: stc = STC.gshared; goto L1; case TOK.enum_: { const tv = peekNext(); if (tv == TOK.leftCurly || tv == TOK.colon) break; if (tv == TOK.identifier) { const nextv = peekNext2(); if (nextv == TOK.leftCurly || nextv == TOK.colon || nextv == TOK.semicolon) break; } stc = STC.manifest; goto L1; } case TOK.at: { stc = parseAttribute(udas); if (stc) goto L1; continue; } L1: storage_class = appendStorageClass(storage_class, stc); nextToken(); continue; case TOK.extern_: { if (peekNext() != TOK.leftParenthesis) { stc = STC.extern_; goto L1; } if (sawLinkage) error("redundant linkage declaration"); sawLinkage = true; linkloc = token.loc; auto res = parseLinkage(); link = res.link; if (res.idents || res.identExps) { error("C++ name spaces not allowed here"); } if (res.cppmangle != CPPMANGLE.def) { error("C++ mangle declaration not allowed here"); } continue; } case TOK.align_: { nextToken(); setAlignment = true; if (token.value == TOK.leftParenthesis) { nextToken(); ealign = parseExpression(); check(TOK.rightParenthesis); } continue; } default: break; } break; } } /********************************** * Parse Declarations. * These can be: * 1. declarations at global/class level * 2. declarations at statement level * Return array of Declaration *'s. */ private AST.Dsymbols* parseDeclarations(bool autodecl, PrefixAttributes!AST* pAttrs, const(char)* comment) { StorageClass storage_class = STC.undefined_; LINK link = linkage; Loc linkloc = this.linkLoc; bool setAlignment = false; AST.Expression ealign; AST.Expressions* udas = null; //printf("parseDeclarations() %s\n", token.toChars()); if (!comment) comment = token.blockComment.ptr; /* Look for AliasReassignment */ if (token.value == TOK.identifier && peekNext() == TOK.assign) return parseAliasReassignment(comment); /* Declarations that start with `alias` */ bool isAliasDeclaration = false; if (token.value == TOK.alias_) { if (auto a = parseAliasDeclarations(comment)) return a; /* Handle these later: * alias StorageClasses type ident; */ isAliasDeclaration = true; } AST.Type ts; if (!autodecl) { parseStorageClasses(storage_class, link, setAlignment, ealign, udas, linkloc); if (token.value == TOK.enum_) { AST.Dsymbol d = parseEnum(); auto a = new AST.Dsymbols(); a.push(d); if (udas) { d = new AST.UserAttributeDeclaration(udas, a); a = new AST.Dsymbols(); a.push(d); } addComment(d, comment); return a; } if (token.value == TOK.struct_ || token.value == TOK.union_ || token.value == TOK.class_ || token.value == TOK.interface_) { AST.Dsymbol s = parseAggregate(); auto a = new AST.Dsymbols(); a.push(s); if (storage_class) { s = new AST.StorageClassDeclaration(storage_class, a); a = new AST.Dsymbols(); a.push(s); } if (setAlignment) { s = new AST.AlignDeclaration(s.loc, ealign, a); a = new AST.Dsymbols(); a.push(s); } if (link != linkage) { s = new AST.LinkDeclaration(linkloc, link, a); a = new AST.Dsymbols(); a.push(s); } if (udas) { s = new AST.UserAttributeDeclaration(udas, a); a = new AST.Dsymbols(); a.push(s); } addComment(s, comment); return a; } /* Look for auto initializers: * storage_class identifier = initializer; * storage_class identifier(...) = initializer; */ if ((storage_class || udas) && token.value == TOK.identifier && hasOptionalParensThen(peek(&token), TOK.assign)) { AST.Dsymbols* a = parseAutoDeclarations(storage_class, comment); if (udas) { AST.Dsymbol s = new AST.UserAttributeDeclaration(udas, a); a = new AST.Dsymbols(); a.push(s); } return a; } /* Look for return type inference for template functions. */ { Token* tk; if ((storage_class || udas) && token.value == TOK.identifier && skipParens(peek(&token), &tk) && skipAttributes(tk, &tk) && (tk.value == TOK.leftParenthesis || tk.value == TOK.leftCurly || tk.value == TOK.in_ || tk.value == TOK.out_ || tk.value == TOK.goesTo || tk.value == TOK.do_ || tk.value == TOK.identifier && tk.ident == Id._body)) { // @@@DEPRECATED_2.117@@@ // https://github.com/dlang/DIPs/blob/1f5959abe482b1f9094f6484a7d0a3ade77fc2fc/DIPs/accepted/DIP1003.md // Deprecated in 2.097 - Can be removed from 2.117 // The deprecation period is longer than usual as `body` // was quite widely used. if (tk.value == TOK.identifier && tk.ident == Id._body) deprecation("Usage of the `body` keyword is deprecated. Use `do` instead."); ts = null; } else { ts = parseBasicType(); ts = parseTypeSuffixes(ts); } } } if (pAttrs) { storage_class |= pAttrs.storageClass; //pAttrs.storageClass = STC.undefined_; } AST.Type tfirst = null; auto a = new AST.Dsymbols(); while (1) { AST.TemplateParameters* tpl = null; bool disable; int alt = 0; const loc = token.loc; Identifier ident; auto t = parseDeclarator(ts, alt, &ident, &tpl, storage_class, &disable, &udas); assert(t); if (!tfirst) tfirst = t; else if (t != tfirst) error("multiple declarations must have the same type, not `%s` and `%s`", tfirst.toChars(), t.toChars()); bool isThis = (t.ty == Tident && (cast(AST.TypeIdentifier)t).ident == Id.This && token.value == TOK.assign); if (ident) checkCstyleTypeSyntax(loc, t, alt, ident); else if (!isThis && (t != AST.Type.terror)) error("no identifier for declarator `%s`", t.toChars()); if (isAliasDeclaration) { AST.Declaration v; AST.Initializer _init = null; /* Aliases can no longer have multiple declarators, storage classes, * linkages, or auto declarations. * These never made any sense, anyway. * The code below needs to be fixed to reject them. * The grammar has already been fixed to preclude them. */ if (udas) error("user-defined attributes not allowed for `alias` declarations"); if (token.value == TOK.assign) { nextToken(); _init = parseInitializer(); } if (_init) { if (isThis) error("cannot use syntax `alias this = %s`, use `alias %s this` instead", _init.toChars(), _init.toChars()); else error("alias cannot have initializer"); } v = new AST.AliasDeclaration(loc, ident, t); v.storage_class = storage_class; if (pAttrs) { /* AliasDeclaration distinguish @safe, @system, @trusted attributes * on prefix and postfix. * @safe alias void function() FP1; * alias @safe void function() FP2; // FP2 is not @safe * alias void function() @safe FP3; */ pAttrs.storageClass &= STC.safeGroup; } AST.Dsymbol s = v; if (link != linkage) { auto ax = new AST.Dsymbols(); ax.push(v); s = new AST.LinkDeclaration(linkloc, link, ax); } a.push(s); switch (token.value) { case TOK.semicolon: nextToken(); addComment(s, comment); break; case TOK.comma: nextToken(); addComment(s, comment); continue; default: error("semicolon expected to close `alias` declaration"); break; } } else if (t.ty == Tfunction) { AST.Expression constraint = null; //printf("%s funcdecl t = %s, storage_class = x%lx\n", loc.toChars(), t.toChars(), storage_class); auto f = new AST.FuncDeclaration(loc, Loc.initial, ident, storage_class | (disable ? STC.disable : 0), t); if (pAttrs) pAttrs.storageClass = STC.undefined_; if (tpl) constraint = parseConstraint(); AST.Dsymbol s = parseContracts(f); auto tplIdent = s.ident; if (link != linkage) { auto ax = new AST.Dsymbols(); ax.push(s); s = new AST.LinkDeclaration(linkloc, link, ax); } if (udas) { auto ax = new AST.Dsymbols(); ax.push(s); s = new AST.UserAttributeDeclaration(udas, ax); } /* A template parameter list means it's a function template */ if (tpl) { // Wrap a template around the function declaration auto decldefs = new AST.Dsymbols(); decldefs.push(s); auto tempdecl = new AST.TemplateDeclaration(loc, tplIdent, tpl, constraint, decldefs); s = tempdecl; StorageClass stc2 = STC.undefined_; if (storage_class & STC.static_) { assert(f.storage_class & STC.static_); f.storage_class &= ~STC.static_; stc2 |= STC.static_; } if (storage_class & STC.deprecated_) { assert(f.storage_class & STC.deprecated_); f.storage_class &= ~STC.deprecated_; stc2 |= STC.deprecated_; } if (stc2 != STC.undefined_) { auto ax = new AST.Dsymbols(); ax.push(s); s = new AST.StorageClassDeclaration(stc2, ax); } } a.push(s); addComment(s, comment); } else if (ident) { AST.Initializer _init = null; if (token.value == TOK.assign) { nextToken(); _init = parseInitializer(); } auto v = new AST.VarDeclaration(loc, t, ident, _init); v.storage_class = storage_class; if (pAttrs) pAttrs.storageClass = STC.undefined_; AST.Dsymbol s = v; if (tpl && _init) { auto a2 = new AST.Dsymbols(); a2.push(s); auto tempdecl = new AST.TemplateDeclaration(loc, ident, tpl, null, a2, 0); s = tempdecl; } if (setAlignment) { auto ax = new AST.Dsymbols(); ax.push(s); s = new AST.AlignDeclaration(v.loc, ealign, ax); } if (link != linkage) { auto ax = new AST.Dsymbols(); ax.push(s); s = new AST.LinkDeclaration(linkloc, link, ax); } if (udas) { auto ax = new AST.Dsymbols(); ax.push(s); s = new AST.UserAttributeDeclaration(udas, ax); } a.push(s); switch (token.value) { case TOK.semicolon: nextToken(); addComment(s, comment); break; case TOK.comma: nextToken(); addComment(s, comment); continue; default: if (loc.linnum != token.loc.linnum) { error("semicolon needed to end declaration of `%s`, instead of `%s`", v.toChars(), token.toChars()); errorSupplemental(loc, "`%s` declared here", v.toChars()); } else { error("semicolon needed to end declaration of `%s` instead of `%s`", v.toChars(), token.toChars()); } break; } } break; } return a; } /******************************** * Parse AliasReassignment: * identifier = type; * Parser is sitting on the identifier. * https://dlang.org/spec/declaration.html#alias-reassignment * Params: * comment = if not null, comment to attach to symbol * Returns: * array of symbols */ private AST.Dsymbols* parseAliasReassignment(const(char)* comment) { const loc = token.loc; auto ident = token.ident; nextToken(); nextToken(); // advance past = auto t = parseType(); AST.Dsymbol s = new AST.AliasAssign(loc, ident, t, null); check(TOK.semicolon); addComment(s, comment); auto a = new AST.Dsymbols(); a.push(s); return a; } /******************************** * Parse declarations that start with `alias` * Parser is sitting on the `alias`. * https://dlang.org/spec/declaration.html#alias * Params: * comment = if not null, comment to attach to symbol * Returns: * array of symbols */ private AST.Dsymbols* parseAliasDeclarations(const(char)* comment) { const loc = token.loc; nextToken(); Loc linkloc = this.linkLoc; AST.Expressions* udas; LINK link = linkage; StorageClass storage_class = STC.undefined_; AST.Expression ealign; bool setAlignment = false; /* Look for: * alias Identifier this; * https://dlang.org/spec/class.html#alias-this */ if (token.value == TOK.identifier && peekNext() == TOK.this_) { auto s = new AST.AliasThis(loc, token.ident); nextToken(); check(TOK.this_); check(TOK.semicolon); auto a = new AST.Dsymbols(); a.push(s); addComment(s, comment); return a; } version (none) { /* Look for: * alias this = identifier; */ if (token.value == TOK.this_ && peekNext() == TOK.assign && peekNext2() == TOK.identifier) { check(TOK.this_); check(TOK.assign); auto s = new AliasThis(loc, token.ident); nextToken(); check(TOK.semicolon); auto a = new Dsymbols(); a.push(s); addComment(s, comment); return a; } } /* Look for: * alias identifier = type; * alias identifier(...) = type; * https://dlang.org/spec/declaration.html#alias */ if (token.value == TOK.identifier && hasOptionalParensThen(peek(&token), TOK.assign)) { auto a = new AST.Dsymbols(); while (1) { auto ident = token.ident; nextToken(); AST.TemplateParameters* tpl = null; if (token.value == TOK.leftParenthesis) tpl = parseTemplateParameterList(); check(TOK.assign); bool hasParsedAttributes; void parseAttributes() { if (hasParsedAttributes) // only parse once return; hasParsedAttributes = true; udas = null; storage_class = STC.undefined_; link = linkage; linkloc = this.linkLoc; setAlignment = false; ealign = null; parseStorageClasses(storage_class, link, setAlignment, ealign, udas, linkloc); } if (token.value == TOK.at) parseAttributes; AST.Declaration v; AST.Dsymbol s; // try to parse function type: // TypeCtors? BasicType ( Parameters ) MemberFunctionAttributes bool attributesAppended; const StorageClass funcStc = parseTypeCtor(); Token* tlu = &token; Token* tk; if (token.value != TOK.function_ && token.value != TOK.delegate_ && isBasicType(&tlu) && tlu && tlu.value == TOK.leftParenthesis) { AST.Type tret = parseBasicType(); auto parameterList = parseParameterList(null); parseAttributes(); if (udas) error("user-defined attributes not allowed for `alias` declarations"); attributesAppended = true; storage_class = appendStorageClass(storage_class, funcStc); AST.Type tf = new AST.TypeFunction(parameterList, tret, link, storage_class); v = new AST.AliasDeclaration(loc, ident, tf); } else if (token.value == TOK.function_ || token.value == TOK.delegate_ || token.value == TOK.leftParenthesis && skipAttributes(peekPastParen(&token), &tk) && (tk.value == TOK.goesTo || tk.value == TOK.leftCurly) || token.value == TOK.leftCurly || token.value == TOK.identifier && peekNext() == TOK.goesTo || token.value == TOK.ref_ && peekNext() == TOK.leftParenthesis && skipAttributes(peekPastParen(peek(&token)), &tk) && (tk.value == TOK.goesTo || tk.value == TOK.leftCurly) ) { // function (parameters) { statements... } // delegate (parameters) { statements... } // (parameters) { statements... } // (parameters) => expression // { statements... } // identifier => expression // ref (parameters) { statements... } // ref (parameters) => expression s = parseFunctionLiteral(); if (udas !is null) { if (storage_class != 0) error("Cannot put a storage-class in an alias declaration."); // parseAttributes shouldn't have set these variables assert(link == linkage && !setAlignment && ealign is null); auto tpl_ = cast(AST.TemplateDeclaration) s; if (tpl_ is null || tpl_.members.dim != 1) { error("user-defined attributes are not allowed on `alias` declarations"); } else { auto fd = cast(AST.FuncLiteralDeclaration) (*tpl_.members)[0]; auto tf = cast(AST.TypeFunction) fd.type; assert(tf.parameterList.parameters.dim > 0); auto as = new AST.Dsymbols(); (*tf.parameterList.parameters)[0].userAttribDecl = new AST.UserAttributeDeclaration(udas, as); } } v = new AST.AliasDeclaration(loc, ident, s); } else { parseAttributes(); // type if (udas) error("user-defined attributes not allowed for alias declarations"); auto t = parseType(); // Disallow meaningless storage classes on type aliases if (storage_class) { // Don't raise errors for STC that are part of a function/delegate type, e.g. // `alias F = ref pure nothrow @nogc @safe int function();` auto tp = t.isTypePointer; const isFuncType = (tp && tp.next.isTypeFunction) || t.isTypeDelegate; const remStc = isFuncType ? (storage_class & ~STC.FUNCATTR) : storage_class; if (remStc) { OutBuffer buf; AST.stcToBuffer(&buf, remStc); // @@@DEPRECATED_2.103@@@ // Deprecated in 2020-07, can be made an error in 2.103 deprecation("storage class `%s` has no effect in type aliases", buf.peekChars()); } } v = new AST.AliasDeclaration(loc, ident, t); } if (!attributesAppended) storage_class = appendStorageClass(storage_class, funcStc); v.storage_class = storage_class; s = v; if (tpl) { auto a2 = new AST.Dsymbols(); a2.push(s); auto tempdecl = new AST.TemplateDeclaration(loc, ident, tpl, null, a2); s = tempdecl; } if (link != linkage) { auto a2 = new AST.Dsymbols(); a2.push(s); s = new AST.LinkDeclaration(linkloc, link, a2); } a.push(s); switch (token.value) { case TOK.semicolon: nextToken(); addComment(s, comment); break; case TOK.comma: nextToken(); addComment(s, comment); if (token.value != TOK.identifier) { error("identifier expected following comma, not `%s`", token.toChars()); break; } if (peekNext() != TOK.assign && peekNext() != TOK.leftParenthesis) { error("`=` expected following identifier"); nextToken(); break; } continue; default: error("semicolon expected to close `alias` declaration"); break; } break; } return a; } // alias StorageClasses type ident; return null; } private AST.Dsymbol parseFunctionLiteral() { const loc = token.loc; AST.TemplateParameters* tpl = null; AST.ParameterList parameterList; AST.Type tret = null; StorageClass stc = 0; TOK save = TOK.reserved; switch (token.value) { case TOK.function_: case TOK.delegate_: save = token.value; nextToken(); if (token.value == TOK.ref_) { // function ref (parameters) { statements... } // delegate ref (parameters) { statements... } stc = STC.ref_; nextToken(); } if (token.value != TOK.leftParenthesis && token.value != TOK.leftCurly) { // function type (parameters) { statements... } // delegate type (parameters) { statements... } tret = parseBasicType(); tret = parseTypeSuffixes(tret); // function return type } if (token.value == TOK.leftParenthesis) { // function (parameters) { statements... } // delegate (parameters) { statements... } } else { // function { statements... } // delegate { statements... } break; } goto case TOK.leftParenthesis; case TOK.ref_: { // ref (parameters) => expression // ref (parameters) { statements... } stc = STC.ref_; nextToken(); goto case TOK.leftParenthesis; } case TOK.leftParenthesis: { // (parameters) => expression // (parameters) { statements... } parameterList = parseParameterList(&tpl); stc = parsePostfix(stc, null); if (StorageClass modStc = stc & STC.TYPECTOR) { if (save == TOK.function_) { OutBuffer buf; AST.stcToBuffer(&buf, modStc); error("function literal cannot be `%s`", buf.peekChars()); } else save = TOK.delegate_; } break; } case TOK.leftCurly: // { statements... } break; case TOK.identifier: { // identifier => expression parameterList.parameters = new AST.Parameters(); Identifier id = Identifier.generateId("__T"); AST.Type t = new AST.TypeIdentifier(loc, id); parameterList.parameters.push(new AST.Parameter(STC.parameter, t, token.ident, null, null)); tpl = new AST.TemplateParameters(); AST.TemplateParameter tp = new AST.TemplateTypeParameter(loc, id, null, null); tpl.push(tp); nextToken(); break; } default: assert(0); } auto tf = new AST.TypeFunction(parameterList, tret, linkage, stc); tf = cast(AST.TypeFunction)tf.addSTC(stc); auto fd = new AST.FuncLiteralDeclaration(loc, Loc.initial, tf, save, null); if (token.value == TOK.goesTo) { check(TOK.goesTo); if (token.value == TOK.leftCurly) { deprecation("Using `(args) => { ... }` to create a delegate that returns a delegate is error-prone."); deprecationSupplemental(token.loc, "Use `(args) { ... }` for a multi-statement function literal or use `(args) => () { }` if you intended for the lambda to return a delegate."); } const returnloc = token.loc; AST.Expression ae = parseAssignExp(); fd.fbody = new AST.ReturnStatement(returnloc, ae); fd.endloc = token.loc; } else { parseContracts(fd); } if (tpl) { // Wrap a template around function fd auto decldefs = new AST.Dsymbols(); decldefs.push(fd); return new AST.TemplateDeclaration(fd.loc, fd.ident, tpl, null, decldefs, false, true); } return fd; } /***************************************** * Parse contracts following function declaration. */ private AST.FuncDeclaration parseContracts(AST.FuncDeclaration f) { LINK linksave = linkage; bool literal = f.isFuncLiteralDeclaration() !is null; // The following is irrelevant, as it is overridden by sc.linkage in // TypeFunction::semantic linkage = LINK.d; // nested functions have D linkage bool requireDo = false; L1: switch (token.value) { case TOK.goesTo: if (requireDo) error("missing `do { ... }` after `in` or `out`"); if (!global.params.shortenedMethods) error("=> shortened method not enabled, compile with compiler switch `-preview=shortenedMethods`"); const returnloc = token.loc; nextToken(); f.fbody = new AST.ReturnStatement(returnloc, parseExpression()); f.endloc = token.loc; check(TOK.semicolon); break; case TOK.leftCurly: if (requireDo) error("missing `do { ... }` after `in` or `out`"); f.fbody = parseStatement(ParseStatementFlags.semi); f.endloc = endloc; break; case TOK.identifier: if (token.ident == Id._body) { // @@@DEPRECATED_2.117@@@ // https://github.com/dlang/DIPs/blob/1f5959abe482b1f9094f6484a7d0a3ade77fc2fc/DIPs/accepted/DIP1003.md // Deprecated in 2.097 - Can be removed from 2.117 // The deprecation period is longer than usual as `body` // was quite widely used. deprecation("Usage of the `body` keyword is deprecated. Use `do` instead."); goto case TOK.do_; } goto default; case TOK.do_: nextToken(); f.fbody = parseStatement(ParseStatementFlags.curly); f.endloc = endloc; break; version (none) { // Do we want this for function declarations, so we can do: // int x, y, foo(), z; case TOK.comma: nextToken(); continue; } case TOK.in_: // in { statements... } // in (expression) auto loc = token.loc; nextToken(); if (!f.frequires) { f.frequires = new AST.Statements; } if (token.value == TOK.leftParenthesis) { nextToken(); AST.Expression e = parseAssignExp(), msg = null; if (token.value == TOK.comma) { nextToken(); if (token.value != TOK.rightParenthesis) { msg = parseAssignExp(); if (token.value == TOK.comma) nextToken(); } } check(TOK.rightParenthesis); e = new AST.AssertExp(loc, e, msg); f.frequires.push(new AST.ExpStatement(loc, e)); requireDo = false; } else { f.frequires.push(parseStatement(ParseStatementFlags.curly | ParseStatementFlags.scope_)); requireDo = true; } goto L1; case TOK.out_: // out { statements... } // out (; expression) // out (identifier) { statements... } // out (identifier; expression) auto loc = token.loc; nextToken(); if (!f.fensures) { f.fensures = new AST.Ensures; } Identifier id = null; if (token.value != TOK.leftCurly) { check(TOK.leftParenthesis); if (token.value != TOK.identifier && token.value != TOK.semicolon) error("`(identifier) { ... }` or `(identifier; expression)` following `out` expected, not `%s`", token.toChars()); if (token.value != TOK.semicolon) { id = token.ident; nextToken(); } if (token.value == TOK.semicolon) { nextToken(); AST.Expression e = parseAssignExp(), msg = null; if (token.value == TOK.comma) { nextToken(); if (token.value != TOK.rightParenthesis) { msg = parseAssignExp(); if (token.value == TOK.comma) nextToken(); } } check(TOK.rightParenthesis); e = new AST.AssertExp(loc, e, msg); f.fensures.push(AST.Ensure(id, new AST.ExpStatement(loc, e))); requireDo = false; goto L1; } check(TOK.rightParenthesis); } f.fensures.push(AST.Ensure(id, parseStatement(ParseStatementFlags.curly | ParseStatementFlags.scope_))); requireDo = true; goto L1; case TOK.semicolon: if (!literal) { // https://issues.dlang.org/show_bug.cgi?id=15799 // Semicolon becomes a part of function declaration // only when 'do' is not required if (!requireDo) nextToken(); break; } goto default; default: if (literal) { const(char)* sbody = requireDo ? "do " : ""; error("missing `%s{ ... }` for function literal", sbody); } else if (!requireDo) // allow contracts even with no body { TOK t = token.value; if (t == TOK.const_ || t == TOK.immutable_ || t == TOK.inout_ || t == TOK.return_ || t == TOK.shared_ || t == TOK.nothrow_ || t == TOK.pure_) error("'%s' cannot be placed after a template constraint", token.toChars); else if (t == TOK.at) error("attributes cannot be placed after a template constraint"); else if (t == TOK.if_) error("cannot use function constraints for non-template functions. Use `static if` instead"); else error("semicolon expected following function declaration"); } break; } if (literal && !f.fbody) { // Set empty function body for error recovery f.fbody = new AST.CompoundStatement(Loc.initial, cast(AST.Statement)null); } linkage = linksave; return f; } /***************************************** */ private void checkDanglingElse(Loc elseloc) { if (token.value != TOK.else_ && token.value != TOK.catch_ && token.value != TOK.finally_ && lookingForElse.linnum != 0) { warning(elseloc, "else is dangling, add { } after condition at %s", lookingForElse.toChars()); } } /* ************************* * Issue errors if C-style syntax * Params: * alt = !=0 for C-style syntax */ private void checkCstyleTypeSyntax(Loc loc, AST.Type t, int alt, Identifier ident) { if (!alt) return; const(char)* sp = !ident ? "" : " "; const(char)* s = !ident ? "" : ident.toChars(); error(loc, "instead of C-style syntax, use D-style `%s%s%s`", t.toChars(), sp, s); } /***************************************** * Parses `foreach` statements, `static foreach` statements and * `static foreach` declarations. * Params: * Foreach = one of Statement, StaticForeachStatement, StaticForeachDeclaration * loc = location of foreach * pLastDecl = non-null for StaticForeachDeclaration * Returns: * the Foreach generated */ private Foreach parseForeach(alias Foreach)(Loc loc, AST.Dsymbol* pLastDecl) { static if (is(Foreach == AST.StaticForeachStatement) || is(Foreach == AST.StaticForeachDeclaration)) { nextToken(); } TOK op = token.value; nextToken(); check(TOK.leftParenthesis); auto parameters = new AST.Parameters(); Identifier lastai; while (1) { Identifier ai = null; AST.Type at; StorageClass storageClass = 0; StorageClass stc = 0; Lagain: if (stc) { storageClass = appendStorageClass(storageClass, stc); nextToken(); } switch (token.value) { case TOK.ref_: stc = STC.ref_; goto Lagain; case TOK.scope_: stc = STC.scope_; goto Lagain; case TOK.out_: error("cannot declare `out` loop variable, use `ref` instead"); stc = STC.out_; goto Lagain; case TOK.enum_: stc = STC.manifest; goto Lagain; case TOK.alias_: storageClass = appendStorageClass(storageClass, STC.alias_); nextToken(); break; case TOK.const_: if (peekNext() != TOK.leftParenthesis) { stc = STC.const_; goto Lagain; } break; case TOK.immutable_: if (peekNext() != TOK.leftParenthesis) { stc = STC.immutable_; goto Lagain; } break; case TOK.shared_: if (peekNext() != TOK.leftParenthesis) { stc = STC.shared_; goto Lagain; } break; case TOK.inout_: if (peekNext() != TOK.leftParenthesis) { stc = STC.wild; goto Lagain; } break; default: break; } if (token.value == TOK.identifier) { const tv = peekNext(); if (tv == TOK.comma || tv == TOK.semicolon || tv == TOK.rightParenthesis) { lastai = token.ident; ai = token.ident; at = null; // infer argument type nextToken(); goto Larg; } } at = parseType(&ai); if (!ai) error("no identifier for declarator `%s`", at.toChars()); Larg: auto p = new AST.Parameter(storageClass, at, ai, null, null); parameters.push(p); if (token.value == TOK.comma) { nextToken(); continue; } break; } if (token.value != TOK.semicolon) { error("missing `; expression` before `)` of `foreach`"); nextToken(); if (lastai && parameters.length >= 2) { errorSupplemental(loc, "perhaps the `;` goes before `%s`", lastai.toChars()); } return null; } nextToken(); AST.Expression aggr = parseExpression(); if (token.value == TOK.slice && parameters.dim == 1) { AST.Parameter p = (*parameters)[0]; nextToken(); AST.Expression upr = parseExpression(); check(TOK.rightParenthesis); Loc endloc; static if (is(Foreach == AST.Statement) || is(Foreach == AST.StaticForeachStatement)) { AST.Statement _body = parseStatement(0, null, &endloc); } else { AST.Statement _body = null; } auto rangefe = new AST.ForeachRangeStatement(loc, op, p, aggr, upr, _body, endloc); static if (is(Foreach == AST.Statement)) { return rangefe; } else static if(is(Foreach == AST.StaticForeachDeclaration)) { return new AST.StaticForeachDeclaration(new AST.StaticForeach(loc, null, rangefe), parseBlock(pLastDecl)); } else static if (is(Foreach == AST.StaticForeachStatement)) { return new AST.StaticForeachStatement(loc, new AST.StaticForeach(loc, null, rangefe)); } } else { check(TOK.rightParenthesis); Loc endloc; static if (is(Foreach == AST.Statement) || is(Foreach == AST.StaticForeachStatement)) { AST.Statement _body = parseStatement(0, null, &endloc); } else { AST.Statement _body = null; } auto aggrfe = new AST.ForeachStatement(loc, op, parameters, aggr, _body, endloc); static if (is(Foreach == AST.Statement)) { return aggrfe; } else static if(is(Foreach == AST.StaticForeachDeclaration)) { return new AST.StaticForeachDeclaration(new AST.StaticForeach(loc, aggrfe, null), parseBlock(pLastDecl)); } else static if (is(Foreach == AST.StaticForeachStatement)) { return new AST.StaticForeachStatement(loc, new AST.StaticForeach(loc, aggrfe, null)); } } } /*** * Parse an assignment condition for if or while statements. * * Returns: * The variable that is declared inside the condition */ AST.Parameter parseAssignCondition() { AST.Parameter param = null; StorageClass storageClass = 0; StorageClass stc = 0; LagainStc: if (stc) { storageClass = appendStorageClass(storageClass, stc); nextToken(); } switch (token.value) { case TOK.ref_: stc = STC.ref_; goto LagainStc; case TOK.scope_: stc = STC.scope_; goto LagainStc; case TOK.auto_: stc = STC.auto_; goto LagainStc; case TOK.const_: if (peekNext() != TOK.leftParenthesis) { stc = STC.const_; goto LagainStc; } break; case TOK.immutable_: if (peekNext() != TOK.leftParenthesis) { stc = STC.immutable_; goto LagainStc; } break; case TOK.shared_: if (peekNext() != TOK.leftParenthesis) { stc = STC.shared_; goto LagainStc; } break; case TOK.inout_: if (peekNext() != TOK.leftParenthesis) { stc = STC.wild; goto LagainStc; } break; default: break; } auto n = peek(&token); if (storageClass != 0 && token.value == TOK.identifier && n.value == TOK.assign) { Identifier ai = token.ident; AST.Type at = null; // infer parameter type nextToken(); check(TOK.assign); param = new AST.Parameter(storageClass, at, ai, null, null); } else if (isDeclaration(&token, NeedDeclaratorId.must, TOK.assign, null)) { Identifier ai; AST.Type at = parseType(&ai); check(TOK.assign); param = new AST.Parameter(storageClass, at, ai, null, null); } else if (storageClass != 0) error("found `%s` while expecting `=` or identifier", n.toChars()); return param; } /***************************************** * Input: * flags PSxxxx * Output: * pEndloc if { ... statements ... }, store location of closing brace, otherwise loc of last token of statement */ AST.Statement parseStatement(int flags, const(char)** endPtr = null, Loc* pEndloc = null) { AST.Statement s; AST.Condition cond; AST.Statement ifbody; AST.Statement elsebody; bool isfinal; const loc = token.loc; //printf("parseStatement()\n"); if (flags & ParseStatementFlags.curly && token.value != TOK.leftCurly) error("statement expected to be `{ }`, not `%s`", token.toChars()); switch (token.value) { case TOK.identifier: { /* A leading identifier can be a declaration, label, or expression. * The easiest case to check first is label: */ if (peekNext() == TOK.colonColon) { // skip ident:: nextToken(); nextToken(); error("use `.` for member lookup, not `::`"); break; } if (peekNext() == TOK.colon) { // It's a label Identifier ident = token.ident; nextToken(); nextToken(); if (token.value == TOK.rightCurly) s = null; else if (token.value == TOK.leftCurly) s = parseStatement(ParseStatementFlags.curly | ParseStatementFlags.scope_); else s = parseStatement(ParseStatementFlags.semiOk); s = new AST.LabelStatement(loc, ident, s); break; } goto case TOK.dot; } case TOK.dot: case TOK.typeof_: case TOK.vector: case TOK.traits: /* https://issues.dlang.org/show_bug.cgi?id=15163 * If tokens can be handled as * old C-style declaration or D expression, prefer the latter. */ if (isDeclaration(&token, NeedDeclaratorId.mustIfDstyle, TOK.reserved, null)) goto Ldeclaration; goto Lexp; case TOK.assert_: case TOK.this_: case TOK.super_: case TOK.int32Literal: case TOK.uns32Literal: case TOK.int64Literal: case TOK.uns64Literal: case TOK.int128Literal: case TOK.uns128Literal: case TOK.float32Literal: case TOK.float64Literal: case TOK.float80Literal: case TOK.imaginary32Literal: case TOK.imaginary64Literal: case TOK.imaginary80Literal: case TOK.charLiteral: case TOK.wcharLiteral: case TOK.dcharLiteral: case TOK.null_: case TOK.true_: case TOK.false_: case TOK.string_: case TOK.leftParenthesis: case TOK.cast_: case TOK.mul: case TOK.min: case TOK.add: case TOK.tilde: case TOK.not: case TOK.plusPlus: case TOK.minusMinus: case TOK.new_: case TOK.delete_: case TOK.delegate_: case TOK.function_: case TOK.typeid_: case TOK.is_: case TOK.leftBracket: case TOK.file: case TOK.fileFullPath: case TOK.line: case TOK.moduleString: case TOK.functionString: case TOK.prettyFunction: Lexp: { AST.Expression exp = parseExpression(); /* https://issues.dlang.org/show_bug.cgi?id=15103 * Improve declaration / initialization syntax error message * Error: found 'foo' when expecting ';' following statement * becomes Error: found `(` when expecting `;` or `=`, did you mean `Foo foo = 42`? */ if (token.value == TOK.identifier && exp.op == EXP.identifier) { error("found `%s` when expecting `;` or `=`, did you mean `%s %s = %s`?", peek(&token).toChars(), exp.toChars(), token.toChars(), peek(peek(&token)).toChars()); nextToken(); } else { /* * https://issues.dlang.org/show_bug.cgi?id=22529 * Avoid empty declaration error in case of missing semicolon * followed by another token and another semicolon. E.g.: * * foo() * return; * * When the missing `;` error is emitted, token is sitting on return. * If we simply use `check` to emit the error, the token is advanced * to `;` and the empty statement error would follow. To avoid that, * we check if the next token is a semicolon and simply output the error, * otherwise we fall back on the old path (advancing the token). */ if (token.value != TOK.semicolon && peek(&token).value == TOK.semicolon) error("found `%s` when expecting `;` following statement", token.toChars()); else check(TOK.semicolon, "statement"); } s = new AST.ExpStatement(loc, exp); break; } case TOK.static_: { // Look ahead to see if it's static assert() or static if() const tv = peekNext(); if (tv == TOK.assert_) { s = new AST.StaticAssertStatement(parseStaticAssert()); break; } if (tv == TOK.if_) { cond = parseStaticIfCondition(); goto Lcondition; } if (tv == TOK.foreach_ || tv == TOK.foreach_reverse_) { s = parseForeach!(AST.StaticForeachStatement)(loc, null); if (flags & ParseStatementFlags.scope_) s = new AST.ScopeStatement(loc, s, token.loc); break; } if (tv == TOK.import_) { AST.Dsymbols* imports = parseImport(); s = new AST.ImportStatement(loc, imports); if (flags & ParseStatementFlags.scope_) s = new AST.ScopeStatement(loc, s, token.loc); break; } goto Ldeclaration; } case TOK.final_: if (peekNext() == TOK.switch_) { nextToken(); isfinal = true; goto Lswitch; } goto Ldeclaration; case TOK.wchar_: case TOK.dchar_: case TOK.bool_: case TOK.char_: case TOK.int8: case TOK.uns8: case TOK.int16: case TOK.uns16: case TOK.int32: case TOK.uns32: case TOK.int64: case TOK.uns64: case TOK.int128: case TOK.uns128: case TOK.float32: case TOK.float64: case TOK.float80: case TOK.imaginary32: case TOK.imaginary64: case TOK.imaginary80: case TOK.complex32: case TOK.complex64: case TOK.complex80: case TOK.void_: // bug 7773: int.max is always a part of expression if (peekNext() == TOK.dot) goto Lexp; if (peekNext() == TOK.leftParenthesis) goto Lexp; goto case; case TOK.alias_: case TOK.const_: case TOK.auto_: case TOK.abstract_: case TOK.extern_: case TOK.align_: case TOK.immutable_: case TOK.shared_: case TOK.inout_: case TOK.deprecated_: case TOK.nothrow_: case TOK.pure_: case TOK.ref_: case TOK.gshared: case TOK.at: case TOK.struct_: case TOK.union_: case TOK.class_: case TOK.interface_: Ldeclaration: { AST.Dsymbols* a = parseDeclarations(false, null, null); if (a.dim > 1) { auto as = new AST.Statements(); as.reserve(a.dim); foreach (i; 0 .. a.dim) { AST.Dsymbol d = (*a)[i]; s = new AST.ExpStatement(loc, d); as.push(s); } s = new AST.CompoundDeclarationStatement(loc, as); } else if (a.dim == 1) { AST.Dsymbol d = (*a)[0]; s = new AST.ExpStatement(loc, d); } else s = new AST.ExpStatement(loc, cast(AST.Expression)null); if (flags & ParseStatementFlags.scope_) s = new AST.ScopeStatement(loc, s, token.loc); break; } case TOK.enum_: { /* Determine if this is a manifest constant declaration, * or a conventional enum. */ AST.Dsymbol d; const tv = peekNext(); if (tv == TOK.leftCurly || tv == TOK.colon) d = parseEnum(); else if (tv != TOK.identifier) goto Ldeclaration; else { const nextv = peekNext2(); if (nextv == TOK.leftCurly || nextv == TOK.colon || nextv == TOK.semicolon) d = parseEnum(); else goto Ldeclaration; } s = new AST.ExpStatement(loc, d); if (flags & ParseStatementFlags.scope_) s = new AST.ScopeStatement(loc, s, token.loc); break; } case TOK.mixin_: { if (isDeclaration(&token, NeedDeclaratorId.mustIfDstyle, TOK.reserved, null)) goto Ldeclaration; if (peekNext() == TOK.leftParenthesis) { // mixin(string) AST.Expression e = parseAssignExp(); check(TOK.semicolon); if (e.op == EXP.mixin_) { AST.MixinExp cpe = cast(AST.MixinExp)e; s = new AST.CompileStatement(loc, cpe.exps); } else { s = new AST.ExpStatement(loc, e); } break; } AST.Dsymbol d = parseMixin(); s = new AST.ExpStatement(loc, d); if (flags & ParseStatementFlags.scope_) s = new AST.ScopeStatement(loc, s, token.loc); break; } case TOK.leftCurly: { const lookingForElseSave = lookingForElse; lookingForElse = Loc.initial; nextToken(); //if (token.value == TOK.semicolon) // error("use `{ }` for an empty statement, not `;`"); auto statements = new AST.Statements(); while (token.value != TOK.rightCurly && token.value != TOK.endOfFile) { statements.push(parseStatement(ParseStatementFlags.semi | ParseStatementFlags.curlyScope)); } if (endPtr) *endPtr = token.ptr; endloc = token.loc; if (pEndloc) { *pEndloc = token.loc; pEndloc = null; // don't set it again } s = new AST.CompoundStatement(loc, statements); if (flags & (ParseStatementFlags.scope_ | ParseStatementFlags.curlyScope)) s = new AST.ScopeStatement(loc, s, token.loc); check(TOK.rightCurly, "compound statement"); lookingForElse = lookingForElseSave; break; } case TOK.while_: { AST.Parameter param = null; nextToken(); check(TOK.leftParenthesis); param = parseAssignCondition(); AST.Expression condition = parseExpression(); check(TOK.rightParenthesis); Loc endloc; AST.Statement _body = parseStatement(ParseStatementFlags.scope_, null, &endloc); s = new AST.WhileStatement(loc, condition, _body, endloc, param); break; } case TOK.semicolon: if (!(flags & ParseStatementFlags.semiOk)) { if (flags & ParseStatementFlags.semi) deprecation("use `{ }` for an empty statement, not `;`"); else error("use `{ }` for an empty statement, not `;`"); } nextToken(); s = new AST.ExpStatement(loc, cast(AST.Expression)null); break; case TOK.do_: { AST.Statement _body; AST.Expression condition; nextToken(); const lookingForElseSave = lookingForElse; lookingForElse = Loc.initial; _body = parseStatement(ParseStatementFlags.scope_); lookingForElse = lookingForElseSave; check(TOK.while_); check(TOK.leftParenthesis); condition = parseExpression(); check(TOK.rightParenthesis); if (token.value == TOK.semicolon) nextToken(); else error("terminating `;` required after do-while statement"); s = new AST.DoStatement(loc, _body, condition, token.loc); break; } case TOK.for_: { AST.Statement _init; AST.Expression condition; AST.Expression increment; nextToken(); check(TOK.leftParenthesis); if (token.value == TOK.semicolon) { _init = null; nextToken(); } else { const lookingForElseSave = lookingForElse; lookingForElse = Loc.initial; _init = parseStatement(0); lookingForElse = lookingForElseSave; } if (token.value == TOK.semicolon) { condition = null; nextToken(); } else { condition = parseExpression(); check(TOK.semicolon, "`for` condition"); } if (token.value == TOK.rightParenthesis) { increment = null; nextToken(); } else { increment = parseExpression(); check(TOK.rightParenthesis); } Loc endloc; AST.Statement _body = parseStatement(ParseStatementFlags.scope_, null, &endloc); s = new AST.ForStatement(loc, _init, condition, increment, _body, endloc); break; } case TOK.foreach_: case TOK.foreach_reverse_: { s = parseForeach!(AST.Statement)(loc, null); break; } case TOK.if_: { AST.Parameter param = null; AST.Expression condition; nextToken(); check(TOK.leftParenthesis); param = parseAssignCondition(); condition = parseExpression(); if (token.value != TOK.rightParenthesis && condition) { error("missing closing `)` after `if (%s`", param ? "declaration".ptr : condition.toChars()); } else check(TOK.rightParenthesis); if (token.value == TOK.rightParenthesis) { if (condition) // if not an error in condition error("extra `)` after `if (%s)`", param ? "declaration".ptr : condition.toChars()); nextToken(); } { const lookingForElseSave = lookingForElse; lookingForElse = loc; ifbody = parseStatement(ParseStatementFlags.scope_); lookingForElse = lookingForElseSave; } if (token.value == TOK.else_) { const elseloc = token.loc; nextToken(); elsebody = parseStatement(ParseStatementFlags.scope_); checkDanglingElse(elseloc); } else elsebody = null; if (condition && ifbody) s = new AST.IfStatement(loc, param, condition, ifbody, elsebody, token.loc); else s = null; // don't propagate parsing errors break; } case TOK.else_: error("found `else` without a corresponding `if`, `version` or `debug` statement"); goto Lerror; case TOK.scope_: if (peekNext() != TOK.leftParenthesis) goto Ldeclaration; // scope used as storage class nextToken(); check(TOK.leftParenthesis); if (token.value != TOK.identifier) { error("scope identifier expected"); goto Lerror; } else { TOK t = TOK.onScopeExit; Identifier id = token.ident; if (id == Id.exit) t = TOK.onScopeExit; else if (id == Id.failure) t = TOK.onScopeFailure; else if (id == Id.success) t = TOK.onScopeSuccess; else error("valid scope identifiers are `exit`, `failure`, or `success`, not `%s`", id.toChars()); nextToken(); check(TOK.rightParenthesis); AST.Statement st = parseStatement(ParseStatementFlags.scope_); s = new AST.ScopeGuardStatement(loc, t, st); break; } case TOK.debug_: nextToken(); if (token.value == TOK.assign) { if (auto ds = parseDebugSpecification()) { if (ds.ident) ds.error("declaration must be at module level"); else ds.error("level declaration must be at module level"); } break; } cond = parseDebugCondition(); goto Lcondition; case TOK.version_: nextToken(); if (token.value == TOK.assign) { if (auto vs = parseVersionSpecification()) { if (vs.ident) vs.error("declaration must be at module level"); else vs.error("level declaration must be at module level"); } break; } cond = parseVersionCondition(); goto Lcondition; Lcondition: { const lookingForElseSave = lookingForElse; lookingForElse = loc; ifbody = parseStatement(0); lookingForElse = lookingForElseSave; } elsebody = null; if (token.value == TOK.else_) { const elseloc = token.loc; nextToken(); elsebody = parseStatement(0); checkDanglingElse(elseloc); } s = new AST.ConditionalStatement(loc, cond, ifbody, elsebody); if (flags & ParseStatementFlags.scope_) s = new AST.ScopeStatement(loc, s, token.loc); break; case TOK.pragma_: { Identifier ident; AST.Expressions* args = null; AST.Statement _body; nextToken(); check(TOK.leftParenthesis); if (token.value != TOK.identifier) { error("`pragma(identifier)` expected"); goto Lerror; } ident = token.ident; nextToken(); if (token.value == TOK.comma && peekNext() != TOK.rightParenthesis) args = parseArguments(); // pragma(identifier, args...); else check(TOK.rightParenthesis); // pragma(identifier); if (token.value == TOK.semicolon) { nextToken(); _body = null; } else _body = parseStatement(ParseStatementFlags.semi); s = new AST.PragmaStatement(loc, ident, args, _body); break; } case TOK.switch_: isfinal = false; goto Lswitch; Lswitch: { nextToken(); check(TOK.leftParenthesis); AST.Expression condition = parseExpression(); check(TOK.rightParenthesis); AST.Statement _body = parseStatement(ParseStatementFlags.scope_); s = new AST.SwitchStatement(loc, condition, _body, isfinal); break; } case TOK.case_: { AST.Expression exp; AST.Expressions cases; // array of Expression's AST.Expression last = null; nextToken(); do { exp = parseAssignExp(); cases.push(exp); if (token.value != TOK.comma) break; nextToken(); //comma } while (token.value != TOK.colon && token.value != TOK.endOfFile); check(TOK.colon); /* case exp: .. case last: */ if (token.value == TOK.slice) { if (cases.dim > 1) error("only one `case` allowed for start of case range"); nextToken(); check(TOK.case_); last = parseAssignExp(); check(TOK.colon); } if (flags & ParseStatementFlags.curlyScope) { auto statements = new AST.Statements(); while (token.value != TOK.case_ && token.value != TOK.default_ && token.value != TOK.endOfFile && token.value != TOK.rightCurly) { auto cur = parseStatement(ParseStatementFlags.semi | ParseStatementFlags.curlyScope); statements.push(cur); // https://issues.dlang.org/show_bug.cgi?id=21739 // Stop at the last break s.t. the following non-case statements are // not merged into the current case. This can happen for // case 1: ... break; // debug { case 2: ... } if (cur && cur.isBreakStatement()) break; } s = new AST.CompoundStatement(loc, statements); } else { s = parseStatement(ParseStatementFlags.semi); } s = new AST.ScopeStatement(loc, s, token.loc); if (last) { s = new AST.CaseRangeStatement(loc, exp, last, s); } else { // Keep cases in order by building the case statements backwards for (size_t i = cases.dim; i; i--) { exp = cases[i - 1]; s = new AST.CaseStatement(loc, exp, s); } } break; } case TOK.default_: { nextToken(); check(TOK.colon); if (flags & ParseStatementFlags.curlyScope) { auto statements = new AST.Statements(); while (token.value != TOK.case_ && token.value != TOK.default_ && token.value != TOK.endOfFile && token.value != TOK.rightCurly) { statements.push(parseStatement(ParseStatementFlags.semi | ParseStatementFlags.curlyScope)); } s = new AST.CompoundStatement(loc, statements); } else s = parseStatement(ParseStatementFlags.semi); s = new AST.ScopeStatement(loc, s, token.loc); s = new AST.DefaultStatement(loc, s); break; } case TOK.return_: { AST.Expression exp; nextToken(); exp = token.value == TOK.semicolon ? null : parseExpression(); check(TOK.semicolon, "`return` statement"); s = new AST.ReturnStatement(loc, exp); break; } case TOK.break_: { Identifier ident; nextToken(); ident = null; if (token.value == TOK.identifier) { ident = token.ident; nextToken(); } check(TOK.semicolon, "`break` statement"); s = new AST.BreakStatement(loc, ident); break; } case TOK.continue_: { Identifier ident; nextToken(); ident = null; if (token.value == TOK.identifier) { ident = token.ident; nextToken(); } check(TOK.semicolon, "`continue` statement"); s = new AST.ContinueStatement(loc, ident); break; } case TOK.goto_: { Identifier ident; nextToken(); if (token.value == TOK.default_) { nextToken(); s = new AST.GotoDefaultStatement(loc); } else if (token.value == TOK.case_) { AST.Expression exp = null; nextToken(); if (token.value != TOK.semicolon) exp = parseExpression(); s = new AST.GotoCaseStatement(loc, exp); } else { if (token.value != TOK.identifier) { error("identifier expected following `goto`"); ident = null; } else { ident = token.ident; nextToken(); } s = new AST.GotoStatement(loc, ident); } check(TOK.semicolon, "`goto` statement"); break; } case TOK.synchronized_: { AST.Expression exp; AST.Statement _body; Token* t = peek(&token); if (skipAttributes(t, &t) && t.value == TOK.class_) goto Ldeclaration; nextToken(); if (token.value == TOK.leftParenthesis) { nextToken(); exp = parseExpression(); check(TOK.rightParenthesis); } else exp = null; _body = parseStatement(ParseStatementFlags.scope_); s = new AST.SynchronizedStatement(loc, exp, _body); break; } case TOK.with_: { AST.Expression exp; AST.Statement _body; Loc endloc = loc; nextToken(); check(TOK.leftParenthesis); exp = parseExpression(); check(TOK.rightParenthesis); _body = parseStatement(ParseStatementFlags.scope_, null, &endloc); s = new AST.WithStatement(loc, exp, _body, endloc); break; } case TOK.try_: { AST.Statement _body; AST.Catches* catches = null; AST.Statement finalbody = null; nextToken(); const lookingForElseSave = lookingForElse; lookingForElse = Loc.initial; _body = parseStatement(ParseStatementFlags.scope_); lookingForElse = lookingForElseSave; while (token.value == TOK.catch_) { AST.Statement handler; AST.Catch c; AST.Type t; Identifier id; const catchloc = token.loc; nextToken(); if (token.value != TOK.leftParenthesis) { deprecation("`catch` statement without an exception specification is deprecated"); deprecationSupplemental(token.loc, "use `catch(Throwable)` for old behavior"); t = null; id = null; } else { check(TOK.leftParenthesis); id = null; t = parseType(&id); check(TOK.rightParenthesis); } handler = parseStatement(0); c = new AST.Catch(catchloc, t, id, handler); if (!catches) catches = new AST.Catches(); catches.push(c); } if (token.value == TOK.finally_) { nextToken(); finalbody = parseStatement(ParseStatementFlags.scope_); } s = _body; if (!catches && !finalbody) error("`catch` or `finally` expected following `try`"); else { if (catches) s = new AST.TryCatchStatement(loc, _body, catches); if (finalbody) s = new AST.TryFinallyStatement(loc, s, finalbody); } break; } case TOK.throw_: { AST.Expression exp; nextToken(); exp = parseExpression(); check(TOK.semicolon, "`throw` statement"); s = new AST.ThrowStatement(loc, exp); break; } case TOK.asm_: s = parseAsm(); break; case TOK.import_: { /* https://issues.dlang.org/show_bug.cgi?id=16088 * * At this point it can either be an * https://dlang.org/spec/grammar.html#ImportExpression * or an * https://dlang.org/spec/grammar.html#ImportDeclaration. * See if the next token after `import` is a `(`; if so, * then it is an import expression. */ if (peekNext() == TOK.leftParenthesis) { AST.Expression e = parseExpression(); check(TOK.semicolon); s = new AST.ExpStatement(loc, e); } else { AST.Dsymbols* imports = parseImport(); s = new AST.ImportStatement(loc, imports); if (flags & ParseStatementFlags.scope_) s = new AST.ScopeStatement(loc, s, token.loc); } break; } case TOK.template_: { AST.Dsymbol d = parseTemplateDeclaration(); s = new AST.ExpStatement(loc, d); break; } default: error("found `%s` instead of statement", token.toChars()); goto Lerror; Lerror: while (token.value != TOK.rightCurly && token.value != TOK.semicolon && token.value != TOK.endOfFile) nextToken(); if (token.value == TOK.semicolon) nextToken(); s = null; break; } if (pEndloc) *pEndloc = prevloc; return s; } private AST.ExpInitializer parseExpInitializer(Loc loc) { auto ae = parseAssignExp(); return new AST.ExpInitializer(loc, ae); } private AST.Initializer parseStructInitializer(Loc loc) { /* Scan ahead to discern between a struct initializer and * parameterless function literal. * * We'll scan the topmost curly bracket level for statement-related * tokens, thereby ruling out a struct initializer. (A struct * initializer which itself contains function literals may have * statements at nested curly bracket levels.) * * It's important that this function literal check not be * pendantic, otherwise a function having the slightest syntax * error would emit confusing errors when we proceed to parse it * as a struct initializer. * * The following two ambiguous cases will be treated as a struct * initializer (best we can do without type info): * {} * {{statements...}} - i.e. it could be struct initializer * with one function literal, or function literal having an * extra level of curly brackets * If a function literal is intended in these cases (unlikely), * source can use a more explicit function literal syntax * (e.g. prefix with "()" for empty parameter list). */ int braces = 1; int parens = 0; for (auto t = peek(&token); 1; t = peek(t)) { switch (t.value) { case TOK.leftParenthesis: parens++; continue; case TOK.rightParenthesis: parens--; continue; // https://issues.dlang.org/show_bug.cgi?id=21163 // lambda params can have the `scope` storage class, e.g // `S s = { (scope Type Id){} }` case TOK.scope_: if (!parens) goto case; continue; /* Look for a semicolon or keyword of statements which don't * require a semicolon (typically containing BlockStatement). * Tokens like "else", "catch", etc. are omitted where the * leading token of the statement is sufficient. */ case TOK.asm_: case TOK.class_: case TOK.debug_: case TOK.enum_: case TOK.if_: case TOK.interface_: case TOK.pragma_: case TOK.semicolon: case TOK.struct_: case TOK.switch_: case TOK.synchronized_: case TOK.try_: case TOK.union_: case TOK.version_: case TOK.while_: case TOK.with_: if (braces == 1) return parseExpInitializer(loc); continue; case TOK.leftCurly: braces++; continue; case TOK.rightCurly: if (--braces == 0) break; continue; case TOK.endOfFile: break; default: continue; } break; } auto _is = new AST.StructInitializer(loc); bool commaExpected = false; nextToken(); while (1) { switch (token.value) { case TOK.identifier: { if (commaExpected) error("comma expected separating field initializers"); const t = peek(&token); Identifier id; if (t.value == TOK.colon) { id = token.ident; nextToken(); nextToken(); // skip over ':' } auto value = parseInitializer(); _is.addInit(id, value); commaExpected = true; continue; } case TOK.comma: if (!commaExpected) error("expression expected, not `,`"); nextToken(); commaExpected = false; continue; case TOK.rightCurly: // allow trailing comma's nextToken(); break; case TOK.endOfFile: error("found end of file instead of initializer"); break; default: if (commaExpected) error("comma expected separating field initializers"); auto value = parseInitializer(); _is.addInit(null, value); commaExpected = true; continue; } break; } return _is; } /***************************************** * Parse initializer for variable declaration. */ private AST.Initializer parseInitializer() { const loc = token.loc; switch (token.value) { case TOK.leftCurly: return parseStructInitializer(loc); case TOK.leftBracket: /* Scan ahead to see if it is an array initializer or * an expression. * If it ends with a ';' ',' or '}', it is an array initializer. */ int brackets = 1; for (auto t = peek(&token); 1; t = peek(t)) { switch (t.value) { case TOK.leftBracket: brackets++; continue; case TOK.rightBracket: if (--brackets == 0) { t = peek(t); if (t.value != TOK.semicolon && t.value != TOK.comma && t.value != TOK.rightBracket && t.value != TOK.rightCurly) return parseExpInitializer(loc); break; } continue; case TOK.endOfFile: break; default: continue; } break; } auto ia = new AST.ArrayInitializer(loc); bool commaExpected = false; nextToken(); while (1) { switch (token.value) { default: if (commaExpected) { error("comma expected separating array initializers, not `%s`", token.toChars()); nextToken(); break; } auto e = parseAssignExp(); if (!e) break; AST.Initializer value; if (token.value == TOK.colon) { nextToken(); value = parseInitializer(); } else { value = new AST.ExpInitializer(e.loc, e); e = null; } ia.addInit(e, value); commaExpected = true; continue; case TOK.leftCurly: case TOK.leftBracket: if (commaExpected) error("comma expected separating array initializers, not `%s`", token.toChars()); auto value = parseInitializer(); AST.Expression e; if (token.value == TOK.colon) { nextToken(); if (auto ei = value.isExpInitializer()) { e = ei.exp; value = parseInitializer(); } else error("initializer expression expected following colon, not `%s`", token.toChars()); } ia.addInit(e, value); commaExpected = true; continue; case TOK.comma: if (!commaExpected) error("expression expected, not `,`"); nextToken(); commaExpected = false; continue; case TOK.rightBracket: // allow trailing comma's nextToken(); break; case TOK.endOfFile: error("found `%s` instead of array initializer", token.toChars()); break; } break; } return ia; case TOK.void_: const tv = peekNext(); if (tv == TOK.semicolon || tv == TOK.comma) { nextToken(); return new AST.VoidInitializer(loc); } return parseExpInitializer(loc); default: return parseExpInitializer(loc); } } /***************************************** * Parses default argument initializer expression that is an assign expression, * with special handling for __FILE__, __FILE_DIR__, __LINE__, __MODULE__, __FUNCTION__, and __PRETTY_FUNCTION__. */ private AST.Expression parseDefaultInitExp() { AST.Expression e = null; const tv = peekNext(); if (tv == TOK.comma || tv == TOK.rightParenthesis) { switch (token.value) { case TOK.file: e = new AST.FileInitExp(token.loc, EXP.file); break; case TOK.fileFullPath: e = new AST.FileInitExp(token.loc, EXP.fileFullPath); break; case TOK.line: e = new AST.LineInitExp(token.loc); break; case TOK.moduleString: e = new AST.ModuleInitExp(token.loc); break; case TOK.functionString: e = new AST.FuncInitExp(token.loc); break; case TOK.prettyFunction: e = new AST.PrettyFuncInitExp(token.loc); break; default: goto LExp; } nextToken(); return e; } LExp: return parseAssignExp(); } /******************** * Parse inline assembler block. * Returns: * inline assembler block as a Statement */ AST.Statement parseAsm() { // Parse the asm block into a sequence of AsmStatements, // each AsmStatement is one instruction. // Separate out labels. // Defer parsing of AsmStatements until semantic processing. const loc = token.loc; Loc labelloc; nextToken(); StorageClass stc = parsePostfix(STC.undefined_, null); if (stc & (STC.const_ | STC.immutable_ | STC.shared_ | STC.wild)) error("`const`/`immutable`/`shared`/`inout` attributes are not allowed on `asm` blocks"); check(TOK.leftCurly); Token* toklist = null; Token** ptoklist = &toklist; Identifier label = null; auto statements = new AST.Statements(); size_t nestlevel = 0; while (1) { switch (token.value) { case TOK.identifier: if (!toklist) { // Look ahead to see if it is a label if (peekNext() == TOK.colon) { // It's a label label = token.ident; labelloc = token.loc; nextToken(); nextToken(); continue; } } goto default; case TOK.leftCurly: ++nestlevel; goto default; case TOK.rightCurly: if (nestlevel > 0) { --nestlevel; goto default; } if (toklist || label) { error("`asm` statements must end in `;`"); } break; case TOK.semicolon: if (nestlevel != 0) error("mismatched number of curly brackets"); if (toklist || label) { // Create AsmStatement from list of tokens we've saved AST.Statement s = new AST.AsmStatement(token.loc, toklist); toklist = null; ptoklist = &toklist; if (label) { s = new AST.LabelStatement(labelloc, label, s); label = null; } statements.push(s); } nextToken(); continue; case TOK.endOfFile: /* { */ error("matching `}` expected, not end of file"); break; case TOK.colonColon: // treat as two separate : tokens for iasmgcc *ptoklist = allocateToken(); memcpy(*ptoklist, &token, Token.sizeof); (*ptoklist).value = TOK.colon; ptoklist = &(*ptoklist).next; *ptoklist = allocateToken(); memcpy(*ptoklist, &token, Token.sizeof); (*ptoklist).value = TOK.colon; ptoklist = &(*ptoklist).next; *ptoklist = null; nextToken(); continue; default: *ptoklist = allocateToken(); memcpy(*ptoklist, &token, Token.sizeof); ptoklist = &(*ptoklist).next; *ptoklist = null; nextToken(); continue; } break; } nextToken(); auto s = new AST.CompoundAsmStatement(loc, statements, stc); return s; } /********************************** * Issue error if the current token is not `value`, * advance to next token. * Params: * loc = location for error message * value = token value to compare with */ void check(Loc loc, TOK value) { if (token.value != value) error(loc, "found `%s` when expecting `%s`", token.toChars(), Token.toChars(value)); nextToken(); } /********************************** * Issue error if the current token is not `value`, * advance to next token. * Params: * value = token value to compare with */ void check(TOK value) { check(token.loc, value); } /********************************** * Issue error if the current token is not `value`, * advance to next token. * Params: * value = token value to compare with * string = for error message */ void check(TOK value, const(char)* string) { if (token.value != value) error("found `%s` when expecting `%s` following %s", token.toChars(), Token.toChars(value), string); nextToken(); } private void checkParens(TOK value, AST.Expression e) { if (precedence[e.op] == PREC.rel && !e.parens) error(e.loc, "`%s` must be surrounded by parentheses when next to operator `%s`", e.toChars(), Token.toChars(value)); } /// enum NeedDeclaratorId { no, // Declarator part must have no identifier opt, // Declarator part identifier is optional must, // Declarator part must have identifier mustIfDstyle, // Declarator part must have identifier, but don't recognize old C-style syntax } /************************************ * Determine if the scanner is sitting on the start of a declaration. * Params: * t = current token of the scanner * needId = flag with additional requirements for a declaration * endtok = ending token * pt = will be set ending token (if not null) * Output: * true if the token `t` is a declaration, false otherwise */ private bool isDeclaration(Token* t, NeedDeclaratorId needId, TOK endtok, Token** pt) { //printf("isDeclaration(needId = %d)\n", needId); int haveId = 0; int haveTpl = 0; while (1) { if ((t.value == TOK.const_ || t.value == TOK.immutable_ || t.value == TOK.inout_ || t.value == TOK.shared_) && peek(t).value != TOK.leftParenthesis) { /* const type * immutable type * shared type * wild type */ t = peek(t); continue; } break; } if (!isBasicType(&t)) { goto Lisnot; } if (!isDeclarator(&t, &haveId, &haveTpl, endtok, needId != NeedDeclaratorId.mustIfDstyle)) goto Lisnot; if ((needId == NeedDeclaratorId.no && !haveId) || (needId == NeedDeclaratorId.opt) || (needId == NeedDeclaratorId.must && haveId) || (needId == NeedDeclaratorId.mustIfDstyle && haveId)) { if (pt) *pt = t; goto Lis; } goto Lisnot; Lis: //printf("\tis declaration, t = %s\n", t.toChars()); return true; Lisnot: //printf("\tis not declaration\n"); return false; } private bool isBasicType(Token** pt) { // This code parallels parseBasicType() Token* t = *pt; switch (t.value) { case TOK.wchar_: case TOK.dchar_: case TOK.bool_: case TOK.char_: case TOK.int8: case TOK.uns8: case TOK.int16: case TOK.uns16: case TOK.int32: case TOK.uns32: case TOK.int64: case TOK.uns64: case TOK.int128: case TOK.uns128: case TOK.float32: case TOK.float64: case TOK.float80: case TOK.imaginary32: case TOK.imaginary64: case TOK.imaginary80: case TOK.complex32: case TOK.complex64: case TOK.complex80: case TOK.void_: t = peek(t); break; case TOK.identifier: L5: t = peek(t); if (t.value == TOK.not) { goto L4; } goto L3; while (1) { L2: t = peek(t); L3: if (t.value == TOK.dot) { Ldot: t = peek(t); if (t.value != TOK.identifier) goto Lfalse; t = peek(t); if (t.value != TOK.not) goto L3; L4: /* Seen a ! * Look for: * !( args ), !identifier, etc. */ t = peek(t); switch (t.value) { case TOK.identifier: goto L5; case TOK.leftParenthesis: if (!skipParens(t, &t)) goto Lfalse; goto L3; case TOK.wchar_: case TOK.dchar_: case TOK.bool_: case TOK.char_: case TOK.int8: case TOK.uns8: case TOK.int16: case TOK.uns16: case TOK.int32: case TOK.uns32: case TOK.int64: case TOK.uns64: case TOK.int128: case TOK.uns128: case TOK.float32: case TOK.float64: case TOK.float80: case TOK.imaginary32: case TOK.imaginary64: case TOK.imaginary80: case TOK.complex32: case TOK.complex64: case TOK.complex80: case TOK.void_: case TOK.int32Literal: case TOK.uns32Literal: case TOK.int64Literal: case TOK.uns64Literal: case TOK.int128Literal: case TOK.uns128Literal: case TOK.float32Literal: case TOK.float64Literal: case TOK.float80Literal: case TOK.imaginary32Literal: case TOK.imaginary64Literal: case TOK.imaginary80Literal: case TOK.null_: case TOK.true_: case TOK.false_: case TOK.charLiteral: case TOK.wcharLiteral: case TOK.dcharLiteral: case TOK.string_: case TOK.file: case TOK.fileFullPath: case TOK.line: case TOK.moduleString: case TOK.functionString: case TOK.prettyFunction: goto L2; default: goto Lfalse; } } break; } break; case TOK.dot: goto Ldot; case TOK.typeof_: case TOK.vector: case TOK.mixin_: /* typeof(exp).identifier... */ t = peek(t); if (!skipParens(t, &t)) goto Lfalse; goto L3; case TOK.traits: // __traits(getMember t = peek(t); if (t.value != TOK.leftParenthesis) goto Lfalse; auto lp = t; t = peek(t); if (t.value != TOK.identifier || t.ident != Id.getMember) goto Lfalse; if (!skipParens(lp, &lp)) goto Lfalse; // we are in a lookup for decl VS statement // so we expect a declarator following __trait if it's a type. // other usages wont be ambiguous (alias, template instance, type qual, etc.) if (lp.value != TOK.identifier) goto Lfalse; break; case TOK.const_: case TOK.immutable_: case TOK.shared_: case TOK.inout_: // const(type) or immutable(type) or shared(type) or wild(type) t = peek(t); if (t.value != TOK.leftParenthesis) goto Lfalse; t = peek(t); if (!isDeclaration(t, NeedDeclaratorId.no, TOK.rightParenthesis, &t)) { goto Lfalse; } t = peek(t); break; default: goto Lfalse; } *pt = t; //printf("is\n"); return true; Lfalse: //printf("is not\n"); return false; } private bool isDeclarator(Token** pt, int* haveId, int* haveTpl, TOK endtok, bool allowAltSyntax = true) { // This code parallels parseDeclarator() Token* t = *pt; int parens; //printf("Parser::isDeclarator() %s\n", t.toChars()); if (t.value == TOK.assign) return false; while (1) { parens = false; switch (t.value) { case TOK.mul: //case TOK.and: t = peek(t); continue; case TOK.leftBracket: t = peek(t); if (t.value == TOK.rightBracket) { t = peek(t); } else if (isDeclaration(t, NeedDeclaratorId.no, TOK.rightBracket, &t)) { // It's an associative array declaration t = peek(t); // ...[type].ident if (t.value == TOK.dot && peek(t).value == TOK.identifier) { t = peek(t); t = peek(t); } } else { // [ expression ] // [ expression .. expression ] if (!isExpression(&t)) return false; if (t.value == TOK.slice) { t = peek(t); if (!isExpression(&t)) return false; if (t.value != TOK.rightBracket) return false; t = peek(t); } else { if (t.value != TOK.rightBracket) return false; t = peek(t); // ...[index].ident if (t.value == TOK.dot && peek(t).value == TOK.identifier) { t = peek(t); t = peek(t); } } } continue; case TOK.identifier: if (*haveId) return false; *haveId = true; t = peek(t); break; case TOK.leftParenthesis: if (!allowAltSyntax) return false; // Do not recognize C-style declarations. t = peek(t); if (t.value == TOK.rightParenthesis) return false; // () is not a declarator /* Regard ( identifier ) as not a declarator * BUG: what about ( *identifier ) in * f(*p)(x); * where f is a class instance with overloaded () ? * Should we just disallow C-style function pointer declarations? */ if (t.value == TOK.identifier) { Token* t2 = peek(t); if (t2.value == TOK.rightParenthesis) return false; } if (!isDeclarator(&t, haveId, null, TOK.rightParenthesis)) return false; t = peek(t); parens = true; break; case TOK.delegate_: case TOK.function_: t = peek(t); if (!isParameters(&t)) return false; skipAttributes(t, &t); continue; default: break; } break; } while (1) { switch (t.value) { static if (CARRAYDECL) { case TOK.leftBracket: parens = false; t = peek(t); if (t.value == TOK.rightBracket) { t = peek(t); } else if (isDeclaration(t, NeedDeclaratorId.no, TOK.rightBracket, &t)) { // It's an associative array declaration t = peek(t); } else { // [ expression ] if (!isExpression(&t)) return false; if (t.value != TOK.rightBracket) return false; t = peek(t); } continue; } case TOK.leftParenthesis: parens = false; if (Token* tk = peekPastParen(t)) { if (tk.value == TOK.leftParenthesis) { if (!haveTpl) return false; *haveTpl = 1; t = tk; } else if (tk.value == TOK.assign) { if (!haveTpl) return false; *haveTpl = 1; *pt = tk; return true; } } if (!isParameters(&t)) return false; while (1) { switch (t.value) { case TOK.const_: case TOK.immutable_: case TOK.shared_: case TOK.inout_: case TOK.pure_: case TOK.nothrow_: case TOK.return_: case TOK.scope_: t = peek(t); continue; case TOK.at: t = peek(t); // skip '@' t = peek(t); // skip identifier continue; default: break; } break; } continue; // Valid tokens that follow a declaration case TOK.rightParenthesis: case TOK.rightBracket: case TOK.assign: case TOK.comma: case TOK.dotDotDot: case TOK.semicolon: case TOK.leftCurly: case TOK.in_: case TOK.out_: case TOK.do_: // The !parens is to disallow unnecessary parentheses if (!parens && (endtok == TOK.reserved || endtok == t.value)) { *pt = t; return true; } return false; case TOK.identifier: if (t.ident == Id._body) { // @@@DEPRECATED_2.117@@@ // https://github.com/dlang/DIPs/blob/1f5959abe482b1f9094f6484a7d0a3ade77fc2fc/DIPs/accepted/DIP1003.md // Deprecated in 2.097 - Can be removed from 2.117 // The deprecation period is longer than usual as `body` // was quite widely used. deprecation("Usage of the `body` keyword is deprecated. Use `do` instead."); goto case TOK.do_; } goto default; case TOK.if_: return haveTpl ? true : false; // Used for mixin type parsing case TOK.endOfFile: if (endtok == TOK.endOfFile) goto case TOK.do_; return false; default: return false; } } assert(0); } private bool isParameters(Token** pt) { // This code parallels parseParameterList() Token* t = *pt; //printf("isParameters()\n"); if (t.value != TOK.leftParenthesis) return false; t = peek(t); for (; 1; t = peek(t)) { L1: switch (t.value) { case TOK.rightParenthesis: break; case TOK.at: Token* pastAttr; if (skipAttributes(t, &pastAttr)) { t = pastAttr; goto default; } break; case TOK.dotDotDot: t = peek(t); break; case TOK.in_: case TOK.out_: case TOK.ref_: case TOK.lazy_: case TOK.scope_: case TOK.final_: case TOK.auto_: case TOK.return_: continue; case TOK.const_: case TOK.immutable_: case TOK.shared_: case TOK.inout_: t = peek(t); if (t.value == TOK.leftParenthesis) { t = peek(t); if (!isDeclaration(t, NeedDeclaratorId.no, TOK.rightParenthesis, &t)) return false; t = peek(t); // skip past closing ')' goto L2; } goto L1; version (none) { case TOK.static_: continue; case TOK.auto_: case TOK.alias_: t = peek(t); if (t.value == TOK.identifier) t = peek(t); if (t.value == TOK.assign) { t = peek(t); if (!isExpression(&t)) return false; } goto L3; } default: { if (!isBasicType(&t)) return false; L2: int tmp = false; if (t.value != TOK.dotDotDot && !isDeclarator(&t, &tmp, null, TOK.reserved)) return false; if (t.value == TOK.assign) { t = peek(t); if (!isExpression(&t)) return false; } if (t.value == TOK.dotDotDot) { t = peek(t); break; } } if (t.value == TOK.comma) { continue; } break; } break; } if (t.value != TOK.rightParenthesis) return false; t = peek(t); *pt = t; return true; } private bool isExpression(Token** pt) { // This is supposed to determine if something is an expression. // What it actually does is scan until a closing right bracket // is found. Token* t = *pt; int brnest = 0; int panest = 0; int curlynest = 0; for (;; t = peek(t)) { switch (t.value) { case TOK.leftBracket: brnest++; continue; case TOK.rightBracket: if (--brnest >= 0) continue; break; case TOK.leftParenthesis: panest++; continue; case TOK.comma: if (brnest || panest) continue; break; case TOK.rightParenthesis: if (--panest >= 0) continue; break; case TOK.leftCurly: curlynest++; continue; case TOK.rightCurly: if (--curlynest >= 0) continue; return false; case TOK.slice: if (brnest) continue; break; case TOK.semicolon: if (curlynest) continue; return false; case TOK.endOfFile: return false; default: continue; } break; } *pt = t; return true; } /******************************************* * Skip parentheses. * Params: * t = on opening $(LPAREN) * pt = *pt is set to token past '$(RPAREN)' on true * Returns: * true successful * false some parsing error */ bool skipParens(Token* t, Token** pt) { if (t.value != TOK.leftParenthesis) return false; int parens = 0; while (1) { switch (t.value) { case TOK.leftParenthesis: parens++; break; case TOK.rightParenthesis: parens--; if (parens < 0) goto Lfalse; if (parens == 0) goto Ldone; break; case TOK.endOfFile: goto Lfalse; default: break; } t = peek(t); } Ldone: if (pt) *pt = peek(t); // skip found rparen return true; Lfalse: return false; } private bool skipParensIf(Token* t, Token** pt) { if (t.value != TOK.leftParenthesis) { if (pt) *pt = t; return true; } return skipParens(t, pt); } //returns true if the next value (after optional matching parens) is expected private bool hasOptionalParensThen(Token* t, TOK expected) { Token* tk; if (!skipParensIf(t, &tk)) return false; return tk.value == expected; } /******************************************* * Skip attributes. * Input: * t is on a candidate attribute * Output: * *pt is set to first non-attribute token on success * Returns: * true successful * false some parsing error */ private bool skipAttributes(Token* t, Token** pt) { while (1) { switch (t.value) { case TOK.const_: case TOK.immutable_: case TOK.shared_: case TOK.inout_: case TOK.final_: case TOK.auto_: case TOK.scope_: case TOK.override_: case TOK.abstract_: case TOK.synchronized_: break; case TOK.deprecated_: if (peek(t).value == TOK.leftParenthesis) { t = peek(t); if (!skipParens(t, &t)) goto Lerror; // t is on the next of closing parenthesis continue; } break; case TOK.nothrow_: case TOK.pure_: case TOK.ref_: case TOK.gshared: case TOK.return_: break; case TOK.at: t = peek(t); if (t.value == TOK.identifier) { /* @identifier * @identifier!arg * @identifier!(arglist) * any of the above followed by (arglist) * @predefined_attribute */ if (isBuiltinAtAttribute(t.ident)) break; t = peek(t); if (t.value == TOK.not) { t = peek(t); if (t.value == TOK.leftParenthesis) { // @identifier!(arglist) if (!skipParens(t, &t)) goto Lerror; // t is on the next of closing parenthesis } else { // @identifier!arg // Do low rent skipTemplateArgument if (t.value == TOK.vector) { // identifier!__vector(type) t = peek(t); if (!skipParens(t, &t)) goto Lerror; } else t = peek(t); } } if (t.value == TOK.leftParenthesis) { if (!skipParens(t, &t)) goto Lerror; // t is on the next of closing parenthesis continue; } continue; } if (t.value == TOK.leftParenthesis) { // @( ArgumentList ) if (!skipParens(t, &t)) goto Lerror; // t is on the next of closing parenthesis continue; } goto Lerror; default: goto Ldone; } t = peek(t); } Ldone: if (pt) *pt = t; return true; Lerror: return false; } AST.Expression parseExpression() { auto loc = token.loc; //printf("Parser::parseExpression() loc = %d\n", loc.linnum); auto e = parseAssignExp(); while (token.value == TOK.comma) { nextToken(); auto e2 = parseAssignExp(); e = new AST.CommaExp(loc, e, e2, false); loc = token.loc; } return e; } /********************************* Expression Parser ***************************/ AST.Expression parsePrimaryExp() { AST.Expression e; AST.Type t; Identifier id; const loc = token.loc; //printf("parsePrimaryExp(): loc = %d\n", loc.linnum); switch (token.value) { case TOK.identifier: { if (peekNext() == TOK.arrow) { // skip `identifier ->` nextToken(); nextToken(); error("use `.` for member lookup, not `->`"); goto Lerr; } if (peekNext() == TOK.goesTo) goto case_delegate; id = token.ident; nextToken(); TOK save; if (token.value == TOK.not && (save = peekNext()) != TOK.is_ && save != TOK.in_) { // identifier!(template-argument-list) auto tempinst = new AST.TemplateInstance(loc, id, parseTemplateArguments()); e = new AST.ScopeExp(loc, tempinst); } else e = new AST.IdentifierExp(loc, id); break; } case TOK.dollar: if (!inBrackets) error("`$` is valid only inside [] of index or slice"); e = new AST.DollarExp(loc); nextToken(); break; case TOK.dot: // Signal global scope '.' operator with "" identifier e = new AST.IdentifierExp(loc, Id.empty); break; case TOK.this_: e = new AST.ThisExp(loc); nextToken(); break; case TOK.super_: e = new AST.SuperExp(loc); nextToken(); break; case TOK.int32Literal: e = new AST.IntegerExp(loc, token.intvalue, AST.Type.tint32); nextToken(); break; case TOK.uns32Literal: e = new AST.IntegerExp(loc, token.unsvalue, AST.Type.tuns32); nextToken(); break; case TOK.int64Literal: e = new AST.IntegerExp(loc, token.intvalue, AST.Type.tint64); nextToken(); break; case TOK.uns64Literal: e = new AST.IntegerExp(loc, token.unsvalue, AST.Type.tuns64); nextToken(); break; case TOK.float32Literal: e = new AST.RealExp(loc, token.floatvalue, AST.Type.tfloat32); nextToken(); break; case TOK.float64Literal: e = new AST.RealExp(loc, token.floatvalue, AST.Type.tfloat64); nextToken(); break; case TOK.float80Literal: e = new AST.RealExp(loc, token.floatvalue, AST.Type.tfloat80); nextToken(); break; case TOK.imaginary32Literal: e = new AST.RealExp(loc, token.floatvalue, AST.Type.timaginary32); nextToken(); break; case TOK.imaginary64Literal: e = new AST.RealExp(loc, token.floatvalue, AST.Type.timaginary64); nextToken(); break; case TOK.imaginary80Literal: e = new AST.RealExp(loc, token.floatvalue, AST.Type.timaginary80); nextToken(); break; case TOK.null_: e = new AST.NullExp(loc); nextToken(); break; case TOK.file: { const(char)* s = loc.filename ? loc.filename : mod.ident.toChars(); e = new AST.StringExp(loc, s.toDString()); nextToken(); break; } case TOK.fileFullPath: { assert(loc.isValid(), "__FILE_FULL_PATH__ does not work with an invalid location"); const s = FileName.toAbsolute(loc.filename); e = new AST.StringExp(loc, s.toDString()); nextToken(); break; } case TOK.line: e = new AST.IntegerExp(loc, loc.linnum, AST.Type.tint32); nextToken(); break; case TOK.moduleString: { const(char)* s = md ? md.toChars() : mod.toChars(); e = new AST.StringExp(loc, s.toDString()); nextToken(); break; } case TOK.functionString: e = new AST.FuncInitExp(loc); nextToken(); break; case TOK.prettyFunction: e = new AST.PrettyFuncInitExp(loc); nextToken(); break; case TOK.true_: e = new AST.IntegerExp(loc, 1, AST.Type.tbool); nextToken(); break; case TOK.false_: e = new AST.IntegerExp(loc, 0, AST.Type.tbool); nextToken(); break; case TOK.charLiteral: e = new AST.IntegerExp(loc, token.unsvalue, AST.Type.tchar); nextToken(); break; case TOK.wcharLiteral: e = new AST.IntegerExp(loc, token.unsvalue, AST.Type.twchar); nextToken(); break; case TOK.dcharLiteral: e = new AST.IntegerExp(loc, token.unsvalue, AST.Type.tdchar); nextToken(); break; case TOK.string_: { // cat adjacent strings auto s = token.ustring; auto len = token.len; auto postfix = token.postfix; while (1) { const prev = token; nextToken(); if (token.value == TOK.string_) { if (token.postfix) { if (token.postfix != postfix) error("mismatched string literal postfixes `'%c'` and `'%c'`", postfix, token.postfix); postfix = token.postfix; } error("Implicit string concatenation is error-prone and disallowed in D"); errorSupplemental(token.loc, "Use the explicit syntax instead " ~ "(concatenating literals is `@nogc`): %s ~ %s", prev.toChars(), token.toChars()); const len1 = len; const len2 = token.len; len = len1 + len2; auto s2 = cast(char*)mem.xmalloc_noscan(len * char.sizeof); memcpy(s2, s, len1 * char.sizeof); memcpy(s2 + len1, token.ustring, len2 * char.sizeof); s = s2; } else break; } e = new AST.StringExp(loc, s[0 .. len], len, 1, postfix); break; } case TOK.void_: t = AST.Type.tvoid; goto LabelX; case TOK.int8: t = AST.Type.tint8; goto LabelX; case TOK.uns8: t = AST.Type.tuns8; goto LabelX; case TOK.int16: t = AST.Type.tint16; goto LabelX; case TOK.uns16: t = AST.Type.tuns16; goto LabelX; case TOK.int32: t = AST.Type.tint32; goto LabelX; case TOK.uns32: t = AST.Type.tuns32; goto LabelX; case TOK.int64: t = AST.Type.tint64; goto LabelX; case TOK.uns64: t = AST.Type.tuns64; goto LabelX; case TOK.int128: t = AST.Type.tint128; goto LabelX; case TOK.uns128: t = AST.Type.tuns128; goto LabelX; case TOK.float32: t = AST.Type.tfloat32; goto LabelX; case TOK.float64: t = AST.Type.tfloat64; goto LabelX; case TOK.float80: t = AST.Type.tfloat80; goto LabelX; case TOK.imaginary32: t = AST.Type.timaginary32; goto LabelX; case TOK.imaginary64: t = AST.Type.timaginary64; goto LabelX; case TOK.imaginary80: t = AST.Type.timaginary80; goto LabelX; case TOK.complex32: t = AST.Type.tcomplex32; goto LabelX; case TOK.complex64: t = AST.Type.tcomplex64; goto LabelX; case TOK.complex80: t = AST.Type.tcomplex80; goto LabelX; case TOK.bool_: t = AST.Type.tbool; goto LabelX; case TOK.char_: t = AST.Type.tchar; goto LabelX; case TOK.wchar_: t = AST.Type.twchar; goto LabelX; case TOK.dchar_: t = AST.Type.tdchar; goto LabelX; LabelX: nextToken(); if (token.value == TOK.leftParenthesis) { e = new AST.TypeExp(loc, t); e = new AST.CallExp(loc, e, parseArguments()); break; } check(TOK.dot, t.toChars()); if (token.value != TOK.identifier) { error("found `%s` when expecting identifier following `%s`.", token.toChars(), t.toChars()); goto Lerr; } e = new AST.DotIdExp(loc, new AST.TypeExp(loc, t), token.ident); nextToken(); break; case TOK.typeof_: { t = parseTypeof(); e = new AST.TypeExp(loc, t); break; } case TOK.vector: { t = parseVector(); e = new AST.TypeExp(loc, t); break; } case TOK.typeid_: { nextToken(); check(TOK.leftParenthesis, "`typeid`"); RootObject o = parseTypeOrAssignExp(); check(TOK.rightParenthesis); e = new AST.TypeidExp(loc, o); break; } case TOK.traits: { /* __traits(identifier, args...) */ Identifier ident; AST.Objects* args = null; nextToken(); check(TOK.leftParenthesis); if (token.value != TOK.identifier) { error("`__traits(identifier, args...)` expected"); goto Lerr; } ident = token.ident; nextToken(); if (token.value == TOK.comma) args = parseTemplateArgumentList(); // __traits(identifier, args...) else check(TOK.rightParenthesis); // __traits(identifier) e = new AST.TraitsExp(loc, ident, args); break; } case TOK.is_: { AST.Type targ; Identifier ident = null; AST.Type tspec = null; TOK tok = TOK.reserved; TOK tok2 = TOK.reserved; AST.TemplateParameters* tpl = null; nextToken(); if (token.value == TOK.leftParenthesis) { nextToken(); if (token.value == TOK.identifier && peekNext() == TOK.leftParenthesis) { error(loc, "unexpected `(` after `%s`, inside `is` expression. Try enclosing the contents of `is` with a `typeof` expression", token.toChars()); nextToken(); Token* tempTok = peekPastParen(&token); memcpy(&token, tempTok, Token.sizeof); goto Lerr; } targ = parseType(&ident); if (token.value == TOK.colon || token.value == TOK.equal) { tok = token.value; nextToken(); if (tok == TOK.equal && (token.value == TOK.struct_ || token.value == TOK.union_ || token.value == TOK.class_ || token.value == TOK.super_ || token.value == TOK.enum_ || token.value == TOK.interface_ || token.value == TOK.package_ || token.value == TOK.module_ || token.value == TOK.argumentTypes || token.value == TOK.parameters || token.value == TOK.const_ && peekNext() == TOK.rightParenthesis || token.value == TOK.immutable_ && peekNext() == TOK.rightParenthesis || token.value == TOK.shared_ && peekNext() == TOK.rightParenthesis || token.value == TOK.inout_ && peekNext() == TOK.rightParenthesis || token.value == TOK.function_ || token.value == TOK.delegate_ || token.value == TOK.return_ || (token.value == TOK.vector && peekNext() == TOK.rightParenthesis))) { tok2 = token.value; nextToken(); } else { tspec = parseType(); } } if (tspec) { if (token.value == TOK.comma) tpl = parseTemplateParameterList(1); else { tpl = new AST.TemplateParameters(); check(TOK.rightParenthesis); } } else check(TOK.rightParenthesis); } else { error("`type identifier : specialization` expected following `is`"); goto Lerr; } e = new AST.IsExp(loc, targ, ident, tok, tspec, tok2, tpl); break; } case TOK.assert_: { // https://dlang.org/spec/expression.html#assert_expressions AST.Expression msg = null; nextToken(); check(TOK.leftParenthesis, "`assert`"); e = parseAssignExp(); if (token.value == TOK.comma) { nextToken(); if (token.value != TOK.rightParenthesis) { msg = parseAssignExp(); if (token.value == TOK.comma) nextToken(); } } check(TOK.rightParenthesis); e = new AST.AssertExp(loc, e, msg); break; } case TOK.mixin_: { // https://dlang.org/spec/expression.html#mixin_expressions nextToken(); if (token.value != TOK.leftParenthesis) error("found `%s` when expecting `%s` following `mixin`", token.toChars(), Token.toChars(TOK.leftParenthesis)); auto exps = parseArguments(); e = new AST.MixinExp(loc, exps); break; } case TOK.import_: { nextToken(); check(TOK.leftParenthesis, "`import`"); e = parseAssignExp(); check(TOK.rightParenthesis); e = new AST.ImportExp(loc, e); break; } case TOK.new_: e = parseNewExp(null); break; case TOK.ref_: { if (peekNext() == TOK.leftParenthesis) { Token* tk = peekPastParen(peek(&token)); if (skipAttributes(tk, &tk) && (tk.value == TOK.goesTo || tk.value == TOK.leftCurly)) { // ref (arguments) => expression // ref (arguments) { statements... } goto case_delegate; } } nextToken(); error("found `%s` when expecting function literal following `ref`", token.toChars()); goto Lerr; } case TOK.leftParenthesis: { Token* tk = peekPastParen(&token); if (skipAttributes(tk, &tk) && (tk.value == TOK.goesTo || tk.value == TOK.leftCurly)) { // (arguments) => expression // (arguments) { statements... } goto case_delegate; } // ( expression ) nextToken(); e = parseExpression(); e.parens = 1; check(loc, TOK.rightParenthesis); break; } case TOK.leftBracket: { /* Parse array literals and associative array literals: * [ value, value, value ... ] * [ key:value, key:value, key:value ... ] */ auto values = new AST.Expressions(); AST.Expressions* keys = null; nextToken(); while (token.value != TOK.rightBracket && token.value != TOK.endOfFile) { e = parseAssignExp(); if (token.value == TOK.colon && (keys || values.dim == 0)) { nextToken(); if (!keys) keys = new AST.Expressions(); keys.push(e); e = parseAssignExp(); } else if (keys) { error("`key:value` expected for associative array literal"); keys = null; } values.push(e); if (token.value == TOK.rightBracket) break; check(TOK.comma); } check(loc, TOK.rightBracket); if (keys) e = new AST.AssocArrayLiteralExp(loc, keys, values); else e = new AST.ArrayLiteralExp(loc, null, values); break; } case TOK.leftCurly: case TOK.function_: case TOK.delegate_: case_delegate: { AST.Dsymbol s = parseFunctionLiteral(); e = new AST.FuncExp(loc, s); break; } default: error("expression expected, not `%s`", token.toChars()); Lerr: // Anything for e, as long as it's not NULL e = new AST.IntegerExp(loc, 0, AST.Type.tint32); nextToken(); break; } return e; } private AST.Expression parseUnaryExp() { AST.Expression e; const loc = token.loc; switch (token.value) { case TOK.and: nextToken(); e = parseUnaryExp(); e = new AST.AddrExp(loc, e); break; case TOK.plusPlus: nextToken(); e = parseUnaryExp(); //e = new AddAssignExp(loc, e, new IntegerExp(loc, 1, Type::tint32)); e = new AST.PreExp(EXP.prePlusPlus, loc, e); break; case TOK.minusMinus: nextToken(); e = parseUnaryExp(); //e = new MinAssignExp(loc, e, new IntegerExp(loc, 1, Type::tint32)); e = new AST.PreExp(EXP.preMinusMinus, loc, e); break; case TOK.mul: nextToken(); e = parseUnaryExp(); e = new AST.PtrExp(loc, e); break; case TOK.min: nextToken(); e = parseUnaryExp(); e = new AST.NegExp(loc, e); break; case TOK.add: nextToken(); e = parseUnaryExp(); e = new AST.UAddExp(loc, e); break; case TOK.not: nextToken(); e = parseUnaryExp(); e = new AST.NotExp(loc, e); break; case TOK.tilde: nextToken(); e = parseUnaryExp(); e = new AST.ComExp(loc, e); break; case TOK.delete_: // @@@DEPRECATED_2.109@@@ // Use of `delete` keyword has been an error since 2.099. // Remove from the parser after 2.109. nextToken(); e = parseUnaryExp(); e = new AST.DeleteExp(loc, e, false); break; case TOK.cast_: // cast(type) expression { nextToken(); check(TOK.leftParenthesis); /* Look for cast(), cast(const), cast(immutable), * cast(shared), cast(shared const), cast(wild), cast(shared wild) */ ubyte m = 0; while (1) { switch (token.value) { case TOK.const_: if (peekNext() == TOK.leftParenthesis) break; // const as type constructor m |= MODFlags.const_; // const as storage class nextToken(); continue; case TOK.immutable_: if (peekNext() == TOK.leftParenthesis) break; m |= MODFlags.immutable_; nextToken(); continue; case TOK.shared_: if (peekNext() == TOK.leftParenthesis) break; m |= MODFlags.shared_; nextToken(); continue; case TOK.inout_: if (peekNext() == TOK.leftParenthesis) break; m |= MODFlags.wild; nextToken(); continue; default: break; } break; } if (token.value == TOK.rightParenthesis) { nextToken(); e = parseUnaryExp(); e = new AST.CastExp(loc, e, m); } else { AST.Type t = parseType(); // cast( type ) t = t.addMod(m); // cast( const type ) check(TOK.rightParenthesis); e = parseUnaryExp(); e = new AST.CastExp(loc, e, t); } break; } case TOK.inout_: case TOK.shared_: case TOK.const_: case TOK.immutable_: // immutable(type)(arguments) / immutable(type).init { StorageClass stc = parseTypeCtor(); AST.Type t = parseBasicType(); t = t.addSTC(stc); if (stc == 0 && token.value == TOK.dot) { nextToken(); if (token.value != TOK.identifier) { error("identifier expected following `(type)`."); return null; } e = new AST.DotIdExp(loc, new AST.TypeExp(loc, t), token.ident); nextToken(); e = parsePostExp(e); } else { e = new AST.TypeExp(loc, t); if (token.value != TOK.leftParenthesis) { error("`(arguments)` expected following `%s`", t.toChars()); return e; } e = new AST.CallExp(loc, e, parseArguments()); } break; } case TOK.leftParenthesis: { auto tk = peek(&token); static if (CCASTSYNTAX) { // If cast if (isDeclaration(tk, NeedDeclaratorId.no, TOK.rightParenthesis, &tk)) { tk = peek(tk); // skip over right parenthesis switch (tk.value) { case TOK.not: tk = peek(tk); if (tk.value == TOK.is_ || tk.value == TOK.in_) // !is or !in break; goto case; case TOK.dot: case TOK.plusPlus: case TOK.minusMinus: case TOK.delete_: case TOK.new_: case TOK.leftParenthesis: case TOK.identifier: case TOK.this_: case TOK.super_: case TOK.int32Literal: case TOK.uns32Literal: case TOK.int64Literal: case TOK.uns64Literal: case TOK.int128Literal: case TOK.uns128Literal: case TOK.float32Literal: case TOK.float64Literal: case TOK.float80Literal: case TOK.imaginary32Literal: case TOK.imaginary64Literal: case TOK.imaginary80Literal: case TOK.null_: case TOK.true_: case TOK.false_: case TOK.charLiteral: case TOK.wcharLiteral: case TOK.dcharLiteral: case TOK.string_: version (none) { case TOK.tilde: case TOK.and: case TOK.mul: case TOK.min: case TOK.add: } case TOK.function_: case TOK.delegate_: case TOK.typeof_: case TOK.traits: case TOK.vector: case TOK.file: case TOK.fileFullPath: case TOK.line: case TOK.moduleString: case TOK.functionString: case TOK.prettyFunction: case TOK.wchar_: case TOK.dchar_: case TOK.bool_: case TOK.char_: case TOK.int8: case TOK.uns8: case TOK.int16: case TOK.uns16: case TOK.int32: case TOK.uns32: case TOK.int64: case TOK.uns64: case TOK.int128: case TOK.uns128: case TOK.float32: case TOK.float64: case TOK.float80: case TOK.imaginary32: case TOK.imaginary64: case TOK.imaginary80: case TOK.complex32: case TOK.complex64: case TOK.complex80: case TOK.void_: { // (type) una_exp nextToken(); auto t = parseType(); check(TOK.rightParenthesis); // if .identifier // or .identifier!( ... ) if (token.value == TOK.dot) { if (peekNext() != TOK.identifier && peekNext() != TOK.new_) { error("identifier or new keyword expected following `(...)`."); return null; } e = new AST.TypeExp(loc, t); e.parens = true; e = parsePostExp(e); } else { e = parseUnaryExp(); e = new AST.CastExp(loc, e, t); error("C style cast illegal, use `%s`", e.toChars()); } return e; } default: break; } } } e = parsePrimaryExp(); e = parsePostExp(e); break; } case TOK.throw_: { nextToken(); // Deviation from the DIP: // Parse AssignExpression instead of Expression to avoid conflicts for comma // separated lists, e.g. function arguments AST.Expression exp = parseAssignExp(); e = new AST.ThrowExp(loc, exp); break; } default: e = parsePrimaryExp(); e = parsePostExp(e); break; } assert(e); // ^^ is right associative and has higher precedence than the unary operators while (token.value == TOK.pow) { nextToken(); AST.Expression e2 = parseUnaryExp(); e = new AST.PowExp(loc, e, e2); } return e; } private AST.Expression parsePostExp(AST.Expression e) { while (1) { const loc = token.loc; switch (token.value) { case TOK.dot: nextToken(); if (token.value == TOK.identifier) { Identifier id = token.ident; nextToken(); if (token.value == TOK.not && peekNext() != TOK.is_ && peekNext() != TOK.in_) { AST.Objects* tiargs = parseTemplateArguments(); e = new AST.DotTemplateInstanceExp(loc, e, id, tiargs); } else e = new AST.DotIdExp(loc, e, id); continue; } if (token.value == TOK.new_) { e = parseNewExp(e); continue; } error("identifier or `new` expected following `.`, not `%s`", token.toChars()); break; case TOK.plusPlus: e = new AST.PostExp(EXP.plusPlus, loc, e); break; case TOK.minusMinus: e = new AST.PostExp(EXP.minusMinus, loc, e); break; case TOK.leftParenthesis: e = new AST.CallExp(loc, e, parseArguments()); continue; case TOK.leftBracket: { // array dereferences: // array[index] // array[] // array[lwr .. upr] AST.Expression index; AST.Expression upr; auto arguments = new AST.Expressions(); inBrackets++; nextToken(); while (token.value != TOK.rightBracket && token.value != TOK.endOfFile) { index = parseAssignExp(); if (token.value == TOK.slice) { // array[..., lwr..upr, ...] nextToken(); upr = parseAssignExp(); arguments.push(new AST.IntervalExp(loc, index, upr)); } else arguments.push(index); if (token.value == TOK.rightBracket) break; check(TOK.comma); } check(TOK.rightBracket); inBrackets--; e = new AST.ArrayExp(loc, e, arguments); continue; } default: return e; } nextToken(); } } private AST.Expression parseMulExp() { const loc = token.loc; auto e = parseUnaryExp(); while (1) { switch (token.value) { case TOK.mul: nextToken(); auto e2 = parseUnaryExp(); e = new AST.MulExp(loc, e, e2); continue; case TOK.div: nextToken(); auto e2 = parseUnaryExp(); e = new AST.DivExp(loc, e, e2); continue; case TOK.mod: nextToken(); auto e2 = parseUnaryExp(); e = new AST.ModExp(loc, e, e2); continue; default: break; } break; } return e; } private AST.Expression parseAddExp() { const loc = token.loc; auto e = parseMulExp(); while (1) { switch (token.value) { case TOK.add: nextToken(); auto e2 = parseMulExp(); e = new AST.AddExp(loc, e, e2); continue; case TOK.min: nextToken(); auto e2 = parseMulExp(); e = new AST.MinExp(loc, e, e2); continue; case TOK.tilde: nextToken(); auto e2 = parseMulExp(); e = new AST.CatExp(loc, e, e2); continue; default: break; } break; } return e; } private AST.Expression parseShiftExp() { const loc = token.loc; auto e = parseAddExp(); while (1) { switch (token.value) { case TOK.leftShift: nextToken(); auto e2 = parseAddExp(); e = new AST.ShlExp(loc, e, e2); continue; case TOK.rightShift: nextToken(); auto e2 = parseAddExp(); e = new AST.ShrExp(loc, e, e2); continue; case TOK.unsignedRightShift: nextToken(); auto e2 = parseAddExp(); e = new AST.UshrExp(loc, e, e2); continue; default: break; } break; } return e; } private AST.Expression parseCmpExp() { const loc = token.loc; auto e = parseShiftExp(); EXP op = EXP.reserved; switch (token.value) { case TOK.equal: op = EXP.equal; goto Lequal; case TOK.notEqual: op = EXP.notEqual; goto Lequal; Lequal: nextToken(); auto e2 = parseShiftExp(); e = new AST.EqualExp(op, loc, e, e2); break; case TOK.not: { // Attempt to identify '!is' const tv = peekNext(); if (tv == TOK.in_) { nextToken(); nextToken(); auto e2 = parseShiftExp(); e = new AST.InExp(loc, e, e2); e = new AST.NotExp(loc, e); break; } if (tv != TOK.is_) break; nextToken(); op = EXP.notIdentity; goto Lidentity; } case TOK.is_: op = EXP.identity; goto Lidentity; Lidentity: nextToken(); auto e2 = parseShiftExp(); e = new AST.IdentityExp(op, loc, e, e2); break; case TOK.lessThan: op = EXP.lessThan; goto Lcmp; case TOK.lessOrEqual: op = EXP.lessOrEqual; goto Lcmp; case TOK.greaterThan: op = EXP.greaterThan; goto Lcmp; case TOK.greaterOrEqual: op = EXP.greaterOrEqual; goto Lcmp; Lcmp: nextToken(); auto e2 = parseShiftExp(); e = new AST.CmpExp(op, loc, e, e2); break; case TOK.in_: nextToken(); auto e2 = parseShiftExp(); e = new AST.InExp(loc, e, e2); break; default: break; } return e; } private AST.Expression parseAndExp() { Loc loc = token.loc; auto e = parseCmpExp(); while (token.value == TOK.and) { checkParens(TOK.and, e); nextToken(); auto e2 = parseCmpExp(); checkParens(TOK.and, e2); e = new AST.AndExp(loc, e, e2); loc = token.loc; } return e; } private AST.Expression parseXorExp() { const loc = token.loc; auto e = parseAndExp(); while (token.value == TOK.xor) { checkParens(TOK.xor, e); nextToken(); auto e2 = parseAndExp(); checkParens(TOK.xor, e2); e = new AST.XorExp(loc, e, e2); } return e; } private AST.Expression parseOrExp() { const loc = token.loc; auto e = parseXorExp(); while (token.value == TOK.or) { checkParens(TOK.or, e); nextToken(); auto e2 = parseXorExp(); checkParens(TOK.or, e2); e = new AST.OrExp(loc, e, e2); } return e; } private AST.Expression parseAndAndExp() { const loc = token.loc; auto e = parseOrExp(); while (token.value == TOK.andAnd) { nextToken(); auto e2 = parseOrExp(); e = new AST.LogicalExp(loc, EXP.andAnd, e, e2); } return e; } private AST.Expression parseOrOrExp() { const loc = token.loc; auto e = parseAndAndExp(); while (token.value == TOK.orOr) { nextToken(); auto e2 = parseAndAndExp(); e = new AST.LogicalExp(loc, EXP.orOr, e, e2); } return e; } private AST.Expression parseCondExp() { const loc = token.loc; auto e = parseOrOrExp(); if (token.value == TOK.question) { nextToken(); auto e1 = parseExpression(); check(TOK.colon); auto e2 = parseCondExp(); e = new AST.CondExp(loc, e, e1, e2); } return e; } AST.Expression parseAssignExp() { AST.Expression e; e = parseCondExp(); if (e is null) return e; // require parens for e.g. `t ? a = 1 : b = 2` void checkRequiredParens() { if (e.op == EXP.question && !e.parens) dmd.errors.error(e.loc, "`%s` must be surrounded by parentheses when next to operator `%s`", e.toChars(), Token.toChars(token.value)); } const loc = token.loc; switch (token.value) { case TOK.assign: checkRequiredParens(); nextToken(); auto e2 = parseAssignExp(); e = new AST.AssignExp(loc, e, e2); break; case TOK.addAssign: checkRequiredParens(); nextToken(); auto e2 = parseAssignExp(); e = new AST.AddAssignExp(loc, e, e2); break; case TOK.minAssign: checkRequiredParens(); nextToken(); auto e2 = parseAssignExp(); e = new AST.MinAssignExp(loc, e, e2); break; case TOK.mulAssign: checkRequiredParens(); nextToken(); auto e2 = parseAssignExp(); e = new AST.MulAssignExp(loc, e, e2); break; case TOK.divAssign: checkRequiredParens(); nextToken(); auto e2 = parseAssignExp(); e = new AST.DivAssignExp(loc, e, e2); break; case TOK.modAssign: checkRequiredParens(); nextToken(); auto e2 = parseAssignExp(); e = new AST.ModAssignExp(loc, e, e2); break; case TOK.powAssign: checkRequiredParens(); nextToken(); auto e2 = parseAssignExp(); e = new AST.PowAssignExp(loc, e, e2); break; case TOK.andAssign: checkRequiredParens(); nextToken(); auto e2 = parseAssignExp(); e = new AST.AndAssignExp(loc, e, e2); break; case TOK.orAssign: checkRequiredParens(); nextToken(); auto e2 = parseAssignExp(); e = new AST.OrAssignExp(loc, e, e2); break; case TOK.xorAssign: checkRequiredParens(); nextToken(); auto e2 = parseAssignExp(); e = new AST.XorAssignExp(loc, e, e2); break; case TOK.leftShiftAssign: checkRequiredParens(); nextToken(); auto e2 = parseAssignExp(); e = new AST.ShlAssignExp(loc, e, e2); break; case TOK.rightShiftAssign: checkRequiredParens(); nextToken(); auto e2 = parseAssignExp(); e = new AST.ShrAssignExp(loc, e, e2); break; case TOK.unsignedRightShiftAssign: checkRequiredParens(); nextToken(); auto e2 = parseAssignExp(); e = new AST.UshrAssignExp(loc, e, e2); break; case TOK.concatenateAssign: checkRequiredParens(); nextToken(); auto e2 = parseAssignExp(); e = new AST.CatAssignExp(loc, e, e2); break; default: break; } return e; } /************************* * Collect argument list. * Assume current token is ',', '$(LPAREN)' or '['. */ private AST.Expressions* parseArguments() { // function call AST.Expressions* arguments; arguments = new AST.Expressions(); const endtok = token.value == TOK.leftBracket ? TOK.rightBracket : TOK.rightParenthesis; nextToken(); while (token.value != endtok && token.value != TOK.endOfFile) { auto arg = parseAssignExp(); arguments.push(arg); if (token.value != TOK.comma) break; nextToken(); //comma } check(endtok); return arguments; } /******************************************* */ private AST.Expression parseNewExp(AST.Expression thisexp) { const loc = token.loc; nextToken(); AST.Expressions* arguments = null; // An anonymous nested class starts with "class" if (token.value == TOK.class_) { nextToken(); if (token.value == TOK.leftParenthesis) arguments = parseArguments(); AST.BaseClasses* baseclasses = null; if (token.value != TOK.leftCurly) baseclasses = parseBaseClasses(); Identifier id = null; AST.Dsymbols* members = null; if (token.value != TOK.leftCurly) { error("`{ members }` expected for anonymous class"); } else { nextToken(); members = parseDeclDefs(0); if (token.value != TOK.rightCurly) error("class member expected"); nextToken(); } auto cd = new AST.ClassDeclaration(loc, id, baseclasses, members, false); auto e = new AST.NewAnonClassExp(loc, thisexp, cd, arguments); return e; } const stc = parseTypeCtor(); auto t = parseBasicType(true); t = parseTypeSuffixes(t); t = t.addSTC(stc); if (t.ty == Taarray) { AST.TypeAArray taa = cast(AST.TypeAArray)t; AST.Type index = taa.index; auto edim = AST.typeToExpression(index); if (!edim) { error("cannot create a `%s` with `new`", t.toChars); return new AST.NullExp(loc); } t = new AST.TypeSArray(taa.next, edim); } else if (token.value == TOK.leftParenthesis && t.ty != Tsarray) { arguments = parseArguments(); } auto e = new AST.NewExp(loc, thisexp, t, arguments); return e; } /********************************************** */ private void addComment(AST.Dsymbol s, const(char)* blockComment) { if (s !is null) this.addComment(s, blockComment.toDString()); } private void addComment(AST.Dsymbol s, const(char)[] blockComment) { if (s !is null) { s.addComment(combineComments(blockComment, token.lineComment, true)); token.lineComment = null; } } /********************************************** * Recognize builtin @ attributes * Params: * ident = identifier * Returns: * storage class for attribute, 0 if not */ static StorageClass isBuiltinAtAttribute(Identifier ident) { return (ident == Id.property) ? STC.property : (ident == Id.nogc) ? STC.nogc : (ident == Id.safe) ? STC.safe : (ident == Id.trusted) ? STC.trusted : (ident == Id.system) ? STC.system : (ident == Id.live) ? STC.live : (ident == Id.future) ? STC.future : (ident == Id.disable) ? STC.disable : 0; } enum StorageClass atAttrGroup = STC.property | STC.nogc | STC.safe | STC.trusted | STC.system | STC.live | /*STC.future |*/ // probably should be included STC.disable; } enum PREC : int { zero, expr, assign, cond, oror, andand, or, xor, and, equal, rel, shift, add, mul, pow, unary, primary, } /********************************** * Set operator precedence for each operator. * * Used by hdrgen */ immutable PREC[EXP.max + 1] precedence = [ EXP.type : PREC.expr, EXP.error : PREC.expr, EXP.objcClassReference : PREC.expr, // Objective-C class reference, same as EXP.type EXP.typeof_ : PREC.primary, EXP.mixin_ : PREC.primary, EXP.import_ : PREC.primary, EXP.dotVariable : PREC.primary, EXP.scope_ : PREC.primary, EXP.identifier : PREC.primary, EXP.this_ : PREC.primary, EXP.super_ : PREC.primary, EXP.int64 : PREC.primary, EXP.float64 : PREC.primary, EXP.complex80 : PREC.primary, EXP.null_ : PREC.primary, EXP.string_ : PREC.primary, EXP.arrayLiteral : PREC.primary, EXP.assocArrayLiteral : PREC.primary, EXP.classReference : PREC.primary, EXP.file : PREC.primary, EXP.fileFullPath : PREC.primary, EXP.line : PREC.primary, EXP.moduleString : PREC.primary, EXP.functionString : PREC.primary, EXP.prettyFunction : PREC.primary, EXP.typeid_ : PREC.primary, EXP.is_ : PREC.primary, EXP.assert_ : PREC.primary, EXP.halt : PREC.primary, EXP.template_ : PREC.primary, EXP.dSymbol : PREC.primary, EXP.function_ : PREC.primary, EXP.variable : PREC.primary, EXP.symbolOffset : PREC.primary, EXP.structLiteral : PREC.primary, EXP.compoundLiteral : PREC.primary, EXP.arrayLength : PREC.primary, EXP.delegatePointer : PREC.primary, EXP.delegateFunctionPointer : PREC.primary, EXP.remove : PREC.primary, EXP.tuple : PREC.primary, EXP.traits : PREC.primary, EXP.default_ : PREC.primary, EXP.overloadSet : PREC.primary, EXP.void_ : PREC.primary, EXP.vectorArray : PREC.primary, EXP._Generic : PREC.primary, // post EXP.dotTemplateInstance : PREC.primary, EXP.dotIdentifier : PREC.primary, EXP.dotTemplateDeclaration : PREC.primary, EXP.dot : PREC.primary, EXP.dotType : PREC.primary, EXP.plusPlus : PREC.primary, EXP.minusMinus : PREC.primary, EXP.prePlusPlus : PREC.primary, EXP.preMinusMinus : PREC.primary, EXP.call : PREC.primary, EXP.slice : PREC.primary, EXP.array : PREC.primary, EXP.index : PREC.primary, EXP.delegate_ : PREC.unary, EXP.address : PREC.unary, EXP.star : PREC.unary, EXP.negate : PREC.unary, EXP.uadd : PREC.unary, EXP.not : PREC.unary, EXP.tilde : PREC.unary, EXP.delete_ : PREC.unary, EXP.new_ : PREC.unary, EXP.newAnonymousClass : PREC.unary, EXP.cast_ : PREC.unary, EXP.throw_ : PREC.unary, EXP.vector : PREC.unary, EXP.pow : PREC.pow, EXP.mul : PREC.mul, EXP.div : PREC.mul, EXP.mod : PREC.mul, EXP.add : PREC.add, EXP.min : PREC.add, EXP.concatenate : PREC.add, EXP.leftShift : PREC.shift, EXP.rightShift : PREC.shift, EXP.unsignedRightShift : PREC.shift, EXP.lessThan : PREC.rel, EXP.lessOrEqual : PREC.rel, EXP.greaterThan : PREC.rel, EXP.greaterOrEqual : PREC.rel, EXP.in_ : PREC.rel, /* Note that we changed precedence, so that < and != have the same * precedence. This change is in the parser, too. */ EXP.equal : PREC.rel, EXP.notEqual : PREC.rel, EXP.identity : PREC.rel, EXP.notIdentity : PREC.rel, EXP.and : PREC.and, EXP.xor : PREC.xor, EXP.or : PREC.or, EXP.andAnd : PREC.andand, EXP.orOr : PREC.oror, EXP.question : PREC.cond, EXP.assign : PREC.assign, EXP.construct : PREC.assign, EXP.blit : PREC.assign, EXP.addAssign : PREC.assign, EXP.minAssign : PREC.assign, EXP.concatenateAssign : PREC.assign, EXP.concatenateElemAssign : PREC.assign, EXP.concatenateDcharAssign : PREC.assign, EXP.mulAssign : PREC.assign, EXP.divAssign : PREC.assign, EXP.modAssign : PREC.assign, EXP.powAssign : PREC.assign, EXP.leftShiftAssign : PREC.assign, EXP.rightShiftAssign : PREC.assign, EXP.unsignedRightShiftAssign : PREC.assign, EXP.andAssign : PREC.assign, EXP.orAssign : PREC.assign, EXP.xorAssign : PREC.assign, EXP.comma : PREC.expr, EXP.declaration : PREC.expr, EXP.interval : PREC.assign, ]; enum ParseStatementFlags : int { semi = 1, // empty ';' statements are allowed, but deprecated scope_ = 2, // start a new scope curly = 4, // { } statement is required curlyScope = 8, // { } starts a new scope semiOk = 0x10, // empty ';' are really ok } struct PrefixAttributes(AST) { StorageClass storageClass; AST.Expression depmsg; LINK link; AST.Visibility visibility; bool setAlignment; AST.Expression ealign; AST.Expressions* udas; const(char)* comment; } /// The result of the `ParseLinkage` function struct ParsedLinkage(AST) { /// What linkage was specified LINK link; /// If `extern(C++, class|struct)`, contains the `class|struct` CPPMANGLE cppmangle; /// If `extern(C++, some.identifier)`, will be the identifiers AST.Identifiers* idents; /// If `extern(C++, (some_tuple_expression)|"string"), will be the expressions AST.Expressions* identExps; } /*********************************** Private *************************************/ /*********************** * How multiple declarations are parsed. * If 1, treat as C. * If 0, treat: * int *p, i; * as: * int* p; * int* i; */ private enum CDECLSYNTAX = 0; /***** * Support C cast syntax: * (type)(expression) */ private enum CCASTSYNTAX = 1; /***** * Support postfix C array declarations, such as * int a[3][4]; */ private enum CARRAYDECL = 1; /***************************** * Destructively extract storage class from pAttrs. */ private StorageClass getStorageClass(AST)(PrefixAttributes!(AST)* pAttrs) { StorageClass stc = STC.undefined_; if (pAttrs) { stc = pAttrs.storageClass; pAttrs.storageClass = STC.undefined_; } return stc; } /************************************** * dump mixin expansion to file for better debugging */ private bool writeMixin(const(char)[] s, ref Loc loc) { if (!global.params.mixinOut) return false; OutBuffer* ob = global.params.mixinOut; ob.writestring("// expansion at "); ob.writestring(loc.toChars()); ob.writenl(); global.params.mixinLines++; loc = Loc(global.params.mixinFile, global.params.mixinLines + 1, loc.charnum); // write by line to create consistent line endings size_t lastpos = 0; for (size_t i = 0; i < s.length; ++i) { // detect LF and CRLF const c = s[i]; if (c == '\n' || (c == '\r' && i+1 < s.length && s[i+1] == '\n')) { ob.writestring(s[lastpos .. i]); ob.writenl(); global.params.mixinLines++; if (c == '\r') ++i; lastpos = i + 1; } } if(lastpos < s.length) ob.writestring(s[lastpos .. $]); if (s.length == 0 || s[$-1] != '\n') { ob.writenl(); // ensure empty line after expansion global.params.mixinLines++; } ob.writenl(); global.params.mixinLines++; return true; }