Fil-C on Nix

Filnix wiki

mbrock/filnix/nanofil

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:

ItemIs
declLoc, Specs, Initsa declaration; Inits is a list of initDcl, Init, Init none, an expression or listEntries
fundefLoc, Specs, Dcl, Bodya function definition
static_assertLoc, Expr, MessageMessage 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:

TypeMeans
basethe type of the specifiers
ptrT, Qualsa pointer to T, Quals a list of qualifiers
arrayT, SizeSize none or an expression
funcT, ParamsParams 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:

SpecifierMeans
storageSS is typedef, extern, static, auto, register or thread_local
tspecKa type specifier keyword such as int or unsigned
qualQa qualifier: const, volatile, restrict or atomic
fspecFinline or noreturn
tnameNamea typedef name
suKind, Tag, Members, AttrsKind struct or union
enumTag, Enumeratorsan enum; Enumerators is packedEs under GNU's packed attribute
shared_basea member declarator after the first, which shares its base type
attrTokensan attribute; attrextension and attrnullability mark an ignored __extension__ or clang nullability
alignastypeTypeName alignasexprE_Alignas
typeoftypeTypeName typeofexprEtypeof; 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:

SNotes
blockItemsItems are statements, decl and static_assert terms
exprE
nullthe empty statement
ifC, Then, ElseElse none without an else
whileC, S doS, C
forInit, C, Step, SInit none, exprE or a decl term; C and Step may be none
switchE, S caseE, S defaultSE may be GNU's rangeLo, Hi
break continue
returnEE may be none
gotoL labelL, S
computed_gotoEGNU goto *E

Expressions

ExpressionMeans
idNan identifier
numV, Suffix−Radixan integer constant, as c_lex gives it
chrV floatCharscharacter and floating constants
strBytesa string literal
wstrElemType, Codesa wide string literal
binOp, A, Ba binary operator
assignOp, A, BOp is = for plain assignment
unOp, AOp neg, plus, bnot, not, deref, addr, preinc, predec, postinc or postdec
condC, A, BC ? A : B
gcondC, BGNU C's C ?: B
commaA, B
callF, Args indexA, I
memberA, F arrowA, FA.F and A->F
castTypeName, E
sizeof_typeTypeName sizeof_exprE alignofTypeName
complitTypeName, Inita compound literal
offsetofTypeName, PathPath 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, TypeNameclang's __builtin_convertvector
stmtexprBlocka GNU statement expression
label_addrLGNU &&L, the address of label L
genericControl, Associations_Generic, Associations a list of assocTypeName, E and assocdefault, E

A TypeName is typeSpecs, Type.

useslists
usesdcgs
usesassoc
c_parse+Tokens, -Itemsdet

Items is the translation unit of the c_lex Tokens, as described above. Throws nanofil_error/1 on a syntax error or unsupported construct.

124c_parseTokens, Items ←
list_to_assoc'__builtin_va_list'−t, Env
phraseunitEnv, Items, Tokens
c_constant_expression+Tokens, -Exprdet

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.

133c_constant_expressionTokens, Expr ←
c_constant_expressionTokens, [], Expr
c_constant_expression+Tokens, +Typedefs, -Exprdet

As c_constant_expression/2, where the names Typedefs are typedefs, as in an attribute's argument (aligned(__alignof__(md5_uint32))).

139c_constant_expressionTokens, Typedefs, Expr ←
empty_assocEnv0
foldltypedef_declared, Typedefs, Env0, Env
phraseconstant_expressionEnv, Expr, Tokens
144constant_expressionEnv, Expr ⟶
conditionalEnv, Expr
→
[]
else
parse_error'expected the end of the expression'
unit+Env, -Items//

the external declarations up to the end of input. The cut commits to the first parse of each declaration.

150unit_, [] ⟶
151unitEnv0, Items ⟶
external_declEnv0, Env, Items, Items1
!
unitEnv, Items1
154unit_, _ ⟶
parse_error'expected a declaration'
156eos[], []

Tokens

160tokenT ⟶
tokT, _
161punctP ⟶
tokpP, _
162kwK ⟶
tokkwK, _

Lookahead: the next token, left in the input.

165peekT, tokT, Loc ⟶
tokT, Loc

The next token's location, or eof.

168locLoc, tokT, Loc ⟶
tokT, Loc
169loceof ⟶
expect+P//

the punctuator P, or a syntax error.

172expectP ⟶
173expectP ⟶
formatatomMsg, 'expected ~w', P
parse_error+Msg//

throw a syntax error at the next token.

176parse_errorMsg, tokT, Loc ⟶
tokT, Loc
!
token_textT, Text
formatatomM, '~w: ~w, at ~w', Loc, Msg, Text
thrownanofil_errorM
182parse_errorMsg ⟶
formatatomM, 'end of input: ~w', Msg
thrownanofil_errorM
token_text+T, -Text

Text shows the c_lex token T in a message.

token_text
188pPP
189kwKK
190idNN
191intV, SintV, S
192floatCsfloatCs
193chrVchrV
194strBsstrBs
195wstrT, VswstrT, Vs

External declarations

external_decl+Env0, -Env, -Items, ?Items1//

one external declaration; Items is Items1 with its item in front, or Items1 itself for a stray ';'. Env is Env0 with its typedef names.

