code-ruby 0.11.0 → 0.13.0

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/Gemfile +1 -0
  3. data/Gemfile.lock +5 -3
  4. data/Rakefile +5 -0
  5. data/bin/code +6 -0
  6. data/lib/code/node/base_10.rb +2 -2
  7. data/lib/code/node/base_16.rb +1 -5
  8. data/lib/code/node/base_2.rb +1 -5
  9. data/lib/code/node/base_8.rb +1 -5
  10. data/lib/code/node/call.rb +4 -4
  11. data/lib/code/node/code.rb +2 -1
  12. data/lib/code/node/decimal.rb +1 -4
  13. data/lib/code/node/dictionary.rb +6 -2
  14. data/lib/code/node/function.rb +3 -3
  15. data/lib/code/node/function_parameter.rb +5 -1
  16. data/lib/code/node/if.rb +2 -1
  17. data/lib/code/node/list.rb +2 -1
  18. data/lib/code/node/statement.rb +23 -23
  19. data/lib/code/node/string.rb +2 -1
  20. data/lib/code/object/argument.rb +4 -3
  21. data/lib/code/object/boolean.rb +4 -24
  22. data/lib/code/object/class.rb +3 -19
  23. data/lib/code/object/code.rb +16 -0
  24. data/lib/code/object/context.rb +6 -9
  25. data/lib/code/object/date.rb +25 -17
  26. data/lib/code/object/decimal.rb +30 -38
  27. data/lib/code/object/dictionary.rb +6 -21
  28. data/lib/code/object/duration.rb +7 -21
  29. data/lib/code/object/function.rb +29 -45
  30. data/lib/code/object/global.rb +56 -40
  31. data/lib/code/object/identifier_list.rb +0 -4
  32. data/lib/code/object/integer.rb +23 -46
  33. data/lib/code/object/json.rb +29 -0
  34. data/lib/code/object/list.rb +4 -21
  35. data/lib/code/object/nothing.rb +2 -20
  36. data/lib/code/object/parameter.rb +51 -0
  37. data/lib/code/object/range.rb +12 -27
  38. data/lib/code/object/string.rb +6 -27
  39. data/lib/code/object/time.rb +14 -24
  40. data/lib/code/object.rb +155 -15
  41. data/lib/code/parser.rb +1 -1
  42. data/lib/code/type.rb +10 -2
  43. data/lib/code/version.rb +1 -1
  44. data/lib/code-ruby.rb +6 -0
  45. data/lib/code.rb +8 -5
  46. data/spec/code/object/dictionary_spec.rb +0 -2
  47. data/spec/code_spec.rb +141 -29
  48. metadata +6 -3
  49. data/lib/code/object/number.rb +0 -11
@@ -2,32 +2,14 @@
2
2
 
3
3
  class Code
4
4
  class Object
5
- class Nothing < ::Code::Object
6
- attr_reader :raw
7
-
8
- def initialize
5
+ class Nothing < Object
6
+ def initialize(*_args, **_kargs, &_block)
9
7
  @raw = nil
10
8
  end
11
9
 
12
- def self.name
13
- "Nothing"
14
- end
15
-
16
- def inspect
17
- "nothing"
18
- end
19
-
20
- def to_s
21
- ""
22
- end
23
-
24
10
  def truthy?
25
11
  false
26
12
  end
27
-
28
- def as_json(...)
29
- raw.as_json(...)
30
- end
31
13
  end
32
14
  end
33
15
  end
@@ -0,0 +1,51 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Code
4
+ class Object
5
+ class Parameter < Object
6
+ def initialize(*args, **_kargs, &_block)
7
+ @raw = Dictionary.new(args.first.presence || {})
8
+ end
9
+
10
+ def code_name
11
+ String.new(raw.code_get(String.new(:name)))
12
+ end
13
+
14
+ def code_keyword
15
+ Boolean.new(raw.code_get(String.new(:keyword)))
16
+ end
17
+
18
+ def code_regular_splat
19
+ Boolean.new(raw.code_get(String.new(:regular_splat)))
20
+ end
21
+
22
+ def code_keyword_splat
23
+ Boolean.new(raw.code_get(String.new(:keyword_splat)))
24
+ end
25
+
26
+ def code_default
27
+ Code.new(raw.code_get(String.new(:default)))
28
+ end
29
+
30
+ def evaluate(...)
31
+ default.evaluate(...)
32
+ end
33
+
34
+ def regular?
35
+ !keyword?
36
+ end
37
+
38
+ def keyword?
39
+ code_keyword.truthy?
40
+ end
41
+
42
+ def regular_splat?
43
+ code_regular_splat.truthy?
44
+ end
45
+
46
+ def keyword_splat?
47
+ code_keyword_splat.truthy?
48
+ end
49
+ end
50
+ end
51
+ end
@@ -3,17 +3,14 @@
3
3
  class Code
