ppr 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,849 @@
1
+ ######################################################################
2
+ # Preprocessor in Ruby: preprocessor whose macros are ruby code. ##
3
+ ######################################################################
4
+
5
+ require "ppr/safer_generator.rb"
6
+ require "ppr/keyword_searcher.rb"
7
+
8
+ module Ppr
9
+
10
+
11
+ ## Converts a +name+ to an attribute symbol.
12
+ def Ppr.to_attribute(name)
13
+ name = name.to_s
14
+ (name.start_with?("@") ? name : "@" + name).to_sym
15
+ end
16
+
17
+ ## Describes a storage for line number
18
+ class LineNumber < SimpleDelegator
19
+ ## Creates a new storage for line number +num+.
20
+ def initialize(num)
21
+ super(num.to_i)
22
+ end
23
+
24
+ ## Sets the line number to +num+.
25
+ def set(num)
26
+ __setobj__(num.to_i)
27
+ end
28
+ end
29
+
30
+
31
+ ## Describes a macro of the ruby preprocessor.
32
+ class Macro
33
+
34
+ ## The name of the macro.
35
+ attr_reader :name
36
+
37
+ ## Creates a new macro named +name+, starting at line number +num+,
38
+ # generated from preprocessor +ppr+ and with possible +variables+.
39
+ #
40
+ # The +expand+ strings can be redefined through keyword arguments.
41
+ # The +final+ flag indicates that the result of the macro expansion
42
+ # shall not be preprocessed again.
43
+ def initialize(name, num, ppr, *variables, expand: ":<", final: true)
44
+ # Check and set the name.
45
+ @name = name.to_str
46
+ # Check and set the line number of the begining of the macro.
47
+ @start_num = num.to_i
48
+ # Check and set the preprocessor
49
+ unless ppr.is_a?(Preprocessor) then
50
+ raise "Invalid class for a preprocessor: #{ppr.class}"
51
+ end
52
+ @ppr = ppr
53
+ # The start of the macro code is unknown at first (will be known
54
+ # when generating the code).
55
+ @code_num = nil
56
+ # Check and set the variables.
57
+ # print "variables=#{variables}\n"
58
+ @variables = variables.map do |variable|
59
+ variable = variable.to_str
60
+ unless variable.match(/^[a-z_][a-z0-9_]*$/)
61
+ raise "Invalid string for a macro variable: #{variable}"
62
+ end
63
+ variable
64
+ end
65
+
66
+ # Initialize the content of the macro as an array of lines.
67
+ @lines = []
68
+
69
+ # Set the default expansion operator string.
70
+ @expand = expand
71
+
72
+ # Set the macro code expansion result status: when final, no
73
+ # other macro is applied on it, otherwise, it is preprocessed again.
74
+ @final = final ? true : false
75
+ end
76
+
77
+ # Converts a +string+ to a quoted string.
78
+ def to_quoted(string)
79
+ return "\"" +
80
+ string.gsub(/[\"]|#\{/, "\"" => "\\\"", "\#{" => "\\\#{") +
81
+ "\""
82
+ #}}} This comment is just to avoid confusing the text editor.
83
+ end
84
+
85
+ ## Tells if the maco expansion result is final (i.e. it is not preprocessed
86
+ # again) or not.
87
+ def final?
88
+ return @final
89
+ end
90
+
91
+ ## Adds a +line+ to the macro.
92
+ def add(line)
93
+ # Process the line.
94
+ # Remove the ending newline if any.
95
+ @lines << line.chomp
96
+ end
97
+ alias << add
98
+
99
+ ## Checks if the macro is empty (no code line yet).
100
+ def empty?
101
+ return @lines.empty?
102
+ end
103
+
104
+ ## Generates the code of the macro invoked at line number +i_number+
105
+ # using +values+ for the variables.
106
+ def generate(i_number,*values)
107
+ # First generate a variable for the resulting text.
108
+ result = "result_"
109
+ count = 0
110
+ # Ensure the variable is not used in the code.
111
+ count += 1 while (@lines.find {|line| line.include?(name + count.to_s)})
112
+ result = result + count.to_s
113
+ # The process the lines and join them into the resulting string code.
114
+ buffer = StringIO.new("")
115
+ # Adds the prologue initializing the result text string and setting
116
+ # the values.
117
+ buffer << result + " = StringIO.new(\"\")\n"
118
+ # Update the macro code start line.
119
+ @code_num = 1
120
+ unless @variables.size == values.size
121
+ raise Macro.e_message(@name,
122
+ "invalid number of argument: got #{values.size}, but expecting #{@variables.size}.",i_number,@start_num)
123
+ end
124
+ @variables.each.with_index do |var,i|
125
+ buffer << "#{var} = #{to_quoted(values[i])}\n"
126
+ @code_num += 1
127
+ end
128
+ # Add each line of the macros.
129
+ @lines.each do |line|
130
+ # If any, replace the expand command by a concatenation to the
131
+ # resulting string buffer.
132
+ line = line.sub(@expand,"#{result} << ")
133
+ # Add the line
134
+ buffer << line << "\n"
135
+ end
136
+ # Adds the epilogue
137
+ buffer << result << ".string"
138
+ return buffer.string
139
+ end
140
+
141
+ # Methods used by apply for handling exception messages.
142
+
143
+ ## Regular expression for identifying a line number inside an exception
144
+ # message
145
+ E_NUMBER = /:[1-9][0-9]*:/
146
+ ## Type of exception which correspond to a macro execution.
147
+ E_TYPE = /\(eval\)\s*/
148
+
149
+ ## Tells if an exception +message+ includes a line number.
150
+ def e_number(message)
151
+ found = E_NUMBER.match(message)
152
+ if found then
153
+ # The number is found, return it.
154
+ return found.to_s[1..-2].to_i
155
+ else
156
+ # The number is not found.
157
+ return nil
158
+ end
159
+ end
160
+
161
+ ## Tells if an exception message is of a given +type+.
162
+ def e_type?(message,type)
163
+ return message =~ Regexp.new(type)
164
+ end
165
+
166
+ ## Shifts the line number inside an exception +message+ by +sh+.
167
+ def e_shift_number(message,sh)
168
+ # Edit the message to fix the line number and raise then.
169
+ return message.gsub(E_NUMBER) { |str|
170
+ # Remove the ':'
171
+ str = str[1..-2]
172
+ # Get and check the line number.
173
+ num = str.to_i
174
+ # print "num=#{num}\n"
175
+ if num.to_s == str then
176
+ # This is really a line number.
177
+ ":#{num+sh}:"
178
+ else
179
+ ":#{str}:"
180
+ end
181
+ }
182
+ end
183
+
184
+ ## Update an exception +message+ to refer macro +name+ invoked at line
185
+ # number +i_number+ and adds a possible macro line +number+.
186
+ def Macro.e_message(name, message, i_number, number = nil)
187
+ result = "Ppr error (#{name}:#{i_number})"
188
+ result << ":#{number}: " if number
189
+ result << message
190
+ return result
191
+ end
192
+
193
+ ## Update an exception +message+ to refer the macro invoked at line number
194
+ # +i_number+ and adds a possible macro line +number+.
195
+ def e_message(message, i_number, number = nil)
196
+ Macro.e_message(@name,message,i_number,number)
197
+ end
198
+
199
+ ## Applies the macro invoked at line number +i_number+ with +arguments+.
200
+ def apply(i_number,*arguments)
201
+ # Generate the code of the macro.
202
+ code = self.generate(i_number,*arguments)
203
+ # Evaluate the code in the safe context of ppr.
204
+ # print "code=#{code}\n"
205
+ begin
206
+ return @ppr.run do |__stream__|
207
+ __ppr__ = @ppr
208
+ begin
209
+ __stream__ << eval(code)
210
+ # rescue Exception => e
211
+ # raise e
212
+ end
213
+ end
214
+ rescue Exception => e
215
+ if e.is_a?(SaferGenerator::SaferException) then
216
+ # An exception happened while executing the macro code,
217
+ # get the cause (which contains the exception which has
218
+ # been raised while executing the macro).
219
+ cause = e.cause
220
+ message = cause.message
221
+ # Update the line number in the message if any.
222
+ if e_number(message) then
223
+ # There is a line number, update it in the context of
224
+ # the processed file.
225
+ message = e_shift_number(message, @start_num - @code_num)
226
+ # Raise the exception again with the updated message.
227
+ raise cause, e_message(message.gsub(E_TYPE,""),i_number)
228
+ else
229
+ # There was not any line number in the message, look
230
+ # for it into the backtrack message.
231
+ number = cause.backtrace.find do |trace|
232
+ found = e_number(trace)
233
+ if found and e_type?(trace,E_TYPE)
234
+ break found
235
+ else
236
+ next nil
237
+ end
238
+ end
239
+ if number then
240
+ # Update the line number in the context of the processed
241
+ # file.
242
+ number += @start_num - @code_num
243
+ # The number found, raise the exception again with
244
+ # the message updated with the number.
245
+ raise cause,
246
+ e_message(message.gsub(E_TYPE,""),i_number,number)
247
+ else
248
+ # No number, use the macro start instead for
249
+ # raising the exception.
250
+ raise cause, e_message(message,i_number,@start_num)
251
+ end
252
+ end
253
+ else
254
+ # An exception happened due to an internal error of the
255
+ # SaferGenerator class, raise it as is.
256
+ raise e
257
+ end
258
+ end
259
+ end
260
+
261
+ end
262
+
263
+
264
+ ## Describes an assignment macro of the ruby preprocessor.
265
+ class Assign < Macro
266
+ ## Creates a new assignment macro whose assigned variable is +var+,
267
+ # starting at line number +num+ generated from preprocessor +ppr+.
268
+ #
269
+ # The +expand+ strings be redefined through keyword arguments.
270
+ def initialize(name, num, ppr, expand: ":<")
271
+ super(name,num,ppr,expand: expand)
272
+ # Creates the attribute which will be assigned.
273
+ @var_sym = Ppr.to_attribute(name)
274
+ end
275
+
276
+ ## Applies the macro invoked at line number +i_number+,
277
+ # its result in assigned to the class variable.
278
+ def apply(i_number)
279
+ # Expands the macro.
280
+ line = super(i_number)
281
+ # Assign the result to the variable.
282
+ @ppr.parameter_set(@var_sym,line)
283
+ # No modification of the output file, so return an empty string.
284
+ return ""
285
+ end
286
+ end
287
+
288
+ ## Descibes an abstract class for loading or requiring files.
289
+ class LoadRequire < Macro
290
+ ## Creates a new load or require macro starting at line number +num+
291
+ # generated from preprocessor +ppr+.
292
+ #
293
+ # The +expand+ strings be redefined through keyword arguments.
294
+ def initialize(num, ppr, expand: ":<")
295
+ super("",num,ppr,expand: expand)
296
+ end
297
+
298
+ ## Loads and preprocess file +name+.
299
+ def loadm(name)
300
+ output = StringIO.new("")
301
+ # print "name=#{name}\n"
302
+ File.open(name,"r") do |input|
303
+ @ppr.preprocess(input,output)
304
+ end
305
+ return output.string
306
+ end
307
+ end
308
+
309
+ ## Describes a macro loading and pasting a file into the current one.
310
+ class Load < LoadRequire
311
+ ## Applies the macro invoked at line number +i_number+,
312
+ # its result is the name of the file to be loaded.
313
+ def apply(i_number)
314
+ # Expand the macro, its result is the name of the file to load.
315
+ name = super(i_number)
316
+ # print "Loading file: #{name}\n"
317
+ # Load and preprocess the file.
318
+ # return File.read(name)
319
+ return loadm(name)
320
+ end
321
+ end
322
+
323
+ ## Describes a macro loading and pasting a file into the current one
324
+ # only if it has not already been loaded before.
325
+ class Require < LoadRequire
326
+ @@required = [] # The already required files.
327
+
328
+ ## Applies the macro invoked at line number +i_number+,
329
+ # its result is the name of the file to be loaded if not already loaded.
330
+ def apply(i_number)
331
+ # Expand the macro, its result is the name of the file to load.
332
+ name = super(i_number)
333
+ # Has it already been required?
334
+ unless @@required.include?(name) then
335
+ # No, mark it as required and load and preprocess the file.
336
+ @@required << name
337
+ # return File.read(name)
338
+ return loadm(name)
339
+ else
340
+ # Yes, nothing to do.
341
+ return ""
342
+ end
343
+ end
344
+ end
345
+
346
+ ## Describes a conditional macro.
347
+ class If < Macro
348
+ ## Creates a new load or require macro starting at line number +num+
349
+ # generated from preprocessor +ppr+.
350
+ #
351
+ # The +expand+ strings be redefined through keyword arguments.
352
+ def initialize(num, ppr, expand: ":<")
353
+ super("",num,ppr,expand: expand)
354
+ end
355
+ end
356
+
357
+
358
+ ## Describes the ruby preprocessor.
359
+ #
360
+ # Usage:
361
+ # ppr = Ppr::Preprocessor.new(<some options>)
362
+ # ppr.preprocess(<some input stream>, <some output stream>)
363
+ class Preprocessor
364
+
365
+ ## Creates a new preprocessor, where +apply+, +applyR+, +define+, +defineR+,
366
+ # +assign+, +loadm+, +requirem+, +ifm+, +elsem+ and +endm+ are the
367
+ # keywords defining the beginings and end of a macro definitions,
368
+ # and where +separator+ is the regular expression used for
369
+ # separating macro references to the remaining of the code, +expand+ is
370
+ # the string representing the expansion operator of the macro, +glue+ is
371
+ # string used for glueing a macro expension to the text,
372
+ # +escape+ is the escape character.
373
+ #
374
+ # Assigned parameters can be added through +param+ to be used within
375
+ # the macros of the preprocessed text.
376
+ def initialize(params = {},
377
+ apply: ".do", applyR: ".doR",
378
+ define: ".def", defineR: ".defR",
379
+ assign: ".assign",
380
+ loadm: ".load", requirem: ".require",
381
+ ifm: ".if", elsem: ".else", endifm: ".endif",
382
+ endm: ".end",
383
+ expand: ":<",
384
+ separator: /^|[^\w]|$/, glue: "##",
385
+ escape: "\\")
386
+ # Check and Initialize the keywords
387
+ # NOTE: since there are a lot of checks, use a generic but
388
+ # harder to read code.
389
+ keys = [ "apply", "applyR", "define", "defineR", "assign",
390
+ "loadm", "requirem", "ifm", "elsem", "endifm", "endm"]
391
+ # Sort the keywords by string content to quickly find erroneously
392
+ # identical ones.
393
+ keys.sort_by! {|key| eval(key) }
394
+ # Check for identical keywords.
395
+ keys.each_with_index do |key,i|
396
+ value = eval(key)
397
+ if i+1 < keys.size then
398
+ # Check if the next keyword has the same string.
399
+ nvalue = eval(keys[i+1])
400
+ if value == nvalue then
401
+ # Two keywords with same string.
402
+ raise "'#{key}:#{value}' and '#{keys[i+1]}:#{nvalue}' keywords must be different."
403
+ end
404
+ end
405
+ end
406
+
407
+ # Seperate the begin of macro keywords from the others (they
408
+ # are used differently).
409
+ other_keys = ["elsem", "endifm", "endm"]
410
+ begin_keys = keys - other_keys
411
+ # Assign the begin of macro keywords to the corresponding attributes.
412
+ begin_keys.each do |key|
413
+ eval("@#{key} = #{key}.to_s")
414
+ end
415
+ # Generates the structures used for detecting the keywords.
416
+ # For the begining of macros.
417
+ @macro_keys = (begin_keys - other_keys).map do
418
+ |key| self.instance_variable_get("@#{key}")
419
+ end.sort!.reverse
420
+ # For the other keywords.
421
+ other_keys.each do |key|
422
+ eval('@'+key+' = Regexp.new("^\s*#{Regexp.escape('+key+')}\s*$")')
423
+ end
424
+
425
+ # Sets the expand command.
426
+ @expand = expand.to_s
427
+ # Check and set the separator, the glue and the escape.
428
+ @separator = Regexp.new("(?:#{separator}|#{glue})")
429
+ @glue = glue.to_s
430
+
431
+ # Initialize the current line number to 0.
432
+ @number = LineNumber.new(0)
433
+ # Initialize the macros.
434
+ @macros = KeywordSearcher.new(@separator)
435
+
436
+ # Initialize the stack for handling the if macros.
437
+ @if_mode = []
438
+
439
+ # Create the execution context for the macros.
440
+ @generator = SaferGenerator.new
441
+ @context = Object.new
442
+ # Process the preprocessing parameters.
443
+ params.each do |k,v|
444
+ parameter_set(k,v)
445
+ end
446
+ end
447
+
448
+ # Methods for handling the execution context of the macros.
449
+
450
+ ## Executes a macro in a safe context.
451
+ def run(&proc)
452
+ @generator.run do |__stream__|
453
+ @context.instance_exec(__stream__,&proc)
454
+ end
455
+ end
456
+
457
+ ## Sets parameter +param+ to +value+.
458
+ def parameter_set(param,value)
459
+ # print "Setting #{Ppr.to_attribute(param)} with #{value.to_s}\n"
460
+ @context.instance_variable_set(Ppr.to_attribute(param),value.to_s)
461
+ end
462
+
463
+ ## Gets the value of parameter +param.
464
+ def parameter_get(param)
465
+ return @context.instance_variable_get(Ppr.to_attribute(param))
466
+ end
467
+
468
+ # Methods for parsing the lines.
469
+
470
+ ## Restores a +string+ whose begining may have been glued.
471
+ def unglue_front(string)
472
+ if string.start_with?(@glue) then
473
+ # There is a glue, so remove it.
474
+ string = string[@glue.size..-1]
475
+ elsif string.start_with?("\\") then
476
+ # There is an escape, so remove it.
477
+ string = string[1..-1]
478
+ end
479
+ return string
480
+ end
481
+
482
+ ## Restores a +string+ whose ending may have been glued.
483
+ def unglue_back(string)
484
+ if string.end_with?(@glue) then
485
+ # There is a glue, so remove it.
486
+ string = string[0..(-@glue.size-1)]
487
+ elsif string.end_with?("\\") then
488
+ # There is an escape, so remove it.
489
+ string = string[0..-2]
490
+ end
491
+ return string
492
+ end
493
+
494
+ ## Gest the range of an argument starting
495
+ # from offset +start+ of +line+.
496
+ def get_argument_range(line, start)
497
+ if start >= line.size then
498
+ raise "incomplete arguments in macro call."
499
+ end
500
+ range = line[start..-1].match(/(\\\)|\\,|[^\),])*/).offset(0)
501
+ return (range[0]+start)..(range[1]+start-1)
502
+ end
503
+
504
+ ## Iterates over the range each argument of a +line+ from offset +start+.
505
+ # NOTE: keywords included into a longer one are ignored.
506
+ def each_argument_range(line,start)
507
+ return to_enum(:each_argument_range,line,start) unless block_given?
508
+ begin
509
+ # Get the next range
510
+ range = get_argument_range(line,start)
511
+ if range.last >= line.size then
512
+ raise "invalid line for arguments: #{line}"
513
+ end
514
+ # Apply the block on the range.
515
+ yield(range)
516
+ # Prepares the next range.
517
+ start = range.last + 2
518
+ end while start > 0 and line[start-1] != ")"
519
+ end
520
+
521
+
522
+ ## Tells if a line corresponds to an end keyword.
523
+ def is_endm?(line)
524
+ @endm.match(line)
525
+ end
526
+
527
+ ## Tells if a line corresponds to an else keyword.
528
+ def is_elsem?(line)
529
+ @elsem.match(line)
530
+ end
531
+
532
+ ## Tells if a line corresponds to an endif keyword.
533
+ def is_endifm?(line)
534
+ @endifm.match(line)
535
+ end
536
+
537
+ ## Extract a macro definition from a +line+ if there is one.
538
+ def get_macro_def(line)
539
+ line = line.strip
540
+ # Locate and identify the macro keyword.
541
+ macro_type = @macro_keys.find { |mdef| line.start_with?(mdef) }
542
+ return nil unless macro_type
543
+ line = line[(macro_type.size)..-1]
544
+ if /^\w/.match(line) then
545
+ # Actually the line was starting with a word including @define,
546
+ # this is not a real macro definition.
547
+ return nil
548
+ end
549
+ # Sets the flags according to the type.
550
+ final = (macro_type == @define or macro_type == @apply) ? true : false
551
+ named = (macro_type == @define or macro_type == @defineR or
552
+ macro_type == @assign) ? true : false
553
+ # Process the macro.
554
+ line = line.strip
555
+ if named then
556
+ # Handle the case of named macros.
557
+ # Extract the macro name.
558
+ name = /[a-zA-Z_]\w*/.match(line).to_s
559
+ if name.empty? then
560
+ # Macro with no name, error.
561
+ raise Macro.e_message(""," macro definition without name.",
562
+ @number)
563
+ end
564
+ line = line[name.size..-1]
565
+ line = line.strip
566
+ # Extract the arguments if any
567
+ # print "line=#{line}\n"
568
+ par = /^\(\s*[a-zA-Z_]\w*\s*(,\s*[a-zA-Z_]\w*\s*)*\)/.match(line)
569
+ if par then
570
+ if macro_type == @assign then
571
+ # Assignment macro: there should not be any argument.
572
+ raise Macro.e_message(""," assignement with argument.",
573
+ @number)
574
+ end
575
+ # There are arguments, process them.
576
+ par = par.to_s
577
+ # Extract them
578
+ arguments = par.scan(/[a-zA-Z_]\w*/)
579
+ line = line[par.size..-1].strip
580
+ else
581
+ # Check if there are some invalid arguments
582
+ if line[0] == "(" then
583
+ # Invalid arguments.
584
+ raise Macro.e_message(name,
585
+ " invalid arguments for macro definition.", @number)
586
+ end
587
+ # No argument.
588
+ arguments = []
589
+ end
590
+ else
591
+ # Handle the case of unnamed macros.
592
+ name = ""
593
+ end
594
+ case macro_type
595
+ when @assign then
596
+ macro = Assign.new(name,@number,self,expand: @expand)
597
+ when @loadm then
598
+ macro = Load.new(@number,self,expand: @expand)
599
+ when @requirem then
600
+ macro = Require.new(@number,self,expand: @expand)
601
+ when @ifm then
602
+ macro = If.new(@number,self,expand: @expand)
603
+ else
604
+ macro = Macro.new(name,@number,self,
605
+ *arguments,final: final,expand: @expand)
606
+ end
607
+ # Is it a one-line macro?
608
+ unless line.empty? then
609
+ # Yes, adds the content to the macro.
610
+ macro << line
611
+ end
612
+ return macro
613
+ end
614
+
615
+
616
+ ## Apply recursively each element of +macros+ to +line+.
617
+ #
618
+ # NOTE: a same macro is apply only once in a portion of the line.
619
+ def apply_macros(line)
620
+ # print "apply_macros on line=#{line}\n"
621
+
622
+ # Initialize the expanded line.
623
+ expanded = ""
624
+
625
+ # Look for a first macro.
626
+ macro,range = @macros.find(line)
627
+ while macro do
628
+ # print "macro.name=#{macro.name}, range=#{range}\n"
629
+ # If the are some arguments, extract them and cut the macro
630
+ # of the line.
631
+ if range.first > 0 then
632
+ sline = [ line[0..(range.first-1)] ]
633
+ else
634
+ sline = [ "" ]
635
+ end
636
+ if line[range.last+1] == "(" then
637
+ # print "Before line=#{line}\n"
638
+ last = range.last+1 # Last character position of the arguments
639
+ begin
640
+ sline +=
641
+ each_argument_range(line,range.last+2).map do |arg_range|
642
+ last = arg_range.last
643
+ apply_macros(line[arg_range])
644
+ end
645
+ rescue Exception => e
646
+ # A problem occurs while extracting the arguments.
647
+ # Re-raise it after processing its message.
648
+ raise e, macro.e_message(" " + e.message,@number)
649
+ end
650
+ range = range.first..(last+1)
651
+ end
652
+ if range.last + 1 < line.size then
653
+ sline << line[(range.last+1)..-1]
654
+ else
655
+ sline << ""
656
+ end
657
+ # print "After sline=#{sline}\n"
658
+ result = macro.apply(@number,*(sline[1..-2]))
659
+ # print "Macro result=#{result}, sline[0]=#{sline[0]} sline[-1]=#{sline[-1]}\n"
660
+ # Recurse on the modified portion of the line if the macro
661
+ # requires it
662
+ result = apply_macros(result) unless macro.final?
663
+ # print "Final expansion result=#{result}\n"
664
+ # Join the macro expansion result to the begining of the line
665
+ # removing the possible glue string.
666
+ expanded += unglue_back(sline[0]) + result
667
+ # print "expanded = #{expanded}\n"
668
+ # The remaining is to treat again after removing the possible
669
+ # glue string
670
+ line = unglue_front(sline[-1])
671
+ # Look for the next macro
672
+ macro,range = @macros.find(line)
673
+ end
674
+ # Add the remaining of the line to the expansion result.
675
+ expanded += line
676
+ # print "## expanded=#{expanded}\n"
677
+ return expanded
678
+ end
679
+
680
+ ## Close a +macro+ being input registering it if named or applying it
681
+ # otherwise.
682
+ #
683
+ # NOTE: for internal use only.
684
+ def close_macro(macro)
685
+ # Is the macro named?
686
+ unless macro.name.empty? or macro.is_a?(Assign) then
687
+ # Yes, register it.
688
+ @macros[macro.name] = macro
689
+ # Return an empty string since not to be applied yet.
690
+ return ""
691
+ else
692
+ # No, apply it straight away.
693
+ line = macro.apply(@number)
694
+ # Shall we process again the result?
695
+ unless macro.final? then
696
+ # print "Not final, line=#{line}.\n"
697
+ # Yes.
698
+ line = apply_macros(line)
699
+ end
700
+ if macro.is_a?(If) then
701
+ # The macro was an if condition, is the result false?
702
+ if line.empty? or line == "false" or line == "nil" then
703
+ # Yes, enter in skip_to_else mode which will skip the text
704
+ # until the next corresponding else keyword.
705
+ @if_mode.push(:skip_to_else)
706
+ else
707
+ # No, enter in keep to else mode which process the text
708
+ # normally until the next corresponding else keyword.
709
+ @if_mode.push(:keep_to_else)
710
+ end
711
+ # The condition is not to keep.
712
+ return ""
713
+ else
714
+ return line
715
+ end
716
+ end
717
+ end
718
+ private :close_macro
719
+
720
+ ## Preprocess an +input+ stream and write the result to an +output+
721
+ # stream.
722
+ def preprocess(input, output)
723
+ # # The current list of macros.
724
+ # @macros = KeywordSearcher.new(@separator)
725
+
726
+ # The macro currently being input.
727
+ cur_macro = nil
728
+
729
+ # Process the input line by line
730
+ input.each_line.with_index do |line,i|
731
+ @number.set(i+1)
732
+ # check if the line is to skip.
733
+ if @if_mode[-1] == :skip_to_else then
734
+ # Skipping until next else...
735
+ if is_elsem?(line) then
736
+ # And there is an else, enter in keep to endif mode.
737
+ @if_mode[-1] = :keep_to_endif
738
+ end
739
+ next # Skip.
740
+ elsif @if_mode[-1] == :keep_to_else then
741
+ # Keeping until an else is met.
742
+ if is_elsem?(line) then
743
+ # And there is an else, enter in skip to endif mode.
744
+ @if_mode[-1] = :skip_to_endif
745
+ # And skip current line since it is a keyword.
746
+ next
747
+ elsif is_endifm?(line) then
748
+ # This is the end of the if macro.
749
+ @if_mode.pop
750
+ # And skip current line since it is a keyword.
751
+ next
752
+ end
753
+ elsif @if_mode[-1] == :skip_to_endif then
754
+ # Skipping until next endif.
755
+ if is_endifm?(line) then
756
+ # And there is an endif, end the if macro.
757
+ @if_mode.pop
758
+ end
759
+ next # Skip
760
+ elsif @if_mode[-1] == :keep_to_endif then
761
+ if is_endifm?(line)
762
+ @if_mode.pop
763
+ # And there is an endif, end the if macro.
764
+ @if_mode.pop
765
+ # And skip current line since it is a keyword.
766
+ next
767
+ end
768
+ end
769
+ # No skip.
770
+
771
+ # Check if there are invalid elsem or endifm
772
+ if is_elsem?(line) then
773
+ raise Macro.e_message( "invalid #{@elsem} keyword.",@number)
774
+ elsif is_endifm?(line) then
775
+ raise Macro.e_message( "invalid #{@endifm} keyword.",@number)
776
+ end
777
+
778
+ # Is a macro being input?
779
+ if cur_macro then
780
+ # Yes.
781
+ if get_macro_def(line) then
782
+ # Yet, there is a begining of a macro definition: error
783
+ raise cur_macro.e_message(
784
+ "cannot define a new macro within another macro.",@number)
785
+ end
786
+ # Is the current macro being closed?
787
+ if is_endm?(line) then
788
+ # Yes, close the macro.
789
+ output << close_macro(cur_macro)
790
+ # The macro ends here.
791
+ cur_macro = nil
792
+ else
793
+ # No add the line to the current macro.
794
+ cur_macro << line
795
+ end
796
+ else
797
+ # There in no macro being input.
798
+ # Check if a new macro definition is present.
799
+ cur_macro = get_macro_def(line)
800
+ if cur_macro and !cur_macro.empty? then
801
+ # This is a one-line macro close it straight await.
802
+ output << close_macro(cur_macro)
803
+ # The macro ends here.
804
+ cur_macro = nil
805
+ next # The macro definition is not to be kept in the result
806
+ end
807
+ next if cur_macro # A new multi-line macro definition is found,
808
+ # it is not to be kept in the result.
809
+ # Check if an end of multi-line macro defintion is present.
810
+ if is_endm?(line) then
811
+ # Not in a macro, so error.
812
+ raise Macro.e_message("",
813
+ "#{@endm} outside a macro definition.",@number)
814
+ end
815
+ # Recursively apply the macro calls of the line.
816
+ line = apply_macros(line)
817
+ # Write the line to the output.
818
+ # print ">#{line}"
819
+ output << line
820
+ end
821
+ end
822
+ end
823
+ end
824
+
825
+
826
+
827
+
828
+ # Might be useful later, so kept as comment...
829
+ # ## Methods for parsing lines for macro processing.
830
+ # module LineParsing
831
+ #
832
+ # ## Processes a +string+ for becoming a valid argument.
833
+ # #
834
+ # # Concreatly, escapes the characters which have a function in an
835
+ # # argument list (e.g., ',', '(').
836
+ # def to_argument(string)
837
+ # return string.gsub(/[,\\)]/, "," => "\\,", "\\" => "\\\\", ")" => "\\)")
838
+ # end
839
+ #
840
+ # ## Restores a +string+ escaped to be a valid argument.
841
+ # def to_unargument(string)
842
+ # return string.gsub("\\\\", "\\")
843
+ # end
844
+ # end
845
+
846
+
847
+
848
+
849
+ end