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

module dmd.cparse;

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.parse;
import dmd.errors;
import dmd.root.filename;
import dmd.root.outbuffer;
import dmd.root.rmem;
import dmd.root.rootobject;
import dmd.root.string;
import dmd.tokens;

/***********************************************************
 */
final class CParser(AST) : Parser!AST
{
    AST.Dsymbols* symbols;      // symbols declared in current scope

    bool addFuncName;             /// add declaration of __func__ to function symbol table

    extern (D) this(TARGET)(AST.Module _module, const(char)[] input, bool doDocComment,
                            const ref TARGET target)
    {
        super(_module, input, doDocComment);

        //printf("CParser.this()\n");
        mod = _module;
        linkage = LINK.c;
        Ccompile = true;

        // Configure sizes for C `long`, `long double`, `wchar_t`
        this.longsize = target.longsize;
        this.long_doublesize = target.long_doublesize;
        this.wchar_tsize = target.wchar_tsize;

        // C `char` is always unsigned in ImportC
    }

    /********************************************
     * Parse translation unit.
     * C11 6.9
     * translation-unit:
     *    external-declaration
     *    translation-unit external-declaration
     *
     * external-declaration:
     *    function-definition
     *    declaration
     * Returns:
     *  array of Dsymbols that were declared
     */
    override AST.Dsymbols* parseModule()
    {
        //printf("cparseTranslationUnit()\n");
        symbols = new AST.Dsymbols();
        while (1)
        {
            if (token.value == TOK.endOfFile)
            {
                // wrap the symbols in `extern (C) { symbols }`
                auto wrap = new AST.Dsymbols();
                auto ld = new AST.LinkDeclaration(token.loc, LINK.c, symbols);
                wrap.push(ld);

                return wrap;
            }

            cparseDeclaration(LVL.global);
        }
    }

    /******************************************************************************/
    /********************************* Statement Parser ***************************/
    //{

