blob: 4b9c0f2119f5b6e90f7b9b3a1fc89ab2d086c7bf [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;
}
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;
}