/**
 * Takes a token stream from the lexer, and parses it into an abstract syntax tree.
 *
 * Specification: $(LINK2 https://dlang.org/spec/grammar.html, D Grammar)
 *
 * Copyright:   Copyright (C) 1999-2022 by The D Language Foundation, All Rights Reserved
 * Authors:     $(LINK2 https://www.digitalmars.com, Walter Bright)
 * License:     $(LINK2 https://www.boost.org/LICENSE_1_0.txt, Boost License 1.0)
 * Source:      $(LINK2 https://github.com/dlang/dmd/blob/master/src/dmd/parse.d, _parse.d)
 * Documentation:  https://dlang.org/phobos/dmd_parse.html
 * Coverage:    https://codecov.io/gh/dlang/dmd/src/master/src/dmd/parse.d
 */

module dmd.parse;

import core.stdc.stdio;
import core.stdc.string;
import dmd.astenums;
import dmd.globals;
import dmd.id;
import dmd.identifier;
import dmd.lexer;
import dmd.errors;
import dmd.root.filename;
import dmd.common.outbuffer;
import dmd.root.rmem;
import dmd.root.rootobject;
import dmd.root.string;
import dmd.tokens;

/***********************************************************
 */
class Parser(AST, Lexer = dmd.lexer.Lexer) : Lexer
{
    AST.ModuleDeclaration* md;

    protected
    {
        AST.Module mod;
        LINK linkage;
        Loc linkLoc;
        CPPMANGLE cppmangle;
        Loc endloc; // set to location of last right curly
        int inBrackets; // inside [] of array index or slice
        Loc lookingForElse; // location of lonely if looking for an else
    }

    /*********************
     * Use this constructor for string mixins.
     * Input:
     *      loc     location in source file of mixin
     */
    extern (D) this(const ref Loc loc, AST.Module _module, const(char)[] input, bool doDocComment)
    {
        super(_module ? _module.srcfile.toChars() : null, input.ptr, 0, input.length, doDocComment, false);

        //printf("Parser::Parser()\n");
        scanloc = loc;

        if (!writeMixin(input, scanloc) && loc.filename)
        {
            /* Create a pseudo-filename for the mixin string, as it may not even exist
             * in the source file.
             */
            char* filename = cast(char*)mem.xmalloc(strlen(loc.filename) + 7 + (loc.linnum).sizeof * 3 + 1);
            sprintf(filename, "%s-mixin-%d", loc.filename, cast(int)loc.linnum);
            scanloc.filename = filename;
        }

        mod = _module;
        linkage = LINK.d;
        //nextToken();              // start up the scanner
    }

    extern (D) this(AST.Module _module, const(char)[] input, bool doDocComment)
    {
        super(_module ? _module.srcfile.toChars() : null, input.ptr, 0, input.length, doDocComment, false);

        //printf("Parser::Parser()\n");
        mod = _module;
        linkage = LINK.d;
        //nextToken();              // start up the scanner
    }

    /++
     + Parse a module, i.e. the optional `module x.y.z` declaration and all declarations
     + found in the current file.
     +
     + Returns: the list of declarations or an empty list in case of malformed declarations,
     +          the module declaration will be stored as `this.md` if found
     +/
    AST.Dsymbols* parseModule()
    {
        if (!parseModuleDeclaration())
            return errorReturn();

        return parseModuleContent();
    }

    /++
     + Parse the optional module declaration
     +
     + Returns: false if a malformed module declaration was found
     +/
    final bool parseModuleDeclaration()
    {
        const comment = token.blockComment;
        bool isdeprecated = false;
        AST.Expression msg = null;

        // Parse optional module attributes
        parseModuleAttributes(msg, isdeprecated);

        // ModuleDeclaration leads off
        if (token.value == TOK.module_)
        {
            const loc = token.loc;
            nextToken();

            /* parse ModuleFullyQualifiedName
             * https://dlang.org/spec/module.html#ModuleFullyQualifiedName
             */

            if (token.value != TOK.identifier)
            {
                error("identifier expected following `module`");
                return false;
            }

            Identifier[] a;
            Identifier id = token.ident;

            while (nextToken() == TOK.dot)
            {
                a ~= id;
                nextToken();
                if (token.value != TOK.identifier)
                {
                    error("identifier expected following `package`");
                    return false;
                }
                id = token.ident;
            }

            md = new AST.ModuleDeclaration(loc, a, id, msg, isdeprecated);

            if (token.value != TOK.semicolon)
                error("`;` expected following module declaration instead of `%s`", token.toChars());
            nextToken();
            addComment(mod, comment);
        }
        return true;
    }

    /++
     + Parse the content of a module, i.e. all declarations found until the end of file.
     +
     + Returns: the list of declarations or an empty list in case of malformed declarations
     +/
    final AST.Dsymbols* parseModuleContent()
    {
        AST.Dsymbol lastDecl = mod;
        AST.Dsymbols* decldefs = parseDeclDefs(0, &lastDecl);

        if (token.value == TOK.rightCurly)
        {
            error(token.loc, "unmatched closing brace");
            return errorReturn();
        }

        if (token.value != TOK.endOfFile)
        {
            error(token.loc, "unrecognized declaration");
            return errorReturn();
        }
        return decldefs;
    }

    /++
     + Skips to the end of the current declaration - denoted by either `;` or EOF
     +
     + Returns: An empty list of Dsymbols
     +/
    private AST.Dsymbols* errorReturn()
    {
        while (token.value != TOK.semicolon && token.value != TOK.endOfFile)
            nextToken();
        nextToken();
        return new AST.Dsymbols();
    }

    /**********************************
     * Parse the ModuleAttributes preceding a module declaration.
     * ModuleDeclaration:
     *    ModuleAttributes(opt) module ModuleFullyQualifiedName ;
     * https://dlang.org/spec/module.html#ModuleAttributes
     * Params:
     *  msg = set to the AssignExpression from DeprecatedAttribute https://dlang.org/spec/module.html#DeprecatedAttribute
     *  isdeprecated = set to true if a DeprecatedAttribute is seen
     */
    private
    void parseModuleAttributes(out AST.Expression msg, out bool isdeprecated)
    {
        Token* tk;
        if (!(skipAttributes(&token, &tk) && tk.value == TOK.module_))
            return;             // no module attributes

        AST.Expressions* udas = null;
        while (token.value != TOK.module_)
        {
            switch (token.value)
            {
            case TOK.deprecated_:
                {
                    // deprecated (...) module ...
                    if (isdeprecated)
                        error("there is only one deprecation attribute allowed for module declaration");
                    isdeprecated = true;
                    nextToken();
                    if (token.value == TOK.leftParenthesis)
                    {
                        check(TOK.leftParenthesis);
                        msg = parseAssignExp();
                        check(TOK.rightParenthesis);
                    }
                    break;
                }
            case TOK.at:
                {
                    AST.Expressions* exps = null;
                    const stc = parseAttribute(exps);
                    if (stc & atAttrGroup)
                    {
                        error("`@%s` attribute for module declaration is not supported", token.toChars());
                    }
                    else
                    {
                        udas = AST.UserAttributeDeclaration.concat(udas, exps);
                    }
                    if (stc)
                        nextToken();
                    break;
                }
            default:
                {
                    error("`module` expected instead of `%s`", token.toChars());
                    nextToken();
                    break;
                }
            }
        }

        if (udas)
        {
            auto a = new AST.Dsymbols();
            auto udad = new AST.UserAttributeDeclaration(udas, a);
            mod.userAttribDecl = udad;
        }
    }

  final:

    /**
     * Parses a `deprecated` declaration
     *
     * Params:
     *   msg = Deprecated message, if any.
     *         Used to support overriding a deprecated storage class with
     *         a deprecated declaration with a message, but to error
     *         if both declaration have a message.
     *
     * Returns:
     *   Whether the deprecated declaration has a message
     */
    private bool parseDeprecatedAttribute(ref AST.Expression msg)
    {
        if (peekNext() != TOK.leftParenthesis)
            return false;

        nextToken();
        check(TOK.leftParenthesis);
        AST.Expression e = parseAssignExp();
        check(TOK.rightParenthesis);
        if (msg)
        {
            error("conflicting storage class `deprecated(%s)` and `deprecated(%s)`", msg.toChars(), e.toChars());
        }
        msg = e;
        return true;
    }