    /**********************
     * C11 6.8
     * statement:
     *    labeled-statement
     *    compound-statement
     *    expression-statement
     *    selection-statement
     *    iteration-statement
     *    jump-statement
     *
     * Params:
     *      flags = PSxxxx
     *      endPtr = store location of closing brace
     *      pEndloc = if { ... statements ... }, store location of closing brace, otherwise loc of last token of statement
     * Returns:
     *      parsed statement
     */
    AST.Statement cparseStatement(int flags, const(char)** endPtr = null, Loc* pEndloc = null)
    {
        AST.Statement s;
        const loc = token.loc;

        //printf("cparseStatement()\n");

        auto symbolsSave = symbols;
        if (!(flags & (ParseStatementFlags.scope_ | ParseStatementFlags.curlyScope)))
            symbols = new AST.Dsymbols();

        switch (token.value)
        {
        case TOK.identifier:
            /* A leading identifier can be a declaration, label, or expression.
             * A quick check of the next token can disambiguate most cases.
             */
            switch (peekNext())
            {
                case TOK.colon:
                {
                    // It's a label
                    auto ident = token.ident;
                    nextToken();    // advance to `:`
                    nextToken();    // advance past `:`
                    if (token.value == TOK.rightCurly)
                        s = null;
                    else if (token.value == TOK.leftCurly)
                        s = cparseStatement(ParseStatementFlags.curly | ParseStatementFlags.scope_);
                    else
                        s = cparseStatement(ParseStatementFlags.semiOk);
                    s = new AST.LabelStatement(loc, ident, s);
                    break;
                }

                case TOK.dot:
                case TOK.arrow:
                case TOK.plusPlus:
                case TOK.minusMinus:
                case TOK.leftBracket:
                case TOK.question:
                case TOK.assign:
                case TOK.addAssign:
                case TOK.minAssign:
                case TOK.mulAssign:
                case TOK.divAssign:
                case TOK.modAssign:
                case TOK.andAssign:
                case TOK.orAssign:
                case TOK.xorAssign:
                case TOK.leftShiftAssign:
                case TOK.rightShiftAssign:
                    goto Lexp;

                case TOK.leftParenthesis:
                {
                    /* If tokens look like a function call, assume it is one,
                     * As any type-name won't be resolved until semantic, this
                     * could be rewritten later.
                     */
                    auto tk = &token;
                    if (isFunctionCall(tk))
                        goto Lexp;
                    goto default;
                }

                default:
                {
                    /* If tokens look like a declaration, assume it is one
                     */
                    auto tk = &token;
                    if (isCDeclaration(tk))
                        goto Ldeclaration;
                    goto Lexp;
                }
            }
            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.leftParenthesis:
        case TOK.and:
        case TOK.mul:
        case TOK.min:
        case TOK.add:
        case TOK.tilde:
        case TOK.not:
        case TOK.plusPlus:
        case TOK.minusMinus:
        case TOK.sizeof_:
        Lexp:
            auto exp = cparseExpression();
            if (token.value == TOK.identifier && exp.op == TOK.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
                check(TOK.semicolon, "statement");
            s = new AST.ExpStatement(loc, exp);
            break;

        // type-specifiers
        case TOK.void_:
        case TOK.char_:
        case TOK.int16:
        case TOK.int32:
        case TOK.int64:
        case TOK.float32:
        case TOK.float64:
        case TOK.signed:
        case TOK.unsigned:
        case TOK._Bool:
        //case TOK._Imaginary:
        case TOK._Complex:
        case TOK.struct_:
        case TOK.union_:
        case TOK.enum_:

        // storage-class-specifiers
        case TOK.typedef_:
        case TOK.extern_:
        case TOK.static_:
        case TOK._Thread_local:
        case TOK.auto_:
        case TOK.register:

        // function-specifiers
        case TOK.inline:
        case TOK._Noreturn:

        // type-qualifiers
        case TOK.const_:
        case TOK.volatile:
        case TOK.restrict:

        // alignment-specifier
        case TOK._Alignas:

        // atomic-type-specifier or type_qualifier
        case TOK._Atomic:

        Ldeclaration:
        {
            cparseDeclaration(LVL.local);
            if (symbols.length > 1)
            {
                auto as = new AST.Statements();
                as.reserve(symbols.length);
                foreach (d; (*symbols)[])
                {
                    s = new AST.ExpStatement(loc, d);
                    as.push(s);
                }
                s = new AST.CompoundDeclarationStatement(loc, as);
                symbols.setDim(0);
            }
            else if (symbols.length == 1)
            {
                auto d = (*symbols)[0];
                s = new AST.ExpStatement(loc, d);
                symbols.setDim(0);
            }
            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._Static_assert:        // _Static_assert ( constant-expression, string-literal ) ;
            s = new AST.StaticAssertStatement(cparseStaticAssert());
            break;

        case TOK.leftCurly:
        {
            /* C11 6.8.2
             * compound-statement:
             *    { block-item-list (opt) }
             *
             * block-item-list:
             *    block-item
             *    block-item-list block-item
             *
             * block-item:
             *    declaration
             *    statement
             */
            nextToken();
            auto statements = new AST.Statements();
            while (token.value != TOK.rightCurly && token.value != TOK.endOfFile)
            {
                statements.push(cparseStatement(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");
            break;
        }

        case TOK.while_:
        {
            nextToken();
            check(TOK.leftParenthesis);
            auto condition = cparseExpression();
            check(TOK.rightParenthesis);
            Loc endloc;
            auto _body = cparseStatement(ParseStatementFlags.scope_, null, &endloc);
            s = new AST.WhileStatement(loc, condition, _body, endloc, null);
            break;
        }

        case TOK.semicolon:
            /* C11 6.8.3 null statement
             */
            nextToken();
            s = new AST.ExpStatement(loc, cast(AST.Expression)null);
            break;

        case TOK.do_:
        {
            nextToken();
            auto _body = cparseStatement(ParseStatementFlags.scope_);
            check(TOK.while_);
            check(TOK.leftParenthesis);
            auto condition = cparseExpression();
            check(TOK.rightParenthesis);
            check(TOK.semicolon, "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
            {
                _init = cparseStatement(0);
            }
            if (token.value == TOK.semicolon)
            {
                condition = null;
                nextToken();
            }
            else
            {
                condition = cparseExpression();
                check(TOK.semicolon, "`for` condition");
            }
            if (token.value == TOK.rightParenthesis)
            {
                increment = null;
                nextToken();
            }
            else
            {
                increment = cparseExpression();
                check(TOK.rightParenthesis);
            }
            Loc endloc;
            auto _body = cparseStatement(ParseStatementFlags.scope_, null, &endloc);
            s = new AST.ForStatement(loc, _init, condition, increment, _body, endloc);
            break;
        }

        case TOK.if_:
        {
            nextToken();
            check(TOK.leftParenthesis);
            auto condition = cparseExpression();
            check(TOK.rightParenthesis);
            auto ifbody = cparseStatement(ParseStatementFlags.scope_);
            AST.Statement elsebody;
            if (token.value == TOK.else_)
            {
                nextToken();
                elsebody = cparseStatement(ParseStatementFlags.scope_);
            }
            else
                elsebody = null;
            if (condition && ifbody)
                s = new AST.IfStatement(loc, null, condition, ifbody, elsebody, token.loc);
            else
                s = null; // don't propagate parsing errors
            break;
        }

        case TOK.else_:
            error("found `else` without a corresponding `if` statement");
            goto Lerror;

        case TOK.switch_:
        {
            nextToken();
            check(TOK.leftParenthesis);
            auto condition = cparseExpression();
            check(TOK.rightParenthesis);
            auto _body = cparseStatement(ParseStatementFlags.scope_);
            s = new AST.SwitchStatement(loc, condition, _body, false);
            break;
        }

        case TOK.case_:
        {

            nextToken();
            auto exp = cparseAssignExp();
            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 = cparseStatement(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 = cparseStatement(ParseStatementFlags.semi);
            }
            s = new AST.ScopeStatement(loc, s, token.loc);
            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(cparseStatement(ParseStatementFlags.semi | ParseStatementFlags.curlyScope));
                }
                s = new AST.CompoundStatement(loc, statements);
            }
            else
                s = cparseStatement(ParseStatementFlags.semi);
            s = new AST.ScopeStatement(loc, s, token.loc);
            s = new AST.DefaultStatement(loc, s);
            break;
        }

        case TOK.return_:
        {
            /*  return ;
             *  return expression ;
             */
            nextToken();
            auto exp = token.value == TOK.semicolon ? null : cparseExpression();
            check(TOK.semicolon, "`return` statement");
            s = new AST.ReturnStatement(loc, exp);
            break;
        }

        case TOK.break_:
            nextToken();
            check(TOK.semicolon, "`break` statement");
            s = new AST.BreakStatement(loc, null);
            break;

        case TOK.continue_:
            nextToken();
            check(TOK.semicolon, "`continue` statement");
            s = new AST.ContinueStatement(loc, null);
            break;

        case TOK.goto_:
        {
            Identifier ident;
            nextToken();
            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.asm_:
            s = parseAsm();
            break;

        default:
            error("found `%s` instead of statement", token.toChars());
            goto Lerror;

        Lerror:
            panic();
            if (token.value == TOK.semicolon)
                nextToken();
            s = null;
            break;
        }
        if (pEndloc)
            *pEndloc = prevloc;
        symbols = symbolsSave;
        return s;
    }

    //}
    /*******************************************************************************/
    /********************************* Expression Parser ***************************/
    //{

    /**************
     * C11 6.5.17
     * expression:
     *  assignment-expression
     *  expression , assignment-expression
     */
    AST.Expression cparseExpression()
    {
        auto loc = token.loc;

        //printf("cparseExpression() loc = %d\n", loc.linnum);
        auto e = cparseAssignExp();
        while (token.value == TOK.comma)
        {
            nextToken();
            auto e2 = cparseAssignExp();
            e = new AST.CommaExp(loc, e, e2, false);
            loc = token.loc;
        }
        return e;
    }


    /*********************
     * C11 6.5.1
     * primary-expression:
     *    identifier
     *    constant
     *    string-literal
     *    ( expression )
     *    generic-selection
     */
    AST.Expression cparsePrimaryExp()
    {
        AST.Expression e;
        const loc = token.loc;

        //printf("parsePrimaryExp(): loc = %d\n", loc.linnum);
        switch (token.value)
        {
        case TOK.identifier:
            if (token.ident is Id.__func__)
            {
                addFuncName = true;     // implicitly declare __func__
            }
            e = new AST.IdentifierExp(loc, token.ident);
            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.string_:
        {
            // cat adjacent strings
            auto s = token.ustring;
            auto len = token.len;
            auto postfix = token.postfix;
            while (1)
            {
                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;
                    }

                    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.leftParenthesis:
            nextToken();
            e = cparseExpression();
            check(TOK.rightParenthesis);
            break;

        case TOK._Generic:
            e = cparseGenericSelection();
            break;

        default:
            error("expression expected, not `%s`", token.toChars());
            // Anything for e, as long as it's not NULL
            e = new AST.IntegerExp(loc, 0, AST.Type.tint32);
            nextToken();
            break;
        }
        return e;
    }

    /*********************************
     * C11 6.5.2
     * postfix-expression:
     *    primary-expression
     *    postfix-expression [ expression ]
     *    postfix-expression ( argument-expression-list (opt) )
     *    postfix-expression . identifier
     *    postfix-expression -> identifier
     *    postfix-expression ++
     *    postfix-expression --
     *    ( type-name ) { initializer-list }
     *    ( type-name ) { initializer-list , }
     *
     * argument-expression-list:
     *    assignment-expression
     *    argument-expression-list , assignment-expression
     */
    private AST.Expression cparsePostfixExp(AST.Expression e)
    {
        e = cparsePrimaryExp();
        return cparsePostfixOperators(e);
    }

    /********************************
     * C11 6.5.2
     * Parse a series of operators for a postfix expression after already parsing
     * a primary-expression or compound literal expression.
     * Params:
     *      e = parsed primary or compound literal expression
     * Returns:
     *      parsed postfix expression
     */
    private AST.Expression cparsePostfixOperators(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;
                    e = new AST.DotIdExp(loc, e, id);
                    break;
                }
                error("identifier expected following `.`, not `%s`", token.toChars());
                break;

            case TOK.arrow:
                nextToken();
                if (token.value == TOK.identifier)
                {
                    Identifier id = token.ident;
                    auto die = new AST.DotIdExp(loc, e, id);
                    die.arrow = true;
                    e = die;
                    break;
                }
                error("identifier expected following `->`, not `%s`", token.toChars());
                break;

            case TOK.plusPlus:
                e = new AST.PostExp(TOK.plusPlus, loc, e);
                break;

            case TOK.minusMinus:
                e = new AST.PostExp(TOK.minusMinus, loc, e);
                break;

            case TOK.leftParenthesis:
                e = new AST.CallExp(loc, e, cparseArguments());
                continue;

            case TOK.leftBracket:
                {
                    // array dereferences:
                    //      array[index]
                    AST.Expression index;
                    auto arguments = new AST.Expressions();

                    inBrackets++;
                    nextToken();
                    index = cparseAssignExp();
                    arguments.push(index);
                    check(TOK.rightBracket);
                    inBrackets--;
                    e = new AST.ArrayExp(loc, e, arguments);
                    continue;
                }
            default:
                return e;
            }
            nextToken();
        }
    }

    /************************
     * C11 6.5.3
     * unary-expression:
     *    postfix-expression
     *    ++ unary-expression
     *    -- unary-expression
     *    unary-operator cast-expression
     *    sizeof unary-expression
     *    sizeof ( type-name )
     *    _Alignof ( type-name )
     *
     * unary-operator:
     *    & * + - ~ !
     */
    private AST.Expression cparseUnaryExp()
    {
        AST.Expression e;
        const loc = token.loc;

        switch (token.value)
        {
        case TOK.plusPlus:
            nextToken();
            // Parse `++` as an unary operator so that cast expressions only give
            // an error for being non-lvalues.
            e = cparseCastExp();
            e = new AST.PreExp(TOK.prePlusPlus, loc, e);
            break;

        case TOK.minusMinus:
            nextToken();
            // Parse `--` as an unary operator, same as prefix increment.
            e = cparseCastExp();
            e = new AST.PreExp(TOK.preMinusMinus, loc, e);
            break;

        case TOK.and:
            nextToken();
            e = cparseCastExp();
            e = new AST.AddrExp(loc, e);
            break;

        case TOK.mul:
            nextToken();
            e = cparseCastExp();
            e = new AST.PtrExp(loc, e);
            break;

        case TOK.min:
            nextToken();
            e = cparseCastExp();
            e = new AST.NegExp(loc, e);
            break;

        case TOK.add:
            nextToken();
            e = cparseCastExp();
            e = new AST.UAddExp(loc, e);
            break;

        case TOK.not:
            nextToken();
            e = cparseCastExp();
            e = new AST.NotExp(loc, e);
            break;

        case TOK.tilde:
            nextToken();
            e = cparseCastExp();
            e = new AST.ComExp(loc, e);
            break;

        case TOK.sizeof_:
        {
            nextToken();
            if (token.value == TOK.leftParenthesis)
            {
                auto tk = peek(&token);
                if (isTypeName(tk))
                {
                    /* Expression may be either be requesting the sizeof a type-name
                     * or a compound literal, which requires checking whether
                     * the next token is leftCurly
                     */
                    nextToken();
                    auto t = cparseTypeName();
                    check(TOK.rightParenthesis);
                    if (token.value == TOK.leftCurly)
                    {
                        // ( type-name ) { initializer-list }
                        auto ci = cparseInitializer();
                        e = new AST.CompoundLiteralExp(loc, t, ci);
                        e = cparsePostfixOperators(e);
                    }
                    else
                    {
                        // ( type-name )
                        e = new AST.TypeExp(loc, t);
                    }
                    e = new AST.DotIdExp(loc, e, Id.__sizeof);
                    break;
                }
            }
            e = cparseUnaryExp();
            e = new AST.DotIdExp(loc, e, Id.__sizeof);
            break;
        }

        case TOK._Alignof:
        {
            nextToken();
            check(TOK.leftParenthesis);
            auto t = cparseTypeName();
            check(TOK.rightParenthesis);
            e = new AST.TypeExp(loc, t);
            e = new AST.DotIdExp(loc, e, Id.__xalignof);
            break;
        }

        default:
            e = cparsePostfixExp(e);
            break;
        }
        assert(e);
        return e;
    }

    /**************
     * C11 6.5.4
     * cast-expression
     *    unary-expression
     *    ( type-name ) cast-expression
     */
    private AST.Expression cparseCastExp()
    {
        if (token.value == TOK.leftParenthesis)
        {
            // If ( type-name )
            auto pt = &token;
            if (isCastExpression(pt))
            {
                // Expression may be either a cast or a compound literal, which
                // requires checking whether the next token is leftCurly
                const loc = token.loc;
                nextToken();
                auto t = cparseTypeName();
                check(TOK.rightParenthesis);

                if (token.value == TOK.leftCurly)
                {
                    // C11 6.5.2.5 ( type-name ) { initializer-list }
                    auto ci = cparseInitializer();
                    auto ce = new AST.CompoundLiteralExp(loc, t, ci);
                    return cparsePostfixOperators(ce);
                }
                else
                {
                    // ( type-name ) cast-expression
                    auto ce = cparseCastExp();
                    return new AST.CastExp(loc, ce, t);
                }
            }
        }
        return cparseUnaryExp();
    }

    /**************
     * C11 6.5.5
     * multiplicative-expression
     *    cast-expression
     *    multiplicative-expression * cast-expression
     *    multiplicative-expression / cast-expression
     *    multiplicative-expression % cast-expression
     */
    private AST.Expression cparseMulExp()
    {
        const loc = token.loc;
        auto e = cparseCastExp();

        while (1)
        {
            switch (token.value)
            {
            case TOK.mul:
                nextToken();
                auto e2 = cparseCastExp();
                e = new AST.MulExp(loc, e, e2);
                continue;

            case TOK.div:
                nextToken();
                auto e2 = cparseCastExp();
                e = new AST.DivExp(loc, e, e2);
                continue;

            case TOK.mod:
                nextToken();
                auto e2 = cparseCastExp();
                e = new AST.ModExp(loc, e, e2);
                continue;

            default:
                break;
            }
            break;
        }
        return e;
    }

    /**************
     * C11 6.5.6
     * additive-expression
     *    multiplicative-expression
     *    additive-expression + multiplicative-expression
     *    additive-expression - multiplicative-expression
     */
    private AST.Expression cparseAddExp()
    {
        const loc = token.loc;
        auto e = cparseMulExp();

        while (1)
        {
            switch (token.value)
            {
            case TOK.add:
                nextToken();
                auto e2 = cparseMulExp();
                e = new AST.AddExp(loc, e, e2);
                continue;

            case TOK.min:
                nextToken();
                auto e2 = cparseMulExp();
                e = new AST.MinExp(loc, e, e2);
                continue;

            default:
                break;
            }
            break;
        }
        return e;
    }

    /**************
     * C11 6.5.7
     * shift-expression
     *    additive-expression
     *    shift-expression << additive-expression
     *    shift-expression >> additive-expression
     */
    private AST.Expression cparseShiftExp()
    {
        const loc = token.loc;
        auto e = cparseAddExp();

        while (1)
        {
            switch (token.value)
            {
            case TOK.leftShift:
                nextToken();
                auto e2 = cparseAddExp();
                e = new AST.ShlExp(loc, e, e2);
                continue;

            case TOK.rightShift:
                nextToken();
                auto e2 = cparseAddExp();
                e = new AST.ShrExp(loc, e, e2);
                continue;

            default:
                break;
            }
            break;
        }
        return e;
    }

    /**************
     * C11 6.5.8
     * relational-expression
     *    shift-expression
     *    relational-expression < shift-expression
     *    relational-expression > shift-expression
     *    relational-expression <= shift-expression
     *    relational-expression >= shift-expression
     */
    private AST.Expression cparseRelationalExp()
    {
        const loc = token.loc;

        auto e = cparseShiftExp();
        TOK op = token.value;

        switch (op)
        {
        case TOK.lessThan:
        case TOK.lessOrEqual:
        case TOK.greaterThan:
        case TOK.greaterOrEqual:
            nextToken();
            auto e2 = cparseShiftExp();
            e = new AST.CmpExp(op, loc, e, e2);
            break;

        default:
            break;
        }
        return e;
    }

    /**************
     * C11 6.5.9
     * equality-expression
     *    relational-expression
     *    equality-expression == relational-expression
     *    equality-expression != relational-expression
     */
    private AST.Expression cparseEqualityExp()
    {
        const loc = token.loc;

        auto e = cparseRelationalExp();
        const TOK op = token.value;

        switch (op)
        {
        case TOK.equal:
        case TOK.notEqual:
            nextToken();
            auto e2 = cparseRelationalExp();
            e = new AST.EqualExp(op, loc, e, e2);
            break;

        default:
            break;
        }
        return e;
    }

    /**************
     * C11 6.5.10
     * AND-expression
     *    equality-expression
     *    AND-expression & equality-expression
     */
    private AST.Expression cparseAndExp()
    {
        Loc loc = token.loc;
        auto e = cparseEqualityExp();
        while (token.value == TOK.and)
        {
            nextToken();
            auto e2 = cparseEqualityExp();
            e = new AST.AndExp(loc, e, e2);
            loc = token.loc;
        }
        return e;
    }

    /**************
     * C11 6.5.11
     * exclusive-OR-expression
     *    AND-expression
     *    exclusive-OR-expression ^ AND-expression
     */
    private AST.Expression cparseXorExp()
    {
        const loc = token.loc;

        auto e = cparseAndExp();
        while (token.value == TOK.xor)
        {
            nextToken();
            auto e2 = cparseAndExp();
            e = new AST.XorExp(loc, e, e2);
        }
        return e;
    }

    /**************
     * C11 6.5.12
     * inclusive-OR-expression
     *    exclusive-OR-expression
     *    inclusive-OR-expression | exclusive-OR-expression
     */
    private AST.Expression cparseOrExp()
    {
        const loc = token.loc;

        auto e = cparseXorExp();
        while (token.value == TOK.or)
        {
            nextToken();
            auto e2 = cparseXorExp();
            e = new AST.OrExp(loc, e, e2);
        }
        return e;
    }

    /**************
     * C11 6.5.13
     * logical-AND-expression
     *    inclusive-OR-expression
     *    logical-AND-expression && inclusive-OR-expression
     */
    private AST.Expression cparseAndAndExp()
    {
        const loc = token.loc;

        auto e = cparseOrExp();
        while (token.value == TOK.andAnd)
        {
            nextToken();
            auto e2 = cparseOrExp();
            e = new AST.LogicalExp(loc, TOK.andAnd, e, e2);
        }
        return e;
    }

    /**************
     * C11 6.5.14
     * logical-OR-expression
     *    logical-AND-expression
     *    logical-OR-expression || logical-AND-expression
     */
    private AST.Expression cparseOrOrExp()
    {
        const loc = token.loc;

        auto e = cparseAndAndExp();
        while (token.value == TOK.orOr)
        {
            nextToken();
            auto e2 = cparseAndAndExp();
            e = new AST.LogicalExp(loc, TOK.orOr, e, e2);
        }
        return e;
    }

    /**************
     * C11 6.5.15
     * conditional-expression:
     *    logical-OR-expression
     *    logical-OR-expression ? expression : conditional-expression
     */
    private AST.Expression cparseCondExp()
    {
        const loc = token.loc;

        auto e = cparseOrOrExp();
        if (token.value == TOK.question)
        {
            nextToken();
            auto e1 = cparseExpression();
            check(TOK.colon);
            auto e2 = cparseCondExp();
            e = new AST.CondExp(loc, e, e1, e2);
        }
        return e;
    }

    /**************
     * C11 6.5.16
     * assignment-expression:
     *    conditional-expression
     *    unary-expression assignment-operator assignment-expression
     *
     * assignment-operator:
     *    = *= /= %= += -= <<= >>= &= ^= |=
     */
    AST.Expression cparseAssignExp()
    {
        AST.Expression e;
        e = cparseCondExp(); // constrain it to being unary-expression in semantic pass
        if (e is null)
            return e;

        const loc = token.loc;
        switch (token.value)
        {
        case TOK.assign:
            nextToken();
            auto e2 = cparseAssignExp();
            e = new AST.AssignExp(loc, e, e2);
            break;

        case TOK.addAssign:
            nextToken();
            auto e2 = cparseAssignExp();
            e = new AST.AddAssignExp(loc, e, e2);
            break;

        case TOK.minAssign:
            nextToken();
            auto e2 = cparseAssignExp();
            e = new AST.MinAssignExp(loc, e, e2);
            break;

        case TOK.mulAssign:
            nextToken();
            auto e2 = cparseAssignExp();
            e = new AST.MulAssignExp(loc, e, e2);
            break;

        case TOK.divAssign:
            nextToken();
            auto e2 = cparseAssignExp();
            e = new AST.DivAssignExp(loc, e, e2);
            break;

        case TOK.modAssign:
            nextToken();
            auto e2 = cparseAssignExp();
            e = new AST.ModAssignExp(loc, e, e2);
            break;

        case TOK.andAssign:
            nextToken();
            auto e2 = cparseAssignExp();
            e = new AST.AndAssignExp(loc, e, e2);
            break;

        case TOK.orAssign:
            nextToken();
            auto e2 = cparseAssignExp();
            e = new AST.OrAssignExp(loc, e, e2);
            break;

        case TOK.xorAssign:
            nextToken();
            auto e2 = cparseAssignExp();
            e = new AST.XorAssignExp(loc, e, e2);
            break;

        case TOK.leftShiftAssign:
            nextToken();
            auto e2 = cparseAssignExp();
            e = new AST.ShlAssignExp(loc, e, e2);
            break;

        case TOK.rightShiftAssign:
            nextToken();
            auto e2 = cparseAssignExp();
            e = new AST.ShrAssignExp(loc, e, e2);
            break;

        default:
            break;
        }

        return e;
    }

    /***********************
     * C11 6.5.1.1
     * _Generic ( assignment-expression, generic-assoc-list )
     *
     * generic-assoc-list:
     *   generic-association
     *   generic-assoc-list generic-association
     *
     * generic-association:
     *   type-name : assignment-expression
     *   default : assignment-expression
     */
    private AST.Expression cparseGenericSelection()
    {
        const loc = token.loc;
        nextToken();
        check(TOK.leftParenthesis);
        auto cntlExp = cparseAssignExp();
        check(TOK.comma);
        auto types = new AST.Types();
        auto exps = new AST.Expressions();
        bool sawDefault;
        while (1)
        {
            AST.Type t;
            if (token.value == TOK.default_)
            {
                nextToken();
                if (sawDefault)
                    error("only one `default` allowed in generic-assoc-list");
                sawDefault = true;
                t = null;
            }
            else
                t = cparseTypeName();
            types.push(t);

            check(TOK.colon);
            auto e = cparseAssignExp();
            exps.push(e);
            if (token.value == TOK.rightParenthesis || token.value == TOK.endOfFile)
                break;
            check(TOK.comma);
        }
        check(TOK.rightParenthesis);
        return new AST.GenericExp(loc, cntlExp, types, exps);
    }

    /***********************
     * C11 6.6 Constant expressions
     * constant-expression:
     *   conditional-expression
     */
    private AST.Expression cparseConstantExp()
    {
        return cparseAssignExp();
    }

    //}
    /********************************************************************************/
    /********************************* Declaration Parser ***************************/
    //{

    /*************************************
     * C11 6.7
     * declaration:
     *    declaration-specifiers init-declarator-list (opt) ;
     *    static_assert-declaration
     *
     * init-declarator-list:
     *    init-declarator
     *    init-declarator-list , init-declarator
     *
     * init-declarator:
     *    declarator
     *    declarator = initializer
     *
     * Params:
     *    level = declaration context
     */
    void cparseDeclaration(LVL level)
    {
        //printf("cparseDeclaration(level = %d)\n", level);
        if (token.value == TOK._Static_assert)
        {
            auto s = cparseStaticAssert();
            symbols.push(s);
            return;
        }

        auto symbolsSave = symbols;
        Specifier specifier;
        auto tspec = cparseDeclarationSpecifiers(level, specifier);

        /* If a declarator does not follow, it is unnamed
         */
        if (token.value == TOK.semicolon && tspec)
        {
            nextToken();
            auto tt = tspec.isTypeTag();
            if (!tt || !tt.id)
                return; // legal but meaningless empty declaration, ignore it

            /* `struct tag;` and `struct tag { ... };`
             * always result in a declaration in the current scope
             */
            auto stag = (tt.tok == TOK.struct_) ? new AST.StructDeclaration(tt.loc, tt.id, false) :
                        (tt.tok == TOK.union_)  ? new AST.UnionDeclaration(tt.loc, tt.id) :
                                                  new AST.EnumDeclaration(tt.loc, tt.id, AST.Type.tint32);
            stag.members = tt.members;
            if (!symbols)
                symbols = new AST.Dsymbols();
            auto stags = applySpecifier(stag, specifier);
            symbols.push(stags);

            if (tt.tok == TOK.enum_)
            {
                if (!tt.members)
                    error(tt.loc, "`enum %s` has no members", stag.toChars());
            }
            return;
        }

        if (tspec && specifier.mod & MOD.xconst)
        {
            tspec = toConst(tspec);
            specifier.mod = MOD.xnone;          // 'used' it
        }

        bool first = true;
        while (1)
        {
            Identifier id;
            AST.Expression asmname;
            auto dt = cparseDeclarator(DTR.xdirect, tspec, id);
            if (!dt)
            {
                panic();
                nextToken();
                break;          // error recovery
            }

            /* GNU Extensions
             * init-declarator:
             *    declarator simple-asm-expr (opt) gnu-attributes (opt)
             *    declarator simple-asm-expr (opt) gnu-attributes (opt) = initializer
             */
            switch (token.value)
            {
                case TOK.assign:
                case TOK.comma:
                case TOK.semicolon:
                case TOK.asm_:
                case TOK.__attribute__:
                    /* This is a data definition, there cannot now be a
                     * function definition.
                     */
                    first = false;
                    if (token.value == TOK.asm_)
                        asmname = cparseSimpleAsmExpr();
                    if (token.value == TOK.__attribute__)
                    {
                        cparseGnuAttributes(specifier);
                        if (token.value == TOK.leftCurly)
                        {
                            error("attributes should be specified before the function definition");
                            auto t = &token;
                            if (skipBraces(t))
                            {
                                token = *t;
                                return;
                            }
                        }
                    }
                    break;

                default:
                    break;
            }

            if (specifier.alignExps && dt.isTypeFunction())
                error("no alignment-specifier for function declaration"); // C11 6.7.5-2
            if (specifier.alignExps && specifier.scw == SCW.xregister)
                error("no alignment-specifier for `register` storage class"); // C11 6.7.5-2

            /* C11 6.9.1 Function Definitions
             * function-definition:
             *   declaration-specifiers declarator declaration-list (opt) compound-statement
             *
             * declaration-list:
             *    declaration
             *    declaration-list declaration
             */
            auto t = &token;
            if (first &&                   // first declarator
                id &&
                dt.isTypeFunction() &&     // function type not inherited from a typedef
                isDeclarationList(t) &&    // optional declaration-list
                level == LVL.global &&     // function definitions only at global scope
                t.value == TOK.leftCurly)  // start of compound-statement
            {
                auto s = cparseFunctionDefinition(id, dt.isTypeFunction(), specifier);
                symbols = symbolsSave;
                symbols.push(s);
                return;
            }
            AST.Dsymbol s = null;
            symbols = symbolsSave;
            if (!symbols)
                symbols = new AST.Dsymbols;     // lazilly create it

            if (level != LVL.global && !tspec && !specifier.scw && !specifier.mod)
                error("declaration-specifier-seq required");
            else if (specifier.scw == SCW.xtypedef)
            {
                if (token.value == TOK.assign)
                    error("no initializer for typedef declaration");
                if (specifier.alignExps)
                    error("no alignment-specifier for typedef declaration"); // C11 6.7.5-2

                bool isalias = true;
                if (auto ts = dt.isTypeStruct())
                {
                    if (ts.sym.isAnonymous())
                    {
                        // This is a typedef for an anonymous struct-or-union.
                        // Directly set the ident for the struct-or-union.
                        ts.sym.ident = id;
                        isalias = false;
                    }
                }
                else if (auto te = dt.isTypeEnum())
                {
                    if (te.sym.isAnonymous())
                    {
                        // This is a typedef for an anonymous enum.
                        te.sym.ident = id;
                        isalias = false;
                    }
                }
                if (isalias)
                    s = new AST.AliasDeclaration(token.loc, id, dt);
            }
            else if (id)
            {
                if (level == LVL.prototype)
                    break;      // declared later as Parameter, not VarDeclaration

                if (dt.ty == AST.Tvoid)
                    error("`void` has no value");

                AST.Initializer initializer;
                bool hasInitializer;
                if (token.value == TOK.assign)
                {
                    nextToken();
                    hasInitializer = true;
                    initializer = cparseInitializer();
                }
                // declare the symbol
                assert(id);
                if (dt.isTypeFunction())
                {
                    if (hasInitializer)
                        error("no initializer for function declaration");
                    if (specifier.scw & SCW.x_Thread_local)
                        error("functions cannot be `_Thread_local`"); // C11 6.7.1-4
                    auto fd = new AST.FuncDeclaration(token.loc, Loc.initial, id, specifiersToSTC(level, specifier), dt, specifier.noreturn);
                    s = fd;
                }
                else
                {
                    // Give non-extern variables an implicit void initializer
                    // if one has not been explicitly set.
                    if (!hasInitializer && !(specifier.scw & SCW.xextern))
                        initializer = new AST.VoidInitializer(token.loc);
                    s = new AST.VarDeclaration(token.loc, dt, id, initializer, specifiersToSTC(level, specifier));
                }
            }
            if (s !is null)
            {
                s = applySpecifier(s, specifier);
                if (level == LVL.local)
                {
                    // Wrap the declaration in `extern (C) { declaration }`
                    // Necessary for function pointers, but harmless to apply to all.
                    auto decls = new AST.Dsymbols(1);
                    (*decls)[0] = s;
                    s = new AST.LinkDeclaration(s.loc, linkage, decls);
                }
                // Saw `asm("name")` in the function, type, or variable definition.
                // This maps directly to `pragma(mangle, "name")`
                if (asmname)
                {
                    auto args = new AST.Expressions(1);
                    (*args)[0] = asmname;
                    auto decls = new AST.Dsymbols(1);
                    (*decls)[0] = s;
                    s = new AST.PragmaDeclaration(asmname.loc, Id.mangle, args, decls);
                }
                symbols.push(s);
            }
            first = false;

            switch (token.value)
            {
                case TOK.identifier:
                    error("missing comma");
                    goto default;

                case TOK.semicolon:
                    nextToken();
                    return;

                case TOK.comma:
                    if (!symbolsSave)
                        symbolsSave = symbols;
                    nextToken();
                    break;

                default:
                    error("`=`, `;` or `,` expected");
                    while (token.value != TOK.semicolon && token.value != TOK.endOfFile)
                        nextToken();
                    nextToken();
                    return;
            }
        }
    }

    /***************************************
     * C11 Function Definitions
     * function-definition
     *    declaration-specifiers declarator declaration-list (opt) compound-statement
     *
     * declaration-list:
     *    declaration
     *    declaration-list declaration
     *
     * It's already been parsed up to the declaration-list (opt).
     * Pick it up from there.
     * Params:
     *    id = function identifier
     *    ft = function type
     *    specifier = function specifiers
     * Returns:
     *  Dsymbol for the function
     */
    AST.Dsymbol cparseFunctionDefinition(Identifier id, AST.TypeFunction ft, ref Specifier specifier)
    {
        if (token.value != TOK.leftCurly)       // if not start of a compound-statement
        {
            // Do declaration-list
            do
            {
                cparseDeclaration(LVL.parameter);
            } while (token.value != TOK.leftCurly);

            /* Since there were declarations, the parameter-list must have been
             * an identifier-list.
             */
            auto pl = ft.parameterList;
            pl.hasIdentifierList = true;        // semantic needs to know to adjust parameter types
            if (pl.varargs != AST.VarArg.none)
                error("function identifier-list cannot end with `...`");
            auto plLength = pl.length;
            if (symbols.length != plLength)
                error("%d identifiers does not match %d declarations", cast(int)plLength, cast(int)symbols.length);

            /* Transfer the types and storage classes from symbols[] to pl[]
             */
            foreach (i; 0 .. plLength)
            {
                auto p = pl[i];  // yes, quadratic

                // Convert typedef-identifier to identifier
                if (p.type)
                {
                    if (auto t = p.type.isTypeIdentifier())
                    {
                        p.ident = t.ident;
                        p.type = null;
                    }
                }

                if (p.type || !(p.storageClass & STC.parameter))
                    error("storage class and type are not allowed in identifier-list");
                foreach (s; (*symbols)[]) // yes, quadratic
                {
                    auto d = s.isDeclaration();
                    if (d && p.ident == d.ident && d.type)
                    {
                        p.type = d.type;
                        p.storageClass = d.storage_class;
                        d.type = null; // don't reuse
                        break;
                    }
                }
                if (!p.type)
                {
                    error("no declaration for identifier `%s`", p.ident.toChars());
                    p.type = AST.Type.terror;
                }
            }
        }

        addFuncName = false;    // gets set to true if somebody references __func__ in this function
        const locFunc = token.loc;

        auto body = cparseStatement(ParseStatementFlags.curly);  // don't start a new scope; continue with parameter scope
        auto fd = new AST.FuncDeclaration(locFunc, prevloc, id, specifiersToSTC(LVL.global, specifier), ft, specifier.noreturn);

        if (addFuncName)
        {
            auto s = createFuncName(locFunc, id);
            body = new AST.CompoundStatement(locFunc, s, body);
        }
        fd.fbody = body;

        // TODO add `symbols` to the function's local symbol table `sc2` in FuncDeclaration::semantic3()

        return fd;
    }

    /***************************************
     * C11 Initialization
     * initializer:
     *    assignment-expression
     *    { initializer-list }
     *    { initializer-list , }
     *
     * initializer-list:
     *    designation (opt) initializer
     *    initializer-list , designation (opt) initializer
     *
     * designation:
     *    designator-list =
     *
     * designator-list:
     *    designator
     *    designator-list designator
     *
     * designator:
     *    [ constant-expression ]
     *    . identifier
     * Returns:
     *    initializer
     */
    AST.Initializer cparseInitializer()
    {
        if (token.value != TOK.leftCurly)
        {
            auto ae = cparseAssignExp();        // assignment-expression
            return new AST.ExpInitializer(token.loc, ae);
        }
        nextToken();
        const loc = token.loc;

        /* Collect one or more `designation (opt) initializer`
         * into ci.initializerList, but lazily create ci
         */
        AST.CInitializer ci;
        while (1)
        {
            /* There can be 0 or more designators preceding an initializer.
             * Collect them in desigInit
             */
            AST.DesigInit desigInit;
            while (1)
            {
                if (token.value == TOK.leftBracket)     // [ constant-expression ]
                {
                    nextToken();
                    auto e = cparseConstantExp();
                    check(TOK.rightBracket);
                    if (!desigInit.designatorList)
                        desigInit.designatorList = new AST.Designators;
                    desigInit.designatorList.push(AST.Designator(e));
                }
                else if (token.value == TOK.dot)        // . identifier
                {
                    nextToken();
                    if (token.value != TOK.identifier)
                    {
                        error("identifier expected following `.` designator");
                        break;
                    }
                    if (!desigInit.designatorList)
                        desigInit.designatorList = new AST.Designators;
                    desigInit.designatorList.push(AST.Designator(token.ident));
                    nextToken();
                }
                else
                {
                    if (desigInit.designatorList)
                        check(TOK.assign);
                    break;
                }
            }

            desigInit.initializer = cparseInitializer();
            if (!ci)
                ci = new AST.CInitializer(loc);
            ci.initializerList.push(desigInit);
            if (token.value == TOK.comma)
            {
                nextToken();
                if (token.value != TOK.rightCurly)
                    continue;
            }
            break;
        }
        check(TOK.rightCurly);
        //printf("ci: %s\n", ci.toChars());
        return ci;
    }

    /*************************************
     * C11 6.7
     * declaration-specifier:
     *    storage-class-specifier declaration-specifiers (opt)
     *    type-specifier declaration-specifiers (opt)
     *    type-qualifier declaration-specifiers (opt)
     *    function-specifier declaration-specifiers (opt)
     *    alignment-specifier declaration-specifiers (opt)
     * Params:
     *  level = declaration context
     *  specifier = specifiers in and out
     * Returns:
     *  resulting type, null if not specified
     */
    private AST.Type cparseDeclarationSpecifiers(LVL level, ref Specifier specifier)
    {
        enum TKW : uint
        {
            xnone      = 0,
            xchar      = 1,
            xsigned    = 2,
            xunsigned  = 4,
            xshort     = 8,
            xint       = 0x10,
            xlong      = 0x20,
            xllong     = 0x40,
            xfloat     = 0x80,
            xdouble    = 0x100,
            xldouble   = 0x200,
            xtag       = 0x400,
            xident     = 0x800,
            xvoid      = 0x1000,
            xbool      = 0x4000,
            ximaginary = 0x8000,
            xcomplex   = 0x10000,
            x_Atomic   = 0x20000,
        }

        AST.Type t;
        Loc loc;
        //printf("parseDeclarationSpecifiers()\n");

        TKW tkw;
        SCW scw = specifier.scw & SCW.xtypedef;
        MOD mod;
        Identifier id;
        Identifier previd;

    Lwhile:
        while (1)
        {
            //printf("token %s\n", token.toChars());
            TKW tkwx;
            SCW scwx;
            MOD modx;
            switch (token.value)
            {
                // Storage class specifiers
                case TOK.static_:    scwx = SCW.xstatic;    break;
                case TOK.extern_:    scwx = SCW.xextern;    break;
                case TOK.auto_:      scwx = SCW.xauto;      break;
                case TOK.register:   scwx = SCW.xregister;  break;
                case TOK.typedef_:   scwx = SCW.xtypedef;   break;
                case TOK.inline:     scwx = SCW.xinline;    break;
                case TOK._Noreturn:  scwx = SCW.x_Noreturn; break;
                case TOK._Thread_local: scwx = SCW.x_Thread_local; break;

                // Type qualifiers
                case TOK.const_:     modx = MOD.xconst;     break;
                case TOK.volatile:   modx = MOD.xvolatile;  break;
                case TOK.restrict:   modx = MOD.xrestrict;  break;

                // Type specifiers
                case TOK.char_:      tkwx = TKW.xchar;      break;
                case TOK.signed:     tkwx = TKW.xsigned;    break;
                case TOK.unsigned:   tkwx = TKW.xunsigned;  break;
                case TOK.int16:      tkwx = TKW.xshort;     break;
                case TOK.int32:      tkwx = TKW.xint;       break;
                case TOK.int64:      tkwx = TKW.xlong;      break;
                case TOK.float32:    tkwx = TKW.xfloat;     break;
                case TOK.float64:    tkwx = TKW.xdouble;    break;
                case TOK.void_:      tkwx = TKW.xvoid;      break;
                case TOK._Bool:      tkwx = TKW.xbool;      break;
                case TOK._Imaginary: tkwx = TKW.ximaginary; break;
                case TOK._Complex:   tkwx = TKW.xcomplex;   break;

                case TOK.identifier:
                    tkwx = TKW.xident;
                    id = token.ident;
                    break;

                case TOK.struct_:
                case TOK.union_:
                {
                    const structOrUnion = token.value;
                    const sloc = token.loc;
                    nextToken();

                    /* GNU Extensions
                     * struct-or-union-specifier:
                     *    struct-or-union gnu-attributes (opt) identifier (opt) { struct-declaration-list } gnu-attributes (opt)
                     *    struct-or-union gnu-attribute (opt) identifier
                     */
                    if (token.value == TOK.__attribute__)
                        cparseGnuAttributes(specifier);

                    t = cparseStruct(sloc, structOrUnion, symbols);
                    tkwx = TKW.xtag;
                    break;
                }

                case TOK.enum_:
                    t = cparseEnum(symbols);
                    tkwx = TKW.xtag;
                    break;

                case TOK._Atomic:
                {
                    // C11 6.7.2.4
                    // type-specifier if followed by `( type-name )`
                    auto tk = peek(&token);
                    if (tk.value == TOK.leftParenthesis)
                    {
                        tk = peek(tk);
                        if (isTypeName(tk) && tk.value == TOK.rightParenthesis)
                        {
                            nextToken();
                            t = cparseTypeName();
                            // TODO - implement the "atomic" part of t
                            tkwx = TKW.x_Atomic;
                            break;
                        }
                    }
                    // C11 6.7.3 type-qualifier if not
                    modx = MOD.x_Atomic;
                    break;
                }

                case TOK._Alignas:
                {
                    /* C11 6.7.5
                     * _Alignas ( type-name )
                     * _Alignas ( constant-expression )
                     */

                    if (level & (LVL.parameter | LVL.prototype))
                        error("no alignment-specifier for parameters"); // C11 6.7.5-2

                    nextToken();
                    check(TOK.leftParenthesis);
                    AST.Expression exp;
                    auto tk = &token;
                    if (isTypeName(tk))  // _Alignas ( type-name )
                    {
                        auto talign = cparseTypeName();
                        /* Convert type to expression: `talign.alignof`
                         */
                        auto e = new AST.TypeExp(loc, talign);
                        exp = new AST.DotIdExp(loc, e, Id.__xalignof);
                    }
                    else  // _Alignas ( constant-expression )
                    {
                        exp = cparseConstantExp();
                    }

                    if (!specifier.alignExps)
                        specifier.alignExps = new AST.Expressions(0);
                    specifier.alignExps.push(exp);

                    check(TOK.rightParenthesis);
                    break;
                }

                case TOK.__attribute__:
                {
                    /* GNU Extensions
                     * declaration-specifiers:
                     *    gnu-attributes declaration-specifiers (opt)
                     */
                    cparseGnuAttributes(specifier);
                    break;
                }

                default:
                    break Lwhile;
            }

            if (tkwx)
            {
                if (tkw & TKW.xlong && tkwx & TKW.xlong)
                {
                    tkw &= ~TKW.xlong;
                    tkwx = TKW.xllong;
                }
                if (tkw && tkwx & TKW.xident)
                {
                    // 2nd identifier can't be a typedef
                    break Lwhile; // leave parser on the identifier for the following declarator
                }
                else if (tkwx & TKW.xident)
                {
                    // 1st identifier, save it for TypeIdentifier
                    previd = id;
                }
                if (tkw & TKW.xident && tkwx ||  // typedef-name followed by type-specifier
                    tkw & tkwx)                  // duplicate type-specifiers
                {
                    error("illegal combination of type specifiers");
                    tkwx = TKW.init;
                }
                tkw |= tkwx;
                if (!(tkwx & TKW.xtag)) // if parser already advanced
                    nextToken();
                continue;
            }

            if (modx)
            {
                mod |= modx;
                nextToken();
                continue;
            }

            if (scwx)
            {
                if (scw & scwx)
                    error("duplicate storage class");
                scw |= scwx;
                const scw2 = scw & (SCW.xstatic | SCW.xextern | SCW.xauto | SCW.xregister | SCW.xtypedef);
                if (scw2 & (scw2 - 1) ||
                    scw & (SCW.xauto | SCW.xregister) && scw & (SCW.xinline | SCW.x_Noreturn))
                {
                    error("conflicting storage class");
                    scw &= ~scwx;
                }
                if (level & (LVL.parameter | LVL.prototype) &&
                    scw & ~SCW.xregister)
                {
                    error("only `register` storage class allowed for function parameters");
                    scw &= ~scwx;
                }
                if (level == LVL.global &&
                    scw & (SCW.xauto | SCW.xregister))
                {
                    error("`auto` and `register` storage class not allowed for global");
                    scw &= ~scwx;
                }
                nextToken();
                continue;
            }
        }

        specifier.scw = scw;
        specifier.mod = mod;

        // Convert TKW bits to type t
        switch (tkw)
        {
            case TKW.xnone:                     t = null; break;

            case TKW.xchar:                     t = AST.Type.tchar; break;
            case TKW.xsigned | TKW.xchar:       t = AST.Type.tint8; break;
            case TKW.xunsigned | TKW.xchar:     t = AST.Type.tuns8; break;

            case TKW.xshort:
            case TKW.xsigned | TKW.xshort:
            case TKW.xsigned | TKW.xshort | TKW.xint:
            case TKW.xshort | TKW.xint:         t = AST.Type.tint16; break;

            case TKW.xunsigned | TKW.xshort | TKW.xint:
            case TKW.xunsigned | TKW.xshort:    t = AST.Type.tuns16; break;

            case TKW.xint:
            case TKW.xsigned:
            case TKW.xsigned | TKW.xint:        t = AST.Type.tint32; break;

            case TKW.xunsigned:
            case TKW.xunsigned | TKW.xint:      t = AST.Type.tuns32; break;

            case TKW.xlong:
            case TKW.xsigned | TKW.xlong:
            case TKW.xsigned | TKW.xlong | TKW.xint:
            case TKW.xlong | TKW.xint:          t = longsize == 4 ? AST.Type.tint32 : AST.Type.tint64; break;

            case TKW.xunsigned | TKW.xlong | TKW.xint:
            case TKW.xunsigned | TKW.xlong:     t = longsize == 4 ? AST.Type.tuns32 : AST.Type.tuns64; break;

            case TKW.xllong:
            case TKW.xsigned | TKW.xllong:
            case TKW.xsigned | TKW.xllong | TKW.xint:
            case TKW.xllong | TKW.xint:          t = AST.Type.tint64; break;

            case TKW.xunsigned | TKW.xllong | TKW.xint:
            case TKW.xunsigned | TKW.xllong:     t = AST.Type.tuns64; break;

            case TKW.xvoid:                     t = AST.Type.tvoid; break;
            case TKW.xbool:                     t = AST.Type.tbool; break;

            case TKW.xfloat:                    t = AST.Type.tfloat32; break;
            case TKW.xdouble:                   t = AST.Type.tfloat64; break;
            case TKW.xlong | TKW.xdouble:       t = realType(RTFlags.realfloat); break;

            case TKW.ximaginary | TKW.xfloat:              t = AST.Type.timaginary32; break;
            case TKW.ximaginary | TKW.xdouble:             t = AST.Type.timaginary64; break;
            case TKW.ximaginary | TKW.xlong | TKW.xdouble: t = realType(RTFlags.imaginary); break;

            case TKW.xcomplex | TKW.xfloat:                t = AST.Type.tcomplex32; break;
            case TKW.xcomplex | TKW.xdouble:               t = AST.Type.tcomplex64; break;
            case TKW.xcomplex | TKW.xlong | TKW.xdouble:   t = realType(RTFlags.complex); break;

            case TKW.xident:                    t = new AST.TypeIdentifier(loc, previd);
                break;

            case TKW.xtag:
                break;          // t is already set

            default:
                error("illegal type combination");
                t = AST.Type.terror;
                break;
        }

        return t;
    }

    /********************************
     * C11 6.7.6
     * Parse a declarator (including function definitions).
     * declarator:
     *    pointer (opt) direct-declarator
     *
     * direct-declarator :
     *    identifier
     *    ( declarator )
     *    direct-declarator [ type-qualifier-list (opt) assignment-expression (opt) ]
     *    direct-declarator [ static type-qualifier-list (opt) assignment-expression ]
     *    direct-declarator [ type-qualifier-list static assignment-expression (opt) ]
     *    direct-declarator [ type-qualifier-list (opt) * ]
     *    direct-declarator ( parameter-type-list )
     *    direct-declarator ( identifier-list (opt) )
     *
     * pointer :
     *    * type-qualifier-list (opt)
     *    * type-qualifier-list (opt) pointer
     *
     * type-qualifier-list :
     *    type-qualifier
     *    type-qualifier-list type-qualifier
     *
     * parameter-type-list :
     *    parameter-list
     *    parameter-list , ...
     *
     * parameter-list :
     *    parameter-declaration
     *    parameter-list , parameter-declaration
     *
     * parameter-declaration :
     *    declaration-specifiers declarator
     *    declaration-specifiers abstract-declarator (opt)
     *
     * identifier-list :
     *    identifier
     *    identifier-list , identifier
     *
     * Params:
     *  declarator   = declarator kind
     *  t            = base type to start with
     *  pident       = set to Identifier if there is one, null if not
     *  storageClass = any storage classes seen so far that apply to a function
     * Returns:
     *  type declared. If a TypeFunction is returned, this.symbols is the
     *  symbol table for the parameter-type-list, which will contain any
     *  declared struct, union or enum tags.
     */
    private AST.Type cparseDeclarator(DTR declarator, AST.Type t,
        out Identifier pident, StorageClass storageClass = 0)
    {
        //printf("cparseDeclarator(%d)\n", declarator);
        AST.Types constTypes; // all the Types that will need `const` applied to them
        constTypes.setDim(0);

        AST.Type parseDecl(AST.Type t)
        {
            AST.Type ts;
            while (1)
            {
                switch (token.value)
                {
                case TOK.identifier:        // identifier
                    //printf("identifier %s\n", token.ident.toChars());
                    if (declarator == DTR.xabstract)
                        error("identifier not allowed in abstract-declarator");
                    pident = token.ident;
                    ts = t;
                    nextToken();
                    break;

                case TOK.leftParenthesis:   // ( declarator )
                    /* like: T (*fp)();
                     *       T ((*fp))();
                     */
                    nextToken();
                    ts = parseDecl(t);
                    check(TOK.rightParenthesis);
                    break;

                case TOK.mul:               // pointer
                    t = new AST.TypePointer(t);
                    nextToken();
                    // add post fixes const/volatile/restrict/_Atomic
                    const mod = cparseTypeQualifierList();
                    if (mod & MOD.xconst)
                        constTypes.push(t);
                    continue;

                default:
                    if (declarator == DTR.xdirect)
                    {
                        error("identifier or `(` expected"); // )
                        panic();
                    }
                    ts = t;
                    break;
                }
                break;
            }

            // parse DeclaratorSuffixes
            while (1)
            {
                /* Insert tx -> t into
                 *   ts -> ... -> t
                 * so that
                 *   ts -> ... -> tx -> t
                 */
                static void insertTx(ref AST.Type ts, AST.Type tx, AST.Type t)
                {
                    AST.Type* pt;
                    for (pt = &ts; *pt != t; pt = &(cast(AST.TypeNext)*pt).next)
                    {
                    }
                    *pt = tx;
                }

                switch (token.value)
                {
                    case TOK.leftBracket:
                    {
                        // post [] syntax, pick up any leading type qualifiers, `static` and `*`
                        AST.Type ta;
                        nextToken();

                        auto mod = cparseTypeQualifierList();   // const/volatile/restrict/_Atomic

                        bool isStatic;
                        bool isVLA;
                        if (token.value == TOK.static_)
                        {
                            isStatic = true;    // `static`
                            nextToken();
                            if (!mod)           // type qualifiers after `static`
                                mod = cparseTypeQualifierList();
                        }
                        else if (token.value == TOK.mul)
                        {
                            if (peekNext() == TOK.rightBracket)
                            {
                                isVLA = true;   // `*`
                                nextToken();
                            }
                        }

                        if (isStatic || token.value != TOK.rightBracket)
                        {
                            //printf("It's a static array\n");
                            AST.Expression e = cparseAssignExp(); // [ expression ]
                            ta = new AST.TypeSArray(t, e);
                        }
                        else
                        {
                            // An array of unknown size, fake it with a DArray
                            ta = new AST.TypeDArray(t); // []
                        }
                        check(TOK.rightBracket);

                        // Issue errors for unsupported types.
                        if (isVLA) // C11 6.7.6.2
                        {
                            error("variable length arrays are not supported");
                        }
                        if (isStatic) // C11 6.7.6.3
                        {
                            error("static array parameters are not supported");
                        }
                        if (declarator != DTR.xparameter)
                        {
                            /* C11 6.7.6.2-4: '*' can only be used with function prototype scope.
                             */
                            if (isVLA)
                                error("variable length array used outside of function prototype");
                            /* C11 6.7.6.2-1: type qualifiers and 'static' shall only appear
                             * in a declaration of a function parameter with an array type.
                             */
                            if (isStatic || mod)
                                error("static or type qualifier used outside of function prototype");
                        }
                        if (ts.isTypeSArray() || ts.isTypeDArray())
                        {
                            /* C11 6.7.6.2-1: type qualifiers and 'static' shall only appear
                             * in the outermost array type derivation.
                             */
                            if (isStatic || mod)
                                error("static or type qualifier used in non-outermost array type derivation");
                            /* C11 6.7.6.2-1: the element type shall not be an incomplete or
                             * function type.
                             */
                            if (ta.isTypeDArray() && !isVLA)
                                error("array type has incomplete element type `%s`", ta.toChars());
                        }

                        // Apply type qualifiers to the constructed type.
                        if (mod & MOD.xconst) // ignore the other bits
                            ta = toConst(ta);
                        insertTx(ts, ta, t);  // ts -> ... -> ta -> t
                        continue;
                    }

                    case TOK.leftParenthesis:
                    {
                        // New symbol table for parameter-list
                        auto symbolsSave = this.symbols;
                        this.symbols = null;

                        auto parameterList = cparseParameterList();
                        AST.Type tf = new AST.TypeFunction(parameterList, t, linkage, 0);
    //                  tf = tf.addSTC(storageClass);  // TODO
                        insertTx(ts, tf, t);  // ts -> ... -> tf -> t

                        if (ts != tf)
                            this.symbols = symbolsSave;
                        break;
                    }

                    default:
                        break;
                }
                break;
            }
            return ts;
        }

        t = parseDecl(t);

        /* Because const is transitive, cannot assemble types from
         * fragments. Instead, types to be annotated with const are put
         * in constTypes[], and a bottom up scan of t is done to apply
         * const
         */
        if (constTypes.length)
        {
            AST.Type constApply(AST.Type t)
            {
                if (t.nextOf())
                {
                    auto tn = cast(AST.TypeNext)t; // t.nextOf() should return a ref instead of this
                    tn.next = constApply(tn.next);
                }
                foreach (tc; constTypes[])
                {
                    if (tc is t)
                    {
                        return toConst(t);
                    }
                }
                return t;
            }

            t = constApply(t);
        }

        //printf("result: %s\n", t.toChars());
        return t;
    }

    /******************************
     * C11 6.7.3
     * type-qualifier:
     *    const
     *    restrict
     *    volatile
     *    _Atomic
     */
    MOD cparseTypeQualifierList()
    {
        MOD mod;
        while (1)
        {
            switch (token.value)
            {
                case TOK.const_:     mod |= MOD.xconst;     break;
                case TOK.volatile:   mod |= MOD.xvolatile;  break;
                case TOK.restrict:   mod |= MOD.xrestrict;  break;
                case TOK._Atomic:    mod |= MOD.x_Atomic;   break;

                default:
                    return mod;
            }
            nextToken();
        }
    }

    /***********************************
     * C11 6.7.7
     */
    AST.Type cparseTypeName()
    {
        Specifier specifier;
        auto tspec = cparseSpecifierQualifierList(LVL.global, specifier);
        Identifier id;
        return cparseDeclarator(DTR.xabstract, tspec, id);
    }

    /***********************************
     * C11 6.7.2.1
     * specifier-qualifier-list:
     *    type-specifier specifier-qualifier-list (opt)
     *    type-qualifier specifier-qualifier-list (opt)
     * Params:
     *  level = declaration context
     *  specifier = specifiers in and out
     * Returns:
     *  resulting type, null if not specified
     */
    AST.Type cparseSpecifierQualifierList(LVL level, ref Specifier specifier)
    {
        auto t = cparseDeclarationSpecifiers(level, specifier);
        if (specifier.scw)
            error("storage class not allowed in specifier-qualified-list");
        return t;
    }

    /***********************************
     * C11 6.7.6.3
     * ( parameter-type-list )
     * ( identifier-list (opt) )
     */
    AST.ParameterList cparseParameterList()
    {
        auto parameters = new AST.Parameters();
        AST.VarArg varargs = AST.VarArg.none;
        StorageClass varargsStc;

        check(TOK.leftParenthesis);
        if (token.value == TOK.void_ && peekNext() == TOK.rightParenthesis)
        {
            nextToken();
            nextToken();
            return AST.ParameterList(parameters, varargs, varargsStc);
        }

        /* The check for identifier-list comes later,
         * when doing the trailing declaration-list (opt)
         */
        while (1)
        {
            if (token.value == TOK.rightParenthesis)
                break;
            if (token.value == TOK.dotDotDot)
            {
                varargs = AST.VarArg.variadic;  // C-style variadics
                nextToken();
                check(TOK.rightParenthesis);
                return AST.ParameterList(parameters, varargs, varargsStc);
            }

            Specifier specifier;
            auto tspec = cparseDeclarationSpecifiers(LVL.prototype, specifier);

            Identifier id;
            auto t = cparseDeclarator(DTR.xparameter, tspec, id);
            if (specifier.mod & MOD.xconst)
                t = toConst(t);
            auto param = new AST.Parameter(STC.parameter, t, id, null, null);
            parameters.push(param);
            if (token.value == TOK.rightParenthesis)
                break;
            check(TOK.comma);
        }
        nextToken();
        return AST.ParameterList(parameters, varargs, varargsStc);
    }

    /***********************************
     * C11 6.7.10
     * _Static_assert ( constant-expression , string-literal ) ;
     */
    private AST.StaticAssert cparseStaticAssert()
    {
        const loc = token.loc;

        //printf("cparseStaticAssert()\n");
        nextToken();
        check(TOK.leftParenthesis);
        auto exp = cparseConstantExp();
        check(TOK.comma);
        if (token.value != TOK.string_)
            error("string literal expected");
        auto msg = cparsePrimaryExp();
        check(TOK.rightParenthesis);
        check(TOK.semicolon);
        return new AST.StaticAssert(loc, exp, msg);
    }

    /*************************
     * Collect argument list.
     * Parser is on opening parenthesis.
     * Returns:
     *    the arguments
     */
    private AST.Expressions* cparseArguments()
    {
        nextToken();
        auto arguments = new AST.Expressions();
        while (token.value != TOK.rightParenthesis && token.value != TOK.endOfFile)
        {
            auto arg = cparseAssignExp();
            arguments.push(arg);
            if (token.value != TOK.comma)
                break;

            nextToken(); // consume comma
        }

        check(TOK.rightParenthesis);

        return arguments;
    }

    /*************************
     * __declspec parser
     * https://docs.microsoft.com/en-us/cpp/cpp/declspec
     * decl-specifier:
     *    __declspec ( extended-decl-modifier-seq )
     *
     * extended-decl-modifier-seq:
     *    extended-decl-modifier (opt)
     *    extended-decl-modifier extended-decl-modifier-seq
     *
     * extended-decl-modifier:
     *    dllimport
     *    dllexport
     */
    private void cparseDeclspec()
    {
        /* Check for dllexport, dllimport
         * Ignore the rest
         */
        bool dllimport;  // TODO implement
        bool dllexport;  // TODO implement
        nextToken();     // move past __declspec
        check(TOK.leftParenthesis);
        while (1)
        {
            if (token.value == TOK.rightParenthesis)
            {
                nextToken();
                break;
            }
            else if (token.value == TOK.endOfFile)
                break;
            else if (token.value == TOK.identifier)
            {
                if (token.ident == Id.dllimport)
                {
                    dllimport = true;
                    nextToken();
                }
                else if (token.ident == Id.dllexport)
                {
                    dllexport = true;
                    nextToken();
                }
                else
                {
                    nextToken();
                    if (token.value == TOK.leftParenthesis)
                        cparseParens();
                }
            }
            else
            {
                error("extended-decl-modifier expected");
            }
            break;
        }
    }

    /*************************
     * Simple asm parser
     * https://gcc.gnu.org/onlinedocs/gcc/Asm-Labels.html
     * simple-asm-expr:
     *   asm ( asm-string-literal )
     *
     * asm-string-literal:
     *   string-literal
     */
    private AST.Expression cparseSimpleAsmExpr()
    {
        nextToken();     // move past asm
        check(TOK.leftParenthesis);
        if (token.value != TOK.string_)
            error("string literal expected");
        auto label = cparsePrimaryExp();
        check(TOK.rightParenthesis);
        return label;
    }

    /*************************
     * __attribute__ parser
     * https://gcc.gnu.org/onlinedocs/gcc/Attribute-Syntax.html
     * gnu-attributes:
     *   gnu-attributes gnu-attribute-specifier
     *
     * gnu-attribute-specifier:
     *    __attribute__ (( gnu-attribute-list ))
     *
     * gnu-attribute-list:
     *    gnu-attribute (opt)
     *    gnu-attribute-list , gnu-attribute
     *
     * Params:
     *  specifier = filled in with the attribute(s)
     */
    private void cparseGnuAttributes(ref Specifier specifier)
    {
        while (token.value == TOK.__attribute__)
        {
            nextToken();     // move past __attribute__
            check(TOK.leftParenthesis);
            check(TOK.leftParenthesis);

            if (token.value != TOK.rightParenthesis)
            {
                while (1)
                {
                    cparseGnuAttribute(specifier);
                    if (token.value != TOK.comma)
                        break;
                    nextToken();
                }
            }

            check(TOK.rightParenthesis);
            check(TOK.rightParenthesis);
        }
    }

    /*************************
     * Parse a single GNU attribute
     * gnu-attribute:
     *    gnu-attribute-name
     *    gnu-attribute-name ( identifier )
     *    gnu-attribute-name ( identifier , expression-list )
     *    gnu-attribute-name ( expression-list (opt) )
     *
     * gnu-attribute-name:
     *    keyword
     *    identifier
     *
     * expression-list:
     *    constant-expression
     *    expression-list , constant-expression
     *
     * Params:
     *  specifier = filled in with the attribute(s)
     */
    private void cparseGnuAttribute(ref Specifier specifier)
    {
        /* Check for dllimport, dllexport, vector_size(bytes)
         * Ignore the rest
         */
        bool dllimport;  // TODO implement
        bool dllexport;  // TODO implement

        if (!isGnuAttributeName())
            return;

        if (token.value == TOK.identifier)
        {
            if (token.ident == Id.dllimport)
            {
                dllimport = true;
                nextToken();
            }
            else if (token.ident == Id.dllexport)
            {
                dllexport = true;
                nextToken();
            }
            else if (token.ident == Id.noreturn)
            {
                specifier.noreturn = true;
                nextToken();
            }
            else if (token.ident == Id.vector_size)
            {
                nextToken();
                check(TOK.leftParenthesis);
                cparseConstantExp();  // TODO implement
                check(TOK.rightParenthesis);
            }
            else
            {
                nextToken();
                if (token.value == TOK.leftParenthesis)
                    cparseParens();
            }
        }
        else
        {
            nextToken();
            if (token.value == TOK.leftParenthesis)
                cparseParens();
        }
    }

    /*************************
     * See if match for GNU attribute name, which may be any identifier,
     * storage-class-specifier, type-specifier, or type-qualifier.
     * Returns:
     *  true if a valid GNU attribute name
     */
    private bool isGnuAttributeName()
    {
        switch (token.value)
        {
            case TOK.identifier:
            case TOK.static_:
            case TOK.unsigned:
            case TOK.int64:
            case TOK.const_:
            case TOK.extern_:
            case TOK.register:
            case TOK.typedef_:
            case TOK.int16:
            case TOK.inline:
            case TOK._Noreturn:
            case TOK.volatile:
            case TOK.signed:
            case TOK.auto_:
            case TOK.restrict:
            case TOK._Complex:
            case TOK._Thread_local:
            case TOK.int32:
            case TOK.char_:
            case TOK.float32:
            case TOK.float64:
            case TOK.void_:
            case TOK._Bool:
            case TOK._Atomic:
                return true;

            default:
                return false;
        }
    }

    /***************************
     * Like skipParens(), but consume the tokens.
     */
    private void cparseParens()
    {
        check(TOK.leftParenthesis);
        int parens = 1;

        while (1)
        {
            switch (token.value)
            {
                case TOK.leftParenthesis:
                    ++parens;
                    break;

                case TOK.rightParenthesis:
                    --parens;
                    if (parens < 0)
                    {
                        error("extra right parenthesis");
                        return;
                    }
                    if (parens == 0)
                    {
                        nextToken();
                        return;
                    }
                    break;

                case TOK.endOfFile:
                    error("end of file found before right parenthesis");
                    return;

                default:
                    break;
            }
            nextToken();
        }
    }

    //}
    /******************************************************************************/
    /***************************** Struct & Enum Parser ***************************/
    //{

    /*************************************
     * C11 6.7.2.2
     * enum-specifier:
     *    enum identifier (opt) { enumerator-list }
     *    enum identifier (opt) { enumerator-list , }
     *    enum identifier
     *
     * enumerator-list:
     *    enumerator
     *    enumerator-list , enumerator
     *
     * enumerator:
     *    enumeration-constant
     *    enumeration-constant = constant-expression
     *
     * enumeration-constant:
     *    identifier
     *
     * Params:
     *  symbols = symbols to add enum declaration to
     * Returns:
     *  type of the enum
     */
    private AST.Type cparseEnum(ref AST.Dsymbols* symbols)
    {
        const loc = token.loc;
        nextToken();

        /* GNU Extensions
         * enum-specifier:
         *    enum gnu-attributes (opt) identifier (opt) { enumerator-list } gnu-attributes (opt)
         *    enum gnu-attributes (opt) identifier (opt) { enumerator-list , } gnu-attributes (opt)
         *    enum gnu-attributes (opt) identifier
         */
        Specifier specifier;
        if (token.value == TOK.__attribute__)
            cparseGnuAttributes(specifier);

        Identifier tag;
        if (token.value == TOK.identifier)
        {
            tag = token.ident;
            nextToken();
        }

        AST.Dsymbols* members;
        if (token.value == TOK.leftCurly)
        {
            nextToken();
            members = new AST.Dsymbols();

            if (token.value == TOK.rightCurly)  // C11 6.7.2.2-1
            {
                if (tag)
                    error("no members for `enum %s`", tag.toChars());
                else
                    error("no members for anonymous enum");
            }

            while (token.value == TOK.identifier)
            {
                auto ident = token.ident;  // enumeration-constant
                nextToken();
                auto mloc = token.loc;

                AST.Expression value;
                if (token.value == TOK.assign)
                {
                    nextToken();
                    value = cparseConstantExp();
                    // TODO C11 6.7.2.2-2 value must fit into an int
                }

                auto em = new AST.EnumMember(mloc, ident, value, null, 0, null, null);
                members.push(em);

                if (token.value == TOK.comma)
                {
                    nextToken();
                    continue;
                }
                break;
            }
            check(TOK.rightCurly);

            /* GNU Extensions
             * Parse the postfix gnu-attributes (opt)
             */
            if (token.value == TOK.__attribute__)
                cparseGnuAttributes(specifier);
        }
        else if (!tag)
            error("missing `identifier` after `enum`");

        /* Need semantic information to determine if this is a declaration,
         * redeclaration, or reference to existing declaration.
         * Defer to the semantic() pass with a TypeTag.
         */
        return new AST.TypeTag(loc, TOK.enum_, tag, members);
    }

    /*************************************
     * C11 6.7.2.1
     * Parse struct and union specifiers.
     * Parser is advanced to the tag identifier or brace.
     * struct-or-union-specifier:
     *    struct-or-union identifier (opt) { struct-declaration-list }
     *    struct-or-union identifier
     *
     * struct-or-union:
     *    struct
     *    union
     *
     * struct-declaration-list:
     *    struct-declaration
     *    struct-declaration-list struct-declaration
     *
     * Params:
     *  loc = location of `struct` or `union`
     *  structOrUnion = TOK.struct_ or TOK.union_
     *  symbols = symbols to add struct-or-union declaration to
     * Returns:
     *  type of the struct
     */
    private AST.Type cparseStruct(Loc loc, TOK structOrUnion, ref AST.Dsymbols* symbols)
    {
        Identifier tag;

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

        AST.Dsymbols* members;
        if (token.value == TOK.leftCurly)
        {
            nextToken();
            auto symbolsSave = symbols;
            symbols = new AST.Dsymbols();
            while (token.value != TOK.rightCurly)
            {
                cparseStructDeclaration();

                if (token.value == TOK.endOfFile)
                    break;
            }
            members = symbols;          // `members` will be non-null even with 0 members
            symbols = symbolsSave;
            check(TOK.rightCurly);

            if ((*members).length == 0) // C11 6.7.2.1-8
            {
                /* allow empty structs as an extension
                 *  struct-declarator-list:
                 *    struct-declarator (opt)
                 */
            }
        }
        else if (!tag)
            error("missing tag `identifier` after `%s`", Token.toChars(structOrUnion));

        /* Need semantic information to determine if this is a declaration,
         * redeclaration, or reference to existing declaration.
         * Defer to the semantic() pass with a TypeTag.
         */
        return new AST.TypeTag(loc, structOrUnion, tag, members);
    }

    /*************************************
     * C11 6.7.2.1
     * Parse a struct declaration member.
     * struct-declaration:
     *    specifier-qualifier-list struct-declarator-list (opt) ;
     *    static_assert-declaration
     *
     * struct-declarator-list:
     *    struct-declarator
     *    struct-declarator-list , struct-declarator
     *
     * struct-declarator:
     *    declarator
     *    declarator (opt) : constant-expression
     */
    void cparseStructDeclaration()
    {
        //printf("cparseStructDeclaration()\n");
        if (token.value == TOK._Static_assert)
        {
            auto s = cparseStaticAssert();
            symbols.push(s);
            return;
        }

        auto symbolsSave = symbols;
        Specifier specifier;
        auto tspec = cparseSpecifierQualifierList(LVL.member, specifier);

        /* If a declarator does not follow, it is unnamed
         */
        if (token.value == TOK.semicolon && tspec)
        {
            nextToken();
            auto tt = tspec.isTypeTag();
            if (!tt)
                return; // legal but meaningless empty declaration

            /* If anonymous struct declaration
             *   struct { ... members ... };
             * C11 6.7.2.1-13
             */
            if (!tt.id && tt.members)
            {
                /* members of anonymous struct are considered members of
                 * the containing struct
                 */
                auto ad = new AST.AnonDeclaration(tt.loc, tt.tok == TOK.union_, tt.members);
                if (!symbols)
                    symbols = new AST.Dsymbols();
                auto s = applySpecifier(ad, specifier);
                symbols.push(s);
                return;
            }
            if (!tt.id && !tt.members)
                return; // already gave error in cparseStruct()

            /* `struct tag;` and `struct tag { ... };`
             * always result in a declaration in the current scope
             */
            // TODO: merge in specifier
            auto stag = (tt.tok == TOK.struct_)
                ? new AST.StructDeclaration(tt.loc, tt.id, false)
                : new AST.UnionDeclaration(tt.loc, tt.id);
            stag.members = tt.members;
            if (!symbols)
                symbols = new AST.Dsymbols();
            auto s = applySpecifier(stag, specifier);
            symbols.push(s);
            return;
        }

        while (1)
        {
            Identifier id;
            AST.Type dt;
            if (token.value == TOK.colon)
            {
                // C11 6.7.2.1-12 unnamed bit-field
                id = Identifier.generateAnonymousId("BitField");
                dt = tspec;
            }
            else
            {
                dt = cparseDeclarator(DTR.xdirect, tspec, id);
                if (!dt)
                {
                    panic();
                    nextToken();
                    break;          // error recovery
                }
            }

            AST.Expression width;
            if (token.value == TOK.colon)
            {
                // C11 6.7.2.1-10 bit-field
                nextToken();
                width = cparseConstantExp();
            }

            if (specifier.mod & MOD.xconst)
                dt = toConst(dt);

            /* GNU Extensions
             * struct-declarator:
             *    declarator gnu-attributes (opt)
             *    declarator (opt) : constant-expression gnu-attributes (opt)
             */
            if (token.value == TOK.__attribute__)
                cparseGnuAttributes(specifier);

            AST.Dsymbol s = null;
            symbols = symbolsSave;
            if (!symbols)
                symbols = new AST.Dsymbols;     // lazilly create it

            if (!tspec && !specifier.scw && !specifier.mod)
                error("specifier-qualifier-list required");
            else if (width)
            {
                if (specifier.alignExps)
                    error("no alignment-specifier for bit field declaration"); // C11 6.7.5-2
                s = new AST.BitFieldDeclaration(width.loc, dt, id, width);
            }
            else if (id)
            {
                if (dt.ty == AST.Tvoid)
                    error("`void` has no value");

                // declare the symbol
                // Give member variables an implicit void initializer
                auto initializer = new AST.VoidInitializer(token.loc);
                s = new AST.VarDeclaration(token.loc, dt, id, initializer, specifiersToSTC(LVL.member, specifier));
                s = applySpecifier(s, specifier);
            }
            if (s !is null)
                symbols.push(s);

            switch (token.value)
            {
                case TOK.identifier:
                    error("missing comma");
                    goto default;

                case TOK.semicolon:
                    nextToken();
                    return;

                case TOK.comma:
                    nextToken();
                    break;

                default:
                    error("`;` or `,` expected");
                    while (token.value != TOK.semicolon && token.value != TOK.endOfFile)
                        nextToken();
                    nextToken();
                    return;
            }
        }
    }

    //}
    /******************************************************************************/
    /********************************* Lookahead Parser ***************************/
    //{

    /************************************
     * 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)
     * Returns:
     *      true at start of a declaration
     */
    private bool isCDeclaration(ref Token* pt)
    {
        //printf("isCDeclaration()\n");
        auto t = pt;
        if (!isDeclarationSpecifiers(t))
            return false;

        while (1)
        {
            if (t.value == TOK.semicolon)
            {
                t = peek(t);
                pt = t;
                return true;
            }
            if (!isCDeclarator(t, DTR.xdirect))
                return false;
            if (t.value == TOK.asm_)
            {
                t = peek(t);
                if (t.value != TOK.leftParenthesis || !skipParens(t, &t))
                    return false;
            }
            if (t.value == TOK.__attribute__)
            {
                t = peek(t);
                if (t.value != TOK.leftParenthesis || !skipParens(t, &t))
                    return false;
            }
            if (t.value == TOK.assign)
            {
                t = peek(t);
                if (!isInitializer(t))
                    return false;
            }
            switch (t.value)
            {
                case TOK.comma:
                    t = peek(t);
                    break;

                case TOK.semicolon:
                    t = peek(t);
                    pt = t;
                    return true;

                default:
                    return false;
            }
        }
    }

    /********************************
     * See if match for initializer.
     * Params:
     *  pt = starting token, updated to one past end of initializer if true
     * Returns:
     *  true if initializer
     */
    private bool isInitializer(ref Token* pt)
    {
        //printf("isInitializer()\n");
        auto t = pt;

        if (t.value == TOK.leftCurly)
        {
            if (!skipBraces(t))
                return false;
            pt = t;
            return true;
        }

        // skip over assignment-expression, ending before comma or semiColon or EOF
        if (!isAssignmentExpression(t))
            return false;
        pt = t;
        return true;
    }

    /********************************
     * See if match for:
     *    postfix-expression ( argument-expression-list(opt) )
     * Params:
     *  pt = starting token, updated to one past end of initializer if true
     * Returns:
     *  true if function call
     */
    private bool isFunctionCall(ref Token* pt)
    {
        //printf("isFunctionCall()\n");
        auto t = pt;

        if (!isPrimaryExpression(t))
            return false;
        if (t.value != TOK.leftParenthesis)
            return false;
        t = peek(t);
        while (1)
        {
            if (!isAssignmentExpression(t))
                return false;
            if (t.value == TOK.comma)
            {
                t = peek(t);
                continue;
            }
            if (t.value == TOK.rightParenthesis)
            {
                t = peek(t);
                break;
            }
            return false;
        }
        if (t.value != TOK.semicolon)
            return false;
        pt = t;
        return true;
    }

    /********************************
     * See if match for assignment-expression.
     * Params:
     *  pt = starting token, updated to one past end of assignment-expression if true
     * Returns:
     *  true if assignment-expression
     */
    private bool isAssignmentExpression(ref Token* pt)
    {
        //printf("isAssignmentExpression()\n");
        auto t = pt;

        /* This doesn't actually check for grammar matching an
         * assignment-expression. It just matches ( ) [ ] looking for
         * an ending token that would terminate one.
         */
        bool any;
        while (1)
        {
            switch (t.value)
            {
                case TOK.comma:
                case TOK.semicolon:
                case TOK.rightParenthesis:
                case TOK.rightBracket:
                case TOK.endOfFile:
                    if (!any)
                        return false;
                    break;

                case TOK.leftParenthesis:
                    if (!skipParens(t, &t))
                        return false;
                    continue;

                case TOK.leftBracket:
                    if (!skipBrackets(t))
                        return false;
                    continue;

                default:
                    any = true;   // assume token was part of an a-e
                    t = peek(t);
                    continue;
            }
            pt = t;
            return true;
        }
    }

    /********************************
     * See if match for constant-expression.
     * Params:
     *  pt = starting token, updated to one past end of constant-expression if true
     * Returns:
     *  true if constant-expression
     */
    private bool isConstantExpression(ref Token* pt)
    {
        return isAssignmentExpression(pt);
    }

    /********************************
     * See if match for declaration-specifiers.
     * No errors are diagnosed.
     * Params:
     *  pt = starting token, updated to one past end of declaration-specifiers if true
     * Returns:
     *  true if declaration-specifiers
     */
    private bool isDeclarationSpecifiers(ref Token* pt)
    {
        //printf("isDeclarationSpecifiers()\n");

        auto t = pt;

        bool any;
        while (1)
        {
            switch (t.value)
            {
                // type-specifiers
                case TOK.void_:
                case TOK.char_:
                case TOK.int16:
                case TOK.int32:
                case TOK.int64:
                case TOK.float32:
                case TOK.float64:
                case TOK.signed:
                case TOK.unsigned:
                case TOK._Bool:
                //case TOK._Imaginary:
                case TOK._Complex:
                case TOK.identifier: // typedef-name
                    t = peek(t);
                    any = true;
                    break;

                case TOK.struct_:
                case TOK.union_:
                case TOK.enum_:
                    t = peek(t);
                    if (t.value == TOK.identifier)
                    {
                        t = peek(t);
                        if (t.value == TOK.leftCurly)
                        {
                            if (!skipBraces(t))
                                return false;
                        }
                    }
                    else if (t.value == TOK.leftCurly)
                    {
                        if (!skipBraces(t))
                            return false;
                    }
                    else
                        return false;
                    any = true;
                    continue;

                // storage-class-specifiers
                case TOK.typedef_:
                case TOK.extern_:
                case TOK.static_:
                case TOK._Thread_local:
                case TOK.auto_:
                case TOK.register:

                // function-specifiers
                case TOK.inline:
                case TOK._Noreturn:

                // type-qualifiers
                case TOK.const_:
                case TOK.volatile:
                case TOK.restrict:
                    t = peek(t);
                    any = true;
                    continue;

                case TOK._Alignas:      // alignment-specifier
                case TOK.__declspec:    // decl-specifier
                case TOK.__attribute__: // attribute-specifier
                    t = peek(t);
                    if (!skipParens(t, &t))
                        return false;
                    any = true;
                    continue;

                // either atomic-type-specifier or type_qualifier
                case TOK._Atomic:  // TODO _Atomic ( type-name )
                    t = peek(t);
                    if (t.value == TOK.leftParenthesis) // maybe atomic-type-specifier
                    {
                        auto tsave = t;
                        t = peek(t);
                        if (!isTypeName(t) || t.value != TOK.rightParenthesis)
                        {   // it's a type-qualifier
                            t = tsave;  // back up parser
                            any = true;
                            continue;
                        }
                        t = peek(t);    // move past right parenthesis of atomic-type-specifier
                    }
                    any = true;
                    continue;

                default:
                    break;
            }
            break;
        }

        if (any)
        {
            pt = t;
            return true;
        }
        return false;
    }

    /**************************************
     * See if declaration-list is present.
     * Returns:
     *    true if declaration-list is present, even an empty one
     */
    bool isDeclarationList(ref Token* pt)
    {
        auto t = pt;
        while (1)
        {
            if (t.value == TOK.leftCurly)
            {
                pt = t;
                return true;
            }
            if (!isCDeclaration(t))
                return false;
        }
    }

    /*******************************************
     * Skip braces.
     * Params:
     *      pt = enters on left brace, set to token past right bracket on true
     * Returns:
     *      true if successful
     */
    private bool skipBraces(ref Token* pt)
    {
        auto t = pt;
        if (t.value != TOK.leftCurly)
            return false;

        int braces = 0;

        while (1)
        {
            switch (t.value)
            {
                case TOK.leftCurly:
                    ++braces;
                    t = peek(t);
                    continue;

                case TOK.rightCurly:
                    --braces;
                    if (braces == 0)
                    {
                        pt = peek(t);
                        return true;
                    }
                    if (braces < 0)
                        return false;

                    t = peek(t);
                    continue;

                case TOK.endOfFile:
                    return false;

                default:
                    t = peek(t);
                    continue;
            }
        }
    }

    /*******************************************
     * Skip brackets.
     * Params:
     *      pt = enters on left bracket, set to token past right bracket on true
     * Returns:
     *      true if successful
     */
    private bool skipBrackets(ref Token* pt)
    {
        auto t = pt;
        if (t.value != TOK.leftBracket)
            return false;

        int brackets = 0;

        while (1)
        {
            switch (t.value)
            {
                case TOK.leftBracket:
                    ++brackets;
                    t = peek(t);
                    continue;

                case TOK.rightBracket:
                    --brackets;
                    if (brackets == 0)
                    {
                        pt = peek(t);
                        return true;
                    }
                    if (brackets < 0)
                        return false;

                    t = peek(t);
                    continue;

                case TOK.endOfFile:
                    return false;

                default:
                    t = peek(t);
                    continue;
            }
        }
    }

    /*********************************
     * Check to see if tokens starting with *pt form a declarator.
     * Params:
     *  pt = pointer to starting token, updated to point past declarator if true is returned
     *  declarator = declarator kind
     * Returns:
     *  true if it does
     */
    private bool isCDeclarator(ref Token* pt, DTR declarator)
    {
        auto t = pt;
        while (1)
        {
            if (t.value == TOK.mul)     // pointer
            {
                t = peek(t);
                if (!isTypeQualifierList(t))
                    return false;
            }
            else
                break;
        }

        if (t.value == TOK.identifier)
        {
            if (declarator == DTR.xabstract)
                return false;
            t = peek(t);
        }
        else if (t.value == TOK.leftParenthesis)
        {
            t = peek(t);
            if (!isCDeclarator(t, declarator))
                return false;
            if (t.value != TOK.rightParenthesis)
                return false;
            t = peek(t);
        }
        else if (declarator == DTR.xdirect)
        {
            return false;
        }

        while (1)
        {
            if (t.value == TOK.leftBracket)
            {
                if (!skipBrackets(t))
                    return false;
            }
            else if (t.value == TOK.leftParenthesis)
            {
                if (!skipParens(t, &t))
                    return false;
            }
            else
                break;
        }
        pt = t;
        return true;
    }

    /***************************
     * Is this the start of a type-qualifier-list?
     * (Can be empty.)
     * Params:
     *  pt = first token; updated with past end of type-qualifier-list if true
     * Returns:
     *  true if start of type-qualifier-list
     */
    private bool isTypeQualifierList(ref Token* pt)
    {
        auto t = pt;
        while (1)
        {
            switch (t.value)
            {
                case TOK.const_:
                case TOK.restrict:
                case TOK.volatile:
                case TOK._Atomic:
                    t = peek(t);
                    continue;

                default:
                    break;
            }
            break;
        }
        pt = t;
        return true;
    }

    /***************************
     * Is this the start of a type-name?
     * Params:
     *  pt = first token; updated with past end of type-name if true
     * Returns:
     *  true if start of type-name
     */
    private bool isTypeName(ref Token* pt)
    {
        auto t = pt;
        //printf("isTypeName() %s\n", t.toChars());
        if (!isSpecifierQualifierList(t))
            return false;
        if (!isCDeclarator(t, DTR.xabstract))
            return false;
        if (t.value != TOK.rightParenthesis)
            return false;
        pt = t;
        return true;
    }

    /***************************
     * Is this the start of a specifier-qualifier-list?
     * Params:
     *  pt = first token; updated with past end of specifier-qualifier-list if true
     * Returns:
     *  true if start of specifier-qualifier-list
     */
    private bool isSpecifierQualifierList(ref Token* pt)
    {
        auto t = pt;
        bool result;
        while (1)
        {
            switch (t.value)
            {
                // Type Qualifiers
                case TOK.const_:
                case TOK.restrict:
                case TOK.volatile:

                // Type Specifiers
                case TOK.char_:
                case TOK.signed:
                case TOK.unsigned:
                case TOK.int16:
                case TOK.int32:
                case TOK.int64:
                case TOK.float32:
                case TOK.float64:
                case TOK.void_:
                case TOK._Bool:
                //case TOK._Imaginary: // ? missing in Spec
                case TOK._Complex:

                // typedef-name
                case TOK.identifier:    // will not know until semantic if typedef
                    t = peek(t);
                    break;

                // struct-or-union-specifier
                // enum-specifier
                case TOK.struct_:
                case TOK.union_:
                case TOK.enum_:
                    t = peek(t);
                    if (t.value == TOK.identifier)
                    {
                        t = peek(t);
                        if (t.value == TOK.leftCurly)
                        {
                            if (!skipBraces(t))
                                return false;
                        }
                    }
                    else if (t.value == TOK.leftCurly)
                    {
                        if (!skipBraces(t))
                            return false;
                    }
                    else
                        return false;
                    break;

                // atomic-type-specifier
                case TOK._Atomic:
                    t = peek(t);
                    if (t.value != TOK.leftParenthesis ||
                        !skipParens(t, &t))
                        return false;
                    break;

                default:
                    if (result)
                        pt = t;
                    return result;
            }
            result = true;
        }
    }

    /************************************
     * Looking at the leading left parenthesis, and determine if it is
     * either of the following:
     *    ( type-name ) cast-expression
     *    ( type-name ) { initializer-list }
     * as opposed to:
     *    ( expression )
     * Params:
     *    pt = starting token, updated to one past end of constant-expression if true
     *    afterParenType = true if already seen ( type-name )
     * Returns:
     *    true if matches ( type-name ) ...
     */
    private bool isCastExpression(ref Token* pt, bool afterParenType = false)
    {
        auto t = pt;
        switch (t.value)
        {
            case TOK.leftParenthesis:
                auto tk = peek(t);  // move past left parenthesis
                if (!isTypeName(tk) || tk.value != TOK.rightParenthesis)
                {
                    if (afterParenType)
                        goto default; // could be ( type-name ) ( unary-expression )
                    return false;
                }
                tk = peek(tk);  // move past right parenthesis

                if (tk.value == TOK.leftCurly)
                {
                    // ( type-name ) { initializer-list }
                    if (!isInitializer(tk))
                        return false;
                    t = tk;
                    break;
                }
                if (!isCastExpression(tk, true))
                {
                    if (afterParenType) // could be ( type-name ) ( unary-expression )
                        goto default;   // where unary-expression also matched type-name
                    return false;
                }
                // ( type-name ) cast-expression
                t = tk;
                break;

            default:
                if (!afterParenType || !isUnaryExpression(t, afterParenType))
                    return false;
                // if we've already seen ( type-name ), then this is a cast
                break;
        }
        pt = t;
        return true;
    }

    /********************************
     * See if match for unary-expression.
     * Params:
     *    pt = starting token, updated to one past end of constant-expression if true
     *    afterParenType = true if already seen ( type-name ) of a cast-expression
     * Returns:
     *    true if unary-expression
     */
    private bool isUnaryExpression(ref Token* pt, bool afterParenType = false)
    {
        auto t = pt;
        switch (t.value)
        {
            case TOK.plusPlus:
            case TOK.minusMinus:
                t = peek(t);
                if (!isUnaryExpression(t, afterParenType))
                    return false;
                break;

            case TOK.and:
            case TOK.mul:
            case TOK.min:
            case TOK.add:
            case TOK.not:
            case TOK.tilde:
                t = peek(t);
                if (!isCastExpression(t, afterParenType))
                    return false;
                break;

            case TOK.sizeof_:
                t = peek(t);
                if (t.value == TOK.leftParenthesis)
                {
                    auto tk = peek(t);
                    if (isTypeName(tk))
                    {
                        if (tk.value != TOK.rightParenthesis)
                            return false;
                        t = peek(tk);
                        break;
                    }
                }
                if (!isUnaryExpression(t, afterParenType))
                    return false;
                break;

            case TOK._Alignof:
                t = peek(t);
                if (t.value != TOK.leftParenthesis)
                    return false;
                t = peek(t);
                if (!isTypeName(t) || t.value != TOK.rightParenthesis)
                    return false;
                break;

            default:
                // Compound literals are handled by cast and sizeof expressions,
                // so be content with just seeing a primary expression.
                if (!isPrimaryExpression(t))
                    return false;
                break;
        }
        pt = t;
        return true;
    }

    /********************************
     * See if match for primary-expression.
     * Params:
     *    pt = starting token, updated to one past end of constant-expression if true
     * Returns:
     *    true if primary-expression
     */
    private bool isPrimaryExpression(ref Token* pt)
    {
        auto t = pt;
        switch (t.value)
        {
            case TOK.identifier:
            case TOK.int32Literal:
            case TOK.uns32Literal:
            case TOK.int64Literal:
            case TOK.uns64Literal:
            case TOK.float32Literal:
            case TOK.float64Literal:
            case TOK.float80Literal:
            case TOK.imaginary32Literal:
            case TOK.imaginary64Literal:
            case TOK.imaginary80Literal:
            case TOK.string_:
                t = peek(t);
                break;

            case TOK.leftParenthesis:
                // ( expression )
                if (!skipParens(t, &t))
                    return false;
                break;

            case TOK._Generic:
                t = peek(t);
                if (!skipParens(t, &t))
                    return false;
                break;

            default:
                return false;
        }
        pt = t;
        return true;
    }

    //}
    /******************************************************************************/
    /********************************* More ***************************************/
    //{

    /**************
     * Declaration context
     */
    enum LVL
    {
        global    = 1,    /// global
        parameter = 2,    /// function parameter (declarations for function identifier-list)
        prototype = 4,    /// function prototype
        local     = 8,    /// local
        member    = 0x10, /// struct member
    }

    /// Types of declarator to parse
    enum DTR
    {
        xdirect    = 1, /// C11 6.7.6 direct-declarator
        xabstract  = 2, /// C11 6.7.7 abstract-declarator
        xparameter = 3, /// parameter declarator may be either direct or abstract
    }

    /// C11 6.7.1 Storage-class specifiers
    enum SCW : uint
    {
        xnone      = 0,
        xtypedef   = 1,
        xextern    = 2,
        xstatic    = 4,
        x_Thread_local = 8,
        xauto      = 0x10,
        xregister  = 0x20,
        // C11 6.7.4 Function specifiers
        xinline    = 0x40,
        x_Noreturn = 0x80,
    }

    /// C11 6.7.3 Type qualifiers
    enum MOD : uint
    {
        xnone     = 0,
        xconst    = 1,
        xvolatile = 2,
        xrestrict = 4,
        x_Atomic  = 8,
    }

    /**********************************
     * Aggregate for all the various specifiers
     */
    struct Specifier
    {
        bool noreturn;  /// noreturn attribute
        SCW scw;        /// storage-class specifiers
        MOD mod;        /// type qualifiers
        AST.Expressions*  alignExps;  /// alignment
    }

    /***********************
     * Convert from C specifiers to D storage class
     * Params:
     *  level = declaration context
     *  specifier = specifiers, context, etc.
     * Returns:
     *  corresponding D storage class
     */
    StorageClass specifiersToSTC(LVL level, const ref Specifier specifier)
    {
        StorageClass stc;
        if (specifier.scw & SCW.x_Thread_local)
        {
            if (level == LVL.global)
            {
                if (specifier.scw & SCW.xextern)
                   stc = AST.STC.extern_;
            }
            else if (level == LVL.local)
            {
                if (specifier.scw & SCW.xextern)
                   stc = AST.STC.extern_;
                else if (specifier.scw & SCW.xstatic)
                    stc = AST.STC.static_;
            }
            else if (level == LVL.member)
            {
                if (specifier.scw & SCW.xextern)
                   stc = AST.STC.extern_;
                else if (specifier.scw & SCW.xstatic)
                    stc = AST.STC.static_;
            }
        }
        else
        {
            if (level == LVL.global)
            {
                if (specifier.scw & SCW.xextern)
                   stc = AST.STC.extern_ | AST.STC.gshared;
                else
                    stc = AST.STC.gshared;
            }
            else if (level == LVL.local)
            {
                if (specifier.scw & SCW.xextern)
                   stc = AST.STC.extern_ | AST.STC.gshared;
                else if (specifier.scw & SCW.xstatic)
                    stc = AST.STC.gshared;
            }
            else if (level == LVL.member)
            {
                if (specifier.scw & SCW.xextern)
                   stc = AST.STC.extern_ | AST.STC.gshared;
                else if (specifier.scw & SCW.xstatic)
                    stc = AST.STC.gshared;
            }
        }
        return stc;
    }

    /***********************
     * Return suitable D float type for C `long double`
     * Params:
     *  flags = kind of float to return (real, imaginary, complex).
     * Returns:
     *  corresponding D type
     */
    private AST.Type realType(RTFlags flags)
    {
        if (long_doublesize == AST.Type.tfloat80.size())
        {
            // On GDC and LDC, D `real` types map to C `long double`, so never
            // return a double type when real.sizeof == double.sizeof.
            final switch (flags)
            {
                case RTFlags.realfloat: return AST.Type.tfloat80;
                case RTFlags.imaginary: return AST.Type.timaginary80;
                case RTFlags.complex:   return AST.Type.tcomplex80;
            }
        }
        else
        {
            final switch (flags)
            {
                case RTFlags.realfloat: return long_doublesize == 8 ? AST.Type.tfloat64 : AST.Type.tfloat80;
                case RTFlags.imaginary: return long_doublesize == 8 ? AST.Type.timaginary64 : AST.Type.timaginary80;
                case RTFlags.complex:   return long_doublesize == 8 ? AST.Type.tcomplex64 : AST.Type.tcomplex80;
            }
        }
    }

    /**************
     * Flags for realType
     */
    private enum RTFlags
    {
        realfloat,
        imaginary,
        complex,
    }

    /********************
     * C11 6.4.2.2 Create declaration to predefine __func__
     *    `static const char __func__[] = " function-name ";`
     * Params:
     *    loc = location for this declaration
     *    id = identifier of function
     * Returns:
     *    statement representing the declaration of __func__
     */
    private AST.Statement createFuncName(Loc loc, Identifier id)
    {
        const fn = id.toString();  // function-name
        auto efn = new AST.StringExp(loc, fn, fn.length, 1, 'c');
        auto ifn = new AST.ExpInitializer(loc, efn);
        auto lenfn = new AST.IntegerExp(loc, fn.length + 1, AST.Type.tuns32); // +1 for terminating 0
        auto tfn = new AST.TypeSArray(AST.Type.tchar, lenfn);
        efn.type = tfn.immutableOf();
        efn.committed = 1;
        auto sfn = new AST.VarDeclaration(loc, tfn, Id.__func__, ifn, STC.gshared | STC.immutable_);
        auto e = new AST.DeclarationExp(loc, sfn);
        return new AST.ExpStatement(loc, e);
    }

    /************************
     * After encountering an error, scan forward until a right brace or ; is found
     * or the end of the file.
     */
    void panic()
    {
        while (token.value != TOK.rightCurly && token.value != TOK.semicolon && token.value != TOK.endOfFile)
            nextToken();
    }

    /**************************
     * Apply `const` to a type.
     * Params:
     *    t = type to add const to
     * Returns:
     *    resulting type
     */
    private AST.Type toConst(AST.Type t)
    {
        // `const` is always applied to the return type, not the
        // type function itself.
        if (auto tf = t.isTypeFunction())
            tf.next = tf.next.addSTC(STC.const_);
        else
            t = t.addSTC(STC.const_);
        return t;
    }

    /***************************
     * Apply specifier to a Dsymbol.
     * Params:
     *  s = Dsymbol
     *  specifier = specifiers to apply
     * Returns:
     *  Dsymbol with specifiers applied
     */
    private AST.Dsymbol applySpecifier(AST.Dsymbol s, ref Specifier specifier)
    {
        if (specifier.alignExps)
        {
            // Wrap declaration in an AlignDeclaration
            auto decls = new AST.Dsymbols(1);
            (*decls)[0] = s;
            s = new AST.AlignDeclaration(s.loc, specifier.alignExps, decls);
        }
        return s;
    }

    //}
}
