kapusta 0.4.1 → 0.7.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.
@@ -4,24 +4,58 @@ module Kapusta
4
4
  module Compiler
5
5
  module EmitterModules
6
6
  module Collections
7
+ include LuaCompat::Emission
8
+
7
9
  private
8
10
 
9
11
  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
- emit_array_collection_step(result_var, body)
12
+ emit_error!(:icollect_no_iterator) unless args[0].is_a?(Vec) && args[0].items.length >= 2
13
+
14
+ emit_iteration(args[0], env, current_scope, method: 'filter_map') do |iter_env|
15
+ emit_sequence(args[1..], iter_env, current_scope, allow_method_definitions: false).first
14
16
  end
15
- emit_collection_result(result_var, '[]', iter_code)
16
17
  end
17
18
 
18
19
  def emit_collect(args, env, current_scope)
19
20
  result_var = temp('result')
20
- iter_code = emit_iteration(args[0], env, current_scope) do |iter_env|
21
- body = emit_sequence(args[1..], iter_env, current_scope, allow_method_definitions: false).first
22
- emit_hash_collection_step(result_var, body)
21
+ values_form = simple_values_call(args[1]) if args.length == 2
22
+ emit_iteration(args[0], env, current_scope,
23
+ method: 'each_with_object({})', extra_block_param: result_var) do |iter_env|
24
+ if values_form
25
+ emit_collect_values_step(result_var, values_form, iter_env, current_scope)
26
+ else
27
+ body = emit_sequence(args[1..], iter_env, current_scope, allow_method_definitions: false).first
28
+ emit_hash_collection_step(result_var, body)
29
+ end
30
+ end
31
+ end
32
+
33
+ def simple_values_call(form)
34
+ return unless form.is_a?(List) && form.items.length == 3
35
+
36
+ head = form.head
37
+ form if head.is_a?(Sym) && head.name == 'values'
38
+ end
39
+
40
+ def emit_collect_values_step(result_var, values_form, iter_env, current_scope)
41
+ key_form = values_form.items[1]
42
+ val_form = values_form.items[2]
43
+ key_code = emit_expr(key_form, iter_env, current_scope)
44
+ val_code = emit_expr(val_form, iter_env, current_scope)
45
+ assignment = "#{result_var}[#{key_code}] = #{val_code}"
46
+ guards = []
47
+ guards << "#{key_code}.nil?" unless definitely_non_nil?(key_form)
48
+ guards << "#{val_code}.nil?" unless definitely_non_nil?(val_form)
49
+ return assignment if guards.empty?
50
+
51
+ "#{assignment} unless #{guards.join(' || ')}"
52
+ end
53
+
54
+ def definitely_non_nil?(form)
55
+ case form
56
+ when Numeric, String, ::Symbol, TrueClass, FalseClass then true
57
+ else false
23
58
  end
24
- emit_collection_result(result_var, '{}', iter_code)
25
59
  end
26
60
 
27
61
  def emit_fcollect(args, env, current_scope)
@@ -35,27 +69,48 @@ module Kapusta
35
69
 
36
70
  def emit_accumulate(args, env, current_scope)
37
71
  bindings = args[0].items
72
+ emit_error!(:accumulate_no_iterator) if bindings.length < 4
73
+
38
74
  acc_name = bindings[0]
39
- iter_bindings = Vec.new(bindings[2..])
40
- loop_env = env.child
41
- acc_var = define_local(loop_env, acc_name.name)
42
- iter_code = emit_iteration(iter_bindings, loop_env, current_scope) do |iter_env|
43
- iter_env.define(acc_name.name, acc_var)
44
- emit_sequence(args[1..], iter_env, current_scope, allow_method_definitions: false).first.then do |body|
45
- emit_sequence_value_assignment(acc_var, body)
46
- end
75
+ init_code = emit_expr(bindings[1], env, current_scope)
76
+ iter_items = bindings[2..]
77
+ iter_expr = iter_items.last
78
+ binding_pats = iter_items[0...-1]
79
+
80
+ body_env = env.child
81
+ acc_var = define_local(body_env, acc_name.name)
82
+
83
+ inject_code = try_emit_inject(iter_expr, binding_pats, body_env, env, current_scope, acc_var,
84
+ init_code, args[1..])
85
+ return inject_code if inject_code
86
+
87
+ iter_code = emit_iteration(Vec.new(iter_items), body_env, current_scope) do |iter_env|
88
+ body_code, = emit_sequence(args[1..], iter_env, current_scope, allow_method_definitions: false)
89
+ emit_sequence_value_assignment(acc_var, body_code)
47
90
  end
