File: decode.rb

package info (click to toggle)
ruby-bert 1.1.6-3
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 232 kB
  • sloc: ruby: 802; ansic: 345; makefile: 7
file content (250 lines) | stat: -rw-r--r-- 5,885 bytes parent folder | download | duplicates (3)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
module BERT
  class Decode
    attr_accessor :in
    include Types

    def self.impl
      'Ruby'
    end

    def self.decode(string)
      io = StringIO.new(string)
      io.set_encoding('binary') if io.respond_to?(:set_encoding)
      new(io).read_any
    end

    def initialize(ins)
      @in = ins
      @peeked = ""
    end

    def read_any
      fail("Bad Magic") unless read_1 == MAGIC
      read_any_raw
    end

    def read_any_raw
      case peek_1
        when ATOM then read_atom
        when SMALL_INT then read_small_int
        when INT then read_int
        when SMALL_BIGNUM then read_small_bignum
        when LARGE_BIGNUM then read_large_bignum
        when FLOAT then read_float
        when SMALL_TUPLE then read_small_tuple
        when LARGE_TUPLE then read_large_tuple
        when NIL then read_nil
        when STRING then read_erl_string
        when LIST then read_list
        when BIN then read_bin
        else
          fail("Unknown term tag: #{peek_1}")
      end
    end

    def read(length)
      if length < @peeked.length
        result = @peeked[0...length]
        @peeked = @peeked[length..-1]
        length = 0
      else
        result = @peeked
        @peeked = ''
        length -= result.length
      end

      if length > 0
        result << @in.read(length)
      end
      result
    end

    def peek(length)
      if length <= @peeked.length
        @peeked[0...length]
      else
        read_bytes = @in.read(length - @peeked.length)
        @peeked << read_bytes if read_bytes
        @peeked
      end
    end

    def peek_1
      peek(1).unpack("C").first
    end

    def peek_2
      peek(2).unpack("n").first
    end

    def read_1
      read(1).unpack("C").first
    end

    def read_2
      read(2).unpack("n").first
    end

    def read_4
      read(4).unpack("N").first
    end

    def read_string(length)
      read(length)
    end

    def read_atom
      fail("Invalid Type, not an atom") unless read_1 == ATOM
      length = read_2
      a = read_string(length)
      case a
        when ""
          Marshal.load("\004\b:\005") # Workaround for inability to do ''.to_sym
        else
          a.to_sym
      end
    end

    def read_small_int
      fail("Invalid Type, not a small int") unless read_1 == SMALL_INT
      read_1
    end

    def read_int
      fail("Invalid Type, not an int") unless read_1 == INT
      value = read_4
      negative = (value >> 31)[0] == 1
      value = (value - (1 << 32)) if negative
      value
    end

    def read_small_bignum
      fail("Invalid Type, not a small bignum") unless read_1 == SMALL_BIGNUM
      size = read_1
      sign = read_1
      bytes = read_string(size).unpack("C" * size)
      added = bytes.zip((0..bytes.length).to_a).inject(0) do |result, byte_index|
        byte, index = *byte_index
        value = (byte * (256 ** index))
        sign != 0 ? (result - value) : (result + value)
      end
      added
    end

    def read_large_bignum
      fail("Invalid Type, not a large bignum") unless read_1 == LARGE_BIGNUM
      size = read_4
      sign = read_1
      bytes = read_string(size).unpack("C" * size)
      added = bytes.zip((0..bytes.length).to_a).inject(0) do |result, byte_index|
        byte, index = *byte_index
        value = (byte * (256 ** index))
        sign != 0 ? (result - value) : (result + value)
      end
      added
    end

    def read_float
      fail("Invalid Type, not a float") unless read_1 == FLOAT
      string_value = read_string(31)
      result = string_value.to_f
    end

    def read_small_tuple
      fail("Invalid Type, not a small tuple") unless read_1 == SMALL_TUPLE
      read_tuple(read_1)
    end

    def read_large_tuple
      fail("Invalid Type, not a small tuple") unless read_1 == LARGE_TUPLE
      read_tuple(read_4)
    end

    def read_tuple(arity)
      if arity > 0
        tag = read_any_raw
        if tag == :bert
          read_complex_type(arity)
        else
          tuple = Tuple.new(arity)
          tuple[0] = tag
          (arity - 1).times { |i| tuple[i + 1] = read_any_raw }
          tuple
        end
      else
        Tuple.new
      end
    end

    def read_complex_type(arity)
      case read_any_raw
        when :nil
          nil
        when :true
          true
        when :false
          false
        when :time
          Time.at(read_any_raw * 1_000_000 + read_any_raw, read_any_raw)
        when :regex
          source = read_any_raw
          opts = read_any_raw
          options = 0
          options |= Regexp::EXTENDED if opts.include?(:extended)
          options |= Regexp::IGNORECASE if opts.include?(:caseless)
          options |= Regexp::MULTILINE if opts.include?(:multiline)
          Regexp.new(source, options)
        when :dict
          read_dict
        else
          nil
      end
    end

    def read_dict
      type = read_1
      fail("Invalid dict spec, not an erlang list") unless [LIST, NIL].include?(type)
      if type == LIST
        length = read_4
      else
        length = 0
      end
      hash = {}
      length.times do |i|
        pair = read_any_raw
        hash[pair[0]] = pair[1]
      end
      read_1 if type == LIST
      hash
    end

    def read_nil
      fail("Invalid Type, not a nil list") unless read_1 == NIL
      []
    end

    def read_erl_string
      fail("Invalid Type, not an erlang string") unless read_1 == STRING
      length = read_2
      read_string(length).unpack('C' * length)
    end

    def read_list
      fail("Invalid Type, not an erlang list") unless read_1 == LIST
      length = read_4
      list = (0...length).map { |i| read_any_raw }
      read_1
      list
    end

    def read_bin
      fail("Invalid Type, not an erlang binary") unless read_1 == BIN
      length = read_4
      read_string(length)
    end

    def fail(str)
      raise str
    end
  end
end