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 alignof_exprE
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, '__int128_t'−t, '__uint128_t'−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
193imagfloatCsimagfloatCs
194chrVchrV
195strBsstrBs
196wstrT, 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.

203external_declEnv, Env, Items, Items ⟶
!
204external_declEnv0, Env, Items, Items1 ⟶
kwextension
!
external_declEnv0, Env, Items, Items1
207external_declEnv, Env, Item|Items, Items ⟶
locLoc
kwstatic_assert
!
static_assert_restEnv, Loc, Item
211external_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).

215external_declEnv, Env, toplevel_asmLoc, Template|Items, Items ⟶
locLoc
kwasm
!
→
[]
else
parse_error'expected a string in parentheses after top-level asm'
222external_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.

256static_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.

268auto_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.

280decl_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.

288typedef_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.

292typedefs_hiddenNames, Env0, Env ←
foldltypedef_hidden, Names, Env0, Env
294typedef_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.

299params_envparamsParams, _, Env0, Env ←
findallN, memberparam_, dclsomeN, _, _, Params, Names
typedefs_hiddenNames, Env0, Env
302params_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).

306knr_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
314knr_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.

320init_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.

335one_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.

345decl_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.

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

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

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

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

428gnu_attrA ⟶
locLoc
kwattribute
A = A0
433gnu_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.

444known_attributesTokens, Loc ←
forall
membergroupInner, Tokens
attribute_itemsInner, Items
memberT|_, Items
,
known_attribute_atWord, Loc
449known_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.

456attribute_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.

463attribute_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.

473declspec_attridalign|Arg, groupidaligned|Arg ←
!
474declspec_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).

480balanced[] ⟶
!
481balancedT|Ts ⟶
!
T = groupInner
486balancedT|Ts ⟶
tokT, _
!
489balanced_ ⟶
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).

499c23_attrs ⟶
500c23_attrs ⟶
[]
502c23_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.

522c23_knownnone, Name0, Loc ←
!
attribute_nameidName0, Name
memberchkName, deprecated, fallthrough, maybe_unused, nodiscard, noreturn, '_Noreturn', reproducible, unsequenced
→
true
else
c23_hintnone, Name, Loc
529c23_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.

540c23_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
548attribute_wordN ⟶
!
549attribute_wordN ⟶

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

553c23_attribute_semanticsPrefix, Name ⟶
memberchkPrefix, gnu, '__gnu__'
memberchkName, packed, '__packed__', aligned, '__aligned__'
!
parse_error'alignment attributes are not supported'
557c23_attribute_semantics_, _ ⟶
[]
559attribute_argumentClose ⟶
punctClose
!
560attribute_argumentClose ⟶
564attribute_argumentClose ⟶
¬memberchkT, p')', p']', p'}'
!
568attribute_argument_ ⟶
parse_error'unbalanced attribute argument'
attribute_delimiter
570'('')'
571'['']'
572'{''}'

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.

585attrs ⟶
586attrs ⟶
[]
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.

591hint_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.

602honored_attributeattrTokens, Word ←
is_listTokens
membergroupInner, Tokens
attribute_itemsInner, Items
memberT|_, Items
c_types:known_attributeWord, honored, _
!
608struct_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
618attr_listA|As ⟶
621attr_listgroupidpacked|As ⟶
[[gnu::packed]], as GNU's
memberchkPrefix, gnu, '__gnu__'
memberchkName, packed, '__packed__'
!
628attr_listAs ⟶
629attr_list[] ⟶
[]
members+Env, -Members//

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

633members_, [] ⟶
!
634membersEnv, Members ⟶
!
membersEnv, Members
637membersEnv, static_assertLoc, E, Msg|Members ⟶
locLoc
kwstatic_assert
!
else
Msg = []
membersEnv, Members
646membersEnv, Members ⟶
decl_specsEnv, Specs
!
→
Members = fieldSpecs, dclnone, base, none, none|Members1
else
field_declsEnv, Specs, [], Members, Members1
membersEnv, Members1
654members_, _ ⟶
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.

