Fil-C on Nix

Filnix wiki

mbrock/filnix/nanofil

c_pp.pl

The C preprocessor (milestone 4e): it expands a C file's macros, includes and conditionals and hands the result to c_lex as tokens.

Preprocessing tokens

A preprocessing token is tSpelling, File:Line, Space, Hide:

PartIs
Spellingthe token's text, as an atom
File:Lineits location
Spaceyes if whitespace precedes the token, else no; # and #define need it
Hidethe hide set: the sorted macro names that must not expand this token again

A newline is the token nl until the input is split into lines.

The environment

The environment is envMacros, IncludeDirs, Features, Once, File:

PartIs
Macrosan assoc from macro names to objBody or funParams, Body, and from savedName to the #pragma push_macro stack of Name
IncludeDirsthe include search path
Featuresthe target's answers to feature operators
Oncethe files seen by #pragma once
Filethe file whose directives are being processed

Only the file and target-profile adapters do I/O. No C source or header is sent to clang.

modulec_pp
useslists
usesdcgs
usespio
usesassoc
usesprocess
usescharsio
usesc_lex
usesc_parsec_constant_expression/2
usesc_typespp_value/2
pp_file+File, +Profile, -Tokens

Tokens are the c_lex tokens of File after preprocessing, each at the location of the preprocessing token it came from. Profile is profile(Macros, IncludeDirs, FeatureAnswers) from pp_profile/2. Throws nanofil_error/1 on a preprocessing or lexical error.

50pp_fileFile, profileMacros, Paths, Features, Tokens ←
retractallsource_cache_, _
file_tokensFile, envMacros, Paths, Features, [], File, _, PpTokens
phraselex_tokensPpTokens, Tokens
lex_tokens+PpTokens//

the c_lex tokens of PpTokens. Each preprocessing token is lexed on its own and keeps its location.

57lex_tokens[] ⟶
[]
58lex_tokenstA, Loc, _, _|PpTokens ⟶
atom_charsA, Cs
c_tokensCs, '<preprocessing token>', Lexed
maplisttoken_relocatedLoc, Lexed, Tokens
seqTokens
lex_tokensPpTokens
66token_relocatedLoc, tokT, _, tokT, Loc
file_tokens+File, +Env0, -Env, -Tokens

File's expanded tokens. Env carries the macros and the #pragma once set back to the includer.

70file_tokensFile, Env0, Env, Tokens ←
Env0 = envMacros0, Paths, Features, Once0, Includer
absolute_file_nameFile, Identity
memberchkIdentity, Once0
→
Env = Env0
Tokens = []
else
read_sourceFile, PpTokens
token_linesPpTokens, Lines
lines_tokensLines, File, [], [], envMacros0, Paths, Features, Once0, File, End, Tokens, []
End = envMacros, Paths, Features, Once, _
Env = envMacros, Paths, Features, Once, Includer
read_source+File, -PpTokens

PpTokens are File's preprocessing tokens, before line splitting. Impure file adapter. Keep the input buffer local so it can be reclaimed before processing nested includes; the macro environment stays explicit. A file's tokens do not depend on the macros, so each file is read once per pp_file/3: headers are included again and again (lvm.c reads 124 files 272 times), and reading was most of the preprocessor's time.

92read_sourceFile, PpTokens ←
source_cacheFile, PpTokens0
→
PpTokens = PpTokens0
else
phrase_from_fileseqCs, File
oncesource_tokensCs, File:1, PpTokens
assertzsource_cacheFile, PpTokens

source_cache(?File, ?PpTokens): read_source/2's tokens of File, for the pp_file/3 call in progress.

102:- dynamicsource_cache/2

Translation phases 2 and 3

source_tokens+Chars, +Loc, -PpTokens

PpTokens are the preprocessing tokens of Chars, which start at Loc. A comment is one whitespace character, even across physical newlines. A parallel location vector survives splicing even inside identifiers and literals. In particular __LINE__ after a continuation is the physical source line, not the number of newlines in the spliced buffer.

112source_tokensCs, Loc, PpTokens ←
located_spliceCs, Loc, Spliced, Locs
phrasepp_tokensPpTokens, Locs, yes, Spliced
located_splice+Chars, +Loc, -Spliced, -Locs

remove backslash-newline and give each remaining character its physical location.

118located_splice\, '\r', '\n'|Cs, File:N, Spliced, Locs ←
!
N1 is N + 1
located_spliceCs, File:N1, Spliced, Locs
121located_splice\, '\n'|Cs, File:N, Spliced, Locs ←
!
N1 is N + 1
located_spliceCs, File:N1, Spliced, Locs

Share one location term per physical line, not one per character.

125located_spliceC|Cs, Loc, C|Spliced, Loc|Locs ←
!
C ≡ '\n'
→
Loc = File:N
N1 is N + 1
Loc1 = File:N1
else
Loc1 = Loc
located_spliceCs, Loc1, Spliced, Locs
133located_splice[], _, [], []
pp_tokensPpTokens, Locs, Space

