erlectricity-funbox 1.1.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.
Files changed (55) hide show
  1. data/.gitignore +24 -0
  2. data/History.txt +35 -0
  3. data/LICENSE +20 -0
  4. data/README.md +130 -0
  5. data/Rakefile +74 -0
  6. data/VERSION.yml +4 -0
  7. data/benchmarks/bench.rb +21 -0
  8. data/erlectricity.gemspec +105 -0
  9. data/examples/echo/README.md +12 -0
  10. data/examples/echo/echo.erl +13 -0
  11. data/examples/echo/echo.rb +11 -0
  12. data/examples/gruff/gruff.erl +61 -0
  13. data/examples/gruff/gruff_provider.rb +31 -0
  14. data/examples/gruff/gruff_run.sh +19 -0
  15. data/examples/gruff/stat_run.sh +20 -0
  16. data/examples/gruff/stat_writer.erl +40 -0
  17. data/examples/simple/README.md +5 -0
  18. data/examples/simple/rerl.rb +111 -0
  19. data/examples/simple/rerl.sh +37 -0
  20. data/examples/tinderl/README.md +14 -0
  21. data/examples/tinderl/tinderl.erl +43 -0
  22. data/examples/tinderl/tinderl.rb +28 -0
  23. data/ext/decoder.c +398 -0
  24. data/ext/extconf.rb +11 -0
  25. data/lib/erlectricity.rb +33 -0
  26. data/lib/erlectricity/condition.rb +66 -0
  27. data/lib/erlectricity/conditions/boolean.rb +11 -0
  28. data/lib/erlectricity/conditions/hash.rb +13 -0
  29. data/lib/erlectricity/conditions/static.rb +34 -0
  30. data/lib/erlectricity/conditions/type.rb +17 -0
  31. data/lib/erlectricity/constants.rb +36 -0
  32. data/lib/erlectricity/decoder.rb +212 -0
  33. data/lib/erlectricity/encoder.rb +164 -0
  34. data/lib/erlectricity/errors/decode_error.rb +3 -0
  35. data/lib/erlectricity/errors/encode_error.rb +3 -0
  36. data/lib/erlectricity/errors/erlectricity_error.rb +3 -0
  37. data/lib/erlectricity/matcher.rb +21 -0
  38. data/lib/erlectricity/port.rb +46 -0
  39. data/lib/erlectricity/receiver.rb +69 -0
  40. data/lib/erlectricity/types/function.rb +3 -0
  41. data/lib/erlectricity/types/list.rb +3 -0
  42. data/lib/erlectricity/types/new_function.rb +3 -0
  43. data/lib/erlectricity/types/new_reference.rb +3 -0
  44. data/lib/erlectricity/types/pid.rb +3 -0
  45. data/lib/erlectricity/types/reference.rb +3 -0
  46. data/lib/erlectricity/version.rb +9 -0
  47. data/test/condition_spec.rb +72 -0
  48. data/test/decode_spec.rb +145 -0
  49. data/test/encode_spec.rb +146 -0
  50. data/test/matcher_spec.rb +81 -0
  51. data/test/port_spec.rb +34 -0
  52. data/test/receiver_spec.rb +103 -0
  53. data/test/spec_suite.rb +2 -0
  54. data/test/test_helper.rb +46 -0
  55. metadata +116 -0
