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
@@ -1,7 +1,179 @@
1
1
  module Erlang
2
- class String < ::String
2
+ # A `String` is a `List` of characters.
3
+ #
4
+ # ### Creating Strings
5
+ #
6
+ # Erlang::String["test"]
7
+ # # => Erlang::String["test"]
8
+ #
9
+ # A `String` is equivalent to a `List` with `Integer` elements:
10
+ #
11
+ # Erlang::String["test"] == Erlang::List[116, 101, 115, 116]
12
+ # # => true
13
+ #
14
+ class String
15
+ include Erlang::Term
16
+ include Erlang::Immutable
17
+
18
+ # Return the data for this `String`
19
+ # @return [::String]
20
+ attr_reader :data
21
+
22
+ class << self
23
+ # Create a new `String` populated with the given `data`.
24
+ # @param data [::String, Symbol, ::Enumerable, Integer] The content of the `Atom`
25
+ # @return [String]
26
+ # @raise [ArgumentError] if `data` cannot be coerced to be a `::String`
27
+ def [](*data)
28
+ return EmptyString if data.empty?
29
+ if data.size == 1
30
+ return data[0] if data[0].kind_of?(Erlang::String)
31
+ end
32
+ unless data.is_a?(::String)
33
+ data = Erlang.iolist_to_binary(data).data
34
+ end
35
+ return new(data)
36
+ end
37
+
38
+ # Return an empty `String`. If used on a subclass, returns an empty instance
39
+ # of that class.
40
+ #
41
+ # @return [String]
42
+ def empty
43
+ return @empty ||= self.new
44
+ end
45
+
46
+ # Compares `a` and `b` and returns whether they are less than,
47
+ # equal to, or greater than each other.
48
+ #
49
+ # @param a [String] The left argument
50
+ # @param b [String] The right argument
51
+ # @return [-1, 0, 1]
52
+ # @raise [ArgumentError] if `a` or `b` is not a `String`
53
+ def compare(a, b)
54
+ raise ArgumentError, "'a' must be of Erlang::String type" if not a.kind_of?(Erlang::String)
55
+ raise ArgumentError, "'b' must be of Erlang::String type" if not b.kind_of?(Erlang::String)
56
+ c = a.size <=> b.size
57
+ return a.data <=> b.data if c == 0
58
+ return Erlang::List.compare(a.to_list, b.to_list)
59
+ end
60
+ end
61
+
62
+ # @private
63
+ def initialize(data = ::String.new.freeze)
64
+ raise ArgumentError, 'data must be a String' if not data.is_a?(::String)
65
+ data = Erlang::Terms.binary_encoding(data)
66
+ @data = data.freeze
67
+ end
68
+
69
+ # @private
70
+ def hash
71
+ return to_list.hash
72
+ end
73
+
74
+ # Returns true if this `String` is empty.
75
+ #
76
+ # @return [Boolean]
77
+ def empty?
78
+ return @data.empty?
79
+ end
80
+
81
+ # Returns an `::Array` with the `::String` data for this `String`.
82
+ #
83
+ # @return [[::String]]
84
+ def flatten
85
+ return [@data]
86
+ end
87
+
88
+ # Returns the length of this `String`.
89
+ #
90
+ # @return [Integer]
91
+ def length
92
+ return @data.bytesize
93
+ end
94
+ alias :size :length
95
+
96
+ # Return true if `other` has the same type and contents as this `String`.
97
+ #
98
+ # @param other [Object] The object to compare with
99
+ # @return [Boolean]
100
+ def eql?(other)
101
+ return true if other.equal?(self)
102
+ if instance_of?(other.class)
103
+ return !!(self.class.compare(self, other) == 0)
104
+ else
105
+ return !!(Erlang.compare(other, self) == 0)
106
+ end
107
+ end
108
+ alias :== :eql?
109
+
110
+ # Return the contents of this `String` as a Erlang-readable `::String`.
111
+ #
112
+ # @example
113
+ # Erlang::String["test"].erlang_inspect
114
+ # # => "\"test\""
115
+ # # Pass `raw` as `true` for the List version
116
+ # Erlang::String["test"].erlang_inspect(true)
117
+ # # => "[116,101,115,116]"
118
+ #
119
+ # @return [::String]
120
+ def erlang_inspect(raw = false)
121
+ if raw == false
122
+ return @data.inspect
123
+ else
124
+ return '[' << @data.bytes.join(',') << ']'
125
+ end
126
+ end
127
+
128
+ # @return [::String] the nicely formatted version of the `String`
3
129
  def inspect