48
91
  [
49
92
  '(-> do',
50
- indent("#{acc_var} = #{emit_expr(bindings[1], env, current_scope)}"),
93
+ indent("#{acc_var} = #{init_code}"),
51
94
  indent(iter_code),
52
95
  indent(acc_var),
53
96
  'end).call'
54
97
  ].join("\n")
55
98
  end
56
99
 
100
+ def try_emit_inject(iter_expr, binding_pats, body_env, env, current_scope, acc_var, init_code, body_forms)
101
+ emit_lua_compat_inject(iter_expr, binding_pats, body_env, env, current_scope, acc_var,
102
+ init_code, body_forms)
103
+ end
104
+
105
+ def inject_block(receiver, params, init_code, bind_code, body_code)
106
+ inner = join_code(bind_code, body_code)
107
+ ["#{receiver}.inject(#{init_code}) do |#{params}|", indent(inner), 'end'].join("\n")
108
+ end
109
+
57
110
  def emit_faccumulate(args, env, current_scope)
58
111
  bindings = args[0].items
112
+ emit_error!(:accumulate_no_iterator) if bindings.length < 5
113
+
59
114
  acc_name = bindings[0]
60
115
  loop_name = bindings[2]
61
116
  loop_env = env.child
@@ -112,51 +167,32 @@ module Kapusta
112
167
  end
113
168
  end
114
169
 
115
- def emit_iteration(bindings_vec, env, current_scope)
170
+ def emit_iteration(bindings_vec, env, current_scope, method: 'each', extra_block_param: nil, &block)
171
+ emit_error!(:each_no_binding) unless bindings_vec.is_a?(Vec)
172
+
116
173
  items = bindings_vec.items
117
174
  iter_expr = items.last
118
175
  binding_pats = items[0...-1]
119
176
 
120
- if iter_expr.is_a?(List) && iter_expr.head.is_a?(Sym)
121
- case iter_expr.head.name
122
- when 'ipairs'
123
- body_env = env.child
124
- value_var, value_bind = bind_iteration_param(binding_pats[1], 'value', body_env)
125
- coll_code = emit_expr(iter_expr.items[1], env, current_scope)
126
- if ignored_pattern?(binding_pats[0])
127
- bind_code = value_bind || ''
128
- body_code = yield(body_env)
129
- return iteration_block("#{coll_code}.each do |#{value_var}|", bind_code, body_code)
130
- end
131
- index_var, index_bind = bind_iteration_param(binding_pats[0], 'index', body_env)
132
- bind_code = [index_bind, value_bind].compact.join("\n")
133
- body_code = yield(body_env)
134
- header = "#{coll_code}.each_with_index do |#{value_var}, #{index_var}|"
135
- return iteration_block(header, bind_code, body_code)
136
- when 'pairs'
137
- body_env = env.child
138
- key_var, key_bind = bind_iteration_param(binding_pats[0], 'key', body_env)
139
- value_var, value_bind = bind_iteration_param(binding_pats[1], 'value', body_env)
140
- bind_code = [key_bind, value_bind].compact.join("\n")
141
- body_code = yield(body_env)
142
- header = "#{emit_expr(iter_expr.items[1], env, current_scope)}.each do |#{key_var}, #{value_var}|"
143
- return iteration_block(header, bind_code, body_code)
144
- end
145
- end
177
+ lua_iteration = emit_lua_compat_iteration(iter_expr, binding_pats, env, current_scope,
178
+ method:, extra_block_param:, &block)
179
+ return lua_iteration if lua_iteration
146
180
 
147
181
  coll_code = emit_expr(iter_expr, env, current_scope)
148
182
  if binding_pats.length == 1
149
183
  body_env = env.child
150
184
  value_var, bind_code = bind_iteration_param(binding_pats[0], 'value', body_env)
