blob: 6fb542fb9aa4278d440c6ad77731ae9aaf439db2 [file] [log] [blame]
/**
* 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;
}
/************************************
* Parse declarations and definitions
* Params:
* once = !=0 means parse exactly one decl or def
* pLastDecl = set to last decl or def parsed
* pAttrs = keep track of attributes
* Returns:
* array of declared symbols
*/
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.ddoc.doOutput || global.params.dihdr.doOutput))
{
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;
const attrloc = token.loc;
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;
}
}
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("`{` expected after template parameter list, not `%s`", token.toChars());
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, "static assert");
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)
{
// @@@DEPRECATED_2.111@@@
// Deprecated in 2.101, remove in 2.111
deprecation("`debug = <integer>` is deprecated, use debug identifiers instead");
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)
{
// @@@DEPRECATED_2.111@@@
// Deprecated in 2.101, remove in 2.111
deprecation("`debug( <integer> )` is deprecated, use debug identifiers instead");
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)
{
// @@@DEPRECATED_2.111@@@
// Deprecated in 2.101, remove in 2.111
deprecation("`version = <integer>` is deprecated, use version identifiers instead");
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)
{
// @@@DEPRECATED_2.111@@@
// Deprecated in 2.101, remove in 2.111
deprecation("`version( <integer> )` is deprecated, use version identifiers instead");
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, "invariant");
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.ddoc.doOutput && 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;
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");
const tv = peekNext();
if (tpl && token.value == TOK.identifier &&
(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
{
at = parseType(&ai);
}
ae = null;
if (token.value == TOK.assign) // = defaultArg
{
nextToken();
ae = parseDefaultInitExp();
}
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)