661field_declsEnv, Specs, Mark, fieldFieldSpecs, Dcl, Width|Fields, Rest ⟶
→
Dcl = dclnone, base, none
Attrs = []
conditionalEnv, Width
else
full_declaratorEnv, no, 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
677enum_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).

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

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

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

the enumerators up to the '}'.

709enumerators_, [] ⟶
!
710enumeratorsEnv, enumeratorName, Value|Es ⟶
tokenidName
!
→
Value = V0
else
Value = none
punct(',')
→
else
Es = []
715enumerators_, _ ⟶
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 ⟪unused⟫ *a, *b) and after it come back as attr(Tokens) specifiers in Attrs, and so do declaration attributes among its pointers' qualifiers (declarator//6).

725full_declaratorEnv, Dcl, Attrs ⟶
full_declaratorEnv, [], Dcl, Attrs
full_declarator+Env, +Slid0, -Dcl, -Attrs//

as full_declarator//3; Slid0 is no where declaration attributes have no declaration to go to (a member), and they are refused among the qualifiers.

731full_declaratorEnv, Slid0, dclsomeName, Type, Asm, Attrs ⟶
declaratorEnv, Name, Type, base, Slid0, Slid
gnu_suffixAsm, Attrs1
findallattrX, memberX, Pre0, Pre
Slid ≡ no
→
Slid1 = []
else
Slid1 = Slid
appendPre, Slid1, Attrs1, Attrs
gnu_suffix-Asm, -Attrs//

attributes and an asm("symbol") label.

741gnu_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. Its qualifiers may hold only hints (a parameter).

757declaratorEnv, Name, Type, Base ⟶
declaratorEnv, Name, Type, Base, no, _
declarator+Env, -Name, -Type, +Base, +Slid0, -Slid//

as declarator//4. A declaration attribute among a pointer's qualifiers (decl_attribute/1), as glibc writes int ⟪weak⟫ *fvoid, is the declaration's: clang moves it there (ProcessDeclAttributes). Slid is Slid0 with those attr(Tokens) appended; when Slid0 is no they are refused as other honored attributes there are.

767declaratorEnv, Name, Type, Base, S0, S ⟶
!
qualifiersQuals, S0, S1
declaratorEnv, Name, Type, ptrBase, Quals, S1, S
771declaratorEnv, Name, Type, Base, S0, S ⟶
punct*emacs: typedef void (ATTRIBUTE_MAY_ALIAS *f) (void);
→
qualifiersQuals, S0, S1
declaratorEnv, Name, Type, ptrBase, Quals, S1, S
else
direct_declaratorEnv, Name, Type, Base, S0, S
779direct_declaratorEnv, Name, Type, Base, S, S ⟶
tokenidName
!
suffixesEnv, Base, Type
782direct_declaratorEnv, Name, Type, Base, S0, S ⟶
declaratorEnv, Name, Type, Mid, S0, S
suffixesEnv, Base, Mid
788qualifiersQuals ⟶
qualifiersQuals, no, _
790qualifiersQuals, S0, S ⟶
memberchkQ, const, (volatile), restrict, atomic
!
Quals = Q|Quals1
qualifiersQuals1, S0, S
794qualifiersQuals, S0, S ⟶
locLoc
!
slid_attributesA, Loc, S0, S1
qualifiersQuals, S1, S
798qualifiersQuals, S0, S ⟶
!
qualifiersQuals, S0, S
799qualifiersQuals, S0, S ⟶
clang's nullability, ignored
tokidN, _
!
qualifiersQuals, S0, S
802qualifiers[], S, S ⟶
[]
slid_attributes+Tokens, +Loc, +Slid0, -Slid

Tokens, attributes among a pointer's qualifiers, go to the declaration as attr(Tokens) when Slid0 is a list and every honored one among them is a declaration attribute; otherwise each must be a hint.

