node-marshal 0.2.1 → 0.2.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 +4 -4
- data/COPYING +23 -23
- data/README.rdoc +56 -49
- data/bin/noderbc +63 -63
- data/bin/noderbc.bat +0 -0
- data/ext/node-marshal/base85r.c +194 -194
- data/ext/node-marshal/extconf.rb +2 -2
- data/ext/node-marshal/node193.h +321 -321
- data/ext/node-marshal/node220.h +347 -347
- data/ext/node-marshal/node230.h +361 -361
- data/ext/node-marshal/nodedump.c +2356 -2296
- data/ext/node-marshal/nodedump.h +60 -60
- data/ext/node-marshal/nodeinfo.c +619 -615
- data/lib/node-marshal.rb +227 -227
- data/test/lifegame.rb +145 -145
- data/test/test_base.rb +226 -226
- data/test/test_complex.rb +136 -136
- data/test/test_lifegame.rb +68 -68
- data/test/test_namedarg.rb +54 -0
- data/test/test_obfuscator.rb +36 -36
- data/test/test_qcall.rb +52 -52
- data/test/tinytet.rb +79 -79
- metadata +4 -3
data/test/lifegame.rb
CHANGED
@@ -1,145 +1,145 @@
|
|
1
|
-
# Implementation of simple Convay's life game designed mainly
|
2
|
-
# for node compiler testing
|
3
|
-
#
|
4
|
-
# (C) 2015 Alexey Voskov
|
5
|
-
# License: 2-clause BSD
|
6
|
-
module LifeGame
|
7
|
-
|
8
|
-
# Representation of the cell of the game grid (field)
|
9
|
-
# Keeps the state of the cell (EMPTY, FILLED, BORN, DEAD)
|
10
|
-
# and neighbours of the cell (as array of pointers to another Cell
|
11
|
-
# object examples)
|
12
|
-
class Cell
|
13
|
-
EMPTY = 0
|
14
|
-
FILLED = 1
|
15
|
-
BORN = 2
|
16
|
-
DEAD = 3
|
17
|
-
|
18
|
-
attr_reader :value, :neighbours
|
19
|
-
def initialize
|
20
|
-
@value = EMPTY
|
21
|
-
@neighbours = []
|
22
|
-
end
|
23
|
-
|
24
|
-
def value=(val)
|
25
|
-
@value = val.to_i
|
26
|
-
end
|
27
|
-
|
28
|
-
def neighbours=(val)
|
29
|
-
@neighbours = val
|
30
|
-
end
|
31
|
-
|
32
|
-
def chr
|
33
|
-
case @value
|
34
|
-
when EMPTY; '.'
|
35
|
-
when FILLED; 'O'
|
36
|
-
when BORN; '+'
|
37
|
-
when DEAD; '-'
|
38
|
-
else; '?'
|
39
|
-
end
|
40
|
-
end
|
41
|
-
|
42
|
-
def update
|
43
|
-
case @value
|
44
|
-
when BORN; @value = FILLED
|
45
|
-
when DEAD; @value = EMPTY
|
46
|
-
end
|
47
|
-
end
|
48
|
-
end
|
49
|
-
|
50
|
-
# Convay's life game grid (field)
|
51
|
-
class Grid
|
52
|
-
public
|
53
|
-
def initialize(height, width)
|
54
|
-
width, height = width.to_i, height.to_i
|
55
|
-
raise "Invalid value of width" if (width < 3 || width > 100)
|
56
|
-
raise "Invalid value of height" if (height < 3 || height > 100)
|
57
|
-
@width, @height = width, height
|
58
|
-
|
59
|
-
@f = Array.new(@height) {|ind| Array.new(@width) {|ind| Cell.new }}
|
60
|
-
# Set neighbours for each cell
|
61
|
-
@@xy_shifts = [[-1, -1], [-1, 0], [-1, 1],
|
62
|
-
[0, -1], [0, 1],
|
63
|
-
[1, -1], [1, 0], [1, 1]]
|
64
|
-
(0...@height).each do |y|
|
65
|
-
(0...@width).each do |x|
|
66
|
-
# Calculate neighbours coordinates
|
67
|
-
xy = @@xy_shifts.map do |elem|
|
68
|
-
q = [elem[0] + y, elem[1] + x]
|
69
|
-
(q[0] < 0 || q[0] >= @height || q[1] < 0 || q[1] >= @width) ? nil : q
|
70
|
-
end
|
71
|
-
xy.compact!
|
72
|
-
# And transform them to the matrix
|
73
|
-
@f[y][x].neighbours = xy.map {|q| @f[q[0]][q[1]] }
|
74
|
-
end
|
75
|
-
end
|
76
|
-
self
|
77
|
-
end
|
78
|
-
# Initialize game field with one glider
|
79
|
-
def cfg_glider!
|
80
|
-
self.clear!
|
81
|
-
@f[1][2].value = Cell::FILLED
|
82
|
-
@f[2][3].value = Cell::FILLED
|
83
|
-
@f[3][1].value = Cell::FILLED
|
84
|
-
@f[3][2].value = Cell::FILLED
|
85
|
-
@f[3][3].value = Cell::FILLED
|
86
|
-
end
|
87
|
-
# Initialize game field with glider gun
|
88
|
-
def cfg_glider_gun!
|
89
|
-
self.clear!
|
90
|
-
gun = [
|
91
|
-
'........................O...........',
|
92
|
-
'......................O.O...........',
|
93
|
-
'............OO......OO............OO',
|
94
|
-
'...........O...O....OO............OO',
|
95
|
-
'OO........O.....O...OO..............',
|
96
|
-
'OO........O...O.OO....O.O...........',
|
97
|
-
'..........O.....O.......O...........',
|
98
|
-
'...........O...O....................',
|
99
|
-
'............OO......................'];
|
100
|
-
yshift, xshift = 2, 2
|
101
|
-
gun.each_index do |yi|
|
102
|
-
line = gun[yi]
|
103
|
-
(0..line.length).each {|xi| @f[yi+yshift][xi+xshift].value = Cell::FILLED if gun[yi][xi] == 'O'}
|
104
|
-
end
|
105
|
-
end
|
106
|
-
# Clear game field
|
107
|
-
def clear!
|
108
|
-
@f.each do |line|
|
109
|
-
line.each { |cell| cell.value = Cell::EMPTY }
|
110
|
-
end
|
111
|
-
end
|
112
|
-
# Convert game field to ASCII string (best viewed when typed in
|
113
|
-
# monospaced font). Suitable for autotesting
|
114
|
-
def to_ascii
|
115
|
-
txt = ""
|
116
|
-
@f.each do |line|
|
117
|
-
line.each { |field| txt += field.chr }
|
118
|
-
txt += "\n"
|
119
|
-
end
|
120
|
-
return txt
|
121
|
-
end
|
122
|
-
# Make one step (turn)
|
123
|
-
def make_step!
|
124
|
-
# Cells birth
|
125
|
-
@f.each_index do |yi|
|
126
|
-
@f[yi].each_index do |xi|
|
127
|
-
n = cell_neighbours_num(yi, xi)
|
128
|
-
@f[yi][xi].value = Cell::BORN if (@f[yi][xi].value == Cell::EMPTY && n == 3)
|
129
|
-
@f[yi][xi].value = Cell::DEAD if (@f[yi][xi].value == Cell::FILLED && !(n == 2 || n == 3))
|
130
|
-
end
|
131
|
-
end
|
132
|
-
# Cells update
|
133
|
-
@f.each do |line|
|
134
|
-
line.each {|val| val.update}
|
135
|
-
end
|
136
|
-
self
|
137
|
-
end
|
138
|
-
|
139
|
-
private
|
140
|
-
def cell_neighbours_num(y, x)
|
141
|
-
(@f[y][x].neighbours.select {|q| q.value == Cell::FILLED || q.value == Cell::DEAD }).length
|
142
|
-
end
|
143
|
-
end
|
144
|
-
|
145
|
-
end
|
1
|
+
# Implementation of simple Convay's life game designed mainly
|
2
|
+
# for node compiler testing
|
3
|
+
#
|
4
|
+
# (C) 2015 Alexey Voskov
|
5
|
+
# License: 2-clause BSD
|
6
|
+
module LifeGame
|
7
|
+
|
8
|
+
# Representation of the cell of the game grid (field)
|
9
|
+
# Keeps the state of the cell (EMPTY, FILLED, BORN, DEAD)
|
10
|
+
# and neighbours of the cell (as array of pointers to another Cell
|
11
|
+
# object examples)
|
12
|
+
class Cell
|
13
|
+
EMPTY = 0
|
14
|
+
FILLED = 1
|
15
|
+
BORN = 2
|
16
|
+
DEAD = 3
|
17
|
+
|
18
|
+
attr_reader :value, :neighbours
|
19
|
+
def initialize
|
20
|
+
@value = EMPTY
|
21
|
+
@neighbours = []
|
22
|
+
end
|
23
|
+
|
24
|
+
def value=(val)
|
25
|
+
@value = val.to_i
|
26
|
+
end
|
27
|
+
|
28
|
+
def neighbours=(val)
|
29
|
+
@neighbours = val
|
30
|
+
end
|
31
|
+
|
32
|
+
def chr
|
33
|
+
case @value
|
34
|
+
when EMPTY; '.'
|
35
|
+
when FILLED; 'O'
|
36
|
+
when BORN; '+'
|
37
|
+
when DEAD; '-'
|
38
|
+
else; '?'
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def update
|
43
|
+
case @value
|
44
|
+
when BORN; @value = FILLED
|
45
|
+
when DEAD; @value = EMPTY
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
# Convay's life game grid (field)
|
51
|
+
class Grid
|
52
|
+
public
|
53
|
+
def initialize(height, width)
|
54
|
+
width, height = width.to_i, height.to_i
|
55
|
+
raise "Invalid value of width" if (width < 3 || width > 100)
|
56
|
+
raise "Invalid value of height" if (height < 3 || height > 100)
|
57
|
+
@width, @height = width, height
|
58
|
+
|
59
|
+
@f = Array.new(@height) {|ind| Array.new(@width) {|ind| Cell.new }}
|
60
|
+
# Set neighbours for each cell
|
61
|
+
@@xy_shifts = [[-1, -1], [-1, 0], [-1, 1],
|
62
|
+
[0, -1], [0, 1],
|
63
|
+
[1, -1], [1, 0], [1, 1]]
|
64
|
+
(0...@height).each do |y|
|
65
|
+
(0...@width).each do |x|
|
66
|
+
# Calculate neighbours coordinates
|
67
|
+
xy = @@xy_shifts.map do |elem|
|
68
|
+
q = [elem[0] + y, elem[1] + x]
|
69
|
+
(q[0] < 0 || q[0] >= @height || q[1] < 0 || q[1] >= @width) ? nil : q
|
70
|
+
end
|
71
|
+
xy.compact!
|
72
|
+
# And transform them to the matrix
|
73
|
+
@f[y][x].neighbours = xy.map {|q| @f[q[0]][q[1]] }
|
74
|
+
end
|
75
|
+
end
|
76
|
+
self
|
77
|
+
end
|
78
|
+
# Initialize game field with one glider
|
79
|
+
def cfg_glider!
|
80
|
+
self.clear!
|
81
|
+
@f[1][2].value = Cell::FILLED
|
82
|
+
@f[2][3].value = Cell::FILLED
|
83
|
+
@f[3][1].value = Cell::FILLED
|
84
|
+
@f[3][2].value = Cell::FILLED
|
85
|
+
@f[3][3].value = Cell::FILLED
|
86
|
+
end
|
87
|
+
# Initialize game field with glider gun
|
88
|
+
def cfg_glider_gun!
|
89
|
+
self.clear!
|
90
|
+
gun = [
|
91
|
+
'........................O...........',
|
92
|
+
'......................O.O...........',
|
93
|
+
'............OO......OO............OO',
|
94
|
+
'...........O...O....OO............OO',
|
95
|
+
'OO........O.....O...OO..............',
|
96
|
+
'OO........O...O.OO....O.O...........',
|
97
|
+
'..........O.....O.......O...........',
|
98
|
+
'...........O...O....................',
|
99
|
+
'............OO......................'];
|
100
|
+
yshift, xshift = 2, 2
|
101
|
+
gun.each_index do |yi|
|
102
|
+
line = gun[yi]
|
103
|
+
(0..line.length).each {|xi| @f[yi+yshift][xi+xshift].value = Cell::FILLED if gun[yi][xi] == 'O'}
|
104
|
+
end
|
105
|
+
end
|
106
|
+
# Clear game field
|
107
|
+
def clear!
|
108
|
+
@f.each do |line|
|
109
|
+
line.each { |cell| cell.value = Cell::EMPTY }
|
110
|
+
end
|
111
|
+
end
|
112
|
+
# Convert game field to ASCII string (best viewed when typed in
|
113
|
+
# monospaced font). Suitable for autotesting
|
114
|
+
def to_ascii
|
115
|
+
txt = ""
|
116
|
+
@f.each do |line|
|
117
|
+
line.each { |field| txt += field.chr }
|
118
|
+
txt += "\n"
|
119
|
+
end
|
120
|
+
return txt
|
121
|
+
end
|
122
|
+
# Make one step (turn)
|
123
|
+
def make_step!
|
124
|
+
# Cells birth
|
125
|
+
@f.each_index do |yi|
|
126
|
+
@f[yi].each_index do |xi|
|
127
|
+
n = cell_neighbours_num(yi, xi)
|
128
|
+
@f[yi][xi].value = Cell::BORN if (@f[yi][xi].value == Cell::EMPTY && n == 3)
|
129
|
+
@f[yi][xi].value = Cell::DEAD if (@f[yi][xi].value == Cell::FILLED && !(n == 2 || n == 3))
|
130
|
+
end
|
131
|
+
end
|
132
|
+
# Cells update
|
133
|
+
@f.each do |line|
|
134
|
+
line.each {|val| val.update}
|
135
|
+
end
|
136
|
+
self
|
137
|
+
end
|
138
|
+
|
139
|
+
private
|
140
|
+
def cell_neighbours_num(y, x)
|
141
|
+
(@f[y][x].neighbours.select {|q| q.value == Cell::FILLED || q.value == Cell::DEAD }).length
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
end
|
data/test/test_base.rb
CHANGED
@@ -1,226 +1,226 @@
|
|
1
|
-
# encoding: UTF-8
|
2
|
-
|
3
|
-
require_relative '../lib/node-marshal.rb'
|
4
|
-
require 'test/unit'
|
5
|
-
|
6
|
-
# This unit test case contains several very simple "smoke tests"
|
7
|
-
# for NodeMarshal class
|
8
|
-
class TestNodeMarshal < Test::Unit::TestCase
|
9
|
-
# Test: Hello, World
|
10
|
-
def test_hello
|
11
|
-
program = <<-EOS
|
12
|
-
puts "Hello, World"
|
13
|
-
"Hello, World"
|
14
|
-
EOS
|
15
|
-
assert_node_compiler(program)
|
16
|
-
end
|
17
|
-
|
18
|
-
# Test: sum of squares
|
19
|
-
def test_sum_of_squares
|
20
|
-
program = <<-EOS
|
21
|
-
sum = 0
|
22
|
-
(1..50).each do |n|
|
23
|
-
sum += n ** 2
|
24
|
-
end
|
25
|
-
sum
|
26
|
-
EOS
|
27
|
-
assert_node_compiler(program)
|
28
|
-
end
|
29
|
-
|
30
|
-
# Test: factorial calculation
|
31
|
-
def test_factorials
|
32
|
-
program = <<-EOS
|
33
|
-
ni = [1, 2, 3, 4, 5, 6, 7, 8, 9]
|
34
|
-
def fact(n)
|
35
|
-
(n == 1) ? 1 : n * fact(n - 1)
|
36
|
-
end
|
37
|
-
ni.map {|x| fact(x) }
|
38
|
-
EOS
|
39
|
-
assert_node_compiler(program)
|
40
|
-
end
|
41
|
-
|
42
|
-
# Simple ROT13 task that combines several language construction
|
43
|
-
def test_rot13
|
44
|
-
program = <<-EOS
|
45
|
-
class ROT13 < String
|
46
|
-
def initialize(str)
|
47
|
-
@rfunc = Proc.new do |chr, limit|
|
48
|
-
newcode = chr.ord + 13
|
49
|
-
newcode -= 26 if newcode > limit.ord
|
50
|
-
newcode.chr
|
51
|
-
end
|
52
|
-
super(str)
|
53
|
-
end
|
54
|
-
|
55
|
-
def rot13
|
56
|
-
ans = ""
|
57
|
-
self.each_char do |c|
|
58
|
-
case c
|
59
|
-
when ('A'..'Z')
|
60
|
-
ans += @rfunc.(c,'Z')
|
61
|
-
when ('a'..'z')
|
62
|
-
ans += @rfunc.(c,'z')
|
63
|
-
else
|
64
|
-
ans += c
|
65
|
-
end
|
66
|
-
end
|
67
|
-
return ROT13.new(ans)
|
68
|
-
end
|
69
|
-
end
|
70
|
-
|
71
|
-
$str = ROT13.new("This is global variable string!")
|
72
|
-
[$str.rot13, $str.rot13.rot13]
|
73
|
-
EOS
|
74
|
-
assert_node_compiler(program)
|
75
|
-
end
|
76
|
-
|
77
|
-
# Test: array initialization and operations
|
78
|
-
def test_array
|
79
|
-
program = <<-EOS
|
80
|
-
[2*2, 3*3, 4*5 + 6, [5,6,7], true && false].flatten
|
81
|
-
EOS
|
82
|
-
assert_node_compiler(program)
|
83
|
-
end
|
84
|
-
|
85
|
-
# Test: Eratosthenes algorithm for searching prime numbers
|
86
|
-
def test_eratosthenes_primes
|
87
|
-
program = <<-EOS
|
88
|
-
table = Array.new(5000) {|index| index + 2}
|
89
|
-
table.each_index do |ind|
|
90
|
-
num = table[ind]
|
91
|
-
if num != 0
|
92
|
-
((ind + num)..(table.length)).step(num) {|i| table[i] = 0 }
|
93
|
-
end
|
94
|
-
end
|
95
|
-
table.select! {|val| val != 0}
|
96
|
-
table
|
97
|
-
EOS
|
98
|
-
assert_node_compiler(program)
|
99
|
-
end
|
100
|
-
|
101
|
-
# Test: classes declaration and inheritance
|
102
|
-
def test_classes
|
103
|
-
program = <<-EOS
|
104
|
-
class XYPoint
|
105
|
-
attr_reader :x, :y
|
106
|
-
def initialize(x, y)
|
107
|
-
@x = x.to_f
|
108
|
-
@y = y.to_f
|
109
|
-
end
|
110
|
-
|
111
|
-
def distance(p)
|
112
|
-
return ((@x - p.x)**2 + (@y - p.y)**2)**0.5
|
113
|
-
end
|
114
|
-
end
|
115
|
-
|
116
|
-
class PolarPoint < XYPoint
|
117
|
-
def initialize(r, phi)
|
118
|
-
phi = phi * (2*Math::PI) / 360
|
119
|
-
super(r*Math::sin(phi), r*Math::cos(phi))
|
120
|
-
end
|
121
|
-
end
|
122
|
-
|
123
|
-
a, b = XYPoint.new(0.0, 0.0), XYPoint.new(3.0, 4.0)
|
124
|
-
c, d = PolarPoint.new(1.0, 0.0), PolarPoint.new(2.0, 90.0)
|
125
|
-
[a.distance(b), c.distance(d)]
|
126
|
-
EOS
|
127
|
-
assert_node_compiler(program)
|
128
|
-
end
|
129
|
-
|
130
|
-
# Test: Base85r encoders and decoders
|
131
|
-
def test_base85r
|
132
|
-
base85_pass = ->str{NodeMarshal.base85r_decode(NodeMarshal.base85r_encode(str))}
|
133
|
-
# Short strings
|
134
|
-
assert_equal("", base85_pass.(""))
|
135
|
-
assert_equal(" ", base85_pass.(" "))
|
136
|
-
assert_equal("AB", base85_pass.("AB"))
|
137
|
-
assert_equal("ABC", base85_pass.("ABC"))
|
138
|
-
assert_equal("ABCD", base85_pass.("ABCD"))
|
139
|
-
assert_equal("ABCDE", base85_pass.("ABCDE"))
|
140
|
-
# Random strings
|
141
|
-
rnd = Random.new
|
142
|
-
20.times do
|
143
|
-
len, str = rnd.rand(4096), ""
|
144
|
-
len.times { str += rnd.rand(255).chr }
|
145
|
-
assert_equal(str, base85_pass.(str))
|
146
|
-
end
|
147
|
-
end
|
148
|
-
|
149
|
-
# Test regular expressions (NODE_MATCH3 node issue)
|
150
|
-
def test_node_match3
|
151
|
-
program = <<-EOS
|
152
|
-
a = " d--abc"
|
153
|
-
a =~ Regexp.new("abc")
|
154
|
-
EOS
|
155
|
-
assert_node_compiler(program)
|
156
|
-
end
|
157
|
-
|
158
|
-
def test_node_block_pass
|
159
|
-
program = <<-EOS
|
160
|
-
[1, 2, 3, "A"].map(&:class)
|
161
|
-
EOS
|
162
|
-
assert_node_compiler(program)
|
163
|
-
end
|
164
|
-
|
165
|
-
# Test expressions like x['a'] &&= true or x['b'] ||= false
|
166
|
-
# (they use NODE_OP_ASGN1 node and symbols that cannot be represented
|
167
|
-
# by String value)
|
168
|
-
def test_node_op_asgn1
|
169
|
-
program = <<EOS
|
170
|
-
x = {a: [1234], b: [5678, 2],
|
171
|
-
and1: true, and2: true, and3: false, and4: false,
|
172
|
-
or1: true, or2: true, or3: false, or4: false}
|
173
|
-
x[:a] &&= 'test'
|
174
|
-
x[:b] ||= 'qqq'
|
175
|
-
|
176
|
-
x[:and1] &&= false; x[:and2] &&= true
|
177
|
-
x[:and3] &&= false; x[:and4] &&= true
|
178
|
-
|
179
|
-
x[:or1] ||= false; x[:or2] ||= true
|
180
|
-
x[:or3] ||= false; x[:or4] ||= true
|
181
|
-
x
|
182
|
-
EOS
|
183
|
-
assert_node_compiler(program)
|
184
|
-
end
|
185
|
-
|
186
|
-
# Tests correct processing of nodes with "#{expr}"--style strings
|
187
|
-
# (correct processing of NODE_ARRAY chain inside NODE_DSTR node)
|
188
|
-
def test_dstr
|
189
|
-
program = 'a = "#{1} and #{2*2} and #{3*3} and #{4*4}"'
|
190
|
-
assert_node_compiler(program)
|
191
|
-
end
|
192
|
-
|
193
|
-
# Check the reaction on the parsing errors during the node creation
|
194
|
-
# In the case of syntax error ArgumentError exception should be generated
|
195
|
-
def test_syntax_error
|
196
|
-
program = "a = 1; a++" # Contains syntax error
|
197
|
-
# a) from memory (string)
|
198
|
-
test_passed = false
|
199
|
-
begin
|
200
|
-
node = NodeMarshal.new(:srcmemory, program)
|
201
|
-
rescue ArgumentError
|
202
|
-
test_passed = true
|
203
|
-
end
|
204
|
-
assert_equal(test_passed, true);
|
205
|
-
# b) from file
|
206
|
-
File.open("_tmp.rb", "w") {|fp| fp << program }
|
207
|
-
test_passed = false
|
208
|
-
begin
|
209
|
-
node = NodeMarshal.new(:srcfile, "_tmp.rb")
|
210
|
-
rescue ArgumentError
|
211
|
-
test_passed = true
|
212
|
-
end
|
213
|
-
assert_equal(test_passed, true);
|
214
|
-
end
|
215
|
-
|
216
|
-
# Part of the tests: compare result of direct usage of eval
|
217
|
-
# for the source code and its
|
218
|
-
def assert_node_compiler(program)
|
219
|
-
res_eval = eval(program)
|
220
|
-
node = NodeMarshal.new(:srcmemory, program)
|
221
|
-
bin = node.to_bin; node = nil; GC.start
|
222
|
-
node = NodeMarshal.new(:binmemory, bin)
|
223
|
-
res_node = node.compile.eval
|
224
|
-
assert_equal(res_node, res_eval, "Invalid result from the compiled node")
|
225
|
-
end
|
226
|
-
end
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
3
|
+
require_relative '../lib/node-marshal.rb'
|
4
|
+
require 'test/unit'
|
5
|
+
|
6
|
+
# This unit test case contains several very simple "smoke tests"
|
7
|
+
# for NodeMarshal class
|
8
|
+
class TestNodeMarshal < Test::Unit::TestCase
|
9
|
+
# Test: Hello, World
|
10
|
+
def test_hello
|
11
|
+
program = <<-EOS
|
12
|
+
puts "Hello, World"
|
13
|
+
"Hello, World"
|
14
|
+
EOS
|
15
|
+
assert_node_compiler(program)
|
16
|
+
end
|
17
|
+
|
18
|
+
# Test: sum of squares
|
19
|
+
def test_sum_of_squares
|
20
|
+
program = <<-EOS
|
21
|
+
sum = 0
|
22
|
+
(1..50).each do |n|
|
23
|
+
sum += n ** 2
|
24
|
+
end
|
25
|
+
sum
|
26
|
+
EOS
|
27
|
+
assert_node_compiler(program)
|
28
|
+
end
|
29
|
+
|
30
|
+
# Test: factorial calculation
|
31
|
+
def test_factorials
|
32
|
+
program = <<-EOS
|
33
|
+
ni = [1, 2, 3, 4, 5, 6, 7, 8, 9]
|
34
|
+
def fact(n)
|
35
|
+
(n == 1) ? 1 : n * fact(n - 1)
|
36
|
+
end
|
37
|
+
ni.map {|x| fact(x) }
|
38
|
+
EOS
|
39
|
+
assert_node_compiler(program)
|
40
|
+
end
|
41
|
+
|
42
|
+
# Simple ROT13 task that combines several language construction
|
43
|
+
def test_rot13
|
44
|
+
program = <<-EOS
|
45
|
+
class ROT13 < String
|
46
|
+
def initialize(str)
|
47
|
+
@rfunc = Proc.new do |chr, limit|
|
48
|
+
newcode = chr.ord + 13
|
49
|
+
newcode -= 26 if newcode > limit.ord
|
50
|
+
newcode.chr
|
51
|
+
end
|
52
|
+
super(str)
|
53
|
+
end
|
54
|
+
|
55
|
+
def rot13
|
56
|
+
ans = ""
|
57
|
+
self.each_char do |c|
|
58
|
+
case c
|
59
|
+
when ('A'..'Z')
|
60
|
+
ans += @rfunc.(c,'Z')
|
61
|
+
when ('a'..'z')
|
62
|
+
ans += @rfunc.(c,'z')
|
63
|
+
else
|
64
|
+
ans += c
|
65
|
+
end
|
66
|
+
end
|
67
|
+
return ROT13.new(ans)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
$str = ROT13.new("This is global variable string!")
|
72
|
+
[$str.rot13, $str.rot13.rot13]
|
73
|
+
EOS
|
74
|
+
assert_node_compiler(program)
|
75
|
+
end
|
76
|
+
|
77
|
+
# Test: array initialization and operations
|
78
|
+
def test_array
|
79
|
+
program = <<-EOS
|
80
|
+
[2*2, 3*3, 4*5 + 6, [5,6,7], true && false].flatten
|
81
|
+
EOS
|
82
|
+
assert_node_compiler(program)
|
83
|
+
end
|
84
|
+
|
85
|
+
# Test: Eratosthenes algorithm for searching prime numbers
|
86
|
+
def test_eratosthenes_primes
|
87
|
+
program = <<-EOS
|
88
|
+
table = Array.new(5000) {|index| index + 2}
|
89
|
+
table.each_index do |ind|
|
90
|
+
num = table[ind]
|
91
|
+
if num != 0
|
92
|
+
((ind + num)..(table.length)).step(num) {|i| table[i] = 0 }
|
93
|
+
end
|
94
|
+
end
|
95
|
+
table.select! {|val| val != 0}
|
96
|
+
table
|
97
|
+
EOS
|
98
|
+
assert_node_compiler(program)
|
99
|
+
end
|
100
|
+
|
101
|
+
# Test: classes declaration and inheritance
|
102
|
+
def test_classes
|
103
|
+
program = <<-EOS
|
104
|
+
class XYPoint
|
105
|
+
attr_reader :x, :y
|
106
|
+
def initialize(x, y)
|
107
|
+
@x = x.to_f
|
108
|
+
@y = y.to_f
|
109
|
+
end
|
110
|
+
|
111
|
+
def distance(p)
|
112
|
+
return ((@x - p.x)**2 + (@y - p.y)**2)**0.5
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
class PolarPoint < XYPoint
|
117
|
+
def initialize(r, phi)
|
118
|
+
phi = phi * (2*Math::PI) / 360
|
119
|
+
super(r*Math::sin(phi), r*Math::cos(phi))
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
a, b = XYPoint.new(0.0, 0.0), XYPoint.new(3.0, 4.0)
|
124
|
+
c, d = PolarPoint.new(1.0, 0.0), PolarPoint.new(2.0, 90.0)
|
125
|
+
[a.distance(b), c.distance(d)]
|
126
|
+
EOS
|
127
|
+
assert_node_compiler(program)
|
128
|
+
end
|
129
|
+
|
130
|
+
# Test: Base85r encoders and decoders
|
131
|
+
def test_base85r
|
132
|
+
base85_pass = ->str{NodeMarshal.base85r_decode(NodeMarshal.base85r_encode(str))}
|
133
|
+
# Short strings
|
134
|
+
assert_equal("", base85_pass.(""))
|
135
|
+
assert_equal(" ", base85_pass.(" "))
|
136
|
+
assert_equal("AB", base85_pass.("AB"))
|
137
|
+
assert_equal("ABC", base85_pass.("ABC"))
|
138
|
+
assert_equal("ABCD", base85_pass.("ABCD"))
|
139
|
+
assert_equal("ABCDE", base85_pass.("ABCDE"))
|
140
|
+
# Random strings
|
141
|
+
rnd = Random.new
|
142
|
+
20.times do
|
143
|
+
len, str = rnd.rand(4096), ""
|
144
|
+
len.times { str += rnd.rand(255).chr }
|
145
|
+
assert_equal(str, base85_pass.(str))
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
# Test regular expressions (NODE_MATCH3 node issue)
|
150
|
+
def test_node_match3
|
151
|
+
program = <<-EOS
|
152
|
+
a = " d--abc"
|
153
|
+
a =~ Regexp.new("abc")
|
154
|
+
EOS
|
155
|
+
assert_node_compiler(program)
|
156
|
+
end
|
157
|
+
|
158
|
+
def test_node_block_pass
|
159
|
+
program = <<-EOS
|
160
|
+
[1, 2, 3, "A"].map(&:class)
|
161
|
+
EOS
|
162
|
+
assert_node_compiler(program)
|
163
|
+
end
|
164
|
+
|
165
|
+
# Test expressions like x['a'] &&= true or x['b'] ||= false
|
166
|
+
# (they use NODE_OP_ASGN1 node and symbols that cannot be represented
|
167
|
+
# by String value)
|
168
|
+
def test_node_op_asgn1
|
169
|
+
program = <<EOS
|
170
|
+
x = {a: [1234], b: [5678, 2],
|
171
|
+
and1: true, and2: true, and3: false, and4: false,
|
172
|
+
or1: true, or2: true, or3: false, or4: false}
|
173
|
+
x[:a] &&= 'test'
|
174
|
+
x[:b] ||= 'qqq'
|
175
|
+
|
176
|
+
x[:and1] &&= false; x[:and2] &&= true
|
177
|
+
x[:and3] &&= false; x[:and4] &&= true
|
178
|
+
|
179
|
+
x[:or1] ||= false; x[:or2] ||= true
|
180
|
+
x[:or3] ||= false; x[:or4] ||= true
|
181
|
+
x
|
182
|
+
EOS
|
183
|
+
assert_node_compiler(program)
|
184
|
+
end
|
185
|
+
|
186
|
+
# Tests correct processing of nodes with "#{expr}"--style strings
|
187
|
+
# (correct processing of NODE_ARRAY chain inside NODE_DSTR node)
|
188
|
+
def test_dstr
|
189
|
+
program = 'a = "#{1} and #{2*2} and #{3*3} and #{4*4}"'
|
190
|
+
assert_node_compiler(program)
|
191
|
+
end
|
192
|
+
|
193
|
+
# Check the reaction on the parsing errors during the node creation
|
194
|
+
# In the case of syntax error ArgumentError exception should be generated
|
195
|
+
def test_syntax_error
|
196
|
+
program = "a = 1; a++" # Contains syntax error
|
197
|
+
# a) from memory (string)
|
198
|
+
test_passed = false
|
199
|
+
begin
|
200
|
+
node = NodeMarshal.new(:srcmemory, program)
|
201
|
+
rescue ArgumentError
|
202
|
+
test_passed = true
|
203
|
+
end
|
204
|
+
assert_equal(test_passed, true);
|
205
|
+
# b) from file
|
206
|
+
File.open("_tmp.rb", "w") {|fp| fp << program }
|
207
|
+
test_passed = false
|
208
|
+
begin
|
209
|
+
node = NodeMarshal.new(:srcfile, "_tmp.rb")
|
210
|
+
rescue ArgumentError
|
211
|
+
test_passed = true
|
212
|
+
end
|
213
|
+
assert_equal(test_passed, true);
|
214
|
+
end
|
215
|
+
|
216
|
+
# Part of the tests: compare result of direct usage of eval
|
217
|
+
# for the source code and its
|
218
|
+
def assert_node_compiler(program)
|
219
|
+
res_eval = eval(program)
|
220
|
+
node = NodeMarshal.new(:srcmemory, program)
|
221
|
+
bin = node.to_bin; node = nil; GC.start
|
222
|
+
node = NodeMarshal.new(:binmemory, bin)
|
223
|
+
res_node = node.compile.eval
|
224
|
+
assert_equal(res_node, res_eval, "Invalid result from the compiled node")
|
225
|
+
end
|
226
|
+
end
|