code-ruby 3.0.13 → 3.1.2
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.
- checksums.yaml +4 -4
- data/Gemfile.lock +33 -41
- data/VERSION +1 -1
- data/lib/code/format.rb +34 -7
- data/lib/code/node/left_operation.rb +22 -5
- data/lib/code/node/square_bracket.rb +5 -2
- data/lib/code/node/while.rb +21 -0
- data/lib/code/object/dictionary.rb +91 -9
- data/lib/code/object/function.rb +28 -3
- data/lib/code/object/html.rb +48 -9
- data/lib/code/object/integer.rb +4 -0
- data/lib/code/object/list.rb +56 -28
- data/lib/code/object/range.rb +20 -0
- data/lib/code/parser.rb +16 -2
- data/spec/code/format_spec.rb +8 -1
- data/spec/code/object/dictionary_spec.rb +2 -0
- data/spec/code/object/function_spec.rb +119 -12
- data/spec/code/object/list_spec.rb +2 -0
- data/spec/code_spec.rb +88 -1
- metadata +1 -1
|
@@ -3,22 +3,22 @@
|
|
|
3
3
|
require "spec_helper"
|
|
4
4
|
|
|
5
5
|
RSpec.describe Code::Object::Function do
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
6
|
+
[
|
|
7
|
+
["even? = (i) => { i.even? } even?(2)", "true"],
|
|
8
|
+
["even? = (i:) => { i.even? } even?(i: 2)", "true"],
|
|
9
|
+
["add = (a, b) => { a + b } add(1, 2)", "3"],
|
|
10
|
+
["minus = (a:, b:) => { a - b } minus(b: 1, a: 2)", "1"]
|
|
11
|
+
].each do |input, expected|
|
|
12
|
+
it "#{input} == #{expected}" do
|
|
13
|
+
expect(Code.evaluate(input)).to eq(Code.evaluate(expected))
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
16
|
|
|
17
17
|
context "valid" do
|
|
18
18
|
[
|
|
19
19
|
"f = () => {} f",
|
|
20
|
-
"f = (x) => {} f(1)"
|
|
21
|
-
|
|
20
|
+
"f = (x) => {} f(1)",
|
|
21
|
+
"f = (x:) => {} f(x: 1)"
|
|
22
22
|
].each do |input|
|
|
23
23
|
it "#{input} is valid" do
|
|
24
24
|
Code.evaluate(input)
|
|
@@ -38,6 +38,46 @@ RSpec.describe Code::Object::Function do
|
|
|
38
38
|
end
|
|
39
39
|
end
|
|
40
40
|
|
|
41
|
+
it "evaluates omitted keyword argument defaults" do
|
|
42
|
+
result = Code.evaluate(<<~CODE)
|
|
43
|
+
f = (number: 1, text: "fallback", missing: nothing, headers: {}) => {
|
|
44
|
+
[
|
|
45
|
+
number,
|
|
46
|
+
text,
|
|
47
|
+
missing.nothing?,
|
|
48
|
+
headers.merge({ authorization: "Bearer x" })
|
|
49
|
+
]
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
f()
|
|
53
|
+
CODE
|
|
54
|
+
|
|
55
|
+
expect(result).to eq(
|
|
56
|
+
Code.evaluate('[1, "fallback", true, { authorization: "Bearer x" }]')
|
|
57
|
+
)
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
it "evaluates keyword defaults in the call context" do
|
|
61
|
+
expect(Code.evaluate("f = (a:, b: a + 1) => { b } f(a: 2)")).to eq(
|
|
62
|
+
Code.evaluate("3")
|
|
63
|
+
)
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
it "binds self when calling a function stored on a dictionary" do
|
|
67
|
+
result = Code.evaluate(<<~CODE)
|
|
68
|
+
object = {}
|
|
69
|
+
object.value = 1
|
|
70
|
+
object.fetch = () => {
|
|
71
|
+
self.value
|
|
72
|
+
}
|
|
73
|
+
object.fetch()
|
|
74
|
+
CODE
|
|
75
|
+
|
|
76
|
+
expect(result).to eq(
|
|
77
|
+
Code.evaluate("1")
|
|
78
|
+
)
|
|
79
|
+
end
|
|
80
|
+
|
|
41
81
|
it "captures self for constructor-like functions that return self" do
|
|
42
82
|
result = Code.evaluate(<<~CODE)
|
|
43
83
|
User = (given_name:, family_name:, birth_date:) => {
|
|
@@ -72,6 +112,73 @@ RSpec.describe Code::Object::Function do
|
|
|
72
112
|
)
|
|
73
113
|
end
|
|
74
114
|
|
|
115
|
+
it "binds parent to the enclosing self for nested constructor functions" do
|
|
116
|
+
result = Code.evaluate(<<~CODE)
|
|
117
|
+
Account = (name:) => {
|
|
118
|
+
self.name = name
|
|
119
|
+
|
|
120
|
+
self.Project = (name:) => {
|
|
121
|
+
self.name = name
|
|
122
|
+
|
|
123
|
+
self.Task = (name:) => {
|
|
124
|
+
self.name = name
|
|
125
|
+
|
|
126
|
+
[
|
|
127
|
+
self.name,
|
|
128
|
+
parent.name,
|
|
129
|
+
parent.parent.name
|
|
130
|
+
]
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
return(self)
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
return(self)
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
account = Account(name: "Acme")
|
|
140
|
+
Project = account.get(:Project)
|
|
141
|
+
project = Project(name: "Migration")
|
|
142
|
+
Task = project.get(:Task)
|
|
143
|
+
Task(name: "Import")
|
|
144
|
+
CODE
|
|
145
|
+
|
|
146
|
+
expect(result).to eq(
|
|
147
|
+
Code.evaluate('["Import", "Migration", "Acme"]')
|
|
148
|
+
)
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
it "allows parent chains deeper than two levels" do
|
|
152
|
+
result = Code.evaluate(<<~CODE)
|
|
153
|
+
A = (name:) => {
|
|
154
|
+
self.name = name
|
|
155
|
+
self.B = (name:) => {
|
|
156
|
+
self.name = name
|
|
157
|
+
self.C = (name:) => {
|
|
158
|
+
self.name = name
|
|
159
|
+
self.D = (name:) => {
|
|
160
|
+
self.name = name
|
|
161
|
+
parent.parent.parent.name
|
|
162
|
+
}
|
|
163
|
+
return(self)
|
|
164
|
+
}
|
|
165
|
+
return(self)
|
|
166
|
+
}
|
|
167
|
+
return(self)
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
a = A(name: "a")
|
|
171
|
+
B = a.get(:B)
|
|
172
|
+
b = B(name: "b")
|
|
173
|
+
C = b.get(:C)
|
|
174
|
+
c = C(name: "c")
|
|
175
|
+
D = c.get(:D)
|
|
176
|
+
D(name: "d")
|
|
177
|
+
CODE
|
|
178
|
+
|
|
179
|
+
expect(result).to eq(Code::Object::String.new("a"))
|
|
180
|
+
end
|
|
181
|
+
|
|
75
182
|
it "supports constructor methods on functions" do
|
|
76
183
|
result = Code.evaluate(<<~CODE)
|
|
77
184
|
User = (given_name:, family_name:) => {
|
|
@@ -5,6 +5,8 @@ require "spec_helper"
|
|
|
5
5
|
RSpec.describe Code::Object::List do
|
|
6
6
|
[
|
|
7
7
|
["[] == []", "true"],
|
|
8
|
+
["[nothing, 1, false, \"\"].compact", '[1, false, ""]'],
|
|
9
|
+
["[nothing, 1, false, \"\"].compact(&:blank?)", "[1]"],
|
|
8
10
|
["[1, 2, 3].sum", "6"],
|
|
9
11
|
["[1, 2] + [3, 4]", "[1, 2, 3, 4]"],
|
|
10
12
|
["[] + []", "[]"],
|
data/spec/code_spec.rb
CHANGED
|
@@ -13,6 +13,74 @@ RSpec.describe Code do
|
|
|
13
13
|
[result, output.string]
|
|
14
14
|
end
|
|
15
15
|
|
|
16
|
+
def control_flow_block_calls
|
|
17
|
+
[
|
|
18
|
+
"[1].each { CONTROL }",
|
|
19
|
+
"[1, 2].any? { CONTROL }",
|
|
20
|
+
"[1].detect { CONTROL }",
|
|
21
|
+
"[1].map { CONTROL }",
|
|
22
|
+
"xs = [1] xs.map! { CONTROL }",
|
|
23
|
+
"[1].max { CONTROL }",
|
|
24
|
+
"[1].none? { CONTROL }",
|
|
25
|
+
"[1].all? { CONTROL }",
|
|
26
|
+
"[1, 2].reduce { |acc, item| CONTROL }",
|
|
27
|
+
"[nothing].compact { CONTROL }",
|
|
28
|
+
"xs = [nothing] xs.compact! { CONTROL }",
|
|
29
|
+
"[1].select { CONTROL }",
|
|
30
|
+
"xs = [1] xs.select! { CONTROL }",
|
|
31
|
+
"[1].reject { CONTROL }",
|
|
32
|
+
"xs = [1] xs.reject! { CONTROL }",
|
|
33
|
+
"[1].sort { CONTROL }",
|
|
34
|
+
"[1].uniq { CONTROL }",
|
|
35
|
+
"(1..1).all? { CONTROL }",
|
|
36
|
+
"(1..1).any? { CONTROL }",
|
|
37
|
+
"(1..1).each { CONTROL }",
|
|
38
|
+
"(1..1).map { CONTROL }",
|
|
39
|
+
"(1..1).select { CONTROL }",
|
|
40
|
+
"{a: 1}.any? { CONTROL }",
|
|
41
|
+
"{a: 1}.compact { CONTROL }",
|
|
42
|
+
"hash = {a: 1} hash.compact! { CONTROL }",
|
|
43
|
+
"{a: 1}.delete(:b) { CONTROL }",
|
|
44
|
+
"hash = {a: 1} hash.delete_if { CONTROL }",
|
|
45
|
+
"hash = {a: 1} hash.delete_unless { CONTROL }",
|
|
46
|
+
"{a: 1}.each { CONTROL }",
|
|
47
|
+
"{a: 1}.fetch(:b) { CONTROL }",
|
|
48
|
+
"hash = {a: 1} hash.keep_if { CONTROL }",
|
|
49
|
+
"hash = {a: 1} hash.keep_unless { CONTROL }",
|
|
50
|
+
"{a: 1}.key(2) { CONTROL }",
|
|
51
|
+
"{a: 1}.merge({a: 2}) { CONTROL }",
|
|
52
|
+
"hash = {a: 1} hash.merge!({a: 2}) { CONTROL }",
|
|
53
|
+
"{a: 1}.select { CONTROL }",
|
|
54
|
+
"hash = {a: 1} hash.select! { CONTROL }",
|
|
55
|
+
"{a: 1}.transform_values { CONTROL }",
|
|
56
|
+
"1.times { CONTROL }",
|
|
57
|
+
"Html.p { CONTROL }",
|
|
58
|
+
"Html.escape { CONTROL }",
|
|
59
|
+
"Html.unescape { CONTROL }",
|
|
60
|
+
"Html.join(Html.br) { CONTROL }",
|
|
61
|
+
"Html.text { CONTROL }",
|
|
62
|
+
"Html.raw { CONTROL }",
|
|
63
|
+
"Html.raw(\"<p>a</p>\").css(\"p\").map { CONTROL }"
|
|
64
|
+
]
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
%w[return break next continue].each do |control_flow|
|
|
68
|
+
it "continues after #{control_flow} in block methods" do
|
|
69
|
+
aggregate_failures do
|
|
70
|
+
control_flow_block_calls.each do |block_call|
|
|
71
|
+
input = "#{block_call.sub("CONTROL", control_flow)} puts(:end)"
|
|
72
|
+
formatted = format_input(input)
|
|
73
|
+
|
|
74
|
+
_result, output = evaluate_with_output(input)
|
|
75
|
+
_formatted_result, formatted_output = evaluate_with_output(formatted)
|
|
76
|
+
|
|
77
|
+
expect(output).to eq("end\n"), input
|
|
78
|
+
expect(formatted_output).to eq("end\n"), formatted
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
|
|
16
84
|
(
|
|
17
85
|
[
|
|
18
86
|
"{ a: 1, b: 2 }.transform_values { |key| key.upcase }",
|
|
@@ -328,6 +396,18 @@ RSpec.describe Code do
|
|
|
328
396
|
["[1, 2, 3]", "[1, 2, 3]"],
|
|
329
397
|
["[1, 2, 3].include?(2)", "true"],
|
|
330
398
|
["[1, 2, 3].include?(4)", "false"],
|
|
399
|
+
[
|
|
400
|
+
"summary = { by_status: { draft: 1 } } k = \"draft\" summary.by_status[k]",
|
|
401
|
+
"1"
|
|
402
|
+
],
|
|
403
|
+
[
|
|
404
|
+
"summary = { by_status: {} } k = \"draft\" summary.by_status[k] = 1 summary",
|
|
405
|
+
'{"by_status" => {"draft" => 1}}'
|
|
406
|
+
],
|
|
407
|
+
[
|
|
408
|
+
"summary = { by_status: {} } [{status: \"draft\"}].each { |track| summary.by_status[track[:status]] = 1 } summary",
|
|
409
|
+
'{"by_status" => {"draft" => 1}}'
|
|
410
|
+
],
|
|
331
411
|
["[1, 2, 3].map { |i| continue(0) if i.even? i ** 2}", "[1, 0, 9]"],
|
|
332
412
|
["[1, 2, 3].map { |i| next if i == 2 i ** 2}", "[1, nothing, 9]"],
|
|
333
413
|
["[1, 2, 3].map { |i| next(0) if i.even? i ** 2}", "[1, 0, 9]"],
|
|
@@ -340,6 +420,7 @@ RSpec.describe Code do
|
|
|
340
420
|
["[1, 2].map(&:to_string)", '["1", "2"]'],
|
|
341
421
|
["[1, 2].map(&:to_string)", '["1", "2"]'],
|
|
342
422
|
["[1].each do end", "[1]"],
|
|
423
|
+
["x = [1].each { break(42) } x", "42"],
|
|
343
424
|
["[[true]]", "[[true]]"],
|
|
344
425
|
["[]", "[]"],
|
|
345
426
|
["\r\n", "nothing"],
|
|
@@ -350,6 +431,8 @@ RSpec.describe Code do
|
|
|
350
431
|
],
|
|
351
432
|
["a = 0 loop a += 1 break end a", "1"],
|
|
352
433
|
["x = loop break(42) end x", "42"],
|
|
434
|
+
["x = loop { |index| break(index) if index > 3 } x", "4"],
|
|
435
|
+
["x = loop do |index| break(index) if index > 3 end x", "4"],
|
|
353
436
|
["a = 0\nuntil a > 10 a += 1 end a", "11"],
|
|
354
437
|
["a = 0\nwhile a < 10 a += 1 end a", "10"],
|
|
355
438
|
["a = 1 3.times { a += 1 } a", "4"],
|
|
@@ -529,7 +612,11 @@ RSpec.describe Code do
|
|
|
529
612
|
end
|
|
530
613
|
end
|
|
531
614
|
|
|
532
|
-
[
|
|
615
|
+
[
|
|
616
|
+
["puts(true)", "true\n"],
|
|
617
|
+
%w[print(false) false],
|
|
618
|
+
["[1].each { break } puts(:end)", "end\n"]
|
|
619
|
+
].each do |input, expected|
|
|
533
620
|
it "#{input} prints #{expected}" do
|
|
534
621
|
formatted = format_input(input)
|
|
535
622
|
|