kapusta 0.1.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 (77) hide show
  1. checksums.yaml +7 -0
  2. data/.rspec +2 -0
  3. data/Gemfile +10 -0
  4. data/README.md +58 -0
  5. data/Rakefile +10 -0
  6. data/bin/console +8 -0
  7. data/bin/setup +4 -0
  8. data/examples/accumulator.kap +16 -0
  9. data/examples/ackermann.kap +7 -0
  10. data/examples/anagram.kap +13 -0
  11. data/examples/binary-search.kap +16 -0
  12. data/examples/block-sort.kap +3 -0
  13. data/examples/calc.kap +10 -0
  14. data/examples/counter.kap +19 -0
  15. data/examples/describe.kap +9 -0
  16. data/examples/destructure.kap +4 -0
  17. data/examples/doto.kap +2 -0
  18. data/examples/egg-count.kap +10 -0
  19. data/examples/even-squares.kap +7 -0
  20. data/examples/exceptions.kap +14 -0
  21. data/examples/factorial.kap +8 -0
  22. data/examples/fib.kap +4 -0
  23. data/examples/fizzbuzz.kap +7 -0
  24. data/examples/gcd.kap +5 -0
  25. data/examples/greet.kap +2 -0
  26. data/examples/hashfn.kap +4 -0
  27. data/examples/kwargs.kap +1 -0
  28. data/examples/leap-year.kap +5 -0
  29. data/examples/match.kap +9 -0
  30. data/examples/min-max.kap +11 -0
  31. data/examples/module-header.kap +6 -0
  32. data/examples/palindrome.kap +8 -0
  33. data/examples/pangram.kap +9 -0
  34. data/examples/pcall.kap +9 -0
  35. data/examples/pipeline.kap +6 -0
  36. data/examples/points.kap +9 -0
  37. data/examples/primes.kap +8 -0
  38. data/examples/raindrops.kap +13 -0
  39. data/examples/record.kap +6 -0
  40. data/examples/regex.kap +9 -0
  41. data/examples/ruby-eval.kap +1 -0
  42. data/examples/safe-lookup.kap +6 -0
  43. data/examples/scopes.kap +18 -0
  44. data/examples/shapes.kap +9 -0
  45. data/examples/squares.kap +3 -0
  46. data/examples/stack.kap +19 -0
  47. data/examples/sum.kap +3 -0
  48. data/examples/tset.kap +4 -0
  49. data/examples/two-sum.kap +17 -0
  50. data/exe/kapfmt +6 -0
  51. data/exe/kapusta +6 -0
  52. data/kapfmt +4 -0
  53. data/kapusta.gemspec +25 -0
  54. data/lib/kapusta/ast.rb +76 -0
  55. data/lib/kapusta/cli.rb +61 -0
  56. data/lib/kapusta/compiler/emitter/bindings.rb +178 -0
  57. data/lib/kapusta/compiler/emitter/collections.rb +245 -0
  58. data/lib/kapusta/compiler/emitter/control_flow.rb +168 -0
  59. data/lib/kapusta/compiler/emitter/expressions.rb +107 -0
  60. data/lib/kapusta/compiler/emitter/interop.rb +277 -0
  61. data/lib/kapusta/compiler/emitter/patterns.rb +105 -0
  62. data/lib/kapusta/compiler/emitter/support.rb +169 -0
  63. data/lib/kapusta/compiler/emitter.rb +45 -0
  64. data/lib/kapusta/compiler/normalizer.rb +122 -0
  65. data/lib/kapusta/compiler/runtime.rb +583 -0
  66. data/lib/kapusta/compiler.rb +47 -0
  67. data/lib/kapusta/env.rb +42 -0
  68. data/lib/kapusta/formatter.rb +685 -0
  69. data/lib/kapusta/reader.rb +215 -0
  70. data/lib/kapusta/support.rb +7 -0
  71. data/lib/kapusta/version.rb +5 -0
  72. data/lib/kapusta.rb +30 -0
  73. data/spec/cli_spec.rb +77 -0
  74. data/spec/examples_spec.rb +258 -0
  75. data/spec/formatter_spec.rb +176 -0
  76. data/spec/spec_helper.rb +12 -0
  77. metadata +119 -0