Locs runs in step with the input characters; Space says whether whitespace precedes the next token.

137pp_tokensnl|Ts, _|Locs, _ ⟶
'\n'
!
pp_tokensTs, Locs, yes
140pp_tokensTs, _|Locs, _ ⟶
C
!
pp_tokensTs, Locs, yes
143pp_tokensTs, _, _|Locs, _ ⟶
/, /
!
line_commentLocs, Rest
pp_tokensTs, Rest, yes
147pp_tokensTs, _, _|Locs, _ ⟶
/, *
!
block_commentLocs, Rest
pp_tokensTs, Rest, yes
151pp_tokenstA, Loc, Space, []|Ts, Loc|Locs, Space ⟶
!
atom_charsA, Cs
lengthCs, N
N1 is N−1
dropN1, Locs, Rest
pp_tokensTs, Rest, no
159pp_tokens[], [], _ ⟶
[]
drop+N, +Xs, -Rest

Rest is Xs without its first N elements.

162drop0, Xs, Xs ←
!
163dropN, _|Xs, Rest ←
N1 is N−1
dropN1, Xs, Rest
space' ''\t''\r''\f''\v'
line_comment+Locs, -Rest//

the comment's characters, leaving the newline; Rest is Locs without their locations.

175line_commentLocs, Locs, '\n' ⟶
'\n'
!
176line_comment_|Locs, Rest ⟶
_
!
line_commentLocs, Rest
179line_comment[], [] ⟶
[]
block_comment+Locs, -Rest//

like line_comment//2, through the */.

182block_comment_, _|Locs, Locs ⟶
*, /
!
183block_comment_|Locs, Rest ⟶
_
!
block_commentLocs, Rest
186block_comment_, _ ⟶
thrownanofil_error'unterminated preprocessing comment'
spelling-Chars//

Chars is the spelling of one preprocessing token, the longest that matches.

191spellingP, Q|Cs ⟶
P, Q
memberchkP, 'L', u, 'U'
memberchkQ, '"', '\''
!
quotedQ, Cs
194spellingQ|Cs ⟶
Q
memberchkQ, '"', '\''
!
quotedQ, Cs
197spellingC|Cs ⟶
200spellingC|Cs ⟶
203spelling'.', C|Cs ⟶
206spellingCs ⟶
A, B, C
Cs = A, B, C
atom_charsP, Cs
!
209spellingCs ⟶
A, B
Cs = A, B
atom_charsP, Cs
!
212spellingC ⟶
C
quoted+Quote, -Chars//

Chars is the rest of a literal through its closing Quote, escapes kept as written.

216quotedQ, \, C|Cs ⟶
\, C
!
quotedQ, Cs
219quotedQ, Q ⟶
Q
!
220quotedQ, C|Cs ⟶
C
C ≢ '\n'
!
quotedQ, Cs
223quoted_, _ ⟶
thrownanofil_error'unterminated preprocessing literal'
226number_restE, S|Cs ⟶
E, S
memberchkE, e, 'E', p, 'P'
memberchkS, +, -
!
230number_restC|Cs ⟶
C
or
C ≡ '.'
!
233number_rest[] ⟶
[]

Lines and directives

token_lines+PpTokens, -Lines

Lines are PpTokens split at each nl, the nl tokens dropped.

239token_lines[], []
240token_linesT|Ts, Line|Lines ←
tokens_line_restT|Ts, Line, Rest
token_linesRest, Lines
tokens_line_rest+PpTokens, -Line, -Rest

Line is PpTokens up to the first nl, and Rest follows it.

246tokens_line_rest[], [], []
247tokens_line_restT|Ts, Line, Rest ←
token_line_restT, Ts, Line, Rest
token_line_rest+T, +Ts, -Line, -Rest

like tokens_line_rest/3 for [T|Ts]; T is nl or a t/4 token.

251token_line_restnl, Ts, [], Ts
252token_line_resttA, Loc, Space, Hide, Ts, tA, Loc, Space, Hide|Line, Rest ←
tokens_line_restTs, Line, Rest
lines_tokens+Lines, +File, +Stack, +Pending, +Env0, -Env, -Ts, ?End

Ordinary lines are collected in Pending (reversed) and expanded together, so a function invocation can span newlines. Directives are barriers; inactive groups never expand tokens.

260lines_tokens[], File, Stack, Pending, Env, Env, Ts, End ←
Stack = []
→
true
else
pp_errorFile:1, 'unterminated #if'
pending_expansionPending, Env, Expanded
appendExpanded, End, Ts
264lines_tokenst#, HashLoc, _, _|Directive|Lines, File, Stack, Pending, Env0, Env, Ts, End ←
!
pending_expansionPending, Env0, Expanded
appendExpanded, Rest, Ts
Directive = tline, _, _, _|Args
activeStack
→
tokens_expansionArgs, Env0, Marker
line_targetMarker, HashLoc, Target
lines_remappedLines, HashLoc, Target, More
Stack1 = Stack
Env1 = Env0
Rest = Next
else
directiveDirective, File, Stack, Stack1, Env0, Env1, Rest, Next
More = Lines
lines_tokensMore, File, Stack1, [], Env1, Env, Next, End
280lines_tokensLine|Lines, File, Stack, Pending, Env0, Env, Ts, End ←
activeStack
→
lines_tokensLines, File, Stack, Line|Pending, Env0, Env, Ts, End
else
lines_tokensLines, File, Stack, Pending, Env0, Env, Ts, End
line_target+Marker, +HashLoc, -Target