4
- "#<#{self.class.name} #{super}>"
130
+ return "Erlang::String[#{@data.inspect}]"
131
+ end
132
+
133
+ # @return [Atom] the `Atom` version of the `String`
134
+ def to_atom
135
+ return Erlang::Atom[@data]
136
+ end
137
+
138
+ # @return [Binary] the `Binary` version of the `String`
139
+ def to_binary
140
+ return Erlang::Binary[@data]
141
+ end
142
+
143
+ # @return [List] the `List` version of the `String`
144
+ def to_list
145
+ return Erlang::List.from_enum(@data.bytes)
146
+ end
147
+
148
+ # @return [self] the `String` version of the `String`
149
+ def to_string
150
+ return self
151
+ end
152
+
153
+ # @return [::String] the string version of the `String`
154
+ def to_s
155
+ return @data
156
+ end
157
+ alias :to_str :to_s
158
+
159
+ # @return [::String]
160
+ # @private
161
+ def marshal_dump
162
+ return @data
163
+ end
164
+
165
+ # @private
166
+ def marshal_load(data)
167
+ initialize(data)
168
+ __send__(:immutable!)
169
+ return self
5
170
  end
6
171
  end
7
- end
172
+
173
+ # The canonical empty `Erlang::String`. Returned by `Erlang::String[]` when
174
+ # invoked with no arguments; also returned by `Erlang::String.empty`. Prefer using this
175
+ # one rather than creating many empty strings using `Erlang::String.new`.
176
+ #
177
+ # @private
178
+ EmptyString = Erlang::String.empty
179
+ end
@@ -0,0 +1,24 @@
1
+ module Erlang
2
+ # @private
3
+ module Term
4
+ class << self
5
+ # Extends the including class with +ClassMethods+.
6
+ #
7
+ # @param [Class] subclass the inheriting class
8
+ def included(base)
9
+ super
10
+ base.extend(ClassMethods)
11
+ base.send(:include, ::Comparable)
12
+ end
13
+
14
+ private :included
15
+ end
16
+
17
+ module ClassMethods
18
+ end
19
+
20
+ def <=>(other)
21
+ return Erlang.compare(self, other)
22
+ end
23
+ end
24
+ end
@@ -1,14 +1,330 @@
1
- require "erlang/terms/version"
1
+ require 'erlang/terms/version'
2
+
3
+ require 'bigdecimal'
4
+ require 'rational'
5
+
6
+ require 'erlang/undefined'
2
7
 
3
8
  module Erlang
9
+ # @private
4
10
  module Terms