@@ -0,0 +1,61 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../kapusta'
4
+ require 'optparse'
5
+
6
+ module Kapusta
7
+ class CLI
8
+ Options = Struct.new(:compile, :help, keyword_init: true)
9
+
10
+ def self.start(argv = ARGV)
11
+ args = argv.dup
12
+ options = parse_options(args)
13
+
14
+ if options.help
15
+ $stdout.puts usage
16
+ return
17
+ end
18
+
19
+ if options.compile
20
+ compile_file(args)
21
+ else
22
+ run_file(args)
23
+ end
24
+ end
25
+
26
+ def self.parse_options(args)
27
+ options = Options.new(compile: false, help: false)
28
+
29
+ OptionParser.new do |parser|
30
+ parser.banner = usage
31
+ parser.on('-c', '--compile', 'Compile .kap to Ruby') { options.compile = true }
32
+ parser.on('-h', '--help', 'Show this help') { options.help = true }
33
+ end.order!(args)
34
+
35
+ options
36
+ end
37
+
38
+ def self.compile_file(args)
39
+ path = args.shift
40
+ abort usage unless path
41
+ abort usage unless args.empty?
42
+
43
+ $stdout.write(Kapusta.compile(File.read(path), path:))
44
+ end
45
+
46
+ def self.run_file(args)
47
+ path = args.shift
48
+ abort usage unless path
49
+
50
+ previous_argv = ARGV.dup
51
+ ARGV.replace(args)
52
+ Kapusta.dofile(path)
53
+ ensure
54
+ ARGV.replace(previous_argv) if previous_argv
55
+ end
56
+
57
+ def self.usage
58
+ 'usage: kapusta [--compile|-c] <file.kap> | kapusta <file.kap> [args...]'
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,178 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Kapusta
4
+ module Compiler
5
+ module EmitterModules
6
+ module Bindings
7
+ private
8
+
9
+ def emit_fn(args, env, current_scope)
10
+ if args[0].is_a?(Vec)
11
+ emit_lambda(args[0], args[1..], env, current_scope)
12
+ else
13
+ name_sym = args[0]
14
+ pattern = args[1]
15
+ body = args[2..]
16
+ ruby_name = temp(sanitize_local(name_sym.name))
17
+ fn_env = env.child
18
+ fn_env.define(name_sym.name, ruby_name)
19
+ <<~RUBY.chomp
20
+ (-> do
21
+ #{ruby_name} = nil
22
+ #{ruby_name} = #{emit_lambda(pattern, body, fn_env, current_scope)}
23
+ #{ruby_name}
24
+ end).call
25
+ RUBY
26
+ end
27
+ end
28
+
29
+ def emit_lambda(pattern, body, env, current_scope)
30
+ args_var = temp('args')
31
+ body_env = env.child
32
+ bindings_code, body_env = emit_pattern_bind(pattern, args_var, body_env)
33
+ body_code, = emit_sequence(body, body_env, current_scope, allow_method_definitions: false)
34
+ <<~RUBY.chomp
35
+ ->(*#{args_var}) do
36
+ #{bindings_code}
37
+ #{body_code}
38
+ end
39
+ RUBY
40
+ end
41
+
42
+ def emit_named_fn_assignment(form, env, current_scope)
43
+ name_sym = form.items[1]
44
+ ruby_name = temp(sanitize_local(name_sym.name))
45
+ env.define(name_sym.name, ruby_name)
46
+ fn_env = env.child
47
+ fn_env.define(name_sym.name, ruby_name)
48
+ lambda_code = emit_lambda(form.items[2], form.items[3..], fn_env, current_scope)
49
+ ["#{ruby_name} = nil\n#{ruby_name} = #{lambda_code}", env]
50
+ end
51
+
52
+ def emit_method_definition(form, env)
53
+ name_sym = form.items[1]
54
+ pattern = form.items[2]
55
+ body = form.items[3..]
56
+ method_env = env.child
57
+ args_var = temp('args')
58
+ bindings_code, body_env = emit_pattern_bind(pattern, args_var, method_env)
59
+ body_code, = emit_sequence(body, body_env, :toplevel, allow_method_definitions: false)
60
+
61
+ if name_sym.name.start_with?('self.')
62
+ ruby_name = Kapusta.kebab_to_snake(name_sym.name.delete_prefix('self.')).to_sym.inspect
63
+ <<~RUBY.chomp
64
+ define_singleton_method(#{ruby_name}) do |*#{args_var}|
65
+ #{bindings_code}
66
+ #{body_code}
67
+ end
68
+ RUBY
69
+ else
70
+ ruby_name = Kapusta.kebab_to_snake(name_sym.name).to_sym.inspect
71
+ <<~RUBY.chomp
72
+ define_method(#{ruby_name}) do |*#{args_var}|
73
+ #{bindings_code}
74
+ #{body_code}
75
+ end
76
+ RUBY
77
+ end
78
+ end
79
+
80
+ def emit_let(args, env, current_scope)
81
+ bindings = args[0]
82
+ body = args[1..]
83
+ child_env = env.child
84
+ binding_codes = []
85
+ items = bindings.items
86
+ i = 0
87
+ while i < items.length
88
+ pattern = items[i]
89
+ value_code = emit_expr(items[i + 1], child_env, current_scope)
90
+ bind_code, child_env = emit_pattern_bind(pattern, value_code, child_env)
91
+ binding_codes << bind_code
92
+ i += 2
93
+ end
94
+ body_code, = emit_sequence(body, child_env, current_scope, allow_method_definitions: false)
95
+ <<~RUBY.chomp
96
+ (-> do
97
+ #{binding_codes.join("\n")}
98
+ #{body_code}
99
+ end).call
100
+ RUBY
101
+ end
102
+
103
+ def emit_local_form(form, env, current_scope)
104
+ target = form.items[1]
105
+ value_code = emit_expr(form.items[2], env, current_scope)
106
+
107
+ if target.is_a?(Sym)
108
+ ruby_name = temp(sanitize_local(target.name))
109
+ env.define(target.name, ruby_name)
110
+ ["#{ruby_name} = #{value_code}\nnil", env]
111
+ else
112
+ bind_code, env = emit_pattern_bind(target, value_code, env)
113
+ ["#{bind_code}\nnil", env]
114
+ end
115
+ end
116
+
117
+ def emit_local_expr(args, env, current_scope)
118
+ code, = emit_local_form(List.new([Sym.new('local'), *args]), env.child, current_scope)
119
+ "(-> do\n#{indent(code)}\nend).call"
120
+ end
121
+
122
+ def emit_set_form(form, env, current_scope)
123
+ target = form.items[1]
124
+ value_code = emit_expr(form.items[2], env, current_scope)
125
+
126
+ if target.is_a?(Sym) && !target.dotted?
127
+ ruby_name =
128
+ if env.defined?(target.name)
129
+ env.lookup(target.name)
130
+ else
131
+ fresh = temp(sanitize_local(target.name))
132
+ env.define(target.name, fresh)
133
+ fresh
134
+ end
135
+ ["#{ruby_name} = #{value_code}", env]
136
+ else
137
+ [emit_set_target(target, value_code, env, current_scope), env]
138
+ end
139
+ end
140
+
141
+ def emit_set_expr(args, env, current_scope)
142
+ target = args[0]
143
+ value_code = emit_expr(args[1], env, current_scope)
144
+ emit_set_target(target, value_code, env, current_scope)
145
+ end
146
+
147
+ def emit_set_target(target, value_code, env, current_scope)
148
+ case target
149
+ when Sym
150
+ if target.dotted?
151
+ base_code, segments = multisym_base(target.segments, env)
152
+ runtime_call(:set_method_path, base_code, segments.inspect, value_code)
153
+ else
154
+ "#{env.lookup(target.name)} = #{value_code}"
155
+ end
156
+ when List
157
+ head = target.head
158
+ if head.is_a?(Sym) && head.name == '.'
159
+ object_code = emit_expr(target.items[1], env, current_scope)
160
+ keys_code = "[#{target.items[2..].map { |item| emit_expr(item, env, current_scope) }.join(', ')}]"
161
+ runtime_call(:set_path, object_code, keys_code, value_code)
162
+ elsif head.is_a?(Sym) && head.name == 'ivar'
163
+ runtime_call(:set_ivar, 'self', target.items[1].name.inspect, value_code)
164
+ elsif head.is_a?(Sym) && head.name == 'cvar'
165
+ runtime_call(:set_cvar, 'self', target.items[1].name.inspect, value_code)
166
+ elsif head.is_a?(Sym) && head.name == 'gvar'
167
+ runtime_call(:set_gvar, target.items[1].name.inspect, value_code)
168
+ else
169
+ raise Error, "bad set target: #{target.inspect}"
170
+ end
171
+ else
172
+ raise Error, "bad set target: #{target.inspect}"
173
+ end
174
+ end
175
+ end
176
+ end
177
+ end
178
+ end
@@ -0,0 +1,245 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Kapusta
4
+ module Compiler
5
+ module EmitterModules
6
+ module Collections
7
+ private
8
+
9
+ def emit_icollect(args, env, current_scope)
10
+ result_var = temp('result')
11
+ iter_code = emit_iteration(args[0], env, current_scope) do |iter_env|
12
+ body = emit_sequence(args[1..], iter_env, current_scope, allow_method_definitions: false).first
13
+ <<~RUBY.chomp
14
+ __kap_value = begin
15
+ #{indent(body)}
16
+ end
17
+ #{result_var} << __kap_value unless __kap_value.nil?
18
+ RUBY
19
+ end
20
+ <<~RUBY.chomp
21
+ (-> do
22
+ #{result_var} = []
23
+ #{iter_code}
24
+ #{result_var}
25
+ end).call
26
+ RUBY
27
+ end
28
+
29
+ def emit_collect(args, env, current_scope)
30
+ result_var = temp('result')
31
+ iter_code = emit_iteration(args[0], env, current_scope) do |iter_env|
32
+ body = emit_sequence(args[1..], iter_env, current_scope, allow_method_definitions: false).first
33
+ <<~RUBY.chomp
34
+ __kap_pair = begin
35
+ #{indent(body)}
36
+ end
37
+ if __kap_pair.is_a?(Array) && __kap_pair.length == 2 && !__kap_pair[0].nil? && !__kap_pair[1].nil?
38
+ #{result_var}[__kap_pair[0]] = __kap_pair[1]
39
+ end
40
+ RUBY
41
+ end
42
+ <<~RUBY.chomp
43
+ (-> do
44
+ #{result_var} = {}
45
+ #{iter_code}
46
+ #{result_var}
47
+ end).call
48
+ RUBY
49
+ end
50
+
51
+ def emit_fcollect(args, env, current_scope)
52
+ result_var = temp('result')
53
+ bindings = args[0].items
54
+ ruby_name = temp(sanitize_local(bindings[0].name))
55
+ loop_env = env.child
56
+ loop_env.define(bindings[0].name, ruby_name)
57
+ start_code = emit_expr(bindings[1], env, current_scope)
58
+ finish_code = emit_expr(bindings[2], env, current_scope)
59
+ step_code = '1'
60
+ until_form = nil
61
+ i = 3
62
+ while i < bindings.length
63
+ if bindings[i].is_a?(Sym) && bindings[i].name == '&until'
64
+ until_form = bindings[i + 1]
65
+ i += 2
66
+ else
67
+ step_code = emit_expr(bindings[i], env, current_scope)
68
+ i += 1
69
+ end
70
+ end
71
+ body_code, = emit_sequence(args[1..], loop_env, current_scope, allow_method_definitions: false)
72
+ until_code = until_form ? "break if #{emit_expr(until_form, loop_env, current_scope)}" : nil
73
+ finish_var = temp('finish')
74
+ step_var = temp('step')
75
+ cmp_var = temp('cmp')
76
+ <<~RUBY.chomp
77
+ (-> do
78
+ #{result_var} = []
79
+ #{ruby_name} = #{start_code}
80
+ #{finish_var} = #{finish_code}
81
+ #{step_var} = #{step_code}
82
+ #{cmp_var} = #{step_var} >= 0 ? :<= : :>=
83
+ while #{ruby_name}.public_send(#{cmp_var}, #{finish_var})
84
+ #{until_code}
85
+ __kap_value = begin
86
+ #{indent(body_code)}
87
+ end
88
+ #{result_var} << __kap_value unless __kap_value.nil?
89
+ #{ruby_name} += #{step_var}
90
+ end
91
+ #{result_var}
92
+ end).call
93
+ RUBY
94
+ end
95
+
96
+ def emit_accumulate(args, env, current_scope)
97
+ bindings = args[0].items
98
+ acc_name = bindings[0]
99
+ acc_var = temp(sanitize_local(acc_name.name))
100
+ iter_bindings = Vec.new(bindings[2..])
101
+ loop_env = env.child
102
+ loop_env.define(acc_name.name, acc_var)
103
+ iter_code = emit_iteration(iter_bindings, loop_env, current_scope) do |iter_env|
104
+ iter_env.define(acc_name.name, acc_var)
105
+ emit_sequence(args[1..], iter_env, current_scope, allow_method_definitions: false).first.then do |body|
106
+ "#{acc_var} = begin\n#{indent(body)}\nend"
107
+ end
108
+ end
109
+ <<~RUBY.chomp
110
+ (-> do
111
+ #{acc_var} = #{emit_expr(bindings[1], env, current_scope)}
112
+ #{iter_code}
113
+ #{acc_var}
114
+ end).call
115
+ RUBY
116
+ end
117
+
118
+ def emit_faccumulate(args, env, current_scope)
119
+ bindings = args[0].items
120
+ acc_name = bindings[0]
121
+ acc_var = temp(sanitize_local(acc_name.name))
122
+ loop_name = bindings[2]
123
+ loop_var = temp(sanitize_local(loop_name.name))
124
+ loop_env = env.child
125
+ loop_env.define(acc_name.name, acc_var)
126
+ loop_env.define(loop_name.name, loop_var)
127
+ start_code = emit_expr(bindings[3], env, current_scope)
128
+ finish_code = emit_expr(bindings[4], env, current_scope)
129
+ step_code = bindings[5] ? emit_expr(bindings[5], env, current_scope) : '1'
130
+ body_code, = emit_sequence(args[1..], loop_env, current_scope, allow_method_definitions: false)
131
+ finish_var = temp('finish')
132
+ step_var = temp('step')
133
+ cmp_var = temp('cmp')
134
+ <<~RUBY.chomp
135
+ (-> do
136
+ #{acc_var} = #{emit_expr(bindings[1], env, current_scope)}
137
+ #{loop_var} = #{start_code}
138
+ #{finish_var} = #{finish_code}
139
+ #{step_var} = #{step_code}
140
+ #{cmp_var} = #{step_var} >= 0 ? :<= : :>=
141
+ while #{loop_var}.public_send(#{cmp_var}, #{finish_var})
142
+ #{acc_var} = begin
143
+ #{indent(body_code)}
144
+ end
145
+ #{loop_var} += #{step_var}
146
+ end
147
+ #{acc_var}
148
+ end).call
149
+ RUBY
150
+ end
151
+
152
+ def emit_hashfn(args, env, current_scope)
153
+ args_var = temp('args')
154
+ hash_env = env.child
155
+ hash_env.define('$', "#{args_var}[0]")
156
+ (1..9).each do |index|
157
+ hash_env.define("$#{index}", "#{args_var}[#{index - 1}]")
158
+ end
159
+ hash_env.define('$...', args_var)
160
+ body_code = emit_expr(args[0], hash_env, current_scope)
161
+ <<~RUBY.chomp
162
+ ->(*#{args_var}) do
163
+ #{body_code}
164
+ end
165
+ RUBY
166
+ end
167
+
168
+ def emit_iteration(bindings_vec, env, current_scope)
169
+ items = bindings_vec.items
170
+ iter_expr = items.last
171
+ binding_pats = items[0...-1]
172
+
173
+ if iter_expr.is_a?(List) && iter_expr.head.is_a?(Sym)
174
+ case iter_expr.head.name
175
+ when 'ipairs'
176
+ value_var = temp('value')
177
+ index_var = temp('index')
178
+ body_env = env.child
179
+ bind_code, body_env = emit_iteration_bindings(
180
+ [[binding_pats[0], index_var], [binding_pats[1], value_var]], body_env
181
+ )
182
+ body_code = yield(body_env)
183
+ return <<~RUBY.chomp
184
+ #{emit_expr(iter_expr.items[1], env, current_scope)}.each_with_index do |#{value_var}, #{index_var}|
185
+ #{bind_code}
186
+ #{body_code}
187
+ end
188
+ RUBY
189
+ when 'pairs'
190
+ key_var = temp('key')
191
+ value_var = temp('value')
192
+ body_env = env.child
193
+ bind_code, body_env = emit_iteration_bindings([[binding_pats[0], key_var], [binding_pats[1], value_var]],
194
+ body_env)
195
+ body_code = yield(body_env)
196
+ return <<~RUBY.chomp
197
+ #{emit_expr(iter_expr.items[1], env, current_scope)}.each do |#{key_var}, #{value_var}|
198
+ #{bind_code}
199
+ #{body_code}
200
+ end
201
+ RUBY
202
+ end
203
+ end
204
+
205
+ coll_code = emit_expr(iter_expr, env, current_scope)
206
+ if binding_pats.length == 1
207
+ value_var = temp('value')
208
+ body_env = env.child
209
+ bind_code, body_env = emit_iteration_bindings([[binding_pats[0], value_var]], body_env)
210
+ body_code = yield(body_env)
211
+ <<~RUBY.chomp
212
+ #{coll_code}.each do |#{value_var}|
213
+ #{bind_code}
214
+ #{body_code}
215
+ end
216
+ RUBY
217
+ else
218
+ parts_var = temp('parts')
219
+ body_env = env.child
220
+ pairs = binding_pats.each_with_index.map { |pattern, i| [pattern, "#{parts_var}[#{i}]"] }
221
+ bind_code, body_env = emit_iteration_bindings(pairs, body_env)
222
+ body_code = yield(body_env)
223
+ <<~RUBY.chomp
224
+ #{coll_code}.each do |*#{parts_var}|
225
+ #{bind_code}
226
+ #{body_code}
227
+ end
228
+ RUBY
229
+ end
230
+ end
231
+
232
+ def emit_iteration_bindings(pairs, env)
233
+ current_env = env
234
+ codes = pairs.compact.map do |pattern, value_code|
235
+ next nil unless pattern
236
+
237
+ code, current_env = emit_pattern_bind(pattern, value_code, current_env)
238
+ code
239
+ end.compact
240
+ [codes.join("\n"), current_env]
241
+ end
242
+ end
243
+ end
244
+ end
245
+ end
@@ -0,0 +1,168 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Kapusta
4
+ module Compiler
5
+ module EmitterModules
6
+ module ControlFlow
7
+ private
8
+
9
+ def emit_if(args, env, current_scope)
10
+ build_if(args, env, current_scope)
11
+ end
12
+
13
+ def build_if(args, env, current_scope)
14
+ return 'nil' if args.empty?
15
+ return emit_expr(args[0], env, current_scope) if args.length == 1
16
+
17
+ cond = emit_expr(args[0], env, current_scope)
18
+ truthy = emit_expr(args[1], env, current_scope)
19
+ falsy = build_if(args[2..], env, current_scope)
20
+ <<~RUBY.chomp
21
+ if #{cond}
22
+ #{truthy}
23
+ else
24
+ #{indent(falsy)}
25
+ end
26
+ RUBY
27
+ end
28
+
29
+ def emit_case(args, env, current_scope)
30
+ value_var = temp('case_value')
31
+ body = build_case_clauses(value_var, args[1..], env, current_scope)
32
+ <<~RUBY.chomp
33
+ (-> do
34
+ #{value_var} = #{emit_expr(args[0], env, current_scope)}
35
+ #{body}
36
+ end).call
37
+ RUBY
38
+ end
39
+
40
+ def build_case_clauses(value_var, clauses, env, current_scope)
41
+ return 'nil' if clauses.empty?
42
+
43
+ pattern = clauses[0]
44
+ body = clauses[1]
45
+ else_code = build_case_clauses(value_var, clauses[2..], env, current_scope)
46
+ emit_case_clause(value_var, pattern, body, else_code, env, current_scope)
47
+ end
48
+
49
+ def emit_case_clause(value_var, pattern, body, else_code, env, current_scope)
50
+ if where_pattern?(pattern)
51
+ emit_guarded_case_clause(value_var, pattern, body, else_code, env, current_scope)
52
+ else
53
+ emit_simple_case_clause(value_var, pattern, body, else_code, env, current_scope)
54
+ end
55
+ end
56
+
57
+ def emit_simple_case_clause(value_var, pattern, body, else_code, env, current_scope)
58
+ match_var = temp('match')
59
+ bindings_var = temp('bindings')
60
+ arm_env = env.child
61
+ assign_code, arm_env = emit_bindings_from_match(pattern, bindings_var, arm_env)
62
+ body_code = emit_expr(body, arm_env, current_scope)
63
+ <<~RUBY.chomp
64
+ #{match_var} = #{runtime_call(:match_pattern, emit_pattern(pattern), value_var)}
65
+ if #{match_var}[0]
66
+ #{bindings_var} = #{match_var}[1]
67
+ #{assign_code}
68
+ #{body_code}
69
+ else
70
+ #{indent(else_code)}
71
+ end
72
+ RUBY
73
+ end
74
+
75
+ def emit_guarded_case_clause(value_var, pattern, body, else_code, env, current_scope)
76
+ inner = pattern.items[1]
77
+ guard = pattern.items[2]
78
+ match_var = temp('match')
79
+ bindings_var = temp('bindings')
80
+ arm_env = env.child
81
+ assign_code, arm_env = emit_bindings_from_match(inner, bindings_var, arm_env)
82
+ guard_code = emit_expr(guard, arm_env, current_scope)
83
+ body_code = emit_expr(body, arm_env, current_scope)
84
+ <<~RUBY.chomp
85
+ #{match_var} = #{runtime_call(:match_pattern, emit_pattern(inner), value_var)}
86
+ if #{match_var}[0]
87
+ #{bindings_var} = #{match_var}[1]
88
+ #{assign_code}
89
+ if #{guard_code}
90
+ #{body_code}
91
+ else
92
+ #{indent(else_code, 2)}
93
+ end
94
+ else
95
+ #{indent(else_code)}
96
+ end
97
+ RUBY
98
+ end
99
+
100
+ def emit_while(args, env, current_scope)
101
+ body_code, = emit_sequence(args[1..], env, current_scope, allow_method_definitions: false)
102
+ <<~RUBY.chomp
103
+ (-> do
104
+ while #{emit_expr(args[0], env, current_scope)}
105
+ #{indent(body_code)}
106
+ end
107
+ nil
108
+ end).call
109
+ RUBY
110
+ end
111
+
112
+ def emit_for(args, env, current_scope)
113
+ bindings = args[0].items
114
+ name = bindings[0]
115
+ start_code = emit_expr(bindings[1], env, current_scope)
116
+ finish_code = emit_expr(bindings[2], env, current_scope)
117
+ step_code = '1'
118
+ until_form = nil
119
+ i = 3
120
+ while i < bindings.length
121
+ if bindings[i].is_a?(Sym) && bindings[i].name == '&until'
122
+ until_form = bindings[i + 1]
123
+ i += 2
124
+ else
125
+ step_code = emit_expr(bindings[i], env, current_scope)
126
+ i += 1
127
+ end
128
+ end
129
+
130
+ loop_env = env.child
131
+ ruby_name = temp(sanitize_local(name.name))
132
+ loop_env.define(name.name, ruby_name)
133
+ body_code, = emit_sequence(args[1..], loop_env, current_scope, allow_method_definitions: false)
134
+ until_code = until_form ? "break if #{emit_expr(until_form, loop_env, current_scope)}" : nil
135
+ cmp_var = temp('cmp')
136
+ step_var = temp('step')
137
+ finish_var = temp('finish')
138
+ <<~RUBY.chomp
139
+ (-> do
140
+ #{ruby_name} = #{start_code}
141
+ #{finish_var} = #{finish_code}
142
+ #{step_var} = #{step_code}
143
+ #{cmp_var} = #{step_var} >= 0 ? :<= : :>=
144
+ while #{ruby_name}.public_send(#{cmp_var}, #{finish_var})
145
+ #{until_code}
146
+ #{indent(body_code)}
147
+ #{ruby_name} += #{step_var}
148
+ end
149
+ nil
150
+ end).call
151
+ RUBY
152
+ end
153
+
154
+ def emit_each(args, env, current_scope)
155
+ iter_code = emit_iteration(args[0], env, current_scope) do |iter_env|
156
+ emit_sequence(args[1..], iter_env, current_scope, allow_method_definitions: false).first
157
+ end
158
+ <<~RUBY.chomp
159
+ (-> do
160
+ #{iter_code}
161
+ nil
162
+ end).call
163
+ RUBY
164
+ end
165
+ end
166
+ end
167
+ end
168
+ end