151
- body_code = yield(body_env)
152
- iteration_block("#{coll_code}.each do |#{value_var}|", bind_code || '', body_code)
185
+ body_code = block.call(body_env)
186
+ params = extra_block_param ? "#{value_var}, #{extra_block_param}" : value_var
187
+ iteration_block("#{coll_code}.#{method} do |#{params}|", bind_code || '', body_code)
153
188
  else
154
189
  parts_var = temp('parts')
155
190
  body_env = env.child
156
191
  pairs = binding_pats.each_with_index.map { |pattern, i| [pattern, "#{parts_var}[#{i}]"] }
157
192
  bind_code, body_env = emit_iteration_bindings(pairs, body_env)
158
- body_code = yield(body_env)
159
- iteration_block("#{coll_code}.each do |*#{parts_var}|", bind_code, body_code)
193
+ body_code = block.call(body_env)
194
+ params = extra_block_param ? "#{parts_var}, #{extra_block_param}" : "*#{parts_var}"
195
+ iteration_block("#{coll_code}.#{method} do |#{params}|", bind_code, body_code)
160
196
  end
161
197
  end
162
198
 
@@ -11,8 +11,7 @@ module Kapusta
11
11
  end
12
12
 
13
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
14
+ emit_error!(:if_no_body) if args.length < 2
16
15
 
17
16
  cond = emit_expr(args[0], env, current_scope)
18
17
  truthy = emit_if_branch(args[1], env, current_scope)
@@ -58,17 +57,38 @@ module Kapusta
58
57
  end
59
58
 
60
59
  def emit_case(args, env, current_scope, mode)
60
+ emit_error!(:case_no_subject) if args.empty?
61
+
62
+ clauses = args[1..]
63
+ emit_error!(:case_no_patterns) if clauses.empty?
64
+ emit_error!(:case_odd_patterns) if clauses.length.odd?
65
+
66
+ value_code = emit_expr(args[0], env, current_scope)
67
+ if simple_case_subject?(args[0]) && simple_expression?(value_code)
68
+ body = try_emit_native_case(value_code, clauses, env, current_scope, mode)
69
+ emit_error!(:case_unsupported) unless body
70
+ return body
71
+ end
72
+
61
73
  value_var = temp('case_value')
62
- body = try_emit_native_case(value_var, args[1..], env, current_scope, mode)
63
- emit_error!('case/match clauses use patterns this compiler cannot translate') unless body
74
+ body = try_emit_native_case(value_var, clauses, env, current_scope, mode)
75
+ emit_error!(:case_unsupported) unless body
64
76
  [
65
77
  '(-> do',
66
- indent("#{value_var} = #{emit_expr(args[0], env, current_scope)}"),
78
+ indent("#{value_var} = #{value_code}"),
67
79
  indent(body),
68
80
  'end).call'
69
81
  ].join("\n")
70
82
  end
71
83
 
84
+ def simple_case_subject?(form)
85
+ case form
86
+ when Sym then !form.dotted?
87
+ when Numeric, String, Symbol, true, false, nil then true
88
+ else false
89
+ end
90
+ end
91
+
72
92
  def try_emit_native_case(value_var, clauses, env, current_scope, mode)
73
93
  arms = []
74
94
  i = 0
@@ -89,10 +109,15 @@ module Kapusta
89
109
  arms.concat(sub_arms)
90
110
  i += 2
91
111
  end
92
- arms << ['else', indent('nil')].join("\n")
112
+ arms << ['else', indent('nil')].join("\n") unless wildcard_last?(clauses)
93
113
  ["case #{value_var}", *arms, 'end'].join("\n")
94
114
  end
95
115
 
116
+ def wildcard_last?(clauses)
117
+ last_pattern = clauses[-2]
118
+ last_pattern.is_a?(Sym) && last_pattern.name == '_'
119
+ end
120
+
96
121
  def try_native_arm(pattern, body, where_guards, env, current_scope, mode)
97
122
  allow_pins = !where_guards.empty? && mode == :case
98
123
  plan = native_pattern_plan(pattern, env, mode:, allow_pins:)
@@ -7,17 +7,19 @@ module Kapusta
7
7
  private
8
8
 
9
9
  def emit_expr(form, env, current_scope)
