ppr 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- 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
@@ -0,0 +1,130 @@
|
|
1
|
+
# require 'yaml'
|
2
|
+
|
3
|
+
######################################################################
|
4
|
+
# Safer string generator. #
|
5
|
+
######################################################################
|
6
|
+
|
7
|
+
|
8
|
+
## Class which allows to safely execute a proc which generate safely
|
9
|
+
# a string into a stream.
|
10
|
+
class SaferGenerator
|
11
|
+
|
12
|
+
## The exception raised when the safer processed failed.
|
13
|
+
class SaferException < RuntimeError
|
14
|
+
end
|
15
|
+
|
16
|
+
## The list of dangerous constants of Object.
|
17
|
+
DANGER_CONSTANTS = [ :File, :IO, :Dir ]
|
18
|
+
|
19
|
+
## The list of dangerous methods of Kernel
|
20
|
+
DANGER_METHODS = [ :system, :`, :open ]
|
21
|
+
|
22
|
+
|
23
|
+
## Creates a new safe context with while removing Kernel methods and
|
24
|
+
# constants from +black_list+ in addition to the default dangerous
|
25
|
+
# ones.
|
26
|
+
def initialize(*black_list)
|
27
|
+
# Set the black list of methods.
|
28
|
+
@black_methods = black_list.select do |symbol|
|
29
|
+
symbol.to_s[0].match(/[a-z_]/)
|
30
|
+
end
|
31
|
+
# Set the black list of constants.
|
32
|
+
@black_constants = black_list.select do |symbol|
|
33
|
+
symbol.to_s[0].match(/[A-Z]/)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
## Strips all the Kernel methods and constants appart from the
|
38
|
+
# elements of the white list.
|
39
|
+
# Also strip Object from dangerous methods and constants apart
|
40
|
+
# from the elements of the white list.
|
41
|
+
def secure
|
42
|
+
# Gather the methods to strip.
|
43
|
+
methods = DANGER_METHODS + @black_methods
|
44
|
+
# Gather the constants to strip.
|
45
|
+
constants = DANGER_CONSTANTS + @black_constants
|
46
|
+
# Strip the dangerous methods.
|
47
|
+
methods.each do |meth|
|
48
|
+
Kernel.send(:undef_method,meth)
|
49
|
+
end
|
50
|
+
# Strip the dangerous constants from Object.
|
51
|
+
constants.each do |cst|
|
52
|
+
Object.send(:remove_const,cst)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
|
57
|
+
## Executes +block+ in a safe context for generating text into a +stream+.
|
58
|
+
#
|
59
|
+
# If no stream is given, returns the result as a string instead.
|
60
|
+
def run(stream = nil, &block)
|
61
|
+
unless stream
|
62
|
+
# No stream given
|
63
|
+
to_return = true
|
64
|
+
stream = StringIO.new("")
|
65
|
+
end
|
66
|
+
# Creates the pipe for communicating with the block.
|
67
|
+
rd,wr = IO.pipe
|
68
|
+
# Creates a process for executing the block.
|
69
|
+
pid = fork
|
70
|
+
if pid then
|
71
|
+
# This is the parent: waits for the block execution result.
|
72
|
+
# No need to write on the pipe. close it.
|
73
|
+
wr.close
|
74
|
+
# Read the result of the process and send it to stream
|
75
|
+
until rd.eof?
|
76
|
+
stream << rd.read
|
77
|
+
end
|
78
|
+
# No more need of rd.
|
79
|
+
rd.close
|
80
|
+
# Wait the end of the child process
|
81
|
+
Process.wait(pid)
|
82
|
+
# Where there a trouble?
|
83
|
+
unless $?.exited? then
|
84
|
+
# pid did not exit, internal error.
|
85
|
+
raise "*Internal error*: safer process #{pid} did not exit."
|
86
|
+
end
|
87
|
+
if $?.exitstatus !=0 then
|
88
|
+
# Reconstruct the exception from the stream, the exit
|
89
|
+
# status is the number of line to use.
|
90
|
+
e0 = Marshal.load( stream.string.each_line.
|
91
|
+
to_a[-$?.exitstatus..-1].join )
|
92
|
+
# Then resend the eception encapsulated into another one
|
93
|
+
# telling the safer process failed.
|
94
|
+
begin
|
95
|
+
raise e0
|
96
|
+
rescue Exception => e1
|
97
|
+
raise SaferException.new("*Error*: exception occured in safer process #{pid}.")
|
98
|
+
end
|
99
|
+
end
|
100
|
+
else
|
101
|
+
# This is the child: enter in safe mode and execute the block.
|
102
|
+
# No need to write on the pipe. close it.
|
103
|
+
rd.close
|
104
|
+
# Secure.
|
105
|
+
secure
|
106
|
+
# Execute the block.
|
107
|
+
begin
|
108
|
+
block.call(wr)
|
109
|
+
rescue Exception => e
|
110
|
+
# The exception is serialized and passed to the main process
|
111
|
+
# through the pipe.
|
112
|
+
e = Marshal.dump(e)
|
113
|
+
wr << "\n" << e
|
114
|
+
# The exit status is the number of line of the serialized
|
115
|
+
# exception.
|
116
|
+
exit!(e.each_line.count)
|
117
|
+
end
|
118
|
+
# No more need of wr.
|
119
|
+
wr.close
|
120
|
+
# End the process without any error.
|
121
|
+
exit!(0)
|
122
|
+
end
|
123
|
+
# Is there a string to return?
|
124
|
+
if to_return then
|
125
|
+
return stream.string
|
126
|
+
else
|
127
|
+
return nil
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
######################################################################
|
2
|
+
## Program testing KeywordSearcher ##
|
3
|
+
######################################################################
|
4
|
+
|
5
|
+
require "ppr.rb"
|
6
|
+
|
7
|
+
# Create the keyword searcher
|
8
|
+
puts "Creating the keyword searcher..."
|
9
|
+
$searcher = KeywordSearcher.new
|
10
|
+
|
11
|
+
# Prepare the input
|
12
|
+
puts "Creating the input text..."
|
13
|
+
$input = "Histoire de rationalisation : Le Renard et les Raisins\n" +
|
14
|
+
"Certain Renard Gascon, d'autres disent Normand,\n" +
|
15
|
+
"Mourant presque de faim, vit au haut d'une treille\n" +
|
16
|
+
"Des Raisins mûrs apparemment,\n" +
|
17
|
+
"Et couverts d'une peau vermeille.\n" +
|
18
|
+
"Le galand en eût fait volontiers un repas ;\n" +
|
19
|
+
"Mais comme il n'y pouvait atteindre :\n" +
|
20
|
+
"\"Ils sont trop verts, dit-il, et bons pour des goujats. \"\n" +
|
21
|
+
"Fit-il pas mieux que de se plaindre ?"
|
22
|
+
|
23
|
+
# Prepare the keywords to search
|
24
|
+
$keywords = [ "Renard", "Normand", "an" ]
|
25
|
+
$keywords.each.with_index do |keyword,i|
|
26
|
+
puts "Adding keyword #{keyword} associated with #{i}..."
|
27
|
+
$searcher[keyword] = i
|
28
|
+
end
|
29
|
+
|
30
|
+
# Prepare the expected result.
|
31
|
+
$expected = [ [0,33..38], [0,63..68], [1,94..100], [2,107..108],[2,224..225] ]
|
32
|
+
|
33
|
+
|
34
|
+
# Interate over the keywords founds in the text.
|
35
|
+
ok = true
|
36
|
+
$searcher.each_in($input).with_index do |entry_range, i|
|
37
|
+
entry, range = *entry_range
|
38
|
+
print "Got entry=#{entry} (#{$keywords[entry]}) at range=#{range}..."
|
39
|
+
unless $input[range] == $keywords[entry] then
|
40
|
+
puts "\nError: at range=#{range} there is #{$input[range]}"
|
41
|
+
ok = false
|
42
|
+
end
|
43
|
+
unless [entry,range] == $expected[i] then
|
44
|
+
puts "\nError: invalid result."
|
45
|
+
puts "Got #{[entry,range]} but expecting #{$expected[i]}."
|
46
|
+
ok = false
|
47
|
+
end
|
48
|
+
puts " ok."
|
49
|
+
end
|
50
|
+
|
51
|
+
|
52
|
+
|
53
|
+
if ok then
|
54
|
+
puts "Success."
|
55
|
+
else
|
56
|
+
puts "Failure."
|
57
|
+
end
|
data/lib/ppr/test_ppr.rb
ADDED
@@ -0,0 +1,291 @@
|
|
1
|
+
######################################################################
|
2
|
+
## Program testing Ppr ##
|
3
|
+
######################################################################
|
4
|
+
|
5
|
+
require "ppr.rb"
|
6
|
+
|
7
|
+
# Function for testing a preprocessor
|
8
|
+
def test_preprocessor(preprocessor,input,expected)
|
9
|
+
# Prepare the output and the input streams
|
10
|
+
puts "Preparing the input and ouput streams..."
|
11
|
+
output = StringIO.new("")
|
12
|
+
# Process the input and exepected arguments.
|
13
|
+
if !input.respond_to?(:each_line) or input.is_a?(String) then
|
14
|
+
# input is actually a file name, open it.
|
15
|
+
input = File.open(input.to_s,"r")
|
16
|
+
end
|
17
|
+
if !expected.respond_to?(:each_line) or expected.is_a?(String) then
|
18
|
+
# expected is actually a file name, open it.
|
19
|
+
expected = StringIO.new(File.read(expected.to_s))
|
20
|
+
end
|
21
|
+
|
22
|
+
# Apply the preprocessor
|
23
|
+
puts "Applying the preprocessor..."
|
24
|
+
preprocessor.preprocess(input,output)
|
25
|
+
|
26
|
+
# Check the result
|
27
|
+
puts "Checking the result..."
|
28
|
+
output.rewind
|
29
|
+
check = output.string == expected.read
|
30
|
+
|
31
|
+
unless check
|
32
|
+
puts "*Error*: invalid expansion result."
|
33
|
+
iline = output.string.each_line
|
34
|
+
expected.rewind
|
35
|
+
expected.each_line.with_index do |exp_line,i|
|
36
|
+
line = iline.next
|
37
|
+
puts "exp_line=#{exp_line}"
|
38
|
+
puts "line=#{line}"
|
39
|
+
if exp_line != line then
|
40
|
+
puts "Expected line #{i+1}:\n#{exp_line}"
|
41
|
+
puts "got:\n#{line}"
|
42
|
+
end
|
43
|
+
end
|
44
|
+
return false
|
45
|
+
end
|
46
|
+
return true
|
47
|
+
end
|
48
|
+
|
49
|
+
# Function for testing a preprocessor on +string+ which should raise an
|
50
|
+
# +exception+ string.
|
51
|
+
def test_preprocessor_exception(preprocessor,string,exception)
|
52
|
+
input = StringIO.new(string)
|
53
|
+
output = StringIO.new("")
|
54
|
+
begin
|
55
|
+
$ppr.preprocess(input,output)
|
56
|
+
puts "*Error*: preprocessed without exception."
|
57
|
+
return false
|
58
|
+
rescue Exception => e
|
59
|
+
if e.to_s.include?(exception.to_s) then
|
60
|
+
puts "Got exception: <#{e}> as expected."
|
61
|
+
return true
|
62
|
+
else
|
63
|
+
puts "*Error*: unexpected exception.",
|
64
|
+
"Got <#{e}> but expecting <#{exception}>."
|
65
|
+
return false
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
# Test the default preprocessor
|
71
|
+
|
72
|
+
puts "Building the default preprocessor with one expansion parameter..."
|
73
|
+
$ppr = Ppr::Preprocessor.new({"hello" => "Hello"})
|
74
|
+
puts "Testing it..."
|
75
|
+
$success = test_preprocessor($ppr,"#{File.dirname(__FILE__)}/test_ppr.txt",
|
76
|
+
"#{File.dirname(__FILE__)}/test_ppr_exp.txt")
|
77
|
+
|
78
|
+
# if $success then
|
79
|
+
# puts "Success."
|
80
|
+
# else
|
81
|
+
# puts "Failure."
|
82
|
+
# end
|
83
|
+
# exit
|
84
|
+
|
85
|
+
puts "\nBuilding a preprocessor with redefined keywords and one expansion parameter."
|
86
|
+
$ppr1 = Ppr::Preprocessor.new({hello: "Hello"},
|
87
|
+
apply: "RUBY", applyR: "RUBYR",
|
88
|
+
define: "MACRO", defineR: "MACRO_R",
|
89
|
+
assign: "ASSIGN",
|
90
|
+
endm: "ENDM",
|
91
|
+
expand: "~>", separator: /^|[^\w]|$/, glue: "@@")
|
92
|
+
puts "Testing it..."
|
93
|
+
$success &= test_preprocessor($ppr1,"#{File.dirname(__FILE__)}/test_ppr2.txt",
|
94
|
+
"#{File.dirname(__FILE__)}/test_ppr_exp.txt")
|
95
|
+
|
96
|
+
puts "Testing invalid proprocessor with identical strings for different keywords...."
|
97
|
+
begin
|
98
|
+
ppr = Ppr::Preprocessor.new(apply: "APPLY", applyR: "APPY_R",
|
99
|
+
define:"APPLY")
|
100
|
+
# Should not be there.
|
101
|
+
puts "*Error*: preprocessor built with invalid arguments did not raise any expcetion."
|
102
|
+
$success = false
|
103
|
+
rescue Exception => e
|
104
|
+
puts "Got exception as expected: #{e.to_s}."
|
105
|
+
end
|
106
|
+
|
107
|
+
|
108
|
+
puts "\nTesting invalid macro definitions..."
|
109
|
+
print "... Unfinished macro... "
|
110
|
+
$success &= test_preprocessor_exception($ppr, ".def\n :< 'Hello' \n",
|
111
|
+
":1) macro definition without name.")
|
112
|
+
|
113
|
+
print "... Incomplete arguments... "
|
114
|
+
$success &= test_preprocessor_exception($ppr,
|
115
|
+
"\n.def HE(\n :< 'Hello' \n.end\n",
|
116
|
+
":2) invalid arguments for macro definition.")
|
117
|
+
|
118
|
+
puts "\nTesting invalid macro call..."
|
119
|
+
print "... Unfinished macro call case #0... "
|
120
|
+
$success &= test_preprocessor_exception($ppr,
|
121
|
+
"\n\n.def HE(name)\n :< 'Hello ',name \n.end\nHE(",
|
122
|
+
"HE:6) incomplete arguments in macro call.")
|
123
|
+
print "... Unfinished macro call case #1... "
|
124
|
+
$success &= test_preprocessor_exception($ppr,
|
125
|
+
"\n.def HE(name)\n :< 'Hello ',name \n.end\nHE(A",
|
126
|
+
"HE:5) incomplete arguments in macro call.")
|
127
|
+
print "... Unfinished macro call case #2... "
|
128
|
+
$success &= test_preprocessor_exception($ppr,
|
129
|
+
".def HE(name)\n :< 'Hello ',name \n.end\nHE(A,",
|
130
|
+
"HE:4) incomplete arguments in macro call.")
|
131
|
+
print "... Unfinished macro call case #3... "
|
132
|
+
$success &= test_preprocessor_exception($ppr,
|
133
|
+
"\n.def HE(name)\n :< 'Hello ',name \n.end\nHE(A,B",
|
134
|
+
"HE:5) incomplete arguments in macro call.")
|
135
|
+
print "... macro call with too many arguments... "
|
136
|
+
$success &= test_preprocessor_exception($ppr,
|
137
|
+
"\n.def HE(name)\n :< 'Hello ',name \n.end\nHE(A,B)",
|
138
|
+
"HE:5):2: invalid number of argument: got 2, but expecting 1")
|
139
|
+
|
140
|
+
puts "\nTesting call of macro with invalid code..."
|
141
|
+
print "... Syntax error in macro code... "
|
142
|
+
$success &= test_preprocessor_exception($ppr,
|
143
|
+
"\n\n\n.def HE(name)\n :< 1,2 \n.end\nHE(Foo)",
|
144
|
+
"HE:7):5: syntax error")
|
145
|
+
print "... Division by zero in macro code... "
|
146
|
+
$success &= test_preprocessor_exception($ppr,
|
147
|
+
"\n.def HE(name)\n :< 1/0 \n.end\nHE(Foo)",
|
148
|
+
"HE:5):3: divided by 0")
|
149
|
+
print "... Undefined symbol in macro code... "
|
150
|
+
$success &= test_preprocessor_exception($ppr,
|
151
|
+
"\n.def HE(name)\n :< foobar \n.end\nHE(Foo)",
|
152
|
+
"HE:5):2: undefined local variable or method")
|
153
|
+
|
154
|
+
|
155
|
+
# puts "\nBuilding a preprocessor with an invalid escape character."
|
156
|
+
# $raised = false
|
157
|
+
# begin
|
158
|
+
# $ppr = Ppr::Preprocessor.new(escape: "HA")
|
159
|
+
# rescue Exception => e
|
160
|
+
# puts "As expected an exception has been raised (#{e.inspect})."
|
161
|
+
# $raised = true
|
162
|
+
# end
|
163
|
+
# unless $raised then
|
164
|
+
# puts "Error: an exception should have been raised."
|
165
|
+
# $success = false
|
166
|
+
# end
|
167
|
+
|
168
|
+
|
169
|
+
puts "\n\nNow going to Test the examples of the documentation."
|
170
|
+
puts "Testing example 1..."
|
171
|
+
$success &= test_preprocessor($ppr,
|
172
|
+
StringIO.new(
|
173
|
+
'Example 1:
|
174
|
+
.do
|
175
|
+
:< "Hello world!"
|
176
|
+
.end'
|
177
|
+
), StringIO.new(
|
178
|
+
'Example 1:
|
179
|
+
Hello world!') )
|
180
|
+
|
181
|
+
puts "\nTesting example 2..."
|
182
|
+
$success &= test_preprocessor($ppr,
|
183
|
+
StringIO.new(
|
184
|
+
'Example 2:
|
185
|
+
.def hello(world)
|
186
|
+
:< "Hello #{world}!"
|
187
|
+
.end
|
188
|
+
hello(Foo)
|
189
|
+
hello( Bar )'
|
190
|
+
), StringIO.new(
|
191
|
+
'Example 2:
|
192
|
+
Hello Foo!
|
193
|
+
Hello Bar !') )
|
194
|
+
|
195
|
+
puts "\nTesting example 3..."
|
196
|
+
$success &= test_preprocessor($ppr,
|
197
|
+
StringIO.new(
|
198
|
+
'Example 3:
|
199
|
+
.def hello(world) :< "Hello #{world}!"
|
200
|
+
.doR
|
201
|
+
:< "hello(WORLD)"
|
202
|
+
.end'
|
203
|
+
), StringIO.new(
|
204
|
+
'Example 3:
|
205
|
+
Hello WORLD!') )
|
206
|
+
|
207
|
+
puts "\nTesting example 4..."
|
208
|
+
$success &= test_preprocessor($ppr,
|
209
|
+
StringIO.new(
|
210
|
+
'Example 4:
|
211
|
+
.defR sum(num)
|
212
|
+
num = num.to_i
|
213
|
+
if num > 2 then
|
214
|
+
:< "(+ sum(#{num-1}) #{num} )"
|
215
|
+
else
|
216
|
+
:< "(+ 1 2 )"
|
217
|
+
end
|
218
|
+
.end
|
219
|
+
Some lisp: sum(5)'
|
220
|
+
), StringIO.new(
|
221
|
+
'Example 4:
|
222
|
+
Some lisp: (+ (+ (+ (+ 1 2 ) 3 ) 4 ) 5 )') )
|
223
|
+
|
224
|
+
puts "\nTesting example 5..."
|
225
|
+
$success &= test_preprocessor($ppr,
|
226
|
+
StringIO.new(
|
227
|
+
'Example 5:
|
228
|
+
.assign he :< "Hello"
|
229
|
+
.do :< @he + " world!\n"
|
230
|
+
.def hehe :< @he+@he
|
231
|
+
hehe'
|
232
|
+
), StringIO.new(
|
233
|
+
'Example 5:
|
234
|
+
Hello world!
|
235
|
+
HelloHello') )
|
236
|
+
|
237
|
+
puts "\nTesting example 6..."
|
238
|
+
$success &= test_preprocessor($ppr,
|
239
|
+
StringIO.new(
|
240
|
+
'Example 6:
|
241
|
+
.load :< "foo.inc"
|
242
|
+
.def foo :< "FooO"
|
243
|
+
.load :< "foo.inc"'
|
244
|
+
), StringIO.new(
|
245
|
+
'Example 6:
|
246
|
+
foo and bar
|
247
|
+
FooO and bar
|
248
|
+
') )
|
249
|
+
|
250
|
+
# Rebuild ppr to avoid conflict with example 6.
|
251
|
+
$ppr = Ppr::Preprocessor.new
|
252
|
+
puts "\nTesting example 7..."
|
253
|
+
$success &= test_preprocessor($ppr,
|
254
|
+
StringIO.new(
|
255
|
+
'Example 7:
|
256
|
+
.require :< "foo.inc"
|
257
|
+
.def foo :< "FooO"
|
258
|
+
.require :< "foo.inc"'
|
259
|
+
), StringIO.new(
|
260
|
+
'Example 7:
|
261
|
+
foo and bar
|
262
|
+
') )
|
263
|
+
|
264
|
+
puts "\nTesting example 8..."
|
265
|
+
$success &= test_preprocessor($ppr,
|
266
|
+
StringIO.new(
|
267
|
+
'Example 8:
|
268
|
+
.if :< (1 == 1)
|
269
|
+
.def is :< "IS"
|
270
|
+
This is true.
|
271
|
+
.else
|
272
|
+
This is false.
|
273
|
+
.endif
|
274
|
+
.if :< (1 == 0)
|
275
|
+
This is really true.
|
276
|
+
.else
|
277
|
+
This is really false.
|
278
|
+
.endif'
|
279
|
+
), StringIO.new(
|
280
|
+
'Example 8:
|
281
|
+
This IS true.
|
282
|
+
This IS really false.
|
283
|
+
') )
|
284
|
+
|
285
|
+
|
286
|
+
|
287
|
+
if $success then
|
288
|
+
puts "Success."
|
289
|
+
else
|
290
|
+
puts "Failure."
|
291
|
+
end
|