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; any other directive line, such as #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<<=
useslists
usesdcgs
usescharsio
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.

38c_tokensCs, File, Tokens ←
phrasetokensTokens, File:1, bol, Cs
→
true
else
thrownanofil_error'C lexer failed'

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.

49tokensTs, Loc, _ ⟶
'\n'
!
loc_next_lineLoc, Loc1
tokensTs, Loc1, bol
53tokensTs, Loc, bol ⟶
#
!
directiveLoc, Loc1
tokensTs, Loc1, bol
57tokensTs, Loc, Where ⟶
C
!
tokensTs, Loc, Where
60tokensTs, Loc, Where ⟶
/, *
!
block_commentLoc, Loc1
tokensTs, Loc1, Where
64tokensTs, Loc, _ ⟶
/, /
!
tokensTs, Loc, mid
68tokenstokT, Loc|Ts, Loc, _ ⟶
tokenT, Loc
!
tokensTs, Loc, mid
71tokens[], _, _ ⟶
[]
72tokens_, Loc, _ ⟶
C
loc_errorLoc, 'unexpected character ~q', C
76loc_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.

85line_rest, '\n' ⟶
'\n'
!
86line_rest ⟶
87line_rest ⟶
[]
block_comment+Loc0, -Loc//

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

91block_commentLoc, Loc ⟶
*, /
!
92block_commentLoc0, Loc ⟶
'\n'
!
loc_next_lineLoc0, Loc1
block_commentLoc1, Loc
96block_commentLoc0, Loc ⟶
_
!
block_commentLoc0, Loc
99block_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.

105directiveLoc0, Loc ⟶
"line"
→
else
[]
number_charsN, Ds
N0 is N−1
→
'"'
→
atom_charsFile, Fs
else
Loc0 = File:_
Loc = File:N0
else
Loc = Loc0
119marker_file[] ⟶
'"'
!
120marker_fileC|Cs ⟶
122spaces ⟶
125spaces ⟶
[]
127digits1D|Ds ⟶
129digits0D|Ds ⟶
132digits0[] ⟶
[]

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

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

144tokenstrBs, Loc ⟶
u, '8', '"'
!
str_bodyLoc, Bs
147tokenchrV, Loc ⟶
P, '\''
!
wide_lit_chars'\'', Loc, Vs
wide_char_valueVs, Type, V, Loc
151tokenwstrType, Vs, Loc ⟶
P, '"'
!
wide_lit_chars'"', Loc, Vs
wide_fitsType, Vs, Loc
155tokenT, _ ⟶
C
!
atom_charsA, C|Cs
→
T = kwK
else
T = idA
161tokenT, Loc ⟶
C
!
numberC, T, Loc
164tokenT, Loc ⟶
'.', D
!
number'.', D, T, Loc
167tokenchrV, Loc ⟶
'\''
!
char_bodyLoc, Bs
char_valueBs, V, Loc
171tokenstrBs, Loc ⟶
'"'
!
str_bodyLoc, Bs
174tokenpP, _ ⟶
176ident_startC ←
177ident_start'_'
178ident_start$
180ident_restC|Cs ⟶
183ident_rest[] ⟶
[]
185ident_charC ←
186ident_char'_'
187ident_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.

193numberCs0, T, Loc ⟶
appendCs0, Cs1, Cs
number_tokenCs, T, Loc
199pp_number_restE, S|Cs ⟶
E, S
memberchkE, e, 'E', p, 'P'
memberchkS, +, -
!
203pp_number_restC|Cs ⟶
C
or
C ≡ '.'
!
206pp_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.

210number_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
219integer_literalV, Suffix−Radix ⟶
'0', X
memberchkX, x, 'X'
→
Ds ≢ []
base_valueDs, 16, V
Radix = other
'0'
→
base_valueDs, 8, V
Radix = other
else
Ds ≢ []
base_valueDs, 10, V
Radix = dec
231hex_digitsD|Ds ⟶
234hex_digits[] ⟶
[]
236oct_digitsD|Ds ⟶
D
D @< '8'
!
239oct_digits[] ⟶
[]
241dec_digitsD|Ds ⟶
244dec_digits[] ⟶
[]
base_value+Digits, +Base, -Value

Value is the number Digits spell in Base.

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

the value of a hexadecimal digit.

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

261int_suffixSuffix ⟶
maplistsuffix_lower, Cs, Ls
atom_charsA, Ls
suffix_nameA, Suffix
268suffix_charsC|Cs ⟶
271suffix_chars[] ⟶
[]
273suffix_lowerC, L ←
sub_atom'ULX', I, 1, _, C
→
sub_atomulx, I, 1, _, L
else
L = C
suffix_name
279''none
280uu
281ll
282llll
283ulul
284luul
285ullull
286lluull

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

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

Character and string bodies: lists of byte values.

300char_bodyLoc, [] ⟶
'\''
!
loc_errorLoc, 'empty character constant', []
303char_bodyLoc, Bs ⟶
lit_chars'\'', Loc, Bs
305str_bodyLoc, Bs ⟶
lit_chars'"', Loc, Bs
lit_chars+Quote, +Loc, -Bytes//

Bytes is the literal body up to the closing Quote, with escapes decoded and other characters encoded as UTF-8.