202external_declEnv, Env, Items, Items ⟶
!
203external_declEnv0, Env, Items, Items1 ⟶
kwextension
!
external_declEnv0, Env, Items, Items1
206external_declEnv, Env, Item|Items, Items ⟶
locLoc
kwstatic_assert
!
static_assert_restEnv, Loc, Item
210external_declEnv, Env, pragma_weakLoc, Name, Target|Items, Items ⟶
tokpragma_weakName, Target, Loc
!

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).

214external_declEnv, Env, toplevel_asmLoc, Template|Items, Items ⟶
locLoc
kwasm
!
→
[]
else
parse_error'expected a string in parentheses after top-level asm'
221external_declEnv0, Env, Item|Items, Items ⟶
locLoc
decl_specsEnv0, Specs
!
→
Item = declLoc, Specs, []
Env = Env0
else
full_declaratorEnv0, Dcl, Attrs0
Dcl = dclsomeName, func_, Params, _
Params ≠ knr_
peekp'{'
→
typedefs_hiddenName, Env0, Env
params_envParams, Env, BodyEnv
blockBodyEnv, Body
appendSpecs, Attrs0, Specs1
Item = fundefLoc, Specs1, Dcl, Body
Dcl = dclsomeName, funcRet, knrNames, Asm
knr_declsEnv0, Decls
peekp'{'
→
typedefs_hiddenName|Names, Env0, Env
appendSpecs, Attrs0, Specs1
Item = fundefLoc, Specs1, dclsomeName, funcRet, knrNames, Decls, Asm, Body
blockEnv, Body
else
init_declaratorsEnv0, Dcl, Inits, Attrs0, Attrs
auto_typeSpecs, Inits, Loc, SpecsA
appendSpecsA, Attrs, Specs1
Item = declLoc, Specs1, Inits
decl_envSpecsA, Inits, Env0, Env
static_assert_rest+Env, +Loc, -Item//

a _Static_assert after its keyword.

255static_assert_restEnv, Loc, static_assertLoc, Expr, Msg ⟶
conditionalEnv, Expr
else
Msg = []
auto_type+Specs0, +Inits, +Loc, -Specs

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.

267auto_typeSpecs0, Inits, Loc, Specs ←
selectchkauto_type, Specs0, Rest
→
Inits = init_, Init
Init ≠ none
Init ≠ list_
→
Specs = typeofexprcommanum0, none−dec, Init|Rest
else
formatatomM, '~w: __auto_type needs one declarator with an expression initializer', Loc
thrownanofil_errorM
else
Specs = Specs0
decl_env+Specs, +Inits, +Env0, -Env

Env is Env0 with the typedef names the declaration adds or (for ordinary names) hides.

279decl_envSpecs, Inits, Env0, Env ←
findallN, memberinitdclsomeN, _, _, _, Inits, Names
memberchkstoragetypedef, Specs
→
foldltypedef_declared, Names, Env0, Env
else
typedefs_hiddenNames, Env0, Env
typedef_declared+Name, +Env0, -Env

Env is Env0 with typedef Name.

287typedef_declaredName, Env0, Env ←
put_assocName, Env0, t, Env
typedefs_hidden+Names, +Env0, -Env

Env is Env0 where none of Names is a typedef name; typedef_hidden/3 is the same for one Name.

291typedefs_hiddenNames, Env0, Env ←
foldltypedef_hidden, Names, Env0, Env
293typedef_hiddenName, Env0, Env ←
del_assocName, Env0, _, Env1
→
Env = Env1
else
Env = Env0
params_env+Params, +Env0, -Env

Env is Env0 for a function body, where parameter names hide typedefs.

298params_envparamsParams, _, Env0, Env ←
findallN, memberparam_, dclsomeN, _, _, Params, Names
typedefs_hiddenNames, Env0, Env
301params_envunspecified, Env, Env
knr_decls+Env, -Decls//

the declarations of a K&R definition's parameters, between its declarator and its body (C11 6.9.1p6).

305knr_declsEnv, declLoc, Specs1, Inits|Decls ⟶
locLoc
decl_specsEnv, Specs
!
full_declaratorEnv, Dcl, Attrs0
init_declaratorsEnv, Dcl, Inits, Attrs0, Attrs
appendSpecs, Attrs, Specs1
knr_declsEnv, Decls
313knr_decls_, [] ⟶
[]
init_declarators+Env, +Dcl, -Inits, +Attrs0, -Attrs//

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.

319init_declaratorsEnv, Dcl, initDcl, Init|Inits, Attrs0, Attrs ⟶
→
initializerEnv, Init
else
Init = none
locLoc
punct(',')
→
full_declaratorEnv, Dcl1, Attrs1
appendAttrs0, Attrs1, Attrs2
init_declaratorsEnv, Dcl1, Inits, Attrs2, Attrs
else
Inits = []
Attrs = Attrs0
one_declarator_attrs+Attrs, +Loc

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.

334one_declarator_attrsAttrs, Loc ←
memberA, Attrs
→
formatatomM, '~w: attribute ~w on one of several declarators is not supported', Loc, Word
thrownanofil_errorM
else
true

Declaration specifiers

decl_specs+Env, -Specs//

nonempty declaration specifiers.

344decl_specsEnv, Specs ⟶
specifiersEnv, no, Specs
Specs ≢ []
specifiers+Env, +TypeSeen, -Specs//

once a type specifier is seen, an identifier is the declarator, not a typedef name.