10
- case form
11
- when Sym then emit_sym(form, env)
12
- when Vec then "[#{form.items.map { |item| emit_expr(item, env, current_scope) }.join(', ')}]"
13
- when HashLit
14
- "{#{form.pairs.map do |key, value|
15
- "#{emit_hash_key(key, env, current_scope)} => #{emit_expr(value, env, current_scope)}"
16
- end.join(', ')}}"
17
- when List then emit_list(form, env, current_scope)
18
- when String, Symbol, Numeric, true, false, nil then form.inspect
19
- else
20
- emit_error!("cannot emit form: #{form.inspect}")
10
+ with_current_form(form) do
11
+ case form
12
+ when Sym then emit_sym(form, env)
13
+ when Vec then "[#{form.items.map { |item| emit_expr(item, env, current_scope) }.join(', ')}]"
14
+ when HashLit
15
+ "{#{form.pairs.map do |key, value|
16
+ "#{emit_hash_key(key, env, current_scope)} => #{emit_expr(value, env, current_scope)}"
17
+ end.join(', ')}}"
18
+ when List then emit_list(form, env, current_scope)
19
+ when String, Symbol, Numeric, true, false, nil then form.inspect
20
+ else
21
+ emit_error!(:cannot_emit_form, form: form.inspect)
22
+ end
21
23
  end
22
24
  end
23
25
 
@@ -29,7 +31,7 @@ module Kapusta
29
31
  end
30
32
 
31
33
  def emit_list(list, env, current_scope)
32
- return 'nil' if list.empty?
34
+ emit_error!(:empty_call) if list.empty?
33
35
 
34
36
  head = list.head
35
37
  args = list.rest
@@ -43,6 +45,11 @@ module Kapusta
43
45
  return emit_self_call(head.name, args, env, current_scope)
44
46
  end
45
47
 
48
+ case head
49
+ when Numeric, String, Symbol, true, false, nil
50
+ emit_error!(:cannot_call_literal, value: head.inspect)
51
+ end
52
+
46
53
  emit_callable_call(emit_expr(head, env, current_scope), args, env, current_scope)
47
54
  end
48
55
 
@@ -51,6 +58,7 @@ module Kapusta
51
58
  when 'fn', 'lambda', 'λ' then emit_fn(args, env, current_scope)
52
59
  when 'let' then emit_let(args, env, current_scope)
53
60
  when 'local', 'var' then emit_local_expr(args, env, current_scope)
61
+ when 'global' then emit_global_expr(args, env, current_scope)
54
62
  when 'set' then emit_set_expr(args, env, current_scope)
55
63
  when 'if' then emit_if(args, env, current_scope)
56
64
  when 'case' then emit_case(args, env, current_scope, :case)
@@ -96,17 +104,15 @@ module Kapusta
96
104
  when '/' then emit_div(args, env, current_scope)
97
105
  when '%' then args.map { |arg| parenthesize(emit_expr(arg, env, current_scope)) }.join(' % ')
98
106
  when 'print' then emit_print(args, env, current_scope)
99
- when 'quasi-sym' then "Kapusta::Sym.new(#{emit_expr(args[0], env, current_scope)})"
107
+ when 'quasi-sym' then "Kapusta::MacroSym.new(#{emit_expr(args[0], env, current_scope)})"
100
108
  when 'quasi-list' then "Kapusta::List.new([#{args.map { |a| emit_expr(a, env, current_scope) }.join(', ')}])"
101
109
  when 'quasi-list-tail' then emit_quasi_list_tail(args, env, current_scope)
102
110
  when 'quasi-vec' then "Kapusta::Vec.new([#{args.map { |a| emit_expr(a, env, current_scope) }.join(', ')}])"
103
111
  when 'quasi-vec-tail' then emit_quasi_vec_tail(args, env, current_scope)
104
112
  when 'quasi-hash' then emit_quasi_hash(args, env, current_scope)
105
113
  when 'quasi-gensym' then emit_quasi_gensym(args[0], env, current_scope)
106
- when 'macro', 'macros', 'import-macros'
107
- emit_error!("#{name} must appear at the top level and is consumed by the macro expander")
108
114
  else
109
- emit_error!("unknown special form: #{name}")
115
+ emit_error!(:unknown_special_form, name:)
110
116
  end
