code-ruby 0.11.0 → 0.13.0

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/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 }", "{}"],