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