for #line N ["file"] at HashLoc, the line after the directive is line N (of file), Target.

288line_targettA, _, _, _|Ts, File:_, NewFile:N ←
atom_numberA, N
integerN
N > 0
marker_fileTs, File, NewFile
marker_file+Tokens, +File0, -File

File is named by the optional "file" operand Tokens of #line, else File0.

296marker_file[], File, File
297marker_fileT|Ts, _, File ←
include_nameT|Ts, File, quote
lines_remapped+Lines, +Old, +New, -Remapped

the tokens of Lines, which follow the directive at Old, renumbered so that the first is at New.

301lines_remapped[], _, _, []
302lines_remappedTs|Lines, Old, New, Rs|Rest ←
maplisttoken_remappedOld, New, Ts, Rs
lines_remappedLines, Old, New, Rest
306token_remappedOld, New, tA, Loc, Space, Hide, tA, Loc1, Space, Hide ←
location_remappedOld, New, Loc, Loc1
309location_remapped_:Old, File:New, _:N, File:M ←
M is N−Old−1 + New
pending_expansion+Pending, +Env, -Ts

Ts are the expanded tokens of the reversed list of lines Pending.

313pending_expansionPending, Env, Ts ←
reversePending, Lines
appendLines, Flat
tokens_expansionFlat, Env, Ts

The conditional stack has one cond(Taken, Seen, Parent) per open #if: Taken says whether the current group is active, Seen whether an earlier group was taken (yes, no, or else after #else), Parent whether the enclosing group is active.

322active[]
323activecondyes, _, _|_
seen_after+Taken, +Seen0, -Seen

Seen says whether a group was taken once the current one is decided.

327seen_afteryes, _, yes
328seen_afterno, Seen, Seen
condition_truth+Value, -Taken

Taken is yes if Value is nonzero.

331condition_truthV, yes ←
V ≠ 0
!
332condition_truth_, no
directive+Tokens, +File, +Stack0, -Stack, +Env0, -Env, -Ts, ?Rest

one directive line after its '#'. Conditionals are tracked even in inactive groups; other directives only run in active ones. Ts is Rest with an included file's tokens in front.

338directive[], _, Stack, Stack, Env, Env, Ts, Ts
339directivetName, Loc, _, _|Args, File, Stack0, Stack, Env0, Env, Ts, Rest ←
→
conditional_stackName, Loc, Args, Env0, Stack0, Stack
Env = Env0
Ts = Rest
activeStack0
→
Stack = Stack0
active_directiveName, Loc, Args, File, Env0, Env, Ts, Rest
else
Stack = Stack0
Env = Env0
Ts = Rest
conditional_directiveififdefifndefelifelseendif
conditional_stack+Name, +Loc, +Args, +Env, +Stack0, -Stack

Stack is the conditional stack after the directive #Name Args.

362conditional_stackelif, Loc, Args, Env, Stack0, condTaken, Seen1, Parent|Tail ←
Stack0 = cond_, Seen, Parent|Tail
Seen ≢ else
→
true
else
pp_errorLoc, 'unexpected #elif'
elif_takenParent, Seen, Args, Env, Taken
seen_afterTaken, Seen, Seen1
370conditional_stackelse, Loc, _, _, Stack0, condTaken, else, Parent|Tail ←
Stack0 = cond_, Seen, Parent|Tail
Seen ≢ else
→
true
else
pp_errorLoc, 'unexpected #else'
else_takenSeen, Parent, Taken
376conditional_stackendif, Loc, _, _, Stack0, Stack ←
Stack0 = _|Stack
→
true
else
pp_errorLoc, 'unexpected #endif'
381conditional_stackif, _, Args, Env, Stack0, Stack ←
opened_stackif, Args, Env, Stack0, Stack
383conditional_stackifdef, _, Args, Env, Stack0, Stack ←
opened_stackifdef, Args, Env, Stack0, Stack
385conditional_stackifndef, _, Args, Env, Stack0, Stack ←
opened_stackifndef, Args, Env, Stack0, Stack
opened_stack+Kind, +Args, +Env, +Stack0, -Stack

Stack is Stack0 with the group that #Kind Args opens.

390opened_stackKind, Args, Env, Stack0, condTaken, Taken, Parent|Stack0 ←
activeStack0
→
Parent = yes
conditionKind, Args, Env, V
else
Parent = no
Taken = no
elif_taken+Parent, +Seen, +Args, +Env, -Taken

Taken says whether the group of #elif Args is active.