4
4
  class Object
5
5
  class Range < Object
6
- attr_reader :raw, :exclude_end, :left, :right
6
+ attr_reader :left, :right, :options, :exclude_end
7
7
 
8
- def initialize(left, right, exclude_end: false)
9
- @left = left
10
- @right = right
11
- @exclude_end = !exclude_end.nil?
12
- @raw = ::Range.new(left, right, exclude_end)
13
- end
14
-
15
- def self.name
16
- "Range"
8
+ def initialize(*args, **_kargs, &_block)
9
+ @left = args.first.presence || Integer.new(0)
10
+ @right = args.second.presence || Integer.new(0)
11
+ @options = Dictionary.new(args.third.presence || {})
12
+ @exclude_end = Boolean.new(options.code_get(String.new(:exclude_end)))
13
+ @raw = ::Range.new(left, right, exclude_end?)
17
14
  end
18
15
 
19
16
  def call(**args)
@@ -45,7 +42,7 @@ class Code
45
42
  sig(args) { Function }
46
43
  code_select(value, **globals)
47
44
  when "step"
48
- sig(args) { Number }
45
+ sig(args) { Integer | Decimal }
49
46
  code_step(value)
50
47
  when "to_list"
51
48
  sig(args)
@@ -102,6 +99,10 @@ class Code
102
99
  )
103
100
  end
104
101
 
102
+ def exclude_end?
103
+ exclude_end.truthy?
104
+ end
105
+
105
106
  def code_step(argument)
106
107
  list = List.new
107
108
  element = left
@@ -126,22 +127,6 @@ class Code
126
127
  def code_to_list
127
128
  List.new(raw.to_a)
128
129
  end
129
-
130
- def exclude_end?
131
- !!exclude_end
132
- end
133
-
134
- def inspect
135
- to_s
136
- end
137
-
138
- def to_s
139
- raw.to_s
140
- end
141
-
142
- def as_json(...)
143
- raw.as_json(...)
144
- end
145
130
  end
146
131
  end
147
132
  end
@@ -3,15 +3,10 @@
3
3
  class Code
4
4
  class Object
5
5
  class String < Object
6
- attr_reader :raw
7
-
8
- def initialize(string)
9
- string = string.raw if string.is_a?(String)
10
- @raw = string.to_s
11
- end
12
-
13
- def self.name
14
- "String"
6
+ def initialize(*args, **_kargs, &_block)
7
+ raw = args.first || Nothing.new
8
+ raw = raw.raw if raw.is_a?(Object)
9
+ @raw = raw.to_s
15
10
  end
16
11
 
17
12
  def call(**args)
@@ -25,7 +20,7 @@ class Code
25
20
  sig(args)
26
21
  code_to_function(**globals)
27
22
  when "*"
28
- sig(args) { Number }
23
+ sig(args) { Integer | Decimal }
29
24
  code_multiplication(value)
30
25
  when "+"
31
26
  sig(args) { Object }
@@ -65,7 +60,7 @@ class Code
65
60
  end
66
61
 
67
62
  def code_to_function(**globals)