    /************************************
     * Parse declarations and definitions
     * Params:
     *  once = !=0 means parse exactly one decl or def
     *  pLastDecl = set to last decl or def parsed
     *  pAttrs = keep track of attributes
     * Returns:
     *  array of declared symbols
     */
    AST.Dsymbols* parseDeclDefs(int once, AST.Dsymbol* pLastDecl = null, PrefixAttributes!AST* pAttrs = null)
    {
        AST.Dsymbol lastDecl = null; // used to link unittest to its previous declaration
        if (!pLastDecl)
            pLastDecl = &lastDecl;

        const linksave = linkage; // save global state

        //printf("Parser::parseDeclDefs()\n");
        auto decldefs = new AST.Dsymbols();
        do
        {
            // parse result
            AST.Dsymbol s = null;
            AST.Dsymbols* a = null;

            PrefixAttributes!AST attrs;
            if (!once || !pAttrs)
            {
                pAttrs = &attrs;
                pAttrs.comment = token.blockComment.ptr;
            }
            AST.Visibility.Kind prot;
            StorageClass stc;
            AST.Condition condition;

            linkage = linksave;

            Loc startloc;

            switch (token.value)
            {
            case TOK.enum_:
                {
                    /* Determine if this is a manifest constant declaration,
                     * or a conventional enum.
                     */
                    const tv = peekNext();
                    if (tv == TOK.leftCurly || tv == TOK.colon)
                        s = parseEnum();
                    else if (tv != TOK.identifier)
                        goto Ldeclaration;
                    else
                    {
                        const nextv = peekNext2();
                        if (nextv == TOK.leftCurly || nextv == TOK.colon || nextv == TOK.semicolon)
                            s = parseEnum();
                        else
                            goto Ldeclaration;
                    }
                    break;
                }
            case TOK.import_:
                a = parseImport();
                // keep pLastDecl
                break;

            case TOK.template_:
                s = cast(AST.Dsymbol)parseTemplateDeclaration();
                break;

            case TOK.mixin_:
                {
                    const loc = token.loc;
                    switch (peekNext())
                    {
                    case TOK.leftParenthesis:
                        {
                            // MixinType
                            if (isDeclaration(&token, NeedDeclaratorId.mustIfDstyle, TOK.reserved, null))
                                goto Ldeclaration;
                            // mixin(string)
                            nextToken();
                            auto exps = parseArguments();
                            check(TOK.semicolon);
                            s = new AST.CompileDeclaration(loc, exps);
                            break;
                        }
                    case TOK.template_:
                        // mixin template
                        nextToken();
                        s = cast(AST.Dsymbol)parseTemplateDeclaration(true);
                        break;

                    default:
                        s = parseMixin();
                        break;
                    }
                    break;
                }
            case TOK.wchar_:
            case TOK.dchar_:
            case TOK.bool_:
            case TOK.char_:
            case TOK.int8:
            case TOK.uns8:
            case TOK.int16:
            case TOK.uns16:
            case TOK.int32:
            case TOK.uns32:
            case TOK.int64:
            case TOK.uns64:
            case TOK.int128:
            case TOK.uns128:
            case TOK.float32:
            case TOK.float64:
            case TOK.float80:
            case TOK.imaginary32:
            case TOK.imaginary64:
            case TOK.imaginary80:
            case TOK.complex32:
            case TOK.complex64:
            case TOK.complex80:
            case TOK.void_:
            case TOK.alias_:
            case TOK.identifier:
            case TOK.super_:
            case TOK.typeof_:
            case TOK.dot:
            case TOK.vector:
            case TOK.struct_:
            case TOK.union_:
            case TOK.class_:
            case TOK.interface_:
            case TOK.traits:
            Ldeclaration:
                a = parseDeclarations(false, pAttrs, pAttrs.comment);
                if (a && a.dim)
                    *pLastDecl = (*a)[a.dim - 1];
                break;

            case TOK.this_:
                if (peekNext() == TOK.dot)
                    goto Ldeclaration;
                s = parseCtor(pAttrs);
                break;

            case TOK.tilde:
                s = parseDtor(pAttrs);
                break;

            case TOK.invariant_:
                const tv = peekNext();
                if (tv == TOK.leftParenthesis || tv == TOK.leftCurly)
                {
                    // invariant { statements... }
                    // invariant() { statements... }
                    // invariant (expression);
                    s = parseInvariant(pAttrs);
                    break;
                }
                error("invariant body expected, not `%s`", token.toChars());
                goto Lerror;

            case TOK.unittest_:
                /**
                 * Ignore unittests in non-root modules.
                 *
                 * This mainly means that unittests *inside templates* are only
                 * ever instantiated if the module lexically declaring the
                 * template is one of the root modules.
                 *
                 * E.g., compiling some project with `-unittest` does NOT
                 * compile and later run any unittests in instantiations of
                 * templates declared in other libraries.
                 *
                 * Declaring unittests *inside* templates is considered an anti-
                 * pattern. In almost all cases, the unittests don't depend on
                 * the template parameters, but instantiate the template with
                 * fixed arguments (e.g., Nullable!T unittests instantiating
                 * Nullable!int), so compiling and running identical tests for
                 * each template instantiation is hardly desirable.
                 * But adding a unittest right below some function being tested
                 * is arguably good for locality, so unittests end up inside
                 * templates.
                 * To make sure a template's unittests are run, it should be
                 * instantiated in the same module, e.g., some module-level
                 * unittest.
                 *
                 * Another reason for ignoring unittests in templates from non-
                 * root modules is for template codegen culling via
                 * TemplateInstance.needsCodegen(). If the compiler decides not
                 * to emit some Nullable!bool because there's an existing
                 * instantiation in some non-root module, it has no idea whether
                 * that module was compiled with -unittest too, and so whether
                 * Nullable!int (instantiated in some unittest inside the
                 * Nullable template) can be culled too. By ignoring unittests
                 * in non-root modules, the compiler won't consider any
                 * template instantiations in these unittests as candidates for
                 * further codegen culling.
                 */
                if (mod.isRoot() && (global.params.useUnitTests || global.params.ddoc.doOutput || global.params.dihdr.doOutput))
                {
                    s = parseUnitTest(pAttrs);
                    if (*pLastDecl)
                        (*pLastDecl).ddocUnittest = cast(AST.UnitTestDeclaration)s;
                }
                else
                {
                    // Skip over unittest block by counting { }
                    Loc loc = token.loc;
                    int braces = 0;
                    while (1)
                    {
                        nextToken();
                        switch (token.value)
                        {
                        case TOK.leftCurly:
                            ++braces;
                            continue;

                        case TOK.rightCurly:
                            if (--braces)
                                continue;
                            nextToken();
                            break;

                        case TOK.endOfFile:
                            /* { */
                            error(loc, "closing `}` of unittest not found before end of file");
                            goto Lerror;

                        default:
                            continue;
                        }
                        break;
                    }
                    // Workaround 14894. Add an empty unittest declaration to keep
                    // the number of symbols in this scope independent of -unittest.
                    s = new AST.UnitTestDeclaration(loc, token.loc, STC.undefined_, null);
                }
                break;

            case TOK.new_:
                s = parseNew(pAttrs);
                break;

            case TOK.colon:
            case TOK.leftCurly:
                error("declaration expected, not `%s`", token.toChars());
                goto Lerror;

            case TOK.rightCurly:
            case TOK.endOfFile:
                if (once)
                    error("declaration expected, not `%s`", token.toChars());
                return decldefs;

            case TOK.static_:
                {
                    const next = peekNext();
                    if (next == TOK.this_)
                        s = parseStaticCtor(pAttrs);
                    else if (next == TOK.tilde)
                        s = parseStaticDtor(pAttrs);
                    else if (next == TOK.assert_)
                        s = parseStaticAssert();
                    else if (next == TOK.if_)
                    {
                        const Loc loc = token.loc;
                        condition = parseStaticIfCondition();
                        AST.Dsymbols* athen;
                        if (token.value == TOK.colon)
                            athen = parseBlock(pLastDecl);
                        else
                        {
                            const lookingForElseSave = lookingForElse;
                            lookingForElse = token.loc;
                            athen = parseBlock(pLastDecl);
                            lookingForElse = lookingForElseSave;
                        }
                        AST.Dsymbols* aelse = null;
                        if (token.value == TOK.else_)
                        {
                            const elseloc = token.loc;
                            nextToken();
                            aelse = parseBlock(pLastDecl);
                            checkDanglingElse(elseloc);
                        }
                        s = new AST.StaticIfDeclaration(loc, condition, athen, aelse);
                    }
                    else if (next == TOK.import_)
                    {
                        a = parseImport();
                        // keep pLastDecl
                    }
                    else if (next == TOK.foreach_ || next == TOK.foreach_reverse_)
                    {
                        s = parseForeach!(AST.StaticForeachDeclaration)(token.loc, pLastDecl);
                    }
                    else
                    {
                        stc = STC.static_;
                        goto Lstc;
                    }
                    break;
                }
            case TOK.const_:
                if (peekNext() == TOK.leftParenthesis)
                    goto Ldeclaration;
                stc = STC.const_;
                goto Lstc;

            case TOK.immutable_:
                if (peekNext() == TOK.leftParenthesis)
                    goto Ldeclaration;
                stc = STC.immutable_;
                goto Lstc;

            case TOK.shared_:
                {
                    const next = peekNext();
                    if (next == TOK.leftParenthesis)
                        goto Ldeclaration;
                    if (next == TOK.static_)
                    {
                        TOK next2 = peekNext2();
                        if (next2 == TOK.this_)
                        {
                            s = parseSharedStaticCtor(pAttrs);
                            break;
                        }
                        if (next2 == TOK.tilde)
                        {
                            s = parseSharedStaticDtor(pAttrs);
                            break;
                        }
                    }
                    stc = STC.shared_;
                    goto Lstc;
                }
            case TOK.inout_:
                if (peekNext() == TOK.leftParenthesis)
                    goto Ldeclaration;
                stc = STC.wild;
                goto Lstc;

            case TOK.final_:
                stc = STC.final_;
                goto Lstc;

            case TOK.auto_:
                stc = STC.auto_;
                goto Lstc;

            case TOK.scope_:
                stc = STC.scope_;
                goto Lstc;

            case TOK.override_:
                stc = STC.override_;
                goto Lstc;

            case TOK.abstract_:
                stc = STC.abstract_;
                goto Lstc;

            case TOK.synchronized_:
                stc = STC.synchronized_;
                goto Lstc;

            case TOK.nothrow_:
                stc = STC.nothrow_;
                goto Lstc;

            case TOK.pure_:
                stc = STC.pure_;
                goto Lstc;

            case TOK.ref_:
                stc = STC.ref_;
                goto Lstc;

            case TOK.gshared:
                stc = STC.gshared;
                goto Lstc;

            case TOK.at:
                {
                    AST.Expressions* exps = null;
                    stc = parseAttribute(exps);
                    if (stc)
                        goto Lstc; // it's a predefined attribute
                    // no redundant/conflicting check for UDAs
                    pAttrs.udas = AST.UserAttributeDeclaration.concat(pAttrs.udas, exps);
                    goto Lautodecl;
                }
            Lstc:
                pAttrs.storageClass = appendStorageClass(pAttrs.storageClass, stc);
                nextToken();

            Lautodecl:

                /* Look for auto initializers:
                 *      storage_class identifier = initializer;
                 *      storage_class identifier(...) = initializer;
                 */
                if (token.value == TOK.identifier && hasOptionalParensThen(peek(&token), TOK.assign))
                {
                    a = parseAutoDeclarations(getStorageClass!AST(pAttrs), pAttrs.comment);
                    if (a && a.dim)
                        *pLastDecl = (*a)[a.dim - 1];
                    if (pAttrs.udas)
                    {
                        s = new AST.UserAttributeDeclaration(pAttrs.udas, a);
                        pAttrs.udas = null;
                    }
                    break;
                }

                /* Look for return type inference for template functions.
                 */
                Token* tk;
                if (token.value == TOK.identifier && skipParens(peek(&token), &tk) && skipAttributes(tk, &tk) &&
                    (tk.value == TOK.leftParenthesis || tk.value == TOK.leftCurly || tk.value == TOK.in_ ||
                     tk.value == TOK.out_ || tk.value == TOK.do_ || tk.value == TOK.goesTo ||
                     tk.value == TOK.identifier && tk.ident == Id._body))
                {
                    // @@@DEPRECATED_2.117@@@
                    // https://github.com/dlang/DIPs/blob/1f5959abe482b1f9094f6484a7d0a3ade77fc2fc/DIPs/accepted/DIP1003.md
                    // Deprecated in 2.097 - Can be removed from 2.117
                    // The deprecation period is longer than usual as `body`
                    // was quite widely used.
                    if (tk.value == TOK.identifier && tk.ident == Id._body)
                        deprecation("usage of the `body` keyword is deprecated. Use `do` instead.");

                    a = parseDeclarations(true, pAttrs, pAttrs.comment);
                    if (a && a.dim)
                        *pLastDecl = (*a)[a.dim - 1];
                    if (pAttrs.udas)
                    {
                        s = new AST.UserAttributeDeclaration(pAttrs.udas, a);
                        pAttrs.udas = null;
                    }
                    break;
                }

                a = parseBlock(pLastDecl, pAttrs);
                auto stc2 = getStorageClass!AST(pAttrs);
                if (stc2 != STC.undefined_)
                {
                    s = new AST.StorageClassDeclaration(stc2, a);
                }
                if (pAttrs.udas)
                {
                    if (s)
                    {
                        a = new AST.Dsymbols();
                        a.push(s);
                    }
                    s = new AST.UserAttributeDeclaration(pAttrs.udas, a);
                    pAttrs.udas = null;
                }
                break;

            case TOK.deprecated_:
                {
                    stc |= STC.deprecated_;
                    if (!parseDeprecatedAttribute(pAttrs.depmsg))
                        goto Lstc;

                    a = parseBlock(pLastDecl, pAttrs);
                    s = new AST.DeprecatedDeclaration(pAttrs.depmsg, a);
                    pAttrs.depmsg = null;
                    break;
                }
            case TOK.leftBracket:
                {
                    if (peekNext() == TOK.rightBracket)
                        error("empty attribute list is not allowed");
                    error("use `@(attributes)` instead of `[attributes]`");
                    AST.Expressions* exps = parseArguments();
                    // no redundant/conflicting check for UDAs

                    pAttrs.udas = AST.UserAttributeDeclaration.concat(pAttrs.udas, exps);
                    a = parseBlock(pLastDecl, pAttrs);
                    if (pAttrs.udas)
                    {
                        s = new AST.UserAttributeDeclaration(pAttrs.udas, a);
                        pAttrs.udas = null;
                    }
                    break;
                }
            case TOK.extern_:
                {
                    if (peekNext() != TOK.leftParenthesis)
                    {
                        stc = STC.extern_;
                        goto Lstc;
                    }

                    const linkLoc = token.loc;
                    auto res = parseLinkage();
                    if (pAttrs.link != LINK.default_)
                    {
                        if (pAttrs.link != res.link)
                        {
                            error("conflicting linkage `extern (%s)` and `extern (%s)`", AST.linkageToChars(pAttrs.link), AST.linkageToChars(res.link));
                        }
                        else if (res.idents || res.identExps || res.cppmangle != CPPMANGLE.def)
                        {
                            // Allow:
                            //      extern(C++, foo) extern(C++, bar) void foo();
                            // to be equivalent with:
                            //      extern(C++, foo.bar) void foo();
                            // Allow also:
                            //      extern(C++, "ns") extern(C++, class) struct test {}
                            //      extern(C++, class) extern(C++, "ns") struct test {}
                        }
                        else
                            error("redundant linkage `extern (%s)`", AST.linkageToChars(pAttrs.link));
                    }
                    pAttrs.link = res.link;
                    this.linkage = res.link;
                    this.linkLoc = linkLoc;
                    a = parseBlock(pLastDecl, pAttrs);
                    if (res.idents)
                    {
                        assert(res.link == LINK.cpp);
                        assert(res.idents.dim);
                        for (size_t i = res.idents.dim; i;)
                        {
                            Identifier id = (*res.idents)[--i];
                            if (s)
                            {
                                a = new AST.Dsymbols();
                                a.push(s);
                            }
                            s = new AST.Nspace(linkLoc, id, null, a);
                        }
                        pAttrs.link = LINK.default_;
                    }
                    else if (res.identExps)
                    {
                        assert(res.link == LINK.cpp);
                        assert(res.identExps.dim);
                        for (size_t i = res.identExps.dim; i;)
                        {
                            AST.Expression exp = (*res.identExps)[--i];
                            if (s)
                            {
                                a = new AST.Dsymbols();
                                a.push(s);
                            }
                            s = new AST.CPPNamespaceDeclaration(linkLoc, exp, a);
                        }
                        pAttrs.link = LINK.default_;
                    }
                    else if (res.cppmangle != CPPMANGLE.def)
                    {
                        assert(res.link == LINK.cpp);
                        s = new AST.CPPMangleDeclaration(linkLoc, res.cppmangle, a);
                    }
                    else if (pAttrs.link != LINK.default_)
                    {
                        s = new AST.LinkDeclaration(linkLoc, pAttrs.link, a);
                        pAttrs.link = LINK.default_;
                    }
                    break;
                }

            case TOK.private_:
                prot = AST.Visibility.Kind.private_;
                goto Lprot;

            case TOK.package_:
                prot = AST.Visibility.Kind.package_;
                goto Lprot;

            case TOK.protected_:
                prot = AST.Visibility.Kind.protected_;
                goto Lprot;

            case TOK.public_:
                prot = AST.Visibility.Kind.public_;
                goto Lprot;

            case TOK.export_:
                prot = AST.Visibility.Kind.export_;
                goto Lprot;
            Lprot:
                {
                    if (pAttrs.visibility.kind != AST.Visibility.Kind.undefined)
                    {
                        if (pAttrs.visibility.kind != prot)
                            error("conflicting visibility attribute `%s` and `%s`", AST.visibilityToChars(pAttrs.visibility.kind), AST.visibilityToChars(prot));
                        else
                            error("redundant visibility attribute `%s`", AST.visibilityToChars(prot));
                    }
                    pAttrs.visibility.kind = prot;
                    const attrloc = token.loc;

                    nextToken();

                    // optional qualified package identifier to bind
                    // visibility to
                    Identifier[] pkg_prot_idents;
                    if (pAttrs.visibility.kind == AST.Visibility.Kind.package_ && token.value == TOK.leftParenthesis)
                    {
                        pkg_prot_idents = parseQualifiedIdentifier("protection package");
                        if (pkg_prot_idents)
                            check(TOK.rightParenthesis);
                        else
                        {
                            while (token.value != TOK.semicolon && token.value != TOK.endOfFile)
                                nextToken();
                            nextToken();
                            break;
                        }
                    }

                    a = parseBlock(pLastDecl, pAttrs);
                    if (pAttrs.visibility.kind != AST.Visibility.Kind.undefined)
                    {
                        if (pAttrs.visibility.kind == AST.Visibility.Kind.package_ && pkg_prot_idents)
                            s = new AST.VisibilityDeclaration(attrloc, pkg_prot_idents, a);
                        else
                            s = new AST.VisibilityDeclaration(attrloc, pAttrs.visibility, a);

                        pAttrs.visibility = AST.Visibility(AST.Visibility.Kind.undefined);
                    }
                    break;
                }
            case TOK.align_:
                {
                    const attrLoc = token.loc;

                    nextToken();

                    AST.Expression e = null; // default
                    if (token.value == TOK.leftParenthesis)
                    {
                        nextToken();
                        e = parseAssignExp();
                        check(TOK.rightParenthesis);
                    }

                    if (pAttrs.setAlignment)
                    {
                        if (e)
                            error("redundant alignment attribute `align(%s)`", e.toChars());
                        else
                            error("redundant alignment attribute `align`");
                    }

                    pAttrs.setAlignment = true;
                    pAttrs.ealign = e;
                    a = parseBlock(pLastDecl, pAttrs);
                    if (pAttrs.setAlignment)
                    {
                        s = new AST.AlignDeclaration(attrLoc, pAttrs.ealign, a);
                        pAttrs.setAlignment = false;
                        pAttrs.ealign = null;
                    }
                    break;
                }
            case TOK.pragma_:
                {
                    AST.Expressions* args = null;
                    const loc = token.loc;

                    nextToken();
                    check(TOK.leftParenthesis);
                    if (token.value != TOK.identifier)
                    {
                        error("`pragma(identifier)` expected");
                        goto Lerror;
                    }
                    Identifier ident = token.ident;
                    nextToken();
                    if (token.value == TOK.comma && peekNext() != TOK.rightParenthesis)
                        args = parseArguments(); // pragma(identifier, args...)
                    else
                        check(TOK.rightParenthesis); // pragma(identifier)

                    AST.Dsymbols* a2 = null;
                    if (token.value == TOK.semicolon)
                    {
                        /* https://issues.dlang.org/show_bug.cgi?id=2354
                         * Accept single semicolon as an empty
                         * DeclarationBlock following attribute.
                         *
                         * Attribute DeclarationBlock
                         * Pragma    DeclDef
                         *           ;
                         */
                        nextToken();
                    }
                    else
                        a2 = parseBlock(pLastDecl);
                    s = new AST.PragmaDeclaration(loc, ident, args, a2);
                    break;
                }
            case TOK.debug_:
                startloc = token.loc;
                nextToken();
                if (token.value == TOK.assign)
                {
                    s = parseDebugSpecification();
                    break;
                }
                condition = parseDebugCondition();
                goto Lcondition;

            case TOK.version_:
                startloc = token.loc;
                nextToken();
                if (token.value == TOK.assign)
                {
                    s = parseVersionSpecification();
                    break;
                }
                condition = parseVersionCondition();
                goto Lcondition;

            Lcondition:
                {
                    AST.Dsymbols* athen;
                    if (token.value == TOK.colon)
                        athen = parseBlock(pLastDecl);
                    else
                    {
                        const lookingForElseSave = lookingForElse;
                        lookingForElse = token.loc;
                        athen = parseBlock(pLastDecl);
                        lookingForElse = lookingForElseSave;
                    }
                    AST.Dsymbols* aelse = null;
                    if (token.value == TOK.else_)
                    {
                        const elseloc = token.loc;
                        nextToken();
                        aelse = parseBlock(pLastDecl);
                        checkDanglingElse(elseloc);
                    }
                    s = new AST.ConditionalDeclaration(startloc, condition, athen, aelse);
                    break;
                }
            case TOK.semicolon:
                // empty declaration
                //error("empty declaration");
                nextToken();
                continue;

            default:
                error("declaration expected, not `%s`", token.toChars());
            Lerror:
                while (token.value != TOK.semicolon && token.value != TOK.endOfFile)
                    nextToken();
                nextToken();
                s = null;
                continue;
            }

            if (s)
            {
                if (!s.isAttribDeclaration())
                    *pLastDecl = s;
                decldefs.push(s);
                addComment(s, pAttrs.comment);
            }
            else if (a && a.dim)
            {
                decldefs.append(a);
            }
        }
        while (!once);

        linkage = linksave;

        return decldefs;
    }