401elif_takenno, _, _, _, no
402elif_takenyes, Seen, Args, Env, Taken ←
elif_openSeen, Args, Env, Taken
elif_open+Seen, +Args, +Env, -Taken

in an active parent, #elif Args is taken if no earlier group was and its condition holds.

406elif_openno, Args, Env, Taken ←
conditionif, Args, Env, V
409elif_openyes, _, _, no
else_taken+Seen, +Parent, -Taken

#else is taken if no earlier group was and the parent is active.

413else_takenno, Parent, Parent
414else_takenyes, _, no
active_directive+Name, +Loc, +Args, +File, +Env0, -Env, -Ts, ?Rest

run #Name Args in an active group; Ts is Rest with an included file's tokens in front. Name is any identifier in the source, so the last clause is a real default case: the cuts commit to a known directive.

420active_directivedefine, _, Args, _, Env0, Env, Ts, Ts ←
!
Env0 = envMacros0, Paths, Features, Once, File
definition_macrosArgs, Macros0, Macros
Env = envMacros, Paths, Features, Once, File
424active_directiveundef, _, Args, _, Env0, Env, Ts, Ts ←
!
Args = tName, _, _, _
Env0 = envMacros0, Paths, Features, Once, File
macro_removedName, Macros0, Macros
Env = envMacros, Paths, Features, Once, File
429active_directiveKind, Loc, Args, File, Env0, Env, Ts, Rest ←
memberchkKind, include, include_next
!
Env0 = env_, Paths, _, _, _
header_operandArgs, Env0, Name, Form
search_pathsKind, File, Paths, Search
include_pathName, Form, File, Search, Path
→
true
else
pp_errorLoc, include_not_foundName
file_tokensPath, Env0, Env, Included
appendIncluded, Rest, Ts
441active_directivepragma, _, Args, File, Env0, Env, Ts, Ts ←
!
pragmaArgs, File, Env0, Env
443active_directiveerror, Loc, Args, _, _, _, _, _ ←
!
pp_errorLoc, error_directiveArgs
445active_directiveident, _, _, _, Env, Env, Ts, Ts ←
!
446active_directiveName, Loc, _, _, _, _, _, _ ←
pp_errorLoc, unsupported_directiveName

#pragma once, push_macro and pop_macro; other pragmas are ignored. Macro save stacks use compound assoc keys, which cannot collide with C identifier atoms. Pragma operands are deliberately not macro-expanded.

452pragmatonce, _, _, _, File, envMacros, Paths, Features, Once, Current, envMacros, Paths, Features, Identity|Once, Current ←
!
absolute_file_nameFile, Identity
456pragmatOp, _, _, _, t'(', _, _, _, Str, t')', _, _, _, _, envMacros0, Paths, Features, Once, Current, envMacros, Paths, Features, Once, Current ←
memberchkOp, push_macro, pop_macro
include_nameStr, Name, quote
!
macro_stack_opOp, Name, Macros0, Macros
463pragma_, _, Env, Env
macro_stack_op+Op, +Name, +Macros0, -Macros

Macros is Macros0 after #pragma Op("Name"). A saved entry is some(Def) or none (undefined); a pop with nothing saved changes nothing.

468macro_stack_oppush_macro, Name, Macros0, Macros ←
get_assocName, Macros0, Def
→
Saved = someDef
else
Saved = none
get_assocsavedName, Macros0, Stack
→
true
else
Stack = []
put_assocsavedName, Macros0, Saved|Stack, Macros
472macro_stack_oppop_macro, Name, Macros0, Macros ←
get_assocsavedName, Macros0, Saved|Stack
→
put_assocsavedName, Macros0, Stack, Macros1
restored_macroSaved, Name, Macros1, Macros
else
Macros = Macros0
restored_macro+Saved, +Name, +Macros0, -Macros

Macros is Macros0 with Name as #pragma push_macro saved it, some(Def) or none.

481restored_macrosomeDef, Name, Macros0, Macros ←
put_assocName, Macros0, Def, Macros
483restored_macronone, Name, Macros0, Macros ←
macro_removedName, Macros0, Macros
macro_removed+Name, +Macros0, -Macros

Macros is Macros0 without Name, which need not be defined.

488macro_removedName, Macros0, Macros ←
del_assocName, Macros0, _, Macros1
→
Macros = Macros1
else
Macros = Macros0
definition_macros+Tokens, +Macros0, -Macros

Macros is Macros0 with the definition Tokens of #define. A macro is function-like if '(' follows its name without a space.

497definition_macrostName, _, _, _|Ts, Macros0, Macros ←
Ts = t'(', _, no, _|Rest
→
macro_paramsRest, Params, Body
Def = funParams, Body
else
Def = objTs
put_assocName, Macros0, Def, Macros
macro_params+Ts, -Params, -Body

named(Name) for each name up to ')'; a final ... (or P...) is variadic(Name).