11
+ PRINTABLE = /\A[[:print:]]+\z/.freeze
12
+ BINARY_ENCODING = Encoding.find('binary')
13
+ UTF8_ENCODING = Encoding.find('utf-8')
14
+ UINT8_SPLAT = 'C*'.freeze
15
+ TERM_ORDER = {
16
+ number: 0,
17
+ atom: 1,
18
+ reference: 2,
19
+ fun: 3,
20
+ port: 4,
21
+ pid: 5,
22
+ tuple: 6,
23
+ map: 7,
24
+ nil: 8,
25
+ list: 9,
26
+ bitstring: 10
27
+ }.freeze
28
+
29
+ def self.binary_encoding(string)
30
+ string = string.dup if string.frozen?
31
+ string = string.force_encoding(BINARY_ENCODING)
32
+ return string
33
+ end
34
+
35
+ def self.printable?(string)
36
+ return !!(PRINTABLE =~ string)
37
+ end
38
+
39
+ def self.utf8_encoding(string)
40
+ string = string.dup if string.frozen?
41
+ begin
42
+ string = string.encode(UTF8_ENCODING)
43
+ rescue EncodingError
44
+ string = string.force_encoding(UTF8_ENCODING)
45
+ end
46
+ if string.valid_encoding?
47
+ return true, string
48
+ else
49
+ string = binary_encoding(string)
50
+ return false, string
51
+ end
52
+ end
53
+ end
54
+
55
+ def self.compare(a, b)
56
+ a = Erlang.from(a)
57
+ b = Erlang.from(b)
58
+ t = type(a)
59
+ c = Erlang::Terms::TERM_ORDER[t] <=> Erlang::Terms::TERM_ORDER[type(b)]
60
+ return c if c != 0
61
+ case t
62
+ when :atom
63
+ return Erlang::Atom.compare(a, b)
64
+ when :bitstring
65
+ return Erlang::Bitstring.compare(a, b)
66
+ when :fun
67
+ return Erlang::Function.compare(a, b)
68
+ when :list
69
+ return Erlang::List.compare(a, b)
70
+ when :map
71
+ return Erlang::Map.compare(a, b)
72
+ when :nil
73
+ return 0
74
+ when :number
75
+ if is_float(a) or is_float(b)
76
+ af = a
77
+ if not is_float(a)
78
+ begin
79
+ af = Erlang::Float[a]
80
+ rescue ArgumentError
81
+ af = a
82
+ end
83
+ end
84
+ bf = b
85
+ if not is_float(b)
86
+ begin
87
+ bf = Erlang::Float[b]
88
+ rescue ArgumentError
89
+ bf = b
90
+ end
91
+ end
92
+ return Erlang::Float.compare(af, bf) if af.kind_of?(Erlang::Float) and bf.kind_of?(Erlang::Float)
93
+ return af.data <=> bf if af.kind_of?(Erlang::Float) and not bf.kind_of?(Erlang::Float)
94
+ afbfcmp = bf.data <=> af
95
+ return -afbfcmp
96
+ end
97
+ return a <=> b
98
+ when :pid
99
+ return Erlang::Pid.compare(a, b)
100
+ when :port
101
+ return Erlang::Port.compare(a, b)
102
+ when :reference
103
+ return Erlang::Reference.compare(a, b)
104
+ when :tuple
105
+ return Erlang::Tuple.compare(a, b)
106
+ else
107
+ raise NotImplementedError
108
+ end
109
+ end
110
+
111
+ def self.from(term)
112
+ return term.to_erlang if term.respond_to?(:to_erlang)
113
+ # integer
114
+ return term if is_integer(term)
115
+ # float
116
+ return term if term.kind_of?(Erlang::Float)
117
+ return Erlang::Float[term] if is_float(term)
118
+ # atom
119
+ return term if term.kind_of?(Erlang::Atom)
120
+ return Erlang::Atom[term] if term.kind_of?(::Symbol)
121
+ return Erlang::Atom[term] if term.kind_of?(::FalseClass)
122
+ return Erlang::Atom[term] if term.kind_of?(::NilClass)
123
+ return Erlang::Atom[term] if term.kind_of?(::TrueClass)
124
+ # reference, function, port, pid, and tuple
125
+ return term if is_reference(term)
126
+ return term if is_function(term)
127
+ return term if is_port(term)
128
+ return term if is_pid(term)
129
+ return term if is_tuple(term)
130
+ # map
131
+ return term if term.kind_of?(Erlang::Map)
132
+ return Erlang::Map[term] if term.kind_of?(::Hash)
133
+ # nil
134
+ return term if term.equal?(Erlang::Nil)
135
+ return Erlang::Nil if is_list(term) and term.empty?
136
+ # list
137
+ return term if term.kind_of?(Erlang::List)
138
+ return term if term.kind_of?(Erlang::String)
139
+ return Erlang::List.from_enum(term) if term.kind_of?(::Array)
140
+ # bitstring
141
+ return term if term.kind_of?(Erlang::Binary)
142
+ return term if term.kind_of?(Erlang::Bitstring)
143
+ return Erlang::Binary[term] if term.kind_of?(::String)
144
+ raise ArgumentError, "unable to convert ruby object of class #{term.class} to erlang term"
145
+ end
146
+
147
+ def self.inspect(term = Erlang::Undefined, raw: false)
148
+ return super() if Erlang::Undefined.equal?(term)
149
+ term = from(term)
150
+ return term.erlang_inspect(raw) if term.respond_to?(:erlang_inspect)
151
+ return term.inspect if is_any(term)
152
+ raise NotImplementedError
153
+ end
154
+
155
+ def self.iolist_to_binary(iolist)
156
+ return iolist if is_binary(iolist)
157
+ return Erlang::Binary.new(iolist) if iolist.is_a?(::String)
158
+ return Erlang::Binary.new(iolist.to_s) if iolist.is_a?(::Symbol)
159
+ if is_list(iolist)
160
+ return Erlang::Binary.new(iolist.flatten.map do |element|
161
+ data = nil
162
+ if element.is_a?(::Integer) and element <= 255 and element >= -256
163
+ element = element + 256 if element < 0
164
+ data = element.chr
165
+ elsif element.is_a?(::String)
166
+ data = element
167
+ elsif is_binary(element)
168
+ data = element.data
169
+ elsif element.is_a?(::Symbol)
170
+ data = element.to_s
171
+ elsif is_list(element)
172
+ data = iolist_to_binary(element).data
173
+ else
174
+ raise ArgumentError, "unknown element in iolist: #{element.inspect}"
175
+ end
176
+ next Erlang::Terms.binary_encoding(data)
177
+ end.join)
178
+ else
179
+ raise ArgumentError, "unrecognized iolist: #{iolist.inspect}"
180
+ end
181
+ end
182
+
183
+ def self.is_any(term)
184
+ return true if is_atom(term)
185
+ return true if is_bitstring(term)
186
+ return true if is_boolean(term)
187
+ return true if is_float(term)
188
+ return true if is_function(term)
189
+ return true if is_integer(term)
190
+ return true if is_list(term)
191
+ return true if is_map(term)
192
+ return true if is_number(term)
193
+ return true if is_pid(term)
194
+ return true if is_port(term)
195
+ return true if is_reference(term)
196
+ return true if is_tuple(term)
197
+ return false
198
+ end
199
+
200
+ def self.is_atom(term)
201
+ return true if term.kind_of?(::Symbol)
202
+ return true if term.kind_of?(::FalseClass)
203
+ return true if term.kind_of?(::NilClass)
204
+ return true if term.kind_of?(::TrueClass)
205
+ return true if term.kind_of?(Erlang::Atom)
206
+ return false
207
+ end
208
+
209
+ def self.is_binary(term)
210
+ return true if term.kind_of?(::String)
211
+ return true if term.kind_of?(Erlang::Binary)
212
+ return false
213
+ end
214
+
215
+ def self.is_bitstring(term)
216
+ return true if is_binary(term)
217
+ return true if term.kind_of?(Erlang::Bitstring)
218
+ return false
219
+ end
220
+
221
+ def self.is_boolean(term)
222
+ return true if term.kind_of?(::FalseClass)
223
+ return true if term.kind_of?(::TrueClass)
224
+ return true if term == :true
225
+ return true if term == :false
226
+ return false
227
+ end
228
+
229
+ def self.is_float(term)
230
+ return true if term.kind_of?(::BigDecimal)
231
+ return true if term.kind_of?(::Float)
232
+ return true if term.kind_of?(::Rational)
233
+ return true if term.kind_of?(Erlang::Float)
234
+ return false
235
+ end
236
+
237
+ def self.is_function(term, arity = nil)
238
+ if arity == nil
239
+ return true if term.kind_of?(Erlang::Export)
240
+ return true if term.kind_of?(Erlang::Function)
241
+ else
242
+ return true if is_function(term) and term.arity == arity
243
+ end
244
+ return false
245
+ end
246
+
247
+ def self.is_integer(term)
248
+ return true if term.kind_of?(::Integer)
249
+ return false
250
+ end
251
+
252
+ def self.is_list(term)
253
+ return true if term.kind_of?(Erlang::List)
254
+ return true if term.kind_of?(Erlang::String)
255
+ return true if term.kind_of?(::Array)
256
+ return true if term.equal?(Erlang::Nil)
257
+ return false
258
+ end
259
+
260
+ def self.is_map(term)
261
+ return true if term.kind_of?(Erlang::Map)
262
+ return true if term.kind_of?(::Hash)
263
+ return false
264
+ end
265
+
266
+ def self.is_number(term)
267
+ return true if is_float(term) or is_integer(term)
268
+ return false
269
+ end
270
+
271
+ def self.is_pid(term)
272
+ return true if term.kind_of?(Erlang::Pid)
273
+ return false
274
+ end
275
+
276
+ def self.is_port(term)
277
+ return true if term.kind_of?(Erlang::Port)
278
+ return false
279
+ end
280
+
281
+ def self.is_reference(term)
282
+ return true if term.kind_of?(Erlang::Reference)
283
+ return false
284
+ end
285
+
286
+ def self.is_tuple(term)
287
+ return true if term.kind_of?(Erlang::Tuple)
288
+ return false
289
+ end
290
+
291
+ def self.type(term)
292
+ return :atom if is_atom(term)
293
+ return :bitstring if is_bitstring(term)
294
+ return :fun if is_function(term)
295
+ return :list if is_list(term) and not term.empty?
296
+ return :map if is_map(term)
297
+ return :nil if is_list(term) and term.empty?
298
+ return :number if is_number(term)
299
+ return :pid if is_pid(term)
300
+ return :port if is_port(term)
301
+ return :reference if is_reference(term)
302
+ return :tuple if is_tuple(term)
303
+ raise NotImplementedError
5
304
  end
305
+
6
306
  end
7
307
 
8
- require "erlang/export"
9
- require "erlang/list"
10
- require "erlang/map"
11
- require "erlang/nil"
12
- require "erlang/pid"
13
- require "erlang/string"
14
- require "erlang/tuple"
308
+ require 'erlang/associable'
309
+ require 'erlang/enumerable'
310
+ require 'erlang/immutable'
311
+
312
+ require 'erlang/error'
313
+ require 'erlang/list'
314
+ require 'erlang/term'
315
+ require 'erlang/trie'
316
+
317
+ require 'erlang/atom'
318
+ require 'erlang/binary'
319
+ require 'erlang/bitstring'
320
+ require 'erlang/cons'
321
+ require 'erlang/export'
322
+ require 'erlang/float'
323
+ require 'erlang/function'
324
+ require 'erlang/map'
325
+ require 'erlang/nil'
326
+ require 'erlang/pid'
327
+ require 'erlang/port'
328
+ require 'erlang/reference'
329
+ require 'erlang/string'
330
+ require 'erlang/tuple'