Skip to content

Commit 6428e70

Browse files
control flow for code
1 parent 3d874f9 commit 6428e70

10 files changed

Lines changed: 150 additions & 43 deletions

File tree

lib/code.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ def evaluate
4949
Node::Code.new(Code.parse(source)).evaluate(
5050
context: context,
5151
error: error,
52+
global_control_flow_root: true,
5253
input: input,
5354
object: object,
5455
output: output,

lib/code/error.rb

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,20 +2,22 @@
22

33
class Code
44
class Error < StandardError
5-
class Break < Error
5+
class ControlFlow < Error
66
attr_reader :code_value
77

88
def initialize(value = nil)
99
@code_value = value.to_code
1010
end
1111
end
1212

13-
class Next < Error
14-
attr_reader :code_value
13+
class Break < ControlFlow; end
1514

16-
def initialize(value = nil)
17-
@code_value = value.to_code
18-
end
19-
end
15+
class Next < ControlFlow; end
16+
17+
class Continue < Next; end
18+
19+
class Return < ControlFlow; end
20+
21+
class Retry < ControlFlow; end
2022
end
2123
end

lib/code/format.rb

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -318,7 +318,7 @@ def format_dictionary_statement_code(statement_code)
318318

319319
def format_call(call, indent:)
320320
name = call[:name]
321-
raw_arguments = Array(call[:arguments])
321+
raw_arguments = call[:arguments].presence || []
322322
arguments = raw_arguments.map { |arg| format_call_argument(arg) }
323323
statement =
324324
if arguments.empty?
@@ -336,6 +336,8 @@ def format_call(call, indent:)
336336
end
337337

338338
def format_call_argument(argument)
339+
return format_code_inline(Array(argument), indent: 0) unless argument.is_a?(Hash)
340+
339341
value = format_code_inline(argument[:value], indent: 0)
340342
return value unless argument.key?(:name)
341343

lib/code/node/code.rb

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,37 @@ def initialize(parsed)
1111
end
1212

1313
def evaluate(**args)
14+
global_control_flow_root = args.fetch(:global_control_flow_root, false)
15+
control_flow_scope = args.fetch(:control_flow_scope, nil)
16+
statement_args =
17+
if global_control_flow_root
18+
args.merge(global_control_flow_root: false)
19+
else
20+
args
21+
end
1422
last = Object::Nothing.new
1523

16-
(@statements || []).each do |statement|
17-
last = statement.evaluate(**args, object: Object::Global.new)
24+
begin
25+
(@statements || []).each do |statement|
26+
last = statement.evaluate(**statement_args, object: Object::Global.new)
27+
end
28+
rescue Error::Retry
29+
retry if control_flow_scope == :group
30+
31+
raise
32+
rescue Error::Break => e
33+
return e.code_value if control_flow_scope == :group
34+
35+
raise
1836
end
1937

2038
last
39+
rescue Error::ControlFlow => e
40+
raise unless global_control_flow_root
41+
42+
retry if e.is_a?(Error::Retry)
43+
44+
e.code_value
2145
end
2246

2347
def resolve(**args)

lib/code/node/right_operation.rb

Lines changed: 28 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -28,21 +28,9 @@ def evaluate(**args)
2828
@left.evaluate(**args)
2929
end
3030
when "while"
31-
left = Object::Nothing.new
32-
33-
left = @left&.evaluate(**args) || Object::Nothing.new while (
34-
@right&.evaluate(**args) || Object::Nothing.new
35-
).truthy?
36-
37-
left
31+
evaluate_conditional_loop(condition_truthy: true, **args)
3832
when "until"
39-
left = Object::Nothing.new
40-
41-
left = @left&.evaluate(**args) || Object::Nothing.new while (
42-
@right&.evaluate(**args) || Object::Nothing.new
43-
).falsy?
44-
45-
left
33+
evaluate_conditional_loop(condition_truthy: false, **args)
4634
when "rescue"
4735
begin
4836
@left&.evaluate(**args) || Object::Nothing.new
@@ -69,6 +57,32 @@ def evaluate(**args)
6957
)
7058
end
7159
end
60+
61+
private
62+
63+
def evaluate_conditional_loop(condition_truthy:, **args)
64+
left = Object::Nothing.new
65+
66+
while loop_condition_truthy?(condition_truthy, **args)
67+
begin
68+
left = @left&.evaluate(**args) || Object::Nothing.new
69+
rescue Error::Next, Error::Continue => e
70+
left = e.code_value
71+
next
72+
rescue Error::Retry
73+
retry
74+
rescue Error::Break => e
75+
return e.code_value
76+
end
77+
end
78+
79+
left
80+
end
81+
82+
def loop_condition_truthy?(condition_truthy, **args)
83+
condition = (@right&.evaluate(**args) || Object::Nothing.new).truthy?
84+
condition_truthy ? condition : !condition
85+
end
7286
end
7387
end
7488
end

