rubycc 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (159) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +56 -0
  3. data/LICENSE.txt +21 -0
  4. data/NOTICE +52 -0
  5. data/README.md +208 -0
  6. data/data/README.md +117 -0
  7. data/data/r10_corpus_scan.json +4082 -0
  8. data/data/r10_manual_classification.json +5342 -0
  9. data/data/r10_verification_m4a.json +1531 -0
  10. data/data/r10_verification_m4b.json +1818 -0
  11. data/data/r10_verification_m4c.json +1489 -0
  12. data/data/r10_verification_m4d.json +318 -0
  13. data/data/r10_verification_rbs.json +192 -0
  14. data/data/verified_gems.json +397 -0
  15. data/exe/rmake +16 -0
  16. data/exe/rubycc +9 -0
  17. data/exe/rubycc-ar +114 -0
  18. data/exe/rubycc-doctor +14 -0
  19. data/exe/rubycc-pkgconf +8 -0
  20. data/include/float.h +87 -0
  21. data/include/iso646.h +18 -0
  22. data/include/libc/alloca.h +18 -0
  23. data/include/libc/arpa/inet.h +61 -0
  24. data/include/libc/assert.h +43 -0
  25. data/include/libc/dirent.h +72 -0
  26. data/include/libc/dlfcn.h +58 -0
  27. data/include/libc/features.h +205 -0
  28. data/include/libc/glibc/aarch64/ctype.h +108 -0
  29. data/include/libc/glibc/aarch64/endian.h +54 -0
  30. data/include/libc/glibc/aarch64/errno.h +154 -0
  31. data/include/libc/glibc/aarch64/fcntl.h +144 -0
  32. data/include/libc/glibc/aarch64/inttypes.h +182 -0
  33. data/include/libc/glibc/aarch64/limits.h +63 -0
  34. data/include/libc/glibc/aarch64/pthread.h +131 -0
  35. data/include/libc/glibc/aarch64/setjmp.h +84 -0
  36. data/include/libc/glibc/aarch64/stdint.h +174 -0
  37. data/include/libc/glibc/aarch64/sys/epoll.h +88 -0
  38. data/include/libc/glibc/aarch64/sys/fcntl.h +19 -0
  39. data/include/libc/glibc/aarch64/sys/select.h +75 -0
  40. data/include/libc/glibc/aarch64/sys/stat.h +136 -0
  41. data/include/libc/glibc/aarch64/sys/syscall.h +167 -0
  42. data/include/libc/glibc/aarch64/sys/time.h +74 -0
  43. data/include/libc/glibc/aarch64/sys/types.h +127 -0
  44. data/include/libc/glibc/aarch64/time.h +113 -0
  45. data/include/libc/glibc/x86_64/ctype.h +108 -0
  46. data/include/libc/glibc/x86_64/endian.h +54 -0
  47. data/include/libc/glibc/x86_64/errno.h +154 -0
  48. data/include/libc/glibc/x86_64/fcntl.h +134 -0
  49. data/include/libc/glibc/x86_64/inttypes.h +182 -0
  50. data/include/libc/glibc/x86_64/limits.h +61 -0
  51. data/include/libc/glibc/x86_64/pthread.h +111 -0
  52. data/include/libc/glibc/x86_64/setjmp.h +82 -0
  53. data/include/libc/glibc/x86_64/stdint.h +163 -0
  54. data/include/libc/glibc/x86_64/sys/epoll.h +91 -0
  55. data/include/libc/glibc/x86_64/sys/fcntl.h +19 -0
  56. data/include/libc/glibc/x86_64/sys/select.h +75 -0
  57. data/include/libc/glibc/x86_64/sys/stat.h +130 -0
  58. data/include/libc/glibc/x86_64/sys/syscall.h +188 -0
  59. data/include/libc/glibc/x86_64/sys/time.h +74 -0
  60. data/include/libc/glibc/x86_64/sys/types.h +123 -0
  61. data/include/libc/glibc/x86_64/time.h +113 -0
  62. data/include/libc/grp.h +50 -0
  63. data/include/libc/langinfo.h +126 -0
  64. data/include/libc/link.h +18 -0
  65. data/include/libc/locale.h +83 -0
  66. data/include/libc/math.h +191 -0
  67. data/include/libc/netinet/in.h +126 -0
  68. data/include/libc/netinet/tcp.h +44 -0
  69. data/include/libc/poll.h +46 -0
  70. data/include/libc/pwd.h +58 -0
  71. data/include/libc/regex.h +53 -0
  72. data/include/libc/sched.h +35 -0
  73. data/include/libc/signal.h +204 -0
  74. data/include/libc/stdio.h +157 -0
  75. data/include/libc/stdlib.h +92 -0
  76. data/include/libc/string.h +74 -0
  77. data/include/libc/strings.h +25 -0
  78. data/include/libc/sys/cdefs.h +126 -0
  79. data/include/libc/sys/inotify.h +111 -0
  80. data/include/libc/sys/ioctl.h +36 -0
  81. data/include/libc/sys/mman.h +65 -0
  82. data/include/libc/sys/param.h +41 -0
  83. data/include/libc/sys/resource.h +109 -0
  84. data/include/libc/sys/socket.h +167 -0
  85. data/include/libc/sys/statfs.h +82 -0
  86. data/include/libc/sys/timerfd.h +55 -0
  87. data/include/libc/sys/uio.h +40 -0
  88. data/include/libc/sys/un.h +25 -0
  89. data/include/libc/sys/utsname.h +35 -0
  90. data/include/libc/sys/wait.h +135 -0
  91. data/include/libc/termios.h +179 -0
  92. data/include/libc/unistd.h +194 -0
  93. data/include/stdalign.h +16 -0
  94. data/include/stdarg.h +31 -0
  95. data/include/stdatomic.h +158 -0
  96. data/include/stdbool.h +15 -0
  97. data/include/stdckdint.h +28 -0
  98. data/include/stddef.h +60 -0
  99. data/include/stdnoreturn.h +18 -0
  100. data/include/x86intrin.h +16 -0
  101. data/lib/rubycc/backend/aarch64.rb +1724 -0
  102. data/lib/rubycc/backend/x86_64.rb +1369 -0
  103. data/lib/rubycc/compile_error.rb +36 -0
  104. data/lib/rubycc/compiler.rb +305 -0
  105. data/lib/rubycc/doctor/builder.rb +151 -0
  106. data/lib/rubycc/doctor/cli.rb +190 -0
  107. data/lib/rubycc/doctor/fetcher.rb +84 -0
  108. data/lib/rubycc/doctor/gemfile.rb +115 -0
  109. data/lib/rubycc/doctor/verified_gems.rb +114 -0
  110. data/lib/rubycc/doctor.rb +16 -0
  111. data/lib/rubycc/driver.rb +463 -0
  112. data/lib/rubycc/front/ast.rb +528 -0
  113. data/lib/rubycc/front/constant_evaluator.rb +631 -0
  114. data/lib/rubycc/front/initializer_resolver.rb +592 -0
  115. data/lib/rubycc/front/lexeme_reader.rb +460 -0
  116. data/lib/rubycc/front/lexer.rb +232 -0
  117. data/lib/rubycc/front/parser.rb +4122 -0
  118. data/lib/rubycc/front/token.rb +49 -0
  119. data/lib/rubycc/ir/call_convention.rb +486 -0
  120. data/lib/rubycc/ir/generator.rb +6036 -0
  121. data/lib/rubycc/ir/ir.rb +417 -0
  122. data/lib/rubycc/link/compat_runtime.rb +89 -0
  123. data/lib/rubycc/link/errors.rb +13 -0
  124. data/lib/rubycc/link/executable_linker.rb +405 -0
  125. data/lib/rubycc/link/library_resolver.rb +437 -0
  126. data/lib/rubycc/link/partial_linker.rb +546 -0
  127. data/lib/rubycc/link/shared_linker.rb +1732 -0
  128. data/lib/rubycc/mkmf_shim.rb +83 -0
  129. data/lib/rubycc/objfile/ar_archive.rb +437 -0
  130. data/lib/rubycc/objfile/elf_reader.rb +646 -0
  131. data/lib/rubycc/objfile/elf_writer.rb +891 -0
  132. data/lib/rubycc/objfile/relocatable_writer.rb +376 -0
  133. data/lib/rubycc/pkgconf/cli.rb +139 -0
  134. data/lib/rubycc/pkgconf/errors.rb +39 -0
  135. data/lib/rubycc/pkgconf/model.rb +28 -0
  136. data/lib/rubycc/pkgconf/parser.rb +109 -0
  137. data/lib/rubycc/pkgconf/pkgconf.rb +15 -0
  138. data/lib/rubycc/pkgconf/resolver.rb +70 -0
  139. data/lib/rubycc/pkgconf/search_path.rb +38 -0
  140. data/lib/rubycc/pkgconf/system_path_filter.rb +131 -0
  141. data/lib/rubycc/preprocess/constant_expression.rb +219 -0
  142. data/lib/rubycc/preprocess/glibc_version.rb +124 -0
  143. data/lib/rubycc/preprocess/pp_token.rb +62 -0
  144. data/lib/rubycc/preprocess/preprocessor.rb +2020 -0
  145. data/lib/rubycc/preprocess/scanner.rb +290 -0
  146. data/lib/rubycc/preprocess/token_converter.rb +157 -0
  147. data/lib/rubycc/rmake/cli.rb +143 -0
  148. data/lib/rubycc/rmake/errors.rb +71 -0
  149. data/lib/rubycc/rmake/executor.rb +818 -0
  150. data/lib/rubycc/rmake/expander.rb +251 -0
  151. data/lib/rubycc/rmake/makefile.rb +352 -0
  152. data/lib/rubycc/rmake/model.rb +137 -0
  153. data/lib/rubycc/rmake/parser.rb +226 -0
  154. data/lib/rubycc/rmake/rmake.rb +15 -0
  155. data/lib/rubycc/type.rb +1236 -0
  156. data/lib/rubycc/version.rb +5 -0
  157. data/lib/rubycc.rb +36 -0
  158. data/lib/rubygems_plugin.rb +102 -0
  159. metadata +219 -0