507macro_paramst')', _, _, _|Ts, [], Ts ←
!
508macro_paramst..., _, _, _, t')', _, _, _|Ts, variadic'__VA_ARGS__', Ts ←
!
510macro_paramstP, _, _, _, t..., _, _, _, t')', _, _, _|Ts, variadicP, Ts ←
!
512macro_paramstP, _, _, _, t(','), _, _, _|Ts, namedP|Ps, Body ←
!
macro_paramsTs, Ps, Body
515macro_paramstP, _, _, _, t')', _, _, _|Ts, namedP, Ts

Includes

include_name+Tokens, -Name, -Form

"name" (quote) or <name> (angle).

520include_nametA, _, _, _, Name, quote ←
atom_charsA, '"'|Cs
appendChars, '"', Cs
atom_charsName, Chars
!
525include_namet<, _, _, _|Ts, Name, angle ←
appendMid, t>, _, _, _, Ts
maplisttoken_spelling, Mid, As
atomic_list_concatAs, Name
530token_spellingtA, _, _, _, A
header_operand+Tokens, +Env, -Name, -Form

the header name of an #include or __has_include operand. The operand is expanded only if it is not already a header name.

535header_operandTs, Env, Name, Form ←
include_nameTs, Name, Form
→
true
else
tokens_expansionTs, Env, Expanded
include_nameExpanded, Name, Form
include_path+Name, +Form, +File, +Dirs, -Path

Path is the first existing file for header Name included from File: an absolute Name, then (for quote) File's directory, then Dirs in order. Each cut commits to the first existing file, so at most one Path is found.

546include_pathName, _, _, _, Name ←
sub_atomName, 0, 1, _, /
exists_fileName
!
550include_pathName, quote, File, _, Path ←
path_joinDir, Name, Path
exists_filePath
!
555include_pathName, _, _, Dirs, Path ←
memberDir, Dirs
path_joinDir, Name, Path
exists_filePath
!
file_directory+File, -Dir

Dir is File up to its last '/', or '.'.

562file_directoryFile, Dir ←
atom_charsFile, Cs
reverseCs, Rev
append_, /|Tail, Rev
!
reverseTail, Ds
atom_charsDir, Ds
569file_directory_, '.'
571path_joinDir, Name, Path ←
atomic_list_concatDir, /, Name, Path
search_paths+Kind, +File, +Dirs, -Search

#include_next and __has_include_next search the directories after the one File is in. Kinds include and __has_include also match the last clause, so a retry yields a second Search (the directories after File's, if any).

577search_pathsinclude, _, Dirs, Dirs
578search_paths'__has_include', _, Dirs, Dirs
579search_paths_, File, Dirs, Rest ←
append_, Dir|Rest0, Dirs
atom_concatDir, /, Prefix
atom_concatPrefix, _, File
→
Rest = Rest0
else
Rest = Dirs

Macro expansion

Macro replacement uses token hide sets rather than a global recursion stack: an unexpandable token stays unexpandable after argument prescan, and a replacement may consume a function-call '(' from the input tail. A function-like replacement's hide set is the intersection of the hide sets of the macro name and of the closing ')', plus the name. tokens_expansion(+Ts, +Env, -Out): Out is Ts fully macro-expanded.

595tokens_expansion[], _, []
596tokens_expansionT|Ts, Env, Out ←
T = tA, Loc, Space, Hide
Env = envMacros, _, _, _, _
¬memberchkA, Hide
get_assocA, Macros, Def
replacementDef, Ts, Rest, Env, Loc, Repl, EndHide
→
invocation_hideEndHide, Hide, BaseHide
maplistreplaced_tokenA|BaseHide, Loc, Repl, Marked
first_spacedMarked, Space, Replaced
appendReplaced, Rest, Next
tokens_expansionNext, Env, Out
builtinT, Ts, Rest, Env, Repl
→
appendRepl, Rest, Next
tokens_expansionNext, Env, Out
else
Out = T|Out1
tokens_expansionTs, Env, Out1
invocation_hide+EndHide, +Hide, -BaseHide

BaseHide is the hide set Hide of a macro name, intersected with that of a function-like invocation's ')' if EndHide is some(Set).

617invocation_hidenone, Hide, Hide
618invocation_hidesomeEndHide, Hide, BaseHide ←
hide_intersectionHide, EndHide, BaseHide
hide_intersection+As, +Bs, -Cs

Cs are the elements of As in Bs.

622hide_intersection[], _, []
623hide_intersectionA|As, Bs, Cs ←
memberchkA, Bs
→
Cs = A|Cs1
else
Cs = Cs1

A replacement token moves to the invocation and gains its hide set.

628replaced_tokenHide, Loc, tA, _, Space, Hide0, tA, Loc, Space, Hide1 ←
appendHide, Hide0, Hides
sortHides, Hide1
632first_spaced[], _, []
633first_spacedtA, Loc, _, Hide|Ts, Space, tA, Loc, Space, Hide|Ts
replacement+Def, +Ts, -Rest, +Env, +Loc, -Repl, -EndHide

EndHide is some(Set), Set the hide set of a function-like invocation's ')', or none for an object-like macro.

