bert 1.0.0 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore CHANGED
@@ -3,3 +3,6 @@
3
3
  coverage
4
4
  rdoc
5
5
  pkg
6
+ ext/bert/c/Makefile
7
+ ext/bert/c/*.bundle
8
+ ext/bert/c/*.o
@@ -1,3 +1,12 @@
1
+ = 1.1.0 / 2009-10-27
2
+ * Major Changes
3
+ * Remove reliance on Erlectricity.
4
+ * Bug fixes
5
+ * Fix unsigned int problem in C decoder
6
+ * Fix stack overflow segfault in C binary decoder for > 8MB binaries
7
+ * Optimize C bytelist decoder
8
+ * Fix bignum encoding
9
+
1
10
  = 1.0.0 / 2009-10-19
2
11
  * No changes. Production ready!
3
12
 
data/README.md CHANGED
@@ -1,13 +1,14 @@
1
1
  BERT
2
2
  ====
3
3
 
4
- BERT is a BERT (Binary ERlang Term) serialization library for Ruby. It can
4
+ A BERT (Binary ERlang Term) serialization library for Ruby. It can
5
5
  encode Ruby objects into BERT format and decode BERT binaries into Ruby
6
6
  objects.
7
7
 
8
8
  See the BERT specification at [bert-rpc.org](http://bert-rpc.org).
9
9
 
10
- Instances of the following Ruby classes will be automatically converted to the proper simple BERT type:
10
+ Instances of the following Ruby classes will be automatically converted to the
11
+ proper simple BERT type:
11
12
 
12
13
  * Fixnum
13
14
  * Float
@@ -15,7 +16,8 @@ Instances of the following Ruby classes will be automatically converted to the p
15
16
  * Array
16
17
  * String
17
18
 
18
- Instances of the following Ruby classes will be automatically converted to the proper complex BERT type:
19
+ Instances of the following Ruby classes will be automatically converted to the
20
+ proper complex BERT type:
19
21
 
20
22
  * NilClass
21
23
  * TrueClass
@@ -24,10 +26,11 @@ Instances of the following Ruby classes will be automatically converted to the p
24
26
  * Time
25
27
  * Regexp
26
28
 
27
- To designate tuples, simply prefix an Array literal with a `t` or use the BERT::Tuple class:
29
+ To designate tuples, simply prefix an Array literal with a `t` or use the
30
+ BERT::Tuple class:
28
31
 
29
32
  t[:foo, [1, 2, 3]]
30
- BERT::Tuple.new([:foo, [1, 2, 3]])
33
+ BERT::Tuple[:foo, [1, 2, 3]]
31
34
 
32
35
  Both of these will be converted to (in Erlang syntax):
33
36
 
data/Rakefile CHANGED
@@ -10,8 +10,10 @@ begin
10
10
  gem.email = "tom@mojombo.com"
11
11
  gem.homepage = "http://github.com/mojombo/bert"
12
12
  gem.authors = ["Tom Preston-Werner"]
13
- gem.add_dependency('erlectricity', '>= 1.1.0')
14
13
  gem.add_development_dependency("thoughtbot-shoulda")
14
+ gem.require_paths = ["lib", "ext"]
15
+ gem.files.include("ext")
16
+ gem.extensions << 'ext/bert/c/extconf.rb'
15
17
  # gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
16
18
  end
17
19
  rescue LoadError
@@ -19,12 +21,42 @@ rescue LoadError
19
21
  end
20
22
 
21
23
  require 'rake/testtask'
22
- Rake::TestTask.new(:test) do |test|
24
+ Rake::TestTask.new(:runtests) do |test|
23
25
  test.libs << 'lib' << 'test'
24
26
  test.pattern = 'test/**/*_test.rb'
25
27
  test.verbose = true
26
28
  end
27
29
 
30
+ task :make do
31
+ Dir.chdir('ext/bert/c') { `make` }
32
+ end
33
+
34
+ task :clean do
35
+ ['rm -f ext/bert/c/*.bundle', 'rm -f ext/bert/c/*.o'].each do |cmd|
36
+ `#{cmd}` && puts(cmd)
37
+ end
38
+ end
39
+
40
+ task :test => :check_dependencies do
41
+ require 'fileutils'
42
+
43
+ puts "\nCleaning extension build files and running all specs in native ruby mode..."
44
+ ['rm -f ext/bert/c/*.bundle', 'rm -f ext/bert/c/*.o'].each do |cmd|
45
+ `#{cmd}` && puts(cmd)
46
+ end
47
+ pid = fork do
48
+ exec 'rake runtests'
49
+ end
50
+ Process.waitpid(pid)
51
+
52
+ puts "\nRunning `make` to build extensions and rerunning decoder specs..."
53
+ Dir.chdir('ext/bert/c') { `make` }
54
+ pid = fork do
55
+ exec 'rake runtests'
56
+ end
57
+ Process.waitpid(pid)
58
+ end
59
+
28
60
  begin
