erlang-terms 1.1.0 → 2.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (49) hide show
  1. checksums.yaml +4 -4
  2. data/.coveralls.yml +1 -0
  3. data/.editorconfig +20 -0
  4. data/.gitignore +10 -18
  5. data/.ruby-gemset +1 -0
  6. data/.ruby-version +1 -0
  7. data/.travis.yml +15 -3
  8. data/.yardopts +6 -0
  9. data/CHANGELOG.md +9 -0
  10. data/Gemfile +21 -1
  11. data/LICENSE.txt +1 -1
  12. data/README.md +95 -17
  13. data/Rakefile +8 -3
  14. data/erlang-terms.gemspec +14 -11
  15. data/lib/erlang-terms.rb +1 -0
  16. data/lib/erlang/associable.rb +98 -0
  17. data/lib/erlang/atom.rb +257 -0
  18. data/lib/erlang/binary.rb +425 -0
  19. data/lib/erlang/bitstring.rb +464 -0
  20. data/lib/erlang/cons.rb +122 -0
  21. data/lib/erlang/enumerable.rb +160 -0
  22. data/lib/erlang/error.rb +4 -0
  23. data/lib/erlang/export.rb +110 -12
  24. data/lib/erlang/float.rb +201 -0
  25. data/lib/erlang/function.rb +259 -0
  26. data/lib/erlang/immutable.rb +101 -0
  27. data/lib/erlang/list.rb +1685 -24
  28. data/lib/erlang/map.rb +935 -21
  29. data/lib/erlang/nil.rb +73 -10
  30. data/lib/erlang/pid.rb +120 -18
  31. data/lib/erlang/port.rb +123 -0
  32. data/lib/erlang/reference.rb +161 -0
  33. data/lib/erlang/string.rb +175 -3
  34. data/lib/erlang/term.rb +24 -0
  35. data/lib/erlang/terms.rb +324 -8
  36. data/lib/erlang/terms/version.rb +1 -1
  37. data/lib/erlang/trie.rb +364 -0
  38. data/lib/erlang/tuple.rb +1582 -14
  39. data/lib/erlang/undefined.rb +32 -0
  40. metadata +49 -71
  41. data/spec/erlang/export_spec.rb +0 -17
  42. data/spec/erlang/list_spec.rb +0 -39
  43. data/spec/erlang/map_spec.rb +0 -24
  44. data/spec/erlang/nil_spec.rb +0 -18
  45. data/spec/erlang/pid_spec.rb +0 -21
  46. data/spec/erlang/string_spec.rb +0 -11
  47. data/spec/erlang/terms_spec.rb +0 -7
  48. data/spec/erlang/tuple_spec.rb +0 -20
  49. data/spec/spec_helper.rb +0 -7
