beambridge 0.9.0

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.
Files changed (57) hide show
  1. checksums.yaml +15 -0
  2. data/.gitignore +23 -0
  3. data/.rspec +2 -0
  4. data/Gemfile +4 -0
  5. data/History.txt +35 -0
  6. data/LICENSE +20 -0
  7. data/README.md +130 -0
  8. data/Rakefile +44 -0
  9. data/VERSION.yml +4 -0
  10. data/beambridge.gemspec +29 -0
  11. data/benchmarks/bench.rb +21 -0
  12. data/examples/echo/README.md +12 -0
  13. data/examples/echo/echo.erl +13 -0
  14. data/examples/echo/echo.rb +10 -0
  15. data/examples/gruff/gruff.erl +61 -0
  16. data/examples/gruff/gruff_provider.rb +30 -0
  17. data/examples/gruff/gruff_run.sh +19 -0
  18. data/examples/gruff/stat_run.sh +20 -0
  19. data/examples/gruff/stat_writer.erl +40 -0
  20. data/examples/simple/README.md +5 -0
  21. data/examples/simple/rerl.rb +110 -0
  22. data/examples/simple/rerl.sh +37 -0
  23. data/examples/tinderl/README.md +14 -0
  24. data/examples/tinderl/tinderl.erl +43 -0
  25. data/examples/tinderl/tinderl.rb +27 -0
  26. data/ext/decoder.c +398 -0
  27. data/ext/extconf.rb +11 -0
  28. data/lib/beambridge.rb +34 -0
  29. data/lib/beambridge/condition.rb +66 -0
  30. data/lib/beambridge/conditions/boolean.rb +11 -0
  31. data/lib/beambridge/conditions/hash.rb +13 -0
  32. data/lib/beambridge/conditions/static.rb +34 -0
  33. data/lib/beambridge/conditions/type.rb +17 -0
  34. data/lib/beambridge/constants.rb +36 -0
  35. data/lib/beambridge/decoder.rb +212 -0
  36. data/lib/beambridge/encoder.rb +164 -0
  37. data/lib/beambridge/errors/beambridge_error.rb +3 -0
  38. data/lib/beambridge/errors/decode_error.rb +3 -0
  39. data/lib/beambridge/errors/encode_error.rb +3 -0
  40. data/lib/beambridge/matcher.rb +21 -0
  41. data/lib/beambridge/port.rb +48 -0
  42. data/lib/beambridge/receiver.rb +69 -0
  43. data/lib/beambridge/types/function.rb +3 -0
  44. data/lib/beambridge/types/list.rb +3 -0
  45. data/lib/beambridge/types/new_function.rb +3 -0
  46. data/lib/beambridge/types/new_reference.rb +3 -0
  47. data/lib/beambridge/types/pid.rb +3 -0
  48. data/lib/beambridge/types/reference.rb +3 -0
  49. data/lib/beambridge/version.rb +3 -0
  50. data/spec/condition_spec.rb +72 -0
  51. data/spec/decode_spec.rb +143 -0
  52. data/spec/encode_spec.rb +152 -0
  53. data/spec/matcher_spec.rb +81 -0
  54. data/spec/port_spec.rb +34 -0
  55. data/spec/receiver_spec.rb +103 -0
  56. data/spec/spec_helper.rb +47 -0
  57. metadata +153 -0