350specifiersEnv, TypeSeen, Spec|Specs ⟶
specifierEnv, TypeSeen, Spec
!
spec_type_seenSpec, TypeSeen, TypeSeen1
specifiersEnv, TypeSeen1, Specs
354specifiers_, _, [] ⟶
[]
spec_type_seen+Spec, +TypeSeen0, -TypeSeen

TypeSeen is yes if Spec is a type specifier, else TypeSeen0.

spec_type_seen
358storage_TypeSeenTypeSeen
359tspec__yes
360qual_TypeSeenTypeSeen
361fspec_TypeSeenTypeSeen
362attr_TypeSeenTypeSeen
363alignas_TypeSeenTypeSeen
364su_, _, _, __yes
365enum_, __yes
366typeof__yes
367auto_type_yes
368atomic__yes
369tname__yes
371specifier_, _, storageS ⟶
memberchkS, typedef, extern, static, auto, register, (thread_local)
!
375specifier_, _, tspecK ⟶
memberchkK, void, char, short, int, long, float, double, signed, unsigned, bool, complex, int128, float16, bf16
!
380specifierEnv, _, atomicT ⟶
C11 6.7.2.4p4
kwatomic
!
type_nameEnv, T
385specifier_, _, qualQ ⟶
memberchkQ, const, (volatile), restrict, atomic
!
387specifier_, _, fspecF ⟶
memberchkF, inline, noreturn
!
389specifier_, _, attrA ⟶
390specifier_, _, attrc23 ⟶
391specifier_, _, attrextension ⟶
kwextension
!
392specifier_, _, attrnullability ⟶
clang's, on a pointer typedef (zix)
tokidN, _
!
394specifierEnv, _, alignasA ⟶
kwalignas
!
type_nameEnv, T
→
A = typeT
else
A = exprE
403specifierEnv, _, Spec ⟶
memberchkK, struct, union
!
struct_specEnv, K, Spec
406specifierEnv, _, Spec ⟶
kwenum
!
enum_specEnv, Spec
409specifier_, _, auto_type ⟶
kwauto_type
!
410specifierEnv, _, typeofA ⟶
kwtypeof
!
starts_type//1 reads __typeof__(__extension__ ({...})) (liburcu's
rcu_dereference) as an expression.
→
A = typeT
else
exprEnv, E
A = exprE
421specifierEnv, no, tnameN ⟶
get_assocN, Env, t
gnu_attr-Tokens//

__attribute__((...)), Tokens its balanced//1 contents.

427gnu_attrA ⟶
locLoc
kwattribute
A = A0
432gnu_attrA ⟶
clang -fdeclspec, as ruby builds
tokid'__declspec', _
→
[]
else
parse_error'this __declspec is not supported'
known_attributes+Tokens, +Loc

each attribute of __attribute__((...)) is one c_types known_attribute/3 lists, whatever its position, so none is dropped unread; any other is refused.

443known_attributesTokens, Loc ←
forall
membergroupInner, Tokens
attribute_itemsInner, Items
memberT|_, Items
,
known_attribute_atWord, Loc
448known_attribute_atWord, Loc ←
c_types:known_attributeWord, _, _
→
true
else
formatatomM, '~w: attribute ~w is not supported', Loc, Word
thrownanofil_errorM
attribute_items+Tokens, -Items

Tokens split at their commas.

455attribute_itemsTokens, Items ←
appendItem, p(',')|Rest, Tokens
→
Items = Item|Items1
attribute_itemsRest, Items1
else
Items = Tokens
attribute_name+Token, -Word

an attribute's name, GCC's __X__ as X.

462attribute_nameT, Word ←
T = idW0
or
T = kwW0
!
atom_concat'__', W1, W0
atom_concatW2, '__', W1
W2 ≢ ''
→
Word = W2
else
Word = W0
declspec_attr+Tokens, -Attr

__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.

472declspec_attridalign|Arg, groupidaligned|Arg ←
!
473declspec_attridW|Arg, groupidW|Arg ←
memberchkW, noreturn, noinline, noalias, deprecated, nothrow, restrict, dllexport, dllimport, novtable
balanced-Toks//

tokens up to the ')' matching an already-consumed '('; nested parentheses become group(Toks).

479balanced[] ⟶
!
480balancedT|Ts ⟶
!
T = groupInner
485balancedT|Ts ⟶
tokT, _
!
488balanced_ ⟶
parse_error'unbalanced parentheses'

c23_attr//: a standard attribute specifier. Do not confuse its opening '[[' with an array suffix. Arguments are balanced token sequences, not C expressions (unknown vendors may use their own argument language).

498c23_attrs ⟶
499c23_attrs ⟶
[]
501c23_attr_entries ⟶
→
→
Known = c23_knownPrefix, Name, Loc
else
Known = c23_knownnone, Prefix, Loc
callKnown
else
[]
else
[]
c23_known+Prefix, +Name, +Loc

