ppr 0.0.2

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,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