68
- Code::Node::Code.new(
63
+ Node::Code.new(
69
64
  [
70
65
  {
71
66
  function: {
@@ -89,22 +84,6 @@ class Code
89
84
  ]
90
85
  ).evaluate(**globals)
91
86
  end
92
-
93
- def inspect
94
- raw.inspect
95
- end
96
-
97
- def succ
98
- String.new(raw.succ)
99
- end
100
-
101
- def to_s
102
- raw
103
- end
104
-
105
- def as_json(...)
106
- raw.as_json(...)
107
- end
108
87
  end
109
88
  end
110
89
  end
@@ -5,17 +5,11 @@ class Code
5
5
  class Time < Object
6
6
  DEFAULT_ZONE = "Etc/UTC"
7
7
 
8
- attr_reader :raw
9
-
10
- def initialize(time)
8
+ def initialize(*args, **_kargs, &_block)
11
9
  ::Time.zone ||= DEFAULT_ZONE
12
- time = time.raw if time.is_a?(Time)
13
- time = time.to_s if time.is_a?(::Time)
14
- @raw = ::Time.zone.parse(time)
15
- end
16
-
17
- def self.name
18
- "Time"
10
+ raw = args.first.presence || ::Time.zone.now
11
+ raw = raw.raw if raw.is_an?(Object)
12
+ @raw = ::Time.zone.parse(raw.to_s)
19
13
  end
20
14
 
21
15
  def self.call(**args)
@@ -28,6 +22,9 @@ class Code
28
22
  when "tomorrow"
29
23
  sig(args)
30
24
  code_tomorrow
25
+ when "yesterday"
26
+ sig(args)
27
+ code_yesterday
31
28
  else
32
29
  super
33
30
  end
@@ -35,12 +32,17 @@ class Code
35
32
 
36
33
  def self.code_tomorrow
37
34
  ::Time.zone ||= DEFAULT_ZONE
38
- new(::Time.zone.tomorrow.beginning_of_day)
35
+ new(::Time.zone.tomorrow)
36
+ end
37
+
38
+ def self.code_yesterday
39
+ ::Time.zone ||= DEFAULT_ZONE
40
+ new(::Time.zone.yesterday)
39
41
  end
40
42
 
41
43
  def self.code_now
42
44
  ::Time.zone ||= DEFAULT_ZONE
43
- new(::Time.zone.now.beginning_of_day)
45
+ new(::Time.zone.now)
44
46
  end
45
47
 
46
48
  def call(**args)
@@ -83,18 +85,6 @@ class Code
83
85
  def code_future?
84
86
  code_after?
85
87
  end
86
-
87
- def inspect
88
- to_s
89
- end
90
-
91
- def to_s
92
- raw.to_s
93
- end
94
-
95
- def as_json(...)
96
- raw.as_json(...)
97
- end
98
88
  end
99
89
  end
100
90
  end
data/lib/code/object.rb CHANGED
@@ -2,12 +2,13 @@
2
2
 
3
3
  class Code
4
4
  class Object
5
- def self.maybe
6
- Type::Maybe.new(self)
5
+ attr_reader :raw
6
+
7
+ def initialize(*_args, **_kargs, &_block)
7
8
  end
8
9
 
9
- def self.name
10
- "Object"
10
+ def self.maybe
11
+ Type::Maybe.new(self)
11
12
  end
12
13
 
13
14
  def self.repeat(minimum = 0, maximum = nil)
@@ -29,6 +30,9 @@ class Code
29
30
  value = arguments.first&.value
30
31
 
31
32
  case operator.to_s
33
+ when "new"
34
+ sig(args) { Object.repeat }
35
+ code_new(*arguments.map(&:value))
32
36
  when "!", "not"
33
37
  sig(args)
34
38
  code_exclamation_point
@@ -65,6 +69,48 @@ class Code
65
69
  when "||", "or"
66
70
  sig(args) { Object }
67
71
  code_or_operator(value)
72
+ when "to_boolean"
73
+ sig(args)
74
+ Boolean.new(self)
75
+ when "to_class"
76
+ sig(args)
77
+ Class.new(self)
78
+ when "to_date"
79
+ sig(args)
80
+ Date.new(self)
81
+ when "to_decimal"
82
+ sig(args)
83
+ Decimal.new(self)
84
+ when "to_dictionary"
85
+ sig(args)
86
+ Dictionary.new(self)
87
+ when "to_duration"
88
+ sig(args)
89
+ Duration.new(self)
90
+ when "to_integer"
91
+ sig(args)
92
+ Integer.new(self)
93
+ when "to_list"
94
+ sig(args)
95
+ List.new(self)
96
+ when "to_nothing"
97
+ sig(args)
98
+ Nothing.new(self)
99
+ when "to_range"
100
+ sig(args)
101
+ Range.new(self)
102
+ when "to_string"
103
+ sig(args)
104
+ String.new(self)
105
+ when "to_time"
106
+ sig(args)
107
+ Time.new(self)
108
+ when "as_json"
109
+ sig(args)
110
+ code_as_json
111
+ when "to_json"
112
+ sig(args) { { pretty: Boolean.maybe } }
113
+ code_to_json(pretty: value&.code_get(String.new(:pretty)))
68
114
  when /=$/
69
115
  sig(args) { Object }
70
116
 
@@ -86,12 +132,16 @@ class Code
86
132
  context.code_fetch(self)
87
133
  else
88
134
  raise(
89
- Code::Error::Undefined,
135
+ Error::Undefined,
90
136
  "#{operator} not defined on #{inspect}:Class"
91
137
  )
92
138
  end
93
139
  end
94
140
 
141
+ def self.code_new(*arguments)
142
+ new(*arguments)
143
+ end
144
+
95
145
  def self.code_and_operator(other)
96
146
  truthy? ? other : self
97
147
  end
@@ -145,10 +195,6 @@ class Code
145
195
  nil
146
196
  end
147
197
 
148
- def self.name
149
- "Object"
150
- end
151
-
152
198
  def self.to_s
153
199
  name
154
200
  end
@@ -161,6 +207,26 @@ class Code
161
207
  true
162
208
  end
163
209
 
210
+ def self.to_json(...)
211
+ as_json(...).to_json(...)
212
+ end
213
+
214
+ def self.as_json(...)
215
+ name.as_json(...)
216
+ end
217
+
218
+ def self.code_to_json(pretty: nil)
219
+ if Boolean.new(pretty).truthy?
220
+ String.new(::JSON.pretty_generate(self))
221
+ else
222
+ String.new(to_json)
223
+ end
224
+ end
225
+
226
+ def self.code_as_json
227
+ Json.to_code(as_json)
228
+ end
229
+
164
230
  def <=>(other)
165
231
  if respond_to?(:raw)
166
232
  raw <=> (other.respond_to?(:raw) ? other.raw : other)
@@ -220,6 +286,48 @@ class Code
220
286
  when "||", "or"
221
287
  sig(args) { Object }
222
288
  code_or_operator(value)
289
+ when "to_boolean"
290
+ sig(args)
291
+ Boolean.new(self)
292
+ when "to_class"
293
+ sig(args)
294
+ Class.new(self)
295
+ when "to_date"
296
+ sig(args)
297
+ Date.new(self)
298
+ when "to_decimal"
299
+ sig(args)
300
+ Decimal.new(self)
301
+ when "to_duration"
302
+ sig(args)
303
+ Duration.new(self)
304
+ when "to_dictionary"
305
+ sig(args)
306
+ Dictionary.new(self)
307
+ when "to_integer"
308
+ sig(args)
309
+ Integer.new(self)
310
+ when "to_list"
311
+ sig(args)
312
+ List.new(self)
313
+ when "to_nothing"
314
+ sig(args)
315
+ Nothing.new(self)
316
+ when "to_range"
317
+ sig(args)
318
+ Range.new(self)
319
+ when "to_string"
320
+ sig(args)
321
+ String.new(self)
322
+ when "to_time"
323
+ sig(args)
324
+ Time.new(self)
325
+ when "as_json"
326
+ sig(args)
327
+ code_as_json
328
+ when "to_json"
329
+ sig(args) { { pretty: Boolean.maybe } }
330
+ code_to_json(pretty: value&.code_get(String.new(:pretty)))
223
331
  when /=$/
224
332
  sig(args) { Object }
225
333
 
@@ -241,8 +349,8 @@ class Code
241
349
  context.code_fetch(self)
242
350
  else
243
351
  raise(
244
- Code::Error::Undefined,
245
- "#{operator} not defined on #{inspect}:#{self.class.name}"
352
+ Error::Undefined,
353
+ "#{operator.inspect} not defined on #{inspect}:#{self.class.name}"
246
354
  )
247
355
  end
248
356
  end
@@ -264,11 +372,23 @@ class Code
264
372
  end
265
373
 
266
374
  def code_exclusive_range(value)
267
- Range.new(self, value, exclude_end: true)
375
+ Range.new(
376
+ self,
377
+ value,
378
+ Dictionary.new({
379
+ String.new(:exclude_end) => Boolean.new(true)
380
+ })
381
+ )
268
382
  end
269
383
 
270
384
  def code_inclusive_range(value)
271
- Range.new(self, value, exclude_end: false)
385
+ Range.new(
386
+ self,
387
+ value,
388
+ Dictionary.new({
389
+ String.new(:exclude_end) => Boolean.new(false)
390
+ })
391
+ )
272
392
  end
273
393
 
274
394
  def code_or_operator(other)
@@ -317,11 +437,31 @@ class Code
317
437
  end
318
438
 
319
439
  def to_json(...)
320
- as_json(...).to_json
440
+ as_json(...).to_json(...)
321
441
  end
322
442
 
323
443
  def as_json(...)
324
- raw.as_json
444
+ raw.as_json(...)
445
+ end
446
+
447
+ def code_to_json(pretty: nil)
448
+ if Boolean.new(pretty).truthy?
449
+ String.new(::JSON.pretty_generate(self))
450
+ else
451
+ String.new(to_json)
452
+ end
453
+ end
454
+
455
+ def code_as_json
456
+ Json.to_code(as_json)
457
+ end
458
+
459
+ def succ
460
+ if raw.respond_to?(:succ)
461
+ self.class.new(raw.succ)
462
+ else
463
+ self.class.new(self)
464
+ end
325
465
  end
326
466
  end
327
467
  end
data/lib/code/parser.rb CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  class Code
4
4
  class Parser
5
- class Error < ::Code::Error
5
+ class Error < Error
6
6
  end
7
7
 
8
8
  def initialize(input)
data/lib/code/type.rb CHANGED
@@ -2,8 +2,16 @@
2
2
 
3
3
  class Code
4
4
  class Type
5
- def name
6
- "Type"
5
+ def maybe
6
+ Maybe.new(self)
7
+ end
8
+
9
+ def repeat(minimum = 0, maximum = nil)
10
+ Repeat.new(self, minimum:, maximum:)
11
+ end
12
+
13
+ def |(other)
14
+ Or.new(self, other)
7
15
  end
8
16
 
9
17
  def valid?(_argument)
data/lib/code/version.rb CHANGED
@@ -2,4 +2,4 @@
2
2
 
3
3
  require_relative "../code"
4
4
 
5
- Code::Version = Gem::Version.new("0.11.0")
5
+ Code::Version = Gem::Version.new("0.13.0")
data/lib/code-ruby.rb CHANGED
@@ -5,7 +5,9 @@ require "active_support/core_ext/date/conversions"
5
5
  require "active_support/core_ext/numeric/time"
6
6
  require "active_support/core_ext/object/json"
7
7
  require "active_support/core_ext/object/blank"
8
+ require "active_support/core_ext/array/access"
8
9
  require "bigdecimal"
10
+ require "bigdecimal/util"
9
11
  require "json"
10
12
  require "language-ruby"
11
13
  require "stringio"
@@ -15,3 +17,7 @@ require "zeitwerk"
15
17
  loader = Zeitwerk::Loader.for_gem(warn_on_extra_files: false)
16
18
  loader.ignore("#{__dir__}/code-ruby.rb")
17
19
  loader.setup
20
+
21
+ class Object
22
+ alias is_an? is_a?
23
+ end
data/lib/code.rb CHANGED
@@ -17,6 +17,12 @@ class Code
17
17
  @context = Object::Context.new
18
18
  end
19
19
 
20
+ def self.parse(input, timeout: DEFAULT_TIMEOUT)
21
+ Timeout.timeout(timeout) do
22
+ Parser.parse(input).to_raw
23
+ end
24
+ end
25
+
20
26
  def self.evaluate(
21
27
  input,
22
28
  output: StringIO.new,
@@ -28,11 +34,8 @@ class Code
28
34
 
29
35
  def evaluate
30
36
  Timeout.timeout(timeout) do
31
- Node::Code.new(::Code::Parser.parse(input).to_raw).evaluate(
32
- context:,
33
- output:,
34
- error:
35
- )
37
+ parsed = Code.parse(input)
38
+ Node::Code.new(parsed).evaluate(context:, output:, error:)
36
39
  end
37
40
  end
38
41
 
@@ -76,10 +76,8 @@ RSpec.describe Code::Object::Dictionary do
76
76
  ["{ a: nothing }.compact", "{}"],
77
77
  ["{ age: 31 }.delete_unless { |key| key == :age }", "{ age: 31 }"],
78
78
  ["{ age: 31 }.delete_unless(Integer)", "{ age: 31 }"],
79
- ["{ age: 31 }.delete_unless(Number)", "{ age: 31 }"],
80
79
  ["{ age: 31 }.keep_unless { |key| key == :age }", "{}"],
81
80
  ["{ age: 31 }.keep_unless(Integer)", "{}"],
82
- ["{ age: 31 }.keep_unless(Number)", "{}"],
83
81
  ["{ first_name: :Dorian } < dictionary", "true"],
84
82
  ["{ first_name: :Dorian }.any? { |_, value| value == :Dorian }", "true"],
85
83
  ["{ first_name: :Dorian }.delete_if { |_, value| value == :Dorian }", "{}"],