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