[[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.

521c23_knownnone, Name0, Loc ←
!
attribute_nameidName0, Name
memberchkName, deprecated, fallthrough, maybe_unused, nodiscard, noreturn, '_Noreturn', reproducible, unsequenced
→
true
else
c23_hintnone, Name, Loc
528c23_knownPrefix, Name, Loc ←
memberchkPrefix, gnu, '__gnu__', clang, '_Clang'
→
attribute_nameidName, Word
c23_hintPrefix, Word, Loc
else
true
c23_hint+Prefix, +Word, +Loc

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.

539c23_hintPrefix, Word, Loc ←
c_types:known_attributeWord, honored, _
→
Prefix ≡ none
→
Shown = Word
else
formatatomShown, '~w::~w', Prefix, Word
formatatomM, '~w: the C23 attribute [[~w]] is not supported', Loc, Shown
thrownanofil_errorM
else
true
547attribute_wordN ⟶
!
548attribute_wordN ⟶

Like their GNU spellings, layout-changing attributes cannot be ignored. Reject here also at positions (e.g. pointers) whose attributes we discard.

552c23_attribute_semanticsPrefix, Name ⟶
memberchkPrefix, gnu, '__gnu__'
memberchkName, packed, '__packed__', aligned, '__aligned__'
!
parse_error'alignment attributes are not supported'
556c23_attribute_semantics_, _ ⟶
[]
558attribute_argumentClose ⟶
punctClose
!
559attribute_argumentClose ⟶
563attribute_argumentClose ⟶
¬memberchkT, p')', p']', p'}'
!
567attribute_argument_ ⟶
parse_error'unbalanced attribute argument'
attribute_delimiter
569'('')'
570'['']'
571'{''}'

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.

584attrs ⟶
585attrs ⟶
[]
hint_attributes+Tokens, +Loc

each attribute of __attribute__((...)) Tokens only hints (c_types known_attribute/3); an honored one, whose effect this position would drop, is refused.

590hint_attributesTokens, Loc ←
forall
membergroupInner, Tokens
attribute_itemsInner, Items
memberT|_, Items
,
c_types:known_attributeWord, honored, _
→
formatatomM, '~w: attribute ~w is not supported in this position', Loc, Word
thrownanofil_errorM
else
true
honored_attribute+Attr, -Word

the attr(Tokens) specifier Attr has an attribute Word that nanofil honors.

601honored_attributeattrTokens, Word ←
is_listTokens
membergroupInner, Tokens
attribute_itemsInner, Items
memberT|_, Items
c_types:known_attributeWord, honored, _
!
607struct_specEnv, Kind, suKind, Tag, Members, Attrs ⟶
attr_listAttrs0
tokenidTag0
→
Tag = someTag0
else
Tag = none
→
membersEnv, Members
attr_listAttrs1
else
Tag = some_
Members = none
Attrs1 = []
appendAttrs0, Attrs1, Attrs
617attr_listA|As ⟶
620attr_listgroupidpacked|As ⟶
[[gnu::packed]], as GNU's
memberchkPrefix, gnu, '__gnu__'
memberchkName, packed, '__packed__'
!
627attr_listAs ⟶
628attr_list[] ⟶
[]
members+Env, -Members//

the member declarations of a struct or union up to its '}'.

632members_, [] ⟶
!
633membersEnv, Members ⟶
!
membersEnv, Members
636membersEnv, static_assertLoc, E, Msg|Members ⟶
locLoc
kwstatic_assert
!
else
Msg = []
membersEnv, Members
645membersEnv, Members ⟶
decl_specsEnv, Specs
!
→
Members = fieldSpecs, dclnone, base, none, none|Members1
else
field_declsEnv, Specs, [], Members, Members1
membersEnv, Members1
653members_, _ ⟶
parse_error'expected a member declaration'
field_decls+Env, +Specs, +Mark, -Fields, ?Rest//

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.

660field_declsEnv, Specs, Mark, fieldFieldSpecs, Dcl, Width|Fields, Rest ⟶
→
Dcl = dclnone, base, none
Attrs = []
conditionalEnv, Width
else
full_declaratorEnv, Dcl, Attrs
→
conditionalEnv, Width
else
Width = none
attr_listAttrs1
findallattrX, memberX, Attrs1, Attrs2
appendMark, Specs, Attrs, Attrs2, FieldSpecs
punct(',')
→
field_declsEnv, Specs, shared_base, Fields, Rest
else
Fields = Rest
676enum_specEnv, enumTag, Enumerators ⟶
tokenidTag0
→
Tag = someTag0
else
Tag = none
→
P ≡ yes
→
Enumerators = packedEs
else
Enumerators = Es
else
Tag = some_
Enumerators = none
enum_attrs+P0, -P//

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).

690enum_attrsP0, P ⟶
locLoc
!
→
P1 = yes
else
A1 = A
P1 = P0
695enum_attrsP0, P ⟶
696enum_attrsP, P ⟶
[]
select_packed+Tokens, -Rest

the attribute list Tokens names packed; Rest is the list without it.

700select_packedgroupInner, groupRest ←
attribute_itemsInner, Items
selectT, Items, Others
!
foldljoin_items, Others, [], Rest
704join_itemsI, [], I ←
!
705join_itemsI, Acc, Joined ←
appendAcc, p(',')|I, Joined
enumerators+Env, -Enumerators//

the enumerators up to the '}'.

708enumerators_, [] ⟶
!
709enumeratorsEnv, enumeratorName, Value|Es ⟶
tokenidName
!
→
Value = V0
else
Value = none
punct(',')
→
else
Es = []
714enumerators_, _ ⟶
parse_error'expected an enumerator'

Declarators

full_declarator+Env, -Dcl, -Attrs//

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.

722full_declaratorEnv, dclsomeName, Type, Asm, Attrs ⟶
declaratorEnv, Name, Type, base
gnu_suffixAsm, Attrs1
findallattrX, memberX, Pre0, Pre
appendPre, Attrs1, Attrs
gnu_suffix-Asm, -Attrs//

attributes and an asm("symbol") label.

731gnu_suffixAsm, Attrs ⟶
attr_listAttrs0
kwasm
→
atom_codesSymbol, Bytes
Asm = asmSymbol
else
Asm = none
attr_listAttrs1
appendAttrs0, Attrs1, Attrs2
findallattrX, memberX, Attrs2, Attrs
declarator+Env, -Name, -Type, +Base//

a declarator of Name; Type is built around Base.

747declaratorEnv, Name, Type, Base ⟶
!
declaratorEnv, Name, Type, ptrBase, Quals
751declaratorEnv, Name, Type, Base ⟶
punct*emacs: typedef void (ATTRIBUTE_MAY_ALIAS *f) (void);
→
declaratorEnv, Name, Type, ptrBase, Quals
else
direct_declaratorEnv, Name, Type, Base
759direct_declaratorEnv, Name, Type, Base ⟶
tokenidName
!
suffixesEnv, Base, Type
762direct_declaratorEnv, Name, Type, Base ⟶
declaratorEnv, Name, Type, Mid
suffixesEnv, Base, Mid
768qualifiersQuals ⟶
memberchkQ, const, (volatile), restrict, atomic
!
Quals = Q|Quals1
772qualifiersQuals ⟶
776qualifiersQuals ⟶
777qualifiersQuals ⟶
clang's nullability, ignored
tokidN, _
!
780qualifiers[] ⟶
[]
782nullabilityN ←
memberchkN, '_Nullable', '_Nonnull', '_Null_unspecified', '_Nullable_result'
abstract_declarator+Env, -Type, +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.

788abstract_declaratorEnv, Type, Base ⟶
!
abstract_declaratorEnv, Type, ptrBase, Quals
792abstract_declaratorEnv, Type, Base ⟶
or
peekp'['
!
abstract_declaratorEnv, Type, Mid
suffixesEnv, Base, Mid
799abstract_declaratorEnv, Type, Base ⟶
suffixesEnv, Base, Type
suffixes+Env, +Base, -Type//

array and function suffixes, applied inside out: Type is Base with them.

803suffixesEnv, Base, Type ⟶
!
suffixesEnv, Base, Type
806suffixesEnv, Base, Type ⟶
!
array_sizeEnv, Size
suffixesEnv, Base, Type1
Type = arrayType1, Size
812suffixesEnv, Base, Type ⟶
!
paramsEnv, Params
suffixesEnv, Base, Type1
Type = funcType1, Params
817suffixes_, Base, Base ⟶
[]
array_size+Env, -Size//

the size between '[' and ']', or none; the ']' stays in the input.

821array_size_, none, tokp']', Loc ⟶
tokp']', Loc
!
822array_size_, star ⟶
a VLA of unspecified size, C11 6.7.6.2p4
peekp']'
!
825array_sizeEnv, Size ⟶
kwstatic
!
array_sizeEnv, Size
828array_sizeEnv, Size ⟶
memberchkQ, const, (volatile), restrict
!
array_sizeEnv, Size
831array_sizeEnv, Size ⟶
assignmentEnv, Size
params+Env, -Params//

a parameter list after its '(', through ')'.

834params_, unspecified ⟶
!
835params_, params[], fixed ⟶
kwvoid
!
838paramsEnv, knrNames ⟶
an identifier list, C11 6.7.6.3p3
knr_namesEnv, Names
!
841paramsEnv, paramsParams, Variadic ⟶
param_listEnv, Params, Variadic
845param_list_, [], variadic ⟶
!
846param_listEnv, Param|Params, Variadic ⟶
paramEnv, Param
punct(',')
→
param_listEnv, Params, Variadic
else
Params = []
Variadic = fixed
853knr_namesEnv, N|Ns ⟶
→
fail
else
[]
tokidN, _
punct(',')
→
knr_namesEnv, Ns
else
Ns = []
861paramEnv, paramSpecs, dclName, Type, none ⟶
decl_specsEnv, Specs
!
declaratorEnv, Name0, Type, base
→
Name = someName0
else
abstract_declaratorEnv, Type, base
Name = none
869param_, _ ⟶
!
parse_error'K&R-style parameter lists are not supported'
872param_, _ ⟶
parse_error'expected a parameter declaration'
type_name+Env, -TypeName//

a type name, type(Specs, Type).

875type_nameEnv, typeSpecs, Type ⟶
decl_specsEnv, Specs
abstract_declaratorEnv, Type, base
starts_type+Env//

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.

882starts_typeEnv, tokkwextension, Loc ⟶
tokkwextension, Loc
!
885starts_typeEnv, tokX, Loc ⟶
tokX, Loc
889type_start_tokenkwK, _ ←
memberchkK, void, char, short, int, long, float, double, signed, unsigned, bool, complex, int128, float16, bf16, struct, union, enum, const, (volatile), restrict, atomic, typeof, attribute, alignas, extension
895type_start_tokenidN, Env ←
get_assocN, Env, t

Initializers

initializer+Env, -Init//

an expression or a braced list(Entries).

901initializerEnv, listEntries ⟶
!
init_entriesEnv, Entries
904initializerEnv, E ⟶
init_entries+Env, -Entries//

the entries of a braced initializer after its '{', through '}'.

908init_entries_, [] ⟶
!
909init_entriesEnv, entryDesignators, Init|Entries ⟶
tokidF, _, tokp:, _GNU's obsolete field: value
→
Designators = fieldF
designatorsEnv, Ds0
Ds0 ≢ []
→
Designators = Ds0
else
Designators = []
initializerEnv, Init
punct(',')
→
init_entriesEnv, Entries
else
Entries = []
924designatorsEnv, D|Ds ⟶
!
927designators_, [] ⟶
[]
929designator_, fieldName ⟶
tokenidName
932designatorEnv, D ⟶
punct...GNU [first ... last]
→
conditionalEnv, Last
D = rangeE, Last
else
D = indexE

Statements

945blockEnv, atLoc, blockItems ⟶
locLoc
block_itemsEnv, Items
block_items+Env, -Items//

the items of a block through its '}'.

951block_items_, [] ⟶
!
952block_itemsEnv0, Item|Items ⟶
block_itemEnv0, Env, Item
block_itemsEnv, Items
block_item+Env0, -Env, -Item//

a declaration or statement in a block; Env is Env0 with the declaration's typedef names.

958block_itemEnv, Env, Item ⟶
locLoc
kwstatic_assert
!
static_assert_restEnv, Loc, Item
962block_itemEnv0, Env, Item ⟶
__extension__ before a block-scope declaration or _Static_assert,
as at file scope (binutils' __extension__ typedef, ruby's
__extension__ static_assert); before an expression it stays.
!
kwextension
block_itemEnv0, Env, Item
969block_itemEnv0, Env, Item ⟶
!
local_declEnv0, Env, Item
972block_itemEnv, Env, Stmt ⟶
statementEnv, Stmt
974extension_declEnv, S, S ←
S = tokkwextension, _|Rest
Rest = tokkwK, _|_
memberchkK, static_assert, extension
→
true
else
phrasestarts_declEnv, Rest, _
starts_decl+Env//

lookahead only: the next tokens begin a declaration, not a label.

982starts_declEnv, S, S ←
phraseleading_attrs, S, Rest
¬phraselabel_start, Rest, _
phrasestarts_typeEnv, Rest, _
→
true
else
Rest = tokkwK, _|_
memberchkK, typedef, extern, static, auto, register, (thread_local), inline, noreturn, auto_type

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).

