rucc 0.1.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 (87) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +55 -0
  3. data/.rspec +1 -0
  4. data/.travis.yml +5 -0
  5. data/Gemfile +6 -0
  6. data/Gemfile.lock +46 -0
  7. data/LICENCE +21 -0
  8. data/README.md +82 -0
  9. data/Rakefile +2 -0
  10. data/Vagrantfile +10 -0
  11. data/bin/console +10 -0
  12. data/bin/rspec +2 -0
  13. data/bin/setup +8 -0
  14. data/exe/rucc +7 -0
  15. data/include/8cc.h +48 -0
  16. data/include/float.h +44 -0
  17. data/include/iso646.h +20 -0
  18. data/include/rucc.h +2 -0
  19. data/include/stdalign.h +11 -0
  20. data/include/stdarg.h +52 -0
  21. data/include/stdbool.h +11 -0
  22. data/include/stddef.h +15 -0
  23. data/include/stdnoreturn.h +8 -0
  24. data/lib/rucc.rb +8 -0
  25. data/lib/rucc/case.rb +22 -0
  26. data/lib/rucc/decl.rb +9 -0
  27. data/lib/rucc/enc.rb +9 -0
  28. data/lib/rucc/engine.rb +138 -0
  29. data/lib/rucc/file_io.rb +108 -0
  30. data/lib/rucc/file_io_list.rb +56 -0
  31. data/lib/rucc/gen.rb +1602 -0
  32. data/lib/rucc/int_evaluator.rb +114 -0
  33. data/lib/rucc/k.rb +73 -0
  34. data/lib/rucc/keyword.rb +17 -0
  35. data/lib/rucc/kind.rb +43 -0
  36. data/lib/rucc/label_gen.rb +13 -0
  37. data/lib/rucc/lexer.rb +40 -0
  38. data/lib/rucc/lexer/impl.rb +683 -0
  39. data/lib/rucc/lexer/preprocessor.rb +888 -0
  40. data/lib/rucc/lexer/preprocessor/cond_incl.rb +27 -0
  41. data/lib/rucc/lexer/preprocessor/constructor.rb +54 -0
  42. data/lib/rucc/lexer/preprocessor/pragma.rb +31 -0
  43. data/lib/rucc/lexer/preprocessor/special_macro.rb +110 -0
  44. data/lib/rucc/libc.rb +47 -0
  45. data/lib/rucc/m.rb +7 -0
  46. data/lib/rucc/macro.rb +24 -0
  47. data/lib/rucc/node.rb +530 -0
  48. data/lib/rucc/node/conv.rb +33 -0
  49. data/lib/rucc/op.rb +61 -0
  50. data/lib/rucc/operator.rb +13 -0
  51. data/lib/rucc/option.rb +30 -0
  52. data/lib/rucc/parser.rb +961 -0
  53. data/lib/rucc/parser/break.rb +18 -0
  54. data/lib/rucc/parser/builtin.rb +25 -0
  55. data/lib/rucc/parser/continue.rb +18 -0
  56. data/lib/rucc/parser/do.rb +33 -0
  57. data/lib/rucc/parser/ensure.rb +39 -0
  58. data/lib/rucc/parser/enum.rb +64 -0
  59. data/lib/rucc/parser/expr.rb +493 -0
  60. data/lib/rucc/parser/for.rb +71 -0
  61. data/lib/rucc/parser/func.rb +274 -0
  62. data/lib/rucc/parser/func_call.rb +54 -0
  63. data/lib/rucc/parser/goto.rb +29 -0
  64. data/lib/rucc/parser/if.rb +23 -0
  65. data/lib/rucc/parser/initializer.rb +237 -0
  66. data/lib/rucc/parser/label.rb +31 -0
  67. data/lib/rucc/parser/return.rb +16 -0
  68. data/lib/rucc/parser/struct_and_union.rb +280 -0
  69. data/lib/rucc/parser/switch.rb +117 -0
  70. data/lib/rucc/parser/while.rb +29 -0
  71. data/lib/rucc/pos.rb +11 -0
  72. data/lib/rucc/rmap.rb +22 -0
  73. data/lib/rucc/s.rb +9 -0
  74. data/lib/rucc/static_label_gen.rb +15 -0
  75. data/lib/rucc/t.rb +18 -0
  76. data/lib/rucc/tempname_gen.rb +14 -0
  77. data/lib/rucc/token.rb +114 -0
  78. data/lib/rucc/token_gen.rb +68 -0
  79. data/lib/rucc/type.rb +304 -0
  80. data/lib/rucc/type/check.rb +39 -0
  81. data/lib/rucc/type/conv.rb +29 -0
  82. data/lib/rucc/type_info.rb +21 -0
  83. data/lib/rucc/utf.rb +126 -0
  84. data/lib/rucc/util.rb +111 -0
  85. data/lib/rucc/version.rb +3 -0
  86. data/rucc.gemspec +38 -0
  87. metadata +201 -0
