Fil-C on Nix

Filnix wiki

mbrock/filnix/nanofil

c_lex.pl

The C lexer (milestone 4a): it turns preprocessed C text into a list of tokens, each with its file and line.

Input

The input is preprocessed text. A line marker # N "file" or #line N "file" sets the location of the next line, and #pragma weak and #pragma pack are tokens; any other directive line, such as another #pragma or #ident, is skipped. Comments are skipped too, so hand-written test input works without a preprocessor.

Tokens

A token is tokT, File:Line, with T one of:

TIs
idAtoman identifier
kwAtoma keyword; GNU spellings are folded, so __restrict__ is kwrestrict and __asm__ is kwasm
intValue, Suffix−Radixan integer constant: Suffix is none, u, l, ul, ll or ull; Radix is dec, or other for hex and octal, which type differently
floatCharsa floating constant, its characters as written
chrValuea character constant, as an int
strBytesa string literal, its bytes without the terminator (u8"..." too, as UTF-8 bytes)
wstrType, CodesL"...", u"..." or U"...": Type is the element type, int (wchar_t), ushort (char16_t) or uint (char32_t), and Codes the elements without the 0
pAtoma punctuator, such as p(->) or p<<=
pragma_weakName, Target#pragma weak Name, Target none, or #pragma weak Name = Target

A #pragma pack line is consumed by pack_tokens/2, which c_tokens/3 and c_pp apply to a whole token list: each struct or union keyword under #pragma pack(N) is followed by the attribute __attribute__((__nanofil_pack__(N))), which c_types' su_layout/5 honors. GCC and clang apply the pragma to the definitions after it.

useslists
usesdcgs
usescharsio
42:- ifcurrent_prolog_flagdialect, swi
usesreadutil
44:- endif
c_tokens+Chars, +File, -Tokensdet

Tokens are the tok(T, File:Line) terms of the C text Chars, read as file File from line 1. Throws nanofil_error/1 on a lexical error.

50c_tokensCs, File, Tokens ←
phrasetokensTokens0, File:1, bol, Cs
→
pack_tokensTokens0, Tokens
else
thrownanofil_error'C lexer failed'
i_file_tokens+File, -Tokensdet