995leading_attrs ⟶
996leading_attrs ⟶
997leading_attrs ⟶
[]

label_start//: lookahead: the next tokens are an identifier and ':'.

1000label_start, A, B ⟶
A, B
A = tokid_, _
B = tokp:, _
local_decl+Env0, -Env, -Decl//

a declaration in a block or for.

1005local_declEnv0, Env, declLoc, Specs1, Inits ⟶
locLoc
decl_specsEnv0, Specs
→
Inits = []
Attrs = []
else
full_declaratorEnv0, Dcl, Attrs0
init_declaratorsEnv0, Dcl, Inits, Attrs0, Attrs
auto_typeSpecs, Inits, Loc, SpecsA
appendSpecsA, Attrs, Specs1
decl_envSpecsA, Inits, Env0, Env
statement+Env, -Stmt//

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).

1023statementEnv, atLoc, S ⟶
1027statement_, _ ⟶
parse_error'expected a statement'
1029statement_bodyEnv, S ⟶
peekp'{'
!
blockEnv, at_, S
1032statement_body_, null ⟶
!
1033statement_bodyEnv, ifC, Then, Else ⟶
kwif
!
exprEnv, C
statementEnv, Then
kwelse
→
statementEnv, Else0
Else = Else0
else
Else = none
1044statement_bodyEnv, whileC, S ⟶
kwwhile
!
exprEnv, C
statementEnv, S
1050statement_bodyEnv, doS, C ⟶
kwdo
!
statementEnv, S
kwwhile
→
[]
else
parse_error'expected while'
exprEnv, C
1058statement_bodyEnv, forInit, C, Step, S ⟶
kwfor
!
→
local_declEnv, Env1, Init
else
opt_exprEnv, E0
Env1 = Env
E0 ≡ none
→
Init = none
else
Init = exprE0
opt_exprEnv1, C
opt_exprEnv1, Step
statementEnv1, S
1074statement_bodyEnv, switchE, S ⟶
kwswitch
!
exprEnv, E
statementEnv, S
1080statement_bodyEnv, caseE, S ⟶
kwcase
!
punct...GNU case range Lo ... Hi
→
E = rangeE0, Hi
else
E = E0
statementEnv, S
1089statement_bodyEnv, defaultS ⟶
kwdefault
!
statementEnv, S
1093statement_body_, break ⟶
kwbreak
!
1096statement_body_, continue ⟶
kwcontinue
!
1099statement_bodyEnv, returnE ⟶
kwreturn
!
opt_exprEnv, E
1103statement_bodyEnv, S ⟶
kwgoto
!
tokenidLabel0
→
S = gotoLabel0
→
exprEnv, E
S = computed_gotoE
else
parse_error'expected a label after goto'

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.