@@ -0,0 +1,464 @@
1
+ module Erlang
2
+ # A `Bitstring` is a series of bits.
3
+ #
4
+ # ### Creating Bitstrings
5
+ #
6
+ # Erlang::Bitstirng["test", bits: 7]
7
+ # # => Erlang::Bitstring[116, 101, 115, 4, bits: 3]
8
+ #
9
+ class Bitstring
10
+ include Erlang::Term
11
+ include Erlang::Immutable
12
+
13
+ # Return the data for this `Bitstring`
14
+ # @return [::String]
15
+ attr_reader :data
16
+
17
+ # Return the bits for this `Bitstring`
18
+ # @return [::Integer]
19
+ attr_reader :bits
20
+
21
+ class << self
22
+ # Create a new `Bitstring` populated with the given `data` and `bits`.
23
+ # @param data [::String, Symbol, ::Enumerable, Integer] The content of the `Binary`
24
+ # @param bits [Integer] The number of bits to keep on the last byte
25
+ # @return [Bitstring, Binary]
26
+ # @raise [ArgumentError] if `data` cannot be coerced to be a `::String` or `bits` is not between 1 and 8
27
+ def [](*data, bits: 8)
28
+ return EmptyBinary if data.empty?
29
+ raise ArgumentError, 'bits must be an Integer' if not bits.is_a?(::Integer)
30
+ raise ArgumentError, 'bits must be between 1 and 8' if bits < 1 or bits > 8
31
+ binary = Erlang.iolist_to_binary(data)
32
+ if bits == 8 or binary.empty?
33
+ return binary
34
+ else
35
+ return new(binary, bits)
36
+ end
37
+ end
38
+
39
+ def empty
40
+ return Erlang::EmptyBinary
41
+ end
42
+
43
+ # Compares `a` and `b` and returns whether they are less than,
44
+ # equal to, or greater than each other.
45
+ #
46
+ # @param a [Bitstring, Binary] The left argument
47
+ # @param b [Bitstring, Binary] The right argument
48
+ # @return [-1, 0, 1]
49
+ # @raise [ArgumentError] if `a` or `b` is not a `Bitstring` or `Binary`
50
+ def compare(a, b)
51
+ raise ArgumentError, "'a' must be of Erlang::Binary or Erlang::Bitstring type" if not a.kind_of?(Erlang::Binary) and not a.kind_of?(Erlang::Bitstring)
52
+ raise ArgumentError, "'b' must be of Erlang::Binary or Erlang::Bitstring type" if not b.kind_of?(Erlang::Binary) and not b.kind_of?(Erlang::Bitstring)
53
+ c = a.bitsize <=> b.bitsize
54
+ return a.data <=> b.data if c == 0
55
+ c = 0
56
+ i = 0
57
+ abytes = a.bytesize
58
+ bbytes = b.bytesize
59
+ abytes -= 1 if a.bits != 8
60
+ bbytes -= 1 if b.bits != 8
61
+ while c == 0 and i < abytes and i < bbytes
62
+ c = a.data.getbyte(i) <=> b.data.getbyte(i)
63
+ i += 1
64
+ end
65
+ if c == 0
66
+ if (a.bits != 8 and i == abytes and i < bbytes) or (b.bits != 8 and i == bbytes and i < abytes) or (a.bits != 8 and b.bits != 8)
67
+ abyte = a.data.getbyte(i)
68
+ bbyte = b.data.getbyte(i)
69
+ askip = 8 - a.bits
70
+ bskip = 8 - b.bits
71
+ i = 0
72
+ loop do
73
+ if i == a.bits and i == b.bits
74
+ c = 0
75
+ break
76
+ elsif i == a.bits
77
+ c = -1
78
+ break
79
+ elsif i == b.bits
80
+ c = 1
81
+ break
82
+ end
83
+ abit = (abyte >> (7 - ((i + askip) & 7))) & 1
84
+ bbit = (bbyte >> (7 - ((i + bskip) & 7))) & 1
85
+ c = abit <=> bbit
86
+ break if c != 0
87
+ i += 1
88
+ break if (i & 7) == 0
89
+ end
90
+ elsif i >= a.bytesize and i < b.bytesize
91
+ c = -1
92
+ elsif i >= b.bytesize and i < a.bytesize
93
+ c = 1
94
+ end
95
+ end
96
+ return c
97
+ end
98
+
99
+ # Concatenates list of `Binary` or `Bitstring` items into a single `Binary` or `Bitstring`.
100
+ #
101
+ # @example
102
+ # Erlang::Bitstring.concat(Erlang::Bitstring[1, bits: 2], Erlang::Binary[255])
103
+ # # => Erlang::Bitstring[127, 3, bits: 2]
104
+ #
105
+ # @param iodata [Binary, Bitstring] The list of bitstrings
106
+ # @return [Binary, Bitstring]
107
+ def concat(*iodata)
108
+ return iodata.reduce(Erlang::EmptyBinary) { |acc, item| acc.concat(item) }
109
+ end
110
+ end
111
+
112
+ # @private
113
+ def initialize(data = nil, bits = 8)
114
+ raise ArgumentError, 'bits must be an Integer' if not bits.is_a?(::Integer)
115
+ raise ArgumentError, 'bits must be between 1 and 8' if bits < 1 or bits > 8
116
+ data = ::String.new if data.nil?
117
+ data = data.data if data.is_a?(Erlang::Binary)
118
+ raise ArgumentError, 'data must be a String' if not data.is_a?(::String)
119
+ data = Erlang::Terms.binary_encoding(data)
120
+ if data.bytesize > 0
121
+ data.setbyte(-1, ((data.getbyte(-1) << (8 - bits)) & 255) >> (8 - bits))
122
+ end
123
+ @data = data.freeze
124
+ @bits = bits.freeze
125
+ @bitsize = (@data.bytesize == 0) ? 0 : (((@data.bytesize - 1) * 8) + @bits)
126
+ end
127
+
128
+ # @private
129
+ def hash
130
+ hash = @data.hash
131
+ return (hash << 5) - hash + bits.hash
132
+ end
133
+
134
+ # @raise [NotImplementedError]
135
+ def at(position)
136
+ raise NotImplementedError
137
+ end
138
+ alias :[] :at
139
+
140
+ # @return [Integer] the number of bits in this `Bitstring`
141
+ def bitsize
142
+ return @bitsize
143
+ end
144
+
145
+ # @private
146
+ BIT_PACK = 'B*'.freeze
147
+
148
+ # Return specific objects from the `Bitstring`. All overloads return `nil` if
149
+ # the starting index is out of range.
150
+ #
151
+ # @overload bitslice(index)
152
+ # Returns a single bit at the given `index`. If `index` is negative,
153
+ # count backwards from the end.
154
+ #
155
+ # @param index [Integer] The index to retrieve. May be negative.
156
+ # @return [Integer, nil]
157
+ # @example
158
+ # b = Erlang::Bitstring[2, bits: 2]
159
+ # b.bitslice(0) # => 1
160
+ # b.bitslice(1) # => 0
161
+ # b.bitslice(-1) # => 0
162
+ # b.bitslice(2) # => nil
163
+ #
164
+ # @overload bitslice(index, length)
165
+ # Return a bitstring starting at `index` and continuing for `length`
166
+ # bits or until the end of the `Bitstring`, whichever occurs first.
167
+ #
168
+ # @param start [Integer] The index to start retrieving bits from. May be
169
+ # negative.
170
+ # @param length [Integer] The number of bits to retrieve.
171
+ # @return [Bitstring, Binary]
172
+ # @example
173
+ # b = Erlang::Bitstring[1, 117, bits: 7]
174
+ # b.bitslice(0, 11) # => Erlang::Bitstring[1, 7, bits: 3]
175
+ # b.bitslice(11, 4) # => Erlang::Bitstring[5, bits: 4]
176
+ # b.bitslice(16, 1) # => nil
177
+ #
178
+ # @overload bitslice(index..end)
179
+ # Return a bitstring starting at `index` and continuing to index
180
+ # `end` or the end of the `Bitstring`, whichever occurs first.
181
+ #
182
+ # @param range [Range] The range of bits to retrieve.
183
+ # @return [Bitstring, Binary]
184
+ # @example
185
+ # b = Erlang::Bitstring[1, 117, bits: 7]
186
+ # b.bitslice(0...11) # => Erlang::Bitstring[1, 7, bits: 3]
187
+ # b.bitslice(11...15) # => Erlang::Bitstring[5, bits: 4]
188
+ # b.bitslice(16..-1) # => nil
189
+ #
190
+ # @see Erlang::Binary#bitslice
191
+ def bitslice(arg, length = (missing_length = true))
192
+ if missing_length
193
+ if arg.is_a?(Range)
194
+ from, to = arg.begin, arg.end
195
+ from += bitsize if from < 0
196
+ return nil if from < 0
197
+ to += bitsize if to < 0
198
+ to += 1 if !arg.exclude_end?
199
+ length = to - from
200
+ length = 0 if length < 0
201
+ length = bitsize - from if (from + length) > bitsize
202
+ return nil if length < 0
203
+ l8 = length.div(8)
204
+ l1 = length % 8
205
+ pad = 8 - l1
206
+ enum = each_bit
207
+ skip = from
208
+ enum = enum.drop_while {
209
+ if skip > 0
210
+ skip -= 1
211
+ next true
212
+ else
213
+ next false
214
+ end
215
+ }
216
+ head = enum.take(length)
217
+ if l1 == 0
218
+ return Erlang::Binary[[head.join].pack(BIT_PACK)]
219
+ else
220
+ tail = head[-l1..-1]
221
+ head = head[0...-l1]
222
+ tail = ([0] * pad).concat(tail)
223
+ return Erlang::Bitstring[[[head.join, tail.join].join].pack(BIT_PACK), bits: l1]
224
+ end
225
+ else
226
+ arg += bitsize if arg < 0
227
+ return nil if arg < 0
228
+ return nil if arg >= bitsize
229
+ a8 = arg.div(8)
230
+ a1 = arg % 8
231
+ byte = @data.getbyte(a8)
232
+ return nil if byte.nil?
233
+ return (byte >> ((@bits - a1 - 1) & 7)) & 1
234
+ end
235
+ else
236
+ return nil if length < 0
237
+ arg += bitsize if arg < 0
238
+ return nil if arg < 0
239
+ length = bitsize - arg if (arg + length) > bitsize
240
+ return nil if length < 0
241
+ l8 = length.div(8)
242
+ l1 = length % 8
243
+ pad = 8 - l1
244
+ enum = each_bit
245
+ skip = arg
246
+ enum = enum.drop_while {
247
+ if skip > 0
248
+ skip -= 1
249
+ next true
250
+ else
251
+ next false
252
+ end
253
+ }
254
+ head = enum.take(length)
255
+ if l1 == 0
256
+ return Erlang::Binary[[head.join].pack(BIT_PACK)]
257
+ else
258
+ tail = head[-l1..-1]
259
+ head = head[0...-l1]
260
+ tail = ([0] * pad).concat(tail)
261
+ return Erlang::Bitstring[[[head.join, tail.join].join].pack(BIT_PACK), bits: l1]
262
+ end
263
+ end
264
+ end
265
+
266
+ # @return [Integer] the number of bytes in this `Bitstring`
267
+ def bytesize
268
+ return @data.bytesize
269
+ end
270
+ alias :size :bytesize
271
+
272
+ # Concatenates list of `Binary` or `Bitstring` items into a single `Binary` or `Bitstring`.
273
+ #
274
+ # @example
275
+ # Erlang::Bitstring[3, bits: 3].concat(Erlang::Bitstring[1, bits: 5])
276
+ # # => "a"
277
+ #
278
+ # @param iodata [Binary, Bitstring] The list of bitstrings
279
+ # @return [Binary, Bitstring]
280
+ def concat(*other)
281
+ if other.size == 1 and (other[0].kind_of?(Erlang::Binary) or other[0].kind_of?(Erlang::Bitstring))
282
+ other = other[0]
283
+ else
284
+ other = Erlang::Binary[*other]
285
+ end
286
+ return other if empty?
287
+ return self if other.empty?
288
+ if @bits == 8
289
+ return to_binary.concat(other)
290
+ else
291
+ bits = (@bits + other.bits) % 8
292
+ head = [*each_bit, *other.each_bit]
293
+ if bits == 0
294
+ return Erlang::Binary[[head.join].pack(BIT_PACK)]
295
+ else
296
+ pad = 8 - bits
297
+ tail = head[-bits..-1]
298
+ head = head[0...-bits]
299
+ tail = ([0] * pad).concat(tail)
300
+ return Erlang::Bitstring[[[head.join, tail.join].join].pack(BIT_PACK), bits: bits]
301
+ end
302
+ end
303
+ end
304
+ alias :+ :concat
305
+
306
+ # @raise [NotImplementedError]
307
+ def copy(n = 1)
308
+ raise NotImplementedError
309
+ end
310
+
311
+ # @raise [NotImplementedError]
312
+ def decode_unsigned(endianness = :big)
313
+ raise NotImplementedError
314
+ end
315
+
316
+ # Call the given block once for each bit in the `Bitstring`, passing each
317
+ # bit from first to last successively to the block. If no block is given,
318
+ # returns an `Enumerator`.
319
+ #
320
+ # @return [self]
321
+ # @yield [Integer]
322
+ def each_bit
323
+ return enum_for(:each_bit) unless block_given?
324
+ index = 0
325
+ headbits = (self.bytesize - 1) * 8
326
+ skipbits = 8 - @bits
327
+ @data.each_byte do |byte|
328
+ loop do
329
+ break if index == @bitsize
330
+ if index >= headbits
331
+ bit = (byte >> (7 - ((index + skipbits) & 7))) & 1
332
+ else
333
+ bit = (byte >> (7 - (index & 7))) & 1
334
+ end
335
+ yield bit
336
+ index += 1
337
+ break if (index & 7) == 0
338
+ end
339
+ end
340
+ return self
341
+ end
342
+
343
+ # Split the bits in this `Bitstring` in groups of `number` bits, and yield
344
+ # each group to the block (as a `List`). If no block is given, returns
345
+ # an `Enumerator`.
346
+ #
347
+ # @example
348
+ # b = Erlang::Bitstring[117, bits: 7]
349
+ # b.each_bitslice(4).to_a # => [Erlang::Bitstring[14, bits: 4], Erlang::Bitstring[5, bits: 3]]
350
+ #
351
+ # @return [self, Enumerator]
352
+ # @yield [Binary, Bitstring] Once for each bitstring.
353
+ # @see Erlang::Bitstring#each_bitslice
354
+ def each_bitslice(number)
355
+ return enum_for(:each_bitslice, number) unless block_given?
356
+ raise ArgumentError, 'number must be a positive Integer' if not number.is_a?(::Integer) or number < 1
357
+ slices = bitsize.div(number) + ((bitsize % number == 0) ? 0 : 1)
358
+ index = 0
359
+ loop do
360
+ break if slices == 0
361
+ yield(bitslice(index * number, number))
362
+ slices -= 1
363
+ index += 1
364
+ end
365
+ return self
366
+ end
367
+
368
+ # @raise [NotImplementedError]
369
+ def each_byte
370
+ raise NotImplementedError
371
+ end
372
+
373
+ # Returns true if this `Bitstring` is empty.
374
+ #
375
+ # @return [Boolean]
376
+ def empty?
377
+ return @data.empty?
378
+ end
379
+
380
+ # Return true if `other` has the same type and contents as this `Bitstring`.
381
+ #
382
+ # @param other [Object] The object to compare with
383
+ # @return [Boolean]
384
+ def eql?(other)
385
+ return true if other.equal?(self)
386
+ if instance_of?(other.class)
387
+ return !!(self.class.compare(self, other) == 0)
388
+ else
389
+ return !!(Erlang.compare(other, self) == 0)
390
+ end
391
+ end
392
+ alias :== :eql?
393
+
394
+ # @raise [NotImplementedError]
395
+ def first
396
+ raise NotImplementedError
397
+ end
398
+
399
+ # @raise [NotImplementedError]
400
+ def last
401
+ raise NotImplementedError
402
+ end
403
+
404
+ # @raise [NotImplementedError]
405
+ def part(position, length)
406
+ raise NotImplementedError
407
+ end
408
+
409
+ # Return the contents of this `Bitstring` as a Erlang-readable `::String`.
410
+ #
411
+ # @example
412
+ # Erlang::Bitstring["test", bits: 3].erlang_inspect
413
+ # # => "<<116,101,115,4:3>>"
414
+ #
415
+ # @return [::String]
416
+ def erlang_inspect(raw = false)
417
+ return Erlang.inspect(Erlang::Binary.new(@data), raw: raw) if @bits == 8
418
+ result = '<<'
419
+ bytes = @data.bytes
420
+ if last = bytes.pop
421
+ result << bytes.join(',')
422
+ result << ',' if not bytes.empty?
423
+ if @bits == 8
424
+ result << "#{last}"
425
+ else
426
+ result << "#{last}:#{@bits}"
427
+ end
428
+ end
429
+ result << '>>'
430
+ return result
431
+ end
432
+
433
+ # @return [::String] the nicely formatted version of the `Bitstring`
434
+ def inspect
435
+ return "Erlang::Bitstring[#{data.bytes.inspect[1..-2]}, bits: #{bits.inspect}]"
436
+ end
437
+
438
+ # @return [Binary] the `Binary` version of the `Bitstring` padded with zeroes
439
+ def to_binary
440
+ return EmptyBinary if empty?
441
+ return Erlang::Binary[@data]
442
+ end
443
+
444
+ # @return [::String] the string version of the `Bitstring`
445
+ def to_s
446
+ return @data
447
+ end
448
+ alias :to_str :to_s
449
+
450
+ # @return [::String]
451
+ # @private
452
+ def marshal_dump
453
+ return [@data, @bits]
454
+ end
455
+
456
+ # @private
457
+ def marshal_load(args)
458
+ data, bits = args
459
+ initialize(data, bits)
460
+ __send__(:immutable!)
461
+ return self
462
+ end
463
+ end
464
+ end