dbc 1.3.0 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,488 @@
1
+ # Copyright (c) 2004 Charles M Mills
2
+ # This document is licenced under The MIT Licence.
3
+ # THIS SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND.
4
+ # See included LICENCE file.
5
+
6
+ require 'caphir/ctokenizer'
7
+
8
+ module Preprocessor
9
+
10
+ class AllTokens
11
+
12
+ include CTokenizer # brings in #error() functions
13
+
14
+ def initialize(source, macro_tokens)
15
+ @source = source
16
+ @macro_tokens = macro_tokens
17
+ end
18
+
19
+ def shift
20
+ if @macro_tokens.empty?
21
+ #raise "still resolving? - impossible" unless @macro_tokens.resolving.empty?
22
+ @source.shift
23
+ else
24
+ @macro_tokens.shift
25
+ end
26
+ end
27
+
28
+ def resolving
29
+ @macro_tokens.resolving
30
+ end
31
+
32
+ def resolving?(t_str)
33
+ @macro_tokens.resolving?(t_str)
34
+ end
35
+
36
+ def shift_and_filter
37
+ if @macro_tokens.empty?
38
+ #raise "still resolving? - impossible" unless @macro_tokens.resolving.empty?
39
+ @source.shift
40
+ else
41
+ t = @macro_tokens.shift
42
+ t = self.shift_and_filter if t[0] == :RESOLVING
43
+ t
44
+ end
45
+ end
46
+
47
+ def empty?
48
+ @source.empty? and @macro_tokens.empty?
49
+ end
50
+
51
+ def args_given?
52
+ if t = @macro_tokens.peek_nonspace
53
+ t[1] == '('
54
+ else
55
+ @source.match?(/\s*\(/)
56
+ end
57
+ end
58
+
59
+ def file; @source.file end
60
+ def line; @source.line end
61
+
62
+ # does not respond to match and scan
63
+ end
64
+
65
+ class SourceTokens
66
+
67
+ include CTokenizer
68
+
69
+ def initialize(source)
70
+ @tokens = []
71
+ self.add_source(source)
72
+ end
73
+
74
+ attr_reader :start_line
75
+
76
+ def add_source(source)
77
+ @tokens.push(source)
78
+ @start_line = true # begining of file
79
+ end
80
+
81
+ def scan(regexp)
82
+ @tokens.last.scan(regexp)
83
+ end
84
+
85
+ def match?(regexp)
86
+ @tokens.last.match?(regexp)
87
+ end
88
+
89
+ def base?
90
+ @tokens.length == 1
91
+ end
92
+
93
+ def empty?
94
+ @tokens.length == 1 and @tokens.last.empty?
95
+ end
96
+
97
+ def file
98
+ @tokens.last.file
99
+ end
100
+ def file=(val)
101
+ @tokens.last.file = val
102
+ end
103
+
104
+ def line
105
+ @tokens.last.line
106
+ end
107
+ def line=(val)
108
+ @tokens.last.line = val
109
+ end
110
+
111
+ def shift
112
+ t = @tokens.last.shift
113
+ while !t[0] and @tokens.length > 1
114
+ # we are done with the current file
115
+ @tokens.pop
116
+ t = @tokens.last.shift
117
+ end # while
118
+ t_sym = t[0]
119
+ unless t_sym == :SPACE or t_sym == :COMMENT
120
+ # spaces and comments don't effect @start_line
121
+ @start_line = (t_sym == :NEWLINE)
122
+ end
123
+ t
124
+ end
125
+
126
+ end # Tokens
127
+
128
+
129
+ class MacroTokens
130
+
131
+ def initialize(resolving=[], tokens=[])
132
+ # resolving is immutable
133
+ @resolving = resolving.freeze
134
+ @tokens = tokens
135
+ end
136
+
137
+ attr_reader :resolving
138
+
139
+ def resolving?(t_str)
140
+ @resolving.index(t_str)
141
+ end
142
+
143
+ # returns :RESOLVING tokens
144
+ def shift
145
+ t = @tokens.shift || CTokenizer::EOF_TOKEN
146
+ @resolving = t[1] if t[0] == :RESOLVING
147
+ t
148
+ end
149
+
150
+ def empty?
151
+ @tokens.empty?
152
+ end
153
+
154
+ def peek_filter
155
+ @tokens.each do |t|
156
+ return t unless t[0] == :RESOLVING
157
+ end
158
+ nil # is no non-resolving token
159
+ end
160
+
161
+ def peek_nonspace
162
+ @tokens.each do |t|
163
+ return t unless t[0] == :SPACE or t[0] == :RESOLVING
164
+ end
165
+ nil # is no non-space token
166
+ end
167
+
168
+ def args_given?
169
+ t = self.peek_nonspace
170
+ t and t[1] == '('
171
+ end
172
+
173
+ # IMPORTANT - source is modified by the add_*_macro() methods
174
+
175
+ def add_function_macro(t_str, source)
176
+ # macro should be unresolved, except for parameters
177
+ # we must add t_str to all resolving lists in the macro
178
+ unless source.empty?
179
+ # IMPORTANT - add t_str to each resolving token
180
+ add_tokens( source.collect! do |t|
181
+ t[0] == :RESOLVING ? [:RESOLVING, t[1].dup << t_str] : t
182
+ end )
183
+ # add t_str to current resolving array
184
+ @resolving = (@resolving.dup << t_str).freeze
185
+ end
186
+ self
187
+ end
188
+
189
+ def add_object_macro(t_str, source)
190
+ unless source.empty?
191
+ add_tokens(source)
192
+ @resolving = (@resolving.dup << t_str).freeze
193
+ end
194
+ self
195
+ end
196
+
197
+ private
198
+ def add_tokens(source)
199
+ # will restore old resolving value
200
+ source << [:RESOLVING, @resolving].freeze
201
+ source[source.length, 0] = @tokens # insert at end
202
+ @tokens = source
203
+ self
204
+ end
205
+
206
+ end # class
207
+
208
+ class Define
209
+
210
+ def Define.resolve_argument(resolving, tokens, defines)
211
+ return tokens if tokens.empty?
212
+ # this is important, as it sets the resolving value
213
+ # for the rest of the token
214
+ out = [ [:RESOLVING, resolving].freeze ]
215
+ stack = MacroTokens.new(resolving, tokens)
216
+ until stack.empty?
217
+ t = stack.shift
218
+ if t[0] == :IDENTIFIER and macro = defines[t_str = t[1]] \
219
+ and not stack.resolving?(t_str)
220
+ if macro.takes_args?
221
+ unless stack.args_given?
222
+ # no arguments given, don't resolve token
223
+ out << t
224
+ next
225
+ end
226
+ stack.add_function_macro(t_str, macro.value(stack) do |a|
227
+ Define.resolve_argument(resolving, a, defines)
228
+ end)
229
+ else
230
+ stack.add_object_macro(t_str, macro.value(stack))
231
+ end
232
+ # since we are saving out, must output this resolving token
233
+ out << [:RESOLVING, stack.resolving].freeze
234
+ else
235
+ out << t
236
+ end
237
+ end # until
238
+ out
239
+ end
240
+
241
+ def initialize(params, tokens)
242
+ tokens = tokens.to_s
243
+ tokens.strip!
244
+ @value = CTokenizer::Lexer.new(tokens).to_a
245
+ if params
246
+ unless params.class == Parameters
247
+ @parameters = Parameters.new
248
+ params.each { |p| @parameters << p }
249
+ else
250
+ @parameters = params
251
+ end
252
+ else
253
+ # will not contain any parameters
254
+ @value = parse(@value, nil)
255
+ @parameters = nil
256
+ end
257
+ @value.freeze
258
+ end
259
+
260
+ def takes_args?
261
+ # if @parameters is empty still want to scan for '(' and ')'
262
+ @parameters
263
+ end
264
+
265
+ # returns a copy of the macro's value with parameters substituted
266
+ def value(stack)
267
+ if takes_args?
268
+ args = @parameters.arguments(stack).collect! do |a|
269
+ # yield parameter tokens to be resolved
270
+ v = a.collect do |t|
271
+ case t[0]
272
+ when :RESOLVING
273
+ # these tokens are removed
274
+ when :COMMENT, :NEWLINE then ' '
275
+ else t[1]
276
+ end
277
+ end.join
278
+ v.strip! # whitespace should be removed
279
+ [:PARAMETER, v, yield(a)]
280
+ # [ :PARAMETER, token text, resolved token ]
281
+ # ie [ :PARAMETER, '__LINE__', [[:CONSTANT, 10]] ]
282
+ end
283
+
284
+ # do argument substitution
285
+ parse( \
286
+ @value.collect do |t|
287
+ if t[0] == :IDENTIFIER and idx = @parameters.index(t[1])
288
+ args[idx]
289
+ else
290
+ t
291
+ end
292
+ end , stack.resolving )
293
+ else
294
+ @value.dup
295
+ end
296
+ end
297
+
298
+ private
299
+
300
+ def parse(tokens, resolving)
301
+ tokens.each_with_index do |v, i|
302
+ case v[1]
303
+ when '##'
304
+ # j == previous non space token index
305
+ j = i
306
+ begin
307
+ j -= 1
308
+ prev_t = tokens[j]
309
+ end while prev_t and prev_t[0] == :SPACE
310
+ # k == next non space token index
311
+ k = i
312
+ begin
313
+ k += 1
314
+ next_t = tokens[k]
315
+ end while next_t and next_t[0] == :SPACE
316
+
317
+ if prev_t and next_t
318
+ # combine the two tokens (regardless of what type they are)
319
+ tokens.fill(nil, j...k)
320
+ tokens[k] = [:PASTED_TOKEN, prev_t[1] + next_t[1]].freeze
321
+ end
322
+ when '#'
323
+ k = i
324
+ begin
325
+ k += 1
326
+ next_t = tokens[k]
327
+ end while next_t and next_t[0] == :SPACE
328
+
329
+ # only stringify parameters
330
+ if next_t and next_t[0] == :PARAMETER
331
+ tokens.fill(nil, i...k)
332
+ tokens[k] = [:STRING, next_t[1].inspect].freeze
333
+ end
334
+ end if v # v may be nil
335
+ end
336
+ out = []
337
+ tokens.each do |t|
338
+ case t[0]
339
+ when :RESOVLING
340
+ resolving = t[1]
341
+ out << t
342
+ when :PARAMETER
343
+ out[out.length, 0] = t[2]
344
+ out << [:RESOLVING, resolving].freeze
345
+ when :PASTED_TOKEN
346
+ out[out.length, 0] = CTokenizer::Lexer.new(t[1]).to_a
347
+ else
348
+ out << t
349
+ end if t # unused tokens were replaced with nil
350
+ end
351
+ out
352
+ end
353
+
354
+ end # class
355
+
356
+ class Parameters < Array
357
+ VA_ARGS = '__VA_ARGS__'.freeze
358
+ ELLIPSIS = '...'.freeze
359
+
360
+ def initialize
361
+ super
362
+ @ellipses = nil
363
+ end
364
+
365
+ def with_ellipses(arg=nil)
366
+ raise "already has ellipses: #{self}" if ellipses?
367
+ if arg and arg != ELLIPSIS
368
+ self << arg
369
+ @ellipses = arg + ELLIPSIS # if arg is 'i' then 'i...'
370
+ else
371
+ self << VA_ARGS
372
+ @ellipses = ELLIPSIS
373
+ end
374
+ self
375
+ end
376
+
377
+ def ellipses?
378
+ @ellipses
379
+ end
380
+
381
+ def to_s
382
+ if ellipses?
383
+ self[0, self.length - 1] << @ellipses
384
+ else
385
+ self
386
+ end.join(',')
387
+ end
388
+
389
+ def inspect
390
+ "\#<#{self.class}:#{self.to_s}>"
391
+ end
392
+
393
+ # it is important that we keep track of the last resolving status when
394
+ # splitting up arguments. That way the argument does not inherit the
395
+ # last resolving status of the previous tokens when it is substituted.
396
+ def arguments(tokens)
397
+
398
+ # skip whitespace
399
+ while true
400
+ t = tokens.shift
401
+ case t[0]
402
+ when :RESOLVING, :SPACE, :COMMENT, :NEWLINE
403
+ # do nothing
404
+ else
405
+ break
406
+ end
407
+ end
408
+
409
+ tokens.error("expecting '('") unless t[1] == '('
410
+
411
+ arguments = [arg = [ [:RESOLVING, tokens.resolving].freeze ] ]
412
+
413
+ while true
414
+ t = tokens.shift
415
+ tokens.unmatched_error(')') unless t and t[0]
416
+
417
+ # comma and the closing ')' are not outputed
418
+ # bunch all the remaining parameters into the last parameter
419
+ if t[1] == ',' and arguments.length < self.length
420
+ # start of a new argument
421
+ arguments.push([ [:RESOLVING, tokens.resolving].freeze ])
422
+ arg = arguments.last
423
+ elsif t[1] == ')'
424
+ break # end of parameter list
425
+ else
426
+ arg << t
427
+ case t[1]
428
+ when '{'
429
+ Parameters.get_closing_braket(arg, tokens)
430
+ when '}'
431
+ tokens.unmatched_error('}')
432
+ when '('
433
+ Parameters.get_closing_paren(arg, tokens)
434
+ end
435
+ end
436
+ end
437
+
438
+ got = arguments.length
439
+ expected = self.length
440
+ if got < expected
441
+ tokens.error("wrong number of arguments (#{got} for #{expected})") unless ellipses?
442
+ expected -= 1 # the variable argument list can be blank
443
+ tokens.error("wrong number of arguments (#{got} for #{expected}+)") \
444
+ unless got == expected
445
+ arguments[expected] = [] # last arg is blank
446
+ end
447
+
448
+ arguments
449
+ end
450
+
451
+ def Parameters.get_closing_paren(arg, tokens)
452
+ begin
453
+ t = tokens.shift
454
+ tokens.unmatched_error(')') unless t and t[0]
455
+
456
+ arg << t
457
+ case t[1]
458
+ when '{'
459
+ get_closing_braket(arg, tokens)
460
+ when '}'
461
+ tokens.unmatched_error('}')
462
+ when '('
463
+ get_closing_paren(arg, tokens)
464
+ end
465
+ end until t[1] == ')'
466
+ end
467
+
468
+ def Parameters.get_closing_braket(arg, tokens)
469
+ begin
470
+ t = tokens.shift
471
+ tokens.unmatched_error('}') unless t and t[0]
472
+
473
+ arg << t
474
+ case t[1]
475
+ when '('
476
+ get_closing_paren(arg, tokens)
477
+ when ')'
478
+ tokens.unmatched_error(')')
479
+ when '{'
480
+ get_closing_braket(arg, tokens)
481
+ end
482
+ end until t[1] == '}'
483
+ end
484
+
485
+ end # Parameters
486
+
487
+ end # module
488
+