    /*****************************************
     * Parse auto declarations of the form:
     *   storageClass ident = init, ident = init, ... ;
     * and return the array of them.
     * Starts with token on the first ident.
     * Ends with scanner past closing ';'
     */
    private AST.Dsymbols* parseAutoDeclarations(StorageClass storageClass, const(char)* comment)
    {
        //printf("parseAutoDeclarations\n");
        auto a = new AST.Dsymbols();

        while (1)
        {
            const loc = token.loc;
            Identifier ident = token.ident;
            nextToken(); // skip over ident

            AST.TemplateParameters* tpl = null;
            if (token.value == TOK.leftParenthesis)
                tpl = parseTemplateParameterList();

            check(TOK.assign);   // skip over '='
            AST.Initializer _init = parseInitializer();
            auto v = new AST.VarDeclaration(loc, null, ident, _init, storageClass);

            AST.Dsymbol s = v;
            if (tpl)
            {
                auto a2 = new AST.Dsymbols();
                a2.push(v);
                auto tempdecl = new AST.TemplateDeclaration(loc, ident, tpl, null, a2, 0);
                s = tempdecl;
            }
            a.push(s);
            switch (token.value)
            {
            case TOK.semicolon:
                nextToken();
                addComment(s, comment);
                break;

            case TOK.comma:
                nextToken();
                if (!(token.value == TOK.identifier && hasOptionalParensThen(peek(&token), TOK.assign)))
                {
                    error("identifier expected following comma");
                    break;
                }
                addComment(s, comment);
                continue;

            default:
                error("semicolon expected following auto declaration, not `%s`", token.toChars());
                break;
            }
            break;
        }
        return a;
    }

    /********************************************
     * Parse declarations after an align, visibility, or extern decl.
     */
    private AST.Dsymbols* parseBlock(AST.Dsymbol* pLastDecl, PrefixAttributes!AST* pAttrs = null)
    {
        AST.Dsymbols* a = null;

        //printf("parseBlock()\n");
        switch (token.value)
        {
        case TOK.semicolon:
            error("declaration expected following attribute, not `;`");
            nextToken();
            break;

        case TOK.endOfFile:
            error("declaration expected following attribute, not end of file");
            break;

        case TOK.leftCurly:
            {
                const lookingForElseSave = lookingForElse;
                lookingForElse = Loc();

                nextToken();
                a = parseDeclDefs(0, pLastDecl);
                if (token.value != TOK.rightCurly)
                {
                    /* { */
                    error("matching `}` expected, not `%s`", token.toChars());
                }
                else
                    nextToken();
                lookingForElse = lookingForElseSave;
                break;
            }
        case TOK.colon:
            nextToken();
            a = parseDeclDefs(0, pLastDecl); // grab declarations up to closing curly bracket
            break;

        default:
            a = parseDeclDefs(1, pLastDecl, pAttrs);
            break;
        }
        return a;
    }

