dbc 1.3.0 → 2.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.
@@ -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
+