1122statement_bodyEnv, S ⟶
kwasm
!
string_bytesTemplate
blank_asmTemplate
asm_sectionsEnv, Outs, Ins, Clobbers
→
S = asm_emptyOuts, Ins, Clobbers, Goto
string_bytesTemplate
asm_sectionsEnv, Outs, Ins, Clobbers
→
S = asmTemplate, Outs, Ins, Clobbers, Goto
→
asm_kindToks, Kind
S = unsupported_asmKind
else
parse_error'expected asm operands in parentheses'

A label's GNU or C23 attributes (gnulib's _GL_UNUSED_LABEL, fail: __attribute__((__unused__));) are ignored.

1140statement_bodyEnv, labelLabel, S ⟶
!
tokidLabel, _, _
statementEnv, S
1145statement_bodyEnv, exprE ⟶
exprEnv, E
asm_kind+Toks, -Kind

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.

1152asm_kindToks, Kind ←
appendStrs, Rest, Toks
maplistasm_string, Strs, Parts
¬Rest = str_|_
append_, strC, group_|_, Rest
memberchk0'm, C
→
appendParts, Template
Kind = memoryTemplate
else
Kind = other
1159asm_stringstrBs, Bs
asm_qualifiers-Goto//

volatile, inline and goto, in any order; Goto is goto if goto is among them, else none.

