kapusta 0.1.3 → 0.1.5

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 2398c1c908306ac808914717a67e74f8085ee6048deb9c9dd665f4dd8c39cc2f
4
- data.tar.gz: 1022c2df415418d26f1b84b1b16830cc525829182a82e93c1a450d0dd4b5bdd2
3
+ metadata.gz: 52369f7a4cd658385e7ddc69f552477d94b55340cb9f796ac635550a7def6e6a
4
+ data.tar.gz: 6d673c9c387b52b0e4cd9e55b8ae2cf1d0a7e2c5f741dc7705f405b0ddaf25c6
5
5
  SHA512:
6
- metadata.gz: 6a31f0bab47d9c5c0d40720c52422717e0d04a9c03bb03a115ac62c7f541226dc3d9b7e9aa052bf55d574e1193ecab82e97b0610d43bbe1702038024486a1953
7
- data.tar.gz: f20bf20a890d1b23f3ec7d2d113a3340c6255132a367a655cddab52ded9e2276bd236439d242ca0ea46f857096a5f5885620dd9a104f8b0f337c9aba75e50366
6
+ metadata.gz: 53510507ba41a4d67ec73184eaef665fee182220407046eb9db64cf05cae7d28a315b5b8cf4ace6071b359a83801bd85d7e45ce5e4aefd3c608fef614f116fee
7
+ data.tar.gz: ff1b88fc11dc10ccffc1459686b17754bd39dc6a941756d7cab304253aec3b63bc36967c9edcf50cf4c29eadee3ae2afb0467383b6428dcb74f41c452173ab4a
data/README.md CHANGED
@@ -41,6 +41,7 @@ Kapusta keeps most core Fennel forms. The main differences come from Ruby's runt
41
41
  | `string.format`, `table.insert`, etc. | use Ruby methods and stdlib instead |
42
42
  | `values` uses Lua multiple returns | `values` lowers to a Ruby array, usually destructured |
43
43
  | `with-open`, `tail!` | not provided |
44
+ | macros | not provided for now |
44
45
 
45
46
  Kapusta-specific additions:
46
47
 