    /**
     * Provide an error message if `added` contains storage classes which are
     * redundant with those in `orig`; otherwise, return the combination.
     *
     * Params:
     *   orig = The already applied storage class.
     *   added = The new storage class to add to `orig`.
     *
     * Returns:
     *   The combination of both storage classes (`orig | added`).
     */
    private StorageClass appendStorageClass(StorageClass orig, StorageClass added)
    {
        void checkConflictSTCGroup(bool at = false)(StorageClass group)
        {
            if (added & group && orig & group & ((orig & group) - 1))
                error(
                    at ? "conflicting attribute `@%s`"
                       : "conflicting attribute `%s`",
                    token.toChars());
        }

        if (orig & added)
        {
            OutBuffer buf;
            AST.stcToBuffer(&buf, added);
            error("redundant attribute `%s`", buf.peekChars());
            return orig | added;
        }

        const Redundant = (STC.const_ | STC.scope_ |
                           (global.params.previewIn ? STC.ref_ : 0));
        orig |= added;

        if ((orig & STC.in_) && (added & Redundant))
        {
            if (added & STC.const_)
                error("attribute `const` is redundant with previously-applied `in`");
            else if (global.params.previewIn)
            {
                error("attribute `%s` is redundant with previously-applied `in`",
                      (orig & STC.scope_) ? "scope".ptr : "ref".ptr);
            }
            else
                error("attribute `scope` cannot be applied with `in`, use `-preview=in` instead");
            return orig;
        }

        if ((added & STC.in_) && (orig & Redundant))
        {
            if (orig & STC.const_)
                error("attribute `in` cannot be added after `const`: remove `const`");
            else if (global.params.previewIn)
            {
                // Windows `printf` does not support `%1$s`
                const(char*) stc_str = (orig & STC.scope_) ? "scope".ptr : "ref".ptr;
                error("attribute `in` cannot be added after `%s`: remove `%s`",
                      stc_str, stc_str);
            }
            else
                error("attribute `in` cannot be added after `scope`: remove `scope` and use `-preview=in`");
            return orig;
        }

        checkConflictSTCGroup(STC.const_ | STC.immutable_ | STC.manifest);
        checkConflictSTCGroup(STC.gshared | STC.shared_);
        checkConflictSTCGroup!true(STC.safeGroup);

        return orig;
    }

    /***********************************************
     * Parse attribute(s), lexer is on '@'.
     *
     * Attributes can be builtin (e.g. `@safe`, `@nogc`, etc...),
     * or be user-defined (UDAs). In the former case, we return the storage
     * class via the return value, while in thelater case we return `0`
     * and set `pudas`.
     *
     * Params:
     *   pudas = An array of UDAs to append to
     *
     * Returns:
     *   If the attribute is builtin, the return value will be non-zero.
     *   Otherwise, 0 is returned, and `pudas` will be appended to.
     */
    private StorageClass parseAttribute(ref AST.Expressions* udas)
    {
        nextToken();
        if (token.value == TOK.identifier)
        {
            // If we find a builtin attribute, we're done, return immediately.
            if (StorageClass stc = isBuiltinAtAttribute(token.ident))
                return stc;

            // Allow identifier, template instantiation, or function call
            // for `@Argument` (single UDA) form.
            AST.Expression exp = parsePrimaryExp();
            if (token.value == TOK.leftParenthesis)
            {
                const loc = token.loc;
                exp = new AST.CallExp(loc, exp, parseArguments());
            }

            if (udas is null)
                udas = new AST.Expressions();
            udas.push(exp);
            return 0;
        }

        if (token.value == TOK.leftParenthesis)
        {
            // Multi-UDAs ( `@( ArgumentList )`) form, concatenate with existing
            if (peekNext() == TOK.rightParenthesis)
                error("empty attribute list is not allowed");
            udas = AST.UserAttributeDeclaration.concat(udas, parseArguments());
            return 0;
        }

        if (token.isKeyword())
            error("`%s` is a keyword, not an `@` attribute", token.toChars());
        else
            error("`@identifier` or `@(ArgumentList)` expected, not `@%s`", token.toChars());

        return 0;
    }

    /***********************************************
     * Parse const/immutable/shared/inout/nothrow/pure postfix
     */
    private StorageClass parsePostfix(StorageClass storageClass, AST.Expressions** pudas)
    {
        while (1)
        {
            StorageClass stc;
            switch (token.value)
            {
            case TOK.const_:
                stc = STC.const_;
                break;

            case TOK.immutable_:
                stc = STC.immutable_;
                break;

            case TOK.shared_:
                stc = STC.shared_;
                break;

            case TOK.inout_:
                stc = STC.wild;
                break;

            case TOK.nothrow_:
                stc = STC.nothrow_;
                break;

            case TOK.pure_:
                stc = STC.pure_;
                break;

            case TOK.return_:
                stc = STC.return_;
                if (peekNext() == TOK.scope_)
                    stc |= STC.returnScope;     // recognize `return scope`
                break;

            case TOK.scope_:
                stc = STC.scope_;
                break;

            case TOK.at:
                {
                    AST.Expressions* udas = null;
                    stc = parseAttribute(udas);
                    if (udas)
                    {
                        if (pudas)
                            *pudas = AST.UserAttributeDeclaration.concat(*pudas, udas);
                        else
                        {
                            // Disallow:
                            //      void function() @uda fp;
                            //      () @uda { return 1; }
                            error("user-defined attributes cannot appear as postfixes");
                        }
                        continue;
                    }
                    break;
                }
            default:
                return storageClass;
            }
            storageClass = appendStorageClass(storageClass, stc);
            nextToken();
        }
    }

    private StorageClass parseTypeCtor()
    {
        StorageClass storageClass = STC.undefined_;

        while (1)
        {
            if (peekNext() == TOK.leftParenthesis)
                return storageClass;

            StorageClass stc;
            switch (token.value)
            {
            case TOK.const_:
                stc = STC.const_;
                break;

            case TOK.immutable_:
                stc = STC.immutable_;
                break;

            case TOK.shared_:
                stc = STC.shared_;
                break;

            case TOK.inout_:
                stc = STC.wild;
                break;

            default:
                return storageClass;
            }
            storageClass = appendStorageClass(storageClass, stc);
            nextToken();
        }
    }

    /**************************************
     * Parse constraint.
     * Constraint is of the form:
     *      if ( ConstraintExpression )
     */
    private AST.Expression parseConstraint()
    {
        AST.Expression e = null;
        if (token.value == TOK.if_)
        {
            nextToken(); // skip over 'if'
            check(TOK.leftParenthesis);
            e = parseExpression();
            check(TOK.rightParenthesis);
        }
        return e;
    }

    /**************************************
     * Parse a TemplateDeclaration.
     */
    private AST.TemplateDeclaration parseTemplateDeclaration(bool ismixin = false)
    {
        AST.TemplateDeclaration tempdecl;
        Identifier id;
        AST.TemplateParameters* tpl;
        AST.Dsymbols* decldefs;
        AST.Expression constraint = null;
        const loc = token.loc;

        nextToken();
        if (token.value != TOK.identifier)
        {
            error("identifier expected following `template`");
            goto Lerr;
        }
        id = token.ident;
        nextToken();
        tpl = parseTemplateParameterList();
        if (!tpl)
            goto Lerr;

        constraint = parseConstraint();

        if (token.value != TOK.leftCurly)
        {
            error("`{` expected after template parameter list, not `%s`", token.toChars());
            goto Lerr;
        }
        decldefs = parseBlock(null);

        tempdecl = new AST.TemplateDeclaration(loc, id, tpl, constraint, decldefs, ismixin);
        return tempdecl;

    Lerr:
        return null;
    }

    /******************************************
     * Parse template parameter list.
     * Input:
     *      flag    0: parsing "( list )"
     *              1: parsing non-empty "list $(RPAREN)"
     */
    private AST.TemplateParameters* parseTemplateParameterList(int flag = 0)
    {
        auto tpl = new AST.TemplateParameters();

        if (!flag && token.value != TOK.leftParenthesis)
        {
            error("parenthesized template parameter list expected following template identifier");
            goto Lerr;
        }
        nextToken();

        // Get array of TemplateParameters
        if (flag || token.value != TOK.rightParenthesis)
        {
            while (token.value != TOK.rightParenthesis)
            {
                AST.TemplateParameter tp;
                Loc loc;
                Identifier tp_ident = null;
                AST.Type tp_spectype = null;
                AST.Type tp_valtype = null;
                AST.Type tp_defaulttype = null;
                AST.Expression tp_specvalue = null;
                AST.Expression tp_defaultvalue = null;

                // Get TemplateParameter

                // First, look ahead to see if it is a TypeParameter or a ValueParameter
                const tv = peekNext();
                if (token.value == TOK.alias_)
                {
                    // AliasParameter
                    nextToken();
                    loc = token.loc; // todo
                    AST.Type spectype = null;
                    if (isDeclaration(&token, NeedDeclaratorId.must, TOK.reserved, null))
                    {
                        spectype = parseType(&tp_ident);
                    }
                    else
                    {
                        if (token.value != TOK.identifier)
                        {
                            error("identifier expected for template `alias` parameter");
                            goto Lerr;
                        }
                        tp_ident = token.ident;
                        nextToken();
                    }
                    RootObject spec = null;
                    if (token.value == TOK.colon) // : Type
                    {
                        nextToken();
                        if (isDeclaration(&token, NeedDeclaratorId.no, TOK.reserved, null))
                            spec = parseType();
                        else
                            spec = parseCondExp();
                    }
                    RootObject def = null;
                    if (token.value == TOK.assign) // = Type
                    {
                        nextToken();
                        if (isDeclaration(&token, NeedDeclaratorId.no, TOK.reserved, null))
                            def = parseType();
                        else
                            def = parseCondExp();
                    }
                    tp = new AST.TemplateAliasParameter(loc, tp_ident, spectype, spec, def);
                }
                else if (tv == TOK.colon || tv == TOK.assign || tv == TOK.comma || tv == TOK.rightParenthesis)
                {
                    // TypeParameter
                    if (token.value != TOK.identifier)
                    {
                        error("identifier expected for template type parameter");
                        goto Lerr;
                    }
                    loc = token.loc;
                    tp_ident = token.ident;
                    nextToken();
                    if (token.value == TOK.colon) // : Type
                    {
                        nextToken();
                        tp_spectype = parseType();
                    }
                    if (token.value == TOK.assign) // = Type
                    {
                        nextToken();
                        tp_defaulttype = parseType();
                    }
                    tp = new AST.TemplateTypeParameter(loc, tp_ident, tp_spectype, tp_defaulttype);
                }
                else if (token.value == TOK.identifier && tv == TOK.dotDotDot)
                {
                    // ident...
                    loc = token.loc;
                    tp_ident = token.ident;
                    nextToken();
                    nextToken();
                    tp = new AST.TemplateTupleParameter(loc, tp_ident);
                }
                else if (token.value == TOK.this_)
                {
                    // ThisParameter
                    nextToken();
                    if (token.value != TOK.identifier)
                    {
                        error("identifier expected for template `this` parameter");
                        goto Lerr;
                    }
                    loc = token.loc;
                    tp_ident = token.ident;
                    nextToken();
                    if (token.value == TOK.colon) // : Type
                    {
                        nextToken();
                        tp_spectype = parseType();
                    }
                    if (token.value == TOK.assign) // = Type
                    {
                        nextToken();
                        tp_defaulttype = parseType();
                    }
                    tp = new AST.TemplateThisParameter(loc, tp_ident, tp_spectype, tp_defaulttype);
                }
                else
                {
                    // ValueParameter
                    loc = token.loc; // todo
                    tp_valtype = parseType(&tp_ident);
                    if (!tp_ident)
                    {
                        error("identifier expected for template value parameter");
                        tp_ident = Identifier.idPool("error");
                    }
                    if (token.value == TOK.colon) // : CondExpression
                    {
                        nextToken();
                        tp_specvalue = parseCondExp();
                    }
                    if (token.value == TOK.assign) // = CondExpression
                    {
                        nextToken();
                        tp_defaultvalue = parseDefaultInitExp();
                    }
                    tp = new AST.TemplateValueParameter(loc, tp_ident, tp_valtype, tp_specvalue, tp_defaultvalue);
                }
                tpl.push(tp);
                if (token.value != TOK.comma)
                    break;
                nextToken();
            }
        }
        check(TOK.rightParenthesis);

    Lerr:
        return tpl;
    }