808slid_attributesTokens, Loc, S0, S ←
S0 ≢ no
forall
membergroupInner, Tokens
attribute_itemsInner, Items
memberT|_, Items
c_types:known_attributeWord, honored, _
,
decl_attributeWord
→
appendS0, attrTokens, S
else
hint_attributesTokens, Loc
S = S0
decl_attribute?Word

an honored attribute of the declared symbol (its linkage, section or role), not of a type, so it means the same wherever in the declaration it is written.

decl_attributeweaksectionaliasweakrefconstructordestructorgnu_inlinecommon
830nullabilityN ←
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.

836abstract_declaratorEnv, Type, Base ⟶
!
abstract_declaratorEnv, Type, ptrBase, Quals
840abstract_declaratorEnv, Type, Base ⟶
or
peekp'['
!
abstract_declaratorEnv, Type, Mid
suffixesEnv, Base, Mid
847abstract_declaratorEnv, Type, Base ⟶
suffixesEnv, Base, Type
suffixes+Env, +Base, -Type//

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

851suffixesEnv, Base, Type ⟶
!
suffixesEnv, Base, Type
854suffixesEnv, Base, Type ⟶
!
array_sizeEnv, Size
suffixesEnv, Base, Type1
Type = arrayType1, Size
860suffixesEnv, Base, Type ⟶
!
paramsEnv, Params
suffixesEnv, Base, Type1
Type = funcType1, Params
865suffixes_, Base, Base ⟶
[]
array_size+Env, -Size//

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

869array_size_, none, tokp']', Loc ⟶
tokp']', Loc
!
870array_size_, star ⟶
a VLA of unspecified size, C11 6.7.6.2p4
peekp']'
!
873array_sizeEnv, Size ⟶
kwstatic
!
array_sizeEnv, Size
876array_sizeEnv, Size ⟶
memberchkQ, const, (volatile), restrict
!
array_sizeEnv, Size
879array_sizeEnv, Size ⟶
assignmentEnv, Size
params+Env, -Params//

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

882params_, unspecified ⟶
!
883params_, params[], fixed ⟶
kwvoid
!
886paramsEnv, knrNames ⟶
an identifier list, C11 6.7.6.3p3
knr_namesEnv, Names
!
889paramsEnv, paramsParams, Variadic ⟶
param_listEnv, Params, Variadic
893param_list_, [], variadic ⟶
!
894param_listEnv, Param|Params, Variadic ⟶
paramEnv, Param
punct(',')
→
param_listEnv, Params, Variadic
else
Params = []
Variadic = fixed
901knr_namesEnv, N|Ns ⟶
→
fail
else
[]
tokidN, _
punct(',')
→
knr_namesEnv, Ns
else
Ns = []
909paramEnv, paramSpecs, dclName, Type, none ⟶
decl_specsEnv, Specs
!
declaratorEnv, Name0, Type, base
→
Name = someName0
else
abstract_declaratorEnv, Type, base
Name = none
917param_, _ ⟶
!
parse_error'K&R-style parameter lists are not supported'
920param_, _ ⟶
parse_error'expected a parameter declaration'
type_name+Env, -TypeName//

a type name, type(Specs, Type).

923type_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.

930starts_typeEnv, tokkwextension, Loc ⟶
tokkwextension, Loc
!
933starts_typeEnv, tokX, Loc ⟶
tokX, Loc
937type_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
943type_start_tokenidN, Env ←
get_assocN, Env, t

Initializers

initializer+Env, -Init//

an expression or a braced list(Entries).

949initializerEnv, listEntries ⟶
!
init_entriesEnv, Entries
952initializerEnv, E ⟶
init_entries+Env, -Entries//

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

956init_entries_, [] ⟶
!
957init_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 = []
972designatorsEnv, D|Ds ⟶
!
975designators_, [] ⟶
[]
977designator_, fieldName ⟶
tokenidName
980designatorEnv, D ⟶
punct...GNU [first ... last]
→
conditionalEnv, Last
D = rangeE, Last
else
D = indexE

