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 ⟪__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), and each #pragma weak inside braces moved to the end, where it is a declaration of the unit: GCC applies one in a function body to the file-scope name, as glibc's td_ta_map_lwp2thr writes #pragma weak ps_get_thread_area in a case. Tokens0 is not copied when it has no pragma (GLib's resource blobs are millions of tokens).

116pack_tokensTokens0, Tokens ←
memberchktokpragma_pack_, _, Tokens0
→
oncephrasepacked_tokensTokens0, none, Tokens1
else
Tokens1 = Tokens0
memberchktokpragma_weak_, _, _, Tokens1
→
weak_walkTokens1, 0, Tokens, Hoisted, Hoisted
else
Tokens = Tokens1
weak_walk+Ts0, +Depth, -Ts, -Tail, -Hoisted

Ts is Ts0, at brace depth Depth, without its #pragma weak tokens inside braces, ending in Tail; Hoisted are those tokens, in order.

129weak_walk[], _, Tail, Tail, []
130weak_walkT|Ts0, D, Ts, Tail, T|Hs ←
T = tokpragma_weak_, _, _
D > 0
!
weak_walkTs0, D, Ts, Tail, Hs
133weak_walkT|Ts0, D0, T|Ts, Tail, Hs ←
T = tokp'{', _
→
D is D0 + 1
T = tokp'}', _
→
D is D0−1
else
D = D0
weak_walkTs0, D, Ts, Tail, Hs
140packed_tokens[], _ ⟶
[]
141packed_tokenstokpragma_packOp, _|Ts, Packs0 ⟶
!
pack_stackOp, Packs0, Packs
packed_tokensTs, Packs
144packed_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
150packed_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.

158pack_stacksetN, _|S, N|S
159pack_stackpushL, keep, C|S, C, sL, C|S
160pack_stackpushL, N, C|S, N, sL, C|S ←
N ≢ keep
161pack_stackpopnone, _|S, Packs ←
S = s_, V|S1
→
Packs = V|S1
else
Packs = none
163pack_stackpopL, C|S, Packs ←
L ≢ none
append_, sL, V|S1, S
→
Packs = V|S1
else
Packs = C|S
166pack_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.

176pack_op[], setnone ←
!
177pack_oppush, pushnone, keep ←
!
178pack_oppop, popnone ←
!
179pack_opshow, show ←
!
180pack_oppush, (','), N, pushnone, N ←
integerN
!
181pack_oppush, (','), L, pushL, keep ←
182pack_oppush, (','), L, (','), N, pushL, N ←
183pack_oppop, (','), L, popL ←
184pack_opN, setN ←
186pack_valueN ←
memberchkN, 1, 2, 4, 8, 16
187pack_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.

195tokensTs, Loc, Where ⟶
tokensTs, Loc, Where, _
197tokensTs, Loc, _, End ⟶
'\n'
!
loc_next_lineLoc, Loc1
tokensTs, Loc1, bol, End
201tokenstokT, Loc|Ts, Loc, bol, End ⟶
#
!
tokensTs, Loc, mid, End
204tokenstokpragma_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
216tokensTs, Loc, bol, End ⟶
#
!
directiveLoc, Loc1
tokensTs, Loc1, bol, End
220tokensTs, Loc, Where, End ⟶
C
!
tokensTs, Loc, Where, End
223tokensTs, Loc, Where, End ⟶
/, *
!
block_commentLoc, Loc1
tokensTs, Loc1, Where, End
227tokensTs, Loc, _, End ⟶
/, /
!
tokensTs, Loc, mid, End
231tokenstokT, Loc|Ts, Loc, _, End ⟶
tokenT, Loc
!
tokensTs, Loc, mid, End
234tokens[], Loc, _, Loc ⟶
[]
235tokens_, Loc, _, _ ⟶
C
loc_errorLoc, 'unexpected character ~q', C
239loc_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.

248line_rest, '\n' ⟶
'\n'
!
249line_rest ⟶
250line_rest ⟶
[]
block_comment+Loc0, -Loc//

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

254block_commentLoc, Loc ⟶
*, /
!
255block_commentLoc0, Loc ⟶
'\n'
!
loc_next_lineLoc0, Loc1
block_commentLoc1, Loc
259block_commentLoc0, Loc ⟶
_
!
block_commentLoc0, Loc
262block_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.

268directiveLoc0, 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.

283pack_line[], '\n' ⟶
'\n'
!
284pack_lineC|Cs ⟶
285pack_line[] ⟶
[]
pack_arg
287tokidA, _A
288tokpA, _A
289tokintN, _, _N
pragma_weak-T//

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

293pragma_weakpragma_weakName, Target ⟶
"pragma"
C
"weak"
D
else
Target = none
298pragma_end, '\n' ⟶
'\n'
!
299pragma_end[], []
300identifierName ⟶
C
atom_charsName, C|Cs
303marker_file[] ⟶
'"'
!
304marker_fileC|Cs ⟶
306spaces ⟶
309spaces ⟶
[]
311digits1D|Ds ⟶
313digits0D|Ds ⟶
316digits0[] ⟶
[]

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