    /******************************************
     * Parse template mixin.
     *      mixin Foo;
     *      mixin Foo!(args);
     *      mixin a.b.c!(args).Foo!(args);
     *      mixin Foo!(args) identifier;
     *      mixin typeof(expr).identifier!(args);
     */
    private AST.Dsymbol parseMixin()
    {
        AST.TemplateMixin tm;
        Identifier id;
        AST.Objects* tiargs;

        //printf("parseMixin()\n");
        const locMixin = token.loc;
        nextToken(); // skip 'mixin'

        auto loc = token.loc;
        AST.TypeQualified tqual = null;
        if (token.value == TOK.dot)
        {
            id = Id.empty;
        }
        else
        {
            if (token.value == TOK.typeof_)
            {
                tqual = parseTypeof();
                check(TOK.dot);
            }
            if (token.value != TOK.identifier)
            {
                error("identifier expected, not `%s`", token.toChars());
                id = Id.empty;
            }
            else
                id = token.ident;
            nextToken();
        }

        while (1)
        {
            tiargs = null;
            if (token.value == TOK.not)
            {
                tiargs = parseTemplateArguments();
            }

            if (tiargs && token.value == TOK.dot)
            {
                auto tempinst = new AST.TemplateInstance(loc, id, tiargs);
                if (!tqual)
                    tqual = new AST.TypeInstance(loc, tempinst);
                else
                    tqual.addInst(tempinst);
                tiargs = null;
            }
            else
            {
                if (!tqual)
                    tqual = new AST.TypeIdentifier(loc, id);
                else
                    tqual.addIdent(id);
            }

            if (token.value != TOK.dot)
                break;

            nextToken();
            if (token.value != TOK.identifier)
            {
                error("identifier expected following `.` instead of `%s`", token.toChars());
                break;
            }
            loc = token.loc;
            id = token.ident;
            nextToken();
        }

        id = null;
        if (token.value == TOK.identifier)
        {
            id = token.ident;
            nextToken();
        }

        tm = new AST.TemplateMixin(locMixin, id, tqual, tiargs);
        if (token.value != TOK.semicolon)
            error("`;` expected after `mixin`");
        nextToken();

        return tm;
    }

    /******************************************
     * Parse template arguments.
     * Input:
     *      current token is opening '!'
     * Output:
     *      current token is one after closing '$(RPAREN)'
     */
    private AST.Objects* parseTemplateArguments()
    {
        AST.Objects* tiargs;

        nextToken();
        if (token.value == TOK.leftParenthesis)
        {
            // ident!(template_arguments)
            tiargs = parseTemplateArgumentList();
        }
        else
        {
            // ident!template_argument
            tiargs = parseTemplateSingleArgument();
        }
        if (token.value == TOK.not)
        {
            TOK tok = peekNext();
            if (tok != TOK.is_ && tok != TOK.in_)
            {
                error("multiple ! arguments are not allowed");
            Lagain:
                nextToken();
                if (token.value == TOK.leftParenthesis)
                    parseTemplateArgumentList();
                else
                    parseTemplateSingleArgument();
                if (token.value == TOK.not && (tok = peekNext()) != TOK.is_ && tok != TOK.in_)
                    goto Lagain;
            }
        }
        return tiargs;
    }

    /******************************************
     * Parse template argument list.
     * Input:
     *      current token is opening '$(LPAREN)',
     *          or ',' for __traits
     * Output:
     *      current token is one after closing '$(RPAREN)'
     */
    private AST.Objects* parseTemplateArgumentList()
    {
        //printf("Parser::parseTemplateArgumentList()\n");
        auto tiargs = new AST.Objects();
        TOK endtok = TOK.rightParenthesis;
        assert(token.value == TOK.leftParenthesis || token.value == TOK.comma);
        nextToken();

        // Get TemplateArgumentList
        while (token.value != endtok)
        {
            tiargs.push(parseTypeOrAssignExp());
            if (token.value != TOK.comma)
                break;
            nextToken();
        }
        check(endtok, "template argument list");
        return tiargs;
    }

    /***************************************
     * Parse a Type or an Expression
     * Returns:
     *  RootObject representing the AST
     */
    RootObject parseTypeOrAssignExp(TOK endtoken = TOK.reserved)
    {
        return isDeclaration(&token, NeedDeclaratorId.no, endtoken, null)
            ? parseType()           // argument is a type
            : parseAssignExp();     // argument is an expression
    }

    /*****************************
     * Parse single template argument, to support the syntax:
     *      foo!arg
     * Input:
     *      current token is the arg
     */
    private AST.Objects* parseTemplateSingleArgument()
    {
        //printf("parseTemplateSingleArgument()\n");
        auto tiargs = new AST.Objects();
        AST.Type ta;
        switch (token.value)
        {
        case TOK.identifier:
            ta = new AST.TypeIdentifier(token.loc, token.ident);
            goto LabelX;

        case TOK.vector:
            ta = parseVector();
            goto LabelX;

        case TOK.void_:
            ta = AST.Type.tvoid;
            goto LabelX;

        case TOK.int8:
            ta = AST.Type.tint8;
            goto LabelX;

        case TOK.uns8:
            ta = AST.Type.tuns8;
            goto LabelX;

        case TOK.int16:
            ta = AST.Type.tint16;
            goto LabelX;

        case TOK.uns16:
            ta = AST.Type.tuns16;
            goto LabelX;

        case TOK.int32:
            ta = AST.Type.tint32;
            goto LabelX;

        case TOK.uns32:
            ta = AST.Type.tuns32;
            goto LabelX;

        case TOK.int64:
            ta = AST.Type.tint64;
            goto LabelX;

        case TOK.uns64:
            ta = AST.Type.tuns64;
            goto LabelX;

        case TOK.int128:
            ta = AST.Type.tint128;
            goto LabelX;

        case TOK.uns128:
            ta = AST.Type.tuns128;
            goto LabelX;

        case TOK.float32:
            ta = AST.Type.tfloat32;
            goto LabelX;

        case TOK.float64:
            ta = AST.Type.tfloat64;
            goto LabelX;

        case TOK.float80:
            ta = AST.Type.tfloat80;
            goto LabelX;

        case TOK.imaginary32:
            ta = AST.Type.timaginary32;
            goto LabelX;

        case TOK.imaginary64:
            ta = AST.Type.timaginary64;
            goto LabelX;

        case TOK.imaginary80:
            ta = AST.Type.timaginary80;
            goto LabelX;

        case TOK.complex32:
            ta = AST.Type.tcomplex32;
            goto LabelX;

        case TOK.complex64:
            ta = AST.Type.tcomplex64;
            goto LabelX;

        case TOK.complex80:
            ta = AST.Type.tcomplex80;
            goto LabelX;

        case TOK.bool_:
            ta = AST.Type.tbool;
            goto LabelX;

        case TOK.char_:
            ta = AST.Type.tchar;
            goto LabelX;

        case TOK.wchar_:
            ta = AST.Type.twchar;
            goto LabelX;

        case TOK.dchar_:
            ta = AST.Type.tdchar;
            goto LabelX;
        LabelX:
            tiargs.push(ta);
            nextToken();
            break;

        case TOK.int32Literal:
        case TOK.uns32Literal:
        case TOK.int64Literal:
        case TOK.uns64Literal:
        case TOK.int128Literal:
        case TOK.uns128Literal:
        case TOK.float32Literal:
        case TOK.float64Literal:
        case TOK.float80Literal:
        case TOK.imaginary32Literal:
        case TOK.imaginary64Literal:
        case TOK.imaginary80Literal:
        case TOK.null_:
        case TOK.true_:
        case TOK.false_:
        case TOK.charLiteral:
        case TOK.wcharLiteral:
        case TOK.dcharLiteral:
        case TOK.string_:
        case TOK.file:
        case TOK.fileFullPath:
        case TOK.line:
        case TOK.moduleString:
        case TOK.functionString:
        case TOK.prettyFunction:
        case TOK.this_:
            {
                // Template argument is an expression
                AST.Expression ea = parsePrimaryExp();
                tiargs.push(ea);
                break;
            }
        default:
            error("template argument expected following `!`");
            break;
        }
        return tiargs;
    }

    /**********************************
     * Parse a static assertion.
     * Current token is 'static'.
     */
    private AST.StaticAssert parseStaticAssert()
    {
        const loc = token.loc;
        AST.Expression exp;
        AST.Expression msg = null;

        //printf("parseStaticAssert()\n");
        nextToken();
        nextToken();
        check(TOK.leftParenthesis);
        exp = parseAssignExp();
        if (token.value == TOK.comma)
        {
            nextToken();
            if (token.value != TOK.rightParenthesis)
            {
                msg = parseAssignExp();
                if (token.value == TOK.comma)
                    nextToken();
            }
        }
        check(TOK.rightParenthesis);
        check(TOK.semicolon, "static assert");
        return new AST.StaticAssert(loc, exp, msg);
    }

    /***********************************
     * Parse typeof(expression).
     * Current token is on the 'typeof'.
     */
    private AST.TypeQualified parseTypeof()
    {
        AST.TypeQualified t;
        const loc = token.loc;

        nextToken();
        check(TOK.leftParenthesis);
        if (token.value == TOK.return_) // typeof(return)
        {
            nextToken();
            t = new AST.TypeReturn(loc);
        }
        else
        {
            AST.Expression exp = parseExpression(); // typeof(expression)
            t = new AST.TypeTypeof(loc, exp);
        }
        check(TOK.rightParenthesis);
        return t;
    }

    /***********************************
     * Parse __vector(type).
     * Current token is on the '__vector'.
     */
    private AST.Type parseVector()
    {
        nextToken();
        check(TOK.leftParenthesis);
        AST.Type tb = parseType();
        check(TOK.rightParenthesis);
        return new AST.TypeVector(tb);
    }