Tokens are c_tokens/3's tokens of the preprocessed (.i) File, lexed a line at a time, so that a line's characters are garbage once it is lexed: the characters of a whole file, 24 bytes each, would sit beside its tokens (a 31 MB tree-sitter parser's are 750 MB). A line marker sets the next line's location, as in c_tokens/3; a block comment that spans lines, which cc -E never leaves, is lexed with the lines up to its end.

65i_file_tokensFile, Tokens ←
setup_call_cleanupopenFile, read, S, encodingoctet, i_linesS, File:1, Tokens0, [], closeS
pack_tokensTokens0, Tokens
71i_linesS, Loc, Ts0, Ts ←
Cs ≡ []
→
Ts0 = Ts
else
i_lineS, Cs, Loc, Loc1, Ts0, Ts1
i_linesS, Loc1, Ts1, Ts
i_line+S, +Cs, +Loc, -Loc1, -Ts0, ?Ts

Ts0-Ts are the tokens of line Cs at Loc (with the lines after it while a comment is open); Loc1 is the location after them.

82i_lineS, Cs, Loc, Loc1, Ts0, Ts ←
catch
oncephrasetokensLTs, Loc, bol, Loc1, Cs
Open = no
,
nanofil_errorM,
sub_atomM, _, _, _, 'unterminated comment'
→
Open = M
else
thrownanofil_errorM
Open ≡ no
→
appendLTs, Ts, Ts0
else
line_charsS, More
More ≡ []
→
thrownanofil_errorOpen
else
appendCs, More, Cs1
i_lineS, Cs1, Loc, Loc1, Ts0, Ts
line_chars+S, -Cs

Cs are the characters of S's next line, with its newline; [] at the end of the file.

101line_charsS, Cs ←
read_line_to_codesS, Codes, []
string_codesStr, Codes
string_charsStr, Cs
pack_tokens+Tokens0, -Tokensdet

Tokens are Tokens0 without their pragma_pack(Op) tokens, each struct or union keyword under a #pragma pack(N) followed by the attribute __nanofil_pack__(N). Tokens0 is not copied when it has no pragma (GLib's resource blobs are millions of tokens).

112pack_tokensTokens0, Tokens ←
memberchktokpragma_pack_, _, Tokens0
→
oncephrasepacked_tokensTokens0, none, Tokens
else
Tokens = Tokens0
118packed_tokens[], _ ⟶
[]
119packed_tokenstokpragma_packOp, _|Ts, Packs0 ⟶
!
pack_stackOp, Packs0, Packs
packed_tokensTs, Packs
122packed_tokenstokkwK, Loc|Ts, Packs ⟶
Packs = N|_
integerN
memberchkK, struct, union
!
formatcharsCs, '__attribute__((__nanofil_pack__(~w)))', N
phrasetokensAttr0, Loc, mid, Cs
tokkwK, Loc
seqAttr0
packed_tokensTs, Packs
128packed_tokensT|Ts, Packs ⟶
T
packed_tokensTs, Packs
pack_stack+Op, +Packs0, -Packs

the #pragma pack stack after Op: the current value, then the saved entries s(Label, Value), Label none for an unnamed push. A pop restores the latest entry, or with a label the latest entry of that label, dropping those above it; a pop that finds none changes nothing (clang warns), except that an unnamed pop of an empty stack restores the default, as GCC's.

136pack_stacksetN, _|S, N|S
137pack_stackpushL, keep, C|S, C, sL, C|S
138pack_stackpushL, N, C|S, N, sL, C|S ←
N ≢ keep
139pack_stackpopnone, _|S, Packs ←
S = s_, V|S1
→
Packs = V|S1
else
Packs = none
141pack_stackpopL, C|S, Packs ←
L ≢ none
append_, sL, V|S1, S
→
Packs = V|S1
else
Packs = C|S
144pack_stackshow, Packs, Packs
pack_op+Args, -Opsemidet

Op is the pragma_pack operation of #pragma pack(Args), Args its operands as atoms and integers: pack() sets the default, pack(N) sets N, pack(push[, Label][, N]) saves the current value first, pack(pop[, Label]) restores it, and pack(show) changes nothing. N is a power of two up to 16. Fails for any other form, which nanofil refuses.

154pack_op[], setnone ←
!
155pack_oppush, pushnone, keep ←
!
156pack_oppop, popnone ←
!
157pack_opshow, show ←
!
158pack_oppush, (','), N, pushnone, N ←
integerN
!
159pack_oppush, (','), L, pushL, keep ←
160pack_oppush, (','), L, (','), N, pushL, N ←
161pack_oppop, (','), L, popL ←
162pack_opN, setN ←
164pack_valueN ←
memberchkN, 1, 2, 4, 8, 16
165pack_labelL ←
atomL
atom_charsL, C|_

Lines, blanks and comments

tokens-Tokens, +Loc, +Where//

Tokens are the tokens of the rest of the input, which starts at Loc. Where is bol at the beginning of a line, where # starts a directive, and mid elsewhere. tokens//4 also gives the location End at the end of the input.

173tokensTs, Loc, Where ⟶
tokensTs, Loc, Where, _
175tokensTs, Loc, _, End ⟶
'\n'
!
loc_next_lineLoc, Loc1
tokensTs, Loc1, bol, End
179tokenstokT, Loc|Ts, Loc, bol, End ⟶
#
!
tokensTs, Loc, mid, End
182tokenstokpragma_packOp, Loc|Ts, Loc, bol, End ⟶
#
"pragma"
C
"pack"
!
catchphrasetokensArgs0, Loc, mid, Cs, _, fail
maplistpack_arg, Args0, Args
Args = '('|Rest
appendInner, ')', Rest
pack_opInner, Op
→
true
else
formatatomM, '~w: this #pragma pack is not supported', Loc
thrownanofil_errorM
tokensTs, Loc, mid, End
194tokensTs, Loc, bol, End ⟶
#
!
directiveLoc, Loc1
tokensTs, Loc1, bol, End
198tokensTs, Loc, Where, End ⟶
C
!
tokensTs, Loc, Where, End
201tokensTs, Loc, Where, End ⟶
/, *
!
block_commentLoc, Loc1
tokensTs, Loc1, Where, End
205tokensTs, Loc, _, End ⟶
/, /
!
tokensTs, Loc, mid, End
209tokenstokT, Loc|Ts, Loc, _, End ⟶
tokenT, Loc
!
tokensTs, Loc, mid, End
212tokens[], Loc, _, Loc ⟶
[]
213tokens_, Loc, _, _ ⟶
C
loc_errorLoc, 'unexpected character ~q', C
217loc_next_lineFile:N, File:N1 ←
N1 is N + 1
blank' ''\t''\r''\f''\v'

line_rest//: the rest of the line, leaving its newline in the input.

226line_rest, '\n' ⟶
'\n'
!
227line_rest ⟶
228line_rest ⟶
[]
block_comment+Loc0, -Loc//

the rest of a comment after its /*; Loc is Loc0 advanced past the comment's newlines.

232block_commentLoc, Loc ⟶
*, /
!
233block_commentLoc0, Loc ⟶
'\n'
!
loc_next_lineLoc0, Loc1
block_commentLoc1, Loc
237block_commentLoc0, Loc ⟶
_
!
block_commentLoc0, Loc
240block_commentLoc, _ ⟶
loc_errorLoc, 'unterminated comment', []
directive+Loc0, -Loc//

a directive line after its #; Loc is the location of the line that ends it. A line marker # N "file" flags or #line N "file" says the next line is line N of file. Any other directive is skipped.

246directiveLoc0, Loc ⟶
"line"
→
else
[]
number_charsN, Ds
N0 is N−1
→
'"'
→
atom_charsFile, Fs
else
Loc0 = File:_
Loc = File:N0
else
Loc = Loc0
pack_line-Cs//

the rest of a #pragma pack line, before its newline.

261pack_line[], '\n' ⟶
'\n'
!
262pack_lineC|Cs ⟶
263pack_line[] ⟶
[]
pack_arg
265tokidA, _A
266tokpA, _A
267tokintN, _, _N
pragma_weak-T//

pragma weak Name or pragma weak Name = Target after the line's #, up to its newline.

271pragma_weakpragma_weakName, Target ⟶
"pragma"
C
"weak"
D
else
Target = none
276pragma_end, '\n' ⟶
'\n'
!
277pragma_end[], []
278identifierName ⟶
C
atom_charsName, C|Cs
281marker_file[] ⟶
'"'
!
282marker_fileC|Cs ⟶
284spaces ⟶
287spaces ⟶
[]
289digits1D|Ds ⟶
291digits0D|Ds ⟶
294digits0[] ⟶
[]

Character classes as ASCII ranges: char_type/2's classes differ between Prolog hosts, and C and IR source only need these.

decimal+Csemidet

C is a decimal digit character, '0' to '9'.

302decimalD ←
D @>= '0'
D @=< '9'
304letterC ←
C @>= a
C @=< z
→
true
else
C @>= 'A'
C @=< 'Z'

Tokens

token-T, +Loc//

one token, the T of a tok(T, Loc) term. Wide and u8 prefixes are tried before identifiers, which they would start.

310tokenstrBs, Loc ⟶
u, '8', '"'
!
str_bodyLoc, Bs
313tokenchrV, Loc ⟶
P, '\''
!
wide_lit_chars'\'', Loc, Vs
wide_char_valueVs, Type, V, Loc
317tokenwstrType, Vs, Loc ⟶
P, '"'
!
wide_lit_chars'"', Loc, Vs
wide_fitsType, Vs, Loc
321tokenT, _ ⟶
C
!
atom_charsA, C|Cs
→
T = kwK
else
T = idA
327tokenT, Loc ⟶
C
!
numberC, T, Loc
330tokenT, Loc ⟶
'.', D
!
number'.', D, T, Loc
333tokenchrV, Loc ⟶
'\''
!
char_bodyLoc, Bs
char_valueBs, V, Loc
337tokenstrBs, Loc ⟶
'"'
!
str_bodyLoc, Bs
340tokenpP, _ ⟶
ident_start+Csemidet

C may begin an identifier: an ASCII letter, '_' or '$' (a GNU extension).

346ident_startC ←
347ident_start'_'
348ident_start$
ident_rest-Cs//det

Cs is the longest run of identifier characters (ident_char/1) next in the input.

354ident_restC|Cs ⟶
357ident_rest[] ⟶
[]
ident_char+Csemidet

C may continue an identifier: an ASCII letter, a digit, '_' or '$'.

362ident_charC ←
363ident_char'_'
364ident_char$

Numbers

number+Prefix, -T, +Loc//

T is the int or float token of the pp-number that starts with the already-read characters Prefix.

370numberCs0, T, Loc ⟶
appendCs0, Cs1, Cs
number_tokenCs, T, Loc
376pp_number_restE, S|Cs ⟶
E, S
memberchkE, e, 'E', p, 'P'
memberchkS, +, -
!
380pp_number_restC|Cs ⟶
C
or
C ≡ '.'
!
383pp_number_rest[] ⟶
[]
number_token+Chars, -T, +Loc

T is int(Value, Suffix-Radix) or float(Chars) for the pp-number Chars; anything else is an error.

387number_tokenCs, T, Loc ←
phraseinteger_literalV, Suffix, Cs
→
T = intV, Suffix
phrasefloat_chars, Cs
→
T = floatCs
else
atom_charsA, Cs
loc_errorLoc, 'bad number ~w', A
396integer_literalV, Suffix−Radix ⟶
'0', X
memberchkX, x, 'X'
→
Ds ≢ []
base_valueDs, 16, V
Radix = other
'0', X
memberchkX, b, 'B'
→
A binary constant (C23 6.4.4.1, a GNU extension before it;
PureDOOM's MUS decoder masks events with 0b01110000) is typed
as an octal or hexadecimal one is.
Ds ≢ []
base_valueDs, 2, V
Radix = other
'0'
→
base_valueDs, 8, V
Radix = other
else
Ds ≢ []
base_valueDs, 10, V
Radix = dec
414hex_digitsD|Ds ⟶
417hex_digits[] ⟶
[]
419bin_digitsD|Ds ⟶
D
memberchkD, '0', '1'
!
422bin_digits[] ⟶
[]
424oct_digitsD|Ds ⟶
D
D @< '8'
!
427oct_digits[] ⟶
[]
429dec_digitsD|Ds ⟶
432dec_digits[] ⟶
[]
base_value+Digits, +Base, -Value

Value is the number Digits spell in Base.

436base_valueDs, Base, V ←
foldladd_digitBase, Ds, 0, V
438add_digitBase, D, V0, V ←
V is V0 × Base + W
digit_weightChar, Weight

the value of a hexadecimal digit.

443digit_weightD, W ←
sub_atom'0123456789abcdef', W, 1, _, D
!
444digit_weightD, W ←
sub_atom'ABCDEF', W0, 1, _, D
W is W0 + 10
int_suffix-Suffix//

Suffix names the rest of the pp-number, an integer suffix in either case and order (u, l, ll, ul, ull or none). It fails if the rest is not such a suffix.

449int_suffixSuffix ⟶
maplistsuffix_lower, Cs, Ls
atom_charsA, Ls
suffix_nameA, Suffix
456suffix_charsC|Cs ⟶
459suffix_chars[] ⟶
[]
461suffix_lowerC, L ←
sub_atom'ULX', I, 1, _, C
→
sub_atomulx, I, 1, _, L
else
L = C
suffix_name
467''none
468uu
469ll
470llll
471ulul
472luul
473ullull
474lluull

Only the characters are checked here; the lowering rejects floats.

477float_chars ⟶
C
or
memberchkC, '.', e, 'E', +, -, f, 'F', l, 'L', x, 'X', p, 'P'
or
!
484float_chars ⟶
[]

Character and string bodies: lists of byte values.

488char_bodyLoc, [] ⟶
'\''
!
loc_errorLoc, 'empty character constant', []
491char_bodyLoc, Bs ⟶
lit_chars'\'', Loc, Bs
493str_bodyLoc, Bs ⟶
lit_chars'"', Loc, Bs
lit_chars+Quote, +Loc, -Bytes//

Bytes is the literal body up to the closing Quote, with escapes decoded. The source is read as bytes (main and c_pp read files so), and a narrow literal keeps them as written, as clang does, valid UTF-8 or not (oniguruma's EUC-JP tests); a code point above 255, from text that was not read as bytes, is encoded as UTF-8.

501lit_charsQ, _, [] ⟶
Q
!
502lit_chars_, Loc, _ ⟶
'\n'
!
loc_errorLoc, 'unterminated literal', []
505lit_charsQ, Loc, Bs ⟶
C11 6.4.3: \u, \U
\
!
utf8_bytesK, Usclang's execution character set is UTF-8
appendUs, Bs0, Bs
lit_charsQ, Loc, Bs0
510lit_charsQ, Loc, B|Bs ⟶
\
!
escapeLoc, B
lit_charsQ, Loc, Bs
514lit_charsQ, Loc, Bs ⟶
C
!
char_codeC, K
K < 256
→
Bs = K|Bs0
else
appendUs, Bs0, Bs
lit_charsQ, Loc, Bs0
518lit_chars_, Loc, _ ⟶
loc_errorLoc, 'unterminated literal', []
escape+Loc, -Byte//

Byte is the value of an escape after its backslash, reduced to 8 bits.

522escape_, B ⟶
523escape_, B ⟶
x
!
Ds ≢ []
base_valueDs, 16, V
B is V /\ 255
527escape_, B ⟶
D
D @< '8'
!
base_valueD|Ds, 8, V
B is V /\ 255

An unknown escape is the character itself, as clang has it with only a warning (C11 6.4.4.4p8 leaves it undefined): btrfs-progs prints "\(".

534escape_, B ⟶
C
C ≢ '\n'
char_codeC, B
!
535escapeLoc, _ ⟶
loc_errorLoc, 'unterminated literal', []
ucn-Code//

a universal character name after its backslash.

538ucnK ⟶
u
base_valueDs, 16, K
539ucnK ⟶
'U'
base_valueDs, 16, K
oct_upto+N, -Digits//

Digits are at most N octal digits, as many as there are.

543oct_upto0, [] ⟶
!
544oct_uptoN, D|Ds ⟶
D
D @< '8'
!
N1 is N−1
oct_uptoN1, Ds
548oct_upto_, [] ⟶
[]
simple_escape
550n10
551t9
552r13
553a7
554b8
555f12
556v11
557\92
558'\''39
559'"'34
560?63
561e27

GNU: ESC

562'E'27

Wide literals: elements are code points, not UTF-8 bytes.

wide_prefix
566'L'int
567uushort
568'U'uint
wide_lit_chars+Quote, +Loc, -Codes//

like lit_chars//3, but Codes are the body's code points: the source's bytes decoded as UTF-8 (a byte that starts no valid sequence stands for itself).

573wide_lit_charsQ, _, [] ⟶
Q
!
574wide_lit_chars_, Loc, _ ⟶
'\n'
!
loc_errorLoc, 'unterminated literal', []
577wide_lit_charsQ, Loc, V|Vs ⟶
\
!
wide_lit_charsQ, Loc, Vs
581wide_lit_charsQ, Loc, K|Vs ⟶
584wide_lit_charsQ, Loc, K|Vs ⟶
C
!
char_codeC, K
wide_lit_charsQ, Loc, Vs
588wide_lit_chars_, Loc, _ ⟶
loc_errorLoc, 'unterminated literal', []
591wide_escape_, B ⟶
592wide_escape_, V ⟶
x
!
Ds ≢ []
base_valueDs, 16, V
596wide_escape_, V ⟶
u
!
base_valueDs, 16, V
600wide_escape_, V ⟶
'U'
!
base_valueDs, 16, V
604wide_escape_, V ⟶
D
D @< '8'
!
base_valueD|Ds, 8, V
608wide_escape_, V ⟶
V ≠ 10
!
609wide_escape_, V ⟶
C
C ≢ '\n'
char_codeC, V
!
610wide_escapeLoc, _ ⟶
loc_errorLoc, 'unterminated literal', []
hex_exactly+N, -Digits//

Digits are exactly N hexadecimal digits.

613hex_exactly0, [] ⟶
!
614hex_exactlyN, D|Ds ⟶
D
W < 16
!
N1 is N−1
wide_fits+Type, +Codes, +Loc

each element of Codes fits Type (no surrogate pairs for char16_t); an error otherwise.

621wide_fitsType, Vs, Loc ←
wide_maxType, Max
memberV, Vs
V > Max
→
loc_errorLoc, 'wide literal element ~w does not fit ~w', V, Type
else
true
wide_max
627int2147483647
628ushort65535
629uint4294967295
wide_char_value+Codes, +Type, -Value, +Loc

Value is the single element of a wide character constant, as an int; wchar_t is int here.

633wide_char_value[], _, _, Loc ←
634wide_char_valueV|Vs, Type, Value, Loc ←
wide_char_valueVs, V, Type, Value, Loc
wide_char_value+Codes, +V, +Type, -Value, +Loc

like wide_char_value/4 for [V|Codes].

639wide_char_value[], V, Type, V, Loc ←
char_fit_typeType, FitType
wide_fitsFitType, V, Loc
642wide_char_value_|_, _, _, _, Loc ←
char_fit_type?Type, ?FitType

a wide character constant of element type Type must fit FitType; U'x' has type char32_t but value as int.

char_fit_type
646intint
647ushortushort
648uintint
char_value+Bytes, -Value, +Loc

Value is the single byte of a character constant, as a (signed) char promoted to int. A multi-character constant is rejected.

653char_value[], _, Loc ←
654char_valueB|Bs, V, Loc ←
char_valueBs, B, V, Loc
char_value+Bytes, +B, -Value, +Loc

like char_value/3 for [B|Bytes].

657char_value[], B, V, _ ←
B ≥ 128
→
V is B−256
else
V = B
662char_value_|_, _, _, Loc ←
664multi_char_errorLoc ←
loc_errorLoc, 'multi-character constant', []
utf8_char-Code//

the next bytes are the UTF-8 encoding of Code, of two to four bytes.

668utf8_charK ⟶
C0
char_codeC0, B0
B0 ≥ 0xC2
B0 ≤ 0xF4
B0 < 0xE0
→
N = 1
K0 is B0 /\ 0x1F
B0 < 0xF0
→
N = 2
K0 is B0 /\ 0x0F
else
N = 3
K0 is B0 /\ 0x07
lengthBs, L
L =:= N + 1

not overlong

676utf8_continuation0, K, K ⟶
!
677utf8_continuationN, K0, K ⟶
C
char_codeC, B
B /\ 0xC0 =:= 0x80
K1 is K0 << 6 \/ B /\ 0x3F
N1 is N−1
utf8_bytes+Code, -Bytes

Bytes is the UTF-8 encoding of Code.

683utf8_bytesK, K ←
K < 128
!
684utf8_bytesK, B1, B2 ←
K < 2048
!
B1 is 192 \/ K >> 6
B2 is 128 \/ K /\ 63
688utf8_bytesK, B1, B2, B3 ←
K < 65536
!
B1 is 224 \/ K >> 12
B2 is 128 \/ K >> 6 /\ 63
B3 is 128 \/ K /\ 63
693utf8_bytesK, B1, B2, B3, B4 ←
B1 is 240 \/ K >> 18
B2 is 128 \/ K >> 12 /\ 63
B3 is 128 \/ K >> 6 /\ 63
B4 is 128 \/ K /\ 63

Punctuators, longest first.

701punctP ⟶
A, B, C
atom_charsP, A, B, C
!
703punctP ⟶
A, B
atom_charsP, A, B
!
705punctP ⟶
C
memberchkC, '[', ']', '(', ')', '{', '}', '.', &, *, +, -, ~, !, /, '%', <, >, ^, ('|'), ?, :, (;), =, (','), #
atom_charsP, C
punctuator?Spellingnondet

Spelling is one of C's punctuators of two or three characters (C11 6.4.6), digraphs aside; the one-character ones are punct//1's.

punctuator...<<=>>=(->)++--<<>><=>==='!='&&'||'*=/='%='+=-=&=^='|='##
digraph?Spellingnondet

Spelling is a digraph (C11 6.4.6p3), a preprocessing token that stands for [, ], {, } or #.

digraph<::>'<%''%>''%:'

Keywords, with GNU alternate spellings folded.

keyword?Spelling, ?Keywordnondet
keyword+Spelling, -Keywordsemidet

Keyword is the kw/1 name of the C or GNU keyword spelled Spelling.

keyword
736autoauto
737breakbreak
738casecase
739charchar
740constconst
741continuecontinue
742defaultdefault
743dodo
744doubledouble
745elseelse
746enumenum
747externextern
748floatfloat
749forfor
750gotogoto
751ifif
752inlineinline
753intint
754longlong
755registerregister
756restrictrestrict
757returnreturn
758shortshort
759signedsigned
760sizeofsizeof
761staticstatic
762structstruct
763switchswitch
764typedeftypedef
765unionunion
766unsignedunsigned
767voidvoid
768(volatile)(volatile)
769whilewhile
770'_Alignas'alignas
771'_Alignof'alignof
772'_Atomic'atomic
773'_Bool'bool
774'_Complex'complex
775'_Generic'generic
776'_Imaginary'imaginary
777'_Noreturn'noreturn
778'_Static_assert'static_assert
779'_Thread_local'(thread_local)
780'__restrict'restrict
781'__restrict__'restrict
782'__inline'inline
783'__inline__'inline
784'__const'const
785'__const__'const
786'__volatile'(volatile)
787'__volatile__'(volatile)
788'__signed'signed
789'__signed__'signed
790asmasm
791'__asm'asm
792'__asm__'asm
793'__attribute'attribute
794'__attribute__'attribute
795'__extension__'extension
796'__auto_type'auto_type
797'__typeof'typeof
798'__typeof__'typeof
799typeoftypeof
800'__alignof'alignof
801'__alignof__'alignof
802'__int128'int128
803'_Float16'float16
804'__bf16'bf16
805'__thread'(thread_local)
c23_tokens+Tokens0, -Tokensdet

Tokens are Tokens0 read as C23, where bool, true, false, nullptr, static_assert, alignas, alignof and thread_local are keywords (C23 6.4.1p1); C17 spells the type-like ones _Bool, _Static_assert, ... and has no true, false or nullptr keywords. main applies it when NANOFIL_STD is c23 (nanofil-cc sets it for -std=c23 or gnu23).

814c23_tokensTs0, Ts ←
maplistc23_token, Ts0, Ts
816c23_tokentokidA, Loc, tokkwK, Loc ←
817c23_tokenT, T
c23_keyword
819boolbool
820truetrue
821falsefalse
822nullptrnullptr
823static_assertstatic_assert
824alignasalignas
825alignofalignof
826(thread_local)(thread_local)
loc_error+Loc, +Format, +Args

throw a nanofil_error/1 located at Loc.

829loc_errorFile:Line, Fmt, Args ←
formatatomMsg0, Fmt, Args
formatatomMsg, '~w:~w: ~w', File, Line, Msg0
thrownanofil_errorMsg