1163asm_qualifiersGoto ⟶
kw(volatile)
→
kwinline
→
kwgoto
→
Goto = goto
else
Goto = none
blank_asm+Template

the template has only spaces, tabs, newlines and carriage returns, what filcc's handleInlineAsm counts as empty asm.

1172blank_asmTemplate ←
forallmemberC, Template, memberchkC, 0' , 0'\t, 0'\n, 0'\r
asm_sections+Env, -Outs, -Ins, -Clobbers//

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.

1177asm_sectionsEnv, Outs, Ins, Clobbers ⟶
→
asm_opsEnv, Outs
→
asm_opsEnv, Ins
→
→
appendCs, labelsLs, Clobbers
else
Clobbers = Cs
else
Clobbers = []
else
Ins = []
Clobbers = []
else
Outs = []
Ins = []
Clobbers = []
asm_ops+Env, -Ops//

operands op(Constraint, E), Constraint an atom; a symbolic name [x] is read and dropped.

1196asm_opsEnv, opC, E|Ops ⟶
→
else
[]
!
exprEnv, E
atom_codesC, Bs
punct(',')
→
asm_opsEnv, Ops
Ops ≢ []
else
Ops = []
1202asm_ops_, [] ⟶
[]
1204asm_clobbersC|Cs ⟶
!
atom_codesC, Bs
else
Cs = []
1207asm_clobbers[] ⟶
[]
1209asm_labelsL|Ls ⟶
!
punct(',')
→
else
Ls = []
1210asm_labels[] ⟶
[]
opt_expr+Env, -E//

an expression, or none before ';' or ')', which stays in the input.

1214opt_expr_, none, tokpP, Loc ⟶
tokpP, Loc
memberchkP, (;), ')'
!
1216opt_exprEnv, E ⟶
exprEnv, E

Expressions

expr+Env, -E//

an expression, commas included.

1221exprEnv, E ⟶
assignmentEnv, E0
punct(',')
→
exprEnv, E1
E = commaE0, E1
else
E = E0
1229assignmentEnv, E ⟶
conditionalEnv, Lhs
→
assignmentEnv, Rhs
E = assignOp, Lhs, Rhs
else
E = Lhs
1237assign_op=
1238assign_opOp ←
compound_assignment+Spelling, -Opsemidet

Spelling is the compound assignment of the arithmetic or shift operator Op: its spelling followed by = (C11 6.5.16.2).

1244compound_assignmentSpelling, Op ←
atom_concatP, =, Spelling
c_operatorP, _, Op, Kind
Kind = arith_
or
Kind ≡ shift
!
1249conditionalEnv, E ⟶
binaryEnv, 1, C
→
→
E = gcondC, B
else
exprEnv, A
E = condC, A, B
else
E = C
binary+Env, +Level, -E//

binary operators of precedence Level and up; level 11 is a cast expression.

1265binaryEnv, 11, E ⟶
!
castEnv, E
1266binaryEnv, Level, E ⟶
Level1 is Level + 1
binaryEnv, Level1, Lhs
binary_restEnv, Level, Lhs, E
1271binary_restEnv, Level, Lhs, E ⟶
c_operatorP, Level, Op, _
!
Level1 is Level + 1
binaryEnv, Level1, Rhs
binary_restEnv, Level, binOp, Lhs, Rhs, E
1276binary_rest_, _, E, E ⟶
[]
c_operator?Spelling, ?Level, ?Op, ?Kindnondet

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.

c_operator
1285'||'1lorlogical
1286&&2landlogical
1287('|')3orarithinteger
1288^4xorarithinteger
1289&5andarithinteger
1290==6eqcompare
1291'!='6necompare
1292<7ltcompare
1293>7gtcompare
1294<=7lecompare
1295>=7gecompare
1296<<8shlshift
1297>>8shrshift
1298+9addaritharithmetic
1299-9subaritharithmetic
1300*10mularitharithmetic
1301/10divaritharithmetic
1302'%'10modarithinteger
cast+Env, -E//

a cast expression or compound literal, else a unary expression.