324decimalD ←
D @>= '0'
D @=< '9'
326letterC ←
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.

332tokenstrBs, Loc ⟶
u, '8', '"'
!
str_bodyLoc, Bs
335tokenchrV, Loc ⟶
P, '\''
!
wide_lit_chars'\'', Loc, Vs
wide_char_valueVs, Type, V, Loc
339tokenwstrType, Vs, Loc ⟶
P, '"'
!
wide_lit_chars'"', Loc, Vs
wide_fitsType, Vs, Loc
343tokenT, _ ⟶
C
!
atom_charsA, C|Cs
→
T = kwK
else
T = idA
349tokenT, Loc ⟶
C
!
numberC, T, Loc
352tokenT, Loc ⟶
'.', D
!
number'.', D, T, Loc
355tokenchrV, Loc ⟶
'\''
!
char_bodyLoc, Bs
char_valueBs, V, Loc
359tokenstrBs, Loc ⟶
'"'
!
str_bodyLoc, Bs
362tokenpP, _ ⟶
ident_start+Csemidet

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

368ident_startC ←
369ident_start'_'
370ident_start$
ident_rest-Cs//det

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

376ident_restC|Cs ⟶
379ident_rest[] ⟶
[]
ident_char+Csemidet

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

384ident_charC ←
385ident_char'_'
386ident_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.

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

T is int(Value, Suffix-Radix), float(Chars) or, for a floating constant with GNU's imaginary suffix i or j before or after its f or l (1.0iF, glibc's _Complex_I), imag(float(Chars)) without it, for the pp-number Chars; anything else is an error.

412number_tokenCs, T, Loc ←
phraseinteger_literalV, Suffix, Cs
→
T = intV, Suffix
¬phraseinteger_literal_, _, Cs1
phrasefloat_chars, Cs1
→
T = imagfloatCs1
phrasefloat_chars, Cs
→
T = floatCs
else
atom_charsA, Cs
loc_errorLoc, 'bad number ~w', A
424imaginary_charsCs, Cs1 ←
appendBody, I, Cs
memberchkI, i, 'I', j, 'J'
!
Cs1 = Body
427imaginary_charsCs, Cs1 ←
appendBody, I, S, Cs
memberchkI, i, 'I', j, 'J'
memberchkS, f, 'F', l, 'L'
appendBody, S, Cs1
432integer_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
450hex_digitsD|Ds ⟶
453hex_digits[] ⟶
[]
455bin_digitsD|Ds ⟶
D
memberchkD, '0', '1'
!
458bin_digits[] ⟶
[]
460oct_digitsD|Ds ⟶
D
D @< '8'
!
463oct_digits[] ⟶
[]
465dec_digitsD|Ds ⟶
468dec_digits[] ⟶
[]
base_value+Digits, +Base, -Value

Value is the number Digits spell in Base.

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

the value of a hexadecimal digit.

479digit_weightD, W ←
sub_atom'0123456789abcdef', W, 1, _, D
!
480digit_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.

485int_suffixSuffix ⟶
maplistsuffix_lower, Cs, Ls
atom_charsA, Ls
suffix_nameA, Suffix
492suffix_charsC|Cs ⟶
495suffix_chars[] ⟶
[]
497suffix_lowerC, L ←
sub_atom'ULX', I, 1, _, C
→
sub_atomulx, I, 1, _, L
else
L = C
suffix_name
503''none
504uu
505ll
506llll
507ulul
508luul
509ullull
510lluull

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

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

Character and string bodies: lists of byte values.

524char_bodyLoc, [] ⟶
'\''
!
loc_errorLoc, 'empty character constant', []
527char_bodyLoc, Bs ⟶
lit_chars'\'', Loc, Bs
529str_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.

537lit_charsQ, _, [] ⟶
Q
!
538lit_chars_, Loc, _ ⟶
'\n'
!
loc_errorLoc, 'unterminated literal', []
541lit_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
546lit_charsQ, Loc, B|Bs ⟶
\
!
escapeLoc, B
lit_charsQ, Loc, Bs
550lit_charsQ, Loc, Bs ⟶
C
!
char_codeC, K
K < 256
→
Bs = K|Bs0
else
appendUs, Bs0, Bs
lit_charsQ, Loc, Bs0
554lit_chars_, Loc, _ ⟶
loc_errorLoc, 'unterminated literal', []
escape+Loc, -Byte//

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

558escape_, B ⟶
559escape_, B ⟶
x
!
Ds ≢ []
base_valueDs, 16, V
B is V /\ 255
563escape_, 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 "\(".

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

a universal character name after its backslash.

574ucnK ⟶
u
base_valueDs, 16, K
575ucnK ⟶
'U'
base_valueDs, 16, K
oct_upto+N, -Digits//

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

579oct_upto0, [] ⟶
!
580oct_uptoN, D|Ds ⟶
D
D @< '8'
!
N1 is N−1
oct_uptoN1, Ds
584oct_upto_, [] ⟶
[]
simple_escape
586n10
587t9
588r13
589a7
590b8
591f12
592v11
593\92
594'\''39
595'"'34
596?63
597e27

