| /** | |
| * 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; | |
| } |