@@ -0,0 +1,888 @@
1
+ require "set"
2
+ require "rucc/m"
3
+ require "rucc/macro"
4
+ require "rucc/lexer/preprocessor/cond_incl"
5
+ require "rucc/lexer/preprocessor/constructor"
6
+ require "rucc/lexer/preprocessor/pragma"
7
+ require "rucc/lexer/preprocessor/special_macro"
8
+
9
+ module Rucc
10
+ class Lexer
11
+ class Preprocessor
12
+ include Constructor
13
+ include SpecialMacro
14
+ include Pragma
15
+
16
+ # @param [Impl] impl
17
+ def initialize(impl)
18
+ @impl = impl
19
+ @std_include_path = []
20
+
21
+ # preprocessor context
22
+ @cond_incl_stack = []
23
+ @macros = {}
24
+ @once = {}
25
+ @include_guard = {}
26
+
27
+ # warning context
28
+ # TODO(south37) Impl warnf
29
+ @enable_warning = true
30
+
31
+ # Used for __DATE__ and __TIME__
32
+ @now = Time.now
33
+
34
+ define_special_macros!
35
+ end
36
+
37
+ # Return parsed node, only used for read_constexpr
38
+ attr_writer :expr_reader
39
+
40
+ # @return [Token]
41
+ def read_token
42
+ while true
43
+ tok = read_expand
44
+ if tok.bol && Token.is_keyword?(tok, '#') && (tok.hideset.size == 0)
45
+ read_directive(tok)
46
+ next
47
+ end
48
+ Util.assert!{ !T::CPP_TOKENS.include?(tok.kind) }
49
+ return maybe_convert_keyword(tok)
50
+ end
51
+ raise "Must not reach here!"
52
+ end
53
+
54
+ # @param [Token] tok
55
+ def unget_token(tok)
56
+ @impl.unget_token(tok)
57
+ end
58
+
59
+ # @return [Token]
60
+ def peek_token
61
+ r = read_token
62
+ unget_token(r)
63
+ r
64
+ end
65
+
66
+ # @param [String] path
67
+ def append_include_path(path)
68
+ @std_include_path << path
69
+ end
70
+
71
+ private
72
+
73
+ # @return [Token]
74
+ def read_expand
75
+ while true
76
+ tok = read_expand_newline
77
+ if tok.kind != T::NEWLINE
78
+ tok.expanded = true
79
+ return tok
80
+ end
81
+ end
82
+ end
83
+
84
+ # Note: This is "expand" function in the Dave Prosser's document.
85
+ # @return [Token]
86
+ def read_expand_newline
87
+ tok = @impl.lex
88
+ # NOTE: return tok if already expanded
89
+ if tok.expanded
90
+ return tok
91
+ end
92
+
93
+ if tok.kind != T::IDENT
94
+ return tok
95
+ end
96
+ name = tok.sval
97
+ macro = @macros[name]
98
+ if !macro || tok.hideset.include?(name)
99
+ return tok
100
+ end
101
+
102
+ case macro.kind
103
+ when M::OBJ
104
+ hideset = tok.hideset.dup
105
+ hideset << name
106
+ tokens = subst(macro, nil, hideset)
107
+ propagate_space(tokens, tok)
108
+ @impl.unget_all(tokens)
109
+ return read_expand
110
+ when M::FUNC
111
+ if !next?('(')
112
+ return tok
113
+ end
114
+ args = read_args(tok, macro)
115
+ rparen = peek_token
116
+ expect!(')')
117
+ hideset = ((tok.hideset & rparen.hideset) << name)
118
+ tokens = subst(macro, args, hideset)
119
+ propagate_space(tokens, tok)
120
+ @impl.unget_all(tokens)
121
+ return read_expand
122
+ when M::SPECIAL
123
+ macro.fn.call(tok)
124
+ return read_expand
125
+ else
126
+ raise "internal error"
127
+ end
128
+ end
129
+
130
+ # @param [Token] tok
131
+ # @return [Token]
132
+ def maybe_convert_keyword(tok)
133
+ return tok if tok.kind != T::IDENT
134
+
135
+ id = (K.keywords[tok.sval] || OP.operators[tok.sval])
136
+ return tok if id.nil?
137
+
138
+ r = tok.dup
139
+ r.kind = T::KEYWORD
140
+ r.id = id
141
+ r
142
+ end
143
+
144
+ # @param [Token] hash
145
+ def read_directive(hash)
146
+ tok = @impl.lex
147
+ if tok.kind == T::NEWLINE
148
+ return
149
+ end
150
+ if tok.kind == T::NUMBER
151
+ read_linemarker(tok)
152
+ return
153
+ end
154
+ if tok.kind != T::IDENT
155
+ raise "unsupported preprocessor directive: #{tok}"
156
+ end
157
+
158
+ # NOTE: only for debug
159
+ # print "#{' ' * 2 * @cond_incl_stack.size}##{tok.sval}\n"
160
+
161
+ case tok.sval
162
+ when "define" then read_define
163
+ when "elif" then read_elif(hash)
164
+ when "else" then read_else(hash)
165
+ when "endif" then read_endif(hash)
166
+ when "error" then read_error(hash)
167
+ when "if" then read_if
168
+ when "ifdef" then read_ifdef
169
+ when "ifndef" then read_ifndef
170
+ when "import" then read_include(hash, tok.file, true)
171
+ when "include" then read_include(hash, tok.file, false)
172
+ when "include_next" then read_include_next(hash, tok.file)
173
+ when "line" then read_line
174
+ when "pragma" then read_pragma
175
+ when "undef" then read_undef
176
+ when "warning" then read_warning(hash)
177
+ else
178
+ raise "unsupported preprocessor directive: #{tok}"
179
+ end
180
+ end
181
+
182
+ # GNU CPP outputs "# linenum filename flags" to preserve original
183
+ # source file information. This function reads them. Flags are ignored.
184
+ #
185
+ # @param [Token] tok
186
+ def read_linemarker(tok)
187
+ if !is_digit_sequence?(tok.sval)
188
+ Util.errort!(tok, "line number expected, but got #{tok}")
189
+ end
190
+ line = tok.sval.to_i
191
+ tok = @impl.lex
192
+ if tok.kind != T::STRING
193
+ Util.errort!(tok, "filename expected, but got #{tok}")
194
+ end
195
+ filename = tok.sval
196
+
197
+ tok = @impl.lex
198
+ while tok.kind != T::NEWLINE
199
+ tok = @impl.lex
200
+ end
201
+ file = @impl.current_file
202
+ file.line = line
203
+ file.name = filename
204
+ end
205
+
206
+ # @param [Token] tok
207
+ # @param [Macro] macro
208
+ # @return [<Token>]
209
+ def read_args(tok, macro)
210
+ if (macro.nargs == 0) && Token.is_keyword?(peek_token, ')')
211
+ # If a macro M has no parameter, argument list of M()
212
+ # is an empty list. If it has one parameter,
213
+ # argument list of M() is a list containing an empty list.
214
+ return []
215
+ end
216
+ args = do_read_args(tok, macro)
217
+ if args.size != macro.nargs
218
+ Util.errort!(tok, "macro argument number does not match");
219
+ end
220
+ args
221
+ end
222
+
223
+ # @param [Token] ident
224
+ # @param [Macro] macro
225
+ # @return [<Token>]
226
+ def do_read_args(ident, macro)
227
+ r = []
228
+ e = false
229
+ while !e
230
+ in_ellipsis = macro.is_varg && (r.size + 1 == macro.nargs)
231
+ arg, e = read_one_arg(ident, in_ellipsis)
232
+ r.push(arg)
233
+ end
234
+ if macro.is_varg && (r.size == macro.nargs - 1)
235
+ r.push([])
236
+ end
237
+ r
238
+ end
239
+
240
+ # @param [Token] ident
241
+ # @param [Boolean] readall
242
+ # @return [<<Token>, Boolean>]
243
+ def read_one_arg(ident, readall)
244
+ r = []
245
+ level = 0
246
+ while true
247
+ tok = @impl.lex
248
+ if tok.kind == T::EOF
249
+ Util.errort!(ident, "unterminated macro argument list")
250
+ end
251
+ if tok.kind == T::NEWLINE
252
+ next
253
+ end
254
+ if tok.bol && Token.is_keyword?(tok, '#')
255
+ read_directive(tok)
256
+ next
257
+ end
258
+ if (level == 0) && Token.is_keyword?(tok, ')')
259
+ unget_token(tok)
260
+ return r, true
261
+ end
262
+ if (level == 0) && Token.is_keyword?(tok, ',') && !readall
263
+ return r, false
264
+ end
265
+ if Token.is_keyword?(tok, '(')
266
+ level += 1
267
+ end
268
+ if Token.is_keyword?(tok, ')')
269
+ level -= 1
270
+ end
271
+ # C11 6.10.3p10: Within the macro argument list,
272
+ # newline is considered a normal whitespace character.
273
+ # I don't know why the standard specifies such a minor detail,
274
+ # but the difference of newline and space is observable
275
+ # if you stringize tokens using #.
276
+ if tok.bol
277
+ tok = copy_token(tok)
278
+ tok.bol = false
279
+ tok.space = true
280
+ end
281
+ r.push(tok)
282
+ end
283
+ end
284
+
285
+ # @param [Char] id
286
+ # @return [Boolean]
287
+ def next?(id)
288
+ tok = @impl.lex
289
+ if Token.is_keyword?(tok, id)
290
+ return true
291
+ end
292
+ @impl.unget_token(tok)
293
+ false
294
+ end
295
+
296
+ # @param(return) [<Token>] tokens
297
+ # @param [Token] tmpl
298
+ def propagate_space(tokens, tmpl)
299
+ if tokens.size == 0
300
+ return
301
+ end
302
+ tok = copy_token(tokens.first)
303
+ tok.space = tmpl.space
304
+ tokens[0] = tok
305
+ end
306
+
307
+ # @param [Char] id
308
+ def expect!(id)
309
+ tok = @impl.lex
310
+ if !Token.is_keyword?(tok, id)
311
+ raise "#{id} expected, but got #{tok}"
312
+ end
313
+ end
314
+
315
+ # @param [Macro] macro
316
+ # @param [Array] args
317
+ # @param [Set] hideset
318
+ # @return [<Token>]
319
+ def subst(macro, args, hideset)
320
+ r = []
321
+ i = 0
322
+ len = macro.body.size
323
+ while i < len
324
+ t0 = macro.body[i]
325
+ t1 = macro.body[i + 1] # Note: nil when i == (macro.body.size - 1)
326
+ t0_param = (t0.kind == T::MACRO_PARAM)
327
+ t1_param = (t1 && t1.kind == T::MACRO_PARAM)
328
+
329
+ if Token.is_keyword?(t0, '#') && t1_param
330
+ r.push(stringize(t0, args[t1.position]))
331
+ i += 2
332
+ next
333
+ end
334
+ if Token.is_keyword?(t0, K::HASHHASH) && t1_param
335
+ arg = args[t1.position]
336
+ # [GNU] [,##__VA_ARG__] is expanded to the empty token sequence
337
+ # if __VA_ARG__ is empty. Otherwise it's expanded to
338
+ # [,<tokens in __VA_ARG__>].
339
+ if t1.is_vararg && (r.size > 0) && Token.is_keyword?(r.last, ',')
340
+ if arg.size > 0
341
+ r += arg
342
+ else
343
+ r.pop
344
+ end
345
+ elsif arg.size > 0
346
+ glue_push(r, arg.first)
347
+ arg[1..-1].each do |e|
348
+ r.push(e)
349
+ end
350
+ end
351
+ i += 2
352
+ next
353
+ end
354
+ if Token.is_keyword?(t0, K::HASHHASH) && t1
355
+ hideset = t1.hideset
356
+ glue_push(r, t1)
357
+ i += 2
358
+ next
359
+ end
360
+ if t0_param && t1 && Token.is_keyword?(t1, K::HASHHASH)
361
+ hideset = t1.hideset
362
+ arg = args[t0.position]
363
+ if arg.size == 0
364
+ i += 2
365
+ next
366
+ else
367
+ r += arg
368
+ i += 1
369
+ next
370
+ end
371
+ end
372
+ if t0_param
373
+ arg = args[t0.position]
374
+ r += expand_all(arg, t0)
375
+ i += 1
376
+ next
377
+ end
378
+ r.push(t0)
379
+ i += 1
380
+ end
381
+ add_hide_set(r, hideset)
382
+ end
383
+
384
+ # @param(result) [<Token>] tokens
385
+ # @param [Token] tok
386
+ def glue_push(tokens, tok)
387
+ last = tokens.pop
388
+ tokens.push(glue_tokens(last, tok))
389
+ end
390
+
391
+ # @param [Token] t
392
+ # @param [Token] u
393
+ # @return [Token]
394
+ def glue_tokens(t, u)
395
+ b = "#{t}#{u}"
396
+ r = @impl.lex_string(b)
397
+ r
398
+ end
399
+
400
+ # @param [Token] tmpl
401
+ # @param [<Token>] args
402
+ def stringize(tmpl, args)
403
+ b = ""
404
+ args.each do |tok|
405
+ if b.size > 0 && tok.space
406
+ b << " "
407
+ end
408
+ b << tok.to_s
409
+ end
410
+ r = tmpl.dup
411
+ r.kind = T::STRING
412
+ r.sval = b
413
+ r.enc = ENC::NONE
414
+ r
415
+ end
416
+
417
+ # @return [<Token>]
418
+ def expand_all(tokens, tmpl)
419
+ @impl.token_buffer_stash(tokens.reverse)
420
+ r = []
421
+ while true
422
+ tok = read_expand
423
+ if tok.kind == T::EOF
424
+ break
425
+ end
426
+ r.push(tok)
427
+ end
428
+ propagate_space(r, tmpl)
429
+ @impl.token_buffer_unstash
430
+ r
431
+ end
432
+
433
+ # @param [<Token>] tokens
434
+ # @param [Set] hideset
435
+ # @return [<Token>]
436
+ def add_hide_set(tokens, hideset)
437
+ r = []
438
+ tokens.each do |token|
439
+ t = copy_token(token)
440
+ t.hideset = (t.hideset | hideset)
441
+ r.push(t)
442
+ end
443
+ r
444
+ end
445
+
446
+ # @param [Token] hash
447
+ # @param [FileIO] file
448
+ # @param [Boolean] isimport
449
+ def read_include(hash, file, isimport)
450
+ filename, std = read_cpp_header_name(hash)
451
+ expect_newline!
452
+ if filename[0] == '/'
453
+ if try_include("/", filename, isimport)
454
+ return
455
+ end
456
+ Util.errort!(hash, "cannot find header file: #{filename}")
457
+ end
458
+ if !std
459
+ dir = file.name ? File.dirname(file.name) : "."
460
+ if try_include(dir, filename, isimport)
461
+ return
462
+ end
463
+ end
464
+ @std_include_path.each do |path|
465
+ if try_include(path, filename, isimport)
466
+ return
467
+ end
468
+ end
469
+ Util.errort!(hash, "cannot find header file: #{filename}")
470
+ end
471
+
472
+ # @raise [RuntimeError]
473
+ def expect_newline!
474
+ tok = @impl.lex
475
+ if tok.kind != T::NEWLINE
476
+ raise "newline expected, but got #{tok}"
477
+ end
478
+ end
479
+
480
+ # @param [Token] hash
481
+ # @return [String, Boolean]
482
+ def read_cpp_header_name(hash)
483
+ # Try reading a filename using a special tokenizer for #include.
484
+ path, std = @impl.read_header_file_name
485
+ if path
486
+ return path, std
487
+ end
488
+
489
+ # If a token following #include does not start with < nor ",
490
+ # try to read the token as a regular token. Macro-expanded
491
+ # form may be a valid header file path.
492
+ tok = read_expand_newline
493
+ if tok.kind == T::NEWLINE
494
+ Util.errort!(hash, "expected filename, but got newline")
495
+ end
496
+ if tok.kind == T::STRING
497
+ std = false
498
+ return tok.sval, std
499
+ end
500
+ if !Token.is_keyword?(tok, '<')
501
+ Util.errort!(tok, "< expected, but got #{tok}")
502
+ end
503
+ tokens = []
504
+ while true
505
+ tok = read_expand_newline
506
+ if tok.kind == T::NEWLINE
507
+ Util.errort!(hash, "premature end of header name")
508
+ end
509
+ if Token.is_keyword?(tok, '>')
510
+ break
511
+ end
512
+ tokens.push(tok)
513
+ end
514
+ std = true
515
+
516
+ return tokens.join, std
517
+ end
518
+
519
+ # @param [String] dir
520
+ # @param [String] filename
521
+ # @param [Boolean] isimport
522
+ # @return [Boolean]
523
+ def try_include(dir, filename, isimport)
524
+ path = File.join(dir, filename)
525
+ if @once[path]
526
+ return true
527
+ end
528
+ if guarded?(path)
529
+ return true
530
+ end
531
+
532
+ # NOTE: file may not exist
533
+ begin
534
+ fp = File.open(path, "r")
535
+ rescue Errno::ENOENT, Errno::ENOTDIR
536
+ return false
537
+ end
538
+
539
+ if isimport
540
+ @once[path] = true
541
+ end
542
+ @impl.push_file(FileIO.new(fp, path))
543
+ true
544
+ end
545
+
546
+ CPP_TOKEN_ZERO = Token.new(T::NUMBER, sval: "0")
547
+ CPP_TOKEN_ONE = Token.new(T::NUMBER, sval: "1")
548
+
549
+ # @param [String] path
550
+ # @return [Boolean]
551
+ def guarded?(path)
552
+ guard = @include_guard[path]
553
+ r = guard && @macros[guard]
554
+ define_obj_macro("__8cc_include_guard", r ? CPP_TOKEN_ONE : CPP_TOKEN_ZERO)
555
+ r
556
+ end
557
+
558
+ def read_line
559
+ tok = read_expand_newline
560
+ if (tok.kind != T::NUMBER) || !is_digit_sequence?(tok.sval)
561
+ Util.errort!(tok, "number expected after #line, but got #{tok}")
562
+ end
563
+ line = tok.sval.to_i
564
+ tok = read_expand_newline
565
+ if tok.kind == T::STRING
566
+ filename = tok.sval
567
+ expect_newline!
568
+ elsif tok.kind != T::NEWLINE
569
+ Util.errort!(tok, "newline or a source name are expected, but got #{tok}")
570
+ end
571
+ f = @impl.current_file
572
+ f.line = line
573
+ if filename
574
+ f.name = filename
575
+ end
576
+ end
577
+
578
+ # @param [String] p
579
+ # @return [Boolean]
580
+ def is_digit_sequence?(p)
581
+ p.match(/^\d*$/)
582
+ end
583
+
584
+ def read_if
585
+ do_read_if(read_constexpr)
586
+ end
587
+
588
+ # @return [Boolean]
589
+ def read_constexpr
590
+ @impl.token_buffer_stash(read_intexpr_line.reverse)
591
+ Util.assert!{ !@expr_reader.nil? }
592
+ expr = @expr_reader.call
593
+ tok = @impl.lex
594
+ if tok.kind != T::EOF
595
+ Util.errort!(tok, "stray token: #{tok}")
596
+ end
597
+ @impl.token_buffer_unstash
598
+ n, _ = IntEvaluator.eval(expr)
599
+ n != 0
600
+ end
601
+
602
+ def read_ifdef
603
+ tok = @impl.lex
604
+ if tok.kind != T::IDENT
605
+ Util.errort!(tok, "identifier expected, but got #{tok}")
606
+ end
607
+ do_read_if(@macros[tok.sval])
608
+ end
609
+
610
+ def read_ifndef
611
+ tok = @impl.lex
612
+ if tok.kind != T::IDENT
613
+ Util.errort!(tok, "identifier expected, but got #{tok}")
614
+ end
615
+ expect_newline!
616
+ do_read_if(!@macros[tok.sval])
617
+ if tok.count == 2
618
+ # "ifndef" is the second token in this file.
619
+ # Prepare to detect an include guard.
620
+ ci = @cond_incl_stack.last
621
+ ci.include_guard = tok.sval
622
+ ci.file = tok.file
623
+ end
624
+ end
625
+
626
+ # @param [Boolean] istrue
627
+ def do_read_if(istrue)
628
+ @cond_incl_stack.push(make_cond_incl(istrue))
629
+
630
+ if (!istrue)
631
+ @impl.skip_cond_incl!
632
+ end
633
+ end
634
+
635
+ def read_define
636
+ name = read_ident
637
+ tok = @impl.lex
638
+ if Token.is_keyword?(tok, '(') && !tok.space
639
+ read_funclike_macro(name)
640
+ return
641
+ end
642
+ @impl.unget_token(tok)
643
+ read_obj_macro(name.sval)
644
+ end
645
+
646
+ # @param [Token] name
647
+ def read_funclike_macro(name)
648
+ param = {}
649
+ param, isvarg = read_funclike_macro_params(name)
650
+ body = read_funclike_macro_body(param)
651
+ hashhash_check!(body)
652
+ macro = make_func_macro(body, param.size, isvarg)
653
+ @macros[name.sval] = macro
654
+ end
655
+
656
+ # @param [Token] name
657
+ # @return [<Hash, Boolean>]
658
+ def read_funclike_macro_params(name)
659
+ pos = 0
660
+ param = {}
661
+ while true
662
+ tok = @impl.lex
663
+ if Token.is_keyword?(tok, ')')
664
+ return param, false
665
+ end
666
+ if pos > 0
667
+ if !Token.is_keyword?(tok, ',')
668
+ Util.errort!(tok, ", expected, but got #{tok}")
669
+ end
670
+ tok = @impl.lex
671
+ end
672
+ if tok.kind == T::NEWLINE
673
+ Util.errort!(name, "missing ')' in macro parameter list")
674
+ end
675
+ if Token.is_keyword?(tok, K::ELLIPSIS)
676
+ param["__VA_ARGS__"] = make_macro_token(pos, true)
677
+ pos += 1
678
+ expect!(')')
679
+ return param, true
680
+ end
681
+ if tok.kind != T::IDENT
682
+ Util.errort!(tok, "identifier expected, but got #{tok}")
683
+ end
684
+ arg = tok.sval
685
+ if next?(K::ELLIPSIS)
686
+ expect!(')')
687
+ param[arg] = make_macro_token(pos, true)
688
+ pos += 1
689
+ return param, true
690
+ end
691
+ param[arg] = make_macro_token(pos, false)
692
+ pos += 1
693
+ end
694
+ end
695
+
696
+ # @param [Hash] param
697
+ # @return [<Token>]
698
+ def read_funclike_macro_body(param)
699
+ r = []
700
+ while true
701
+ tok = @impl.lex
702
+ if tok.kind == T::NEWLINE
703
+ return r
704
+ end
705
+ if tok.kind == T::IDENT
706
+ subst = param[tok.sval]
707
+ if subst
708
+ subst = copy_token(subst)
709
+ subst.space = tok.space
710
+ r.push(subst)
711
+ next
712
+ end
713
+ end
714
+ r.push(tok)
715
+ end
716
+ end
717
+
718
+ # @param [String] name
719
+ def read_obj_macro(name)
720
+ body = []
721
+ while true
722
+ tok = @impl.lex
723
+ if tok.kind == T::NEWLINE
724
+ break
725
+ end
726
+ body.push(tok)
727
+ end
728
+ hashhash_check!(body)
729
+ @macros[name] = make_obj_macro(body)
730
+ end
731
+
732
+ # @param [<Token>] v
733
+ # @raise
734
+ def hashhash_check!(v)
735
+ if v.size == 0
736
+ return
737
+ end
738
+ if Token.is_keyword?(v.first, K::HASHHASH)
739
+ Util.errort!(vec_head(v), "'##' cannot appear at start of macro expansion")
740
+ end
741
+ if Token.is_keyword?(v.last, K::HASHHASH)
742
+ Util.errort!(vec_tail(v), "'##' cannot appear at end of macro expansion")
743
+ end
744
+ end
745
+
746
+ # @return [Token]
747
+ def read_ident
748
+ tok = @impl.lex
749
+ if tok.kind != T::IDENT
750
+ Util.errort!(tok, "identifier expected, but got #{tok}")
751
+ end
752
+ tok
753
+ end
754
+
755
+ # @return [<Token>]
756
+ def read_intexpr_line
757
+ r = []
758
+ while true
759
+ tok = read_expand_newline
760
+ if tok.kind == T::NEWLINE
761
+ return r
762
+ end
763
+ if Token.is_ident?(tok, "defined")
764
+ r.push(read_defined_op)
765
+ elsif tok.kind == T::IDENT
766
+ # C11 6.10.1.4 says that remaining identifiers
767
+ # should be replaced with pp-number 0.
768
+ r.push(CPP_TOKEN_ZERO)
769
+ else
770
+ r.push(tok)
771
+ end
772
+ end
773
+ end
774
+
775
+ # @return [Token]
776
+ def read_defined_op
777
+ tok = @impl.lex
778
+ if Token.is_keyword?(tok, '(')
779
+ tok = @impl.lex
780
+ expect!(')')
781
+ end
782
+ if tok.kind != T::IDENT
783
+ Util.errort!(tok, "identifier expected, but got #{tok}")
784
+ end
785
+ @macros[tok.sval] ? CPP_TOKEN_ONE : CPP_TOKEN_ZERO
786
+ end
787
+
788
+ # @param [Token] hash
789
+ def read_elif(hash)
790
+ if @cond_incl_stack.size == 0
791
+ Util.errort!(hash, "stray #elif")
792
+ end
793
+ ci = @cond_incl_stack.last
794
+ if ci.ctx == CondInclCtx::ELSE
795
+ Util.errort!(hash, "#elif after #else")
796
+ end
797
+ ci.ctx = CondInclCtx::ELIF
798
+ ci.include_guard = nil
799
+ if ci.wastrue || !read_constexpr
800
+ @impl.skip_cond_incl!
801
+ return
802
+ end
803
+ ci.wastrue = true
804
+ end
805
+
806
+ # @param [Token] hash
807
+ def read_else(hash)
808
+ if @cond_incl_stack.size == 0
809
+ Util.errort!(hash, "stray #else")
810
+ end
811
+ ci = @cond_incl_stack.last
812
+ if ci.ctx == CondInclCtx::ELSE
813
+ Util.errort!(hash, "#else appears in #else")
814
+ end
815
+ expect_newline!
816
+ ci.ctx = CondInclCtx::ELSE
817
+ ci.include_guard = nil
818
+ if ci.wastrue
819
+ @impl.skip_cond_incl!
820
+ end
821
+ end
822
+
823
+ # @param [Token] hash
824
+ def read_endif(hash)
825
+ if @cond_incl_stack.size == 0
826
+ Util.errort!(hash, "stray #endif")
827
+ end
828
+ ci = @cond_incl_stack.pop
829
+ expect_newline!
830
+
831
+ # Detect an #ifndef and #endif pair that guards the entire
832
+ # header file. Remember the macro name guarding the file
833
+ # so that we can skip the file next time.
834
+ if !ci.include_guard || ci.file != hash.file
835
+ return
836
+ end
837
+ last = skip_newlines!
838
+ if ci.file != last.file
839
+ @include_guard[ci.file.name] = ci.include_guard
840
+ end
841
+ end
842
+
843
+ # Skips all newlines and returns the first non-newline token.
844
+ #
845
+ # @return [Token]
846
+ def skip_newlines!
847
+ tok = @impl.lex
848
+ while (tok.kind == T::NEWLINE)
849
+ tok = @impl.lex
850
+ end
851
+ @impl.unget_token(tok)
852
+ tok
853
+ end
854
+
855
+ # @param [Token] hash
856
+ def read_error(hash)
857
+ Util.errort!(hash, "#error: #{read_error_message}")
858
+ end
859
+
860
+ # @param [Token] hash
861
+ def read_warning(hash)
862
+ print "#warning: #{read_error_message}\n"
863
+ # warnt(hash, "#warning: %s", read_error_message());
864
+ end
865
+
866
+ # @return [String]
867
+ def read_error_message
868
+ b = ""
869
+ while true
870
+ tok = @impl.lex
871
+ if tok.kind == T::NEWLINE
872
+ return b
873
+ end
874
+ if (b.size != 0) && tok.space
875
+ b << ' '
876
+ end
877
+ b << "#{tok}"
878
+ end
879
+ end
880
+
881
+ def read_undef
882
+ name = read_ident
883
+ expect_newline!
884
+ @macros.delete(name.sval)
885
+ end
886
+ end
887
+ end
888
+ end