@@ -0,0 +1,2020 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rbconfig"
4
+ require_relative "scanner"
5
+ require_relative "token_converter"
6
+ require_relative "constant_expression"
7
+ require_relative "glibc_version"
8
+ require_relative "../front/constant_evaluator"
9
+ require_relative "../compile_error"
10
+
11
+ module Rubycc
12
+ module Preprocess
13
+ # Entry point for translation phases 1-4: source text in, a Front::Token
14
+ # stream out. It scans preprocessing tokens (line splicing and comment
15
+ # removal), executes directives, expands macros, and converts what remains
16
+ # into ordinary tokens.
17
+ #
18
+ # The macro table and the #include search path are instance state so an
19
+ # includee shares them with its includer: a translation unit is preprocessed
20
+ # as a single accumulating pass, with #include recursion feeding an includee's
21
+ # tokens through the same line-oriented loop. Conditional groups (#if and its
22
+ # kin) select which lines reach that loop. Both object-like and function-like
23
+ # macros are expanded (6.10.3), the latter gathering their arguments across
24
+ # line boundaries; the "#" (stringize) and "##" (paste) operators, the
25
+ # compiler-supplied macros (__FILE__ and kin), the __has_* queries and
26
+ # "#pragma once" complete the model.
27
+ class Preprocessor
28
+ # The compiler-supplied ("freestanding") headers rubycc ships in the gem's
29
+ # top-level include/ directory: stdarg.h, stddef.h and kin, which glibc
30
+ # does not provide because they are the compiler's responsibility. Resolved
31
+ # relative to this file so it works from a source checkout and an installed
32
+ # gem alike (preprocess -> rubycc -> lib -> gem root, then include/).
33
+ BUNDLED_INCLUDE_DIR = File.expand_path("../../../include", __dir__).freeze
34
+
35
+ # The bundled libc compatibility headers (R8): rubycc's own copies of the C
36
+ # library's headers (stdio.h, stdlib.h, string.h and kin), shipped so a
37
+ # hosted translation unit compiles on a host that lacks the system libc's
38
+ # development headers (the distroless target). They live in two layers under
39
+ # include/libc/: a common declaration layer (BUNDLED_LIBC_INCLUDE_DIR) and a
40
+ # target-specific layer that pins the type widths, struct layouts and macro
41
+ # values to one concrete libc-and-arch ABI. Two such layers ship today,
42
+ # glibc/x86-64 and glibc/aarch64; which one is on the search path is chosen
43
+ # per instance by the `libc_arch` keyword (see #initialize), so a cross
44
+ # compile reads the target's ABI rather than the host's. This constant names
45
+ # the x86-64 default's directory -- kept so the default path and the tests
46
+ # that pin its order can refer to it -- while #default_system_include_paths
47
+ # substitutes the instance's own arch directory (@libc_arch_include_dir). The
48
+ # arch layer is searched before the common layer so a same-named header in it
49
+ # (an ABI-specific override) wins over the shared declaration.
50
+ # The libc the headers describe is *not* a directory axis: both layers carry
51
+ # the glibc and the musl value of everything the two disagree on, selected
52
+ # by #if on LIBC_MUSL_MACRO (see LIBCS), so the directory names below still
53
+ # say "glibc" only because that is where the files have always lived.
54
+ BUNDLED_LIBC_ARCH_INCLUDE_DIR = File.expand_path("../../../include/libc/glibc/x86_64", __dir__).freeze
55
+ BUNDLED_LIBC_INCLUDE_DIR = File.expand_path("../../../include/libc", __dir__).freeze
56
+
57
+ # The bundled libc-and-arch layers that ship under include/libc/glibc/;
58
+ # `libc_arch` (see #initialize) must name one of them.
59
+ LIBC_ARCHS = %w[x86_64 aarch64].freeze
60
+
61
+ # The C libraries the bundled headers can be read under; `libc` (see
62
+ # #initialize) must name one of them. This is an axis of its own, at right
63
+ # angles to `libc_arch`: the two libcs disagree on a handful of ABI values
64
+ # (struct rusage's size, O_ACCMODE, the fast-integer widths, BUFSIZ and
65
+ # kin) on every machine alike, so the difference is expressed as #if
66
+ # branches inside the bundled headers rather than as another directory
67
+ # layer -- fifteen-odd divergences do not justify duplicating a hundred
68
+ # files whose remaining content is identical, and an #if keeps both
69
+ # measured values side by side where they can be audited (R8).
70
+ #
71
+ # The musl side is complete only for x86-64: the aarch64 arch layer's five
72
+ # ABI-switched headers carry glibc's values alone, because the musl
73
+ # figures were measured on x86-64 and an arch layer is exactly where a
74
+ # value may move between machines (each of those files says so in its own
75
+ # provenance note). An aarch64 musl target therefore reads musl's common
76
+ # layer and glibc's arch layer until an aarch64 musl run measures it.
77
+ LIBCS = %w[glibc musl].freeze
78
+
79
+ # The macro the bundled headers select their musl branch on. It is
80
+ # predefined (to 1) only when `libc` is "musl"; on glibc it stays
81
+ # undefined, so `#if defined(__RUBYCC_LIBC_MUSL__)` reads "the musl ABI"
82
+ # and its #else arm is the long-standing glibc one. Unlike the platform
83
+ # macros it is reserved on *both* settings (see #reject_reserved_name): it
84
+ # names which ABI the bundled headers were pinned to when the compiler was
85
+ # configured, so a translation unit that could -D it into existence, or
86
+ # -U it away, would get headers describing one libc and an object laid out
87
+ # for the other.
88
+ LIBC_MUSL_MACRO = "__RUBYCC_LIBC_MUSL__"
89
+
90
+ # The libc system header directories, in the order gcc reports them for
91
+ # angled includes. Only the C library's own directories are listed; the
92
+ # compiler's private include directory is deliberately absent, because
93
+ # BUNDLED_INCLUDE_DIR supplies those headers instead.
94
+ #
95
+ # The first entry is Debian's multiarch directory, which is named after the
96
+ # target: `/usr/include/x86_64-linux-gnu` holds the x86-64 `bits/`, and its
97
+ # AArch64 counterpart holds a different one. It therefore belongs to
98
+ # `libc_arch` exactly like the bundled arch layer does -- see
99
+ # LIBC_SYSTEM_INCLUDE_PATHS_FOR, which #libc_system_include_paths uses per
100
+ # instance. Naming one target here unconditionally was a real defect: an
101
+ # AArch64 host searched a directory that does not exist and never looked in
102
+ # its own (GAPS V).
103
+ LIBC_MULTIARCH_INCLUDE_DIRS = {
104
+ "x86_64" => "/usr/include/x86_64-linux-gnu",
105
+ "aarch64" => "/usr/include/aarch64-linux-gnu"
106
+ }.freeze
107
+
108
+ def self.libc_system_include_paths_for(libc_arch)
109
+ multiarch = LIBC_MULTIARCH_INCLUDE_DIRS.fetch(libc_arch) do
110
+ raise ArgumentError, "unsupported libc arch: #{libc_arch.inspect}"
111
+ end
112
+ [multiarch, "/usr/include"].freeze
113
+ end
114
+
115
+ # The x86-64 baseline, kept as a constant for the same reason
116
+ # DEFAULT_SYSTEM_INCLUDE_PATHS is: it is the shape every per-instance list
117
+ # takes, with its own arch in the multiarch slot.
118
+ LIBC_SYSTEM_INCLUDE_PATHS = libc_system_include_paths_for("x86_64")
119
+
120
+ # The default system include search path: the bundled freestanding headers
121
+ # first (so rubycc's stdarg.h/stddef.h win over any same-named file further
122
+ # down), then the bundled libc compatibility headers (arch layer before
123
+ # common layer), then the host libc directories. A bundled libc header thus
124
+ # wins over the host's same-named one, yet can still reach the host copy via
125
+ # #include_next, which resumes the search past whichever directory the
126
+ # bundled header was found in. The host directories stay on the path by
127
+ # default (they are only dropped in the distroless mode, where they are
128
+ # absent anyway); the whole default path is appended after the user's
129
+ # -I/-isystem directories, and suppressed entirely by -nostdinc.
130
+ #
131
+ # This is the x86-64 baseline: #default_system_include_paths builds the same
132
+ # list per instance, substituting the instance's own arch layer for the arch
133
+ # slot, so a cross compile's path is this shape with a different arch layer.
134
+ DEFAULT_SYSTEM_INCLUDE_PATHS = [
135
+ BUNDLED_INCLUDE_DIR,
136
+ BUNDLED_LIBC_ARCH_INCLUDE_DIR,
137
+ BUNDLED_LIBC_INCLUDE_DIR,
138
+ *LIBC_SYSTEM_INCLUDE_PATHS
139
+ ].freeze
140
+
141
+ # The hermetic-headers system search path: the bundled freestanding and
142
+ # bundled libc layers only, with the host libc directories dropped. Selected
143
+ # by RUBYCC_HERMETIC_HEADERS (see #default_system_include_paths) so a full
144
+ # `gem install` can be driven with rubycc's own headers exclusively -- the
145
+ # distroless posture -- without every conftest command having to pass
146
+ # -nostdinc explicitly. It is the same set the distroless ruby.h build uses,
147
+ # so if a translation unit reaches for a declaration only the host's real
148
+ # headers carry, it fails here the way it would on a headerless image
149
+ # instead of silently borrowing it from /usr/include. Like
150
+ # DEFAULT_SYSTEM_INCLUDE_PATHS this is the x86-64 baseline;
151
+ # #default_system_include_paths builds the per-instance equivalent with the
152
+ # instance's own arch layer.
153
+ HERMETIC_SYSTEM_INCLUDE_PATHS = [
154
+ BUNDLED_INCLUDE_DIR,
155
+ BUNDLED_LIBC_ARCH_INCLUDE_DIR,
156
+ BUNDLED_LIBC_INCLUDE_DIR
157
+ ].freeze
158
+
159
+ # The environment variable that switches the default system search path to
160
+ # the hermetic (bundled-only) set. Any non-empty value other than "0"
161
+ # enables it; the default (unset) keeps the host libc directories on the
162
+ # path, so existing behaviour is unchanged.
163
+ HERMETIC_HEADERS_ENV = "RUBYCC_HERMETIC_HEADERS"
164
+
165
+ # A guard against unbounded #include recursion (a header that includes
166
+ # itself); 200 is comfortably deeper than any sane header nesting.
167
+ INCLUDE_DEPTH_LIMIT = 200
168
+
169
+ # The cumulative ceiling on how many tokens macro expansion may process
170
+ # across a whole translation unit. Blue-painting (see #expand_tokens) stops
171
+ # self-reference and mutual recursion, but nothing otherwise bounds an
172
+ # exponentially expanding macro — the classic "#define B1 B0 B0 ... #define
173
+ # B40 B39 B39" doubles its output each level, so "B40" would materialize
174
+ # 2^40 tokens and exhaust CPU and memory long before finishing. Charging one
175
+ # unit per token pulled from the work queue and tripping this ceiling turns
176
+ # that runaway into a located CompileError. The bound is a whole-run
177
+ # cumulative budget (expand_tokens runs once per gathered line and once per
178
+ # #if condition, and recurses for each argument), so an expansion that
179
+ # explodes across many small calls is still caught. The real #include
180
+ # <ruby.h> header graph — the whole CRuby + libc header set, a worst-case
181
+ # legitimate input — consumes about 137k, so one million leaves a 7x margin
182
+ # while it never fires on real code. It is deliberately not larger: because
183
+ # a doubling macro is rejected only after the full budget is processed, the
184
+ # ceiling also caps the worst-case work a hostile input can force (about
185
+ # three seconds here), so raising it would trade rejection latency for
186
+ # headroom no real translation unit needs.
187
+ EXPANSION_TOKEN_LIMIT = 1_000_000
188
+
189
+ # The ceiling on conditional-directive nesting within a single file. A
190
+ # deeply nested tower of "#if"s is not a stack risk (the frames are held in
191
+ # a heap array, not on the Ruby stack), but capping it keeps a hostile
192
+ # source from building an arbitrarily large conditional stack; 256 is far
193
+ # beyond any real header's conditional nesting.
194
+ CONDITIONAL_NESTING_LIMIT = 256
195
+
196
+ # The ceiling on parenthesis nesting inside a function-like macro's argument
197
+ # list. #collect_arguments balances parentheses with a plain integer depth
198
+ # counter (no recursion, so this is not a stack guard), but bounding it
199
+ # rejects a pathological "M(((((...)))))" up front rather than scanning an
200
+ # unbounded run. It is generous — a macro argument is a full expression, and
201
+ # the parser re-checks nesting downstream — so this only trips on clearly
202
+ # abusive input.
203
+ MACRO_ARGUMENT_NESTING_LIMIT = 2000
204
+
205
+ # The directives that steer a conditional group (6.10.1). They are acted on
206
+ # whether or not the enclosing group is active, so nesting stays balanced
207
+ # inside a skipped region; every other directive is inert while skipping.
208
+ # Kept as a Hash (used only for membership) so the check is O(1).
209
+ CONDITIONAL_DIRECTIVES = %w[if ifdef ifndef elif else endif].to_h { |name| [name, true] }.freeze
210
+
211
+ # One nesting level of a conditional. `active` is whether this frame's
212
+ # current group is being emitted (its condition held and the frame itself
213
+ # is reachable); `taken` records whether any group of this #if has been
214
+ # selected yet, so a later #elif/#else knows to stay inert; `seen_else`
215
+ # guards against an #elif/#else after the #else; `parent_active` is whether
216
+ # the enclosing context was active when the #if opened, which freezes the
217
+ # whole conditional off when it was not. `token` is the opening directive,
218
+ # for an "unterminated conditional" diagnosed at end of file.
219
+ Frame = Struct.new(:active, :taken, :seen_else, :parent_active, :token)
220
+
221
+ # One entry of the macro table. `kind` is :object or :function; `params`
222
+ # is the ordered parameter-name list (empty for an object macro, and for a
223
+ # function macro it excludes any trailing "..."); `variadic` records that a
224
+ # variable part is present so the rest arguments are meaningful; `va_name`
225
+ # is the identifier that stands for the rest arguments in the replacement
226
+ # list -- "__VA_ARGS__" for the ISO bare-"..." form and the written name for
227
+ # the GNU "name..." form, nil when not variadic; `replacement` is the raw
228
+ # replacement-list tokens as written.
229
+ Macro = Struct.new(:kind, :params, :variadic, :replacement, :va_name)
230
+
231
+ # One function-like invocation's arguments. `raw` is each argument's
232
+ # unexpanded tokens (what "#" and "##" operate on), `commas` the top-level
233
+ # comma tokens between them (kept verbatim so #__VA_ARGS__ can reproduce the
234
+ # exact spacing), and `expanded` a per-argument memo of the fully expanded
235
+ # form, filled lazily so an argument used only by "#"/"##" is never expanded.
236
+ Invocation = Struct.new(:raw, :commas, :expanded)
237
+
238
+ # The macros the preprocessor supplies itself (6.10.8): each is expanded
239
+ # from the use site, so its value cannot be a fixed replacement list stored
240
+ # at definition time. __GNUC__ is deliberately absent (DESIGN R7), so a
241
+ # header cannot select a gcc-specific path. None may be redefined or undefined.
242
+ # Kept as a Hash (used only for membership) so the check is O(1).
243
+ BUILTIN_MACROS = %w[__FILE__ __LINE__ __STDC__ __STDC_VERSION__ __RUBYCC__].to_h { |name| [name, true] }.freeze
244
+
245
+ # The identifiers __has_builtin (6.10.1) answers true for: exactly the
246
+ # builtins rubycc's front end actually recognizes — the varargs intrinsics,
247
+ # the branch-prediction hint, the stack allocator, offsetof, the
248
+ # constant/choose folds, the count-leading/trailing-zero scans, the
249
+ # unreachable hint, memcpy, the three overflow-checked arithmetic forms,
250
+ # the nine __atomic_* forms and the ten legacy __sync_* forms. Every other
251
+ # builtin query is false, so a header that guards a fallback behind
252
+ # __has_builtin (e.g. json's bswap path) takes the fallback for one rubycc
253
+ # does not provide. Kept in sync with the parser's builtin keywords. Kept
254
+ # as a Hash (used only for membership) so the check is O(1).
255
+ KNOWN_BUILTINS = %w[__builtin_va_start __builtin_va_arg __builtin_va_end __builtin_va_copy
256
+ __builtin_expect __builtin_alloca __builtin_offsetof
257
+ __builtin_constant_p __builtin_choose_expr
258
+ __builtin_ctz __builtin_ctzll __builtin_clz __builtin_clzll
259
+ __builtin_unreachable __builtin_memcpy
260
+ __builtin_add_overflow __builtin_sub_overflow
261
+ __builtin_mul_overflow
262
+ __atomic_load_n __atomic_store_n __atomic_exchange_n
263
+ __atomic_compare_exchange_n
264
+ __atomic_fetch_add __atomic_fetch_sub
265
+ __atomic_add_fetch __atomic_sub_fetch
266
+ __atomic_or_fetch __atomic_thread_fence
267
+ __sync_fetch_and_add __sync_fetch_and_sub
268
+ __sync_add_and_fetch __sync_sub_and_fetch
269
+ __sync_or_and_fetch __sync_lock_test_and_set
270
+ __sync_lock_release __sync_synchronize
271
+ __sync_bool_compare_and_swap
272
+ __sync_val_compare_and_swap].to_h { |name| [name, true] }.freeze
273
+
274
+ # The platform macros gcc keeps predefined even under strict ISO C
275
+ # (-std=c11): only the reserved forms (a leading underscore followed by
276
+ # another underscore or an uppercase letter, 7.1.3), so headers relying on
277
+ # "linux", "unix" or "i386" (the non-reserved spellings, gcc drops these
278
+ # under -std=c11) still see them undefined here. __GNUC__ is deliberately
279
+ # excluded (DESIGN R7): rubycc targets Linux/ELF LP64 only, so this fixed
280
+ # set is enough for glibc's own dispatch (e.g. gnu/stubs.h) to settle on
281
+ # the right branch, without claiming gcc compatibility beyond that. Unlike
282
+ # BUILTIN_MACROS these are ordinary #define'd entries in @macros, so a
283
+ # translation unit may #undef or redefine them (gcc allows this too).
284
+ #
285
+ # These are the macros every supported target shares; the CPU-identifying
286
+ # ones are per-target and arrive through `arch_macros` below.
287
+ PREDEFINED_PLATFORM_MACROS = %w[__linux__ __gnu_linux__ __unix__ __ELF__
288
+ __LP64__ _LP64 __STDC_HOSTED__].freeze
289
+
290
+ # The CPU-identifying macros for each target, the subset of gcc's that
291
+ # glibc's own headers dispatch on. Getting these from the target rather
292
+ # than fixing them was forced by the aarch64 backend: a unit compiled for
293
+ # aarch64 that asked `#ifdef __x86_64__` used to take the x86-64 branch,
294
+ # and the cross libc headers were being read under the wrong CPU identity.
295
+ X86_64_ARCH_MACROS = %w[__x86_64__ __amd64__].freeze
296
+ AARCH64_ARCH_MACROS = %w[__aarch64__ __AARCH64EL__].freeze
297
+
298
+ # The numeric macros gcc predefines: the limit/size ones describing the
299
+ # target's fundamental types, and the memory-order enumerators the
300
+ # __atomic_* builtins take as an argument (grouped here because they are
301
+ # the same kind of thing — a fixed integer replacement text a translation
302
+ # unit may #undef, not a use-site-computed BUILTIN_MACROS entry).
303
+ # glibc's headers reach for these directly when __GNUC__
304
+ # is absent (e.g. limits.h's __LONG_MAX__ via ruby's special_consts.h), so
305
+ # they must carry gcc's exact spellings — value base (hex vs decimal) and
306
+ # integer suffix — for a gcc-differential #if to agree. The right-hand sides
307
+ # are the verbatim replacement texts of `gcc -dM -E </dev/null` on this
308
+ # x86-64 LP64 target; they become ordinary object macros (a translation unit
309
+ # may #undef or redefine them). __WCHAR_MIN__'s value is a parenthesized
310
+ # expression referring to another of these, which expands recursively at the
311
+ # use site like any macro. Only the reserved "__X__" forms are provided;
312
+ # __GNUC__ and version macros stay absent (DESIGN R7). The replacement text
313
+ # is re-scanned into pp-tokens rather than hand-built, so multi-token values
314
+ # need no special casing.
315
+ PREDEFINED_NUMERIC_MACROS = {
316
+ "__CHAR_BIT__" => "8",
317
+ "__SCHAR_MAX__" => "0x7f",
318
+ "__SHRT_MAX__" => "0x7fff",
319
+ "__INT_MAX__" => "0x7fffffff",
320
+ "__LONG_MAX__" => "0x7fffffffffffffffL",
321
+ "__LONG_LONG_MAX__" => "0x7fffffffffffffffLL",
322
+ "__WCHAR_MAX__" => "0x7fffffff",
323
+ "__WCHAR_MIN__" => "(-__WCHAR_MAX__ - 1)",
324
+ "__WINT_MAX__" => "0xffffffffU",
325
+ "__WINT_MIN__" => "0U",
326
+ "__PTRDIFF_MAX__" => "0x7fffffffffffffffL",
327
+ "__SIZE_MAX__" => "0xffffffffffffffffUL",
328
+ "__INTMAX_MAX__" => "0x7fffffffffffffffL",
329
+ "__UINTMAX_MAX__" => "0xffffffffffffffffUL",
330
+ "__INTPTR_MAX__" => "0x7fffffffffffffffL",
331
+ "__UINTPTR_MAX__" => "0xffffffffffffffffUL",
332
+ "__SIZEOF_INT__" => "4",
333
+ "__SIZEOF_LONG__" => "8",
334
+ "__SIZEOF_LONG_LONG__" => "8",
335
+ "__SIZEOF_SHORT__" => "2",
336
+ "__SIZEOF_POINTER__" => "8",
337
+ "__SIZEOF_SIZE_T__" => "8",
338
+ "__SIZEOF_PTRDIFF_T__" => "8",
339
+ "__SIZEOF_FLOAT__" => "4",
340
+ "__SIZEOF_DOUBLE__" => "8",
341
+ "__SIZEOF_WCHAR_T__" => "4",
342
+ "__SIZEOF_WINT_T__" => "4",
343
+ # The memory-order arguments the __atomic_* builtins take (C11 7.17.3's
344
+ # memory_order enumerators, which gcc predefines under these spellings).
345
+ # The values are the verbatim ones `gcc -dM -E </dev/null` prints on this
346
+ # target, so a header comparing them (or building one out of another)
347
+ # agrees with a gcc build. rubycc implements every atomic operation at
348
+ # the strongest order regardless of which of these is passed — see
349
+ # IR::Generator#gen_builtin_atomic — but the constants must still carry
350
+ # gcc's values, because a caller may compute with them.
351
+ "__ATOMIC_RELAXED" => "0",
352
+ "__ATOMIC_CONSUME" => "1",
353
+ "__ATOMIC_ACQUIRE" => "2",
354
+ "__ATOMIC_RELEASE" => "3",
355
+ "__ATOMIC_ACQ_REL" => "4",
356
+ "__ATOMIC_SEQ_CST" => "5"
357
+ }.freeze
358
+
359
+ # The libc this host's C library is: "musl" or "glibc" (see LIBCS). Read
360
+ # from RbConfig's arch triplet, which is how MRI itself distinguishes a
361
+ # musl build ("x86_64-linux-musl") from a glibc one ("x86_64-linux") --
362
+ # the same source test/abi_harness/harness.rb's #host_libc and
363
+ # tools/verify_gem_tests.rb's environment_string read, so the compiler,
364
+ # the ABI harness and the verification records all agree on what "this
365
+ # environment" is. It is the default for `libc` below, which is what makes
366
+ # an unconfigured compile on a musl host read the musl branches; a cross
367
+ # compile passes the target's own.
368
+ def self.host_libc
369
+ RbConfig::CONFIG["arch"].to_s.include?("musl") ? "musl" : "glibc"
370
+ end
371
+
372
+ # The glibc version macros, predefined on a glibc target so the bundled
373
+ # <features.h> does not have to name a version it cannot know. __GLIBC__
374
+ # is a constant (glibc's major has been 2 since 1997); the minor is
375
+ # measured from the C library the compile will link against, because a
376
+ # single shipped header set otherwise reports one host's version on every
377
+ # host, and a version gate then selects a branch the local libc may not be
378
+ # able to back (docs/development/GAPS.md gap U).
379
+ GLIBC_MAJOR_MACRO = "__GLIBC__"
380
+ GLIBC_MINOR_MACRO = "__GLIBC_MINOR__"
381
+ GLIBC_MAJOR = 2
382
+
383
+ # The measured glibc minor version for the `libc_arch` target, or nil when
384
+ # this host offers nothing to measure (see GlibcVersion). nil is not an
385
+ # error and not a substitute value: the two macros are then left
386
+ # undefined, and the bundled <features.h> supplies its own fallback pair
387
+ # -- the reference platform's 2.39 -- exactly as it did before this was
388
+ # measured at all. Keeping the fallback in the header rather than
389
+ # repeating the number here also keeps a header read outside rubycc (or
390
+ # under -nostdinc with the host's own headers) on the same value.
391
+ def self.host_glibc_minor(libc_arch = "x86_64")
392
+ GlibcVersion.minor_for(libc_arch)
393
+ end
394
+
395
+ # `char_unsigned` says whether plain `char` is unsigned on the target being
396
+ # compiled for (it is under AAPCS64, and is not under the x86-64 System V
397
+ # psABI, hence the default). When it is, __CHAR_UNSIGNED__ joins the
398
+ # predefined macros with the value gcc gives it, so a header can select the
399
+ # same branch it would there — the bundled <limits.h> uses exactly that to
400
+ # pick CHAR_MIN/CHAR_MAX.
401
+ # `arch_macros` names the target's CPU-identifying macros (see
402
+ # X86_64_ARCH_MACROS); it defaults to x86-64's, the default target.
403
+ # `libc_arch` selects which bundled libc-and-arch header layer sits on the
404
+ # default search path ("x86_64" or "aarch64", see LIBC_ARCHS); it defaults to
405
+ # x86-64 so an unconfigured host compile is byte-for-byte unchanged, and a
406
+ # cross compile passes the target's own so its ABI headers (struct stat's
407
+ # 128-byte aarch64 layout, the narrower nlink_t/blksize_t, the unsigned
408
+ # WCHAR_MIN/MAX and kin) are read instead of the host's.
409
+ # `libc` selects which C library's ABI those bundled headers describe
410
+ # ("glibc" or "musl", see LIBCS); it defaults to the host's own (see
411
+ # .host_libc), and on "musl" it predefines LIBC_MUSL_MACRO so the headers
412
+ # take their musl branches.
413
+ # `glibc_minor` is the glibc minor version the version macros are to
414
+ # report on a glibc target; it defaults to the one measured from the C
415
+ # library that target links against (see .host_glibc_minor), and nil --
416
+ # which is also what an unmeasurable host yields -- leaves both macros
417
+ # undefined for the bundled <features.h> to fall back on. It is a keyword
418
+ # so a caller can pin a version deliberately (a cross compile against a
419
+ # sysroot this host cannot search, and the tests' fallback case). The
420
+ # default measures only on a glibc target: on "musl" the value is unused,
421
+ # and reading a C library to answer a question nobody asks would cost
422
+ # every musl translation unit a megabyte-scale read for nothing.
423
+ def initialize(char_unsigned: false, arch_macros: X86_64_ARCH_MACROS, libc_arch: "x86_64",
424
+ libc: Preprocessor.host_libc,
425
+ glibc_minor: (libc == "glibc" ? Preprocessor.host_glibc_minor(libc_arch) : nil))
426
+ unless LIBC_ARCHS.include?(libc_arch)
427
+ raise ArgumentError, "unsupported libc arch: #{libc_arch.inspect} (expected one of #{LIBC_ARCHS.join(", ")})"
428
+ end
429
+ unless LIBCS.include?(libc)
430
+ raise ArgumentError, "unsupported libc: #{libc.inspect} (expected one of #{LIBCS.join(", ")})"
431
+ end
432
+ unless glibc_minor.nil? || (glibc_minor.is_a?(Integer) && !glibc_minor.negative?)
433
+ raise ArgumentError, "glibc minor version must be a non-negative Integer or nil: #{glibc_minor.inspect}"
434
+ end
435
+
436
+ # The bundled libc arch layer this instance searches (see
437
+ # BUNDLED_LIBC_ARCH_INCLUDE_DIR). For the x86-64 default it equals that
438
+ # constant, so the default search path is identical to before.
439
+ @libc_arch_include_dir = File.expand_path("../../../include/libc/glibc/#{libc_arch}", __dir__)
440
+ # The host libc directories this instance searches. The multiarch slot
441
+ # follows the same `libc_arch` as the bundled layer above, so a compile
442
+ # never looks for another target's `bits/` (GAPS V).
443
+ @libc_system_include_paths = self.class.libc_system_include_paths_for(libc_arch)
444
+ # name (String) => Macro.
445
+ @macros = {}
446
+ (arch_macros + PREDEFINED_PLATFORM_MACROS).each { |name| @macros[name] = predefined_target_macro }
447
+ @macros["__CHAR_UNSIGNED__"] = predefined_target_macro if char_unsigned
448
+ @macros[LIBC_MUSL_MACRO] = predefined_target_macro if libc == "musl"
449
+ PREDEFINED_NUMERIC_MACROS.each { |name, text| @macros[name] = predefined_numeric_macro(text) }
450
+ # The glibc version pair, defined only on a glibc target and only when
451
+ # the version could be measured. They are ordinary numeric macros like
452
+ # the ones above (a translation unit may #undef or redefine them), and
453
+ # the bundled <features.h> defines each only when it is absent, so this
454
+ # is what makes a version gate agree with the host's own headers. On
455
+ # musl, and when nothing could be measured, nothing is defined here and
456
+ # <features.h> keeps supplying the reference platform's pair, which is
457
+ # what every compile did before the measurement existed.
458
+ if libc == "glibc" && glibc_minor
459
+ @macros[GLIBC_MAJOR_MACRO] = predefined_numeric_macro(GLIBC_MAJOR.to_s)
460
+ @macros[GLIBC_MINOR_MACRO] = predefined_numeric_macro(glibc_minor.to_s)
461
+ end
462
+ @include_depth = 0
463
+ # Absolute paths of files that asked (via "#pragma once") to be read at
464
+ # most once; a later #include resolving to one of them is skipped.
465
+ @pragma_once = {}
466
+ # Absolute path => index into @include_paths of the -I directory a file
467
+ # was found in. Only files resolved along the search path get an entry
468
+ # (the main source file and a quote-relative resolution beside its
469
+ # includer never do), which is exactly what #include_next needs to tell
470
+ # "resume the search past here" from "there is no here" (GNU extension).
471
+ @include_origin = {}
472
+ # Resolved include path (the exact spelling #include resolves to) =>
473
+ # that file's scanned pp-token array. Scanning (phases 2-3) is a pure
474
+ # function of the file's bytes — no macro state reaches it — and both
475
+ # PPToken and the directive walk are non-mutating (painting copies), so
476
+ # one scan per header serves every re-#include verbatim. This is where
477
+ # a real unit burns most of its time otherwise: ruby.h's include graph
478
+ # re-includes the same headers hundreds of times, and each guard-skipped
479
+ # body still had to be re-scanned to find its #endif (Step 108).
480
+ @scan_cache = {}
481
+ # Resolved include path => the file's include-guard macro name, or nil
482
+ # when its shape rules the optimization out (gcc's multiple-include
483
+ # optimization, Step 109). A header whose entire significant content is
484
+ # wrapped in one "#ifndef G ... #endif" (or "#if !defined(G)") behaves,
485
+ # when G is defined, exactly like an empty file: the walk would activate
486
+ # nothing and change no state. So a re-#include whose recorded guard is
487
+ # currently defined skips the directive walk outright. Detection is a
488
+ # pure function of the scanned tokens (see #detect_include_guard); the
489
+ # skip consults the live macro table, so an #undef of the guard makes
490
+ # the next #include process the file again.
491
+ @guard_cache = {}
492
+ end
493
+
494
+ def run(source, filename:, include_paths: [], defines: [], system_includes: true)
495
+ TokenConverter.new.convert(
496
+ preprocess(source, filename: filename, include_paths: include_paths,
497
+ defines: defines, system_includes: system_includes)
498
+ )
499
+ end
500
+
501
+ # Runs translation phases 1-4 and returns the resulting preprocessing-token
502
+ # stream (terminated by the unit's :eof), before it is converted into
503
+ # Front tokens. It is what #run builds on, and what the `-E` driver mode
504
+ # re-spells into preprocessed text. `defines` is the ordered command-line
505
+ # `-D`/`-U` list (see #apply_command_line_definitions).
506
+ # `system_includes` (the default) appends the compiler-supplied and libc
507
+ # directories after the caller's -I/-isystem set, so an angled #include of
508
+ # <stdarg.h> or a libc header resolves with no explicit -I; the driver's
509
+ # -nostdinc passes it false to search only the caller's directories.
510
+ def preprocess(source, filename:, include_paths: [], defines: [], system_includes: true)
511
+ system_paths = system_includes ? default_system_include_paths : []
512
+ @system_include_paths = system_paths.map { |path| File.expand_path(path) }
513
+ @include_paths = include_paths + system_paths
514
+ # #resolve_include's cache keys a resolved path off @include_paths (and,
515
+ # for quote includes, the includer's directory), so it must start empty
516
+ # every run rather than survive across calls with a different search path.
517
+ @resolve_cache = {}
518
+ # The whole-run macro-expansion budget (see EXPANSION_TOKEN_LIMIT), reset
519
+ # here so every translation unit starts with a full allowance.
520
+ @expansion_tokens = 0
521
+ # The presumed-line state a #line directive sets (6.10.4): a delta added
522
+ # to a token's physical line for __LINE__, and a presumed file name for
523
+ # __FILE__ (nil = the token's own file). It is per-file, saved and
524
+ # restored across #include (see #process_include).
525
+ @presumed_line_delta = 0
526
+ @presumed_file = nil
527
+ apply_command_line_definitions(defines)
528
+ pp_tokens = Scanner.new(source, filename: filename).scan
529
+ output = []
530
+ process_lines(pp_tokens, filename, output)
531
+ # process_lines stops at the unit's end-of-file marker without emitting
532
+ # it; carry it through so the converter can terminate its stream.
533
+ output << pp_tokens.last
534
+ output
535
+ end
536
+
537
+ private
538
+
539
+ # The system search path appended after the caller's -I/-isystem set: the
540
+ # full default (bundled headers ahead of the host libc directories), or the
541
+ # hermetic bundled-only set when RUBYCC_HERMETIC_HEADERS selects it. The arch
542
+ # slot is this instance's @libc_arch_include_dir rather than the fixed
543
+ # constant, so a cross compile searches the target's ABI layer; for the
544
+ # x86-64 default the result equals DEFAULT_SYSTEM_INCLUDE_PATHS /
545
+ # HERMETIC_SYSTEM_INCLUDE_PATHS exactly. The environment is read per call
546
+ # rather than cached so a process can flip the mode between translation units
547
+ # (the mkmf conftest sequence does not, but a test driving several compiles
548
+ # in-process may).
549
+ def default_system_include_paths
550
+ bundled = [BUNDLED_INCLUDE_DIR, @libc_arch_include_dir, BUNDLED_LIBC_INCLUDE_DIR]
551
+ return bundled if hermetic_headers?
552
+
553
+ [*bundled, *@libc_system_include_paths]
554
+ end
555
+
556
+ def hermetic_headers?
557
+ value = ENV[HERMETIC_HEADERS_ENV]
558
+ !value.nil? && !value.empty? && value != "0"
559
+ end
560
+
561
+ # Applies the driver's command-line `-D`/`-U` requests before the source is
562
+ # read, in the order they were given (so a later `-U` undoes an earlier
563
+ # `-D`, as gcc does). Each is turned into the directive it stands for and
564
+ # run through the ordinary directive machinery — `-DNAME` into
565
+ # `#define NAME 1`, `-DNAME=VALUE` into `#define NAME VALUE` (the split on
566
+ # the first `=` keeps a function-like `-DF(x)=…` intact), and `-UNAME` into
567
+ # `#undef NAME` — so redefinition and validation behave exactly as an
568
+ # in-source directive would. Their (empty) output is discarded; only the
569
+ # effect on the shared macro table matters.
570
+ def apply_command_line_definitions(defines)
571
+ return if defines.empty?
572
+
573
+ preamble = defines.map { |op, arg| command_line_directive(op, arg) }.join("\n") + "\n"
574
+ tokens = Scanner.new(preamble, filename: "<command-line>").scan
575
+ process_lines(tokens, "<command-line>", [])
576
+ end
577
+
578
+ def command_line_directive(op, arg)
579
+ case op
580
+ when :define
581
+ name, value = arg.split("=", 2)
582
+ "#define #{name} #{value || "1"}"
583
+ when :undef
584
+ "#undef #{arg}"
585
+ end
586
+ end
587
+
588
+ # Walks one file's preprocessing tokens a logical line at a time. A line
589
+ # whose first token is "#" is a directive (6.10); every other line has its
590
+ # tokens macro-expanded into `output`, but only while every enclosing
591
+ # conditional is active. `stack` is this file's conditional nesting; a
592
+ # frame it opens must be closed in the same file (a #include appears only on
593
+ # an active line and runs with its own fresh stack), so any frame still open
594
+ # at end of file is an unterminated conditional.
595
+ #
596
+ # A function-like macro invocation may span several physical lines, so the
597
+ # tokens of a run of consecutive active non-directive lines (newlines and
598
+ # all) are gathered and expanded together. A directive line, an inactive
599
+ # region, or end of file ends the run: activeness only ever changes at a
600
+ # directive, so a run is uniformly active, and a call left open where a run
601
+ # ends is diagnosed there (a directive splitting an argument list is 6.10.3
602
+ # undefined behavior). Newlines are dropped by the expander, so the output
603
+ # stream stays free of them.
604
+ def process_lines(tokens, filename, output)
605
+ stack = []
606
+ index = 0
607
+ at_line_start = true
608
+ run = []
609
+ while index < tokens.length
610
+ tok = tokens[index]
611
+ break if tok.eof?
612
+
613
+ if tok.newline?
614
+ run << tok if active?(stack)
615
+ at_line_start = true
616
+ index += 1
617
+ elsif at_line_start && tok.punct?("#")
618
+ expand_run(run, output)
619
+ run = []
620
+ index = process_directive(tokens, index, filename, output, stack)
621
+ at_line_start = true
622
+ else
623
+ at_line_start = false
624
+ run << tok if active?(stack)
625
+ index += 1
626
+ end
627
+ end
628
+ expand_run(run, output)
629
+ raise_at(stack.last.token, "unterminated conditional directive") unless stack.empty?
630
+ end
631
+
632
+ def expand_run(run, output)
633
+ expand_tokens(run, output) unless run.empty?
634
+ end
635
+
636
+ # Whether output is currently being emitted: true unless some enclosing
637
+ # conditional selected against it. A frame's `active` already folds in its
638
+ # parents' state, so only the innermost need be consulted.
639
+ def active?(stack)
640
+ stack.empty? || stack.last.active
641
+ end
642
+
643
+ # Dispatches the directive that "#" at `hash_index` introduces and returns
644
+ # the index of the first token past its terminating newline. A conditional
645
+ # directive is always acted on (it may re-activate a skipped region or nest
646
+ # inside it); every other directive, and even a malformed one, is silently
647
+ # dropped while skipping, matching how gcc discards an excluded group whole.
648
+ # An empty directive line (just "#") is the null directive (6.10p2).
649
+ def process_directive(tokens, hash_index, filename, output, stack)
650
+ hash = tokens[hash_index]
651
+ args, next_index = collect_line(tokens, hash_index + 1)
652
+ return next_index if args.empty?
653
+
654
+ name = args[0]
655
+ body = args[1..]
656
+ if name.type == :identifier && CONDITIONAL_DIRECTIVES.key?(name.text)
657
+ dispatch_conditional(name, body, stack)
658
+ return next_index
659
+ end
660
+ return next_index unless active?(stack)
661
+
662
+ unless name.type == :identifier
663
+ raise_at(name, "invalid preprocessing directive '##{name.text}'")
664
+ end
665
+
666
+ case name.text
667
+ when "include" then handle_include(hash, body, output, filename)
668
+ when "include_next" then handle_include_next(hash, body, output, filename)
669
+ when "define" then handle_define(name, body)
670
+ when "undef" then handle_undef(name, body)
671
+ when "line" then handle_line(name, body)
672
+ when "error" then handle_error(hash, body)
673
+ when "pragma" then handle_pragma(body, filename)
674
+ else
675
+ raise_at(name, "invalid preprocessing directive '##{name.text}'")
676
+ end
677
+ next_index
678
+ end
679
+
680
+ # --- conditional inclusion (6.10.1) ----------------------------------------
681
+
682
+ def dispatch_conditional(name, body, stack)
683
+ case name.text
684
+ when "if" then handle_if(name, body, stack, :if)
685
+ when "ifdef" then handle_if(name, body, stack, :ifdef)
686
+ when "ifndef" then handle_if(name, body, stack, :ifndef)
687
+ when "elif" then handle_elif(name, body, stack)
688
+ when "else" then handle_else(name, stack)
689
+ when "endif" then handle_endif(name, stack)
690
+ end
691
+ end
692
+
693
+ # Opens a new conditional frame. Its first group's condition is only
694
+ # evaluated when the enclosing context is active; inside a skipped region
695
+ # the frame is pushed inert (never taken, so no #elif/#else can revive it)
696
+ # and the condition is left unread, so an undefined name or bad expression
697
+ # there is not diagnosed (6.10.1p6, and matching gcc).
698
+ def handle_if(name, body, stack, kind)
699
+ if stack.length >= CONDITIONAL_NESTING_LIMIT
700
+ raise_at(name, "#{name.text} directives nested too deeply")
701
+ end
702
+
703
+ if active?(stack)
704
+ condition = evaluate_group(name, body, kind)
705
+ stack.push(Frame.new(condition, condition, false, true, name))
706
+ else
707
+ stack.push(Frame.new(false, true, false, false, name))
708
+ end
709
+ end
710
+
711
+ def evaluate_group(name, body, kind)
712
+ case kind
713
+ when :if then evaluate_if_expression(name, body)
714
+ when :ifdef then defined_condition(name, body, true)
715
+ when :ifndef then defined_condition(name, body, false)
716
+ end
717
+ end
718
+
719
+ def handle_elif(name, body, stack)
720
+ raise_at(name, "#elif without #if") if stack.empty?
721
+
722
+ frame = stack.last
723
+ raise_at(name, "#elif after #else") if frame.seen_else
724
+ # Only evaluate when the enclosing context is active and no earlier group
725
+ # of this #if was taken; otherwise this branch cannot win, so its
726
+ # expression is left unevaluated (and undiagnosed).
727
+ if frame.parent_active && !frame.taken
728
+ condition = evaluate_if_expression(name, body)
729
+ frame.active = condition
730
+ frame.taken = true if condition
731
+ else
732
+ frame.active = false
733
+ end
734
+ end
735
+
736
+ def handle_else(name, stack)
737
+ raise_at(name, "#else without #if") if stack.empty?
738
+
739
+ frame = stack.last
740
+ raise_at(name, "#else after #else") if frame.seen_else
741
+ frame.seen_else = true
742
+ # The else group is taken exactly when the context is active and nothing
743
+ # earlier was; either way no further group of this #if can be selected.
744
+ frame.active = frame.parent_active && !frame.taken
745
+ frame.taken = true
746
+ end
747
+
748
+ def handle_endif(name, stack)
749
+ raise_at(name, "#endif without #if") if stack.empty?
750
+
751
+ stack.pop
752
+ end
753
+
754
+ # #ifdef/#ifndef NAME: NAME must be a lone identifier, and the frame is
755
+ # active when the macro's presence in the table matches the sense wanted.
756
+ def defined_condition(name, body, want_defined)
757
+ raise_at(name, "no macro name given in ##{name.text} directive") if body.empty?
758
+
759
+ macro = body[0]
760
+ raise_at(macro, "macro names must be identifiers") unless macro.type == :identifier
761
+ raise_at(body[1], "extra tokens at end of ##{name.text} directive") if body.length > 1
762
+ # gcc makes "#ifdef" of the __has_* query operators true, and a header
763
+ # uses that to prefer the operator over a config.h fallback. They are
764
+ # safe to expose because each answers honestly — __has_builtin says 0
765
+ # for a builtin rubycc lacks, and a header __has_include reports present
766
+ # is one rubycc can now compile (the gcc alternate keywords the kernel
767
+ # UAPI headers lean on, "__signed__" and kin, are recognized).
768
+ defined_macro_name?(macro.text) == want_defined
769
+ end
770
+
771
+ # Whether `defined`/#ifdef/#ifndef sees NAME as a macro (6.10.8p1): an
772
+ # ordinary entry in the macro table, one of the self-supplied builtins
773
+ # (__FILE__ etc., 6.10.8.1) that never occupies a table slot because its
774
+ # value is computed at the use site, or a __has_* query operator (gcc
775
+ # extension both directives share, see query_operator_name?).
776
+ def defined_macro_name?(name)
777
+ @macros.key?(name) || BUILTIN_MACROS.key?(name) || query_operator_name?(name)
778
+ end
779
+
780
+ # Evaluates a #if/#elif controlling constant-expression (6.10.1) to a
781
+ # boolean. In order: fold each preprocessor operator (`defined` and the
782
+ # `__has_*` queries) to 1/0 with its operand unexpanded, macro-expand what
783
+ # remains, fold operators a second time (a header may hide a __has_* query
784
+ # behind a macro, e.g. RBIMPL_HAS_BUILTIN(x) -> __has_builtin(x), so the
785
+ # operator only surfaces after expansion; gcc likewise re-honors a
786
+ # macro-produced `defined`), then replace every surviving identifier with 0,
787
+ # convert to ordinary tokens, and evaluate the parsed expression with the
788
+ # shared constant evaluator. A non-zero result is true. One post-expansion
789
+ # pass suffices: fold_operators never expands its own operands, so a query
790
+ # it uncovers cannot itself reveal another.
791
+ def evaluate_if_expression(directive, body)
792
+ raise_at(directive, "##{directive.text} with no expression") if body.empty?
793
+
794
+ expanded = []
795
+ expand_tokens(fold_operators(body), expanded)
796
+ refolded = fold_operators(expanded)
797
+ neutral = refolded.map { |tok| tok.type == :identifier ? number_zero(tok) : tok }
798
+ tokens = to_front_tokens(neutral, directive)
799
+ node = ConstantExpressionParser.new(tokens, directive.text).parse
800
+ evaluate_constant(node)
801
+ end
802
+
803
+ # Converts the neutralized expression tokens to the Front::Token stream the
804
+ # expression parser consumes, terminating it with an :eof. A floating
805
+ # constant is not an integer constant-expression, so it is rejected here
806
+ # (6.10.1p1) before it can reach the evaluator.
807
+ def to_front_tokens(pp_tokens, directive)
808
+ tokens = TokenConverter.new.convert(pp_tokens)
809
+ floating = tokens.find { |tok| tok.type == :float }
810
+ raise_at_front(floating, "floating constant in preprocessor expression") if floating
811
+ tokens << Front::Token.new(type: :eof, value: nil, filename: directive.filename,
812
+ line: directive.line, column: directive.column,
813
+ source_line: directive.source_line)
814
+ tokens
815
+ end
816
+
817
+ def evaluate_constant(node)
818
+ Front::ConstantEvaluator.evaluate(node) != 0
819
+ rescue Front::ConstantEvaluator::DivisionByZero => e
820
+ raise_at_front(e.token, "division by zero in preprocessor expression")
821
+ rescue Front::ConstantEvaluator::NotConstant => e
822
+ raise_at_front(e.token, "token is not valid in preprocessor expressions")
823
+ end
824
+
825
+ # Rewrites each preprocessor operator in a #if expression to the pp-number 1
826
+ # or 0, its operand left unexpanded (6.10.1p1, p4): `defined` against the
827
+ # macro table, `__has_include` against the include search, `__has_attribute`
828
+ # true only for the layout attributes rubycc honors (aligned/packed), and
829
+ # `__has_builtin`
830
+ # true only for the varargs intrinsics. Any other token passes through to be
831
+ # macro-expanded (an unrecognized identifier later neutralizes to 0).
832
+ def fold_operators(body)
833
+ result = []
834
+ index = 0
835
+ while index < body.length
836
+ tok = body[index]
837
+ if tok.type == :identifier && (handler = PP_OPERATORS[tok.text])
838
+ value, index = send(handler, tok, body, index + 1)
839
+ result << number_flag(tok, value)
840
+ else
841
+ result << tok
842
+ index += 1
843
+ end
844
+ end
845
+ result
846
+ end
847
+
848
+ # Each #if operator's fold method, keyed by its spelling.
849
+ PP_OPERATORS = {
850
+ "defined" => :fold_defined, "__has_include" => :fold_has_include,
851
+ "__has_attribute" => :fold_has_attribute, "__has_builtin" => :fold_has_builtin
852
+ }.freeze
853
+
854
+ def fold_defined(operator, body, index)
855
+ name, index = read_defined_operand(operator, body, index)
856
+ [defined_macro_name?(name), index]
857
+ end
858
+
859
+ # gcc treats the __has_* query operators as satisfying `defined`, so
860
+ # `#if defined(__has_builtin)` is 1 there; a header uses that to prefer
861
+ # the operator over a config.h fallback (ruby/internal/has/builtin.h).
862
+ # The `defined` operator itself is not answered here (only the __has_*
863
+ # queries), so this excludes it from PP_OPERATORS.
864
+ def query_operator_name?(name)
865
+ PP_OPERATORS.key?(name) && name != "defined"
866
+ end
867
+
868
+ # __has_include ( "f" | <f> ): true when the header resolves like the same
869
+ # #include would (quote relative to this file, then the search path).
870
+ def fold_has_include(operator, body, index)
871
+ raise_at(operator, "missing '(' after '__has_include'") unless body[index]&.punct?("(")
872
+
873
+ close = closing_paren(operator, body, index)
874
+ kind, name = parse_header_name(operator, body[(index + 1)...close])
875
+ [include_exists?(kind, name, operator.filename), close + 1]
876
+ end
877
+
878
+ # The GNU attributes rubycc gives real semantics: the layout attributes the
879
+ # parser honors on a struct/union (Step 28), and the constructor/destructor
880
+ # attributes that register a function in .init_array / .fini_array (Step
881
+ # 155). Every other attribute is accepted and discarded, so __has_attribute
882
+ # answers true only for these. Kept as a Hash (used only for membership) so
883
+ # the check is O(1).
884
+ KNOWN_ATTRIBUTES = %w[aligned packed constructor destructor].to_h { |name| [name, true] }.freeze
885
+
886
+ # __has_attribute ( X ): true for the attributes rubycc actually acts on
887
+ # (in either the plain or the "__name__" spelling), false for every other
888
+ # name. The name is normalized the same way the parser normalizes an
889
+ # attribute token.
890
+ def fold_has_attribute(operator, body, index)
891
+ name, index = read_paren_identifier(operator, body, index, "__has_attribute")
892
+ [KNOWN_ATTRIBUTES.key?(normalize_attribute_name(name)), index]
893
+ end
894
+
895
+ # Collapses a "__name__" attribute spelling to "name" (a single leading and
896
+ # trailing "__" pair stripped when both are present), matching the parser's
897
+ # #normalize_attribute_name so "__aligned__" and "aligned" agree here too.
898
+ def normalize_attribute_name(name)
899
+ if name.start_with?("__") && name.end_with?("__") && name.length > 4
900
+ name[2..-3]
901
+ else
902
+ name
903
+ end
904
+ end
905
+
906
+ def fold_has_builtin(operator, body, index)
907
+ name, index = read_paren_identifier(operator, body, index, "__has_builtin")
908
+ [KNOWN_BUILTINS.key?(name), index]
909
+ end
910
+
911
+ # The index of the ")" that closes the "(" at `index`; the operand between
912
+ # them is a header name, which has no nested parentheses.
913
+ def closing_paren(operator, body, index)
914
+ close = index + 1
915
+ close += 1 until body[close].nil? || body[close].punct?(")")
916
+ raise_at(operator, "missing ')' after '#{operator.text}'") if body[close].nil?
917
+ close
918
+ end
919
+
920
+ # Reads a "( identifier )" operand for the __has_* attribute/builtin queries,
921
+ # returning [identifier-text, index-past-")"].
922
+ def read_paren_identifier(operator, body, index, what)
923
+ name = body[index + 1]
924
+ unless body[index]&.punct?("(") && name&.type == :identifier && body[index + 2]&.punct?(")")
925
+ raise_at(operator, "operator '#{what}' requires a parenthesized identifier")
926
+ end
927
+ [name.text, index + 3]
928
+ end
929
+
930
+ # Whether a header name resolves without reading it: quote form beside the
931
+ # querying file then along the search path, angled form only the latter.
932
+ def include_exists?(kind, name, includer)
933
+ directories = []
934
+ directories << File.dirname(includer) if kind == :quote
935
+ directories.concat(@include_paths)
936
+ directories.any? { |dir| File.file?(File.join(dir, name)) }
937
+ end
938
+
939
+ # Reads the operand of a `defined` operator starting at `index`, returning
940
+ # [macro-name, index-past-operand]. Accepts NAME or ( NAME ).
941
+ def read_defined_operand(directive, body, index)
942
+ if body[index]&.punct?("(")
943
+ name = body[index + 1]
944
+ unless name && name.type == :identifier && body[index + 2]&.punct?(")")
945
+ raise_at(directive, "operator 'defined' requires an identifier")
946
+ end
947
+ [name.text, index + 3]
948
+ else
949
+ name = body[index]
950
+ raise_at(directive, "operator 'defined' requires an identifier") unless name&.type == :identifier
951
+
952
+ [name.text, index + 1]
953
+ end
954
+ end
955
+
956
+ def number_flag(site, present)
957
+ number_token(site, present ? "1" : "0")
958
+ end
959
+
960
+ def number_zero(site)
961
+ number_token(site, "0")
962
+ end
963
+
964
+ def number_token(site, text)
965
+ PPToken.new(
966
+ type: :pp_number, text: text,
967
+ filename: site.filename, line: site.line, column: site.column,
968
+ source_line: site.source_line, space_before: site.space_before
969
+ )
970
+ end
971
+
972
+ # A PREDEFINED_TARGET_MACROS entry: an ordinary object macro whose sole
973
+ # replacement token is the pp-number "1". Its position ("<built-in>", line
974
+ # 0) is a placeholder that no diagnostic ever surfaces: #substitute always
975
+ # relocates a replacement token to the invocation site via #relocate
976
+ # before it can reach the token stream, so this position only ever exists
977
+ # transiently inside the macro table.
978
+ def predefined_target_macro
979
+ token = PPToken.new(
980
+ type: :pp_number, text: "1",
981
+ filename: "<built-in>", line: 0, column: 0, source_line: ""
982
+ )
983
+ Macro.new(:object, [], false, [token])
984
+ end
985
+
986
+ # A PREDEFINED_NUMERIC_MACROS entry: an ordinary object macro whose
987
+ # replacement is `text` re-scanned into pp-tokens, so a multi-token value
988
+ # (like __WCHAR_MIN__'s parenthesized expression) becomes a proper token
989
+ # list without hand-building each token. The scanner appends an :eof (and
990
+ # never a newline for a single line), dropped here. As with the target
991
+ # macros the tokens carry a placeholder "<built-in>" location that #relocate
992
+ # replaces with the use site before any diagnostic could reference it.
993
+ def predefined_numeric_macro(text)
994
+ tokens = Scanner.new(text, filename: "<built-in>").scan.reject(&:eof?)
995
+ Macro.new(:object, [], false, tokens)
996
+ end
997
+
998
+ # The tokens of the logical line starting at `start` (up to but excluding
999
+ # the newline), paired with the index just past that newline. A line ended
1000
+ # by end-of-file yields the eof index so the caller's loop can stop there.
1001
+ def collect_line(tokens, start)
1002
+ stop = start
1003
+ stop += 1 until tokens[stop].newline? || tokens[stop].eof?
1004
+ rest = tokens[start...stop]
1005
+ next_index = tokens[stop].newline? ? stop + 1 : stop
1006
+ [rest, next_index]
1007
+ end
1008
+
1009
+ # --- #include ----------------------------------------------------------
1010
+
1011
+ def handle_include(hash, body, output, includer)
1012
+ kind, name = parse_include_name(hash, body)
1013
+ path = resolve_include(kind, name, includer, hash)
1014
+ process_include(hash, name, path, output)
1015
+ end
1016
+
1017
+ # #include_next NAME (GNU extension): like #include, but the search for
1018
+ # NAME resumes just past the -I directory `includer` itself was found in,
1019
+ # rather than starting over from the front of the list. It exists so a
1020
+ # header can shadow a same-named one further down the search path while
1021
+ # still #including that later copy (gcc's fixinclude wrappers use it on
1022
+ # limits.h and syslimits.h). Both "NAME" and <NAME> are accepted, but
1023
+ # unlike a plain #include the quote form does not additionally search
1024
+ # `includer`'s own directory (gcc's behavior).
1025
+ def handle_include_next(hash, body, output, includer)
1026
+ kind, name = parse_include_name(hash, body)
1027
+ path = resolve_include_next(kind, name, includer, hash)
1028
+ process_include(hash, name, path, output)
1029
+ end
1030
+
1031
+ # Reads and processes the header resolved to `path`, shared by #include
1032
+ # and #include_next once each has found its file.
1033
+ def process_include(hash, name, path, output)
1034
+ # A header that asked for "#pragma once" is read at most once per unit; a
1035
+ # later #include resolving to the same file is silently skipped (6.10.6).
1036
+ return if @pragma_once.key?(File.expand_path(path))
1037
+
1038
+ # A guarded header whose guard macro is currently defined would emit
1039
+ # nothing and change nothing — skip its walk entirely (see @guard_cache).
1040
+ guard = @guard_cache[path]
1041
+ return if guard && @macros.key?(guard)
1042
+
1043
+ raise_at(hash, "#include nested too deeply") if @include_depth >= INCLUDE_DEPTH_LIMIT
1044
+
1045
+ @include_depth += 1
1046
+ # A #line presumption is per-file (6.10.4): the includee starts with its
1047
+ # own natural line/file numbering, and the includer's presumption resumes
1048
+ # when the include returns.
1049
+ saved_delta = @presumed_line_delta
1050
+ saved_file = @presumed_file
1051
+ @presumed_line_delta = 0
1052
+ @presumed_file = nil
1053
+ begin
1054
+ # The includee's tokens carry its own filename and line numbers (N3), so
1055
+ # a diagnostic raised inside it points at the header, not the includer.
1056
+ # A header's scan is cached under its resolved spelling: the token
1057
+ # array is reused read-only across re-#includes (see @scan_cache),
1058
+ # while the directive walk below still runs each time so conditional
1059
+ # inclusion sees the current macro state.
1060
+ tokens = @scan_cache[path] ||=
1061
+ Scanner.new(read_source(path, name, hash), filename: path).scan
1062
+ process_lines(tokens, path, output)
1063
+ # Analyze the guard shape once per file, after a successful walk.
1064
+ @guard_cache[path] = detect_include_guard(tokens) unless @guard_cache.key?(path)
1065
+ ensure
1066
+ @presumed_line_delta = saved_delta
1067
+ @presumed_file = saved_file
1068
+ @include_depth -= 1
1069
+ end
1070
+ end
1071
+
1072
+ # Decides whether a scanned file has the classic include-guard shape: its
1073
+ # first significant line opens "#ifndef G" (or "#if !defined(G)" /
1074
+ # "#if !defined G") and the matching "#endif" is the last significant
1075
+ # thing in the file. Returns G's name, or nil for any other shape. Two
1076
+ # disqualifiers matter for correctness, not just fit: an #else/#elif on
1077
+ # the guard level would make the file emit *different* content when the
1078
+ # guard is defined (so skipping would be wrong), and tokens after the
1079
+ # closing #endif would be lost by a skip. The C23 #elifdef/#elifndef
1080
+ # spellings are rejected the same way, defensively — this compiler does
1081
+ # not process them, but a guard analysis must not assume that. Purely
1082
+ # syntactic over the token array: like the scan itself, the answer never
1083
+ # depends on macro state, so it is computed once per file.
1084
+ def detect_include_guard(tokens)
1085
+ index = 0
1086
+ index += 1 while tokens[index]&.newline?
1087
+ return nil unless tokens[index]&.punct?("#")
1088
+
1089
+ args, index = collect_line(tokens, index + 1)
1090
+ name = guard_macro_name(args)
1091
+ return nil if name.nil?
1092
+
1093
+ depth = 1
1094
+ at_line_start = true
1095
+ while index < tokens.length
1096
+ tok = tokens[index]
1097
+ break if tok.eof?
1098
+
1099
+ if tok.newline?
1100
+ at_line_start = true
1101
+ index += 1
1102
+ elsif at_line_start && tok.punct?("#")
1103
+ args, index = collect_line(tokens, index + 1)
1104
+ directive = args[0]
1105
+ next unless directive&.type == :identifier
1106
+
1107
+ case directive.text
1108
+ when "if", "ifdef", "ifndef"
1109
+ depth += 1
1110
+ when "elif", "else", "elifdef", "elifndef"
1111
+ return nil if depth == 1
1112
+ when "endif"
1113
+ depth -= 1
1114
+ if depth.zero?
1115
+ index += 1 while tokens[index]&.newline?
1116
+ return tokens[index].nil? || tokens[index].eof? ? name : nil
1117
+ end
1118
+ end
1119
+ else
1120
+ at_line_start = false
1121
+ index += 1
1122
+ end
1123
+ end
1124
+ # Ran out before the guard closed: malformed (or truncated) file; the
1125
+ # directive walk owns the diagnostic, this is simply not a guard.
1126
+ nil
1127
+ end
1128
+
1129
+ # The macro a candidate guard line tests, or nil when the line is not one
1130
+ # of the two recognized spellings.
1131
+ def guard_macro_name(args)
1132
+ directive = args[0]
1133
+ return nil unless directive&.type == :identifier
1134
+
1135
+ if directive.text == "ifndef"
1136
+ return args[1].text if args.length == 2 && args[1].type == :identifier
1137
+ elsif directive.text == "if"
1138
+ rest = args[1..]
1139
+ bang = rest[0]&.punct?("!") && rest[1]&.type == :identifier && rest[1].text == "defined"
1140
+ return nil unless bang
1141
+ return rest[2].text if rest.length == 3 && rest[2].type == :identifier
1142
+ if rest.length == 5 && rest[2].punct?("(") && rest[3].type == :identifier && rest[4].punct?(")")
1143
+ return rest[3].text
1144
+ end
1145
+ end
1146
+ nil
1147
+ end
1148
+
1149
+ # Parses a #include/#include_next operand. A directly written header-name
1150
+ # is already complete and is kept verbatim: in particular, identifiers
1151
+ # inside `<...>` are not accidentally expanded. Otherwise the operand is
1152
+ # macro-expanded and the resulting token sequence must form a header-name
1153
+ # (6.10.2); this is what permits `#define H "extconf.h"` followed by
1154
+ # `#include H`, as emitted by mkmf for pg.
1155
+ def parse_include_name(hash, body)
1156
+ return parse_header_name(hash, body) if direct_header_name?(body)
1157
+
1158
+ expanded = []
1159
+ expand_tokens(body, expanded)
1160
+ parse_header_name(hash, expanded)
1161
+ end
1162
+
1163
+ # Whether `body` is already a complete quoted or angled header-name. The
1164
+ # scanner does not treat header-names specially, so an angled form arrives
1165
+ # as "<" ... ">". A malformed direct form is deliberately left for the
1166
+ # macro-expansion path: a macro can legally supply the missing pieces.
1167
+ def direct_header_name?(body)
1168
+ return true if body.length == 1 && body[0].type == :string
1169
+
1170
+ return false unless body.first&.punct?("<")
1171
+
1172
+ body.index { |token| token.punct?(">") } == body.length - 1
1173
+ end
1174
+
1175
+ # Reconstructs a header name from a complete token sequence. The
1176
+ # characters are taken verbatim (6.10.2), never macro-expanded here; the
1177
+ # caller has already decided whether expansion was needed.
1178
+ def parse_header_name(hash, body)
1179
+ raise_at(hash, "#include expects \"FILENAME\" or <FILENAME>") if body.empty?
1180
+
1181
+ first = body[0]
1182
+ if first.type == :string
1183
+ raise_at(body[1], "extra tokens at end of #include directive") if body.length > 1
1184
+ [:quote, first.text[1..-2]]
1185
+ elsif first.punct?("<")
1186
+ close = body.index { |t| t.punct?(">") }
1187
+ raise_at(first, "missing terminating > character") if close.nil?
1188
+ if close < body.length - 1
1189
+ raise_at(body[close + 1], "extra tokens at end of #include directive")
1190
+ end
1191
+ name = body[1...close].map(&:text).join
1192
+ raise_at(first, "empty filename in #include directive") if name.empty?
1193
+ [:angle, name]
1194
+ else
1195
+ raise_at(first, "#include expects \"FILENAME\" or <FILENAME>")
1196
+ end
1197
+ end
1198
+
1199
+ # Resolves a header name to a filesystem path. A quoted include is looked
1200
+ # for first beside the file that names it, then along the search path; an
1201
+ # angled include only along the search path (6.10.2p2-3). A match found
1202
+ # along the search path records which directory it came from, so a later
1203
+ # #include_next from this same file knows where to resume.
1204
+ #
1205
+ # The same header name is resolved over and over within one translation
1206
+ # unit (each use site re-#includes it, guarded headers included, before
1207
+ # Step 109's scan cache short-circuits their body), so successful lookups
1208
+ # are memoized in @resolve_cache. A quote include's cache key carries the
1209
+ # includer's directory since the "beside" candidate is includer-relative;
1210
+ # an angle include's key is just the name, since it never consults the
1211
+ # includer. A cache hit skips #record_include_origin: the first resolution
1212
+ # already recorded whatever origin this same path implies.
1213
+ def resolve_include(kind, name, includer, hash)
1214
+ key = kind == :quote ? [File.dirname(includer), name] : name
1215
+ cached = @resolve_cache[key]
1216
+ return cached if cached
1217
+
1218
+ if kind == :quote
1219
+ beside = File.join(File.dirname(includer), name)
1220
+ if File.file?(beside)
1221
+ @resolve_cache[key] = beside
1222
+ return beside
1223
+ end
1224
+ end
1225
+
1226
+ index, path = search_include_paths(name, 0)
1227
+ raise_at(hash, "#{name}: No such file or directory") unless path
1228
+
1229
+ record_include_origin(path, index)
1230
+ @resolve_cache[key] = path
1231
+ path
1232
+ end
1233
+
1234
+ # Resolves a header name for #include_next: search resumes one directory
1235
+ # past wherever `includer` itself was found along the search path. A file
1236
+ # with no recorded origin (the main source file, or a quote-relative
1237
+ # resolution beside its includer) has no "here" to resume past, so gcc
1238
+ # falls back to plain #include semantics for it, which this does too.
1239
+ def resolve_include_next(kind, name, includer, hash)
1240
+ origin = @include_origin[File.expand_path(includer)]
1241
+ return resolve_include(kind, name, includer, hash) unless origin
1242
+
1243
+ index, path = search_include_paths(name, origin + 1)
1244
+ raise_at(hash, "#{name}: No such file or directory") unless path
1245
+
1246
+ record_include_origin(path, index)
1247
+ path
1248
+ end
1249
+
1250
+ # The first directory in @include_paths, starting the scan at `start`,
1251
+ # holding `name`; returns [its index, the joined path] or [nil, nil].
1252
+ def search_include_paths(name, start)
1253
+ @include_paths.each_with_index do |dir, index|
1254
+ next if index < start
1255
+
1256
+ candidate = File.join(dir, name)
1257
+ return [index, candidate] if File.file?(candidate)
1258
+ end
1259
+ [nil, nil]
1260
+ end
1261
+
1262
+ def record_include_origin(path, index)
1263
+ @include_origin[File.expand_path(path)] = index
1264
+ end
1265
+
1266
+ def read_source(path, name, hash)
1267
+ File.read(path)
1268
+ rescue SystemCallError
1269
+ raise_at(hash, "#{name}: No such file or directory")
1270
+ end
1271
+
1272
+ # --- #define / #undef ------------------------------------------------------
1273
+
1274
+ def handle_define(directive, body)
1275
+ raise_at(directive, "no macro name given in #define directive") if body.empty?
1276
+
1277
+ name = body[0]
1278
+ raise_at(name, "macro names must be identifiers") unless name.type == :identifier
1279
+ reject_reserved_name(name, "define")
1280
+
1281
+ rest = body[1..]
1282
+ first = rest[0]
1283
+ # A "(" abutting the name with no separating space opens a parameter list
1284
+ # (function-like); a space before it, or its absence, makes an object
1285
+ # macro whose replacement merely begins with that token.
1286
+ macro =
1287
+ if first && first.punct?("(") && !first.space_before
1288
+ parse_function_macro(name, rest)
1289
+ else
1290
+ Macro.new(:object, [], false, rest)
1291
+ end
1292
+ validate_replacement(macro)
1293
+
1294
+ existing = @macros[name.text]
1295
+ if existing.nil?
1296
+ @macros[name.text] = macro
1297
+ elsif !identical_macro?(existing, macro) &&
1298
+ !compatible_restrict_redefinition?(name.text, existing, macro) &&
1299
+ !system_header_redefinition?(name)
1300
+ # A benign redefinition (an identical definition) is allowed; a
1301
+ # differing one is an error (6.10.3p2, simplified to token spellings).
1302
+ raise_at(name, "macro '#{name.text}' redefined")
1303
+ else
1304
+ # Ruby's musl config.h and libpq's pg_config.h use the two reserved
1305
+ # spellings of the same restrict qualifier. Keep the later spelling
1306
+ # so subsequent expansion follows the header that most recently
1307
+ # established the platform contract.
1308
+ @macros[name.text] = macro
1309
+ end
1310
+ end
1311
+
1312
+ # GCC accepts the musl/Ruby and libpq pair of restrict definitions as a
1313
+ # harmless portability difference: `__restrict__` and `__restrict` are
1314
+ # both recognized by the front end as the same ignored qualifier. Do not
1315
+ # weaken ordinary macro redefinition diagnostics for any other spelling.
1316
+ def compatible_restrict_redefinition?(name, existing, replacement)
1317
+ return false unless name == "restrict"
1318
+ return false unless existing.kind == :object && replacement.kind == :object
1319
+
1320
+ spellings = ["__restrict", "__restrict__"]
1321
+ [existing.replacement, replacement.replacement].all? do |tokens|
1322
+ tokens.length == 1 && tokens.first.type == :identifier && spellings.include?(tokens.first.text)
1323
+ end
1324
+ end
1325
+
1326
+ # GCC diagnoses a conflicting redefinition from a system header as a
1327
+ # warning, not a preprocessing error. Rubycc has no warning stream, so
1328
+ # keep the header's later definition and continue; this is required by
1329
+ # libffi, whose target header changes FFI_GO_CLOSURES after fiddle's local
1330
+ # compatibility header intentionally set it to zero. User-source
1331
+ # conflicts retain the strict diagnostic above.
1332
+ def system_header_redefinition?(name)
1333
+ filename = name.filename.to_s
1334
+ return false if filename.empty? || filename.start_with?("<")
1335
+
1336
+ path = File.expand_path(filename)
1337
+ @system_include_paths.any? { |root| path == root || path.start_with?("#{root}/") }
1338
+ end
1339
+
1340
+ # Parses a function-like macro's parameter list, `rest` being the tokens
1341
+ # after the macro name with rest[0] the opening "(". Returns the Macro, its
1342
+ # `replacement` the tokens past the closing ")". The list is a comma-
1343
+ # separated run of identifiers, optionally empty, optionally ending in a
1344
+ # variable part: the ISO bare "..." (spelled "__VA_ARGS__" in the body, and
1345
+ # "(...)" alone is allowed) or the GNU "name..." (a parameter name fused to
1346
+ # "...", spelled by that name in the body).
1347
+ def parse_function_macro(name, rest)
1348
+ params = []
1349
+ variadic = false
1350
+ va_name = nil
1351
+ index = 1
1352
+ unless rest[index]&.punct?(")")
1353
+ loop do
1354
+ token = rest[index]
1355
+ if token&.punct?("...")
1356
+ variadic = true
1357
+ va_name = "__VA_ARGS__"
1358
+ index += 1
1359
+ break
1360
+ elsif token&.type == :identifier
1361
+ raise_at(token, "duplicate macro parameter \"#{token.text}\"") if params.include?(token.text)
1362
+
1363
+ # A "name..." names the variable part; the name owns the rest
1364
+ # arguments and no further parameters may follow it.
1365
+ if rest[index + 1]&.punct?("...")
1366
+ variadic = true
1367
+ va_name = token.text
1368
+ index += 2
1369
+ break
1370
+ end
1371
+
1372
+ params << token.text
1373
+ index += 1
1374
+ else
1375
+ raise_at(token || name, "expected parameter name in macro parameter list")
1376
+ end
1377
+
1378
+ separator = rest[index]
1379
+ if separator&.punct?(",")
1380
+ index += 1
1381
+ elsif separator&.punct?(")")
1382
+ break
1383
+ else
1384
+ raise_at(separator || name, "expected ',' or ')' in macro parameter list")
1385
+ end
1386
+ end
1387
+ raise_at(rest[index] || name, "missing ')' in macro parameter list") unless rest[index]&.punct?(")")
1388
+ end
1389
+ Macro.new(:function, params, variadic, rest[(index + 1)..] || [], va_name)
1390
+ end
1391
+
1392
+ # Checks a replacement list for well-formed "#" and "##" placement at
1393
+ # definition time (6.10.3.2p1, 6.10.3.3p1). "##" may not sit at either end
1394
+ # of any replacement list; in a function-like macro "#" must be followed by
1395
+ # a parameter (or __VA_ARGS__), the only operands it can stringize. In an
1396
+ # object-like macro "#" is an ordinary token, so it is left unexamined.
1397
+ def validate_replacement(macro)
1398
+ rep = macro.replacement
1399
+ edge = rep.first if rep.first&.punct?("##")
1400
+ edge ||= rep.last if rep.last&.punct?("##")
1401
+ raise_at(edge, "'##' cannot appear at either end of a macro expansion") if edge
1402
+
1403
+ return unless macro.kind == :function
1404
+
1405
+ rep.each_with_index do |tok, index|
1406
+ next unless tok.punct?("#")
1407
+
1408
+ operand = rep[index + 1]
1409
+ raise_at(tok, "'#' is not followed by a macro parameter") unless parameter_ref?(macro, operand)
1410
+ end
1411
+ end
1412
+
1413
+ # Whether `tok` names one of `macro`'s parameters, counting the variable
1414
+ # part's name (ISO "__VA_ARGS__" or the GNU "name...") as a parameter; the
1415
+ # two spellings "#" and "##" may take.
1416
+ def parameter_ref?(macro, tok)
1417
+ return false unless tok&.type == :identifier
1418
+
1419
+ macro.params.include?(tok.text) || variadic_ref?(macro, tok)
1420
+ end
1421
+
1422
+ # A macro name may not shadow a builtin (6.10.8.4) nor be the "defined"
1423
+ # operator (6.10.1p4); both diagnose rather than silently redefine. The
1424
+ # libc-selecting macro is refused the same way, on either libc setting: it
1425
+ # records the ABI the bundled headers were pinned to at configuration
1426
+ # time, so a unit that could define or undefine it would read one libc's
1427
+ # headers while the rest of the compile assumed the other's.
1428
+ def reject_reserved_name(name, verb)
1429
+ if name.text == "defined"
1430
+ raise_at(name, "\"defined\" cannot be used as a macro name")
1431
+ elsif BUILTIN_MACROS.key?(name.text) || name.text == LIBC_MUSL_MACRO
1432
+ verb = verb == "define" ? "define" : "undefine"
1433
+ raise_at(name, "cannot #{verb} builtin macro \"#{name.text}\"")
1434
+ end
1435
+ end
1436
+
1437
+ def handle_undef(directive, body)
1438
+ raise_at(directive, "no macro name given in #undef directive") if body.empty?
1439
+
1440
+ name = body[0]
1441
+ raise_at(name, "macro names must be identifiers") unless name.type == :identifier
1442
+ reject_reserved_name(name, "undef")
1443
+ raise_at(body[1], "extra tokens at end of #undef directive") if body.length > 1
1444
+ # Undefining a name that was never a macro is not an error (6.10.3.5p2).
1445
+ @macros.delete(name.text)
1446
+ end
1447
+
1448
+ # Two definitions are the same when their kind, parameter names, variadic
1449
+ # flag, variable-part name and replacement-list spellings all agree
1450
+ # (6.10.3p1-2); the name distinguishes an ISO from a GNU named variadic.
1451
+ def identical_macro?(one, other)
1452
+ one.kind == other.kind && one.variadic == other.variadic &&
1453
+ one.va_name == other.va_name && one.params == other.params &&
1454
+ identical_replacement?(one.replacement, other.replacement)
1455
+ end
1456
+
1457
+ def identical_replacement?(one, other)
1458
+ return false unless one.length == other.length
1459
+
1460
+ one.zip(other).all? { |a, b| a.type == b.type && a.text == b.text }
1461
+ end
1462
+
1463
+ # --- #error ----------------------------------------------------------------
1464
+
1465
+ def handle_error(hash, body)
1466
+ # The message is the directive's tokens spelled out and single-spaced; it
1467
+ # is never macro-expanded (matching gcc).
1468
+ message = body.map(&:text).join(" ")
1469
+ raise_at(hash, message.empty? ? "#error" : message)
1470
+ end
1471
+
1472
+ # --- #line -----------------------------------------------------------------
1473
+
1474
+ # Acts on a "#line" directive (6.10.4). Its arguments are macro-expanded
1475
+ # first (6.10.4p5), then read as a digit sequence and an optional string:
1476
+ # the *next* source line is presumed to be that line number, and the
1477
+ # presumed file name is changed when a string is given (kept otherwise).
1478
+ # Both feed __LINE__ / __FILE__ (see #expand_builtin) and diagnostic
1479
+ # wording; the physical tokens keep their real positions, only the
1480
+ # presumption changes. A file that generates code with #line markers (e.g.
1481
+ # gperf's zonetab.h "#line 1 \"zonetab.list\"") relies on this being
1482
+ # accepted rather than a hard error.
1483
+ def handle_line(directive, body)
1484
+ raise_at(directive, "#line directive requires a positive integer argument") if body.empty?
1485
+
1486
+ expanded = []
1487
+ expand_tokens(body, expanded)
1488
+
1489
+ number = expanded[0]
1490
+ unless number&.type == :pp_number && number.text.match?(/\A[0-9]+\z/)
1491
+ raise_at(number || directive, "#line directive requires a positive integer argument")
1492
+ end
1493
+ line_no = number.text.to_i
1494
+
1495
+ presumed_file = @presumed_file
1496
+ rest = expanded[1..]
1497
+ unless rest.empty?
1498
+ file = rest[0]
1499
+ unless file.type == :string && file.text.start_with?('"')
1500
+ raise_at(file, "invalid filename in #line directive")
1501
+ end
1502
+ raise_at(rest[1], "extra tokens at end of #line directive") if rest.length > 1
1503
+ presumed_file = decode_line_filename(file.text)
1504
+ end
1505
+
1506
+ # The next physical line (the directive's line + 1) is presumed to be
1507
+ # `line_no`, so a token on physical line P presents as
1508
+ # line_no + (P - (directive.line + 1)) = P + (line_no - directive.line - 1).
1509
+ @presumed_line_delta = line_no - directive.line - 1
1510
+ @presumed_file = presumed_file
1511
+ end
1512
+
1513
+ # The raw name a #line filename string literal denotes: its surrounding
1514
+ # quotes removed and its \" and \\ escapes resolved, so __FILE__ (which
1515
+ # re-escapes a raw value) reproduces the original name.
1516
+ def decode_line_filename(spelling)
1517
+ spelling[1..-2].gsub(/\\(["\\])/, '\1')
1518
+ end
1519
+
1520
+ # --- #pragma ---------------------------------------------------------------
1521
+
1522
+ # Acts on a "#pragma" (6.10.6). "#pragma once" records the current file so a
1523
+ # future #include of it is skipped; every other pragma (including a bare
1524
+ # one) is accepted and discarded. The "_Pragma" operator form (6.10.9) is
1525
+ # handled during rescanning; see #consume_pragma_operator.
1526
+ def handle_pragma(body, filename)
1527
+ first = body[0]
1528
+ return unless first&.type == :identifier && first.text == "once"
1529
+
1530
+ @pragma_once[File.expand_path(filename)] = true
1531
+ end
1532
+
1533
+ # --- macro expansion -------------------------------------------------------
1534
+
1535
+ # Expands `tokens` into `output` by rescanning (6.10.3.4). The tokens are
1536
+ # driven through a work queue: the head is examined and, when it names a
1537
+ # macro it is not painted against, replaced in place by its (rescannable)
1538
+ # substitution, which is pushed back on the front so the next turn sees it.
1539
+ # Anything that is not an active macro name is emitted; newlines are the
1540
+ # inter-line glue of a gathered run and never reach the output.
1541
+ #
1542
+ # Painting is per token: an object macro, and every literal token of a
1543
+ # function macro's replacement, is painted with the macro's own name added
1544
+ # to the token it came from, so that name cannot expand again through it
1545
+ # (this alone halts self-reference and mutual recursion). Argument tokens
1546
+ # keep their own painting untouched, which is what lets a macro name that
1547
+ # arrives from an argument still expand in its new surroundings.
1548
+ def expand_tokens(tokens, output)
1549
+ queue = tokens.dup
1550
+ until queue.empty?
1551
+ tok = queue.shift
1552
+ # Charge one unit of the whole-run expansion budget per token examined.
1553
+ # A macro's substitution is pushed back onto the queue and re-examined,
1554
+ # so an exponentially expanding macro is charged for every token it
1555
+ # generates and trips this ceiling instead of running away.
1556
+ @expansion_tokens += 1
1557
+ if @expansion_tokens > EXPANSION_TOKEN_LIMIT
1558
+ raise_at(tok, "macro expansion is too large (possible runaway or exponentially expanding macro)")
1559
+ end
1560
+ if pragma_operator?(tok)
1561
+ consume_pragma_operator(tok, queue)
1562
+ elsif expandable_builtin?(tok)
1563
+ queue.unshift(*expand_builtin(tok))
1564
+ elsif expandable_macro?(tok)
1565
+ macro = @macros[tok.text]
1566
+ if macro.kind == :object
1567
+ queue.unshift(*substitute(tok, macro, nil))
1568
+ else
1569
+ expand_function_macro(tok, macro, queue, output)
1570
+ end
1571
+ elsif !tok.newline?
1572
+ output << tok
1573
+ end
1574
+ end
1575
+ end
1576
+
1577
+ # Whether `tok` should expand now: an identifier naming a macro that its own
1578
+ # painting does not forbid (6.10.3.4).
1579
+ def expandable_macro?(tok)
1580
+ tok.type == :identifier && @macros.key?(tok.text) && !tok.suppress.include?(tok.text)
1581
+ end
1582
+
1583
+ # Whether `tok` names a compiler-supplied macro (6.10.8). These cannot be
1584
+ # redefined (rejected at #define), so the user table never hides one; and
1585
+ # each expands to a non-identifier, so it can neither recurse nor need paint.
1586
+ def expandable_builtin?(tok)
1587
+ tok.type == :identifier && BUILTIN_MACROS.key?(tok.text)
1588
+ end
1589
+
1590
+ # Whether `tok` is the "_Pragma" operator (6.10.9). It is handled during
1591
+ # rescanning, not as a directive, so it works whether written literally or
1592
+ # produced by macro expansion (Ruby's config.h defines its symbol-export
1593
+ # markers as "_Pragma(...)"), which is exactly the case that must be
1594
+ # accepted for <ruby.h> to preprocess.
1595
+ def pragma_operator?(tok)
1596
+ tok.type == :identifier && tok.text == "_Pragma"
1597
+ end
1598
+
1599
+ # Consumes a "_Pragma ( string-literal )" operator and drops all four
1600
+ # tokens. The operand would destringize into a #pragma line, but the only
1601
+ # pragma this compiler acts on is "once", which is meaningless mid-file via
1602
+ # _Pragma, so every operand is accepted and ignored (ROADMAP's "accept
1603
+ # only"). Newlines between the tokens are inter-token whitespace. A "_Pragma"
1604
+ # not followed by a parenthesized string literal is a hard error.
1605
+ def consume_pragma_operator(operator, queue)
1606
+ skip_queued_newlines(queue)
1607
+ malformed_pragma(operator) unless queue.first&.punct?("(")
1608
+ queue.shift # "("
1609
+ skip_queued_newlines(queue)
1610
+ malformed_pragma(operator) unless queue.first&.type == :string
1611
+ queue.shift # the string literal (ignored)
1612
+ skip_queued_newlines(queue)
1613
+ malformed_pragma(operator) unless queue.first&.punct?(")")
1614
+ queue.shift # ")"
1615
+ end
1616
+
1617
+ def skip_queued_newlines(queue)
1618
+ queue.shift while queue.first&.newline?
1619
+ end
1620
+
1621
+ def malformed_pragma(operator)
1622
+ raise_at(operator, "_Pragma takes a parenthesized string literal")
1623
+ end
1624
+
1625
+ # The single token a builtin macro stands for at its use site: the current
1626
+ # file and line (6.10.8.1), or a fixed conformance constant. __FILE__ is the
1627
+ # source name as a string literal with " and \ escaped; __LINE__ the line as
1628
+ # a preprocessing number.
1629
+ def expand_builtin(tok)
1630
+ token =
1631
+ case tok.text
1632
+ # __FILE__/__LINE__ honor any #line presumption in force (6.10.4): the
1633
+ # presumed file overrides the token's own, and the presumed-line delta
1634
+ # shifts its physical line. Both are the identity until a #line runs.
1635
+ when "__FILE__" then string_token(tok, @presumed_file || tok.filename)
1636
+ when "__LINE__" then number_token(tok, (tok.line + @presumed_line_delta).to_s)
1637
+ when "__STDC__" then number_token(tok, "1")
1638
+ when "__STDC_VERSION__" then number_token(tok, "201112L")
1639
+ when "__RUBYCC__" then number_token(tok, "1")
1640
+ end
1641
+ [token]
1642
+ end
1643
+
1644
+ # A string-literal preprocessing token whose spelling encodes `value`, its
1645
+ # embedded " and \ escaped so the converter reads back the original text.
1646
+ def string_token(site, value)
1647
+ PPToken.new(
1648
+ type: :string, text: quote(value),
1649
+ filename: site.filename, line: site.line, column: site.column,
1650
+ source_line: site.source_line, space_before: site.space_before
1651
+ )
1652
+ end
1653
+
1654
+ def quote(value)
1655
+ "\"#{value.gsub(/["\\]/) { |ch| "\\#{ch}" }}\""
1656
+ end
1657
+
1658
+ # Handles a function-like macro name pulled from the queue. A name not
1659
+ # followed (across any newlines) by "(" is a plain identifier, not a call
1660
+ # (6.10.3p10), so it is emitted as is. Otherwise the arguments up to the
1661
+ # matching ")" are consumed and checked, then the painted substitution is
1662
+ # pushed back on the queue for rescanning. Arguments are carried both raw
1663
+ # (for "#"/"##") and, lazily, pre-expanded, so each is expanded at most once.
1664
+ def expand_function_macro(tok, macro, queue, output)
1665
+ unless call_follows?(queue)
1666
+ output << tok
1667
+ return
1668
+ end
1669
+
1670
+ raw, commas = collect_arguments(tok, queue)
1671
+ raw = match_arity(tok, macro, raw)
1672
+ invocation = Invocation.new(raw, commas, Array.new(raw.length))
1673
+ queue.unshift(*substitute(tok, macro, invocation))
1674
+ end
1675
+
1676
+ # Whether the next non-newline token waiting in `queue` opens an argument
1677
+ # list; a call may sit on a later line than its macro name.
1678
+ def call_follows?(queue)
1679
+ index = 0
1680
+ index += 1 while queue[index]&.newline?
1681
+ queue[index] ? queue[index].punct?("(") : false
1682
+ end
1683
+
1684
+ # Consumes the argument list of a call: the leading newlines and "(" already
1685
+ # confirmed by #call_follows?, then tokens up to the matching ")", split on
1686
+ # top-level commas. Nested parentheses are balanced so a comma or ")" inside
1687
+ # them belongs to an argument, and a newline is inter-token space that is
1688
+ # dropped. Returns [arguments, commas]: the argument token lists and the
1689
+ # separating comma tokens themselves (kept so #__VA_ARGS__ can reproduce the
1690
+ # exact spelling). Running out of tokens is an unterminated invocation.
1691
+ def collect_arguments(tok, queue)
1692
+ queue.shift while queue.first&.newline?
1693
+ queue.shift # the "("
1694
+ arguments = []
1695
+ commas = []
1696
+ current = []
1697
+ depth = 0
1698
+ loop do
1699
+ raise_at(tok, "unterminated function-like macro invocation") if queue.empty?
1700
+
1701
+ token = queue.shift
1702
+ if token.newline?
1703
+ next
1704
+ elsif depth.zero? && token.punct?(")")
1705
+ arguments << current
1706
+ return [arguments, commas]
1707
+ elsif depth.zero? && token.punct?(",")
1708
+ arguments << current
1709
+ commas << token
1710
+ current = []
1711
+ else
1712
+ if token.punct?("(")
1713
+ depth += 1
1714
+ if depth > MACRO_ARGUMENT_NESTING_LIMIT
1715
+ raise_at(token, "macro argument parentheses nested too deeply")
1716
+ end
1717
+ end
1718
+ depth -= 1 if token.punct?(")")
1719
+ current << token
1720
+ end
1721
+ end
1722
+ end
1723
+
1724
+ # Checks a call's argument count against the macro and returns the argument
1725
+ # list to substitute. A parameterless macro admits only "F()", which the
1726
+ # collector reports as one empty argument and which is normalized to none. A
1727
+ # variadic macro needs at least its named parameters, its variable part
1728
+ # possibly empty; a plain one needs exactly its parameters.
1729
+ def match_arity(tok, macro, arguments)
1730
+ named = macro.params.length
1731
+ if named.zero? && !macro.variadic
1732
+ return [] if arguments.length == 1 && arguments[0].empty?
1733
+
1734
+ raise_at(tok, "macro \"#{tok.text}\" passed #{arguments.length} arguments, but takes just 0")
1735
+ elsif macro.variadic
1736
+ raise_at(tok, arity_message(tok, arguments.length, named, "at least")) if arguments.length < named
1737
+ elsif arguments.length != named
1738
+ raise_at(tok, arity_message(tok, arguments.length, named, "exactly"))
1739
+ end
1740
+ arguments
1741
+ end
1742
+
1743
+ def arity_message(tok, given, wanted, qualifier)
1744
+ "macro \"#{tok.text}\" requires #{qualifier} #{wanted} arguments, but #{given} given"
1745
+ end
1746
+
1747
+ # Fully expands one argument's tokens in isolation (6.10.3.1) before it is
1748
+ # substituted, reusing the same queue algorithm; the tokens keep their own
1749
+ # painting, since the macro being called is not yet in play for them.
1750
+ def expand_argument(argument)
1751
+ expanded = []
1752
+ expand_tokens(argument, expanded)
1753
+ expanded
1754
+ end
1755
+
1756
+ # Builds a macro's substitution by walking its replacement list once
1757
+ # (6.10.3). `invocation` carries a function-like call's arguments, or is nil
1758
+ # for an object-like macro. "#" stringizes the following parameter's raw
1759
+ # argument; "##" pastes the token to its left onto the operand to its right;
1760
+ # a plain parameter becomes its pre-expanded argument, unless it abuts a
1761
+ # "##", where the raw argument is used instead (6.10.3.1p1); every other
1762
+ # token is a literal relocated to the use site and painted with the macro's
1763
+ # name. `span` tracks how many tokens the token just placed contributed, so
1764
+ # a following "##" knows its left operand (0 marks a placemarker).
1765
+ def substitute(tok, macro, invocation)
1766
+ painted = paint(tok)
1767
+ rep = macro.replacement
1768
+ result = []
1769
+ span = 0
1770
+ index = 0
1771
+ while index < rep.length
1772
+ cur = rep[index]
1773
+ following_paste = rep[index + 1]&.punct?("##")
1774
+ if macro.kind == :function && cur.punct?("#")
1775
+ result << stringize(cur, painted, raw_operand(macro, invocation, rep[index + 1]))
1776
+ span = 1
1777
+ index += 2
1778
+ elsif cur.punct?("##")
1779
+ operand = rep[index + 1]
1780
+ if gnu_comma_paste?(macro, invocation, rep[index - 1], operand)
1781
+ span = comma_paste(macro, invocation, result, tok, painted)
1782
+ else
1783
+ right = paste_operand(macro, invocation, operand, tok, painted)
1784
+ span = paste(result, span, right, tok, painted)
1785
+ end
1786
+ index += 2
1787
+ else
1788
+ placed = replacement_tokens(macro, invocation, cur, tok, painted, raw: following_paste)
1789
+ result.concat(placed)
1790
+ span = placed.length
1791
+ index += 1
1792
+ end
1793
+ end
1794
+ result
1795
+ end
1796
+
1797
+ # The tokens a plain (non-operator) replacement element expands to: a
1798
+ # parameter's argument (raw when it abuts "##", else pre-expanded and
1799
+ # memoized), the variable arguments for __VA_ARGS__, or the element itself
1800
+ # relocated and painted.
1801
+ def replacement_tokens(macro, invocation, rep, site, painted, raw:)
1802
+ param = parameter_index(macro, rep)
1803
+ if param
1804
+ raw ? invocation.raw[param] : expanded_argument(invocation, param)
1805
+ elsif variadic_ref?(macro, rep)
1806
+ raw ? raw_variadic(macro, invocation) : variable_arguments(site, macro, invocation, painted)
1807
+ else
1808
+ [relocate(rep, site, painted)]
1809
+ end
1810
+ end
1811
+
1812
+ # The raw (unexpanded) tokens the "#"/"##" operand `operand` denotes: a
1813
+ # parameter's argument or the reconstructed variable-argument sequence. The
1814
+ # operand was checked at #define time to be a parameter or __VA_ARGS__.
1815
+ def raw_operand(macro, invocation, operand)
1816
+ param = parameter_index(macro, operand)
1817
+ param ? invocation.raw[param] : raw_variadic(macro, invocation)
1818
+ end
1819
+
1820
+ # The tokens forming the right side of a "##": a parameter's raw argument
1821
+ # (possibly empty, i.e. a placemarker), or a single relocated literal.
1822
+ def paste_operand(macro, invocation, operand, site, painted)
1823
+ if invocation && parameter_ref?(macro, operand)
1824
+ raw_operand(macro, invocation, operand)
1825
+ else
1826
+ [relocate(operand, site, painted)]
1827
+ end
1828
+ end
1829
+
1830
+ def parameter_index(macro, rep)
1831
+ rep.type == :identifier ? macro.params.index(rep.text) : nil
1832
+ end
1833
+
1834
+ # Whether `rep` is the identifier standing for the variable arguments: the
1835
+ # macro's `va_name` (ISO "__VA_ARGS__" or the GNU written name). In a named
1836
+ # form "__VA_ARGS__" is an ordinary identifier and does not match here.
1837
+ def variadic_ref?(macro, rep)
1838
+ macro.variadic && rep.type == :identifier && rep.text == macro.va_name
1839
+ end
1840
+
1841
+ # One argument's fully expanded tokens, computed on first use and cached in
1842
+ # the invocation so a parameter named several times expands only once.
1843
+ def expanded_argument(invocation, index)
1844
+ invocation.expanded[index] ||= expand_argument(invocation.raw[index])
1845
+ end
1846
+
1847
+ # The tokens __VA_ARGS__ stands for in a plain position: the pre-expanded
1848
+ # variable arguments (those past the named parameters) laid end to end,
1849
+ # separated by comma tokens spelled at the call site and painted as literals.
1850
+ def variable_arguments(site, macro, invocation, painted)
1851
+ result = []
1852
+ (macro.params.length...invocation.raw.length).each_with_index do |arg, position|
1853
+ result << comma_token(site, painted) if position.positive?
1854
+ result.concat(expanded_argument(invocation, arg))
1855
+ end
1856
+ result
1857
+ end
1858
+
1859
+ # The raw variable-argument tokens for a "#"/"##" operand: the unexpanded
1860
+ # arguments past the named parameters, rejoined by the very comma tokens the
1861
+ # call used, so #__VA_ARGS__ reproduces the source spacing exactly.
1862
+ def raw_variadic(macro, invocation)
1863
+ named = macro.params.length
1864
+ result = []
1865
+ (named...invocation.raw.length).each do |arg|
1866
+ result << invocation.commas[arg - 1] if arg > named
1867
+ result.concat(invocation.raw[arg])
1868
+ end
1869
+ result
1870
+ end
1871
+
1872
+ # Stringizes an operand's raw tokens into a string-literal token (6.10.3.2):
1873
+ # the token spellings joined with a single space wherever the source had
1874
+ # whitespace and none at the ends, with " and \ escaped inside string and
1875
+ # character tokens. The result sits at the use site under the literal paint.
1876
+ def stringize(hash, painted, tokens)
1877
+ inner = +""
1878
+ tokens.each_with_index do |t, index|
1879
+ inner << " " if index.positive? && t.space_before
1880
+ inner << stringized_spelling(t)
1881
+ end
1882
+ PPToken.new(
1883
+ type: :string, text: "\"#{inner}\"",
1884
+ filename: hash.filename, line: hash.line, column: hash.column,
1885
+ source_line: hash.source_line, space_before: hash.space_before, suppress: painted
1886
+ )
1887
+ end
1888
+
1889
+ # A token's contribution to a stringized argument: verbatim, except inside a
1890
+ # string literal or character constant, where each " and \ gains a backslash.
1891
+ def stringized_spelling(token)
1892
+ if token.type == :string || token.type == :char
1893
+ token.text.gsub(/["\\]/) { |ch| "\\#{ch}" }
1894
+ else
1895
+ token.text
1896
+ end
1897
+ end
1898
+
1899
+ # Concatenates a "##"'s two operands (6.10.3.3). `left_span` is how many
1900
+ # tokens the left operand placed; 0 means it was a placemarker, so the paste
1901
+ # is just the right operand, and an empty right operand likewise leaves the
1902
+ # left alone. Otherwise the left operand's last token and the right's first
1903
+ # are spelled together and re-lexed into one token. Returns the new span so
1904
+ # a chained "##" pastes onto this result. Left to right by construction.
1905
+ def paste(result, left_span, right, site, painted)
1906
+ return concat_span(result, right) if left_span.zero?
1907
+ return left_span if right.empty?
1908
+
1909
+ left = result.pop
1910
+ result << fuse(left, right.first, site, painted)
1911
+ result.concat(right[1..])
1912
+ right.length
1913
+ end
1914
+
1915
+ def concat_span(result, tokens)
1916
+ result.concat(tokens)
1917
+ tokens.length
1918
+ end
1919
+
1920
+ # Whether a "##" is the GNU comma-paste ", ## <variable-arguments>": a
1921
+ # literal comma written immediately to its left and the variable-part name
1922
+ # immediately to its right, inside a function-like call. Only this exact
1923
+ # shape overrides the ordinary paste; every other "##" keeps its meaning.
1924
+ def gnu_comma_paste?(macro, invocation, left, operand)
1925
+ invocation && left&.punct?(",") && variadic_ref?(macro, operand)
1926
+ end
1927
+
1928
+ # Carries out a GNU comma-paste. It is not a token paste: when the call
1929
+ # supplies no variable arguments the preceding comma, already placed as the
1930
+ # left operand, is dropped and nothing is spliced in. Otherwise the comma
1931
+ # stays and the variable arguments are spliced in expanded, exactly as a
1932
+ # plain use of the variable-part name would produce -- so an argument slot
1933
+ # that is present but empty (a trailing comma) keeps the comma. Returns the
1934
+ # new span.
1935
+ def comma_paste(macro, invocation, result, site, painted)
1936
+ if no_variable_arguments?(macro, invocation)
1937
+ result.pop if result.last&.punct?(",")
1938
+ 0
1939
+ else
1940
+ args = variable_arguments(site, macro, invocation, painted)
1941
+ concat_span(result, args)
1942
+ end
1943
+ end
1944
+
1945
+ # Whether the call supplied no variable arguments (so a GNU comma-paste
1946
+ # drops its comma). That holds when nothing was written past the named
1947
+ # parameters, and also -- matching gcc -- when the macro has no named
1948
+ # parameters and the whole argument list is empty ("Z()" for "Z(...)"),
1949
+ # which reads as zero variable arguments, not one empty one. A trailing
1950
+ # comma with nothing after it ("F(a,)") does supply one empty argument.
1951
+ def no_variable_arguments?(macro, invocation)
1952
+ return true if invocation.raw.length <= macro.params.length
1953
+
1954
+ macro.params.empty? && invocation.raw.length == 1 && invocation.raw[0].empty?
1955
+ end
1956
+
1957
+ # Fuses two tokens into one by re-lexing their joined spelling through the
1958
+ # scanner (6.10.3.3p3): the result must be exactly one preprocessing token,
1959
+ # or the paste is ill-formed. It takes the left token's leading space and
1960
+ # the literal paint, and is located at the use site for rescanning.
1961
+ def fuse(left, right, site, painted)
1962
+ spelled = Scanner.new(left.text + right.text, filename: site.filename).scan.reject(&:eof?)
1963
+ unless spelled.length == 1
1964
+ raise_at(site, "pasting \"#{left.text}\" and \"#{right.text}\" does not give a valid preprocessing token")
1965
+ end
1966
+
1967
+ fused = spelled.first
1968
+ PPToken.new(
1969
+ type: fused.type, text: fused.text,
1970
+ filename: site.filename, line: site.line, column: site.column,
1971
+ source_line: site.source_line, space_before: left.space_before, suppress: painted
1972
+ )
1973
+ end
1974
+
1975
+ # The painting a token produced by expanding `tok` carries: `tok`'s own
1976
+ # suppression set plus the macro name now being expanded, frozen so the
1977
+ # shared list is never mutated by a later token's painting.
1978
+ def paint(tok)
1979
+ (tok.suppress + [tok.text]).freeze
1980
+ end
1981
+
1982
+ def comma_token(site, suppress)
1983
+ PPToken.new(
1984
+ type: :punct, text: ",",
1985
+ filename: site.filename, line: site.line, column: site.column,
1986
+ source_line: site.source_line, suppress: suppress
1987
+ )
1988
+ end
1989
+
1990
+ # A copy of a replacement token relocated to the invocation site (so tokens
1991
+ # produced by expansion are diagnosed at the point of use, not at the
1992
+ # #define that spelled them) and carrying its computed painting.
1993
+ def relocate(rep, site, suppress)
1994
+ PPToken.new(
1995
+ type: rep.type, text: rep.text,
1996
+ filename: site.filename, line: site.line, column: site.column,
1997
+ source_line: site.source_line, space_before: rep.space_before, suppress: suppress
1998
+ )
1999
+ end
2000
+
2001
+ def raise_at(pp, description)
2002
+ raise CompileError.new(
2003
+ description,
2004
+ filename: pp.filename, line: pp.line, column: pp.column,
2005
+ source_line: pp.source_line
2006
+ )
2007
+ end
2008
+
2009
+ # As #raise_at, but for a Front::Token (produced during #if evaluation)
2010
+ # rather than a preprocessing token; both expose the same location fields.
2011
+ def raise_at_front(token, description)
2012
+ raise CompileError.new(
2013
+ description,
2014
+ filename: token.filename, line: token.line, column: token.column,
2015
+ source_line: token.source_line
2016
+ )
2017
+ end
2018
+ end
2019
+ end
2020
+ end