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.
- checksums.yaml +7 -0
- data/.gitignore +9 -0
- data/.travis.yml +5 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +21 -0
- data/README.md +333 -0
- data/Rakefile +10 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/lib/ppr.rb +12 -0
- data/lib/ppr/bar.inc +1 -0
- data/lib/ppr/foo.inc +1 -0
- data/lib/ppr/keyword_searcher.rb +105 -0
- data/lib/ppr/ppr_core.rb +849 -0
- data/lib/ppr/safer_generator.rb +130 -0
- data/lib/ppr/test_keyword_searcher.rb +57 -0
- data/lib/ppr/test_ppr.inc +7 -0
- data/lib/ppr/test_ppr.rb +291 -0
- data/lib/ppr/test_ppr.txt +62 -0
- data/lib/ppr/test_ppr2.txt +61 -0
- data/lib/ppr/test_ppr_exp.txt +31 -0
- data/lib/ppr/test_safer_generator.rb +140 -0
- data/lib/ppr/version.rb +3 -0
- data/ppr.gemspec +39 -0
- metadata +114 -0
data/lib/ppr/ppr_core.rb
ADDED
@@ -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
|