node-marshal 0.1.1
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/README.rdoc +18 -0
- data/bin/noderbc +27 -0
- data/bin/noderbc.bat +1 -0
- data/ext/node-marshal/COPYING +23 -0
- data/ext/node-marshal/base85r.c +190 -0
- data/ext/node-marshal/extconf.rb +3 -0
- data/ext/node-marshal/libobj/readme.txt +1 -0
- data/ext/node-marshal/node193.h +312 -0
- data/ext/node-marshal/node220.h +338 -0
- data/ext/node-marshal/nodedump.c +1804 -0
- data/ext/node-marshal/nodedump.h +67 -0
- data/ext/node-marshal/nodeinfo.c +466 -0
- data/lib/node-marshal.rb +64 -0
- data/test/lifegame.rb +145 -0
- data/test/test_base.rb +161 -0
- data/test/test_complex.rb +133 -0
- data/test/test_lifegame.rb +68 -0
- data/test/tinytet.rb +79 -0
- metadata +72 -0
data/lib/node-marshal.rb
ADDED
@@ -0,0 +1,64 @@
|
|
1
|
+
require_relative '../ext/node-marshal/nodemarshal.so'
|
2
|
+
require 'zlib'
|
3
|
+
|
4
|
+
class NodeMarshal
|
5
|
+
# call-seq:
|
6
|
+
# obj.to_compiled_rb(outfile, opts)
|
7
|
+
#
|
8
|
+
# Transforms node to the Ruby file
|
9
|
+
# - +outfile+ -- name of the output file
|
10
|
+
# - +opts+ -- Hash with options (+:compress+, +:so_path+)
|
11
|
+
# +:compress+ can be +true+ or +false+, +:so_path+ is a test string
|
12
|
+
# with the command for nodemarshal.so inclusion (default is
|
13
|
+
# require_relative '../ext/node-marshal/nodemarshal.so')
|
14
|
+
def to_compiled_rb(outfile, *args)
|
15
|
+
compress = false
|
16
|
+
so_path = "require_relative '../ext/node-marshal/nodemarshal.so'"
|
17
|
+
if args.length > 0
|
18
|
+
opts = args[0]
|
19
|
+
if opts.has_key?(:compress)
|
20
|
+
compress = opts[:compress]
|
21
|
+
end
|
22
|
+
if opts.has_key?(:so_path)
|
23
|
+
so_path = opts[:so_path]
|
24
|
+
end
|
25
|
+
end
|
26
|
+
# Compression
|
27
|
+
if compress
|
28
|
+
zlib_include = "require 'zlib'"
|
29
|
+
data_txt = NodeMarshal.base85r_encode(Zlib::deflate(self.to_bin))
|
30
|
+
data_bin = "Zlib::inflate(NodeMarshal.base85r_decode(data_txt))"
|
31
|
+
else
|
32
|
+
zlib_include = "# No compression"
|
33
|
+
data_txt = self.to_text
|
34
|
+
data_bin = "NodeMarshal.base85r_decode(data_txt)"
|
35
|
+
end
|
36
|
+
# Document header
|
37
|
+
txt = <<EOS
|
38
|
+
# Ruby compressed source code
|
39
|
+
# RUBY_PLATFORM: #{RUBY_PLATFORM}
|
40
|
+
# RUBY_VERSION: #{RUBY_VERSION}
|
41
|
+
#{zlib_include}
|
42
|
+
#{so_path}
|
43
|
+
data_txt = <<DATABLOCK
|
44
|
+
#{data_txt}
|
45
|
+
DATABLOCK
|
46
|
+
data_bin = #{data_bin}
|
47
|
+
node = NodeMarshal.new(:binmemory, data_bin)
|
48
|
+
node.filename = __FILE__
|
49
|
+
node.filepath = File.expand_path(node.filename)
|
50
|
+
node.compile.eval
|
51
|
+
EOS
|
52
|
+
# Process input arguments
|
53
|
+
if outfile != nil
|
54
|
+
File.open(outfile, 'w') {|fp| fp << txt}
|
55
|
+
end
|
56
|
+
return txt
|
57
|
+
end
|
58
|
+
|
59
|
+
def self.compile_rb_file(outfile, inpfile, *args)
|
60
|
+
node = NodeMarshal.new(:srcfile, inpfile)
|
61
|
+
node.to_compiled_rb(outfile, *args)
|
62
|
+
return true
|
63
|
+
end
|
64
|
+
end
|
data/test/lifegame.rb
ADDED
@@ -0,0 +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
|
data/test/test_base.rb
ADDED
@@ -0,0 +1,161 @@
|
|
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
|
+
# test_string_eval(program, [1, 2, 6, 24, 120, 720, 5040, 40320, 362880], "Factorial");
|
41
|
+
end
|
42
|
+
|
43
|
+
# Simple ROT13 task that combines several language construction
|
44
|
+
def test_rot13
|
45
|
+
program = <<-EOS
|
46
|
+
class ROT13 < String
|
47
|
+
def initialize(str)
|
48
|
+
@rfunc = Proc.new do |chr, limit|
|
49
|
+
newcode = chr.ord + 13
|
50
|
+
newcode -= 26 if newcode > limit.ord
|
51
|
+
newcode.chr
|
52
|
+
end
|
53
|
+
super(str)
|
54
|
+
end
|
55
|
+
|
56
|
+
def rot13
|
57
|
+
ans = ""
|
58
|
+
self.each_char do |c|
|
59
|
+
case c
|
60
|
+
when ('A'..'Z')
|
61
|
+
ans += @rfunc.(c,'Z')
|
62
|
+
when ('a'..'z')
|
63
|
+
ans += @rfunc.(c,'z')
|
64
|
+
else
|
65
|
+
ans += c
|
66
|
+
end
|
67
|
+
end
|
68
|
+
return ROT13.new(ans)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
$str = ROT13.new("This is global variable string!")
|
73
|
+
[$str.rot13, $str.rot13.rot13]
|
74
|
+
EOS
|
75
|
+
assert_node_compiler(program)
|
76
|
+
end
|
77
|
+
|
78
|
+
# Test: array initialization and operations
|
79
|
+
def test_array
|
80
|
+
program = <<-EOS
|
81
|
+
[2*2, 3*3, 4*5 + 6, [5,6,7], true && false].flatten
|
82
|
+
EOS
|
83
|
+
assert_node_compiler(program)
|
84
|
+
end
|
85
|
+
|
86
|
+
# Test: Eratosthenes algorithm for searching prime numbers
|
87
|
+
def test_eratosthenes_primes
|
88
|
+
program = <<-EOS
|
89
|
+
table = Array.new(5000) {|index| index + 2}
|
90
|
+
table.each_index do |ind|
|
91
|
+
num = table[ind]
|
92
|
+
if num != 0
|
93
|
+
((ind + num)..(table.length)).step(num) {|i| table[i] = 0 }
|
94
|
+
end
|
95
|
+
end
|
96
|
+
table.select! {|val| val != 0}
|
97
|
+
table
|
98
|
+
EOS
|
99
|
+
assert_node_compiler(program)
|
100
|
+
end
|
101
|
+
|
102
|
+
# Test: classes declaration and inheritance
|
103
|
+
def test_classes
|
104
|
+
program = <<-EOS
|
105
|
+
class XYPoint
|
106
|
+
attr_reader :x, :y
|
107
|
+
def initialize(x, y)
|
108
|
+
@x = x.to_f
|
109
|
+
@y = y.to_f
|
110
|
+
end
|
111
|
+
|
112
|
+
def distance(p)
|
113
|
+
return ((@x - p.x)**2 + (@y - p.y)**2)**0.5
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
class PolarPoint < XYPoint
|
118
|
+
def initialize(r, phi)
|
119
|
+
phi = phi * (2*Math::PI) / 360
|
120
|
+
super(r*Math::sin(phi), r*Math::cos(phi))
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
a, b = XYPoint.new(0.0, 0.0), XYPoint.new(3.0, 4.0)
|
125
|
+
c, d = PolarPoint.new(1.0, 0.0), PolarPoint.new(2.0, 90.0)
|
126
|
+
[a.distance(b), c.distance(d)]
|
127
|
+
EOS
|
128
|
+
|
129
|
+
assert_node_compiler(program)
|
130
|
+
end
|
131
|
+
|
132
|
+
# Test: Base85r encoders and decoders
|
133
|
+
def test_base85r
|
134
|
+
base85_pass = ->str{NodeMarshal.base85r_decode(NodeMarshal.base85r_encode(str))}
|
135
|
+
# Short strings
|
136
|
+
assert_equal("", base85_pass.(""))
|
137
|
+
assert_equal(" ", base85_pass.(" "))
|
138
|
+
assert_equal("AB", base85_pass.("AB"))
|
139
|
+
assert_equal("ABC", base85_pass.("ABC"))
|
140
|
+
assert_equal("ABCD", base85_pass.("ABCD"))
|
141
|
+
assert_equal("ABCDE", base85_pass.("ABCDE"))
|
142
|
+
# Random strings
|
143
|
+
rnd = Random.new
|
144
|
+
20.times do
|
145
|
+
len, str = rnd.rand(4096), ""
|
146
|
+
len.times { str += rnd.rand(255).chr }
|
147
|
+
assert_equal(str, base85_pass.(str))
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
# Part of the tests: compare result of direct usage of eval
|
152
|
+
# for the source code and its
|
153
|
+
def assert_node_compiler(program)
|
154
|
+
res_eval = eval(program)
|
155
|
+
node = NodeMarshal.new(:srcmemory, program)
|
156
|
+
bin = node.to_bin; node = nil; GC.start
|
157
|
+
node = NodeMarshal.new(:binmemory, bin)
|
158
|
+
res_node = node.compile.eval
|
159
|
+
assert_equal(res_node, res_eval, "Invalid result from the compiled node")
|
160
|
+
end
|
161
|
+
end
|
@@ -0,0 +1,133 @@
|
|
1
|
+
require_relative '../lib/node-marshal.rb'
|
2
|
+
require 'test/unit'
|
3
|
+
|
4
|
+
# Wrapper for Ruby Proc class (Proc and lambdas) that
|
5
|
+
# saves the text representation of the function
|
6
|
+
class TestFunc
|
7
|
+
def initialize(text)
|
8
|
+
@text = text.to_s
|
9
|
+
@value = eval(text)
|
10
|
+
end
|
11
|
+
|
12
|
+
def to_s
|
13
|
+
@text
|
14
|
+
end
|
15
|
+
|
16
|
+
def inspect
|
17
|
+
@text
|
18
|
+
end
|
19
|
+
|
20
|
+
def call(x,a)
|
21
|
+
@value.(x,a)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
# Class for generation of the complex structure that includes:
|
26
|
+
# Hashes, Arrays, Floats, Fixnums, Ranges, Strings, Symbols, Procs
|
27
|
+
# It is used for node testing
|
28
|
+
class TestHashTree
|
29
|
+
attr_reader :value, :depth
|
30
|
+
def initialize(*args)
|
31
|
+
if args.length == 2
|
32
|
+
rnd = Random.new
|
33
|
+
lambdas = ['->x,a{a*x**2 + 2*x + 1}', '->x,a{Math::sin(a) + x**2 * a**0.5}']
|
34
|
+
data_size, depth = args[0], args[1]
|
35
|
+
@depth = depth
|
36
|
+
@value = {:depth => depth, :data => [], :leaves =>[] }
|
37
|
+
data_size.times do
|
38
|
+
case rnd.rand(30000) % 6
|
39
|
+
when 0
|
40
|
+
@value[:data] << 10**(rnd.rand(600000) * 0.001 - 300)
|
41
|
+
when 1
|
42
|
+
@value[:data] << rnd.rand(2**31 - 1)
|
43
|
+
when 2
|
44
|
+
rndstr = (Array.new(25) { (rnd.rand(127 - 32) + 32).chr }).join
|
45
|
+
@value[:data] << rndstr
|
46
|
+
when 3
|
47
|
+
@value[:data] << TestFunc.new(lambdas[rnd.rand(30000) % lambdas.length])
|
48
|
+
when 4
|
49
|
+
a, b = rnd.rand(100), rnd.rand(10)
|
50
|
+
@value[:data] << (a..(a + b))
|
51
|
+
when 5
|
52
|
+
a, b = rnd.rand(100), rnd.rand(10)
|
53
|
+
@value[:data] << (a...(a + b))
|
54
|
+
else
|
55
|
+
nil
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
if depth > 0
|
60
|
+
3.times do
|
61
|
+
@value[:leaves] << TestHashTree.new(data_size, depth - 1)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
elsif args.length == 1
|
65
|
+
h = args[0]
|
66
|
+
raise(ArgumentError, 'Input argument must be a hash') if !h.is_a?(Hash)
|
67
|
+
@depth = h[:depth]
|
68
|
+
@value = {
|
69
|
+
:depth => @depth,
|
70
|
+
:data => h[:data].map {|x| (x.class == String) ? x.dup : x },
|
71
|
+
:leaves => h[:leaves].map {|x| TestHashTree.new(x)}}
|
72
|
+
else
|
73
|
+
raise ArgumentError, 'Invalid numer of arguments'
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
def to_s
|
78
|
+
@value.to_s
|
79
|
+
end
|
80
|
+
|
81
|
+
def inspect
|
82
|
+
self.to_s
|
83
|
+
end
|
84
|
+
# Recursive comparison of two complex structures
|
85
|
+
def ==(val)
|
86
|
+
if (self.depth != val.depth) ||
|
87
|
+
(self.value[:data].length != val.value[:data].length)
|
88
|
+
return false
|
89
|
+
else
|
90
|
+
# Compare data
|
91
|
+
res = true
|
92
|
+
v1, v2 = self.value[:data], val.value[:data]
|
93
|
+
v1.each_index do |ind|
|
94
|
+
if (v1[ind].class != Proc && v1[ind].class != TestFunc)
|
95
|
+
res = false if v1[ind] != v2[ind]
|
96
|
+
else
|
97
|
+
x, a = 1.2345, 1.5
|
98
|
+
y1, y2 = v1[ind].call(x,a), v2[ind].call(x,a)
|
99
|
+
res = false if y1 != y2
|
100
|
+
end
|
101
|
+
end
|
102
|
+
# Compare leaves
|
103
|
+
p1, p2 = self.value[:leaves], val.value[:leaves]
|
104
|
+
p1.each_index {|ind| res = false if p1[ind] != p2[ind] }
|
105
|
+
return res
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
# Tests that check the processing of large and/or complex nodes
|
111
|
+
class TestComplex < Test::Unit::TestCase
|
112
|
+
def test_bruteforce
|
113
|
+
# Create test tree, turn it to node and save it to disk
|
114
|
+
puts 'Creating the tree...'
|
115
|
+
tree_src = TestHashTree.new(20, 7);
|
116
|
+
puts 'Dumping the node...'
|
117
|
+
tree_str = tree_src.to_s
|
118
|
+
node = NodeMarshal.new(:srcmemory, tree_str)
|
119
|
+
tree_bin = node.to_bin
|
120
|
+
|
121
|
+
File.open('node.bin', 'wb') {|fp| fp << tree_bin }
|
122
|
+
puts " Source code size: %d bytes" % tree_str.length
|
123
|
+
puts " Binary data size: %d bytes" % tree_bin.length
|
124
|
+
# Clear the memory
|
125
|
+
node = nil; GC.start
|
126
|
+
# Load the node from the disk and turn it to tree
|
127
|
+
puts 'Loading the node...'
|
128
|
+
node = NodeMarshal.new(:binfile, 'node.bin')
|
129
|
+
puts node.inspect
|
130
|
+
tree_dest = TestHashTree.new(node.compile.eval)
|
131
|
+
assert_equal(tree_src, tree_dest)
|
132
|
+
end
|
133
|
+
end
|