29
61
  require 'rcov/rcovtask'
30
62
  Rcov::RcovTask.new do |test|
@@ -38,8 +70,6 @@ rescue LoadError
38
70
  end
39
71
  end
40
72
 
41
- task :test => :check_dependencies
42
-
43
73
  task :default => :test
44
74
 
45
75
  require 'rake/rdoctask'
data/VERSION CHANGED
@@ -1 +1 @@
1
- 1.0.0
1
+ 1.1.0
@@ -0,0 +1,36 @@
1
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
2
+
3
+ require 'rubygems'
4
+ require 'bert'
5
+ require 'json'
6
+ require 'yajl'
7
+ require 'benchmark'
8
+
9
+ ITER = 1_000
10
+
11
+ tiny = t[:ok, :awesome]
12
+ small = t[:ok, :answers, [42] * 42]
13
+ large = ["abc" * 1000] * 100
14
+ complex = [42, {:foo => 'bac' * 100}, t[(1..100).to_a]] * 10
15
+
16
+ Benchmark.bm do |bench|
17
+ bench.report("BERT tiny") {ITER.times {BERT.decode(BERT.encode(tiny))}}
18
+ bench.report("BERT small") {ITER.times {BERT.decode(BERT.encode(small))}}
19
+ bench.report("BERT large") {ITER.times {BERT.decode(BERT.encode(large))}}
20
+ bench.report("BERT complex") {ITER.times {BERT.decode(BERT.encode(complex))}}
21
+
22
+ bench.report("JSON tiny") {ITER.times {JSON.load(JSON.dump(tiny))}}
23
+ bench.report("JSON small") {ITER.times {JSON.load(JSON.dump(small))}}
24
+ bench.report("JSON large") {ITER.times {JSON.load(JSON.dump(large))}}
25
+ bench.report("JSON complex") {ITER.times {JSON.load(JSON.dump(complex))}}
26
+
27
+ bench.report("YAJL tiny") {ITER.times {Yajl::Parser.parse(Yajl::Encoder.encode(tiny))}}
28
+ bench.report("YAJL small") {ITER.times {Yajl::Parser.parse(Yajl::Encoder.encode(small))}}
29
+ bench.report("YAJL large") {ITER.times {Yajl::Parser.parse(Yajl::Encoder.encode(large))}}
30
+ bench.report("YAJL complex") {ITER.times {Yajl::Parser.parse(Yajl::Encoder.encode(complex))}}
31
+
32
+ bench.report("Ruby tiny") {ITER.times {Marshal.load(Marshal.dump(tiny))}}
33
+ bench.report("Ruby small") {ITER.times {Marshal.load(Marshal.dump(small))}}
34
+ bench.report("Ruby large") {ITER.times {Marshal.load(Marshal.dump(large))}}
35
+ bench.report("Ruby complex") {ITER.times {Marshal.load(Marshal.dump(complex))}}
36
+ end
@@ -0,0 +1,87 @@
1
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
2
+
3
+ require 'rubygems'
4
+ require 'json'
5
+ require 'yajl'
6
+ require 'benchmark'
7
+
8
+ ITER = 1_000
9
+
10
+ def setup
11
+ tiny = t[:ok, :awesome]
12
+ small = t[:ok, :answers, [42] * 42]
13
+ large = ["abc" * 1000] * 100
14
+ complex = [42, {:foo => 'bac' * 100}, t[(1..100).to_a]] * 10
15
+
16
+ $tiny_encoded_bert = BERT.encode(tiny)
17
+ $small_encoded_bert = BERT.encode(small)
18
+ $large_encoded_bert = BERT.encode(large)
19
+ $complex_encoded_bert = BERT.encode(complex)
20
+
21
+ $tiny_encoded_json = JSON.dump(tiny)
22
+ $small_encoded_json = JSON.dump(small)
23
+ $large_encoded_json = JSON.dump(large)
24
+ $complex_encoded_json = JSON.dump(complex)
25
+
26
+ $tiny_encoded_yajl = Yajl::Encoder.encode(tiny)
27
+ $small_encoded_yajl = Yajl::Encoder.encode(small)
28
+ $large_encoded_yajl = Yajl::Encoder.encode(large)
29
+ $complex_encoded_yajl = Yajl::Encoder.encode(complex)
30
+
31
+ $tiny_encoded_ruby = Marshal.dump(tiny)
32
+ $small_encoded_ruby = Marshal.dump(small)
33
+ $large_encoded_ruby = Marshal.dump(large)
34
+ $complex_encoded_ruby = Marshal.dump(complex)
35
+ end
36
+
37
+ Benchmark.bm(13) do |bench|
38
+ pid = fork do
39
+ Dir.chdir(File.join(File.dirname(__FILE__), *%w[.. ext bert c])) { `make` }
40
+ require 'bert'
41
+ raise "Could not load C extension" unless BERT::Decode.impl == 'C'
42
+ setup
43
+ puts "BERT C Extension Decoder"
44
+ bench.report("BERT tiny") {ITER.times {BERT.decode($tiny_encoded_bert)}}
45
+ bench.report("BERT small") {ITER.times {BERT.decode($small_encoded_bert)}}
46
+ bench.report("BERT large") {ITER.times {BERT.decode($large_encoded_bert)}}
47
+ bench.report("BERT complex") {ITER.times {BERT.decode($complex_encoded_bert)}}
48
+ puts
49
+ end
50
+ Process.waitpid(pid)
51
+
52
+ pid = fork do
53
+ Dir.chdir(File.join(File.dirname(__FILE__), *%w[.. ext bert c])) do
54
+ ['*.bundle', '*.o'].each { |pat| `rm -f #{pat}` }
55
+ end
56
+ require 'bert'
57
+ raise "Not using Ruby decoder" unless BERT::Decode.impl == 'Ruby'
58
+ setup
59
+ puts "BERT Pure Ruby Decoder"
60
+ bench.report("BERT tiny") {ITER.times {BERT.decode($tiny_encoded_bert)}}
61
+ bench.report("BERT small") {ITER.times {BERT.decode($small_encoded_bert)}}
62
+ bench.report("BERT large") {ITER.times {BERT.decode($large_encoded_bert)}}
63
+ bench.report("BERT complex") {ITER.times {BERT.decode($complex_encoded_bert)}}
64
+ puts
65
+ end
66
+ Process.waitpid(pid)
67
+
68
+ require 'bert'
69
+ setup
70
+
71
+ bench.report("JSON tiny") {ITER.times {JSON.load($tiny_encoded_json)}}
72
+ bench.report("JSON small") {ITER.times {JSON.load($small_encoded_json)}}
73
+ bench.report("JSON large") {ITER.times {JSON.load($large_encoded_json)}}
74
+ bench.report("JSON complex") {ITER.times {JSON.load($complex_encoded_json)}}
75
+ puts
76
+
77
+ bench.report("YAJL tiny") {ITER.times {Yajl::Parser.parse($tiny_encoded_yajl)}}
78
+ bench.report("YAJL small") {ITER.times {Yajl::Parser.parse($small_encoded_yajl)}}
79
+ bench.report("YAJL large") {ITER.times {Yajl::Parser.parse($large_encoded_yajl)}}
80
+ bench.report("YAJL complex") {ITER.times {Yajl::Parser.parse($complex_encoded_yajl)}}
81
+ puts
82
+
83
+ bench.report("Ruby tiny") {ITER.times {Marshal.load($tiny_encoded_ruby)}}
84
+ bench.report("Ruby small") {ITER.times {Marshal.load($small_encoded_ruby)}}
85
+ bench.report("Ruby large") {ITER.times {Marshal.load($large_encoded_ruby)}}
86
+ bench.report("Ruby complex") {ITER.times {Marshal.load($complex_encoded_ruby)}}
87
+ end
@@ -0,0 +1,36 @@
1
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
2
+
3
+ require 'rubygems'
4
+ require 'bert'
5
+ require 'json'
6
+ require 'yajl'
7
+ require 'benchmark'
8
+
9
+ ITER = 1_000
10
+
11
+ tiny = t[:ok, :awesome]
12
+ small = t[:ok, :answers, [42] * 42]
13
+ large = ["abc" * 1000] * 100
14
+ complex = [42, {:foo => 'bac' * 100}, t[(1..100).to_a]] * 10
15
+
16
+ Benchmark.bm(13) do |bench|
17
+ bench.report("BERT tiny") {ITER.times {BERT.encode(tiny)}}
18
+ bench.report("BERT small") {ITER.times {BERT.encode(small)}}
19
+ bench.report("BERT large") {ITER.times {BERT.encode(large)}}
20
+ bench.report("BERT complex") {ITER.times {BERT.encode(complex)}}
21
+ puts
22
+ bench.report("JSON tiny") {ITER.times {JSON.dump(tiny)}}
23
+ bench.report("JSON small") {ITER.times {JSON.dump(small)}}
24
+ bench.report("JSON large") {ITER.times {JSON.dump(large)}}
25
+ bench.report("JSON complex") {ITER.times {JSON.dump(complex)}}
26
+ puts
27
+ bench.report("JSON tiny") {ITER.times {Yajl::Encoder.encode(tiny)}}
28
+ bench.report("JSON small") {ITER.times {Yajl::Encoder.encode(small)}}
29
+ bench.report("JSON large") {ITER.times {Yajl::Encoder.encode(large)}}
30
+ bench.report("JSON complex") {ITER.times {Yajl::Encoder.encode(complex)}}
31
+ puts
32
+ bench.report("Ruby tiny") {ITER.times {Marshal.dump(tiny)}}
33
+ bench.report("Ruby small") {ITER.times {Marshal.dump(small)}}
34
+ bench.report("Ruby large") {ITER.times {Marshal.dump(large)}}
35
+ bench.report("Ruby complex") {ITER.times {Marshal.dump(complex)}}
36
+ end
@@ -0,0 +1,55 @@
1
+ user system total real
2
+
3
+ Comparisons
4
+
5
+ JSON tiny 0.020000 0.000000 0.020000 ( 0.017486)
6
+ JSON small 0.070000 0.000000 0.070000 ( 0.080681)
7
+ JSON large 15.260000 0.600000 15.860000 ( 16.427857)
8
+ JSON complex 1.470000 0.010000 1.480000 ( 1.558230)
9
+
10
+ YAJL tiny 0.010000 0.000000 0.010000 ( 0.015537)
11
+ YAJL small 0.050000 0.000000 0.050000 ( 0.061879)
12
+ YAJL large 3.610000 0.800000 4.410000 ( 4.675255)
13
+ YAJL complex 1.030000 0.000000 1.030000 ( 1.066871)
14
+
15
+ Ruby tiny 0.010000 0.000000 0.010000 ( 0.007117)
16
+ Ruby small 0.020000 0.000000 0.020000 ( 0.015964)
17
+ Ruby large 0.040000 0.000000 0.040000 ( 0.042695)
18
+ Ruby complex 0.040000 0.000000 0.040000 ( 0.048395)
19
+
20
+ Ruby encoder / Ruby decoder
21
+ 41503465479e8762916d6997d91639f0d7308a13
22
+
23
+ BERT tiny 0.090000 0.000000 0.090000 ( 0.092357)
24
+ BERT small 0.830000 0.000000 0.830000 ( 0.853270)
25
+ BERT large 4.190000 0.620000 4.810000 ( 4.959149)
26
+ BERT complex 19.380000 0.080000 19.460000 ( 20.402862)
27
+
28
+ Simple C decoder / Ruby encoder
29
+ 41503465479e8762916d6997d91639f0d7308a13
30
+
31
+ BERT tiny 0.030000 0.000000 0.030000 ( 0.033826)
32
+ BERT small 0.390000 0.010000 0.400000 ( 0.413229)
33
+ BERT large 2.270000 0.550000 2.820000 ( 3.029141)
34
+ BERT complex 8.680000 0.040000 8.720000 ( 9.097990)
35
+
36
+ Smarter Ruby decoder
37
+
38
+ BERT tiny 0.070000 0.000000 0.070000 ( 0.075155)
39
+ BERT small 0.810000 0.010000 0.820000 ( 0.831905)
40
+ BERT large 4.340000 0.600000 4.940000 ( 5.064875)
41
+ BERT complex 18.460000 0.070000 18.530000 ( 19.096184)
42
+
43
+ Smarter C decoder
44
+
45
+ BERT tiny 0.030000 0.000000 0.030000 ( 0.035685)
46
+ BERT small 0.350000 0.010000 0.360000 ( 0.358929)
47
+ BERT large 2.410000 0.560000 2.970000 ( 3.056593)
48
+ BERT complex 7.910000 0.040000 7.950000 ( 8.236641)
49
+
50
+ Smart C Decoder only
51
+
52
+ BERT tiny 0.000000 0.000000 0.000000 ( 0.001820)
53
+ BERT small 0.000000 0.000000 0.000000 ( 0.003859)
54
+ BERT large 0.430000 0.010000 0.440000 ( 0.499631)
55
+ BERT complex 0.080000 0.010000 0.090000 ( 0.086992)
@@ -5,13 +5,14 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = %q{bert}
8
- s.version = "1.0.0"
8
+ s.version = "1.1.0"
9
9
 