111
117
  end
112
118
 
@@ -138,6 +144,9 @@ module Kapusta
138
144
  def emit_concat(args, env, current_scope)
139
145
  return '""' if args.empty?
140
146
 
147
+ args.each do |arg|
148
+ emit_error!(:vararg_with_operator) if arg.is_a?(Sym) && arg.name == '...'
149
+ end
141
150
  args.map { |arg| emit_string_part(arg, env, current_scope) }.join(' + ')
142
151
  end
143
152
 
@@ -7,6 +7,8 @@ module Kapusta
7
7
  private
8
8
 
9
9
  def emit_lookup(args, env, current_scope)
10
+ emit_error!(:dot_no_args) if args.empty?
11
+
10
12
  object_code = emit_expr(args[0], env, current_scope)
11
13
  keys = args[1..].map { |arg| emit_expr(arg, env, current_scope) }
12
14
  return object_code if keys.empty?
@@ -91,7 +93,7 @@ module Kapusta
91
93
 
92
94
  def emit_module_wrapper(name_sym, body)
93
95
  segments = constant_segments(name_sym)
94
- emit_error!("invalid module name: #{name_sym.name}") unless segments
96
+ emit_error!(:invalid_module_name, name: name_sym.name) unless segments
95
97
  inner = build_nested_module(segments, body)
96
98
  ['(-> do', indent(inner), indent(segments.join('::')), 'end).call'].join("\n")
97
99
  end
@@ -105,7 +107,7 @@ module Kapusta
105
107
 
106
108
  def emit_class_wrapper(name_sym, supers, env, body)
107
109
  segments = constant_segments(name_sym)
108
- emit_error!("invalid class name: #{name_sym.name}") unless segments
110
+ emit_error!(:invalid_class_name, name: name_sym.name) unless segments
109
111
  super_code = class_super_code(supers, env)
110
112
  inner = build_nested_class(segments, super_code, body)
111
113
  ['(-> do', indent(inner), indent(segments.join('::')), 'end).call'].join("\n")
@@ -425,16 +427,17 @@ module Kapusta
425
427
  def emit_sym(sym, env)
426
428
  name = sym.name
427
429
  return 'self' if name == 'self'
428
- return 'Float::INFINITY' if name == 'math.huge'
429
430
 
430
431
  if (binding = env.lookup_if_defined(sym))
431
432
  return binding_value_code(binding)
432
433
  end
434
+
435
+ emit_error!(:unexpected_vararg) if name == '...'
433
436
  return emit_multisym_value(sym, env) if sym.dotted?
434
437
  return 'ARGV' if name == 'ARGV'
435
438
  return name if name.match?(/\A[A-Z]/)
436
439
 
437
- emit_error!("undefined symbol: #{name}")
440
+ emit_error!(:undefined_symbol, name:)
438
441
  end
439
442
 
440
443
  def emit_gvar(sym)
@@ -458,7 +461,7 @@ module Kapusta
458
461
  const_path << segments[idx]
459
462
  idx += 1
460
463
  end
461
- emit_error!("bad multisym: #{segments.join('.')}") if const_path.empty?
464
+ emit_error!(:bad_multisym, path: segments.join('.')) if const_path.empty?
462
465
 
463
466
  [const_path.join('::'), segments[idx..]]
464
467
  end
@@ -10,6 +10,7 @@ module Kapusta
10
10
  if pattern.is_a?(Sym)
11
11
  return ['', env] if pattern.name == '_'
12
12
 
13
+ validate_binding_symbol!(pattern)
13
14
  ruby_name = define_local(env, pattern)
14
15
  return ["#{ruby_name} = #{value_code}", env]
15
16
  end
@@ -17,7 +18,26 @@ module Kapusta
17
18
  native = try_emit_native_pattern_bind(pattern, value_code, env)
18
19
  return native if native
19
20
 