lib/code/node/statement.rb

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ def initialize(parsed)
1111
elsif parsed.key?(:boolean)
1212
@statement = Boolean.new(parsed.delete(:boolean))
1313
elsif parsed.key?(:group)
14+
@control_flow_scope = :group
1415
@statement = Code.new(parsed.delete(:group))
1516
elsif parsed.key?(:call)
1617
@statement = Call.new(parsed.delete(:call))
@@ -56,7 +57,12 @@ def initialize(parsed)
5657
end
5758

5859
def evaluate(**args)
59-
@statement&.evaluate(**args) || Object::Nothing.new
60+
if @control_flow_scope == :group
61+
@statement&.evaluate(**args, control_flow_scope: @control_flow_scope) ||
62+
Object::Nothing.new
63+
else
64+
@statement&.evaluate(**args) || Object::Nothing.new
65+
end
6066
end
6167

6268
def resolve(**args)

lib/code/node/while.rb

Lines changed: 50 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -20,29 +20,61 @@ def initialize(parsed)
2020
def evaluate(**args)
2121
case @operator
2222
when WHILE_KEYWORD
23-
last = Object::Nothing.new
24-
25-
last = @body&.evaluate(**args) || Object::Nothing.new while (
26-
@statement&.evaluate(**args) || Object::Nothing.new
27-
).truthy?
28-
29-
last
23+
evaluate_conditional_loop(condition_truthy: true, **args)
3024
when UNTIL_KEYWORD
31-
last = Object::Nothing.new
32-
33-
last = @body&.evaluate(**args) || Object::Nothing.new while (
34-
@statement&.evaluate(**args) || Object::Nothing.new
35-
).falsy?
36-
37-
last
25+
evaluate_conditional_loop(condition_truthy: false, **args)
3826
when LOOP_KEYWORD
39-
loop { @body&.evaluate(**args) || Object::Nothing.new }
40-
Object::Nothing.new
27+
evaluate_infinite_loop(**args)
4128
else
4229
Object::Nothing.new
4330
end
44-
rescue Error::Break => e
45-
e.code_value
31+
end
32+
33+
private
34+
35+
def evaluate_conditional_loop(condition_truthy:, **args)
36+
last = Object::Nothing.new
37+
38+
while loop_condition_truthy?(condition_truthy, **args)
39+
begin
40+
last = @body&.evaluate(**args) || Object::Nothing.new
41+
rescue Error::Next, Error::Continue => e
42+
last = e.code_value
43+
next
44+
rescue Error::Retry
45+
retry
46+
rescue Error::Break => e
47+
return e.code_value
48+
end
49+
end
50+
51+
last
52+
end
53+
54+
def evaluate_infinite_loop(**args)
55+
last = Object::Nothing.new
56+
57+
loop do
58+
begin
59+
last = @body&.evaluate(**args) || Object::Nothing.new
60+
rescue Error::Next, Error::Continue => e
61+
last = e.code_value
62+
next
63+
rescue Error::Retry
64+
retry
65+
rescue Error::Break => e
66+
return e.code_value
67+
end
68+
end
69+
70+
last
71+
end
72+
73+
def loop_condition_truthy?(condition_truthy, **args)
74+
condition =
75+
(@statement&.evaluate(**args) || Object::Nothing.new).truthy?
76+
77+
condition_truthy ? condition : !condition
4678
end
4779
end
4880
end

