erlectricity 0.1.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.
- 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
|