GNU: ESC

598'E'27

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

wide_prefix
602'L'int
603uushort
604'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).

609wide_lit_charsQ, _, [] ⟶
Q
!
610wide_lit_chars_, Loc, _ ⟶
'\n'
!
loc_errorLoc, 'unterminated literal', []
613wide_lit_charsQ, Loc, V|Vs ⟶
\
!
wide_lit_charsQ, Loc, Vs
617wide_lit_charsQ, Loc, K|Vs ⟶
620wide_lit_charsQ, Loc, K|Vs ⟶
C
!
char_codeC, K
wide_lit_charsQ, Loc, Vs
624wide_lit_chars_, Loc, _ ⟶
loc_errorLoc, 'unterminated literal', []
627wide_escape_, B ⟶
628wide_escape_, V ⟶
x
!
Ds ≢ []
base_valueDs, 16, V
632wide_escape_, V ⟶
u
!
base_valueDs, 16, V
636wide_escape_, V ⟶
'U'
!
base_valueDs, 16, V
640wide_escape_, V ⟶
D
D @< '8'
!
base_valueD|Ds, 8, V
644wide_escape_, V ⟶
V ≠ 10
!
645wide_escape_, V ⟶
C
C ≢ '\n'
char_codeC, V
!
646wide_escapeLoc, _ ⟶
loc_errorLoc, 'unterminated literal', []
hex_exactly+N, -Digits//

Digits are exactly N hexadecimal digits.

649hex_exactly0, [] ⟶
!
650hex_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.

657wide_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
663int2147483647
664ushort65535
665uint4294967295
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.

669wide_char_value[], _, _, Loc ←
670wide_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].

675wide_char_value[], V, Type, V, Loc ←
char_fit_typeType, FitType
wide_fitsFitType, V, Loc
678wide_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
682intint
683ushortushort
684uintint
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.

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

like char_value/3 for [B|Bytes].

693char_value[], B, V, _ ←
B ≥ 128
→
V is B−256
else
V = B
698char_value_|_, _, _, Loc ←
700multi_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.

704utf8_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

712utf8_continuation0, K, K ⟶
!
713utf8_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.

719utf8_bytesK, K ←
K < 128
!
720utf8_bytesK, B1, B2 ←
K < 2048
!
B1 is 192 \/ K >> 6
B2 is 128 \/ K /\ 63
724utf8_bytesK, B1, B2, B3 ←
K < 65536
!
B1 is 224 \/ K >> 12
B2 is 128 \/ K >> 6 /\ 63
B3 is 128 \/ K /\ 63
729utf8_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.

737punctP ⟶
A, B, C
atom_charsP, A, B, C
!
739punctP ⟶
A, B
atom_charsP, A, B
!
741punctP ⟶
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
772autoauto
773breakbreak
774casecase
775charchar
776constconst
777continuecontinue
778defaultdefault
779dodo
780doubledouble
781elseelse
782enumenum
783externextern
784floatfloat
785forfor
786gotogoto
787ifif
788inlineinline
789intint
790longlong
791registerregister
792restrictrestrict
793returnreturn
794shortshort
795signedsigned
796sizeofsizeof
797staticstatic
798structstruct
799switchswitch
800typedeftypedef
801unionunion
802unsignedunsigned
803voidvoid
804(volatile)(volatile)
805whilewhile
806'_Alignas'alignas
807'_Alignof'alignof
808'_Atomic'atomic
809'_Bool'bool
810'_Complex'complex
811'_Generic'generic
812'_Imaginary'imaginary
813'__real__'real
814'__real'real
815'__imag__'imag
816'__imag'imag
817'_Noreturn'noreturn
818'_Static_assert'static_assert
819'_Thread_local'(thread_local)
820'__restrict'restrict
821'__restrict__'restrict
822'__inline'inline
823'__inline__'inline
824'__const'const
825'__const__'const
826'__volatile'(volatile)
827'__volatile__'(volatile)
828'__signed'signed
829'__signed__'signed
830asmasm
831'__asm'asm
832'__asm__'asm
833'__attribute'attribute
834'__attribute__'attribute
835'__extension__'extension
836'__auto_type'auto_type
837'__typeof'typeof
838'__typeof__'typeof
839typeoftypeof
840'__alignof'alignof
841'__alignof__'alignof
842'__int128'int128
843'_Float16'float16
844'__bf16'bf16
845'__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).

854c23_tokensTs0, Ts ←
maplistc23_token, Ts0, Ts
856c23_tokentokidA, Loc, tokkwK, Loc ←
857c23_tokenT, T
c23_keyword
859boolbool
860truetrue
861falsefalse
862nullptrnullptr
863static_assertstatic_assert
864alignasalignas
865alignofalignof
866(thread_local)(thread_local)
loc_error+Loc, +Format, +Args

throw a nanofil_error/1 located at Loc.

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