@@ -0,0 +1,164 @@
1
+ module Erlectricity
2
+ class Encoder
3
+ include Erlectricity::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 Erlectricity::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 Erlectricity::NewReference then write_new_reference(obj)
28
+ when Erlectricity::Pid then write_pid(obj)
29
+ when Erlectricity::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 <= Erlectricity::External::MAX_INT && num >= Erlectricity::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?(Erlectricity::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? Erlectricity::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.respond_to?(:bytesize) ? data.bytesize : data.length
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
@@ -0,0 +1,3 @@
1
+ class DecodeError < ErlectricityError
2
+
3
+ end
@@ -0,0 +1,3 @@
1
+ class EncodeError < ErlectricityError
2
+
3
+ end
@@ -0,0 +1,3 @@
1
+ class ErlectricityError < StandardError
2
+
3
+ end
@@ -0,0 +1,21 @@
1
+ module Erlectricity
2
+ class Matcher
3
+ attr_accessor :condition, :block
4
+ attr_accessor :receiver
5
+
6
+ def initialize(parent, condition, block)
7
+ self.receiver = parent
8
+ @block = block
9
+ @condition = Condition.for(condition)
10
+ end
11
+
12
+ def run(arg)
13
+ args = @condition.binding_for(arg)
14
+ block.call(*args)
15
+ end
16
+
17
+ def matches?(arg)
18
+ @condition.satisfies?(arg)
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,46 @@
1
+ module Erlectricity
2
+ class Port
3
+ attr_reader :input, :output
4
+ attr_reader :skipped
5
+ attr_reader :queue
6
+
7
+ def initialize(input=STDIN, output=STDOUT)
8
+ @input = input
9
+ @output = output
10
+
11
+ input.sync = true
12
+ output.sync = true
13
+
14
+ @encoder = Erlectricity::Encoder.new(nil)
15
+ @skipped = []
16
+ @queue = []
17
+ end
18
+
19
+ def receive
20
+ queue.empty? ? read_from_input : queue.shift
21
+ end
22
+
23
+ def send(term)
24
+ @encoder.out = StringIO.new('', 'w')
25
+ @encoder.write_any(term)
26
+ data = @encoder.out.string
27
+ output.write([data.length].pack("N"))
28
+ output.write(data)
29
+ end
30
+
31
+ def restore_skipped
32
+ @queue = self.skipped + self.queue
33
+ end
34
+
35
+ private
36
+
37
+ def read_from_input
38
+ raw = input.read(4)
39
+ return nil unless raw
40
+
41
+ packet_length = raw.unpack('N').first
42
+ data = input.read(packet_length)
43
+ Erlectricity::Decoder.decode(data)
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,69 @@
1
+ module Erlectricity
2
+ class Receiver
3
+ attr_accessor :port
4
+ attr_accessor :parent
5
+ attr_accessor :matchers
6
+
7
+ RECEIVE_LOOP = Object.new
8
+ NO_MATCH = Object.new
9
+
10
+ def initialize(port, parent = nil, &block)
11
+ @port = port
12
+ @parent = parent
13
+ @matchers = []
14
+ block.call(self) if block
15
+ end
16
+
17
+ def process(arg)
18
+ matcher = @matchers.find { |r| r.matches?(arg) }
19
+
20
+ if matcher
21
+ port.restore_skipped
22
+ matcher.run(arg)
23
+ else
24
+ NO_MATCH
25
+ end
26
+ end
27
+
28
+ def when(arg, &block)
29
+ condition = Condition.for(arg)
30
+ @matchers << Matcher.new(self, condition, block)
31
+ end
32
+
33
+ def run
34
+ loop do
35
+ msg = port.receive
36
+ return if msg.nil?
37
+
38
+ case result = process(msg)
39
+ when RECEIVE_LOOP then next
40
+ when NO_MATCH
41
+ port.skipped << msg
42
+ next
43
+ else
44
+ break result
45
+ end
46
+ end
47
+ end
48
+
49
+ def receive(&block)
50
+ Receiver.new(port, self, &block).run
51
+ end
52
+
53
+ def receive_loop
54
+ RECEIVE_LOOP
55
+ end
56
+
57
+ def send!(term)
58
+ port.send(term)
59
+ end
60
+ end
61
+ end
62
+
63
+ module Kernel
64
+ def receive(input = nil, output = nil, &block)
65
+ input ||= IO.new(3)
66
+ output ||= IO.new(4)
67
+ Erlectricity::Receiver.new(Erlectricity::Port.new(input, output), nil, &block).run
68
+ end
69
+ end
@@ -0,0 +1,3 @@
1
+ module Erlectricity
2
+ Function = Struct.new :pid, :module, :index, :uniq, :free_vars
3
+ end
@@ -0,0 +1,3 @@
1
+ class Erlectricity::List < Array
2
+
3
+ end
@@ -0,0 +1,3 @@
1
+ module Erlectricity
2
+ NewFunction = Struct.new :arity, :uniq, :index,:num_free, :module, :old_index, :old_uniq, :pid, :free_vars
3
+ end
@@ -0,0 +1,3 @@
1
+ module Erlectricity
2
+ NewReference = Struct.new :node, :creation, :id
3
+ end
@@ -0,0 +1,3 @@
1
+ module Erlectricity
2
+ Pid = Struct.new :node, :id, :serial, :creation
3
+ end
@@ -0,0 +1,3 @@
1
+ module Erlectricity
2
+ Reference = Struct.new :node, :id, :creator
3
+ end
@@ -0,0 +1,9 @@
1
+ module Erlectricity #:nodoc:
2
+ module VERSION #:nodoc:
3
+ MAJOR = 0
4
+ MINOR = 2
5
+ TINY = 1
6
+
7
+ STRING = [MAJOR, MINOR, TINY].join('.')
8
+ end
9
+ end
@@ -0,0 +1,72 @@
1
+ require File.dirname(__FILE__) + '/test_helper.rb'
2
+
3
+ context "Erlectricity::StaticConditions" do
4
+ specify "should satisfy on the same value" do
5
+ Erlectricity::StaticCondition.new(:foo).satisfies?(:foo).should == true
6
+ Erlectricity::StaticCondition.new([:foo]).satisfies?([:foo]).should == true
7
+ Erlectricity::StaticCondition.new(3).satisfies?(3).should == true
8
+ end
9
+
10
+ specify "should not satisfy on different values" do
11
+ Erlectricity::StaticCondition.new(:foo).satisfies?("foo").should == false
12
+ Erlectricity::StaticCondition.new([:foo]).satisfies?(:foo).should == false
13
+ Erlectricity::StaticCondition.new(Object.new).satisfies?(Object.new).should == false
14
+ Erlectricity::StaticCondition.new(3).satisfies?(3.0).should == false
15
+ end
16
+
17
+ specify "should not produce any bindings" do
18
+ s = Erlectricity::StaticCondition.new(:foo)
19
+ s.binding_for(:foo).should == nil
20
+ end
21
+ end
22
+
23
+ context "Erlectricity::TypeConditions" do
24
+ specify "should be satisfied when the arg has the same class" do
25
+ Erlectricity::TypeCondition.new(Symbol).satisfies?(:foo).should == true
26
+ Erlectricity::TypeCondition.new(Symbol).satisfies?(:bar).should == true
27
+ Erlectricity::TypeCondition.new(String).satisfies?("foo").should == true
28
+ Erlectricity::TypeCondition.new(String).satisfies?("bar").should == true
29
+ Erlectricity::TypeCondition.new(Array).satisfies?([]).should == true
30
+ Erlectricity::TypeCondition.new(Fixnum).satisfies?(3).should == true
31
+ end
32
+
33
+ specify "should be satisfied when the arg is of a descendent class" do
34
+ Erlectricity::TypeCondition.new(Object).satisfies?(:foo).should == true
35
+ Erlectricity::TypeCondition.new(Object).satisfies?("foo").should == true
36
+ Erlectricity::TypeCondition.new(Object).satisfies?(3).should == true
37
+ end
38
+
39
+ specify "should not be satisfied when the arg is of a different class" do
40
+ Erlectricity::TypeCondition.new(String).satisfies?(:foo).should == false
41
+ Erlectricity::TypeCondition.new(Symbol).satisfies?("foo").should == false
42
+ Erlectricity::TypeCondition.new(Fixnum).satisfies?(3.0).should == false
43
+ end
44
+
45
+ specify "should bind the arg with no transormations" do
46
+ s = Erlectricity::TypeCondition.new(Symbol)
47
+ s.binding_for(:foo).should == :foo
48
+ s.binding_for(:bar).should == :bar
49
+ end
50
+ end
51
+
52
+ context "Erlectricity::HashConditions" do
53
+ specify "should satisfy an args of the form [[key, value], [key, value]]" do
54
+ Erlectricity::HashCondition.new.satisfies?([[:foo, 3], [:bar, Object.new]]).should == true
55
+ Erlectricity::HashCondition.new.satisfies?([[:foo, 3]]).should == true
56
+ end
57
+
58
+ specify "should satisfy on empty arrays" do
59
+ Erlectricity::HashCondition.new.satisfies?([]).should == true
60
+ end
61
+
62
+ specify "should nat satisfy other args" do
63
+ Erlectricity::HashCondition.new.satisfies?(:foo).should == false
64
+ Erlectricity::HashCondition.new.satisfies?("foo").should == false
65
+ Erlectricity::HashCondition.new.satisfies?(3.0).should == false
66
+ end
67
+
68
+ specify "should bind to a Hash" do
69
+ s = Erlectricity::HashCondition.new()
70
+ s.binding_for([[:foo, 3], [:bar, [3,4,5]]]).should == {:foo => 3, :bar => [3,4,5] }
71
+ end
72
+ end
@@ -0,0 +1,145 @@
1
+ require File.dirname(__FILE__) + '/test_helper.rb'
2
+
3
+ context "When unpacking from a binary stream" do
4
+ setup do
5
+ end
6
+
7
+ specify "an erlang atom should decode to a ruby symbol" do
8
+ get("haha").should == :haha
9
+ end
10
+
11
+ specify "an erlang number encoded as a small_int (< 255) should decode to a fixnum" do
12
+ get("0").should == 0
13
+ get("255").should == 255
14
+ end
15
+
16
+ specify "an erlang number encoded as a int (signed 27-bit number) should decode to a fixnum" do
17
+ get("256").should == 256
18
+ get("#{(1 << 27) -1}").should == (1 << 27) -1
19
+ get("-1").should == -1
20
+ get("#{-(1 << 27)}").should == -(1 << 27)
21
+ end
22
+
23
+ specify "an erlang number encoded as a small bignum (1 byte length) should decode to fixnum if it can" do
24
+ get("#{(1 << 27)}").should == (1 << 27)
25
+ get("#{-(1 << 27) - 1}").should == -(1 << 27) - 1
26
+ get("#{(1 << word_length) - 1}").should == (1 << word_length) - 1
27
+ get("#{-(1 << word_length)}").should == -(1 << word_length)
28
+ end
29
+
30
+ specify "an erlang number encoded as a small bignum (1 byte length) should decode to bignum if it can't be a fixnum" do
31
+ get("#{(1 << word_length)}").should == (1 << word_length)
32
+ get("#{-(1 << word_length) - 1}").should == -(1 << word_length) - 1
33
+ get("#{(1 << (255 * 8)) - 1}").should == (1 << (255 * 8)) - 1
34
+ get("#{-((1 << (255 * 8)) - 1)}").should == -((1 << (255 * 8)) - 1)
35
+ end
36
+
37
+ specify "an erlang number encoded as a big bignum (4 byte length) should decode to bignum" do
38
+ get("#{(1 << (255 * 8)) }").should == (1 << (255 * 8))
39
+ get("#{-(1 << (255 * 8))}").should == -(1 << (255 * 8))
40
+ get("#{(1 << (512 * 8)) }").should == (1 << (512 * 8))
41
+ get("#{-(1 << (512 * 8))}").should == -(1 << (512 * 8))
42
+ end
43
+
44
+ specify "an erlang float should decode to a Float" do
45
+ get("#{1.0}").should == 1.0
46
+ get("#{-1.0}").should == -1.0
47
+ get("#{123.456}").should == 123.456
48
+ get("#{123.456789012345}").should == 123.456789012345
49
+ end
50
+
51
+ specify "an erlang reference should decode to a Reference object" do
52
+ ref = get("make_ref()")
53
+ ref.should.be.instance_of Erlectricity::NewReference
54
+ ref.node.should.be.instance_of Symbol
55
+ end
56
+
57
+ specify "an erlang pid should decode to a Pid object" do
58
+ pid = get("spawn(fun() -> 3 end)")
59
+ pid.should.be.instance_of Erlectricity::Pid
60
+ pid.node.should.be.instance_of Symbol
61
+ end
62
+
63
+ specify "an erlang tuple encoded as a small tuple (1-byte length) should decode to an array" do
64
+ ref = get("{3}")
65
+ ref.length.should == 1
66
+ ref.first.should == 3
67
+
68
+ ref = get("{3, a, make_ref()}")
69
+ ref.length.should == 3
70
+ ref[0].should == 3
71
+ ref[1].should == :a
72
+ ref[2].class.should == Erlectricity::NewReference
73
+
74
+ tuple_meat = (['3'] * 255).join(', ')
75
+ ref = get("{#{tuple_meat}}")
76
+ ref.length.should == 255
77
+ ref.each{|r| r.should == 3}
78
+ end
79
+
80
+ specify "an erlang tuple encoded as a large tuple (4-byte length) should decode to an array" do
81
+ tuple_meat = (['3'] * 256).join(', ')
82
+ ref = get("{#{tuple_meat}}")
83
+ ref.length.should == 256
84
+ ref.each{|r| r.should == 3}
85
+
86
+ tuple_meat = (['3'] * 512).join(', ')
87
+ ref = get("{#{tuple_meat}}")
88
+ ref.length.should == 512
89
+ ref.each{|r| r.should == 3}
90
+ end
91
+
92
+ specify "an empty erlang list encoded as a nil should decode to an array" do
93
+ get("[]").class.should == Erl::List
94
+ get("[]").should == []
95
+ end
96
+
97
+ specify "an erlang list encoded as a string should decode to an array of bytes (less than ideal, but consistent)" do
98
+ get("\"asdasd\"").class.should == Erl::List
99
+ get("\"asdasd\"").should == "asdasd".split('').map{|c| c[0]}
100
+ get("\"#{'a' * 65534}\"").should == ['a'[0]] * 65534
101
+ end
102
+
103
+ specify "an erlang list encoded as a list should decode to an erl::list" do
104
+ get("[3,4,256]").class.should == Erl::List
105
+ get("[3,4,256]").should == [3,4,256]
106
+ get("\"#{'a' * 65535 }\"").should == [97] * 65535
107
+ get("[3,4, foo, {3,4,5,bar}, 256]").should == [3,4, :foo, [3,4,5,:bar], 256]
108
+ end
109
+
110
+ specify "an erlang binary should decode to a string" do
111
+ get("<< 3,4,255 >>").should == "\003\004\377"
112
+ get("<< \"whatup\" >>").should == "whatup"
113
+ get("<< 99,0,99 >>").should == "c\000c"
114
+ end
115
+
116
+ specify "the empty atom should decode to the empty symbol" do
117
+ empty_symbol = get("''")
118
+ empty_symbol.should.be.instance_of Symbol
119
+ empty_symbol.to_s.should == ""
120
+ end
121
+
122
+ specify "erlang atomic booleans should decode to ruby booleans" do
123
+ get("true").should == true
124
+ get("false").should == false
125
+ get("falsereio").should == :falsereio
126
+ get("t").should == :t
127
+ get("f").should == :f
128
+ end
129
+
130
+ specify "massive binaries should not overflow the stack" do
131
+ bin = [131,109,0,128,0,0].pack('c*') + ('a' * (8 * 1024 * 1024))
132
+ assert_equal (8 * 1024 * 1024), Erlectricity::Decoder.decode(bin).size
133
+ end
134
+
135
+ specify "a good thing should be awesome" do
136
+ get(%Q-[{options,{struct,[{test,<<"I'm chargin' mah lazer">>}]}},{passage,<<"Why doesn't this work?">>}]-).should ==
137
+ [[:options, [:struct, [[:test, "I'm chargin' mah lazer"]]]], [:passage, "Why doesn't this work?"]]
138
+ end
139
+
140
+ def get(str)
141
+ x = "term_to_binary(#{str.gsub(/"/, '\\\"')})"
142
+ bin = run_erl(x)
143
+ Erlectricity::Decoder.decode(bin)
144
+ end
145
+ end