erlectricity 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/Manifest.txt +41 -0
- data/README.txt +3 -0
- data/Rakefile +68 -0
- data/examples/gruff/gruff.erl +62 -0
- data/examples/gruff/gruff_provider.rb +38 -0
- data/examples/gruff/gruff_run.erl +17 -0
- data/examples/gruff/stat_run.erl +18 -0
- data/examples/gruff/stat_writer.erl +40 -0
- data/examples/tinderl/tinderl.erl +41 -0
- data/examples/tinderl/tinderl.rb +23 -0
- data/lib/erlectricity/condition.rb +48 -0
- data/lib/erlectricity/conditions/hash.rb +15 -0
- data/lib/erlectricity/conditions/static.rb +17 -0
- data/lib/erlectricity/conditions/type.rb +19 -0
- data/lib/erlectricity/constants.rb +37 -0
- data/lib/erlectricity/decoder.rb +202 -0
- data/lib/erlectricity/encoder.rb +127 -0
- data/lib/erlectricity/errors/decode_error.rb +3 -0
- data/lib/erlectricity/errors/encode_error.rb +3 -0
- data/lib/erlectricity/errors/erlectricity_error.rb +3 -0
- data/lib/erlectricity/match_context.rb +20 -0
- data/lib/erlectricity/matcher.rb +60 -0
- data/lib/erlectricity/port.rb +46 -0
- data/lib/erlectricity/receiver.rb +74 -0
- data/lib/erlectricity/types/function.rb +3 -0
- data/lib/erlectricity/types/new_function.rb +3 -0
- data/lib/erlectricity/types/new_reference.rb +3 -0
- data/lib/erlectricity/types/pid.rb +3 -0
- data/lib/erlectricity/types/reference.rb +3 -0
- data/lib/erlectricity/version.rb +9 -0
- data/lib/erlectricity.rb +23 -0
- data/setup.rb +1585 -0
- data/test/condition_spec.rb +78 -0
- data/test/decode_spec.rb +133 -0
- data/test/encode_spec.rb +125 -0
- data/test/matcher_spec.rb +73 -0
- data/test/port_spec.rb +35 -0
- data/test/receiver_spec.rb +105 -0
- data/test/spec_suite.rb +2 -0
- data/test/test_erlectricity.rb +2 -0
- data/test/test_helper.rb +36 -0
- metadata +87 -0
data/Manifest.txt
ADDED
@@ -0,0 +1,41 @@
|
|
1
|
+
Manifest.txt
|
2
|
+
README.txt
|
3
|
+
Rakefile
|
4
|
+
examples/gruff/gruff.erl
|
5
|
+
examples/gruff/gruff_provider.rb
|
6
|
+
examples/gruff/gruff_run.erl
|
7
|
+
examples/gruff/stat_run.erl
|
8
|
+
examples/gruff/stat_writer.erl
|
9
|
+
examples/tinderl/tinderl.erl
|
10
|
+
examples/tinderl/tinderl.rb
|
11
|
+
lib/erlectricity.rb
|
12
|
+
lib/erlectricity/condition.rb
|
13
|
+
lib/erlectricity/conditions/hash.rb
|
14
|
+
lib/erlectricity/conditions/static.rb
|
15
|
+
lib/erlectricity/conditions/type.rb
|
16
|
+
lib/erlectricity/constants.rb
|
17
|
+
lib/erlectricity/decoder.rb
|
18
|
+
lib/erlectricity/encoder.rb
|
19
|
+
lib/erlectricity/errors/decode_error.rb
|
20
|
+
lib/erlectricity/errors/encode_error.rb
|
21
|
+
lib/erlectricity/errors/erlectricity_error.rb
|
22
|
+
lib/erlectricity/match_context.rb
|
23
|
+
lib/erlectricity/matcher.rb
|
24
|
+
lib/erlectricity/port.rb
|
25
|
+
lib/erlectricity/receiver.rb
|
26
|
+
lib/erlectricity/types/function.rb
|
27
|
+
lib/erlectricity/types/new_function.rb
|
28
|
+
lib/erlectricity/types/new_reference.rb
|
29
|
+
lib/erlectricity/types/pid.rb
|
30
|
+
lib/erlectricity/types/reference.rb
|
31
|
+
lib/erlectricity/version.rb
|
32
|
+
setup.rb
|
33
|
+
test/condition_spec.rb
|
34
|
+
test/decode_spec.rb
|
35
|
+
test/encode_spec.rb
|
36
|
+
test/matcher_spec.rb
|
37
|
+
test/port_spec.rb
|
38
|
+
test/receiver_spec.rb
|
39
|
+
test/spec_suite.rb
|
40
|
+
test/test_erlectricity.rb
|
41
|
+
test/test_helper.rb
|
data/README.txt
ADDED
data/Rakefile
ADDED
@@ -0,0 +1,68 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rake'
|
3
|
+
require 'rake/clean'
|
4
|
+
require 'rake/testtask'
|
5
|
+
require 'rake/packagetask'
|
6
|
+
require 'rake/gempackagetask'
|
7
|
+
require 'rake/rdoctask'
|
8
|
+
require 'rake/contrib/rubyforgepublisher'
|
9
|
+
require 'fileutils'
|
10
|
+
require 'hoe'
|
11
|
+
include FileUtils
|
12
|
+
require File.join(File.dirname(__FILE__), 'lib', 'erlectricity', 'version')
|
13
|
+
|
14
|
+
AUTHOR = 'Scott Fleckenstein' # can also be an array of Authors
|
15
|
+
EMAIL = "nullstyle@gmail.com"
|
16
|
+
DESCRIPTION = "A library to interface erlang and ruby through the erlang port system"
|
17
|
+
GEM_NAME = 'erlectricity' # what ppl will type to install your gem
|
18
|
+
RUBYFORGE_PROJECT = 'erlectricity' # The unix name for your project
|
19
|
+
HOMEPATH = "http://#{RUBYFORGE_PROJECT}.rubyforge.org"
|
20
|
+
DOWNLOAD_PATH = "http://rubyforge.org/projects/#{RUBYFORGE_PROJECT}"
|
21
|
+
|
22
|
+
NAME = "erlectricity"
|
23
|
+
REV = nil # UNCOMMENT IF REQUIRED: File.read(".svn/entries")[/committed-rev="(d+)"/, 1] rescue nil
|
24
|
+
VERS = Erlectricity::VERSION::STRING + (REV ? ".#{REV}" : "")
|
25
|
+
CLEAN.include ['**/.*.sw?', '*.gem', '.config', '**/.DS_Store']
|
26
|
+
RDOC_OPTS = ['--quiet', '--title', 'erlectricity documentation',
|
27
|
+
"--opname", "index.html",
|
28
|
+
"--line-numbers",
|
29
|
+
"--main", "README",
|
30
|
+
"--inline-source"]
|
31
|
+
|
32
|
+
class Hoe
|
33
|
+
def extra_deps
|
34
|
+
@extra_deps.reject { |x| Array(x).first == 'hoe' }
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
# Generate all the Rake tasks
|
39
|
+
# Run 'rake -T' to see list of generated tasks (from gem root directory)
|
40
|
+
hoe = Hoe.new(GEM_NAME, VERS) do |p|
|
41
|
+
p.author = AUTHOR
|
42
|
+
p.description = DESCRIPTION
|
43
|
+
p.email = EMAIL
|
44
|
+
p.summary = DESCRIPTION
|
45
|
+
p.url = HOMEPATH
|
46
|
+
p.rubyforge_name = RUBYFORGE_PROJECT if RUBYFORGE_PROJECT
|
47
|
+
p.test_globs = ["test/**/test_*.rb"]
|
48
|
+
p.clean_globs = CLEAN #An array of file patterns to delete on clean.
|
49
|
+
|
50
|
+
# == Optional
|
51
|
+
p.changes = p.paragraphs_of("History.txt", 0..1).join("\n\n")
|
52
|
+
#p.extra_deps = [] # An array of rubygem dependencies [name, version], e.g. [ ['active_support', '>= 1.3.1'] ]
|
53
|
+
#p.spec_extras = {} # A hash of extra values to set in the gemspec.
|
54
|
+
end
|
55
|
+
|
56
|
+
desc 'Release the website and new gem version'
|
57
|
+
task :deploy => [:check_version, :release]
|
58
|
+
|
59
|
+
task :check_version do
|
60
|
+
unless ENV['VERSION']
|
61
|
+
puts 'Must pass a VERSION=x.y.z release version'
|
62
|
+
exit
|
63
|
+
end
|
64
|
+
unless ENV['VERSION'] == VERS
|
65
|
+
puts "Please update your version.rb to match the release version, currently #{VERS}"
|
66
|
+
exit
|
67
|
+
end
|
68
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
-module(gruff).
|
2
|
+
-export([start/0, stop/0, plot/4]).
|
3
|
+
|
4
|
+
start() ->
|
5
|
+
spawn(fun() ->
|
6
|
+
register(gruff, self()),
|
7
|
+
process_flag(trap_exit, true),
|
8
|
+
Cmd = "ruby ./gruff_provider.rb",
|
9
|
+
Port = open_port({spawn, Cmd}, [{packet, 4}, use_stdio, exit_status, binary]),
|
10
|
+
port_loop(Port)
|
11
|
+
end).
|
12
|
+
|
13
|
+
stop() -> gruff ! stop.
|
14
|
+
|
15
|
+
plot(Name, Font, Data, Labels) ->
|
16
|
+
gruff ! {plot, self(), Name, Font, Data, Labels},
|
17
|
+
receive
|
18
|
+
{result, Bin} -> Bin
|
19
|
+
end.
|
20
|
+
|
21
|
+
send_data(_Port, []) -> ok;
|
22
|
+
send_data(Port, [{Name, Points}|Rest]) ->
|
23
|
+
Data = {data, Name, Points},
|
24
|
+
Port ! {self(), {command, term_to_binary(Data)}},
|
25
|
+
send_data(Port, Rest).
|
26
|
+
|
27
|
+
send_labels(Port, Labels) ->
|
28
|
+
Data = {labels, Labels},
|
29
|
+
Port ! {self(), {command, term_to_binary(Data)}}.
|
30
|
+
|
31
|
+
|
32
|
+
port_loop(Port) ->
|
33
|
+
receive
|
34
|
+
{plot, Caller, Name, Font, Data, Labels} ->
|
35
|
+
PlotData = term_to_binary({plot, Name, 'Line', Font}),
|
36
|
+
Port ! {self(), {command, PlotData}},
|
37
|
+
|
38
|
+
send_data(Port, Data),
|
39
|
+
send_labels(Port, Labels),
|
40
|
+
|
41
|
+
EndData = term_to_binary('end'),
|
42
|
+
Port ! {self(), {command, EndData}},
|
43
|
+
Result = get_result(Port),
|
44
|
+
Caller ! {result, Result },
|
45
|
+
|
46
|
+
port_loop(Port);
|
47
|
+
|
48
|
+
stop ->
|
49
|
+
Port ! {self(), close},
|
50
|
+
receive
|
51
|
+
{Port, closed} -> exit(normal)
|
52
|
+
end
|
53
|
+
end.
|
54
|
+
|
55
|
+
get_result(Port) ->
|
56
|
+
receive
|
57
|
+
{Port, {data, Data}} ->
|
58
|
+
{result, Bin} = binary_to_term(Data),
|
59
|
+
Bin;
|
60
|
+
{'EXIT', Port, Reason} ->
|
61
|
+
exit({port_terminated,Reason})
|
62
|
+
end.
|
@@ -0,0 +1,38 @@
|
|
1
|
+
$:.unshift(File.dirname(__FILE__) + "/../../lib/")
|
2
|
+
require 'rubygems'
|
3
|
+
require 'erlectricity'
|
4
|
+
require 'gruff'
|
5
|
+
|
6
|
+
receive do
|
7
|
+
|
8
|
+
|
9
|
+
|
10
|
+
match(:plot, string(:name), atom(:style), string(:font)) do
|
11
|
+
graph = Gruff.const_get(style).new
|
12
|
+
graph.title = name
|
13
|
+
graph.font = font
|
14
|
+
graph.legend_font_size = 10
|
15
|
+
|
16
|
+
|
17
|
+
receive do
|
18
|
+
match(:data, atom(:name), list(:points)) do
|
19
|
+
graph.data name, points
|
20
|
+
receive_loop
|
21
|
+
end
|
22
|
+
|
23
|
+
match(:labels, hash(:label_data)) do
|
24
|
+
graph.labels = label_data
|
25
|
+
receive_loop
|
26
|
+
end
|
27
|
+
|
28
|
+
match(:end){ :ok }
|
29
|
+
end
|
30
|
+
|
31
|
+
|
32
|
+
send! :result, graph.to_blob
|
33
|
+
receive_loop
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
37
|
+
|
38
|
+
|
@@ -0,0 +1,17 @@
|
|
1
|
+
#!/usr/bin/env escript
|
2
|
+
-export([main/1]).
|
3
|
+
main(_Any) ->
|
4
|
+
Data = [
|
5
|
+
{apples, [10, 2, 3, 4, 4, 3]},
|
6
|
+
{oranges, [4, 8, 7, 9, 8, 9]},
|
7
|
+
{watermelons, [2, 3, 1, 5, 6, 8]},
|
8
|
+
{peaches, [9, 9, 10, 8, 7, 9]}
|
9
|
+
],
|
10
|
+
gruff:start(),
|
11
|
+
Result = gruff:plot(
|
12
|
+
<<"My Charts">>,
|
13
|
+
<<"/Users/scott/Library/Fonts/Arial">>,
|
14
|
+
Data,
|
15
|
+
[{0, <<"2003">>}, {2, <<"2004">>}, {4, <<"2005">>}]
|
16
|
+
),
|
17
|
+
file:write_file("out.png", Result).
|
@@ -0,0 +1,18 @@
|
|
1
|
+
#!/usr/bin/env escript
|
2
|
+
-export([main/1]).
|
3
|
+
main(_Any) ->
|
4
|
+
gruff:start(),
|
5
|
+
MemoryWriter = stat_writer:start(<<"Memory Info">>, fun() -> erlang:memory() end),
|
6
|
+
ProcessWriter = stat_writer:start(<<"Process Info">>,
|
7
|
+
fun() ->
|
8
|
+
{_, QueueLength} = erlang:process_info(erlang:whereis(gruff), message_queue_len),
|
9
|
+
[{processes, erlang:system_info(process_count)},
|
10
|
+
{gruff_queue_length, QueueLength}]
|
11
|
+
end
|
12
|
+
),
|
13
|
+
receive
|
14
|
+
after 20000 ->
|
15
|
+
MemoryWriter ! stop,
|
16
|
+
ProcessWriter ! stop,
|
17
|
+
elang:halt()
|
18
|
+
end.
|
@@ -0,0 +1,40 @@
|
|
1
|
+
-module(stat_writer).
|
2
|
+
-export([start/2, loop/3]).
|
3
|
+
|
4
|
+
start(Title, Fun) ->
|
5
|
+
spawn(?MODULE, loop, [Title, Fun, []]).
|
6
|
+
|
7
|
+
loop(Title, Fun, []) ->
|
8
|
+
Data = accumulate([], Fun()),
|
9
|
+
loop(Title, Fun, Data, 0).
|
10
|
+
|
11
|
+
loop(Title, Fun, Data, Generation) ->
|
12
|
+
receive
|
13
|
+
{stop} -> ok
|
14
|
+
after 3000 ->
|
15
|
+
NewGeneration = Generation + 1,
|
16
|
+
NewData = accumulate(Data, Fun()),
|
17
|
+
NewChart = gruff:plot(
|
18
|
+
list_to_binary([Title, << "- Generation" >>, integer_to_list(NewGeneration)]),
|
19
|
+
<<"/Users/scott/Library/Fonts/Arial">>,
|
20
|
+
NewData,
|
21
|
+
[]
|
22
|
+
),
|
23
|
+
file:write_file(io_lib:format("~s - ~s.png", [Title, integer_to_list(NewGeneration)]),NewChart),
|
24
|
+
loop(Title, Fun, NewData, NewGeneration)
|
25
|
+
end.
|
26
|
+
|
27
|
+
process_axis({Name, PreviousReadings}, {Name, Reading}) ->
|
28
|
+
{Name, [Reading|PreviousReadings]}.
|
29
|
+
|
30
|
+
accumulate(Data, []) -> Data;
|
31
|
+
accumulate([], [{Name, Reading}|Rest]) ->
|
32
|
+
Data = [{Name, [Reading]}],
|
33
|
+
accumulate(Data, Rest);
|
34
|
+
accumulate(Data, [{Name, Reading}|Rest]) ->
|
35
|
+
MergedData = case lists:keysearch(Name, 1, Data) of
|
36
|
+
{value, Axis} -> lists:keyreplace(Name, 1, Data, process_axis(Axis, {Name, Reading}));
|
37
|
+
false ->
|
38
|
+
[{Name, [Reading]}|Data]
|
39
|
+
end,
|
40
|
+
accumulate(MergedData, Rest).
|
@@ -0,0 +1,41 @@
|
|
1
|
+
-module(tinderl).
|
2
|
+
-export([start/4, stop/0, speak/1, paste/1]).
|
3
|
+
|
4
|
+
start(Domain, Email, Password, Room) ->
|
5
|
+
spawn(fun() ->
|
6
|
+
register(tinderl, self()),
|
7
|
+
process_flag(trap_exit, true),
|
8
|
+
Cmd = lists:flatten(io_lib:format("ruby ./tinderl.rb ~s ~s ~s ~s", [Domain, Email, Password, Room])),
|
9
|
+
Port = open_port({spawn, Cmd}, [{packet, 4}, use_stdio, exit_status, binary]),
|
10
|
+
port_loop(Port)
|
11
|
+
end).
|
12
|
+
|
13
|
+
stop() -> tinderl ! stop.
|
14
|
+
|
15
|
+
speak(String) when is_binary(String) -> tinderl ! {speak, self(), String}.
|
16
|
+
paste(String) when is_binary(String) -> tinderl ! {paste, self(), String}.
|
17
|
+
|
18
|
+
port_loop(Port) ->
|
19
|
+
receive
|
20
|
+
{speak, _Caller, String} ->
|
21
|
+
Data = term_to_binary({speak, String}),
|
22
|
+
Port ! {self(), {command, Data}},
|
23
|
+
|
24
|
+
port_loop(Port);
|
25
|
+
|
26
|
+
{paste, _Caller, String} ->
|
27
|
+
Data = term_to_binary({paste, String}),
|
28
|
+
Port ! {self(), {command, Data}},
|
29
|
+
|
30
|
+
port_loop(Port);
|
31
|
+
|
32
|
+
stop ->
|
33
|
+
Port ! {self(), close},
|
34
|
+
receive
|
35
|
+
{Port, closed} -> exit(normal)
|
36
|
+
end;
|
37
|
+
|
38
|
+
{'EXIT', Port, Reason} ->
|
39
|
+
exit({port_terminated,Reason})
|
40
|
+
end.
|
41
|
+
|
@@ -0,0 +1,23 @@
|
|
1
|
+
$:.unshift(File.dirname(__FILE__) + "/../../lib/")
|
2
|
+
require 'rubygems'
|
3
|
+
require 'erlectricity'
|
4
|
+
require 'tinder'
|
5
|
+
|
6
|
+
domain, email, password, room_name = *ARGV
|
7
|
+
campfire = Tinder::Campfire.new domain
|
8
|
+
campfire.login email, password
|
9
|
+
room = campfire.find_room_by_name room_name
|
10
|
+
|
11
|
+
receive do
|
12
|
+
match(:speak, any(:comment)) do
|
13
|
+
room.speak comment
|
14
|
+
receive_loop
|
15
|
+
end
|
16
|
+
|
17
|
+
match(:paste, any(:comment)) do
|
18
|
+
room.paste comment
|
19
|
+
receive_loop
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
room.leave if room
|
@@ -0,0 +1,48 @@
|
|
1
|
+
module Erlectricity
|
2
|
+
class Condition
|
3
|
+
attr_accessor :binding_name
|
4
|
+
|
5
|
+
def initialize(binding_name=nil)
|
6
|
+
self.binding_name = binding_name
|
7
|
+
end
|
8
|
+
|
9
|
+
def bindings_for(arg)
|
10
|
+
{}
|
11
|
+
end
|
12
|
+
|
13
|
+
def satisfies?(arg)
|
14
|
+
false
|
15
|
+
end
|
16
|
+
|
17
|
+
end
|
18
|
+
|
19
|
+
module Conditions
|
20
|
+
def atom(name=nil)
|
21
|
+
TypeCondition.new(Symbol, name)
|
22
|
+
end
|
23
|
+
|
24
|
+
def any(name=nil)
|
25
|
+
TypeCondition.new(Object, name)
|
26
|
+
end
|
27
|
+
|
28
|
+
def number(name=nil)
|
29
|
+
TypeCondition.new(Fixnum, name)
|
30
|
+
end
|
31
|
+
|
32
|
+
def pid(name=nil)
|
33
|
+
TypeCondition.new(Erlectricity::Pid, name)
|
34
|
+
end
|
35
|
+
|
36
|
+
def string(name=nil)
|
37
|
+
TypeCondition.new(String, name)
|
38
|
+
end
|
39
|
+
|
40
|
+
def list(name=nil)
|
41
|
+
TypeCondition.new(Array, name)
|
42
|
+
end
|
43
|
+
|
44
|
+
def hash(name=nil)
|
45
|
+
HashCondition.new(name)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module Erlectricity
|
2
|
+
class HashCondition < Condition
|
3
|
+
|
4
|
+
def satisfies?(arg)
|
5
|
+
return false unless arg.class == Array
|
6
|
+
arg.all?{|x| x.class == Array && x.length == 2}
|
7
|
+
end
|
8
|
+
|
9
|
+
def bindings_for(arg)
|
10
|
+
return {} unless self.binding_name
|
11
|
+
flattened = arg.inject([]){|memo, kv| memo + kv}
|
12
|
+
{self.binding_name => Hash[*flattened]}
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module Erlectricity
|
2
|
+
class StaticCondition < Condition
|
3
|
+
attr_accessor :value
|
4
|
+
def initialize(value, name=nil)
|
5
|
+
self.value = value
|
6
|
+
super(name)
|
7
|
+
end
|
8
|
+
|
9
|
+
def satisfies?(arg)
|
10
|
+
arg.eql? value
|
11
|
+
end
|
12
|
+
|
13
|
+
def bindings_for(arg)
|
14
|
+
{}
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module Erlectricity
|
2
|
+
class TypeCondition < Condition
|
3
|
+
attr_accessor :type
|
4
|
+
|
5
|
+
def initialize(type, name=nil)
|
6
|
+
self.type = type
|
7
|
+
super(name)
|
8
|
+
end
|
9
|
+
|
10
|
+
def satisfies?(arg)
|
11
|
+
arg.is_a? self.type
|
12
|
+
end
|
13
|
+
|
14
|
+
def bindings_for(arg)
|
15
|
+
return {} unless self.binding_name
|
16
|
+
{self.binding_name => arg}
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
module Erlectricity
|
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
|
+
|
37
|
+
end
|
@@ -0,0 +1,202 @@
|
|
1
|
+
module Erlectricity
|
2
|
+
class Decoder
|
3
|
+
attr_accessor :in
|
4
|
+
include Erlectricity::External::Types
|
5
|
+
|
6
|
+
def self.read_any_from(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 == Erlectricity::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
|
+
read_string(length).to_sym
|
96
|
+
end
|
97
|
+
|
98
|
+
def read_small_int
|
99
|
+
fail("Invalid Type, not a small int") unless read_1 == SMALL_INT
|
100
|
+
read_1
|
101
|
+
end
|
102
|
+
|
103
|
+
def read_int
|
104
|
+
fail("Invalid Type, not an int") unless read_1 == INT
|
105
|
+
value = read_4
|
106
|
+
negative = (value >> 31)[0] == 1
|
107
|
+
value = (value - (1 << 32)) if negative
|
108
|
+
value = Fixnum.induced_from(value)
|
109
|
+
end
|
110
|
+
|
111
|
+
def read_small_bignum
|
112
|
+
fail("Invalid Type, not a small bignum") unless read_1 == SMALL_BIGNUM
|
113
|
+
size = read_1
|
114
|
+
sign = read_1
|
115
|
+
bytes = read_string(size).unpack("C" * size)
|
116
|
+
added = bytes.zip((0..bytes.length).to_a).inject(0) do |result, byte_index|
|
117
|
+
byte, index = *byte_index
|
118
|
+
value = (byte * (256 ** index))
|
119
|
+
sign != 0 ? (result - value) : (result + value)
|
120
|
+
end
|
121
|
+
Bignum.induced_from(added)
|
122
|
+
end
|
123
|
+
|
124
|
+
def read_large_bignum
|
125
|
+
fail("Invalid Type, not a large bignum") unless read_1 == LARGE_BIGNUM
|
126
|
+
size = read_4
|
127
|
+
sign = read_1
|
128
|
+
bytes = read_string(size).unpack("C" * size)
|
129
|
+
added = bytes.zip((0..bytes.length).to_a).inject(0) do |result, byte_index|
|
130
|
+
byte, index = *byte_index
|
131
|
+
value = (byte * (256 ** index))
|
132
|
+
sign != 0 ? (result - value) : (result + value)
|
133
|
+
end
|
134
|
+
Bignum.induced_from(added)
|
135
|
+
end
|
136
|
+
|
137
|
+
def read_float
|
138
|
+
fail("Invalid Type, not a float") unless read_1 == FLOAT
|
139
|
+
string_value = read_string(31)
|
140
|
+
result = string_value.to_f
|
141
|
+
end
|
142
|
+
|
143
|
+
def read_new_reference
|
144
|
+
fail("Invalid Type, not a new-style reference") unless read_1 == NEW_REF
|
145
|
+
size = read_2
|
146
|
+
node = read_atom
|
147
|
+
creation = read_1
|
148
|
+
id = (0...size).map{|i| read_4 }
|
149
|
+
NewReference.new(node, creation, id)
|
150
|
+
end
|
151
|
+
|
152
|
+
def read_pid
|
153
|
+
fail("Invalid Type, not a pid") unless read_1 == PID
|
154
|
+
node = read_atom
|
155
|
+
id = read_4
|
156
|
+
serial = read_4
|
157
|
+
creation = read_1
|
158
|
+
Pid.new(node, id, serial, creation)
|
159
|
+
end
|
160
|
+
|
161
|
+
def read_small_tuple
|
162
|
+
fail("Invalid Type, not a small tuple") unless read_1 == SMALL_TUPLE
|
163
|
+
arity = read_1
|
164
|
+
|
165
|
+
(0...arity).map{|i| read_any_raw }
|
166
|
+
end
|
167
|
+
|
168
|
+
def read_large_tuple
|
169
|
+
fail("Invalid Type, not a small tuple") unless read_1 == LARGE_TUPLE
|
170
|
+
arity = read_4
|
171
|
+
(0...arity).map{|i| read_any_raw}
|
172
|
+
end
|
173
|
+
|
174
|
+
def read_nil
|
175
|
+
fail("Invalid Type, not a nil list") unless read_1 == NIL
|
176
|
+
[]
|
177
|
+
end
|
178
|
+
|
179
|
+
def read_erl_string
|
180
|
+
fail("Invalid Type, not an erlang string") unless read_1 == STRING
|
181
|
+
length = read_2
|
182
|
+
read_string(length).unpack('C' * length)
|
183
|
+
end
|
184
|
+
|
185
|
+
def read_list
|
186
|
+
fail("Invalid Type, not an erlang list") unless read_1 == LIST
|
187
|
+
length = read_4
|
188
|
+
(0...length).map{|i| read_any_raw}
|
189
|
+
end
|
190
|
+
|
191
|
+
def read_bin
|
192
|
+
fail("Invalid Type, not an erlang binary") unless read_1 == BIN
|
193
|
+
length = read_4
|
194
|
+
read_string(length)
|
195
|
+
end
|
196
|
+
|
197
|
+
def fail(str)
|
198
|
+
raise DecodeError, str
|
199
|
+
end
|
200
|
+
|
201
|
+
end
|
202
|
+
end
|