Statements

993blockEnv, atLoc, blockItems ⟶
locLoc
block_itemsEnv, Items0
Map ≡ []
→
Items = Items0
else
local_renameMap, Items0, Items
local_labels-Map//

GNU's local label declarations at the start of a block (__label__ a, b;), as glibc's macros declare the labels of their statement expressions. Map pairs each name with a name of its own in this block, made from the declaration's place in the token list (tokens still to read), so each expansion's labels differ.

1005local_labelsMap ⟶
tokenid'__label__'
!
findallN−New,
memberN, Names
formatatomNew, '~w..~w', N, K
,
Map0
appendMap0, Map1, Map
1013local_labels[] ⟶
[]
1015tokens_leftK, S, S ←
lengthS, K
1017label_namesL|Ls ⟶
!
else
Ls = []
1020label_names_ ⟶
parse_error'expected a label name after __label__'
local_rename+Map, +T0, -T

T is the block T0 with the labels of Map renamed in its label statements, gotos, &&label and asm goto labels. Inner blocks were renamed first, so their own declarations shadow.

1025local_renameMap, T0, T ←
¬compoundT0
→
T = T0
T0 = gotoL
→
local_nameMap, L, L1
T = gotoL1
T0 = label_addrL
→
local_nameMap, L, L1
T = label_addrL1
T0 = labelsLs
is_listLs
→
maplistlocal_nameMap, Ls, Ls1
T = labelsLs1
T0 = labelL, S0
atomL
→
local_nameMap, L, L1
local_renameMap, S0, S
T = labelL1, S
else
T0 =.. F|As0
maplistlocal_renameMap, As0, As
T =.. F|As
1035local_nameMap, L, L1 ←
memberchkL−L1, Map
→
true
else
L1 = L
block_items+Env, -Items//

the items of a block through its '}'.

1038block_items_, [] ⟶
!
1039block_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.

1045block_itemEnv, Env, Item ⟶
locLoc
kwstatic_assert
!
static_assert_restEnv, Loc, Item
1049block_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
1056block_itemEnv0, Env, Item ⟶
!
local_declEnv0, Env, Item
1059block_itemEnv, Env, Stmt ⟶
statementEnv, Stmt
1061extension_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.

1069starts_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: return fx; (filc/tests musttail).

1082leading_attrs ⟶
1083leading_attrs ⟶
1084leading_attrs ⟶
[]

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

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

a declaration in a block or for.

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

1110statementEnv, atLoc, S ⟶
1114statement_, _ ⟶
parse_error'expected a statement'
1116statement_bodyEnv, S ⟶
peekp'{'
!
blockEnv, at_, S
1119statement_body_, null ⟶
!
1120statement_bodyEnv, ifC, Then, Else ⟶
kwif
!
exprEnv, C
statementEnv, Then
kwelse
→
statementEnv, Else0
Else = Else0
else
Else = none
1131statement_bodyEnv, whileC, S ⟶
kwwhile
!
exprEnv, C
statementEnv, S
1137statement_bodyEnv, doS, C ⟶
kwdo
!
statementEnv, S
kwwhile
→
[]
else
parse_error'expected while'
exprEnv, C
1145statement_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
1161statement_bodyEnv, switchE, S ⟶
kwswitch
!
exprEnv, E
statementEnv, S
1167statement_bodyEnv, caseE, S ⟶
kwcase
!
punct...GNU case range Lo ... Hi
→
E = rangeE0, Hi
else
E = E0
statementEnv, S
1176statement_bodyEnv, defaultS ⟶
kwdefault
!
statementEnv, S
1180statement_body_, break ⟶
kwbreak
!
1183statement_body_, continue ⟶
kwcontinue
!
1186statement_bodyEnv, returnE ⟶
kwreturn
!
opt_exprEnv, E
1190statement_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.