638replacementobjBody, Ts, Ts, _, Loc, Repl, none ←
tokens_pastedBody, Repl, Loc
639replacementfunParams, Body, t'(', _, _, _|Ts, Rest, Env, Loc, Repl, someEndHide ←
macro_argumentsTs, 0, [], [], Args0, Rest, EndHide
Args0 = []
Params = []
→
Args = []
else
Args = Args0
params_bindingsParams, Args, Env, Bindings, Loc
body_substitutionBody, Bindings, Repl0
tokens_pastedRepl0, Repl, Loc
macro_arguments+Ts, +Depth, +ArgRev, +ArgsRev, -Args, -Rest, -EndHide

the tokens after '(' split at top-level commas, up to the matching ')' whose hide set is EndHide.

650macro_argumentst')', _, _, Hide|Ts, 0, Rev, Acc, Args, Ts, Hide ←
!
reverseRev, Arg
reverseArg|Acc, Args
653macro_argumentst(','), _, _, _|Ts, 0, Rev, Acc, Args, Rest, Hide ←
!
reverseRev, Arg
macro_argumentsTs, 0, [], Arg|Acc, Args, Rest, Hide
656macro_argumentsT|Ts, Depth, Rev, Acc, Args, Rest, Hide ←
T = tA, _, _, _
A ≡ '('
→
Depth1 is Depth + 1
A ≡ ')'
→
Depth1 is Depth−1
else
Depth1 = Depth
macro_argumentsTs, Depth1, T|Rev, Acc, Args, Rest, Hide
params_bindings+Params, +Args, +Env, -Bindings, +Loc

bind(P, Raw, Env) for each parameter; a variadic one takes the remaining arguments.

666params_bindings[], Args, _, [], Loc ←
no_argumentsArgs, Loc
667params_bindingsParam|Params, Args, Env, Bindings, Loc ←
param_bindingsParam, Params, Args, Env, Bindings, Loc
no_arguments+Args, +Loc

Args is empty, as no parameters are left.

671no_arguments[], _
672no_arguments_|_, Loc ←
param_bindings+Param, +Params, +Args, +Env, -Bindings, +Loc

like params_bindings/5 for [Param|Params]. Only the last can be variadic.

676param_bindingsvariadicP, [], Args, Env, bindP, Raw, Env, _ ←
args_joinedArgs, Raw
678param_bindingsnamedP, Params, Args, Env, Bindings, Loc ←
named_bindingsArgs, P, Params, Env, Bindings, Loc
681named_bindings[], _, _, _, _, Loc ←
682named_bindingsA|As, P, Params, Env, bindP, A, Env|Bindings, Loc ←
params_bindingsParams, As, Env, Bindings, Loc
685argument_count_errorLoc ←
pp_errorLoc, 'wrong macro argument count'
args_joined+Args, -Ts

Ts are the token lists Args joined by commas.

688args_joined[], []
689args_joinedA|As, Ts ←
args_joinedAs, A, Ts
args_joined+Args, +A, -Ts

Ts is A, then each of Args after a comma.

692args_joined[], A, A
693args_joinedA|As, A0, Ts ←
args_joinedAs, A, Rest
appendA0, t(','), '<macro>':0, no, []|Rest, Ts
697body_substitutionTs, Bindings, Rs ←
body_substitutionTs, Bindings, no, Rs
body_substitution+Body, +Bindings, +AfterPaste, -Rs

AfterPaste is yes right after ##. # P stringifies the raw argument, an operand of ## is the raw argument, and any other use is the fully expanded argument; an empty argument becomes the placemarker ''.

703body_substitution[], _, _, []
704body_substitutiont#, Loc, Space, Hide, tP, _, _, _|Ts, Bindings, _, tStr, Loc, Space, Hide|Rs ←
memberchkbindP, Raw, _, Bindings
!
body_substitutionTs, Bindings, no, Rs
709body_substitutiontP, Loc, Space, Hide|Ts, Bindings, AfterPaste, Rs ←
memberchkbindP, Raw, Env, Bindings
→
AfterPaste ≡ yes
or
Ts = t##, _, _, _|_
→
Use = Raw
else
tokens_expansionRaw, Env, Use
Use = []
→
R = t'', Loc, Space, Hide
else
first_spacedUse, Space, R
else
R = tP, Loc, Space, Hide
P ≡ ##
→
Next = yes
else
Next = no
appendR, Tail, Rs
body_substitutionTs, Bindings, Next, Tail
tokens_string+Ts, -A

A is the string literal spelling of Ts, as # makes it.

727tokens_stringTs, A ←
phrasetokens_textTs, first, Cs
phraseescaped_charsCs, Escaped
append'"'|Escaped, '"', All
atom_charsA, All
733tokens_text[], _ ⟶
[]
734tokens_texttA, _, Space, _|Ts, Which ⟶
Space ≡ yes
Which ≢ first
→
' '
else
[]
atom_charsA, Cs
seqCs
tokens_textTs, next
740escaped_chars[] ⟶
[]
741escaped_charsC|Cs ⟶
memberchkC, '"', \
→
\, C
else
C
tokens_pasted+Ts, -Rs, +Loc