    /***********************************
     * Parse:
     *      extern (linkage)
     *      extern (C++, namespaces)
     *      extern (C++, "namespace", "namespaces", ...)
     *      extern (C++, (StringExp))
     * The parser is on the 'extern' token.
     */
    private ParsedLinkage!(AST) parseLinkage()
    {
        ParsedLinkage!(AST) result;
        nextToken();
        assert(token.value == TOK.leftParenthesis);
        nextToken();
        ParsedLinkage!(AST) returnLinkage(LINK link)
        {
            check(TOK.rightParenthesis);
            result.link = link;
            return result;
        }
        ParsedLinkage!(AST) invalidLinkage()
        {
            error("valid linkage identifiers are `D`, `C`, `C++`, `Objective-C`, `Windows`, `System`");
            return returnLinkage(LINK.d);
        }

        if (token.value != TOK.identifier)
            return returnLinkage(LINK.d);

        Identifier id = token.ident;
        nextToken();
        if (id == Id.Windows)
            return returnLinkage(LINK.windows);
        else if (id == Id.D)
            return returnLinkage(LINK.d);
        else if (id == Id.System)
            return returnLinkage(LINK.system);
        else if (id == Id.Objective) // Looking for tokens "Objective-C"
        {
            if (token.value != TOK.min)
                return invalidLinkage();

            nextToken();
            if (token.ident != Id.C)
                return invalidLinkage();

            nextToken();
            return returnLinkage(LINK.objc);
        }
        else if (id != Id.C)
            return invalidLinkage();

        if (token.value != TOK.plusPlus)
            return returnLinkage(LINK.c);

        nextToken();
        if (token.value != TOK.comma) // , namespaces or class or struct
            return returnLinkage(LINK.cpp);

        nextToken();

        if (token.value == TOK.rightParenthesis)
            return returnLinkage(LINK.cpp); // extern(C++,)

        if (token.value == TOK.class_ || token.value == TOK.struct_)
        {
            result.cppmangle = token.value == TOK.class_ ? CPPMANGLE.asClass : CPPMANGLE.asStruct;
            nextToken();
        }
        else if (token.value == TOK.identifier) // named scope namespace
        {
            result.idents = new AST.Identifiers();
            while (1)
            {
                Identifier idn = token.ident;
                result.idents.push(idn);
                nextToken();
                if (token.value == TOK.dot)
                {
                    nextToken();
                    if (token.value == TOK.identifier)
                        continue;
                    error("identifier expected for C++ namespace");
                    result.idents = null;  // error occurred, invalidate list of elements.
                }
                break;
            }
        }
        else // non-scoped StringExp namespace
        {
            result.identExps = new AST.Expressions();
            while (1)
            {
                result.identExps.push(parseCondExp());
                if (token.value != TOK.comma)
                    break;
                nextToken();
                // Allow trailing commas as done for argument lists, arrays, ...
                if (token.value == TOK.rightParenthesis)
                    break;
            }
        }
        return returnLinkage(LINK.cpp);
    }

    /***********************************
     * Parse ident1.ident2.ident3
     *
     * Params:
     *  entity = what qualified identifier is expected to resolve into.
     *     Used only for better error message
     *
     * Returns:
     *     array of identifiers with actual qualified one stored last
     */
    private Identifier[] parseQualifiedIdentifier(const(char)* entity)
    {
        Identifier[] qualified;

        do
        {
            nextToken();
            if (token.value != TOK.identifier)
            {
                error("`%s` expected as dot-separated identifiers, got `%s`", entity, token.toChars());
                return qualified;
            }

            Identifier id = token.ident;
            qualified ~= id;

            nextToken();
        }
        while (token.value == TOK.dot);

        return qualified;
    }

    private AST.DebugSymbol parseDebugSpecification()
    {
        AST.DebugSymbol s;
        nextToken();
        if (token.value == TOK.identifier)
            s = new AST.DebugSymbol(token.loc, token.ident);
        else if (token.value == TOK.int32Literal || token.value == TOK.int64Literal)
        {
            // @@@DEPRECATED_2.111@@@
            // Deprecated in 2.101, remove in 2.111
            deprecation("`debug = <integer>` is deprecated, use debug identifiers instead");

            s = new AST.DebugSymbol(token.loc, cast(uint)token.unsvalue);
        }
        else
        {
            error("identifier or integer expected, not `%s`", token.toChars());
            s = null;
        }
        nextToken();
        if (token.value != TOK.semicolon)
            error("semicolon expected");
        nextToken();
        return s;
    }

    /**************************************
     * Parse a debug conditional
     */
    private AST.Condition parseDebugCondition()
    {
        uint level = 1;
        Identifier id = null;
        Loc loc = token.loc;

        if (token.value == TOK.leftParenthesis)
        {
            nextToken();

            if (token.value == TOK.identifier)
                id = token.ident;
            else if (token.value == TOK.int32Literal || token.value == TOK.int64Literal)
            {
                // @@@DEPRECATED_2.111@@@
                // Deprecated in 2.101, remove in 2.111
                deprecation("`debug( <integer> )` is deprecated, use debug identifiers instead");

                level = cast(uint)token.unsvalue;
            }
            else
                error("identifier or integer expected inside `debug(...)`, not `%s`", token.toChars());
            loc = token.loc;
            nextToken();
            check(TOK.rightParenthesis);
        }
        return new AST.DebugCondition(loc, mod, level, id);
    }

    /**************************************
     * Parse a version specification
     */
    private AST.VersionSymbol parseVersionSpecification()
    {
        AST.VersionSymbol s;
        nextToken();
        if (token.value == TOK.identifier)
            s = new AST.VersionSymbol(token.loc, token.ident);
        else if (token.value == TOK.int32Literal || token.value == TOK.int64Literal)
        {
            // @@@DEPRECATED_2.111@@@
            // Deprecated in 2.101, remove in 2.111
            deprecation("`version = <integer>` is deprecated, use version identifiers instead");
            s = new AST.VersionSymbol(token.loc, cast(uint)token.unsvalue);
        }
        else
        {
            error("identifier or integer expected, not `%s`", token.toChars());
            s = null;
        }
        nextToken();
        if (token.value != TOK.semicolon)
            error("semicolon expected");
        nextToken();
        return s;
    }

    /**************************************
     * Parse a version conditional
     */
    private AST.Condition parseVersionCondition()
    {
        uint level = 1;
        Identifier id = null;
        Loc loc;

        if (token.value == TOK.leftParenthesis)
        {
            nextToken();
            /* Allow:
             *    version (unittest)
             *    version (assert)
             * even though they are keywords
             */
            loc = token.loc;
            if (token.value == TOK.identifier)
                id = token.ident;
            else if (token.value == TOK.int32Literal || token.value == TOK.int64Literal)
            {
                // @@@DEPRECATED_2.111@@@
                // Deprecated in 2.101, remove in 2.111
                deprecation("`version( <integer> )` is deprecated, use version identifiers instead");

                level = cast(uint)token.unsvalue;
            }
            else if (token.value == TOK.unittest_)
                id = Identifier.idPool(Token.toString(TOK.unittest_));
            else if (token.value == TOK.assert_)
                id = Identifier.idPool(Token.toString(TOK.assert_));
            else
                error("identifier or integer expected inside `version(...)`, not `%s`", token.toChars());
            nextToken();
            check(TOK.rightParenthesis);
        }
        else
            error("(condition) expected following `version`");
        return new AST.VersionCondition(loc, mod, level, id);
    }

    /***********************************************
     *      static if (expression)
     *          body
     *      else
     *          body
     * Current token is 'static'.
     */
    private AST.Condition parseStaticIfCondition()
    {
        AST.Expression exp;
        AST.Condition condition;
        const loc = token.loc;

        nextToken();
        nextToken();
        if (token.value == TOK.leftParenthesis)
        {
            nextToken();
            exp = parseAssignExp();
            check(TOK.rightParenthesis);
        }
        else
        {
            error("(expression) expected following `static if`");
            exp = null;
        }
        condition = new AST.StaticIfCondition(loc, exp);
        return condition;
    }

    /*****************************************
     * Parse a constructor definition:
     *      this(parameters) { body }
     * or postblit:
     *      this(this) { body }
     * or constructor template:
     *      this(templateparameters)(parameters) { body }
     * Current token is 'this'.
     */
    private AST.Dsymbol parseCtor(PrefixAttributes!AST* pAttrs)
    {
        AST.Expressions* udas = null;
        const loc = token.loc;
        StorageClass stc = getStorageClass!AST(pAttrs);

        nextToken();
        if (token.value == TOK.leftParenthesis && peekNext() == TOK.this_ && peekNext2() == TOK.rightParenthesis)
        {
            // this(this) { ... }
            nextToken();
            nextToken();
            check(TOK.rightParenthesis);

            stc = parsePostfix(stc, &udas);
            if (stc & STC.immutable_)
                deprecation("`immutable` postblit is deprecated. Please use an unqualified postblit.");
            if (stc & STC.shared_)
                deprecation("`shared` postblit is deprecated. Please use an unqualified postblit.");
            if (stc & STC.const_)
                deprecation("`const` postblit is deprecated. Please use an unqualified postblit.");
            if (stc & STC.static_)
                error(loc, "postblit cannot be `static`");

            auto f = new AST.PostBlitDeclaration(loc, Loc.initial, stc, Id.postblit);
            AST.Dsymbol s = parseContracts(f);
            if (udas)
            {
                auto a = new AST.Dsymbols();
                a.push(f);
                s = new AST.UserAttributeDeclaration(udas, a);
            }
            return s;
        }

        /* Look ahead to see if:
         *   this(...)(...)
         * which is a constructor template
         */
        AST.TemplateParameters* tpl = null;
        if (token.value == TOK.leftParenthesis && peekPastParen(&token).value == TOK.leftParenthesis)
        {
            tpl = parseTemplateParameterList();
        }

        /* Just a regular constructor
         */
        auto parameterList = parseParameterList(null);
        stc = parsePostfix(stc, &udas);

        if (parameterList.varargs != VarArg.none || AST.Parameter.dim(parameterList.parameters) != 0)
        {
            if (stc & STC.static_)
                error(loc, "constructor cannot be static");
        }
        else if (StorageClass ss = stc & (STC.shared_ | STC.static_)) // this()
        {
            if (ss == STC.static_)
                error(loc, "use `static this()` to declare a static constructor");
            else if (ss == (STC.shared_ | STC.static_))
                error(loc, "use `shared static this()` to declare a shared static constructor");
        }

        AST.Expression constraint = tpl ? parseConstraint() : null;

        AST.Type tf = new AST.TypeFunction(parameterList, null, linkage, stc); // RetrunType -> auto
        tf = tf.addSTC(stc);

        auto f = new AST.CtorDeclaration(loc, Loc.initial, stc, tf);
        AST.Dsymbol s = parseContracts(f);
        if (udas)
        {
            auto a = new AST.Dsymbols();
            a.push(f);
            s = new AST.UserAttributeDeclaration(udas, a);
        }

        if (tpl)
        {
            // Wrap a template around it
            auto decldefs = new AST.Dsymbols();
            decldefs.push(s);
            s = new AST.TemplateDeclaration(loc, f.ident, tpl, constraint, decldefs);
        }

        return s;
    }

    /*****************************************
     * Parse a destructor definition:
     *      ~this() { body }
     * Current token is '~'.
     */
    private AST.Dsymbol parseDtor(PrefixAttributes!AST* pAttrs)
    {
        AST.Expressions* udas = null;
        const loc = token.loc;
        StorageClass stc = getStorageClass!AST(pAttrs);

        nextToken();
        check(TOK.this_);
        check(TOK.leftParenthesis);
        check(TOK.rightParenthesis);

        stc = parsePostfix(stc, &udas);
        if (StorageClass ss = stc & (STC.shared_ | STC.static_))
        {
            if (ss == STC.static_)
                error(loc, "use `static ~this()` to declare a static destructor");
            else if (ss == (STC.shared_ | STC.static_))
                error(loc, "use `shared static ~this()` to declare a shared static destructor");
        }

        auto f = new AST.DtorDeclaration(loc, Loc.initial, stc, Id.dtor);
        AST.Dsymbol s = parseContracts(f);
        if (udas)
        {
            auto a = new AST.Dsymbols();
            a.push(f);
            s = new AST.UserAttributeDeclaration(udas, a);
        }
        return s;
    }

