c_parse.pl
The C parser (milestone 4a): it turns the tokens from c_lex into a syntax tree.
The only semantic state is the set of typedef names in scope, which the grammar needs to tell declarations from expressions. It is the assoc Env, from each name to t, threaded through the nonterminals. Types, constants and tags are resolved later, in c_types and c_lower.
Translation unit
A translation unit is a list of these items:
| Item | Is |
|---|---|
declLoc, Specs, Inits | a declaration; Inits is a list of initDcl, Init, Init none, an expression or listEntries |
fundefLoc, Specs, Dcl, Body | a function definition |
static_assertLoc, Expr, Message | Message the string's bytes, or [] without one |
pragma_weakLoc, Name, Target | #pragma weak Name, Target none, or #pragma weak Name = Target |
Entries are entryDesignators, Init terms, each designator fieldName or indexExpr.
Declarators
A declarator is dclName, Type, Asm. Name is someIdentifier, or none in abstract declarators; Asm is asmSymbol or none. Type is built around the atom base, which stands for the type the Specs give:
| Type | Means |
|---|---|
base | the type of the specifiers |
ptrT, Quals | a pointer to T, Quals a list of qualifiers |
arrayT, Size | Size none or an expression |
funcT, Params | Params unspecified, paramsPs, Variadic, a K&R identifier list knrNames, or in a definition knrNames, Decls with the parameters' declarations |
In paramsPs, Variadic, Ps is a list of paramSpecs, Dcl and Variadic is fixed or variadic.
Specifiers
Specs is a list of:
| Specifier | Means |
|---|---|
storageS | S is typedef, extern, static, auto, register or thread_local |
tspecK | a type specifier keyword such as int or unsigned |
qualQ | a qualifier: const, volatile, restrict or atomic |
fspecF | inline or noreturn |
tnameName | a typedef name |
suKind, Tag, Members, Attrs | Kind struct or union |
enumTag, Enumerators | an enum; Enumerators is packedEs under GNU's packed attribute |
shared_base | a member declarator after the first, which shares its base type |
attrTokens | an attribute; attrextension and attrnullability mark an ignored __extension__ or clang nullability |
alignastypeTypeName alignasexprE | _Alignas |
typeoftypeTypeName typeofexprE | typeof; GNU __auto_type with initializer E is typeofexprcommanum0, none−dec, E |
atomicTypeName | _Atomic ( TypeName ) |
A Tag is someIdentifier or none. Members is none, or a list of fieldSpecs, Dcl, Width and static_assert terms, Width none or an expression. Enumerators is none, or a list of enumeratorName, Value, Value an expression or none.
Statements
A statement is atLoc, S, with S one of:
| S | Notes |
|---|---|
blockItems | Items are statements, decl and static_assert terms |
exprE | |
null | the empty statement |
ifC, Then, Else | Else none without an else |
whileC, S doS, C | |
forInit, C, Step, S | Init none, exprE or a decl term; C and Step may be none |
switchE, S caseE, S defaultS | E may be GNU's rangeLo, Hi |
break continue | |
returnE | E may be none |
gotoL labelL, S | |
computed_gotoE | GNU goto *E |
Expressions
| Expression | Means |
|---|---|
idN | an identifier |
numV, Suffix−Radix | an integer constant, as c_lex gives it |
chrV floatChars | character and floating constants |
strBytes | a string literal |
wstrElemType, Codes | a wide string literal |
binOp, A, B | a binary operator |
assignOp, A, B | Op is = for plain assignment |
unOp, A | Op neg, plus, bnot, not, deref, addr, preinc, predec, postinc or postdec |
condC, A, B | C ? A : B |
gcondC, B | GNU C's C ?: B |
commaA, B | |
callF, Args indexA, I | |
memberA, F arrowA, F | A.F and A->F |
castTypeName, E | |
sizeof_typeTypeName sizeof_exprE alignofTypeName | |
complitTypeName, Init | a compound literal |
offsetofTypeName, Path | Path a list of fieldF and indexE |
va_argA, TypeName | __builtin_va_arg |
bit_castTypeName, E | __builtin_bit_cast |
types_compatibleTypeName, TypeName | __builtin_types_compatible_p |
callid'__builtin_convertvector', E, TypeName | clang's __builtin_convertvector |
stmtexprBlock | a GNU statement expression |
label_addrL | GNU &&L, the address of label L |
genericControl, Associations | _Generic, Associations a list of assocTypeName, E and assocdefault, E |
A TypeName is typeSpecs, Type.
Expr is the constant expression, a conditional expression, that the c_lex Tokens are, as #if reads them. No name is a typedef. Throws nanofil_error/1 on a syntax error.
As c_constant_expression/2, where the names Typedefs are typedefs, as in an attribute's argument (aligned(__alignof__(md5_uint32))).
the external declarations up to the end of input. The cut commits to the first parse of each declaration.
Tokens
Lookahead: the next token, left in the input.
the punctuator P, or a syntax error.
throw a syntax error at the next token.
External declarations
one external declaration; Items is Items1 with its item in front, or Items1 itself for a stray ';'. Env is Env0 with its typedef names.
A top-level asm is toplevel_asm(Loc, Template); c_lower passes on the directives filcc does and refuses the rest (c_toplevel_asm/2).
a _Static_assert after its keyword.
GNU __auto_type among Specs0 is the type of its one declarator's initializer after lvalue conversion, array and function decay, which is the type of (0, Init): typeof of that, not evaluated. GCC refuses it with several declarators or none, or without an expression initializer.
Env is Env0 with the typedef names the declaration adds or (for ordinary names) hides.
Env is Env0 with typedef Name.
Env is Env0 for a function body, where parameter names hide typedefs.
the declarations of a K&R definition's parameters, between its declarator and its body (C11 6.9.1p6).
Inits are the init(Dcl, Init) of the declarator list whose first declarator Dcl is already read. The declarators' attributes are collected into Attrs and apply to the whole declaration.
no attribute of a declaration with several declarators that is written with one of them (before it or after it) is honored. Those apply to the whole declaration here, so int a = 1, z __attribute__((cleanup(f))) = 5; would also run f(&a), and static void a(void), b(void) __attribute__((constructor)); would run a. Hints, and attributes among the specifiers, are fine.
Declaration specifiers
nonempty declaration specifiers.
once a type specifier is seen, an identifier is the declarator, not a typedef name.
TypeSeen is yes if Spec is a type specifier, else TypeSeen0.
| 358 | storage_ | TypeSeen | TypeSeen |
| 359 | tspec_ | _ | yes |
| 360 | qual_ | TypeSeen | TypeSeen |
| 361 | fspec_ | TypeSeen | TypeSeen |
| 362 | attr_ | TypeSeen | TypeSeen |
| 363 | alignas_ | TypeSeen | TypeSeen |
| 364 | su_, _, _, _ | _ | yes |
| 365 | enum_, _ | _ | yes |
| 366 | typeof_ | _ | yes |
| 367 | auto_type | _ | yes |
| 368 | atomic_ | _ | yes |
| 369 | tname_ | _ | yes |
__attribute__((...)), Tokens its balanced//1 contents.
each attribute of __attribute__((...)) is one c_types known_attribute/3 lists, whatever its position, so none is dropped unread; any other is refused.
Tokens split at their commas.
an attribute's name, GCC's __X__ as X.
__declspec(Tokens) is the GNU attribute Attr, as clang maps it: align(N) is aligned(N), and the ones that do not change code or layout keep their names. Others (thread, selectany, property, ...) are refused.
[[Name]] is a standard attribute or one c_types lists, as is [[gnu::Name]] or [[clang::Name]]; others in those namespaces are refused. An attribute of another namespace (vendor::x, and msvc::x on this target) is ignored, as clang ignores it with a warning, so filcc's program cannot depend on it.
nanofil keeps no C23 attribute, so an honored one ([[gnu::constructor]], [[gnu::cleanup(f)]], [[gnu::mode(DI)]], [[gnu::vector_size(16)]]) is refused rather than dropped. struct_spec//3 reads [[gnu::packed]] on a struct itself.
Like their GNU spellings, layout-changing attributes cannot be ignored. Reject here also at positions (e.g. pointers) whose attributes we discard.
attrs//: GNU and C23 attributes at a position whose attributes nanofil does not keep: an enum specifier, a pointer's qualifiers, a nested or abstract declarator, a parameter's end, an enumerator, a statement or a label. Only hints may stand there (c23_known/3 refuses honored C23 attributes everywhere): enum __attribute__((packed)), int * __attribute__((aligned(16))) p or char * __attribute__((cleanup(f))) p would otherwise lose their effect.
each attribute of __attribute__((...)) Tokens only hints (c_types known_attribute/3); an honored one, whose effect this position would drop, is refused.
the attr(Tokens) specifier Attr has an attribute Word that nanofil honors.
the member declarations of a struct or union up to its '}'.
Fields-Rest is the difference list of fields one member declaration declares. Each after the first has its specifiers marked shared_base: c_types gives it the first one's base type, so struct { ... } a, b; defines one struct (a = b assigns), not one per declarator.
an enum definition's attributes, before its tag or after its body. P is yes if one is GNU's packed (c_types enum_type/5); any other must be a hint (attrs//0).
the attribute list Tokens names packed; Rest is the list without it.
the enumerators up to the '}'.
Declarators
a declarator with its GNU suffix. Attributes before a declarator (GNU allows them after the comma of a declarator list, as in char *a, __attribute__((unused)) *b) and after it come back as attr(Tokens) specifiers in Attrs.
a declarator of Name; Type is built around Base.
like declarator//4 without a name. A '(' starts a nested declarator only before a '*' or '[' (perhaps after attributes), which cannot begin a parameter list; otherwise it is a parameter list.
array and function suffixes, applied inside out: Type is Base with them.
the size between '[' and ']', or none; the ']' stays in the input.
a parameter list after its '(', through ')'.
a type name, type(Specs, Type).
lookahead: the next token begins a type name. __extension__ can prefix either a type or an expression. Look past it without consuming it, rather than misreading glibc's ({...}) as a type.
Initializers
the entries of a braced initializer after its '{', through '}'.
field: valueStatements
the items of a block through its '}'.
a declaration or statement in a block; Env is Env0 with the declaration's typedef names.
lookahead only: the next tokens begin a declaration, not a label.
leading_attrs//: the GNU and C23 attributes before a block item, which begin a statement, not a declaration, when no type or storage class follows: __attribute__((musttail)) return f(x); (filc/tests musttail).
label_start//: lookahead: the next tokens are an identifier and ':'.
a declaration in a block or for.
a statement at(Loc, S). The cut commits to the first parse; if there is none, it is a syntax error. GNU attributes may begin a statement too, as in a label's __attribute__((fallthrough)); (an attributed null statement).
GNU asm. One whose template is blank parses as asm_empty(Outs, Ins, Clobbers, Goto), which emits no instruction (c_lower checks its operands): dash's INTOFF barrier, libgcrypt's and xxhash's operand guards. Any other asm would run code nanofil cannot check: it parses as unsupported_asm(Kind), which c_lower refuses, so only a function that is lowered is refused, not an unused static inline one from a header (immintrin.h's hresetintrin.h), which clang never emits either. Kind is memory(Template) if an operand has a memory constraint, which clang's Fil-C compiles to a run-time error (gnulib's fpucw.h), else other.
A label's GNU or C23 attributes (gnulib's _GL_UNUSED_LABEL, fail: __attribute__((__unused__));) are ignored.
memory(Template) if some operand of the asm with tokens Toks has a memory constraint ("m", "=m", "+rm"...), else other. Template is its instruction text.
volatile, inline and goto, in any order; Goto is goto if goto is among them, else none.
the template has only spaces, tabs, newlines and carriage returns, what filcc's handleInlineAsm counts as empty asm.
the outputs, inputs and clobbers after the template, each section optional from the right; an asm goto's labels after them are kept as labels(Names) in Clobbers.
Expressions
an expression, commas included.
Spelling is the compound assignment of the arithmetic or shift operator Op: its spelling followed by = (C11 6.5.16.2).
binary operators of precedence Level and up; level 11 is a cast expression.
C's binary operators (C11 6.5.5-6.5.14): Spelling is Op, of precedence Level (1 binds loosest). Kind says what it does to its operands: arith(Domain) converts integer or arithmetic operands to their common type, shift promotes each, compare converts and compares, and logical tests each for truth.
| 1285 | '||' | 1 | lor | logical |
| 1286 | && | 2 | land | logical |
| 1287 | ('|') | 3 | or | arithinteger |
| 1288 | ^ | 4 | xor | arithinteger |
| 1289 | & | 5 | and | arithinteger |
| 1290 | == | 6 | eq | compare |
| 1291 | '!=' | 6 | ne | compare |
| 1292 | < | 7 | lt | compare |
| 1293 | > | 7 | gt | compare |
| 1294 | <= | 7 | le | compare |
| 1295 | >= | 7 | ge | compare |
| 1296 | << | 8 | shl | shift |
| 1297 | >> | 8 | shr | shift |
| 1298 | + | 9 | add | aritharithmetic |
| 1299 | - | 9 | sub | aritharithmetic |
| 1300 | * | 10 | mul | aritharithmetic |
| 1301 | / | 10 | div | aritharithmetic |
| 1302 | '%' | 10 | mod | arithinteger |
E is A with the postfix operators that follow.
a primary expression.
C23's true and false are the bool constants 1 and 0, and nullptr a null pointer constant (C23 6.4.4.5, 6.4.4.6). nanofil gives nullptr the type void *, not nullptr_t: the two convert alike, and only _Generic, which would select void * for it, tells them apart.
one or more adjacent string literals, each str(Bytes) or wstr(Type, Codes). They concatenate; if one is wide, all are, and a plain one's bytes become elements (exact for ASCII).
adjacent plain string literals as one list of bytes.
S is the string literal Parts concatenate.
S is the literal of Elements: plain if no part is wide, else of the one wide type.