apply ## and drop placemarkers. A pasted spelling must be exactly one preprocessing token.

747tokens_pastedtA, Loc, Space, Hide, t##, _, _, _, tB, _, _, Hide1|Ts, Rs, MacroLoc ←
!
atom_concatA, B, C
C ≡ ''
→
true
atom_charsC, Cs
phrasespellingCs, Cs
→
true
else
pp_errorMacroLoc, invalid_token_pasteA, B
hide_intersectionHide, Hide1, Hide2
tokens_pastedtC, Loc, Space, Hide2|Ts, Rs, MacroLoc
760tokens_pastedt'', _, _, _|Ts, Rs, Loc ←
!
tokens_pastedTs, Rs, Loc
763tokens_pastedT|Ts, T|Rs, Loc ←
tokens_pastedTs, Rs, Loc
764tokens_pasted[], [], _
builtin+T, +Ts, -Rest, +Env, -Repl

T, applied to a prefix of Ts that leaves Rest, is a builtin operator replaced by Repl. Builtin operators are not macros in clang's -dM output. Feature answers are discovered once with the target profile, not guessed from __GNUC__.

770builtint'__LINE__', Loc, Space, Hide, Ts, Ts, _, tA, Loc, Space, Hide ←
Loc = _:Line
atom_numberA, Line
773builtint'__FILE__', Loc, Space, Hide, Ts, Ts, _, tA, Loc, Space, Hide ←
Loc = File:_
tokens_stringtFile, Loc, no, [], A
776builtintOp, Loc, Space, Hide, t'(', _, _, _|Ts, Rest, Env, tA, Loc, Space, Hide ←
memberchkOp, '__has_include', '__has_include_next'
macro_argumentsTs, 0, [], [], Arg, Rest, _
header_operandArg, Env, Name, Form
Env = env_, Paths, _, _, File
search_pathsOp, File, Paths, Search
include_pathName, Form, File, Search, _
→
A = '1'
else
A = '0'
784builtintOp, Loc, Space, Hide, t'(', _, _, _, tName, _, _, _, t')', _, _, _|Ts, Ts, env_, _, Features, _, _, tA, Loc, Space, Hide ←
feature_valueFeatures, Op, Name, A
feature_operator'__has_attribute''__has_builtin''__has_extension''__has_feature''__has_c_attribute''__building_module'
is_defined+Name, +Env

Name is a macro or builtin operator, as defined and #ifdef see it.

799is_definedA, envMacros, _, _, _, _ ←
get_assocA, Macros, _
!
800is_defined'__has_include', _
801is_defined'__has_include_next', _
802is_defined'__LINE__', _
803is_defined'__FILE__', _
804is_definedA, _ ←

Conditions

condition(+Kind, +Args, +Env, -Value) for #if, #ifdef and #ifndef.

809conditionifdef, tA, _, _, _, Env, V ←
→
V = 1
else
V = 0
811conditionifndef, Args, Env, V ←
conditionifdef, Args, Env, Defined
V is 1−Defined
814conditionif, Args, Env, V ←
defined_tokensArgs, Env, Replaced
tokens_expansionReplaced, Env, Expanded
maplistexpr_token, Expanded, Tokens
pp_valueExpr, V
→
true
else
Tokens = tok_, Loc|_
→
true
else
Loc = '<condition>':0
pp_errorLoc, not_constantExpr

defined X and defined(X) become 1 or 0 before macro expansion.

825defined_tokens[], _, []
826defined_tokenstdefined, Loc, Space, Hide, t'(', _, _, _, tA, _, _, _, t')', _, _, _|Ts, Env, tV, Loc, Space, Hide|Rs ←
!
→
V = '1'
else
V = '0'
defined_tokensTs, Env, Rs
831defined_tokenstdefined, Loc, Space, Hide, tA, _, _, _|Ts, Env, tV, Loc, Space, Hide|Rs ←
!
→
V = '1'
else
V = '0'
defined_tokensTs, Env, Rs
836defined_tokensT|Ts, Env, T|Rs ←
defined_tokensTs, Env, Rs
expr_token+PpToken, -Token

the c_lex token of a #if operand or operator. A name left after expansion, keyword or not, is 0.

840expr_tokentA, Loc, _, _, tokT, Loc ←
atom_charsA, Cs
catchc_tokensCs, '<condition>', tokT0, _, nanofil_error_, fail
→
Cs = C|_
→
T = int0, none−dec
else
pp_errorLoc, bad_if_tokenA
849expr_operandid_, int0, none−dec ←
!
850expr_operandkw_, int0, none−dec ←
!
851expr_operandT, T
853pp_errorLoc, Why ←
formatatomMsg, '~w: ~w', Loc, Why
thrownanofil_errorMsg

Target profile

pp_profile+Clang, -Profile
Profile is profile(Macros, IncludeDirs, FeatureAnswers) for the
target of the clang executable Clang: its predefined macros, its
#include <...> search path and its answers to feature operators.

