rinterface 0.0.3
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.
- data/.gitignore +4 -0
- data/README.md +111 -0
- data/Rakefile +107 -0
- data/VERSION +1 -0
- data/examples/client.rb +19 -0
- data/examples/math_server.erl +28 -0
- data/lib/rinterface/epmd.rb +58 -0
- data/lib/rinterface/erl.rb +24 -0
- data/lib/rinterface/erlang/decoder.rb +223 -0
- data/lib/rinterface/erlang/encoder.rb +107 -0
- data/lib/rinterface/erlang/external_format.rb +28 -0
- data/lib/rinterface/erlang/types.rb +37 -0
- data/lib/rinterface/node.rb +242 -0
- data/lib/rinterface.rb +13 -0
- data/rinterface.gemspec +71 -0
- data/spec/rinterface/erl_spec.rb +24 -0
- data/spec/rinterface/erlang/external_format_spec.rb +5 -0
- data/spec/rinterface/node_spec.rb +5 -0
- data/spec/rinterface_spec.rb +134 -0
- data/spec/spec_helper.rb +63 -0
- data/spec/spec_server.erl +29 -0
- metadata +112 -0
data/.gitignore
ADDED
data/README.md
ADDED
@@ -0,0 +1,111 @@
|
|
1
|
+
rinterface
|
2
|
+
==========
|
3
|
+
|
4
|
+
Pure Ruby client that can send RPC calls to an Erlang node.
|
5
|
+
_It's very much a work in progress._
|
6
|
+
|
7
|
+
__License:__ MIT ?
|
8
|
+
|
9
|
+
|
10
|
+
## Install
|
11
|
+
|
12
|
+
gem install rinterface
|
13
|
+
|
14
|
+
|
15
|
+
## Try it out
|
16
|
+
|
17
|
+
Open a terminal and run:
|
18
|
+
|
19
|
+
rake run
|
20
|
+
|
21
|
+
This will start the erlang node named 'math'.
|
22
|
+
|
23
|
+
Open another terminal, and run:
|
24
|
+
|
25
|
+
ruby examples/client.rb
|
26
|
+
|
27
|
+
|
28
|
+
## How to use?
|
29
|
+
|
30
|
+
In your Ruby code, make a call to the Erlang node like this:
|
31
|
+
|
32
|
+
Erlang::Node(nodename,module,function,args) => [:ok,Response] | [:badprc,Reason]
|
33
|
+
|
34
|
+
|
35
|
+
r = Erlang::Node.rpc("math","math_server","add",[10,20])
|
36
|
+
|
37
|
+
if r[0] == :badrpc
|
38
|
+
puts "Got and Error. Reason #{r[1]}"
|
39
|
+
else
|
40
|
+
puts "Success: #{r[1]}"
|
41
|
+
end
|
42
|
+
|
43
|
+
Where:
|
44
|
+
|
45
|
+
* math is the node name (the -sname of the Erlang node)
|
46
|
+
* math_server is the name of the module
|
47
|
+
* add is the funtion to call
|
48
|
+
* [10,20] is an array of arguments to pass to the function
|
49
|
+
|
50
|
+
The result will always be an Array of the form:
|
51
|
+
|
52
|
+
[:ok, Response]
|
53
|
+
|
54
|
+
Where Response is the result from the Erlang, or:
|
55
|
+
|
56
|
+
[:badrpc, Reason]
|
57
|
+
|
58
|
+
Where Reason is the 'why' it failed.
|
59
|
+
|
60
|
+
### Experimental new way
|
61
|
+
|
62
|
+
The code above can be written like this now:
|
63
|
+
|
64
|
+
Erl::MathServer.add(10, 20)
|
65
|
+
|
66
|
+
(Ain`t Ruby a beauty? ;)
|
67
|
+
|
68
|
+
|
69
|
+
## So you wanna...
|
70
|
+
|
71
|
+
### Test your Erlang code from RSpec...
|
72
|
+
|
73
|
+
Here's a quick and simple example. Make sure you put the rinterface lib into RAILS_ROOT/lib and start the math_server in 'test'
|
74
|
+
|
75
|
+
|
76
|
+
|
77
|
+
### Call Erlang from your Rails app...
|
78
|
+
|
79
|
+
Here's a quick and simple example. Make sure you put the rinterface lib into RAILS_ROOT/lib and start the math_server in 'test'
|
80
|
+
In the controller:
|
81
|
+
|
82
|
+
controllers/math_controller.rb
|
83
|
+
|
84
|
+
require "lib/rinterface"
|
85
|
+
|
86
|
+
class MathController < ApplicationController
|
87
|
+
def index
|
88
|
+
a = params[:a]
|
89
|
+
b = params[:b]
|
90
|
+
r = Erlang::Node.rpc("math","math_server","add",[a.to_i,b.to_i])
|
91
|
+
|
92
|
+
if r[0] == :badrpc
|
93
|
+
@result = "Error"
|
94
|
+
else
|
95
|
+
@result = r[1]
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
|
101
|
+
Finally, add a template for the view, and try 'http://localhost:3000/math?a=2&b=3'.
|
102
|
+
This is not ideal yet and not something I'd use yet in production,
|
103
|
+
but it's a starting point for experimenting.
|
104
|
+
|
105
|
+
|
106
|
+
## Specs
|
107
|
+
|
108
|
+
Don`t forget to run specs before/after patching.
|
109
|
+
|
110
|
+
Use either "rake spec" or "spec spec",
|
111
|
+
it`ll start the erlang daemon automatically.
|
data/Rakefile
ADDED
@@ -0,0 +1,107 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
require 'rubygems'
|
3
|
+
require 'rake'
|
4
|
+
require 'rake/clean'
|
5
|
+
CLEAN << FileList['**/*.dump']
|
6
|
+
CLOBBER << FileList['**/*.beam']
|
7
|
+
SRC = FileList['**/*.erl']
|
8
|
+
OBJ = SRC.ext("beam") #)pathmap("%{examples,spec}X.beam")
|
9
|
+
ERLC_FLAGS = "-Iinclude +warn_unused_vars +warn_unused_import"
|
10
|
+
START_MODULE = "math_server"
|
11
|
+
|
12
|
+
# Gem
|
13
|
+
#
|
14
|
+
begin
|
15
|
+
require 'jeweler'
|
16
|
+
Jeweler::Tasks.new do |gem|
|
17
|
+
gem.name = "rinterface"
|
18
|
+
gem.summary = "Erlang RPC Ruby Client"
|
19
|
+
gem.description = "Pure Ruby client that can send RPC calls to an Erlang node"
|
20
|
+
gem.email = "r@interf.ace"
|
21
|
+
gem.homepage = "http://github.com/davebryson/rinterface"
|
22
|
+
gem.authors = ["Dave Bryson", "Éverton Ribeiro", "Marcos Piccinini"]
|
23
|
+
gem.add_dependency "eventmachine"
|
24
|
+
gem.add_development_dependency "rspec"
|
25
|
+
# gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
|
26
|
+
end
|
27
|
+
Jeweler::GemcutterTasks.new
|
28
|
+
rescue LoadError
|
29
|
+
puts "Jeweler (or a dependency) not available. Install it with: sudo gem install jeweler"
|
30
|
+
end
|
31
|
+
|
32
|
+
# Compile
|
33
|
+
#
|
34
|
+
rule ".beam" => ".erl" do |t|
|
35
|
+
sh "erlc -o #{t.name.split("/")[0]} -W #{ERLC_FLAGS} #{t.source}"
|
36
|
+
end
|
37
|
+
|
38
|
+
desc "Compile all"
|
39
|
+
task :compile => OBJ
|
40
|
+
task :default => :compile
|
41
|
+
|
42
|
+
# Shell
|
43
|
+
#
|
44
|
+
desc "Open up a shell"
|
45
|
+
task :shell => [:compile] do
|
46
|
+
sh("erl -sname rinterface -pa examples")
|
47
|
+
end
|
48
|
+
|
49
|
+
namespace :daemon do
|
50
|
+
desc "Daemon run #{START_MODULE}:start()"
|
51
|
+
task :start => [:compile] do
|
52
|
+
pid = sh("erl -heart -W -pa examples -sname math -s #{START_MODULE} -detached")
|
53
|
+
puts "Now try 'ruby examples/client.rb' #{pid}"
|
54
|
+
end
|
55
|
+
|
56
|
+
task :status do
|
57
|
+
sh("ps aux | grep #{START_MODULE}")
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
namespace :spec do
|
62
|
+
desc "Open up a shell and run spec test server"
|
63
|
+
task :run => [:compile] do
|
64
|
+
sh("erl -noshell -W -pa spec -sname spec -s spec_server&")
|
65
|
+
end
|
66
|
+
desc "Open up a shell and run spec test server"
|
67
|
+
task :shell => [:compile] do
|
68
|
+
sh("erl -W -pa spec -sname spec -s spec_server")
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
task :test => [:compile] do
|
73
|
+
puts "Running suite.."
|
74
|
+
Rake::Task["spec"].invoke
|
75
|
+
Rake::Task["spec"].invoke
|
76
|
+
end
|
77
|
+
|
78
|
+
# Spec
|
79
|
+
#
|
80
|
+
require 'spec/rake/spectask'
|
81
|
+
Spec::Rake::SpecTask.new(:spec) do |spec|
|
82
|
+
spec.libs << 'lib' << 'spec'
|
83
|
+
spec.spec_files = FileList['spec/**/*_spec.rb']
|
84
|
+
end
|
85
|
+
|
86
|
+
Spec::Rake::SpecTask.new(:rcov) do |spec|
|
87
|
+
spec.libs << 'lib' << 'spec'
|
88
|
+
spec.pattern = 'spec/**/*_spec.rb'
|
89
|
+
spec.rcov = true
|
90
|
+
end
|
91
|
+
task :spec => :check_dependencies
|
92
|
+
|
93
|
+
# RDoc
|
94
|
+
#
|
95
|
+
require 'rake/rdoctask'
|
96
|
+
Rake::RDocTask.new do |rdoc|
|
97
|
+
if File.exist?('VERSION')
|
98
|
+
version = File.read('VERSION')
|
99
|
+
else
|
100
|
+
version = ""
|
101
|
+
end
|
102
|
+
|
103
|
+
rdoc.rdoc_dir = 'rdoc'
|
104
|
+
rdoc.title = "rinterface #{version}"
|
105
|
+
rdoc.rdoc_files.include('README*')
|
106
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
107
|
+
end
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.0.3
|
data/examples/client.rb
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
require 'lib/rinterface'
|
2
|
+
|
3
|
+
|
4
|
+
# Try different responses...
|
5
|
+
|
6
|
+
puts "Bad rpc test. Try to call the wrong service"
|
7
|
+
r = Erlang::Node.rpc("math","matx_server","add",[10,20])
|
8
|
+
puts "Got: #{r.inspect}"
|
9
|
+
|
10
|
+
puts "--------"
|
11
|
+
puts "Bad port test, No Port for Service. Can't find a port for 'ath'"
|
12
|
+
r = Erlang::Node.rpc("ath","matx_server","add",[10,20])
|
13
|
+
puts "Got: #{r.inspect}"
|
14
|
+
|
15
|
+
puts "--------"
|
16
|
+
puts "Good call, add 10 + 20"
|
17
|
+
r = Erlang::Node.rpc("math","math_server","add",[10,20])
|
18
|
+
puts "Got: #{r.inspect}"
|
19
|
+
|
@@ -0,0 +1,28 @@
|
|
1
|
+
%%
|
2
|
+
%% Simple add server
|
3
|
+
%%
|
4
|
+
-module(math_server).
|
5
|
+
-export([start/0,add/2]).
|
6
|
+
|
7
|
+
start() ->
|
8
|
+
register(?MODULE,spawn(fun() -> loop() end)).
|
9
|
+
|
10
|
+
add(X,Y) ->
|
11
|
+
?MODULE ! {self(),add,X,Y},
|
12
|
+
receive
|
13
|
+
{?MODULE,Response} -> Response
|
14
|
+
end.
|
15
|
+
|
16
|
+
sum(X,Y) -> X+Y.
|
17
|
+
|
18
|
+
loop() ->
|
19
|
+
receive
|
20
|
+
{From,add,X,Y} ->
|
21
|
+
error_logger:info_msg("Got the request, and doing the add...~n"),
|
22
|
+
Sum = X+Y,
|
23
|
+
From ! {?MODULE,Sum},
|
24
|
+
loop();
|
25
|
+
Any ->
|
26
|
+
error_logger:info_msg("Got a crazy msg: ~p~n",[Any]),
|
27
|
+
loop()
|
28
|
+
end.
|
@@ -0,0 +1,58 @@
|
|
1
|
+
#
|
2
|
+
# Connects to epmd to find the port number of the requested node
|
3
|
+
# this only implements port_please_request
|
4
|
+
#
|
5
|
+
module Erlang
|
6
|
+
class EpmdConnection < EM::Connection
|
7
|
+
include EM::Deferrable
|
8
|
+
attr_accessor :nodename
|
9
|
+
|
10
|
+
def self.lookup_node(nodename)
|
11
|
+
EM.connect("127.0.0.1",4369,self) do |conn|
|
12
|
+
conn.nodename = nodename
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def connection_completed
|
17
|
+
send_data lookup_port
|
18
|
+
end
|
19
|
+
|
20
|
+
def receive_data(data)
|
21
|
+
parse_response(data)
|
22
|
+
end
|
23
|
+
|
24
|
+
def unbind
|
25
|
+
end
|
26
|
+
|
27
|
+
def lookup_port
|
28
|
+
out = StringIO.new('', 'w')
|
29
|
+
|
30
|
+
# Create the header with length: 2
|
31
|
+
out.write([@nodename.size + 1].pack('n'))
|
32
|
+
|
33
|
+
# Next the request
|
34
|
+
# tag. Length: 1
|
35
|
+
out.write([122].pack("C"))
|
36
|
+
# nodename
|
37
|
+
out.write(nodename)
|
38
|
+
out.string
|
39
|
+
end
|
40
|
+
|
41
|
+
# If we get a good result we only return
|
42
|
+
# the port (not reading all the information
|
43
|
+
def parse_response(input)
|
44
|
+
i = StringIO.new(input)
|
45
|
+
code = i.read(1).unpack('C').first
|
46
|
+
result = i.read(1).unpack('C').first
|
47
|
+
if result == 0
|
48
|
+
# good response read the port
|
49
|
+
port = i.read(2).unpack('n').first
|
50
|
+
set_deferred_success port
|
51
|
+
else
|
52
|
+
set_deferred_failure 0
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module Rinterface
|
2
|
+
|
3
|
+
module Erl
|
4
|
+
|
5
|
+
class << self
|
6
|
+
|
7
|
+
def method_missing(fun, node, *rest)
|
8
|
+
Erlang::Node.fun(node.to_s, @@module, fun.to_s, *rest)
|
9
|
+
end
|
10
|
+
|
11
|
+
def const_missing(m)
|
12
|
+
@@module = snake(m)
|
13
|
+
self
|
14
|
+
end
|
15
|
+
|
16
|
+
def snake(txt)
|
17
|
+
txt.to_s.split(/(?=[A-Z])/).join('_').downcase
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
@@ -0,0 +1,223 @@
|
|
1
|
+
module Erlang
|
2
|
+
|
3
|
+
class Decode
|
4
|
+
include External::Types
|
5
|
+
|
6
|
+
attr_accessor :in
|
7
|
+
|
8
|
+
def self.read_bits(string)
|
9
|
+
new(StringIO.new(string))
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.read_any_from(string)
|
13
|
+
new(StringIO.new(string)).read_any
|
14
|
+
end
|
15
|
+
|
16
|
+
def initialize(ins)
|
17
|
+
@in = ins
|
18
|
+
@peeked = ""
|
19
|
+
end
|
20
|
+
|
21
|
+
def read_any
|
22
|
+
raise "Bad Math on Version" unless read_1 == External::VERSION
|
23
|
+
read_any_raw
|
24
|
+
end
|
25
|
+
|
26
|
+
def read_any_raw
|
27
|
+
case peek_1
|
28
|
+
when ATOM then read_atom
|
29
|
+
when SMALL_INT then read_small_int
|
30
|
+
when INT then read_int
|
31
|
+
when SMALL_BIGNUM then read_small_bignum
|
32
|
+
when LARGE_BIGNUM then read_large_bignum
|
33
|
+
when NEW_FLOAT then read_double
|
34
|
+
when FLOAT then read_float
|
35
|
+
when NEW_REF then read_new_reference
|
36
|
+
when PID then read_pid
|
37
|
+
when SMALL_TUPLE then read_small_tuple
|
38
|
+
when LARGE_TUPLE then read_large_tuple
|
39
|
+
when NIL then read_nil
|
40
|
+
when STRING then read_erl_string
|
41
|
+
when LIST then read_list
|
42
|
+
when BIN then read_bin
|
43
|
+
else
|
44
|
+
fail("Unknown term tag: #{peek_1}")
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def read(length)
|
49
|
+
# p @peeked
|
50
|
+
if length < @peeked.length
|
51
|
+
result = @peeked[0...length]
|
52
|
+
@peeked = @peeked[length..-1]
|
53
|
+
length = 0
|
54
|
+
else
|
55
|
+
result = @peeked
|
56
|
+
@peeked = ''
|
57
|
+
length -= result.length
|
58
|
+
end
|
59
|
+
|
60
|
+
if length > 0
|
61
|
+
result << @in.read(length)
|
62
|
+
end
|
63
|
+
result
|
64
|
+
end
|
65
|
+
|
66
|
+
def peek(length)
|
67
|
+
if length <= @peeked.length
|
68
|
+
@peeked[0...length]
|
69
|
+
else
|
70
|
+
read_bytes = @in.read(length - @peeked.length)
|
71
|
+
@peeked << read_bytes if read_bytes
|
72
|
+
@peeked
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
def peek_1
|
77
|
+
peek(1).unpack("C").first
|
78
|
+
end
|
79
|
+
|
80
|
+
def peek_2
|
81
|
+
peek(2).unpack("n").first
|
82
|
+
end
|
83
|
+
|
84
|
+
def read_1
|
85
|
+
read(1).unpack("C").first
|
86
|
+
end
|
87
|
+
|
88
|
+
def read_2
|
89
|
+
read(2).unpack("n").first
|
90
|
+
end
|
91
|
+
|
92
|
+
def read_4
|
93
|
+
read(4).unpack("N").first
|
94
|
+
end
|
95
|
+
|
96
|
+
def read_string(length)
|
97
|
+
read(length)
|
98
|
+
end
|
99
|
+
|
100
|
+
def read_atom
|
101
|
+
fail("Invalid Type, not an atom") unless read_1 == ATOM
|
102
|
+
length = read_2
|
103
|
+
if length == 0
|
104
|
+
''
|
105
|
+
else
|
106
|
+
read_string(length).to_sym
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
def read_small_int
|
111
|
+
fail("Invalid Type, not a small int") unless read_1 == SMALL_INT
|
112
|
+
read_1
|
113
|
+
end
|
114
|
+
|
115
|
+
def read_int
|
116
|
+
fail("Invalid Type, not an int") unless read_1 == INT
|
117
|
+
value = read_4
|
118
|
+
negative = (value >> 31)[0] == 1
|
119
|
+
value = (value - (1 << 32)) if negative
|
120
|
+
value = Fixnum.induced_from(value)
|
121
|
+
end
|
122
|
+
|
123
|
+
def read_small_bignum
|
124
|
+
fail("Invalid Type, not a small bignum") unless read_1 == SMALL_BIGNUM
|
125
|
+
size = read_1
|
126
|
+
sign = read_1
|
127
|
+
bytes = read_string(size).unpack("C" * size)
|
128
|
+
added = bytes.zip((0..bytes.length).to_a).inject(0) do |result, byte_index|
|
129
|
+
byte, index = *byte_index
|
130
|
+
value = (byte * (256 ** index))
|
131
|
+
sign != 0 ? (result - value) : (result + value)
|
132
|
+
end
|
133
|
+
Bignum.induced_from(added)
|
134
|
+
end
|
135
|
+
|
136
|
+
def read_large_bignum
|
137
|
+
fail("Invalid Type, not a large bignum") unless read_1 == LARGE_BIGNUM
|
138
|
+
size = read_4
|
139
|
+
sign = read_1
|
140
|
+
bytes = read_string(size).unpack("C" * size)
|
141
|
+
added = bytes.zip((0..bytes.length).to_a).inject(0) do |result, byte_index|
|
142
|
+
byte, index = *byte_index
|
143
|
+
value = (byte * (256 ** index))
|
144
|
+
sign != 0 ? (result - value) : (result + value)
|
145
|
+
end
|
146
|
+
Bignum.induced_from(added)
|
147
|
+
end
|
148
|
+
|
149
|
+
def read_double
|
150
|
+
fail("Invalid Type, not a double") unless read_1 == NEW_FLOAT
|
151
|
+
read_string(64).unpack('G').first
|
152
|
+
end
|
153
|
+
|
154
|
+
def read_float
|
155
|
+
fail("Invalid Type, not a float") unless read_1 == FLOAT
|
156
|
+
|
157
|
+
read_string(32).unpack('g').first
|
158
|
+
end
|
159
|
+
|
160
|
+
def read_new_reference
|
161
|
+
fail("Invalid Type, not a new-style reference") unless read_1 == NEW_REF
|
162
|
+
size = read_2
|
163
|
+
node = read_atom
|
164
|
+
creation = read_1
|
165
|
+
id = (0...size).map{|i| read_4 }
|
166
|
+
NewReference.new(node, creation, id)
|
167
|
+
end
|
168
|
+
|
169
|
+
def read_pid
|
170
|
+
fail("Invalid Type, not a pid") unless read_1 == PID
|
171
|
+
node = read_atom
|
172
|
+
id = read_4
|
173
|
+
serial = read_4
|
174
|
+
creation = read_1
|
175
|
+
Terms::Pid.new(node, id, serial, creation)
|
176
|
+
end
|
177
|
+
|
178
|
+
def read_small_tuple
|
179
|
+
fail("Invalid Type, not a small tuple") unless read_1 == SMALL_TUPLE
|
180
|
+
arity = read_1
|
181
|
+
|
182
|
+
(0...arity).map{|i| read_any_raw }
|
183
|
+
end
|
184
|
+
|
185
|
+
def read_large_tuple
|
186
|
+
fail("Invalid Type, not a small tuple") unless read_1 == LARGE_TUPLE
|
187
|
+
arity = read_4
|
188
|
+
(0...arity).map{|i| read_any_raw}
|
189
|
+
end
|
190
|
+
|
191
|
+
def read_nil
|
192
|
+
fail("Invalid Type, not a nil list") unless read_1 == NIL
|
193
|
+
[]
|
194
|
+
end
|
195
|
+
|
196
|
+
def read_erl_string
|
197
|
+
fail("Invalid Type, not an erlang string") unless read_1 == STRING
|
198
|
+
length = read_2
|
199
|
+
read_string(length).unpack('C' * length)
|
200
|
+
end
|
201
|
+
|
202
|
+
def read_list
|
203
|
+
fail("Invalid Type, not an erlang list") unless read_1 == LIST
|
204
|
+
length = read_4
|
205
|
+
list = (0...length).map{|i| read_any_raw}
|
206
|
+
read_1
|
207
|
+
list
|
208
|
+
end
|
209
|
+
|
210
|
+
def read_bin
|
211
|
+
fail("Invalid Type, not an erlang binary") unless read_1 == BIN
|
212
|
+
length = read_4
|
213
|
+
read_string(length)
|
214
|
+
end
|
215
|
+
|
216
|
+
class DecodeError < StandardError; end
|
217
|
+
def fail(str)
|
218
|
+
raise DecodeError, str
|
219
|
+
end
|
220
|
+
|
221
|
+
end
|
222
|
+
|
223
|
+
end
|
@@ -0,0 +1,107 @@
|
|
1
|
+
module Erlang
|
2
|
+
class Encoder
|
3
|
+
include External::Types
|
4
|
+
include Terms
|
5
|
+
|
6
|
+
attr_accessor :out
|
7
|
+
def initialize
|
8
|
+
@out = StringIO.new('', 'w')
|
9
|
+
end
|
10
|
+
|
11
|
+
def rewind
|
12
|
+
@out.rewind
|
13
|
+
end
|
14
|
+
|
15
|
+
def term_to_binary obj
|
16
|
+
write_1 External::VERSION
|
17
|
+
write_any_raw obj
|
18
|
+
end
|
19
|
+
|
20
|
+
def write_any_raw obj
|
21
|
+
case obj
|
22
|
+
when Symbol then write_symbol(obj)
|
23
|
+
when Fixnum, Bignum then write_integer(obj)
|
24
|
+
when Float then write_double(obj)
|
25
|
+
when Array then write_tuple(obj)
|
26
|
+
when String then write_binary(obj)
|
27
|
+
when Pid then write_pid(obj)
|
28
|
+
when List then write_list(obj)
|
29
|
+
else
|
30
|
+
raise "Failed encoding!"
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def write_1(byte)
|
35
|
+
@out.write([byte].pack("C"))
|
36
|
+
end
|
37
|
+
|
38
|
+
def write_2(short)
|
39
|
+
@out.write([short].pack("n"))
|
40
|
+
end
|
41
|
+
|
42
|
+
def write_4(long)
|
43
|
+
@out.write([long].pack("N"))
|
44
|
+
end
|
45
|
+
|
46
|
+
def write_string(string)
|
47
|
+
@out.write(string)
|
48
|
+
end
|
49
|
+
|
50
|
+
def write_symbol(sym)
|
51
|
+
data = sym.to_s
|
52
|
+
write_1 ATOM
|
53
|
+
write_2 data.length
|
54
|
+
write_string data
|
55
|
+
end
|
56
|
+
|
57
|
+
# TODO: Bignum support
|
58
|
+
def write_integer(num)
|
59
|
+
if 0 <= num && num < 256
|
60
|
+
write_1 SMALL_INT
|
61
|
+
write_1 num
|
62
|
+
else
|
63
|
+
write_1 INT
|
64
|
+
write_4 num
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def write_double(num)
|
69
|
+
write_1 NEW_FLOAT
|
70
|
+
@out.write([num].pack('G'))
|
71
|
+
end
|
72
|
+
|
73
|
+
def write_tuple(data)
|
74
|
+
if data.length < 256
|
75
|
+
write_1 SMALL_TUPLE
|
76
|
+
write_1 data.length
|
77
|
+
else
|
78
|
+
write_1 LARGE_TUPLE
|
79
|
+
write_4 data.length
|
80
|
+
end
|
81
|
+
data.each{|e| write_any_raw e }
|
82
|
+
end
|
83
|
+
|
84
|
+
def write_pid(pid)
|
85
|
+
write_1(103)
|
86
|
+
write_symbol(pid.node)
|
87
|
+
write_4((pid.node_id & 0x7fff))
|
88
|
+
write_4((pid.serial & 0x1fff))
|
89
|
+
write_1((pid.creation & 0x3))
|
90
|
+
end
|
91
|
+
|
92
|
+
def write_list(list)
|
93
|
+
len = list.data.size
|
94
|
+
write_1(108)
|
95
|
+
write_4(len)
|
96
|
+
list.data.each{ |i| write_any_raw i }
|
97
|
+
write_1(106)
|
98
|
+
end
|
99
|
+
|
100
|
+
def write_binary(data)
|
101
|
+
write_1 BIN
|
102
|
+
write_4 data.length
|
103
|
+
write_string data
|
104
|
+
end
|
105
|
+
|
106
|
+
end
|
107
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
#
|
2
|
+
# adopted from Erlectricity
|
3
|
+
# this version is slightly tweaked, a bit sloppy, and needs a cleanin'
|
4
|
+
#
|
5
|
+
module Erlang
|
6
|
+
module Terms
|
7
|
+
|
8
|
+
class Pid
|
9
|
+
attr_reader :node, :node_id, :serial, :creation
|
10
|
+
def initialize(node,nid,serial,created)
|
11
|
+
@node = node
|
12
|
+
@node_id = nid
|
13
|
+
@serial = serial
|
14
|
+
@creation = created
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
class List
|
19
|
+
attr_reader :data
|
20
|
+
def initialize(array)
|
21
|
+
@data = array
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
26
|
+
|
27
|
+
|
28
|
+
end
|