blob: bb12aa7c995da8df852711e15fcf67ca03c0676e [file] [log] [blame]
/**
* Takes a token stream from the lexer, and parses it into an abstract syntax tree.
*
* Specification: C11
*
* Copyright: Copyright (C) 1999-2022 by The D Language Foundation, All Rights Reserved
* Authors: $(LINK2 https://www.digitalmars.com, Walter Bright)
* License: $(LINK2 https://www.boost.org/LICENSE_1_0.txt, Boost License 1.0)
* Source: $(LINK2 https://github.com/dlang/dmd/blob/master/src/dmd/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.array;
import dmd.root.filename;
import dmd.common.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
bool importBuiltins; /// seen use of C compiler builtins, so import __builtins;
private
{
structalign_t packalign; // current state of #pragma pack alignment
// #pragma pack stack
Array!Identifier* records; // identifers (or null)
Array!structalign_t* packs; // parallel alignment values
}
/* C cannot be parsed without determining if an identifier is a type or a variable.
* For expressions like `(T)-3`, is it a cast or a minus expression?
* It also occurs with `typedef int (F)(); F fun;`
* but to build the AST we need to distinguish `fun` being a function as opposed to a variable.
* To fix, build a symbol table for the typedefs.
* Symbol table of typedefs indexed by Identifier cast to void*.
* 1. if an identifier is a typedef, then it will return a non-null Type
* 2. if an identifier is not a typedef, then it will return null
*/
Array!(void*) typedefTab; /// Array of AST.Type[Identifier], typedef's indexed by Identifier
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;
this.packalign.setDefault();
// Configure sizes for C `long`, `long double`, `wchar_t`, ...
this.boolsize = target.boolsize;
this.shortsize = target.shortsize;
this.intsize = target.intsize;
this.longsize = target.longsize;
this.long_longsize = target.long_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();
typedefTab.push(null); // C11 6.2.1-3 symbol table for "file scope"
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);
if (importBuiltins)
{
/* Seen references to C builtin functions.
* Import their definitions
*/
auto s = new AST.Import(Loc.initial, null, Id.builtins, null, false);
wrap.push(s);
}
// end of file scope
typedefTab.pop();
assert(typedefTab.length == 0);
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");
const typedefTabLengthSave = typedefTab.length;
auto symbolsSave = symbols;
if (flags & ParseStatementFlags.scope_)
{
typedefTab.push(null); // introduce new block scope
}
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.charLiteral:
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 == EXP.identifier)
{
error("found `%s` when expecting `;` or `=`, did you mean `%s %s = %s`?", peek(&token).toChars(), exp.toChars(), token.toChars(), peek(peek(&token)).toChars());
nextToken();
}
else
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:
case TOK.__stdcall:
// 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;
typedefTab.setDim(typedefTabLengthSave);
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
* __builtin_va_arg(assign_expression, type)
*/
AST.Expression cparsePrimaryExp()
{
AST.Expression e;
const loc = token.loc;
//printf("parsePrimaryExp(): loc = %d\n", loc.linnum);
switch (token.value)
{
case TOK.identifier:
const id = token.ident.toString();
if (id.length > 2 && id[0] == '_' && id[1] == '_') // leading double underscore
{
if (token.ident is Id.__func__)
{
addFuncName = true; // implicitly declare __func__
}
else if (token.ident is Id.builtin_va_arg)
{
e = cparseBuiltin_va_arg();
break;
}
else
importBuiltins = true; // probably one of those compiler extensions
}
e = new AST.IdentifierExp(loc, token.ident);
nextToken();
break;
case TOK.charLiteral:
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(EXP.plusPlus, loc, e);
break;
case TOK.minusMinus:
e = new AST.PostExp(EXP.minusMinus, loc, e);
break;
case TOK.leftParenthesis:
e = new AST.CallExp(loc, e, 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(EXP.prePlusPlus, loc, e);
break;
case TOK.minusMinus:
nextToken();
// Parse `--` as an unary operator, same as prefix increment.
e = cparseCastExp();
e = new AST.PreExp(EXP.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;
}
// must be an expression
e = cparsePrimaryExp();
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)
{
//printf("cparseCastExp()\n");
auto tk = peek(&token);
bool iscast;
bool isexp;
if (tk.value == TOK.identifier)
{
iscast = isTypedef(tk.ident);
isexp = !iscast;
}
if (isexp)
{
// ( identifier ) is an expression
return cparseUnaryExp();
}
// 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);
pt = &token;
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);
}
if (iscast)
{
// ( type-name ) cast-expression
auto ce = cparseCastExp();
return new AST.CastExp(loc, ce, t);
}
if (t.isTypeIdentifier() &&
isexp &&
token.value == TOK.leftParenthesis &&
!isCastExpression(pt))
{
/* (t)(...)... might be a cast expression or a function call,
* with different grammars: a cast would be cparseCastExp(),
* a function call would be cparsePostfixExp(CallExp(cparseArguments())).
* We can't know until t is known. So, parse it as a function call
* and let semantic() rewrite the AST as a CastExp if it turns out
* to be a type.
*/
auto ie = new AST.IdentifierExp(loc, t.isTypeIdentifier().ident);
ie.parens = true; // let semantic know it might be a CastExp
AST.Expression e = new AST.CallExp(loc, ie, cparseArguments());
return cparsePostfixOperators(e);
}
// ( 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();
EXP op = EXP.reserved;
switch (token.value)
{
case TOK.lessThan: op = EXP.lessThan; goto Lcmp;
case TOK.lessOrEqual: op = EXP.lessOrEqual; goto Lcmp;
case TOK.greaterThan: op = EXP.greaterThan; goto Lcmp;
case TOK.greaterOrEqual: op = EXP.greaterOrEqual; goto Lcmp;
Lcmp:
nextToken();
auto e2 = 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();
EXP op = EXP.reserved;
switch (token.value)
{
case TOK.equal: op = EXP.equal; goto Lequal;
case TOK.notEqual: op = EXP.notEqual; goto Lequal;
Lequal:
nextToken();
auto e2 = 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, EXP.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, EXP.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();
}
/*****************************
* gcc extension:
* type __builtin_va_arg(assign-expression, type)
* Rewrite as `va_arg` template from `core.stdc.stdarg`:
* va_arg!(type)(assign-expression);
* Lexer is on `__builtin_va_arg`
*/
private AST.Expression cparseBuiltin_va_arg()
{
importBuiltins = true; // need core.stdc.stdarg
nextToken();
check(TOK.leftParenthesis);
auto arguments = new AST.Expressions();
auto arg = cparseAssignExp();
arguments.push(arg);
check(TOK.comma);
auto t = cparseTypeName();
auto tiargs = new AST.Objects();
tiargs.push(t);
const loc = loc;
auto ti = new AST.TemplateInstance(loc, Id.va_arg, tiargs);
auto tie = new AST.ScopeExp(loc, ti);
AST.Expression e = new AST.CallExp(loc, tie, arguments);
check(TOK.rightParenthesis);
return e;
}
//}
/********************************************************************************/
/********************************* 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;
}
if (token.value == TOK._import) // import declaration extension
{
auto a = parseImport();
if (a && a.length)
symbols.append(a);
return;
}
const typedefTabLengthSave = typedefTab.length;
auto symbolsSave = symbols;
Specifier specifier;
specifier.packalign = this.packalign;
auto tspec = cparseDeclarationSpecifiers(level, specifier);
/* If a declarator does not follow, it is unnamed
*/
if (token.value == TOK.semicolon)
{
nextToken();
if (!tspec)
return; // accept empty declaration as an extension
auto tt = tspec.isTypeTag();
if (!tt ||
!tt.id && (tt.tok == TOK.struct_ || tt.tok == TOK.union_))
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, tt.base);
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, specifier);
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__:
if (token.value == TOK.asm_)
asmname = cparseSimpleAsmExpr();
if (token.value == TOK.__attribute__)
{
cparseGnuAttributes(specifier);
if (token.value == TOK.leftCurly)
break; // function definition
}
/* This is a data definition, there cannot now be a
* function definition.
*/
first = false;
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);
typedefTab.setDim(typedefTabLengthSave);
symbols = symbolsSave;
symbols.push(s);
return;
}
AST.Dsymbol s = null;
typedefTab.setDim(typedefTabLengthSave);
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;
}
}
else if (auto tt = dt.isTypeTag())
{
if (tt.id || tt.tok == TOK.enum_)
{
/* `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, tt.base);
stag.members = tt.members;
tt.members = null;
if (!symbols)
symbols = new AST.Dsymbols();
symbols.push(stag);
if (tt.tok == TOK.enum_)
{
isalias = false;
s = new AST.AliasDeclaration(token.loc, id, stag);
}
}
}
if (isalias)
s = new AST.AliasDeclaration(token.loc, id, dt);
insertTypedefToTypedefTab(id, dt); // remember typedefs
}
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 (isFunctionTypedef(dt))
{
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 | SCW.xstatic | SCW.x_Thread_local) || level == LVL.global))
initializer = new AST.VoidInitializer(token.loc);
s = new AST.VarDeclaration(token.loc, dt, id, initializer, specifiersToSTC(level, specifier));
}
if (level != LVL.global)
insertIdToTypedefTab(id); // non-typedef declarations can hide typedefs in outer scopes
}
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:
if (s)
{
error("missing comma or semicolon after declaration of `%s`, found `%s` instead", s.toChars(), token.toChars());
goto Lend;
}
goto default;
case TOK.semicolon:
nextToken();
return;
case TOK.comma:
if (!symbolsSave)
symbolsSave = symbols;
nextToken();
break;
default:
error("`=`, `;` or `,` expected to end declaration instead of `%s`", token.toChars());
Lend:
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)
{
/* Start function scope
*/
typedefTab.push(null);
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.
*/
ft.parameterList.hasIdentifierList = true; // semantic needs to know to adjust parameter types
auto pl = ft.parameterList;
if (pl.varargs != AST.VarArg.none && pl.length)
error("function identifier-list cannot end with `...`");
ft.parameterList.varargs = AST.VarArg.variadic; // but C11 allows extra arguments
importBuiltins = true; // will need __va_list_tag
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
typedefTab.pop(); // end of function 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;
case TOK.__stdcall: modx = MOD.x__stdcall; 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 = integerTypeForSize(shortsize); break;
case TKW.xunsigned | TKW.xshort | TKW.xint:
case TKW.xunsigned | TKW.xshort: t = unsignedTypeForSize(shortsize); break;
case TKW.xint:
case TKW.xsigned:
case TKW.xsigned | TKW.xint: t = integerTypeForSize(intsize); break;
case TKW.xunsigned:
case TKW.xunsigned | TKW.xint: t = unsignedTypeForSize(intsize); break;
case TKW.xlong:
case TKW.xsigned | TKW.xlong:
case TKW.xsigned | TKW.xlong | TKW.xint:
case TKW.xlong | TKW.xint: t = integerTypeForSize(longsize); break;
case TKW.xunsigned | TKW.xlong | TKW.xint:
case TKW.xunsigned | TKW.xlong: t = unsignedTypeForSize(longsize); break;
case TKW.xllong:
case TKW.xsigned | TKW.xllong:
case TKW.xsigned | TKW.xllong | TKW.xint:
case TKW.xllong | TKW.xint: t = integerTypeForSize(long_longsize); break;
case TKW.xunsigned | TKW.xllong | TKW.xint:
case TKW.xunsigned | TKW.xllong: t = unsignedTypeForSize(long_longsize); break;
case TKW.xvoid: t = AST.Type.tvoid; break;
case TKW.xbool: t = boolsize == 1 ? AST.Type.tbool : integerTypeForSize(boolsize); 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:
{
const idx = previd.toString();
if (idx.length > 2 && idx[0] == '_' && idx[1] == '_') // leading double underscore
importBuiltins = true; // probably one of those compiler extensions
t = null;
if (scw & SCW.xtypedef)
{
/* Punch through to what the typedef is, to support things like:
* typedef T* T;
*/
auto pt = lookupTypedef(previd);
if (pt && *pt) // if previd is a known typedef
t = *pt;
}
if (!t)
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
* specifier = specifiers in and out
* 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, ref Specifier specifier)
{
//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();
if (token.value == TOK.__stdcall) // T (__stdcall*fp)();
{
specifier.mod |= MOD.x__stdcall;
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);
if (token.value == TOK.__attribute__)
cparseGnuAttributes(specifier);
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
{
/* C11 6.7.6.2-4 An [ ] array is an incomplete array type
*/
ta = new AST.TypeSArray(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.isTypeSArray() && ta.isTypeSArray().isIncomplete() && !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();
const lkg = specifier.mod & MOD.x__stdcall ? LINK.windows : linkage;
AST.Type tf = new AST.TypeFunction(parameterList, t, lkg, 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;
}
if (declarator == DTR.xparameter &&
t.isTypePointer())
{
/* Because there are instances in .h files of "const pointer to mutable",
* skip applying transitive `const`
* https://issues.dlang.org/show_bug.cgi?id=22534
*/
auto tn = cast(AST.TypeNext)t;
tn.next = constApply(tn.next);
}
else
t = constApply(t);
}
//printf("result: %s\n", t.toChars());
return t;
}
/******************************
* C11 6.7.3
* type-qualifier:
* const
* restrict
* volatile
* _Atomic
* __stdcall
*/
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;
case TOK.__stdcall: mod |= MOD.x__stdcall; break;
default:
return mod;
}
nextToken();
}
}
/***********************************
* C11 6.7.7
*/
AST.Type cparseTypeName()
{
Specifier specifier;
specifier.packalign.setDefault();
auto tspec = cparseSpecifierQualifierList(LVL.global, specifier);
Identifier id;
return cparseDeclarator(DTR.xabstract, tspec, id, specifier);
}
/***********************************
* 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) // func(void)
{
nextToken();
nextToken();
return AST.ParameterList(parameters, varargs, varargsStc);
}
if (token.value == TOK.rightParenthesis) // func()
{
nextToken();
importBuiltins = true; // will need __va_list_tag
return AST.ParameterList(parameters, AST.VarArg.variadic, varargsStc);
}
/* Create function prototype scope
*/
typedefTab.push(null);
AST.ParameterList finish()
{
typedefTab.pop();
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)
{
if (parameters.length == 0) // func(...)
error("named parameter required before `...`");
importBuiltins = true; // will need __va_list_tag
varargs = AST.VarArg.variadic; // C-style variadics
nextToken();
check(TOK.rightParenthesis);
return finish();
}
Specifier specifier;
specifier.packalign.setDefault();
auto tspec = cparseDeclarationSpecifiers(LVL.prototype, specifier);
if (tspec && specifier.mod & MOD.xconst)
{
tspec = toConst(tspec);
specifier.mod = MOD.xnone; // 'used' it
}
Identifier id;
auto t = cparseDeclarator(DTR.xparameter, tspec, id, specifier);
if (token.value == TOK.__attribute__)
cparseGnuAttributes(specifier);
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 finish();
}
/***********************************
* 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;
specifier.packalign.setDefault();
if (token.value == TOK.__attribute__)
cparseGnuAttributes(specifier);
Identifier tag;
if (token.value == TOK.identifier)
{
tag = token.ident;
nextToken();
}
/* clang extension: add optional base type after the identifier
* https://en.cppreference.com/w/cpp/language/enum
* enum Identifier : Type
*/
AST.Type base = AST.Type.tint32; // C11 6.7.2.2-4 implementation defined default base type
if (token.value == TOK.colon)
{
nextToken();
base = cparseTypeName();
}
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;
if (token.value == TOK.__attribute__)
{
/* gnu-attributes can appear here, but just scan and ignore them
* https://gcc.gnu.org/onlinedocs/gcc/Enumerator-Attributes.html
*/
Specifier specifierx;
specifierx.packalign.setDefault();
cparseGnuAttributes(specifierx);
}
AST.Expression value;
if (token.value == TOK.assign)
{
nextToken();
value = cparseConstantExp();
// TODO C11 6.7.2.2-2 value must fit into an int
}
if (token.value == TOK.__attribute__)
{
/* gnu-attributes can appear here, but just scan and ignore them
* https://gcc.gnu.org/onlinedocs/gcc/Enumerator-Attributes.html
*/
Specifier specifierx;
specifierx.packalign.setDefault();
cparseGnuAttributes(specifierx);
}
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, base, 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();
members = new AST.Dsymbols(); // so `members` will be non-null even with 0 members
while (token.value != TOK.rightCurly)
{
cparseStructDeclaration(members);
if (token.value == TOK.endOfFile)
break;
}
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, null, 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
* Params:
* members = where to put the fields (members)
*/
void cparseStructDeclaration(AST.Dsymbols* members)
{
//printf("cparseStructDeclaration()\n");
if (token.value == TOK._Static_assert)
{
auto s = cparseStaticAssert();
members.push(s);
return;
}
Specifier specifier;
specifier.packalign = this.packalign;
auto tspec = cparseSpecifierQualifierList(LVL.member, specifier);
if (tspec && specifier.mod & MOD.xconst)
{
tspec = toConst(tspec);
specifier.mod = MOD.xnone; // 'used' it
}
/* 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);
auto s = applySpecifier(ad, specifier);
members.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, specifier);
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();
}
/* GNU Extensions
* struct-declarator:
* declarator gnu-attributes (opt)
* declarator (opt) : constant-expression gnu-attributes (opt)
*/
if (token.value == TOK.__attribute__)
cparseGnuAttributes(specifier);
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
auto s = new AST.BitFieldDeclaration(width.loc, dt, id, width);
members.push(s);
}
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);
AST.Dsymbol s = new AST.VarDeclaration(token.loc, dt, id, initializer, specifiersToSTC(LVL.member, specifier));
s = applySpecifier(s, specifier);
members.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)
{
auto t = pt;
//printf("isCDeclaration() %s\n", t.toChars());
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)
{
auto t = pt;
//printf("isAssignmentExpression() %s\n", t.toChars());
/* 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;
/*
https://issues.dlang.org/show_bug.cgi?id=22267
Fix issue 22267: If the parser encounters the following
`identifier variableName = (expression);`
the initializer is not identified as such since the parentheses
cause the parser to keep walking indefinitely
(whereas `(1) + 1` would not be affected.).
*/
any = true;
continue;
case TOK.leftBracket:
if (!skipBrackets(t))
return false;
continue;
case TOK.leftCurly:
if (!skipBraces(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 seenType;
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:
t = peek(t);
seenType = true;
any = true;
continue;
case TOK.identifier: // typedef-name
if (!seenType)
{
t = peek(t);
seenType = true;
any = true;
continue;
}
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:
case TOK.__stdcall:
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:
case TOK.__stdcall:
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:
case TOK.__stdcall:
// 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)
{
enum log = false;
if (log) printf("isCastExpression(tk: `%s`, afterParenType: %d)\n", token.toChars(pt.value), afterParenType);
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 (tk.value == TOK.leftParenthesis && peek(tk).value == TOK.rightParenthesis)
{
return false; // (type-name)() is not a cast (it might be a function call)
}
if (!isCastExpression(tk, true))
{
if (afterParenType) // could be ( type-name ) ( unary-expression )
goto default; // where unary-expression also matched type-name
return true;
}
// ( 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;
if (log) printf("isCastExpression true\n");
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.charLiteral:
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,
x__stdcall = 0x10, // Windows linkage extension
}
/**********************************
* 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
structalign_t packalign; /// #pragma pack alignment value
}
/***********************
* 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 if (specifier.scw & SCW.xstatic)
stc = AST.STC.gshared | AST.STC.static_;
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 signed integer type for the given size
* Params:
* size = size of type
* Returns:
* corresponding signed D integer type
*/
private AST.Type integerTypeForSize(ubyte size)
{
if (size <= 1)
return AST.Type.tint8;
if (size <= 2)
return AST.Type.tint16;
if (size <= 4)
return AST.Type.tint32;
if (size <= 8)
return AST.Type.tint64;
error("unsupported integer type");
return AST.Type.terror;
}
/***********************
* Return suitable unsigned integer type for the given size
* Params:
* size = size of type
* Returns:
* corresponding unsigned D integer type
*/
private AST.Type unsignedTypeForSize(ubyte size)
{
if (size <= 1)
return AST.Type.tuns8;
if (size <= 2)
return AST.Type.tuns16;
if (size <= 4)
return AST.Type.tuns32;
if (size <= 8)
return AST.Type.tuns64;
error("unsupported integer type");
return AST.Type.terror;
}
/***********************
* 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)
{
//printf("applySpecifier() %s\n", s.toChars());
if (specifier.alignExps)
{
//printf(" applying _Alignas %s, packalign %d\n", (*specifier.alignExps)[0].toChars(), cast(int)specifier.packalign);
// Wrap declaration in an AlignDeclaration
auto decls = new AST.Dsymbols(1);
(*decls)[0] = s;
s = new AST.AlignDeclaration(s.loc, specifier.alignExps, decls);
}
else if (!specifier.packalign.isDefault())
{
//printf(" applying packalign %d\n", cast(int)specifier.packalign);
// Wrap #pragma pack in an AlignDeclaration
auto decls = new AST.Dsymbols(1);
(*decls)[0] = s;
s = new AST.AlignDeclaration(s.loc, specifier.packalign, decls);
}
return s;
}
//}
/******************************************************************************/
/************************** typedefTab symbol table ***************************/
//{
/********************************
* Determines if type t is a function type.
* Params:
* t = type to test
* Returns:
* true if it represents a function
*/
bool isFunctionTypedef(AST.Type t)
{
//printf("isFunctionTypedef() %s\n", t.toChars());
if (t.isTypeFunction())
return true;
if (auto tid = t.isTypeIdentifier())
{
auto pt = lookupTypedef(tid.ident);
if (pt && *pt)
{
return (*pt).isTypeFunction() !is null;
}
}
return false;
}
/********************************
* Determine if `id` is a symbol for a Typedef.
* Params:
* id = possible typedef
* Returns:
* true if id is a Type
*/
bool isTypedef(Identifier id)
{
auto pt = lookupTypedef(id);
return (pt && *pt);
}
/*******************************
* Add `id` to typedefTab[], but only if it will mask an existing typedef.
* Params: id = identifier for non-typedef symbol
*/
void insertIdToTypedefTab(Identifier id)
{
//printf("insertIdToTypedefTab(id: %s) level %d\n", id.toChars(), cast(int)typedefTab.length - 1);
if (isTypedef(id)) // if existing typedef
{
/* Add id as null, so we can later distinguish it from a non-null typedef
*/
auto tab = cast(void*[void*])(typedefTab[$ - 1]);
tab[cast(void*)id] = cast(void*)null;
}
}
/*******************************
* Add `id` to typedefTab[]
* Params:
* id = identifier for typedef symbol
* t = type of the typedef symbol
*/
void insertTypedefToTypedefTab(Identifier id, AST.Type t)
{
//printf("insertTypedefToTypedefTab(id: %s, t: %s) level %d\n", id.toChars(), t ? t.toChars() : "null".ptr, cast(int)typedefTab.length - 1);
if (auto tid = t.isTypeIdentifier())
{
// Try to resolve the TypeIdentifier to its type
auto pt = lookupTypedef(tid.ident);
if (pt && *pt)
t = *pt;
}
auto tab = cast(void*[void*])(typedefTab[$ - 1]);
tab[cast(void*)id] = cast(void*)t;
typedefTab[$ - 1] = cast(void*)tab;
}
/*********************************
* Lookup id in typedefTab[].
* Returns:
* if not found, then null.
* if found, then Type*. Deferencing it will yield null if it is not
* a typedef, and a type if it is a typedef.
*/
AST.Type* lookupTypedef(Identifier id)
{
foreach_reverse (tab; typedefTab[])
{
if (auto pt = cast(void*)id in cast(void*[void*])tab)
{
return cast(AST.Type*)pt;
}
}
return null; // not found
}
//}
/******************************************************************************/
/********************************* Directive Parser ***************************/
//{
override bool parseSpecialTokenSequence()
{
Token n;
scan(&n);
if (n.value == TOK.int32Literal)
{
poundLine(n, true);
return true;
}
if (n.value == TOK.identifier)
{
if (n.ident == Id.line)
{
poundLine(n, false);
return true;
}
else if (n.ident == Id.__pragma)
{
pragmaDirective(scanloc);
return true;
}
}
error("C preprocessor directive `#%s` is not supported", n.toChars());
return false;
}
/*********************************************
* C11 6.10.6 Pragma directive
* # pragma pp-tokens(opt) new-line
* The C preprocessor sometimes leaves pragma directives in
* the preprocessed output. Ignore them.
* Upon return, p is at start of next line.
*/
private void pragmaDirective(const ref Loc loc)
{
Token n;
scan(&n);
if (n.value == TOK.identifier && n.ident == Id.pack)
return pragmaPack(loc);
if (n.value != TOK.endOfLine)
skipToNextLine();
}
/*********
* # pragma pack
* https://gcc.gnu.org/onlinedocs/gcc-4.4.4/gcc/Structure_002dPacking-Pragmas.html
* https://docs.microsoft.com/en-us/cpp/preprocessor/pack
* Scanner is on the `pack`
* Params:
* startloc = location to use for error messages
*/
private void pragmaPack(const ref Loc startloc)
{
const loc = startloc;
Token n;
scan(&n);
if (n.value != TOK.leftParenthesis)
{
error(loc, "left parenthesis expected to follow `#pragma pack`");
if (n.value != TOK.endOfLine)
skipToNextLine();
return;
}
void closingParen()
{
if (n.value != TOK.rightParenthesis)
{
error(loc, "right parenthesis expected to close `#pragma pack(`");
}
if (n.value != TOK.endOfLine)
skipToNextLine();
}
void setPackAlign(ref const Token t)
{
const n = t.unsvalue;
if (n < 1 || n & (n - 1) || ushort.max < n)
error(loc, "pack must be an integer positive power of 2, not 0x%llx", cast(ulong)n);
packalign.set(cast(uint)n);
packalign.setPack(true);
}
scan(&n);
if (!records)
{
records = new Array!Identifier;
packs = new Array!structalign_t;
}
/* # pragma pack ( show )
*/
if (n.value == TOK.identifier && n.ident == Id.show)
{
if (packalign.isDefault())
warning(startloc, "current pack attribute is default");
else
warning(startloc, "current pack attribute is %d", packalign.get());
scan(&n);
return closingParen();
}
/* # pragma pack ( push )
* # pragma pack ( push , identifier )
* # pragma pack ( push , integer )
* # pragma pack ( push , identifier , integer )
*/
if (n.value == TOK.identifier && n.ident == Id.push)
{
scan(&n);
Identifier record = null;
if (n.value == TOK.comma)
{
scan(&n);
if (n.value == TOK.identifier)
{
record = n.ident;
scan(&n);
if (n.value == TOK.comma)
{
scan(&n);
if (n.value == TOK.int32Literal)
{
setPackAlign(n);
scan(&n);
}
else
error(loc, "alignment value expected, not `%s`", n.toChars());
}
}
else if (n.value == TOK.int32Literal)
{
setPackAlign(n);
scan(&n);
}
else
error(loc, "alignment value expected, not `%s`", n.toChars());
}
this.records.push(record);
this.packs.push(packalign);
return closingParen();
}
/* # pragma pack ( pop )
* # pragma pack ( pop PopList )
* PopList :
* , IdentifierOrInteger
* , IdentifierOrInteger PopList
* IdentifierOrInteger:
* identifier
* integer
*/
if (n.value == TOK.identifier && n.ident == Id.pop)
{
scan(&n);
while (n.value == TOK.comma)
{
scan(&n);
if (n.value == TOK.identifier)
{
for (size_t len = this.records.length; len; --len)
{
if ((*this.records)[len - 1] == n.ident)
{
packalign = (*this.packs)[len - 1];
this.records.setDim(len - 1);
this.packs.setDim(len - 1);
break;
}
}
scan(&n);
}
else if (n.value == TOK.int32Literal)
{
setPackAlign(n);
this.records.push(null);
this.packs.push(packalign);
scan(&n);
}
}
return closingParen();
}
/* # pragma pack ( integer )
*/
if (n.value == TOK.int32Literal)
{
setPackAlign(n);
scan(&n);
return closingParen();
}
/* # pragma pack ( )
*/
if (n.value == TOK.rightParenthesis)
{
packalign.setDefault();
return closingParen();
}
error(loc, "unrecognized `#pragma pack(%s)`", n.toChars());
if (n.value != TOK.endOfLine)
skipToNextLine();
}
//}
}