lib/code/object/function.rb

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,8 @@ def code_call(*arguments, **globals)
7272
end
7373

7474
code_body.code_evaluate(**globals, context: code_context)
75+
rescue Error::Return => e
76+
e.code_value
7577
end
7678

7779
def signature_for_call

lib/code/object/global.rb

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,18 @@ def call(**args)
2323
when "break"
2424
sig(args) { Object.repeat }
2525
raise Error::Break, code_value || Nothing.new
26+
when "continue"
27+
sig(args) { Object.repeat }
28+
raise Error::Continue, code_value || Nothing.new
2629
when "next"
2730
sig(args) { Object.repeat }
2831
raise Error::Next, code_value || Nothing.new
32+
when "redo", "retry"
33+
sig(args) { Object.repeat }
34+
raise Error::Retry, code_value || Nothing.new
35+
when "return"
36+
sig(args) { Object.repeat }
37+
raise Error::Return, code_value || Nothing.new
2938
when "Class"
3039
sig(args) { Object.repeat }
3140
if code_arguments.any?

spec/code_spec.rb

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -313,6 +313,7 @@ def evaluate_with_output(input)
313313
["[1, 2, 3]", "[1, 2, 3]"],
314314
["[1, 2, 3].include?(2)", "true"],
315315
["[1, 2, 3].include?(4)", "false"],
316+
["[1, 2, 3].map { |i| continue(0) if i.even? i ** 2}", "[1, 0, 9]"],
316317
["[1, 2, 3].map { |i| next if i == 2 i ** 2}", "[1, nothing, 9]"],
317318
["[1, 2, 3].map { |i| next(0) if i.even? i ** 2}", "[1, 0, 9]"],
318319
["[1, 2, 3].select { |n| n.even? }", "[2]"],
@@ -328,9 +329,17 @@ def evaluate_with_output(input)
328329
["[]", "[]"],
329330
["\r\n", "nothing"],
330331
["a = 0 [1, 2, 3].each { |i| next if i == 2 a += i } a", "4"],
332+
["a = 0\nb = 0\nwhile a < 4\n a += 1\n continue if a == 2\n b += a\nend\nb", "8"],
331333
["a = 0 loop a += 1 break end a", "1"],
334+
["a = 1\nbegin\n a += 1\n break if a > 3\n retry\nend\na", "4"],
335+
["x = loop break(42) end x", "42"],
336+
["a = 0 a += 1 retry if a < 3 a", "3"],
332337
["a = 0\nuntil a > 10 a += 1 end a", "11"],
333338
["a = 0\nwhile a < 10 a += 1 end a", "10"],
339+
[
340+
"a = 0\nretried = false\nwhile a < 2\n a += 1\n retry if a == 1 && !retried && (retried = true)\nend\na",
341+
"2"
342+
],
334343
["a = 1 3.times { a += 1 } a", "4"],
335344
["a = 1 a *= 2 a", "2"],
336345
["a = 1 a += 1 a", "2"],
@@ -360,9 +369,15 @@ def evaluate_with_output(input)
360369
["if false 1 elsif false 2", "nothing"],
361370
["if false 1 elsif true 2", "2"],
362371
["if false 1", "nothing"],
372+
["break(5)", "5"],
373+
["continue(6)", "6"],
363374
["if true 1", "1"],
375+
["next(7)", "7"],
364376
["not not false", "false"],
365377
["not true", "false"],
378+
["retry(8)", "8"],
379+
["return(9)", "9"],
380+
["f = () => { return(3) 4 } f()", "3"],
366381
["a = 1 orirginal = 2 orirginal", "2"],
367382
["a = 1 andy = 2 andy", "2"],
368383
["ifonly = 1 ifonly", "1"],

0 commit comments

Comments
 (0)