1209statement_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:;) are ignored.

1227statement_bodyEnv, labelLabel, S ⟶
!
tokidLabel, _, _
statementEnv, S
1232statement_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.

1239asm_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
1246asm_stringstrBs, Bs
asm_qualifiers-Goto//

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

1250asm_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.

1259blank_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.

1264asm_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.

1283asm_opsEnv, opC, E|Ops ⟶
→
else
[]
!
exprEnv, E
atom_codesC, Bs
punct(',')
→
asm_opsEnv, Ops
Ops ≢ []
else
Ops = []
1289asm_ops_, [] ⟶
[]
1291asm_clobbersC|Cs ⟶
!
atom_codesC, Bs
else
Cs = []
1294asm_clobbers[] ⟶
[]
1296asm_labelsL|Ls ⟶
!
punct(',')
→
else
Ls = []
1297asm_labels[] ⟶
[]
opt_expr+Env, -E//

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

1301opt_expr_, none, tokpP, Loc ⟶
tokpP, Loc
memberchkP, (;), ')'
!
1303opt_exprEnv, E ⟶
exprEnv, E

Expressions

expr+Env, -E//

an expression, commas included.

1308exprEnv, E ⟶
assignmentEnv, E0
punct(',')
→
exprEnv, E1
E = commaE0, E1
else
E = E0
1316assignmentEnv, E ⟶
conditionalEnv, Lhs
→
assignmentEnv, Rhs
E = assignOp, Lhs, Rhs
else
E = Lhs
1324assign_op=
1325assign_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).

1331compound_assignmentSpelling, Op ←
atom_concatP, =, Spelling
c_operatorP, _, Op, Kind
Kind = arith_
or
Kind ≡ shift
!
1336conditionalEnv, 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.

1352binaryEnv, 11, E ⟶
!
castEnv, E
1353binaryEnv, Level, E ⟶
Level1 is Level + 1
binaryEnv, Level1, Lhs
binary_restEnv, Level, Lhs, E
1358binary_restEnv, Level, Lhs, E ⟶
c_operatorP, Level, Op, _
!
Level1 is Level + 1
binaryEnv, Level1, Rhs
binary_restEnv, Level, binOp, Lhs, Rhs, E
1363binary_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
1372'||'1lorlogical
1373&&2landlogical
1374('|')3orarithinteger
1375^4xorarithinteger
1376&5andarithinteger
1377==6eqcompare
1378'!='6necompare
1379<7ltcompare
1380>7gtcompare
1381<=7lecompare
1382>=7gecompare
1383<<8shlshift
1384>>8shrshift
1385+9addaritharithmetic
1386-9subaritharithmetic
1387*10mularitharithmetic
1388/10divaritharithmetic
1389'%'10modarithinteger
cast+Env, -E//

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

1393castEnv, E ⟶
!
type_nameEnv, Type
→
init_entriesEnv, Entries
postfixEnv, complitType, listEntries, E
else
castEnv, E0
E = castType, E0
1404castEnv, E ⟶
unaryEnv, E
1406unaryEnv, unpreinc, E ⟶
!
unaryEnv, E
1409unaryEnv, unpredec, E ⟶
!
unaryEnv, E
1412unaryEnv, unOp, E ⟶
unopP, Op
!
castEnv, E
1415unaryEnv, 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
1429unaryEnv, real_partE ⟶
GNU __real__ and __imag__
kwreal
!
castEnv, E
1432unaryEnv, imag_partE ⟶
kwimag
!
castEnv, E
1435unaryEnv, E ⟶
kwalignof
!
→
type_nameEnv, Type
E = alignofType
else
unaryEnv, E0GNU __alignof__ of an expression
E = alignof_exprE0
1444unaryEnv, E ⟶
kwextension
!
castEnv, E
1447unary_, label_addrLabel ⟶
!
tokenidLabel0
→
Label = Label0
else
parse_error'expected a label after &&'
1452unaryEnv, E ⟶
primaryEnv, P
postfixEnv, P, E
unop
1456&addr
1457*deref
1458+plus
1459-neg
1460~bnot
1461!not
postfix+Env, +A, -E//