20
- emit_error!("destructure pattern this compiler cannot translate: #{pattern.inspect}")
21
+ emit_error!(:destructure_unsupported, pattern: pattern.inspect)
22
+ end
23
+
24
+ def validate_binding_symbol!(sym)
25
+ name = sym.name
26
+ emit_error!(:shadowed_special, name:) if Compiler::SPECIAL_FORMS.include?(name)
27
+ return unless sym.is_a?(MacroSym)
28
+
29
+ emit_error!(:macro_unsafe_bind, name:)
30
+ end
31
+
32
+ def validate_destructure_pattern!(pattern)
33
+ items = pattern.items
34
+ items.each_with_index do |item, idx|
35
+ emit_error!(:bind_table_dots) if item.is_a?(Sym) && item.name == '...'
36
+ next unless item.is_a?(Sym) && item.name == '&'
37
+
38
+ emit_error!(:rest_not_last) if idx + 2 < items.length
39
+ emit_error!(:rest_not_last) if idx + 1 >= items.length
40
+ end
21
41
  end
22
42
 
23
43
  def try_emit_native_pattern_bind(pattern, value_code, env)
@@ -32,6 +52,7 @@ module Kapusta
32
52
  end
33
53
 
34
54
  def try_emit_native_vec_bind(pattern, value_code, env)
55
+ validate_destructure_pattern!(pattern)
35
56
  parts = []
36
57
  deferred = []
37
58
  current_env = env
@@ -225,12 +246,60 @@ module Kapusta
225
246
  end
226
247
 
227
248
  def compile_native_hash(pattern, env, mode:, allow_pins:, state:)
228
- pairs = pattern.pairs.map do |key, value|
229
- raise PatternNotTranslatable unless key.is_a?(Symbol)
230
-
249
+ sym_pairs, other_pairs = pattern.pairs.partition { |key, _| key.is_a?(Symbol) }
250
+ sym_parts = sym_pairs.map do |key, value|
231
251
  "#{key}: #{compile_native_pattern(value, env, mode:, allow_pins:, state:)}"
232
252
  end
233
- "{#{pairs.join(', ')}}"
253
+ return "{#{sym_parts.join(', ')}}" if other_pairs.empty?
254
+
255
+ capture = temp('hash')
256
+ sym_pattern = sym_parts.empty? ? 'Hash' : "{#{sym_parts.join(', ')}}"
257
+ other_pairs.each do |key, value|
258
+ compile_native_hash_value(value, capture, compile_native_hash_key(key), env, mode:, state:)
259
+ end
260
+ "#{sym_pattern} => #{capture}"
261
+ end
262
+
263
+ def compile_native_hash_key(key)
264
+ case key
265
+ when Symbol, String, Numeric, true, false then key.inspect
266
+ when nil then 'nil'
267
+ else raise PatternNotTranslatable
268
+ end
269
+ end
270
+
271
+ def compile_native_hash_value(value, capture, key_code, env, mode:, state:)
272
+ lookup = "#{capture}[#{key_code}]"
273
+ case value
274
+ when Sym
275
+ name = value.name
276
+ return if name == '_'
277
+
278
+ if nil_allowing_pattern_name?(name)
279
+ raise PatternNotTranslatable if state[:bound_names].key?(name)
280
+
281
+ state[:bound_names][name] = true
282
+ state[:binding_names] << name
283
+ state[:guards] << "(#{sanitize_local(name)} = #{lookup}; true)"
284
+ else
285
+ binding = mode == :match ? env.lookup_if_defined(name) : nil
286
+ if state[:bound_names].key?(name)
287
+ raise PatternNotTranslatable
288
+ elsif binding
289
+ state[:guards] << "#{lookup} == #{binding_value_code(binding)}"
290
+ else
291
+ state[:bound_names][name] = true
292
+ state[:binding_names] << name
293
+ state[:guards] << "!(#{sanitize_local(name)} = #{lookup}).nil?"
294
+ end
295
+ end
296
+ when nil
297
+ state[:guards] << "#{lookup}.nil?"
298
+ when Symbol, String, Numeric, true, false
299
+ state[:guards] << "#{lookup} == #{value.inspect}"
300
+ else
301
+ raise PatternNotTranslatable
302
+ end
234
303
  end
235
304
 
236
305
  def compile_native_pin(pattern, env, mode:, allow_pins:)
@@ -6,8 +6,27 @@ module Kapusta
6
6
  module Support
7
7
  private
8
8
 