310lit_charsQ, _, [] ⟶
Q
!
311lit_chars_, Loc, _ ⟶
'\n'
!
loc_errorLoc, 'unterminated literal', []
314lit_charsQ, Loc, Bs ⟶
\
!
escapeLoc, B
lit_charsQ, Loc, Bs0
Bs = B|Bs0
319lit_charsQ, Loc, Bs ⟶
C
!
char_codeC, K
lit_charsQ, Loc, Bs0
appendUs, Bs0, Bs
324lit_chars_, Loc, _ ⟶
loc_errorLoc, 'unterminated literal', []
escape+Loc, -Byte//

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

328escape_, B ⟶
329escape_, B ⟶
x
!
Ds ≢ []
base_valueDs, 16, V
B is V /\ 255
333escape_, B ⟶
D
D @< '8'
!
base_valueD|Ds, 8, V
B is V /\ 255
337escapeLoc, _ ⟶
C
loc_errorLoc, 'unknown escape \\~w', C
oct_upto+N, -Digits//

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

341oct_upto0, [] ⟶
!
342oct_uptoN, D|Ds ⟶
D
D @< '8'
!
N1 is N−1
oct_uptoN1, Ds
346oct_upto_, [] ⟶
[]
simple_escape
348n10
349t9
350r13
351a7
352b8
353f12
354v11
355\92
356'\''39
357'"'34
358?63
359e27

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

wide_prefix
363'L'int
364uushort
365'U'uint
wide_lit_chars+Quote, +Loc, -Codes//

like lit_chars//3, but Codes are the body's code points.

369wide_lit_charsQ, _, [] ⟶
Q
!
370wide_lit_chars_, Loc, _ ⟶
'\n'
!
loc_errorLoc, 'unterminated literal', []
373wide_lit_charsQ, Loc, V|Vs ⟶
\
!
wide_lit_charsQ, Loc, Vs
377wide_lit_charsQ, Loc, K|Vs ⟶
C
!
char_codeC, K
wide_lit_charsQ, Loc, Vs
381wide_lit_chars_, Loc, _ ⟶
loc_errorLoc, 'unterminated literal', []
384wide_escape_, B ⟶
385wide_escape_, V ⟶
x
!
Ds ≢ []
base_valueDs, 16, V
389wide_escape_, V ⟶
u
!
base_valueDs, 16, V
393wide_escape_, V ⟶
'U'
!
base_valueDs, 16, V
397wide_escape_, V ⟶
D
D @< '8'
!
base_valueD|Ds, 8, V
401wide_escapeLoc, _ ⟶
C
loc_errorLoc, 'unknown escape \\~w', C
hex_exactly+N, -Digits//

Digits are exactly N hexadecimal digits.

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

412wide_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
418int2147483647
419ushort65535
420uint4294967295
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.

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

430wide_char_value[], V, Type, V, Loc ←
char_fit_typeType, FitType
wide_fitsFitType, V, Loc
433wide_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
437intint
438ushortushort
439uintint
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.

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

like char_value/3 for [B|Bytes].

448char_value[], B, V, _ ←
B ≥ 128
→
V is B−256
else
V = B
453char_value_|_, _, _, Loc ←
455multi_char_errorLoc ←
loc_errorLoc, 'multi-character constant', []
utf8_bytes+Code, -Bytes

Bytes is the UTF-8 encoding of Code.

458utf8_bytesK, K ←
K < 128
!
459utf8_bytesK, B1, B2 ←
K < 2048
!
B1 is 192 \/ K >> 6
B2 is 128 \/ K /\ 63
463utf8_bytesK, B1, B2, B3 ←
K < 65536
!
B1 is 224 \/ K >> 12
B2 is 128 \/ K >> 6 /\ 63
B3 is 128 \/ K /\ 63
468utf8_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.

476punctP ⟶
A, B, C
atom_charsP, A, B, C
!
478punctP ⟶
A, B
atom_charsP, A, B
!
480punctP ⟶
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
511autoauto
512breakbreak
513casecase
514charchar
515constconst
516continuecontinue
517defaultdefault
518dodo
519doubledouble
520elseelse
521enumenum
522externextern
523floatfloat
524forfor
525gotogoto
526ifif
527inlineinline
528intint
529longlong
530registerregister
531restrictrestrict
532returnreturn
533shortshort
534signedsigned
535sizeofsizeof
536staticstatic
537structstruct
538switchswitch
539typedeftypedef
540unionunion
541unsignedunsigned
542voidvoid
543(volatile)(volatile)
544whilewhile
545'_Alignas'alignas
546'_Alignof'alignof
547'_Atomic'atomic
548'_Bool'bool
549'_Complex'complex
550'_Generic'generic
551'_Imaginary'imaginary
552'_Noreturn'noreturn
553'_Static_assert'static_assert
554'_Thread_local'(thread_local)
555'__restrict'restrict
556'__restrict__'restrict
557'__inline'inline
558'__inline__'inline
559'__const'const
560'__const__'const
561'__volatile'(volatile)
562'__volatile__'(volatile)
563'__signed'signed
564'__signed__'signed
565asmasm
566'__asm'asm
567'__asm__'asm
568'__attribute'attribute
569'__attribute__'attribute
570'__extension__'extension
571'__typeof'typeof
572'__typeof__'typeof
573typeoftypeof
574'__alignof'alignof
575'__alignof__'alignof
576'__int128'int128
577'__thread'(thread_local)
loc_error+Loc, +Format, +Args

throw a nanofil_error/1 located at Loc.

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