E is A with the postfix operators that follow.

1464postfixEnv, A, E ⟶
!
exprEnv, I
postfixEnv, indexA, I, E
1469postfixEnv, A, E ⟶
!
argsEnv, Args
postfixEnv, callA, Args, E
1473postfixEnv, A, E ⟶
!
postfixEnv, memberA, F, E
1477postfixEnv, A, E ⟶
punct(->)
!
postfixEnv, arrowA, F, E
1481postfixEnv, A, E ⟶
!
postfixEnv, unpostinc, A, E
1484postfixEnv, A, E ⟶
!
postfixEnv, unpostdec, A, E
1487postfix_, E, E ⟶
[]
1489field_nameF ⟶
!
1490field_name_ ⟶
parse_error'expected a member name'
args+Env, -Args//

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

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

a primary expression.

1503primaryEnv, offsetofType, Path ⟶
tokenid'__builtin_offsetof'
!
type_nameEnv, Type
expect(',')
offsetof_pathEnv, Steps
Path = fieldF|Steps
1512primaryEnv, va_argA, Type ⟶
tokenid'__builtin_va_arg'
!
expect(',')
type_nameEnv, Type
1516primaryEnv, bit_castType, E ⟶
tokenid'__builtin_bit_cast'
!
type_nameEnv, Type
expect(',')
1520primaryEnv, callid'__builtin_convertvector', A, Type ⟶
tokenid'__builtin_convertvector'
!
expect(',')
type_nameEnv, Type
1524primaryEnv, 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.

1532primaryEnv, castType, numV, none−dec ⟶
locLoc
kwtrue
→
V = 1
else
kwfalse
V = 0
!
phrasetype_nameEnv, Type, tokkwbool, Loc
1535primaryEnv, castType, num0, none−dec ⟶
locLoc
kwnullptr
!
phrasetype_nameEnv, Type, tokkwvoid, Loc, tokp*, Loc
1538primary_, idN ⟶
!
1539primary_, numV, S ⟶
tokenintV, S
!
1540primary_, floatCs ⟶
tokenfloatCs
!
1541primary_, imagfloatCs ⟶
tokenimagfloatCs
!
1542primary_, chrV ⟶
tokenchrV
!
1543primary_, S ⟶
peekstr_
or
peekwstr_, _
!
parts_stringParts, S
1547primaryEnv, stmtexprBlock ⟶
peekp'{'
!
blockEnv, Block
1552primaryEnv, genericControl, Associations ⟶
kwgeneric
!
assignmentEnv, Control
expect(',')
generic_associationsEnv, Associations
1559primaryEnv, E ⟶
!
exprEnv, E
1563primary_, _ ⟶
parse_error'expected an expression'
1565generic_associationsEnv, assocType, Expr|Rest ⟶
kwdefault
→
Type = default
else
type_nameEnv, Type
assignmentEnv, Expr
punct(',')
→
else
Rest = []
1575offsetof_pathEnv, fieldF|Steps ⟶
1579offsetof_pathEnv, indexE|Steps ⟶
!
exprEnv, E
offsetof_pathEnv, Steps
1584offsetof_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).

1589string_literalsS|Ss ⟶
!
else
Ss = []
1593string_literalstrB ⟶
tokenstrB
1594string_literalwstrT, Vs ⟶
tokenwstrT, Vs
string_bytes-Bytes//

adjacent plain string literals as one list of bytes.

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

S is the string literal Parts concatenate.

1603parts_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.

1614types_string[], All, strAll
1615types_stringT|Ts, All, S ←
wide_stringTs, T, All, S
1617wide_string[], T, All, wstrT, All
1618wide_string_|_, _, _, _ ←
thrownanofil_error'concatenating string literals of different wide types'