bert 1.0.0 → 1.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/.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
+ }