    /*****************************************
     * Parse a static constructor definition:
     *      static this() { body }
     * Current token is 'static'.
     */
    private AST.Dsymbol parseStaticCtor(PrefixAttributes!AST* pAttrs)
    {
        //Expressions *udas = NULL;
        const loc = token.loc;
        StorageClass stc = getStorageClass!AST(pAttrs);

        nextToken();
        nextToken();
        check(TOK.leftParenthesis);
        check(TOK.rightParenthesis);

        stc = parsePostfix(stc & ~STC.TYPECTOR, null) | stc;
        if (stc & STC.shared_)
            error(loc, "use `shared static this()` to declare a shared static constructor");
        else if (stc & STC.static_)
            appendStorageClass(stc, STC.static_); // complaint for the redundancy
        else if (StorageClass modStc = stc & STC.TYPECTOR)
        {
            OutBuffer buf;
            AST.stcToBuffer(&buf, modStc);
            error(loc, "static constructor cannot be `%s`", buf.peekChars());
        }
        stc &= ~(STC.static_ | STC.TYPECTOR);

        auto f = new AST.StaticCtorDeclaration(loc, Loc.initial, stc);
        AST.Dsymbol s = parseContracts(f);
        return s;
    }

    /*****************************************
     * Parse a static destructor definition:
     *      static ~this() { body }
     * Current token is 'static'.
     */
    private AST.Dsymbol parseStaticDtor(PrefixAttributes!AST* pAttrs)
    {
        AST.Expressions* udas = null;
        const loc = token.loc;
        StorageClass stc = getStorageClass!AST(pAttrs);

        nextToken();
        nextToken();
        check(TOK.this_);
        check(TOK.leftParenthesis);
        check(TOK.rightParenthesis);

        stc = parsePostfix(stc & ~STC.TYPECTOR, &udas) | stc;
        if (stc & STC.shared_)
            error(loc, "use `shared static ~this()` to declare a shared static destructor");
        else if (stc & STC.static_)
            appendStorageClass(stc, STC.static_); // complaint for the redundancy
        else if (StorageClass modStc = stc & STC.TYPECTOR)
        {
            OutBuffer buf;
            AST.stcToBuffer(&buf, modStc);
            error(loc, "static destructor cannot be `%s`", buf.peekChars());
        }
        stc &= ~(STC.static_ | STC.TYPECTOR);

        auto f = new AST.StaticDtorDeclaration(loc, Loc.initial, stc);
        AST.Dsymbol s = parseContracts(f);
        if (udas)
        {
            auto a = new AST.Dsymbols();
            a.push(f);
            s = new AST.UserAttributeDeclaration(udas, a);
        }
        return s;
    }

    /*****************************************
     * Parse a shared static constructor definition:
     *      shared static this() { body }
     * Current token is 'shared'.
     */
    private AST.Dsymbol parseSharedStaticCtor(PrefixAttributes!AST* pAttrs)
    {
        //Expressions *udas = NULL;
        const loc = token.loc;
        StorageClass stc = getStorageClass!AST(pAttrs);

        nextToken();
        nextToken();
        nextToken();
        check(TOK.leftParenthesis);
        check(TOK.rightParenthesis);

        stc = parsePostfix(stc & ~STC.TYPECTOR, null) | stc;
        if (StorageClass ss = stc & (STC.shared_ | STC.static_))
            appendStorageClass(stc, ss); // complaint for the redundancy
        else if (StorageClass modStc = stc & STC.TYPECTOR)
        {
            OutBuffer buf;
            AST.stcToBuffer(&buf, modStc);
            error(loc, "shared static constructor cannot be `%s`", buf.peekChars());
        }
        stc &= ~(STC.static_ | STC.TYPECTOR);

        auto f = new AST.SharedStaticCtorDeclaration(loc, Loc.initial, stc);
        AST.Dsymbol s = parseContracts(f);
        return s;
    }

    /*****************************************
     * Parse a shared static destructor definition:
     *      shared static ~this() { body }
     * Current token is 'shared'.
     */
    private AST.Dsymbol parseSharedStaticDtor(PrefixAttributes!AST* pAttrs)
    {
        AST.Expressions* udas = null;
        const loc = token.loc;
        StorageClass stc = getStorageClass!AST(pAttrs);

        nextToken();
        nextToken();
        nextToken();
        check(TOK.this_);
        check(TOK.leftParenthesis);
        check(TOK.rightParenthesis);

        stc = parsePostfix(stc & ~STC.TYPECTOR, &udas) | stc;
        if (StorageClass ss = stc & (STC.shared_ | STC.static_))
            appendStorageClass(stc, ss); // complaint for the redundancy
        else if (StorageClass modStc = stc & STC.TYPECTOR)
        {
            OutBuffer buf;
            AST.stcToBuffer(&buf, modStc);
            error(loc, "shared static destructor cannot be `%s`", buf.peekChars());
        }
        stc &= ~(STC.static_ | STC.TYPECTOR);

        auto f = new AST.SharedStaticDtorDeclaration(loc, Loc.initial, stc);
        AST.Dsymbol s = parseContracts(f);
        if (udas)
        {
            auto a = new AST.Dsymbols();
            a.push(f);
            s = new AST.UserAttributeDeclaration(udas, a);
        }
        return s;
    }

    /*****************************************
     * Parse an invariant definition:
     *      invariant { statements... }
     *      invariant() { statements... }
     *      invariant (expression);
     * Current token is 'invariant'.
     */
    private AST.Dsymbol parseInvariant(PrefixAttributes!AST* pAttrs)
    {
        const loc = token.loc;
        StorageClass stc = getStorageClass!AST(pAttrs);

        nextToken();
        if (token.value == TOK.leftParenthesis) // optional () or invariant (expression);
        {
            nextToken();
            if (token.value != TOK.rightParenthesis) // invariant (expression);
            {
                AST.Expression e = parseAssignExp(), msg = null;
                if (token.value == TOK.comma)
                {
                    nextToken();
                    if (token.value != TOK.rightParenthesis)
                    {
                        msg = parseAssignExp();
                        if (token.value == TOK.comma)
                            nextToken();
                    }
                }
                check(TOK.rightParenthesis);
                check(TOK.semicolon, "invariant");
                e = new AST.AssertExp(loc, e, msg);
                auto fbody = new AST.ExpStatement(loc, e);
                auto f = new AST.InvariantDeclaration(loc, token.loc, stc, null, fbody);
                return f;
            }
            nextToken();
        }

        auto fbody = parseStatement(ParseStatementFlags.curly);
        auto f = new AST.InvariantDeclaration(loc, token.loc, stc, null, fbody);
        return f;
    }

    /*****************************************
     * Parse a unittest definition:
     *      unittest { body }
     * Current token is 'unittest'.
     */
    private AST.Dsymbol parseUnitTest(PrefixAttributes!AST* pAttrs)
    {
        const loc = token.loc;
        StorageClass stc = getStorageClass!AST(pAttrs);

        nextToken();

        const(char)* begPtr = token.ptr + 1; // skip '{'
        const(char)* endPtr = null;
        AST.Statement sbody = parseStatement(ParseStatementFlags.curly, &endPtr);

        /** Extract unittest body as a string. Must be done eagerly since memory
         will be released by the lexer before doc gen. */
        char* docline = null;
        if (global.params.ddoc.doOutput && endPtr > begPtr)
        {
            /* Remove trailing whitespaces */
            for (const(char)* p = endPtr - 1; begPtr <= p && (*p == ' ' || *p == '\r' || *p == '\n' || *p == '\t'); --p)
            {
                endPtr = p;
            }

            size_t len = endPtr - begPtr;
            if (len > 0)
            {
                docline = cast(char*)mem.xmalloc_noscan(len + 2);
                memcpy(docline, begPtr, len);
                docline[len] = '\n'; // Terminate all lines by LF
                docline[len + 1] = '\0';
            }
        }

        auto f = new AST.UnitTestDeclaration(loc, token.loc, stc, docline);
        f.fbody = sbody;
        return f;
    }

    /*****************************************
     * Parse a new definition:
     *      @disable new();
     * Current token is 'new'.
     */
    private AST.Dsymbol parseNew(PrefixAttributes!AST* pAttrs)
    {
        const loc = token.loc;
        StorageClass stc = getStorageClass!AST(pAttrs);
        if (!(stc & STC.disable))
        {
            error("`new` allocator must be annotated with `@disabled`");
        }
        nextToken();

        /* @@@DEPRECATED_2.108@@@
         * After deprecation period (2.108), remove all code in the version(all) block.
         */
        version (all)
        {
            auto parameterList = parseParameterList(null);  // parameterList ignored
            if (parameterList.parameters.length > 0 || parameterList.varargs != VarArg.none)
                deprecation("`new` allocator with non-empty parameter list is deprecated");
            auto f = new AST.NewDeclaration(loc, stc);
            if (token.value != TOK.semicolon)
            {
                deprecation("`new` allocator with function definition is deprecated");
                parseContracts(f);  // body ignored
                f.fbody = null;
                f.fensures = null;
                f.frequires = null;
            }
            else
                nextToken();
            return f;
        }
        else
        {
            check(TOK.leftParenthesis);
            check(TOK.rightParenthesis);
            check(TOK.semicolon);
            return new AST.NewDeclaration(loc, stc);
        }
    }