@@ -0,0 +1,11 @@
1
+ # Loads mkmf which is used to make makefiles for Ruby extensions
2
+ require 'mkmf'
3
+
4
+ # Give it a name
5
+ extension_name = 'decoder'
6
+
7
+ # The destination
8
+ dir_config(extension_name)
9
+
10
+ # Do the work
11
+ create_makefile(extension_name)
@@ -0,0 +1,34 @@
1
+ $:.unshift File.join(File.dirname(__FILE__), *%w[.. ext])
2
+
3
+ require 'stringio'
4
+
5
+ require 'beambridge/version'
6
+ require 'beambridge/constants'
7
+ require 'beambridge/types/new_reference'
8
+ require 'beambridge/types/pid'
9
+ require 'beambridge/types/function'
10
+ require 'beambridge/types/list'
11
+
12
+ begin
13
+ # try to load the decoder C extension
14
+ require 'decoder'
15
+ rescue LoadError
16
+ # fall back on the pure ruby version
17
+ require 'beambridge/decoder'
18
+ end
19
+
20
+ require 'beambridge/encoder'
21
+
22
+ require 'beambridge/port'
23
+ require 'beambridge/matcher'
24
+ require 'beambridge/condition'
25
+ require 'beambridge/conditions/boolean'
26
+ require 'beambridge/conditions/hash'
27
+ require 'beambridge/conditions/static'
28
+ require 'beambridge/conditions/type'
29
+ require 'beambridge/receiver'
30
+ require 'beambridge/errors/beambridge_error'
31
+ require 'beambridge/errors/decode_error'
32
+ require 'beambridge/errors/encode_error'
33
+
34
+ Erl = Beambridge
@@ -0,0 +1,66 @@
1
+ module Beambridge
2
+ class Condition
3
+ def self.for(a)
4
+ case a
5
+ when Condition then a
6
+ when Class then TypeCondition.new(a)
7
+ else StaticCondition.new(a)
8
+ end
9
+ end
10
+
11
+ def initialize
12
+ end
13
+
14
+ def binding_for(arg)
15
+ nil
16
+ end
17
+
18
+ def satisfies?(arg)
19
+ false
20
+ end
21
+
22
+ alias === satisfies?
23
+ end
24
+
25
+ module Conditions
26
+ def atom
27
+ TypeCondition.new(Symbol)
28
+ end
29
+
30
+ def any
31
+ TypeCondition.new(Object)
32
+ end
33
+
34
+ def number
35
+ TypeCondition.new(Fixnum)
36
+ end
37
+
38
+ def pid
39
+ TypeCondition.new(Beambridge::Pid)
40
+ end
41
+
42
+ def ref
43
+ TypeCondition.new(Beambridge::NewReference)
44
+ end
45
+
46
+ def string
47
+ TypeCondition.new(String)
48
+ end
49
+
50
+ def list
51
+ TypeCondition.new(Array)
52
+ end
53
+
54
+ def hash
55
+ HashCondition.new()
56
+ end
57
+
58
+ def boolean
59
+ BooleanCondition.new()
60
+ end
61
+ end
62
+
63
+ extend Conditions
64
+ end
65
+
66
+ Any = Object
@@ -0,0 +1,11 @@
1
+ module Beambridge
2
+ class BooleanCondition < Condition
3
+ def satisfies?(arg)
4
+ [TrueClass, FalseClass].include?(arg.class)
5
+ end
6
+
7
+ def binding_for(arg)
8
+ arg
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,13 @@
1
+ module Beambridge
2
+ class HashCondition < Condition
3
+ def satisfies?(arg)
4
+ return false unless arg.class == Array
5
+ arg.all? { |x| x.class == Array && x.length == 2 }
6
+ end
7
+
8
+ def binding_for(arg)
9
+ flattened = arg.inject([]) { |memo, kv| memo + kv }
10
+ Hash[*flattened]
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,34 @@
1
+ module Beambridge
2
+ class StaticCondition < Condition
3
+ attr_accessor :value
4
+ def initialize(value)
5
+ if value.is_a?(Array)
6
+ self.value = value.map do |v|
7
+ Condition.for(v)
8
+ end
9
+ else
10
+ self.value = value
11
+ end
12
+ end
13
+
14
+ def satisfies?(arg)
15
+ if value.is_a?(Array)
16
+ return false unless arg.is_a?(Array)
17
+ return false if value.length != arg.length
18
+ value.zip(arg).all? do |l, r|
19
+ l.respond_to?(:satisfies?) ? l.satisfies?(r) : l.eql?(r)
20
+ end
21
+ else
22
+ arg.eql?(value)
23
+ end
24
+ end
25
+
26
+ def binding_for(arg)
27
+ if value.is_a?(Array)
28
+ value.zip(arg).map { |l, r| l.binding_for(r) }.compact
29
+ else
30
+ nil
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,17 @@
1
+ module Beambridge
2
+ class TypeCondition < Condition
3
+ attr_accessor :type
4
+
5
+ def initialize(type)
6
+ self.type = type
7
+ end
8
+
9
+ def satisfies?(arg)
10
+ arg.is_a?(self.type)
11
+ end
12
+
13
+ def binding_for(arg)
14
+ arg
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,36 @@
1
+ module Beambridge
2
+ module External
3
+ module Types
4
+ SMALL_INT = 97
5
+ INT = 98
6
+
7
+ SMALL_BIGNUM = 110
8
+ LARGE_BIGNUM = 111
9
+
10
+ FLOAT = 99
11
+
12
+ ATOM = 100
13
+ REF = 101 #old style reference
14
+ NEW_REF = 114
15
+ PORT = 102 #not supported accross node boundaries
16
+ PID = 103
17
+
18
+ SMALL_TUPLE = 104
19
+ LARGE_TUPLE = 105
20
+
21
+ NIL = 106
22
+ STRING = 107
23
+ LIST = 108
24
+ BIN = 109
25
+
26
+ FUN = 117
27
+ NEW_FUN = 112
28
+ end
29
+
30
+ VERSION = 131
31
+
32
+ MAX_INT = (1 << 27) -1
33
+ MIN_INT = -(1 << 27)
34
+ MAX_ATOM = 255
35
+ end
36
+ end
@@ -0,0 +1,212 @@
1
+ module Beambridge
2
+ class Decoder
3
+ attr_accessor :in
4
+ include Beambridge::External::Types
5
+
6
+ def self.decode(string)
7
+ new(StringIO.new(string)).read_any
8
+ end
9
+
10
+ def initialize(ins)
11
+ @in = ins
12
+ @peeked = ""
13
+ end
14
+
15
+ def read_any
16
+ fail("Bad Magic") unless read_1 == Beambridge::External::VERSION
17
+ read_any_raw
18
+ end
19
+
20
+ def read_any_raw
21
+ case peek_1
22
+ when ATOM then read_atom
23
+ when SMALL_INT then read_small_int
24
+ when INT then read_int
25
+ when SMALL_BIGNUM then read_small_bignum
26
+ when LARGE_BIGNUM then read_large_bignum
27
+ when FLOAT then read_float
28
+ when NEW_REF then read_new_reference
29
+ when PID then read_pid
30
+ when SMALL_TUPLE then read_small_tuple
31
+ when LARGE_TUPLE then read_large_tuple
32
+ when NIL then read_nil
33
+ when STRING then read_erl_string
34
+ when LIST then read_list
35
+ when BIN then read_bin
36
+ else
37
+ fail("Unknown term tag: #{peek_1}")
38
+ end
39
+ end
40
+
41
+ def read(length)
42
+ if length < @peeked.length
43
+ result = @peeked[0...length]
44
+ @peeked = @peeked[length..-1]
45
+ length = 0
46
+ else
47
+ result = @peeked
48
+ @peeked = ''
49
+ length -= result.length
50
+ end
51
+
52
+ if length > 0
53
+ result << @in.read(length)
54
+ end
55
+ result
56
+ end
57
+
58
+ def peek(length)
59
+ if length <= @peeked.length
60
+ @peeked[0...length]
61
+ else
62
+ read_bytes = @in.read(length - @peeked.length)
63
+ @peeked << read_bytes if read_bytes
64
+ @peeked
65
+ end
66
+ end
67
+
68
+ def peek_1
69
+ peek(1).unpack("C").first
70
+ end
71
+
72
+ def peek_2
73
+ peek(2).unpack("n").first
74
+ end
75
+
76
+ def read_1
77
+ read(1).unpack("C").first
78
+ end
79
+
80
+ def read_2
81
+ read(2).unpack("n").first
82
+ end
83
+
84
+ def read_4
85
+ read(4).unpack("N").first
86
+ end
87
+
88
+ def read_string(length)
89
+ read(length)
90
+ end
91
+
92
+ def read_atom
93
+ fail("Invalid Type, not an atom") unless read_1 == ATOM
94
+ length = read_2
95
+ a = read_string(length)
96
+ case a
97
+ when "true"
98
+ true
99
+ when "false"
100
+ false
101
+ when ""
102
+ Marshal.load("\004\b:\005") # Workaround for inability to do ''.to_sym
103
+ else
104
+ a.to_sym
105
+ end
106
+ end
107
+
108
+ def read_small_int
109
+ fail("Invalid Type, not a small int") unless read_1 == SMALL_INT
110
+ read_1
111
+ end
112
+
113
+ def read_int
114
+ fail("Invalid Type, not an int") unless read_1 == INT
115
+ value = read_4
116
+ negative = (value >> 31)[0] == 1
117
+ value = (value - (1 << 32)) if negative
118
+ value = Fixnum.induced_from(value)
119
+ end
120
+
121
+ def read_small_bignum
122
+ fail("Invalid Type, not a small bignum") unless read_1 == SMALL_BIGNUM
123
+ size = read_1
124
+ sign = read_1
125
+ bytes = read_string(size).unpack("C" * size)
126
+ added = bytes.zip((0..bytes.length).to_a).inject(0) do |result, byte_index|
127
+ byte, index = *byte_index
128
+ value = (byte * (256 ** index))
129
+ sign != 0 ? (result - value) : (result + value)
130
+ end
131
+ Bignum.induced_from(added)
132
+ end
133
+
134
+ def read_large_bignum
135
+ fail("Invalid Type, not a large bignum") unless read_1 == LARGE_BIGNUM
136
+ size = read_4
137
+ sign = read_1
138
+ bytes = read_string(size).unpack("C" * size)
139
+ added = bytes.zip((0..bytes.length).to_a).inject(0) do |result, byte_index|
140
+ byte, index = *byte_index
141
+ value = (byte * (256 ** index))
142
+ sign != 0 ? (result - value) : (result + value)
143
+ end
144
+ Bignum.induced_from(added)
145
+ end
146
+
147
+ def read_float
148
+ fail("Invalid Type, not a float") unless read_1 == FLOAT
149
+ string_value = read_string(31)
150
+ result = string_value.to_f
151
+ end
152
+
153
+ def read_new_reference
154
+ fail("Invalid Type, not a new-style reference") unless read_1 == NEW_REF
155
+ size = read_2
156
+ node = read_atom
157
+ creation = read_1
158
+ id = (0...size).map { |i| read_4 }
159
+ NewReference.new(node, creation, id)
160
+ end
161
+
162
+ def read_pid
163
+ fail("Invalid Type, not a pid") unless read_1 == PID
164
+ node = read_atom
165
+ id = read_4
166
+ serial = read_4
167
+ creation = read_1
168
+ Pid.new(node, id, serial, creation)
169
+ end
170
+
171
+ def read_small_tuple
172
+ fail("Invalid Type, not a small tuple") unless read_1 == SMALL_TUPLE
173
+ arity = read_1
174
+ (0...arity).map { |i| read_any_raw }
175
+ end
176
+
177
+ def read_large_tuple
178
+ fail("Invalid Type, not a small tuple") unless read_1 == LARGE_TUPLE
179
+ arity = read_4
180
+ (0...arity).map { |i| read_any_raw }
181
+ end
182
+
183
+ def read_nil
184
+ fail("Invalid Type, not a nil list") unless read_1 == NIL
185
+ Beambridge::List.new([])
186
+ end
187
+
188
+ def read_erl_string
189
+ fail("Invalid Type, not an erlang string") unless read_1 == STRING
190
+ length = read_2
191
+ Beambridge::List.new(read_string(length).unpack('C' * length))
192
+ end
193
+
194
+ def read_list
195
+ fail("Invalid Type, not an erlang list") unless read_1 == LIST
196
+ length = read_4
197
+ list = (0...length).map { |i| read_any_raw }
198
+ read_1
199
+ Beambridge::List.new(list)
200
+ end
201
+
202
+ def read_bin
203
+ fail("Invalid Type, not an erlang binary") unless read_1 == BIN
204
+ length = read_4
205
+ read_string(length)
206
+ end
207
+
208
+ def fail(str)
209
+ raise DecodeError, str
210
+ end
211
+ end
212
+ end
@@ -0,0 +1,164 @@
1
+ module Beambridge
2
+ class Encoder
3
+ include Beambridge::External::Types
4
+
5
+ attr_accessor :out
6
+
7
+ def initialize(out)
8
+ self.out = out
9
+ end
10
+
11
+ def self.encode(data)
12
+ io = StringIO.new
13
+ self.new(io).write_any(data)
14
+ io.string
15
+ end
16
+
17
+ def write_any obj
18
+ write_1 Beambridge::External::VERSION
19
+ write_any_raw obj
20
+ end
21
+
22
+ def write_any_raw obj
23
+ case obj
24
+ when Symbol then write_symbol(obj)
25
+ when Fixnum, Bignum then write_fixnum(obj)
26
+ when Float then write_float(obj)
27
+ when Beambridge::NewReference then write_new_reference(obj)
28
+ when Beambridge::Pid then write_pid(obj)
29
+ when Beambridge::List then write_list(obj)
30
+ when Array then write_tuple(obj)
31
+ when String then write_binary(obj)
32
+ when Time then write_any_raw(obj.to_i.divmod(1000000) + [obj.usec])
33
+ when TrueClass, FalseClass then write_boolean(obj)
34
+ else
35
+ fail(obj)
36
+ end
37
+ end
38
+
39
+ def write_1(byte)
40
+ out.write([byte].pack("C"))
41
+ end
42
+
43
+ def write_2(short)
44
+ out.write([short].pack("n"))
45
+ end
46
+
47
+ def write_4(long)
48
+ out.write([long].pack("N"))
49
+ end
50
+
51
+ def write_string(string)
52
+ out.write(string)
53
+ end
54
+
55
+ def write_boolean(bool)
56
+ write_symbol(bool.to_s.to_sym)
57
+ end
58
+
59
+ def write_symbol(sym)
60
+ fail(sym) unless sym.is_a?(Symbol)
61
+ data = sym.to_s
62
+ write_1 ATOM
63
+ write_2 data.length
64
+ write_string data
65
+ end
66
+
67
+ def write_fixnum(num)
68
+ if num >= 0 && num < 256
69
+ write_1 SMALL_INT
70
+ write_1 num
71
+ elsif num <= Beambridge::External::MAX_INT && num >= Beambridge::External::MIN_INT
72
+ write_1 INT
73
+ write_4 num
74
+ else
75
+ write_bignum num
76
+ end
77
+ end
78
+
79
+ def write_float(float)
80
+ write_1 FLOAT
81
+ write_string format("%15.15e", float).ljust(31, "\000")
82
+ end
83
+
84
+ def write_bignum(num)
85
+ if num.is_a?(Bignum)
86
+ n = num.size
87
+ else
88
+ n = (num.to_s(2).size / 8.0).ceil
89
+ end
90
+ if n <= 256
91
+ write_1 SMALL_BIGNUM
92
+ write_1 n
93
+ write_bignum_guts(num)
94
+ else
95
+ write_1 LARGE_BIGNUM
96
+ write_4 n
97
+ write_bignum_guts(num)
98
+ end
99
+ end
100
+
101
+ def write_bignum_guts(num)
102
+ write_1 (num >= 0 ? 0 : 1)
103
+ num = num.abs
104
+ while num != 0
105
+ rem = num % 256
106
+ write_1 rem
107
+ num = num >> 8
108
+ end
109
+ end
110
+
111
+ def write_new_reference(ref)
112
+ fail(ref) unless ref.is_a?(Beambridge::NewReference)
113
+ write_1 NEW_REF
114
+ write_2 ref.id.length
115
+ write_symbol(ref.node)
116
+ write_1 ref.creation
117
+ write_string ref.id.pack('N' * ref.id.length)
118
+ end
119
+
120
+ def write_pid(pid)
121
+ fail(pid) unless pid.is_a? Beambridge::Pid
122
+ write_1 PID
123
+ write_symbol(pid.node)
124
+ write_4 pid.id
125
+ write_4 pid.serial
126
+ write_1 pid.creation
127
+ end
128
+
129
+ def write_tuple(data)
130
+ fail(data) unless data.is_a? Array
131
+
132
+ if data.length < 256
133
+ write_1 SMALL_TUPLE
134
+ write_1 data.length
135
+ else
136
+ write_1 LARGE_TUPLE
137
+ write_4 data.length
138
+ end
139
+
140
+ data.each { |e| write_any_raw e }
141
+ end
142
+
143
+ def write_list(data)
144
+ fail(data) unless data.is_a? Array
145
+ write_1 NIL and return if data.empty?
146
+ write_1 LIST
147
+ write_4 data.length
148
+ data.each{|e| write_any_raw e }
149
+ write_1 NIL
150
+ end
151
+
152
+ def write_binary(data)
153
+ write_1 BIN
154
+ write_4 data.bytesize
155
+ write_string data
156
+ end
157
+
158
+ private
159
+
160
+ def fail(obj)
161
+ raise EncodeError, "Cannot encode to erlang external format: #{obj.inspect}"
162
+ end
163
+ end
164
+ end