@@ -0,0 +1,24 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
5
+ SOURCE_DIR="$ROOT_DIR/examples"
6
+ TARGET_DIR="$ROOT_DIR/examples-compiled"
7
+
8
+ mkdir -p "$TARGET_DIR"
9
+
10
+ shopt -s nullglob
11
+ kap_files=("$SOURCE_DIR"/*.kap)
12
+
13
+ if ((${#kap_files[@]} == 0)); then
14
+ printf 'No .kap files found in %s\n' "$SOURCE_DIR" >&2
15
+ exit 0
16
+ fi
17
+
18
+ for kap_file in "${kap_files[@]}"; do
19
+ name="$(basename "$kap_file" .kap)"
20
+ ruby_file="$TARGET_DIR/$name.rb"
21
+
22
+ "$ROOT_DIR/exe/kapusta" --compile "$kap_file" > "$ruby_file"
23
+ printf 'Compiled %s -> %s\n' "$kap_file" "$ruby_file"
24
+ done
@@ -0,0 +1,7 @@
1
+ (fn contains-duplicate? [nums]
2
+ (let [seen (collect [n nums] (values n true))]
3
+ (< (length seen) (length nums))))
4
+
5
+ (print (contains-duplicate? [1 2 3 1]))
6
+ (print (contains-duplicate? [1 2 3 4]))
7
+ (print (contains-duplicate? [1 1 1 3 3 4 3 2 4 2]))
@@ -13,9 +13,8 @@ module Kapusta
13
13
  name_sym = args[0]
14
14
  pattern = args[1]
15
15
  body = args[2..]
16
- ruby_name = temp(sanitize_local(name_sym.name))
17
16
  fn_env = env.child
18
- fn_env.define(name_sym.name, ruby_name)
17
+ ruby_name = define_local(fn_env, name_sym.name)
19
18
  <<~RUBY.chomp
20
19
  (-> do
21
20
  #{ruby_name} = nil
@@ -27,22 +26,65 @@ module Kapusta
27
26
  end
28
27
 
29
28
  def emit_lambda(pattern, body, env, current_scope)
29
+ return emit_simple_lambda(pattern, body, env, current_scope) if simple_parameter_pattern?(pattern)
30
+
30
31
  args_var = temp('args')
31
32
  body_env = env.child
32
33
  bindings_code, body_env = emit_pattern_bind(pattern, args_var, body_env)
33
34
  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
35
+ block_locals = pattern_names(pattern).map { |name| body_env.lookup(name) }.uniq
36
+ block_locals_clause = block_locals.empty? ? '' : "; #{block_locals.join(', ')}"
37
+ [
38
+ "->(*#{args_var}#{block_locals_clause}) do",
39
+ indent(join_code(bindings_code, body_code)),
40
+ 'end'
41
+ ].join("\n")
42
+ end
43
+
44
+ def emit_simple_lambda(pattern, body, env, current_scope)
45
+ body_env = env.child
46
+ params = pattern.items.map { |sym| define_local(body_env, sym.name, shadow: true) }
47
+ body_code, = emit_sequence(body, body_env, current_scope, allow_method_definitions: false)
48
+ header = params.empty? ? 'proc do' : "proc do |#{params.join(', ')}|"
49
+ [
50
+ header,
51
+ indent(body_code),
52
+ 'end'
53
+ ].join("\n")
54
+ end
55
+
56
+ def simple_parameter_pattern?(pattern)
57
+ pattern.is_a?(Vec) && pattern.items.all? { |item| item.is_a?(Sym) && !item.dotted? && item.name != '&' }
58
+ end
59
+
60
+ def emit_definition_form(form, env, current_scope)
61
+ return [emit_method_definition(form, env), env] unless current_scope == :toplevel
62
+
63
+ emit_toplevel_method_definition(form, env)
64
+ end
65
+
66
+ def emit_toplevel_method_definition(form, env)
67
+ name_sym = form.items[1]
68
+ pattern = form.items[2]
69
+ body = form.items[3..]
70
+ return [nil, env] if name_sym.dotted?
71
+ return [nil, env] unless simple_parameter_pattern?(pattern)
72
+
73
+ ruby_name = direct_method_definition_name(name_sym)
74
+ return [nil, env] unless ruby_name
75
+ return [nil, env] if captures_outer_binding?(body, env, pattern_names(pattern))
76
+
77
+ env.define(name_sym.name, Env::MethodBinding.new(ruby_name))
78
+ definition = emit_direct_method_definition(name_sym, pattern, body, env)
79
+ if needs_toplevel_method_bridge?(ruby_name)
80
+ definition = join_code(definition, emit_toplevel_method_bridge(ruby_name))
81
+ end
82
+ [definition, env]
40
83
  end
41
84
 
42
85
  def emit_named_fn_assignment(form, env, current_scope)
43
86
  name_sym = form.items[1]
44
- ruby_name = temp(sanitize_local(name_sym.name))
45
- env.define(name_sym.name, ruby_name)
87
+ ruby_name = define_local(env, name_sym.name)
46
88
  fn_env = env.child
47
89
  fn_env.define(name_sym.name, ruby_name)
48
90
  lambda_code = emit_lambda(form.items[2], form.items[3..], fn_env, current_scope)
@@ -53,31 +95,131 @@ module Kapusta
53
95
  name_sym = form.items[1]
54
96
  pattern = form.items[2]
55
97
  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)
98
+ direct_definition = emit_direct_method_definition(name_sym, pattern, body, env)
99
+ return direct_definition if direct_definition
100
+
101
+ block_header, body_code = emit_method_body(pattern, body, env)
60
102
 
61
103
  if name_sym.name.start_with?('self.')
62
104
  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
105
+ [
106
+ "define_singleton_method(#{ruby_name}) #{block_header}",
107
+ indent(body_code),
108
+ 'end'
109
+ ].join("\n")
69
110
  else
70
111
  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
112
+ [
113
+ "define_method(#{ruby_name}) #{block_header}",
114
+ indent(body_code),
115
+ 'end'
116
+ ].join("\n")
117
+ end
118
+ end
119
+
120
+ def emit_direct_method_definition(name_sym, pattern, body, env)
121
+ return unless simple_parameter_pattern?(pattern)
122
+
123
+ ruby_name = direct_method_definition_name(name_sym)
124
+ return unless ruby_name
125
+ return if captures_outer_binding?(body, env, pattern_names(pattern))
126
+
127
+ body_env = env.child
128
+ params = pattern.items.map { |sym| define_local(body_env, sym.name, shadow: true) }
129
+ body_code, = emit_sequence(body, body_env, :toplevel, allow_method_definitions: false)
130
+ header = params.empty? ? "def #{ruby_name}" : "def #{ruby_name}(#{params.join(', ')})"
131
+ [
132
+ header,
133
+ indent(body_code),
134
+ 'end'
135
+ ].join("\n")
136
+ end
137
+
138
+ def direct_method_definition_name(name_sym)
139
+ source_name = name_sym.name
140
+ if source_name.start_with?('self.')
141
+ method_name = Kapusta.kebab_to_snake(source_name.delete_prefix('self.'))
142
+ return unless direct_method_name?(method_name)
143
+
144
+ "self.#{method_name}"
145
+ else
146
+ method_name = Kapusta.kebab_to_snake(source_name)
147
+ return unless direct_method_name?(method_name)
148
+
149
+ method_name
77
150
  end
78
151
  end
79
152
 
153
+ def needs_toplevel_method_bridge?(ruby_name)
154
+ %w[context describe example it specify].include?(ruby_name)
155
+ end
156
+
157
+ def emit_toplevel_method_bridge(ruby_name)
158
+ method_name = ruby_name.to_sym.inspect
159
+ "define_singleton_method(#{method_name}, Object.instance_method(#{method_name}).bind(self))"
160
+ end
161
+
162
+ def emit_method_body(pattern, body, env)
163
+ return emit_simple_method_body(pattern, body, env) if simple_parameter_pattern?(pattern)
164
+
165
+ args_var = temp('args')
166
+ method_env = env.child
167
+ bindings_code, body_env = emit_pattern_bind(pattern, args_var, method_env)
168
+ body_code, = emit_sequence(body, body_env, :toplevel, allow_method_definitions: false)
169
+ block_locals = pattern_names(pattern).map { |name| body_env.lookup(name) }.uniq
170
+ block_locals_clause = block_locals.empty? ? '' : "; #{block_locals.join(', ')}"
171
+ ["do |*#{args_var}#{block_locals_clause}|", join_code(bindings_code, body_code)]
172
+ end
173
+
174
+ def emit_simple_method_body(pattern, body, env)
175
+ body_env = env.child
176
+ params = pattern.items.map { |sym| define_local(body_env, sym.name, shadow: true) }
177
+ body_code, = emit_sequence(body, body_env, :toplevel, allow_method_definitions: false)
178
+ [params.empty? ? 'do' : "do |#{params.join(', ')}|", body_code]
179
+ end
180
+
181
+ def captures_outer_binding?(forms, env, local_names)
182
+ forms.any? { |form| form_captures_outer_binding?(form, env, local_names) }
183
+ end
184
+
185
+ def form_captures_outer_binding?(form, env, local_names)
186
+ case form
187
+ when Sym
188
+ sym_captures_outer_binding?(form, env, local_names)
189
+ when Vec, List
190
+ form.items.any? { |item| form_captures_outer_binding?(item, env, local_names) }
191
+ when HashLit
192
+ form.pairs.any? do |key, value|
193
+ form_captures_outer_binding?(key, env, local_names) ||
194
+ form_captures_outer_binding?(value, env, local_names)
195
+ end
196
+ else
197
+ false
198
+ end
199
+ end
200
+
201
+ def sym_captures_outer_binding?(sym, env, local_names)
202
+ name = sym.dotted? ? sym.segments.first : sym.name
203
+ return false if local_names.include?(name) || !env.defined?(name)
204
+
205
+ !method_binding?(env.lookup(name))
206
+ end
207
+
80
208
  def emit_let(args, env, current_scope)
209
+ binding_code, body_code = emit_let_parts(args, env, current_scope, result: true)
210
+ <<~RUBY.chomp
211
+ (-> do
212
+ #{indent(join_code(binding_code, body_code))}
213
+ end).call
214
+ RUBY
215
+ end
216
+
217
+ def emit_let_statement(args, env, current_scope)
218
+ binding_code, body_code = emit_let_parts(args, env, current_scope, result: false)
219
+ join_code(binding_code, body_code)
220
+ end
221
+
222
+ def emit_let_parts(args, env, current_scope, result:)
81
223
  bindings = args[0]
82
224
  body = args[1..]
83
225
  child_env = env.child
@@ -91,13 +233,14 @@ module Kapusta
91
233
  binding_codes << bind_code
92
234
  i += 2
93
235
  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
236
+ body_code, = emit_sequence(body, child_env, current_scope,
237
+ allow_method_definitions: false,
238
+ result:)
239
+ [binding_codes.join("\n"), body_code]
240
+ end
241
+
242
+ def join_code(*chunks)
243
+ chunks.reject(&:empty?).join("\n")
101
244
  end
102
245
 
103
246
  def emit_local_form(form, env, current_scope)
@@ -105,8 +248,7 @@ module Kapusta
105
248
  value_code = emit_expr(form.items[2], env, current_scope)
106
249
 
107
250
  if target.is_a?(Sym)
108
- ruby_name = temp(sanitize_local(target.name))
109
- env.define(target.name, ruby_name)
251
+ ruby_name = define_local(env, target.name)
110
252
  ["#{ruby_name} = #{value_code}\nnil", env]
111
253
  else
112
254
  bind_code, env = emit_pattern_bind(target, value_code, env)
@@ -126,11 +268,12 @@ module Kapusta
126
268
  if target.is_a?(Sym) && !target.dotted?
127
269
  ruby_name =
128
270
  if env.defined?(target.name)
129
- env.lookup(target.name)
271
+ binding = env.lookup(target.name)
272
+ raise Error, "cannot set method binding: #{target.name}" if method_binding?(binding)
273
+
274
+ binding
130
275
  else
131
- fresh = temp(sanitize_local(target.name))
132
- env.define(target.name, fresh)
133
- fresh
276
+ define_local(env, target.name)
134
277
  end
135
278
  ["#{ruby_name} = #{value_code}", env]
136
279
  else
@@ -151,7 +294,10 @@ module Kapusta
151
294
  base_code, segments = multisym_base(target.segments, env)
152
295
  runtime_call(:set_method_path, base_code, segments.inspect, value_code)
153
296
  else
154
- "#{env.lookup(target.name)} = #{value_code}"
297
+ binding = env.lookup(target.name)
298
+ raise Error, "cannot set method binding: #{target.name}" if method_binding?(binding)
299
+
300
+ "#{binding} = #{value_code}"
155
301
  end
156
302
  when List
157
303
  head = target.head
@@ -164,7 +310,12 @@ module Kapusta
164
310
  elsif head.is_a?(Sym) && head.name == 'cvar'
165
311
  runtime_call(:set_cvar, 'self', target.items[1].name.inspect, value_code)
166
312
  elsif head.is_a?(Sym) && head.name == 'gvar'
167
- runtime_call(:set_gvar, target.items[1].name.inspect, value_code)
313
+ ruby_name = global_name(target.items[1].name)
314
+ if direct_global_name?(ruby_name)
315
+ "$#{ruby_name} = #{value_code}"
316
+ else
317
+ runtime_call(:set_gvar, target.items[1].name.inspect, value_code)
318
+ end
168
319
  else
169
320
  raise Error, "bad set target: #{target.inspect}"
170
321
  end
@@ -10,75 +10,39 @@ module Kapusta
10
10
  result_var = temp('result')
11
11
  iter_code = emit_iteration(args[0], env, current_scope) do |iter_env|
12
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
13
+ emit_array_collection_step(result_var, body)
19
14
  end
20
- <<~RUBY.chomp
21
- (-> do
22
- #{result_var} = []
23
- #{iter_code}
24
- #{result_var}
25
- end).call
26
- RUBY
15
+ emit_collection_result(result_var, '[]', iter_code)
27
16
  end
28
17
 
29
18
  def emit_collect(args, env, current_scope)
30
19
  result_var = temp('result')
31
20
  iter_code = emit_iteration(args[0], env, current_scope) do |iter_env|
32
21
  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
22
+ emit_hash_collection_step(result_var, body)
41
23
  end
42
- <<~RUBY.chomp
43
- (-> do
44
- #{result_var} = {}
45
- #{iter_code}
46
- #{result_var}
47
- end).call
48
- RUBY
24
+ emit_collection_result(result_var, '{}', iter_code)
49
25
  end
50
26
 
51
27
  def emit_fcollect(args, env, current_scope)
52
28
  result_var = temp('result')
53
29
  parsed = parse_counted_for_bindings(args[0].items, env, current_scope)
54
30
  body_code, = emit_sequence(args[1..], parsed[:loop_env], current_scope, allow_method_definitions: false)
55
- collecting_body = <<~RUBY.chomp
56
- __kap_value = begin
57
- #{indent(body_code)}
58
- end
59
- #{result_var} << __kap_value unless __kap_value.nil?
60
- RUBY
31
+ collecting_body = emit_array_collection_step(result_var, body_code)
61
32
  loop_code = emit_counted_loop(**parsed, current_scope:, body_code: collecting_body)
62
- <<~RUBY.chomp
63
- (-> do
64
- #{result_var} = []
65
- #{indent(loop_code)}
66
- #{result_var}
67
- end).call
68
- RUBY
33
+ emit_collection_result(result_var, '[]', loop_code)
69
34
  end
70
35
 
71
36
  def emit_accumulate(args, env, current_scope)
72
37
  bindings = args[0].items
73
38
  acc_name = bindings[0]
74
- acc_var = temp(sanitize_local(acc_name.name))
75
39
  iter_bindings = Vec.new(bindings[2..])
76
40
  loop_env = env.child
77
- loop_env.define(acc_name.name, acc_var)
41
+ acc_var = define_local(loop_env, acc_name.name)
78
42
  iter_code = emit_iteration(iter_bindings, loop_env, current_scope) do |iter_env|
79
43
  iter_env.define(acc_name.name, acc_var)
80
44
  emit_sequence(args[1..], iter_env, current_scope, allow_method_definitions: false).first.then do |body|
81
- "#{acc_var} = begin\n#{indent(body)}\nend"
45
+ emit_sequence_value_assignment(acc_var, body)
82
46
  end
83
47
  end
84
48
  <<~RUBY.chomp
@@ -93,14 +57,12 @@ module Kapusta
93
57
  def emit_faccumulate(args, env, current_scope)
94
58
  bindings = args[0].items
95
59
  acc_name = bindings[0]
96
- acc_var = temp(sanitize_local(acc_name.name))
97
60
  loop_name = bindings[2]
98
- loop_var = temp(sanitize_local(loop_name.name))
99
61
  loop_env = env.child
100
- loop_env.define(acc_name.name, acc_var)
101
- loop_env.define(loop_name.name, loop_var)
62
+ acc_var = define_local(loop_env, acc_name.name)
63
+ loop_var = define_local(loop_env, loop_name.name)
102
64
  body_code, = emit_sequence(args[1..], loop_env, current_scope, allow_method_definitions: false)
103
- accumulating_body = "#{acc_var} = begin\n#{indent(body_code)}\nend"
65
+ accumulating_body = emit_sequence_value_assignment(acc_var, body_code)
104
66
  loop_code = emit_counted_loop(
105
67
  ruby_name: loop_var,
106
68
  start_code: emit_expr(bindings[3], env, current_scope),
@@ -210,6 +172,42 @@ module Kapusta
210
172
  end.compact
211
173
  [codes.join("\n"), current_env]
212
174
  end
175
+
176
+ def emit_collection_result(result_var, initial_code, iter_code)
177
+ <<~RUBY.chomp
178
+ (-> do
179
+ #{result_var} = #{initial_code}
180
+ #{indent(iter_code)}
181
+ #{result_var}
182
+ end).call
183
+ RUBY
184
+ end
185
+
186
+ def emit_array_collection_step(result_var, body_code)
187
+ value_var = temp('value')
188
+ <<~RUBY.chomp
189
+ #{emit_sequence_value_assignment(value_var, body_code)}
190
+ #{result_var} << #{value_var} unless #{value_var}.nil?
191
+ RUBY
192
+ end
193
+
194
+ def emit_hash_collection_step(result_var, body_code)
195
+ pair_var = temp('pair')
196
+ <<~RUBY.chomp
197
+ #{emit_sequence_value_assignment(pair_var, body_code)}
198
+ if #{pair_var}.is_a?(Array) && #{pair_var}.length == 2 && !#{pair_var}[0].nil? && !#{pair_var}[1].nil?
199
+ #{result_var}[#{pair_var}[0]] = #{pair_var}[1]
200
+ end
201
+ RUBY
202
+ end
203
+
204
+ def emit_sequence_value_assignment(target_var, body_code)
205
+ <<~RUBY.chomp
206
+ #{target_var} = begin
207
+ #{indent(body_code)}
208
+ end
209
+ RUBY
210
+ end
213
211
  end
214
212
  end
215
213
  end
@@ -16,14 +16,35 @@ module Kapusta
16
16
 
17
17
  cond = emit_expr(args[0], env, current_scope)
18
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
19
+ lines = ["if #{cond}", indent(truthy)]
20
+ append_else_lines(lines, args[2..], env, current_scope)
21
+ lines << 'end'
22
+ lines.join("\n")
23
+ end
24
+
25
+ def append_else_lines(lines, args, env, current_scope)
26
+ return if args.empty?
27
+
28
+ if args.length == 1 && if_form?(args[0])
29
+ append_elsif_lines(lines, args[0].rest, env, current_scope)
30
+ elsif args.length >= 2
31
+ append_elsif_lines(lines, args, env, current_scope)
32
+ else
33
+ lines << 'else'
34
+ lines << indent(emit_expr(args[0], env, current_scope))
35
+ end
36
+ end
37
+
38
+ def append_elsif_lines(lines, args, env, current_scope)
39
+ return append_else_lines(lines, args, env, current_scope) if args.length < 2
40
+
41
+ lines << "elsif #{emit_expr(args[0], env, current_scope)}"
42
+ lines << indent(emit_expr(args[1], env, current_scope))
43
+ append_else_lines(lines, args[2..], env, current_scope)
44
+ end
45
+
46
+ def if_form?(form)
47
+ form.is_a?(List) && form.head.is_a?(Sym) && form.head.name == 'if'
27
48
  end
28
49
 
29
50
  def emit_case(args, env, current_scope, mode)
@@ -106,21 +127,16 @@ module Kapusta
106
127
  end
107
128
 
108
129
  def emit_while(args, env, current_scope)
109
- body_code, = emit_sequence(args[1..], env, current_scope, allow_method_definitions: false)
110
130
  <<~RUBY.chomp
111
131
  (-> do
112
- while #{emit_expr(args[0], env, current_scope)}
113
- #{indent(body_code)}
114
- end
132
+ #{indent(emit_while_statement(args, env, current_scope))}
115
133
  nil
116
134
  end).call
117
135
  RUBY
118
136
  end
119
137
 
120
138
  def emit_for(args, env, current_scope)
121
- parsed = parse_counted_for_bindings(args[0].items, env, current_scope)
122
- body_code, = emit_sequence(args[1..], parsed[:loop_env], current_scope, allow_method_definitions: false)
123
- loop_code = emit_counted_loop(**parsed, current_scope:, body_code:)
139
+ loop_code = emit_for_statement(args, env, current_scope)
124
140
  <<~RUBY.chomp
125
141
  (-> do
126
142
  #{indent(loop_code)}
@@ -130,9 +146,7 @@ module Kapusta
130
146
  end
131
147
 
132
148
  def emit_each(args, env, current_scope)
133
- iter_code = emit_iteration(args[0], env, current_scope) do |iter_env|
134
- emit_sequence(args[1..], iter_env, current_scope, allow_method_definitions: false).first
135
- end
149
+ iter_code = emit_each_statement(args, env, current_scope)
136
150
  <<~RUBY.chomp
137
151
  (-> do
138
152
  #{iter_code}
@@ -140,6 +154,33 @@ module Kapusta
140
154
  end).call
141
155
  RUBY
142
156
  end
157
+
158
+ def emit_while_statement(args, env, current_scope)
159
+ body_code, = emit_sequence(args[1..], env, current_scope,
160
+ allow_method_definitions: false,
161
+ result: false)
162
+ [
163
+ "while #{emit_expr(args[0], env, current_scope)}",
164
+ indent(body_code),
165
+ 'end'
166
+ ].join("\n")
167
+ end
168
+
169
+ def emit_for_statement(args, env, current_scope)
170
+ parsed = parse_counted_for_bindings(args[0].items, env, current_scope)
171
+ body_code, = emit_sequence(args[1..], parsed[:loop_env], current_scope,
172
+ allow_method_definitions: false,
173
+ result: false)
174
+ emit_counted_loop(**parsed, current_scope:, body_code:)
175
+ end
176
+
177
+ def emit_each_statement(args, env, current_scope)
178
+ emit_iteration(args[0], env, current_scope) do |iter_env|
179
+ emit_sequence(args[1..], iter_env, current_scope,
180
+ allow_method_definitions: false,
181
+ result: false).first
182
+ end
183
+ end
143
184
  end
144
185
  end
145
186
  end