    /**********************************************
     * Parse parameter list.
     */
    private AST.ParameterList parseParameterList(AST.TemplateParameters** tpl)
    {
        auto parameters = new AST.Parameters();
        VarArg varargs = VarArg.none;
        StorageClass varargsStc;

        // Attributes allowed for ...
        enum VarArgsStc = STC.const_ | STC.immutable_ | STC.shared_ | STC.scope_ | STC.return_ | STC.returnScope;

        check(TOK.leftParenthesis);
        while (1)
        {
            Identifier ai = null;
            AST.Type at;
            StorageClass storageClass = 0;
            StorageClass stc;
            AST.Expression ae;
            AST.Expressions* udas = null;
            for (; 1; nextToken())
            {
            L3:
                switch (token.value)
                {
                case TOK.rightParenthesis:
                    if (storageClass != 0 || udas !is null)
                        error("basic type expected, not `)`");
                    break;

                case TOK.dotDotDot:
                    varargs = VarArg.variadic;
                    varargsStc = storageClass;
                    if (varargsStc & ~VarArgsStc)
                    {
                        OutBuffer buf;
                        AST.stcToBuffer(&buf, varargsStc & ~VarArgsStc);
                        error("variadic parameter cannot have attributes `%s`", buf.peekChars());
                        varargsStc &= VarArgsStc;
                    }
                    nextToken();
                    break;

                case TOK.const_:
                    if (peekNext() == TOK.leftParenthesis)
                        goto default;
                    stc = STC.const_;
                    goto L2;

                case TOK.immutable_:
                    if (peekNext() == TOK.leftParenthesis)
                        goto default;
                    stc = STC.immutable_;
                    goto L2;

                case TOK.shared_:
                    if (peekNext() == TOK.leftParenthesis)
                        goto default;
                    stc = STC.shared_;
                    goto L2;

                case TOK.inout_:
                    if (peekNext() == TOK.leftParenthesis)
                        goto default;
                    stc = STC.wild;
                    goto L2;
                case TOK.at:
                    {
                        AST.Expressions* exps = null;
                        StorageClass stc2 = parseAttribute(exps);
                        if (stc2 & atAttrGroup)
                        {
                            error("`@%s` attribute for function parameter is not supported", token.toChars());
                        }
                        else
                        {
                            udas = AST.UserAttributeDeclaration.concat(udas, exps);
                        }
                        if (token.value == TOK.dotDotDot)
                            error("variadic parameter cannot have user-defined attributes");
                        if (stc2)
                            nextToken();
                        goto L3;
                        // Don't call nextToken again.
                    }
                case TOK.in_:
                    if (global.params.vin)
                        message(scanloc, "Usage of 'in' on parameter");
                    stc = STC.in_;
                    goto L2;

                case TOK.out_:
                    stc = STC.out_;
                    goto L2;

                case TOK.ref_:
                    stc = STC.ref_;
                    goto L2;

                case TOK.lazy_:
                    stc = STC.lazy_;
                    goto L2;

                case TOK.scope_:
                    stc = STC.scope_;
                    goto L2;

                case TOK.final_:
                    stc = STC.final_;
                    goto L2;

                case TOK.auto_:
                    stc = STC.auto_;
                    goto L2;

                case TOK.return_:
                    stc = STC.return_;
                    if (peekNext() == TOK.scope_)
                        stc |= STC.returnScope;
                    goto L2;
                L2:
                    storageClass = appendStorageClass(storageClass, stc);
                    continue;

                    version (none)
                    {
                    case TOK.static_:
                        stc = STC.static_;
                        goto L2;

                    case TOK.auto_:
                        storageClass = STC.auto_;
                        goto L4;

                    case TOK.alias_:
                        storageClass = STC.alias_;
                        goto L4;
                    L4:
                        nextToken();
                        ai = null;
                        if (token.value == TOK.identifier)
                        {
                            ai = token.ident;
                            nextToken();
                        }

                        at = null; // no type
                        ae = null; // no default argument
                        if (token.value == TOK.assign) // = defaultArg
                        {
                            nextToken();
                            ae = parseDefaultInitExp();
                            hasdefault = 1;
                        }
                        else
                        {
                            if (hasdefault)
                                error("default argument expected for `alias %s`", ai ? ai.toChars() : "");
                        }
                        goto L3;
                    }
                default:
                    {
                        stc = storageClass & (STC.IOR | STC.lazy_);
                        // if stc is not a power of 2
                        if (stc & (stc - 1) && !(stc == (STC.in_ | STC.ref_)))
                            error("incompatible parameter storage classes");
                        //if ((storageClass & STC.scope_) && (storageClass & (STC.ref_ | STC.out_)))
                            //error("scope cannot be ref or out");

                        const tv = peekNext();
                        if (tpl && token.value == TOK.identifier &&
                            (tv == TOK.comma || tv == TOK.rightParenthesis || tv == TOK.dotDotDot))
                        {
                            Identifier id = Identifier.generateId("__T");
                            const loc = token.loc;
                            at = new AST.TypeIdentifier(loc, id);
                            if (!*tpl)
                                *tpl = new AST.TemplateParameters();
                            AST.TemplateParameter tp = new AST.TemplateTypeParameter(loc, id, null, null);
                            (*tpl).push(tp);

                            ai = token.ident;
                            nextToken();
                        }
                        else
                        {
                            at = parseType(&ai);
                        }
                        ae = null;
                        if (token.value == TOK.assign) // = defaultArg
                        {
                            nextToken();
                            ae = parseDefaultInitExp();
                        }
                        auto param = new AST.Parameter(storageClass | STC.parameter, at, ai, ae, null);
                        if (udas)
                        {
                            auto a = new AST.Dsymbols();
                            auto udad = new AST.UserAttributeDeclaration(udas, a);
                            param.userAttribDecl = udad;
                        }
                        if (token.value == TOK.at)
                        {
                            AST.Expressions* exps = null;
                            StorageClass stc2 = parseAttribute(exps);
                            if (stc2 & atAttrGroup)
                            {
                                error("`@%s` attribute for function parameter is not supported", token.toChars());
                            }
                            else
                            {
                                error("user-defined attributes cannot appear as postfixes", token.toChars());
                            }
                            if (stc2)
                                nextToken();
                        }
                        if (token.value == TOK.dotDotDot)
                        {
                            /* This is:
                             *      at ai ...
                             */
                            if (storageClass & (STC.out_ | STC.ref_))
                                error("variadic argument cannot be `out` or `ref`");
                            varargs = VarArg.typesafe;
                            parameters.push(param);
                            nextToken();
                            break;
                        }
                        parameters.push(param);
                        if (token.value == TOK.comma)
                        {
                            nextToken();
                            goto L1;
                        }
                        break;
                    }
                }
                break;
            }
            break;

        L1:
        }
        check(TOK.rightParenthesis);
        return AST.ParameterList(parameters, varargs, varargsStc);
    }

    /*************************************
     */
    private AST.EnumDeclaration parseEnum()
    {
        AST.EnumDeclaration e;
        Identifier id;
        AST.Type memtype;
        auto loc = token.loc;

        // printf("Parser::parseEnum()\n");
        nextToken();
        id = null;
        if (token.value == TOK.identifier)
        {
            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 uad = new AST.UserAttributeDeclaration(udas, new AST.Dsymbols());
                    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
     * Returns:
     *  array of Declarations.
     */
    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());

            if (token.value == TOK.colon && !ident && t.ty != Tfunction)
            {
                // Unnamed bit field
                ident = Identifier.generateAnonymousId("BitField");
            }

            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.Expression width;
                if (token.value == TOK.colon)
                {
                    nextToken();
                    width = parseCondExp();
                }

                AST.Initializer _init = null;
                if (token.value == TOK.assign)
                {
                    nextToken();
                    _init = parseInitializer();
                }

                AST.Dsymbol s;
                if (width)
                {
                    if (!global.params.bitfields)
                        error("use -preview=bitfields for bitfield support");
                    if (_init)
                        error("initializer not allowed for bit-field declaration");
                    if (storage_class)
                        error("storage class not allowed for bit-field declaration");
                    s = new AST.BitFieldDeclaration(width.loc, t, ident, width);
                }
                else
                {
                    auto v = new AST.VarDeclaration(loc, t, ident, _init);
                    v.storage_class = storage_class;
                    if (pAttrs)
                        pAttrs.storageClass = STC.undefined_;
                    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(s.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`", s.toChars(), token.toChars());
                        errorSupplemental(loc, "`%s` declared here", s.toChars());
                    }
                    else
                    {
                        error("semicolon needed to end declaration of `%s` instead of `%s`", s.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, "alias reassignment");
        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, "`alias Identifier this`");
            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, "`alias this = Identifier`");
                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) ||
                    token.value == TOK.auto_ && peekNext() == TOK.ref_ &&
                        peekNext2() == TOK.leftParenthesis &&
                        skipAttributes(peekPastParen(peek(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
                    // auto ref (parameters) { statements... }
                    // auto 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.auto_)
            {
                nextToken();
                if (token.value == TOK.ref_)
                {
                    // function auto ref (parameters) { statements... }
                    // delegate auto ref (parameters) { statements... }
                    stc = STC.auto_ | STC.ref_;
                    nextToken();
                }
                else
                    error("`auto` can only be used as part of `auto ref` for function literal return values");
            }
            else 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.auto_:
            {
                nextToken();
                if (token.value == TOK.ref_)
                {
                    // auto ref (parameters) => expression
                    // auto ref (parameters) { statements... }
                    stc = STC.auto_ | STC.ref_;
                    nextToken();
                }
                else
                    error("`auto` can only be used as part of `auto ref` for function literal return values");
                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, null, stc & STC.auto_);

        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
            {
                auto ret = parseStatement(ParseStatementFlags.curly | ParseStatementFlags.scope_);
                assert(ret);
                f.frequires.push(ret);
                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);
    }

    /*****************************
     * Ad-hoc error message for missing or extra parens that close a condition.
     * Params:
     *  start = "if", "while", etc. Must be 0 terminated.
     *  param = if the condition is a declaration, this will be non-null
     *  condition = if param is null, then this is the conditional Expression. If condition is null,
     *      then an error in the condition was already reported.
     */
    private void closeCondition(string start, AST.Parameter param, AST.Expression condition)
    {
        string format;
        if (token.value != TOK.rightParenthesis && condition)
        {
            format = "missing closing `)` after `%s (%s`";
        }
        else
            check(TOK.rightParenthesis);
        if (token.value == TOK.rightParenthesis)
        {
            if (condition) // if not an error in condition
                format = "extra `)` after `%s (%s)`";
            nextToken();
        }
        if (format)
            error(format.ptr, start.ptr, param ? "declaration".ptr : condition.toChars());
    }

    /*****************************************
     * 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;
                const tv = peekNext();
                if (tv == TOK.leftParenthesis)
                {
                    // mixin(string)
                    AST.Expression e = parseAssignExp();
                    check(TOK.semicolon, "mixin");
                    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;
                }
                else if (tv == TOK.template_)
                {
                    // mixin template
                    nextToken();
                    AST.Dsymbol d = parseTemplateDeclaration(true);
                    s = new AST.ExpStatement(loc, d);
                    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_:
            {
                nextToken();
                check(TOK.leftParenthesis);
                auto param = parseAssignCondition();
                auto condition = parseExpression();
                closeCondition("while", param, condition);

                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;

                nextToken();
                const lookingForElseSave = lookingForElse;
                lookingForElse = Loc.initial;
                _body = parseStatement(ParseStatementFlags.scope_);
                lookingForElse = lookingForElseSave;
                check(TOK.while_);
                check(TOK.leftParenthesis);
                auto condition = parseExpression();
                closeCondition("do .. while", null, condition);
                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_:
            {
                nextToken();
                check(TOK.leftParenthesis);
                auto param = parseAssignCondition();
                auto condition = parseExpression();
                closeCondition("if", param, condition);

                {
                    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();
                closeCondition("switch", null, condition);
                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();
                    closeCondition("synchronized", null, exp);
                }
                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();
                closeCondition("with", null, exp);
                _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, "`import` Expression");
                    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 = new AST.ErrorStatement;
            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.auto_:
            {
                if (peekNext() == TOK.ref_ && peekNext2() == TOK.leftParenthesis)
                {
                    Token* tk = peekPastParen(peek(peek(&token)));
                    if (skipAttributes(tk, &tk) && (tk.value == TOK.goesTo || tk.value == TOK.leftCurly))
                    {
                        // auto ref (arguments) => expression
                        // auto ref (arguments) { statements... }
                        goto case_delegate;
                    }
                }
                nextToken();
                error("found `%s` when expecting `ref` and function literal following `auto`", token.toChars());
                goto Lerr;
            }
        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 AST.ErrorExp.get();
                    }
                    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 `(...)`.");
                                        nextToken();
                                        return AST.ErrorExp.get();
                                    }
                                    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;
            // `new Type[expr]` is a static array
            auto edim = AST.typeToExpression(index);
            if (edim)
                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.doOutput)
        return false;

    OutBuffer* ob = global.params.mixinOut.buffer;

    ob.writestring("// expansion at ");
    ob.writestring(loc.toChars());
    ob.writenl();

    global.params.mixinOut.bufferLines++;

    loc = Loc(global.params.mixinOut.name.ptr, global.params.mixinOut.bufferLines + 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.mixinOut.bufferLines++;
            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.mixinOut.bufferLines++;
    }
    ob.writenl();
    global.params.mixinOut.bufferLines++;

    return true;
}