Impure adapter: discover the selected target, never preprocess the input.

This avoids embedding Nix store paths or a second, stale copy of the pin's 400+ predefined macros. Pipes are drained before waiting for the child.

868pp_profileClang, profileMacros, Paths, Features ←
clang_outputClang, '-dM', '-E', '-x', c, '/dev/null', Defs
source_tokensDefs, '<built-in>':1, PpTokens
token_linesPpTokens, Lines
empty_assocMacros0
foldlprofile_define, Lines, Macros0, Macros
process_createClang, '-E', '-v', '-x', c, '/dev/null', stdoutnull, stderrpipeErr, processPid
read_charsErr, Verbose
closeErr
process_waitPid, exit0
phrasetext_linesVerboseLines, Verbose
include_directoriesVerboseLines, Paths
feature_profileClang, Features
883profile_definet#, _, _, _, tdefine, _, _, _|Ts, Macros0, Macros ←
!
definition_macrosTs, Macros0, Macros
886profile_define_, Macros, Macros
clang_output+Command, +Args, -Chars

run Command with Args; Chars is its standard output. Fails unless it exits with status 0.

890clang_outputCommand, Args, Cs ←
process_createCommand, Args, stdoutpipeOut, processPid
read_charsOut, Cs
closeOut
process_waitPid, exit0
896read_charsStream, Cs ←
get_charStream, C
C ≡ end_of_file
→
Cs = []
else
Cs = C|Rest
read_charsStream, Rest
904text_linesLine|Lines ⟶
907text_lines[] ⟶
[]

A line needs at least a newline or one character.

910text_line[] ⟶
'\n'
!
911text_lineC|Cs ⟶
915text_rest[] ⟶
'\n'
!
916text_restC|Cs ⟶
919text_rest[] ⟶
[]
include_directories+Lines, -Paths

Paths are the directories listed after clang -v's "#include <...>" line; fails if there is none.

923include_directoriesLine|Lines, Paths ←
Line = "#include <...> search starts here:"
→
path_linesLines, Paths
else
929path_lines' '|Cs|Lines, Path|Paths ←
!
atom_charsPath, Cs
path_linesLines, Paths
933path_lines_, []

Batch the feature names used by glibc. The adapter uses a shell pipeline because this Trealla revision leaves the child's own stdin writer open with stdin(pipe(...)); closing the parent stream cannot deliver EOF.

938feature_names'__has_attribute', '__nothrow__', '__cold__', '__malloc__', '__alloc_align__', '__pure__', '__const__', '__unused__', '__used__', '__deprecated__', '__format_arg__', '__format__', '__nonnull__', '__returns_nonnull__', '__warn_unused_result__', '__always_inline__', '__artificial__', '__indirect_return__'
944feature_names'__has_builtin', '__builtin_expect', '__builtin_FILE', '__builtin_fclose'
946feature_names'__has_extension', '__attribute_deprecated_with_message__', c_generic_selections
948feature_names'__has_feature', modules
949feature_names'__has_c_attribute', []
950feature_names'__building_module', '_Builtin_stddef'
952feature_profileClang, featuresClang, Answers ←
findallOp−Name,
feature_namesOp, Names
memberName, Names
,
Pairs
query_featuresClang, Pairs, Answers
feature_value+Features, +Op, +Name, -A

A is the spelling of Op(Name), asking clang if the profile has no answer for it. This is target introspection only: pass an operator and a single name, never a source expression, file, declaration or include to clang.

960feature_valuefeaturesClang, Answers, Op, Name, A ←
memberchkanswerOp, Name, A, Answers
→
true
else
atom_charsName, C|Cs
maplistident_char, Cs
query_featuresClang, Op−Name, answerOp, Name, A
query_features+Clang, +Pairs, -Answers

Answers are clang's answer(Op, Name, A) for each Op-Name in Pairs.

971query_featuresClang, Pairs, Answers ←
findallQuoted,
memberOp−Name, Pairs
formatatomQuery, '~w(~w)', Op, Name
shell_quotedQuery, Quoted
,
Queries
atomic_list_concatQueries, ' ', Args
shell_quotedClang, ClangQuoted
formatatomCmd, "printf '%s\\n' ~w | ~w -E -P -x c -", Args, ClangQuoted
clang_output'/bin/sh', '-c', Cmd, Cs
source_tokensCs, '<features>':1, PpTokens
exclude=nl, PpTokens, Values
maplistfeature_answer, Pairs, Values, Answers
987feature_answerOp−Name, tA, _, _, _, answerOp, Name, A
shell_quoted+A, -Quoted

Quoted is A in single quotes for /bin/sh.

990shell_quotedA, Quoted ←
atom_charsA, Cs
phraseshell_charsCs, Escaped
append'\''|Escaped, '\'', All
atom_charsQuoted, All
996shell_chars[] ⟶
[]
997shell_chars'\''|Cs ⟶
"'\\''"
!
1000shell_charsC|Cs ⟶