erlang-terms 1.1.0 → 2.0.1

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