1306castEnv, E ⟶
!
type_nameEnv, Type
→
init_entriesEnv, Entries
postfixEnv, complitType, listEntries, E
else
castEnv, E0
E = castType, E0
1317castEnv, E ⟶
unaryEnv, E
1319unaryEnv, unpreinc, E ⟶
!
unaryEnv, E
1322unaryEnv, unpredec, E ⟶
!
unaryEnv, E
1325unaryEnv, unOp, E ⟶
unopP, Op
!
castEnv, E
1328unaryEnv, E ⟶
kwsizeof
!
→
type_nameEnv, Type
punct'{'sizeof (T){...}, of a compound literal
→
init_entriesEnv, Entries
postfixEnv, complitType, listEntries, E0
E = sizeof_exprE0
else
E = sizeof_typeType
else
unaryEnv, E0
E = sizeof_exprE0
1342unaryEnv, alignofType ⟶
kwalignof
!
type_nameEnv, Type
1347unaryEnv, E ⟶
kwextension
!
castEnv, E
1350unary_, label_addrLabel ⟶
!
tokenidLabel0
→
Label = Label0
else
parse_error'expected a label after &&'
1355unaryEnv, E ⟶
primaryEnv, P
postfixEnv, P, E
unop
1359&addr
1360*deref
1361+plus
1362-neg
1363~bnot
1364!not
postfix+Env, +A, -E//

E is A with the postfix operators that follow.

1367postfixEnv, A, E ⟶
!
exprEnv, I
postfixEnv, indexA, I, E
1372postfixEnv, A, E ⟶
!
argsEnv, Args
postfixEnv, callA, Args, E
1376postfixEnv, A, E ⟶
!
postfixEnv, memberA, F, E
1380postfixEnv, A, E ⟶
punct(->)
!
postfixEnv, arrowA, F, E
1384postfixEnv, A, E ⟶
!
postfixEnv, unpostinc, A, E
1387postfixEnv, A, E ⟶
!
postfixEnv, unpostdec, A, E
1390postfix_, E, E ⟶
[]
1392field_nameF ⟶
!
1393field_name_ ⟶
parse_error'expected a member name'
args+Env, -Args//

call arguments after '(', through ')'.

1396args_, [] ⟶
!
1397argsEnv, A|As ⟶
punct(',')
→
argsEnv, As
else
As = []
primary+Env, -E//

a primary expression.

1406primaryEnv, offsetofType, Path ⟶
tokenid'__builtin_offsetof'
!
type_nameEnv, Type
expect(',')
offsetof_pathEnv, Steps
Path = fieldF|Steps
1415primaryEnv, va_argA, Type ⟶
tokenid'__builtin_va_arg'
!
expect(',')
type_nameEnv, Type
1419primaryEnv, bit_castType, E ⟶
tokenid'__builtin_bit_cast'
!
type_nameEnv, Type
expect(',')
1423primaryEnv, callid'__builtin_convertvector', A, Type ⟶
tokenid'__builtin_convertvector'
!
expect(',')
type_nameEnv, Type
1427primaryEnv, types_compatibleA, B ⟶
tokenid'__builtin_types_compatible_p'
!
type_nameEnv, A
expect(',')
type_nameEnv, B

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.

1435primaryEnv, castType, numV, none−dec ⟶
locLoc
kwtrue
→
V = 1
else
kwfalse
V = 0
!
phrasetype_nameEnv, Type, tokkwbool, Loc
1438primaryEnv, castType, num0, none−dec ⟶
locLoc
kwnullptr
!
phrasetype_nameEnv, Type, tokkwvoid, Loc, tokp*, Loc
1441primary_, idN ⟶
!
1442primary_, numV, S ⟶
tokenintV, S
!
1443primary_, floatCs ⟶
tokenfloatCs
!
1444primary_, chrV ⟶
tokenchrV
!
1445primary_, S ⟶
peekstr_
or
peekwstr_, _
!
parts_stringParts, S
1449primaryEnv, stmtexprBlock ⟶
peekp'{'
!
blockEnv, Block
1454primaryEnv, genericControl, Associations ⟶
kwgeneric
!
assignmentEnv, Control
expect(',')
generic_associationsEnv, Associations
1461primaryEnv, E ⟶
!
exprEnv, E
1465primary_, _ ⟶
parse_error'expected an expression'
1467generic_associationsEnv, assocType, Expr|Rest ⟶
kwdefault
→
Type = default
else
type_nameEnv, Type
assignmentEnv, Expr
punct(',')
→
else
Rest = []
1477offsetof_pathEnv, fieldF|Steps ⟶
1481offsetof_pathEnv, indexE|Steps ⟶
!
exprEnv, E
offsetof_pathEnv, Steps
1486offsetof_path_, [] ⟶
[]
string_literals-Parts//

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).

1491string_literalsS|Ss ⟶
!
else
Ss = []
1495string_literalstrB ⟶
tokenstrB
1496string_literalwstrT, Vs ⟶
tokenwstrT, Vs
string_bytes-Bytes//

adjacent plain string literals as one list of bytes.

1500string_bytesBs ⟶
tokenstrB0
!
→
appendB0, B1, Bs
else
Bs = B0
parts_string+Parts, -S

S is the string literal Parts concatenate.

1505parts_stringParts, S ←
findallT, memberwstrT, _, Parts, Types0
sortTypes0, Types
findallVs,
memberP, Parts
P = strVs
or
P = wstr_, Vs
,
Vss
appendVss, All
types_stringTypes, All, S
types_string+WideTypes, +Elements, -S

S is the literal of Elements: plain if no part is wide, else of the one wide type.

1516types_string[], All, strAll
1517types_stringT|Ts, All, S ←
wide_stringTs, T, All, S
1519wide_string[], T, All, wstrT, All
1520wide_string_|_, _, _, _ ←
thrownanofil_error'concatenating string literals of different wide types'