10
10
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
11
  s.authors = ["Tom Preston-Werner"]
12
- s.date = %q{2009-10-19}
12
+ s.date = %q{2009-10-28}
13
13
  s.description = %q{BERT Serializiation for Ruby}
14
14
  s.email = %q{tom@mojombo.com}
15
+ s.extensions = ["ext/bert/c/extconf.rb"]
15
16
  s.extra_rdoc_files = [
16
17
  "LICENSE",
17
18
  "README.md"
@@ -24,10 +25,20 @@ Gem::Specification.new do |s|
24
25
  "README.md",
25
26
  "Rakefile",
26
27
  "VERSION",
28
+ "bench/bench.rb",
29
+ "bench/decode_bench.rb",
30
+ "bench/encode_bench.rb",
31
+ "bench/results.txt",
27
32
  "bert.gemspec",
33
+ "ext/bert/c/decode.c",
34
+ "ext/bert/c/extconf.rb",
28
35
  "lib/bert.rb",
36
+ "lib/bert/bert.rb",
37
+ "lib/bert/decode.rb",
29
38
  "lib/bert/decoder.rb",
39
+ "lib/bert/encode.rb",
30
40
  "lib/bert/encoder.rb",
41
+ "lib/bert/types.rb",
31
42
  "test/bert_test.rb",
32
43
  "test/decoder_test.rb",
33
44
  "test/encoder_test.rb",
@@ -35,7 +46,7 @@ Gem::Specification.new do |s|
35
46
  ]
