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
@@ -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'