9
- def emit_error!(message)
10
- raise Error, "#{@path}: #{message}"
9
+ def emit_error!(code, **args)
10
+ form = current_form
11
+ line = form.respond_to?(:line) ? form.line : nil
12
+ column = form.respond_to?(:column) ? form.column : nil
13
+ raise Error.new(Kapusta::Errors.format(code, **args), path: @path, line:, column:)
14
+ end
15
+
16
+ def with_current_form(form)
17
+ @form_stack ||= []
18
+ @form_stack.push(form) if positionable?(form)
19
+ yield
20
+ ensure
21
+ @form_stack.pop if positionable?(form)
22
+ end
23
+
24
+ def current_form
25
+ (@form_stack ||= []).last
26
+ end
27
+
28
+ def positionable?(form)
29
+ form.respond_to?(:line) && form.respond_to?(:column)
11
30
  end
12
31
 
13
32
  def emit_forms_with_headers(forms, env, current_scope, result: true)
@@ -67,27 +86,29 @@ module Kapusta
67
86
  end
68
87
 
69
88
  def emit_form_in_sequence(form, env, current_scope, allow_method_definitions:, result_needed: true)
70
- if allow_method_definitions &&
71
- method_definition_form?(form) &&
72
- %i[toplevel module class].include?(current_scope)
73
- code, env = emit_definition_form(form, env, current_scope)
74
- return [code, env] if code
75
- end
89
+ with_current_form(form) do
90
+ if allow_method_definitions &&
91
+ method_definition_form?(form) &&
92
+ %i[toplevel module class].include?(current_scope)
93
+ code, env = emit_definition_form(form, env, current_scope)
94
+ return [code, env] if code
95
+ end
76
96
 
77
- if named_function_form?(form)
78
- emit_named_fn_assignment(form, env, current_scope)
79
- elsif local_form?(form)
80
- code, env = emit_local_form(form, env, current_scope)
81
- code = code.delete_suffix("\nnil") unless result_needed
82
- [code, env]
83
- elsif do_form?(form)
84
- emit_do_form(form.rest, env, current_scope, result_needed:)
85
- elsif sequence_statement_form?(form)
86
- emit_sequence_statement_form(form, env, current_scope, result_needed:)
87
- elsif set_new_local_form?(form, env)
88
- emit_set_form(form, env, current_scope)
89
- else
90
- [emit_expr(form, env, current_scope), env]
97
+ if named_function_form?(form)
98
+ emit_named_fn_assignment(form, env, current_scope)
99
+ elsif local_form?(form)
100
+ code, env = emit_local_form(form, env, current_scope)
101
+ code = code.delete_suffix("\nnil") unless result_needed
102
+ [code, env]
103
+ elsif do_form?(form)
104
+ emit_do_form(form.rest, env, current_scope, result_needed:)
105
+ elsif sequence_statement_form?(form)
106
+ emit_sequence_statement_form(form, env, current_scope, result_needed:)
107
+ elsif set_new_local_form?(form, env)
108
+ emit_set_form(form, env, current_scope)
109
+ else
110
+ [emit_expr(form, env, current_scope), env]
111
+ end
91
112
  end
92
113
  end
93
114
 
@@ -107,7 +128,7 @@ module Kapusta
107
128
  code, current_env = emit_form_in_sequence(form, current_env, current_scope,
108
129
  allow_method_definitions:,
109
130
  result_needed: result && index == forms.length - 1)
110
- codes << code
131
+ codes << code unless code.empty?
111
132
  end
112
133
  [codes.join("\n"), current_env]
113
134
  end
@@ -228,6 +249,7 @@ module Kapusta
228
249
  end
229
250
 
230
251
  def parse_counted_for_bindings(bindings, env, current_scope)
252
+ emit_error!(:counted_no_range) if bindings.length < 3
231
253
  name_sym = bindings[0]
232
254
  loop_env = env.child
233
255
  ruby_name = define_local(loop_env, name_sym.name)
@@ -31,7 +31,7 @@ module Kapusta
31
31
 
32
32
  def emit_file(forms)
33
33
  env = Env.new
34
- body = emit_forms_with_headers(forms, env, :toplevel)
34
+ body = emit_forms_with_headers(forms, env, :toplevel, result: false)
35
35
  "#{body}\n"
36
36
  end
37
37
  end