36
47
  s.homepage = %q{http://github.com/mojombo/bert}
37
48
  s.rdoc_options = ["--charset=UTF-8"]
38
- s.require_paths = ["lib"]
49
+ s.require_paths = ["lib", "ext"]
39
50
  s.rubygems_version = %q{1.3.5}
40
51
  s.summary = %q{BERT Serializiation for Ruby}
41
52
  s.test_files = [
@@ -50,14 +61,11 @@ Gem::Specification.new do |s|
50
61
  s.specification_version = 3
51
62
 
52
63
  if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
53
- s.add_runtime_dependency(%q<erlectricity>, [">= 1.1.0"])
54
64
  s.add_development_dependency(%q<thoughtbot-shoulda>, [">= 0"])
55
65
  else
56
- s.add_dependency(%q<erlectricity>, [">= 1.1.0"])
57
66
  s.add_dependency(%q<thoughtbot-shoulda>, [">= 0"])
58
67
  end
59
68
  else
60
- s.add_dependency(%q<erlectricity>, [">= 1.1.0"])
61
69
  s.add_dependency(%q<thoughtbot-shoulda>, [">= 0"])
62
70
  end
63
71
  end
@@ -0,0 +1,438 @@
1
+ #include "ruby.h"
2
+ #include <string.h>
3
+
4
+ #define ERL_VERSION 131
5
+ #define ERL_SMALL_INT 97
6
+ #define ERL_INT 98
7
+ #define ERL_SMALL_BIGNUM 110
8
+ #define ERL_LARGE_BIGNUM 111
9
+ #define ERL_FLOAT 99
10
+ #define ERL_ATOM 100
11
+ #define ERL_SMALL_TUPLE 104
12
+ #define ERL_LARGE_TUPLE 105
13
+ #define ERL_NIL 106
14
+ #define ERL_STRING 107
15
+ #define ERL_LIST 108
16
+ #define ERL_BIN 109
17
+
18
+ static VALUE mBERT;
19
+ static VALUE cDecode;
20
+ static VALUE cTuple;
21
+ void Init_decode();
22
+
23
+ VALUE method_decode(VALUE klass, VALUE rString);
24
+
25
+ VALUE read_any_raw(unsigned char **pData);
26
+
27
+ // printers
28
+
29
+ void p(VALUE val) {
30
+ rb_funcall(rb_mKernel, rb_intern("p"), 1, val);
31
+ }
32
+
33
+ // checkers
34
+
35
+ void check_int(int num) {
36
+ char buf[17];
37
+ sprintf(buf, "%u", num);
38
+ rb_raise(rb_eStandardError, buf);
39
+ }
40
+
41
+ void check_str(char *str) {
42
+ rb_raise(rb_eStandardError, str);
43
+ }
44
+
45
+ // string peekers/readers
46
+
47
+ unsigned int peek_1(unsigned char **pData) {
48
+ return (unsigned int) **pData;
49
+ }
50
+
51
+ unsigned int peek_2(unsigned char **pData) {
52
+ return (unsigned int) ((**pData << 8) + *(*pData + 1));
53
+ }
54
+
55
+ unsigned int peek_4(unsigned char **pData) {
56
+ return (unsigned int) ((**pData << 24) + (*(*pData + 1) << 16) + (*(*pData + 2) << 8) + *(*pData + 3));
57
+ }
58
+
59
+ unsigned int read_1(unsigned char **pData) {
60
+ unsigned int val = peek_1(pData);
61
+ *pData += 1;
62
+ return val;
63
+ }
64
+
65
+ unsigned int read_2(unsigned char **pData) {
66
+ unsigned int val = peek_2(pData);
67
+ *pData += 2;
68
+ return val;
69
+ }
70
+
71
+ unsigned int read_4(unsigned char **pData) {
72
+ unsigned int val = peek_4(pData);
73
+ *pData += 4;
74
+ return val;
75
+ }
76
+
77
+ // tuples
78
+
79
+ VALUE read_tuple(unsigned char **pData, unsigned int arity);
80
+
81
+ VALUE read_dict_pair(unsigned char **pData) {
82
+ if(read_1(pData) != ERL_SMALL_TUPLE) {
83
+ rb_raise(rb_eStandardError, "Invalid dict pair, not a small tuple");
84
+ }
85
+
86
+ int arity = read_1(pData);
87
+
88
+ if(arity != 2) {
89
+ rb_raise(rb_eStandardError, "Invalid dict pair, not a 2-tuple");
90
+ }
91
+
92
+ return read_tuple(pData, arity);
93
+ }
94
+
95
+ VALUE read_dict(unsigned char **pData) {
96
+ int type = read_1(pData);
97
+ if(!(type == ERL_LIST || type == ERL_NIL)) {
98
+ rb_raise(rb_eStandardError, "Invalid dict spec, not an erlang list");
99
+ }
100
+
101
+ unsigned int length = 0;
102
+ if(type == ERL_LIST) {
103
+ length = read_4(pData);
104
+ }
105
+
106
+ VALUE cHash = rb_const_get(rb_cObject, rb_intern("Hash"));
107
+ VALUE hash = rb_funcall(cHash, rb_intern("new"), 0);
108
+
109
+ int i;
110
+ for(i = 0; i < length; ++i) {
111
+ VALUE pair = read_dict_pair(pData);
112
+ VALUE first = rb_ary_entry(pair, 0);
113
+ VALUE last = rb_ary_entry(pair, 1);
114
+ rb_funcall(hash, rb_intern("store"), 2, first, last);
115
+ }
116
+
117
+ if(type == ERL_LIST) {
118
+ read_1(pData);
119
+ }
120
+
121
+ return hash;
122
+ }
123
+
124
+ VALUE read_complex_type(unsigned char **pData, int arity) {
125
+ VALUE type = read_any_raw(pData);
126
+ ID id = SYM2ID(type);
127
+ if(id == rb_intern("nil")) {
128
+ return Qnil;
129
+ } else if(id == rb_intern("true")) {
130
+ return Qtrue;
131
+ } else if(id == rb_intern("false")) {
132
+ return Qfalse;
133
+ } else if(id == rb_intern("time")) {
134
+ VALUE megasecs = read_any_raw(pData);
135
+ VALUE msecs = rb_funcall(megasecs, rb_intern("*"), 1, INT2NUM(1000000));
136
+ VALUE secs = read_any_raw(pData);
137
+ VALUE microsecs = read_any_raw(pData);
138
+ VALUE stamp = rb_funcall(msecs, rb_intern("+"), 1, secs);
139
+ return rb_funcall(rb_cTime, rb_intern("at"), 2, stamp, microsecs);
140
+ } else if(id == rb_intern("regex")) {
141
+ VALUE source = read_any_raw(pData);
142
+ VALUE opts = read_any_raw(pData);
143
+ int flags = 0;
144
+ if(rb_ary_includes(opts, ID2SYM(rb_intern("caseless"))))
145
+ flags = flags | 1;
146
+ if(rb_ary_includes(opts, ID2SYM(rb_intern("extended"))))
147
+ flags = flags | 2;
148
+ if(rb_ary_includes(opts, ID2SYM(rb_intern("multiline"))))
149
+ flags = flags | 4;
150
+ return rb_funcall(rb_cRegexp, rb_intern("new"), 2, source, INT2NUM(flags));
151
+ } else if(id == rb_intern("dict")) {
152
+ return read_dict(pData);
153
+ } else {
154
+ return Qnil;
155
+ }
156
+ }
157
+
158
+ VALUE read_tuple(unsigned char **pData, unsigned int arity) {
159
+ if(arity > 0) {
160
+ VALUE tag = read_any_raw(pData);
161
+ if(SYM2ID(tag) == rb_intern("bert")) {
162
+ return read_complex_type(pData, arity);
163
+ } else {
164
+ VALUE tuple = rb_funcall(cTuple, rb_intern("new"), 1, INT2NUM(arity));
165
+ rb_ary_store(tuple, 0, tag);
166
+ int i;
167
+ for(i = 1; i < arity; ++i) {
168
+ rb_ary_store(tuple, i, read_any_raw(pData));
169
+ }
170
+ return tuple;
171
+ }
172
+ } else {
173
+ return rb_funcall(cTuple, rb_intern("new"), 0);
174
+ }
175
+ }
176
+
177
+ VALUE read_small_tuple(unsigned char **pData) {
178
+ if(read_1(pData) != ERL_SMALL_TUPLE) {
179
+ rb_raise(rb_eStandardError, "Invalid Type, not a small tuple");
180
+ }
181
+
182
+ int arity = read_1(pData);
183
+ return read_tuple(pData, arity);
184
+ }
185
+
186
+ VALUE read_large_tuple(unsigned char **pData) {
187
+ if(read_1(pData) != ERL_LARGE_TUPLE) {
188
+ rb_raise(rb_eStandardError, "Invalid Type, not a large tuple");
189
+ }
190
+
191
+ unsigned int arity = read_4(pData);
192
+ return read_tuple(pData, arity);
193
+ }
194
+
195
+ // lists
196
+
197
+ VALUE read_list(unsigned char **pData) {
198
+ if(read_1(pData) != ERL_LIST) {
199
+ rb_raise(rb_eStandardError, "Invalid Type, not an erlang list");
200
+ }
201
+
202
+ unsigned int size = read_4(pData);
203
+
204
+ VALUE array = rb_ary_new2(size);
205
+
206
+ int i;
207
+ for(i = 0; i < size; ++i) {
208
+ rb_ary_store(array, i, read_any_raw(pData));
209
+ }
210
+
211
+ read_1(pData);
212
+
213
+ return array;
214
+ }
215
+
216
+ // primitives
217
+
218
+ void read_string_raw(unsigned char *dest, unsigned char **pData, unsigned int length) {
219
+ memcpy((char *) dest, (char *) *pData, length);
220
+ *(dest + length) = (unsigned char) 0;
221
+ *pData += length;
222
+ }
223
+
224
+ VALUE read_bin(unsigned char **pData) {
225
+ if(read_1(pData) != ERL_BIN) {
226
+ rb_raise(rb_eStandardError, "Invalid Type, not an erlang binary");
227
+ }
228
+
229
+ unsigned int length = read_4(pData);
230
+
231
+ VALUE rStr = rb_str_new((char *) *pData, length);
232
+ *pData += length;
233
+
234
+ return rStr;
235
+ }
236
+
237
+ VALUE read_string(unsigned char **pData) {
238
+ if(read_1(pData) != ERL_STRING) {
239
+ rb_raise(rb_eStandardError, "Invalid Type, not an erlang string");
240
+ }
241
+
242
+ int length = read_2(pData);
243
+ VALUE array = rb_ary_new2(length);
244
+
245
+ int i = 0;
246
+ for(i; i < length; ++i) {
247
+ rb_ary_store(array, i, INT2NUM(**pData));
248
+ *pData += 1;
249
+ }
250
+
251
+ return array;
252
+ }
253
+
254
+ VALUE read_atom(unsigned char **pData) {
255
+ if(read_1(pData) != ERL_ATOM) {
256
+ rb_raise(rb_eStandardError, "Invalid Type, not an atom");
257
+ }
258
+
259
+ int length = read_2(pData);
260
+
261
+ unsigned char buf[length + 1];
262
+ read_string_raw(buf, pData, length);
263
+
264
+ return ID2SYM(rb_intern((char *) buf));
265
+ }
266
+
267
+ VALUE read_small_int(unsigned char **pData) {
268
+ if(read_1(pData) != ERL_SMALL_INT) {
269
+ rb_raise(rb_eStandardError, "Invalid Type, not a small int");
270
+ }
271
+
272
+ int value = read_1(pData);
273
+
274
+ return INT2FIX(value);
275
+ }
276
+
277
+ VALUE read_int(unsigned char **pData) {
278
+ if(read_1(pData) != ERL_INT) {
279
+ rb_raise(rb_eStandardError, "Invalid Type, not an int");
280
+ }
281
+
282
+ long long value = read_4(pData);
283
+
284
+ long long negative = ((value >> 31) & 0x1 == 1);
285
+
286
+ if(negative) {
287
+ value = (value - ((long long) 1 << 32));
288
+ }
289
+
290
+ return INT2FIX(value);
291
+ }
292
+
293
+ VALUE read_small_bignum(unsigned char **pData) {
294
+ if(read_1(pData) != ERL_SMALL_BIGNUM) {
295
+ rb_raise(rb_eStandardError, "Invalid Type, not a small bignum");
296
+ }
297
+
298
+ unsigned int size = read_1(pData);
299
+ unsigned int sign = read_1(pData);
300
+
301
+ VALUE num = INT2NUM(0);
302
+ VALUE tmp;
303
+
304
+ unsigned char buf[size + 1];
305
+ read_string_raw(buf, pData, size);
306
+
307
+ int i;
308
+ for(i = 0; i < size; ++i) {
309
+ tmp = INT2FIX(*(buf + i));
310
+ tmp = rb_funcall(tmp, rb_intern("<<"), 1, INT2NUM(i * 8));
311
+ num = rb_funcall(num, rb_intern("+"), 1, tmp);
312
+ }
313
+
314
+ if(sign) {
315
+ num = rb_funcall(num, rb_intern("*"), 1, INT2NUM(-1));
316
+ }
317
+
318
+ return num;
319
+ }
320
+
321
+ VALUE read_large_bignum(unsigned char **pData) {
322
+ if(read_1(pData) != ERL_LARGE_BIGNUM) {
323
+ rb_raise(rb_eStandardError, "Invalid Type, not a small bignum");
324
+ }
325
+
326
+ unsigned int size = read_4(pData);
327
+ unsigned int sign = read_1(pData);
328
+
329
+ VALUE num = INT2NUM(0);
330
+ VALUE tmp;
331
+
332
+ unsigned char buf[size + 1];
333
+ read_string_raw(buf, pData, size);
334
+
335
+ int i;
336
+ for(i = 0; i < size; ++i) {
337
+ tmp = INT2FIX(*(buf + i));
338
+ tmp = rb_funcall(tmp, rb_intern("<<"), 1, INT2NUM(i * 8));
339
+
340
+ num = rb_funcall(num, rb_intern("+"), 1, tmp);
341
+ }
342
+
343
+ if(sign) {
344
+ num = rb_funcall(num, rb_intern("*"), 1, INT2NUM(-1));
345
+ }
346
+
347
+ return num;
348
+ }
349
+
350
+ VALUE read_float(unsigned char **pData) {
351
+ if(read_1(pData) != ERL_FLOAT) {
352
+ rb_raise(rb_eStandardError, "Invalid Type, not a float");
353
+ }
354
+
355
+ unsigned char buf[32];
356
+ read_string_raw(buf, pData, 31);
357
+
358
+ VALUE rString = rb_str_new2((char *) buf);
359
+
360
+ return rb_funcall(rString, rb_intern("to_f"), 0);
361
+ }
362
+
363
+ VALUE read_nil(unsigned char **pData) {
364
+ if(read_1(pData) != ERL_NIL) {
365
+ rb_raise(rb_eStandardError, "Invalid Type, not a nil list");
366
+ }
367
+
368
+ return rb_ary_new2(0);
369
+ }
370
+
371
+ // read_any_raw
372
+
373
+ VALUE read_any_raw(unsigned char **pData) {
374
+ switch(peek_1(pData)) {
375
+ case ERL_SMALL_INT:
376
+ return read_small_int(pData);
377
+ break;
378
+ case ERL_INT:
379
+ return read_int(pData);
380
+ break;
381
+ case ERL_FLOAT:
382
+ return read_float(pData);
383
+ break;
384
+ case ERL_ATOM:
385
+ return read_atom(pData);
386
+ break;
387
+ case ERL_SMALL_TUPLE:
388
+ return read_small_tuple(pData);
389
+ break;
390
+ case ERL_LARGE_TUPLE:
391
+ return read_large_tuple(pData);
392
+ break;
393
+ case ERL_NIL:
394
+ return read_nil(pData);
395
+ break;
396
+ case ERL_STRING:
397
+ return read_string(pData);
398
+ break;
399
+ case ERL_LIST:
400
+ return read_list(pData);
401
+ break;
402
+ case ERL_BIN:
403
+ return read_bin(pData);
404
+ break;
405
+ case ERL_SMALL_BIGNUM:
406
+ return read_small_bignum(pData);
407
+ break;
408
+ case ERL_LARGE_BIGNUM:
409
+ return read_large_bignum(pData);
410
+ break;
411
+ }
412
+ return Qnil;
413
+ }
414
+
415
+ VALUE method_decode(VALUE klass, VALUE rString) {
416
+ unsigned char *data = (unsigned char *) StringValuePtr(rString);
417
+
418
+ unsigned char **pData = &data;
419
+
420
+ // check protocol version
421
+ if(read_1(pData) != ERL_VERSION) {
422
+ rb_raise(rb_eStandardError, "Bad Magic");
423
+ }
424
+
425
+ return read_any_raw(pData);
426
+ }
427
+
428
+ VALUE method_impl(VALUE klass) {
429
+ return rb_str_new("C", 1);
430
+ }
431
+
432
+ void Init_decode() {
433
+ mBERT = rb_const_get(rb_cObject, rb_intern("BERT"));
434
+ cDecode = rb_define_class_under(mBERT, "Decode", rb_cObject);
435
+ cTuple = rb_const_get(mBERT, rb_intern("Tuple"));
436
+ rb_define_singleton_method(cDecode, "decode", method_decode, 1);
437
+ rb_define_singleton_method(cDecode, "impl", method_impl, 0);
438
+ }