kapusta 0.3.0 → 0.5.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.
@@ -7,12 +7,11 @@ module Kapusta
7
7
  private
8
8
 
9
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
- emit_array_collection_step(result_var, body)
10
+ emit_error!(:icollect_no_iterator) unless args[0].is_a?(Vec) && args[0].items.length >= 2
11
+
12
+ emit_iteration(args[0], env, current_scope, method: 'filter_map') do |iter_env|
13
+ emit_sequence(args[1..], iter_env, current_scope, allow_method_definitions: false).first
14
14
  end
15
- emit_collection_result(result_var, '[]', iter_code)
16
15
  end
17
16
 
18
17
  def emit_collect(args, env, current_scope)
@@ -35,27 +34,69 @@ module Kapusta
35
34
 
36
35
  def emit_accumulate(args, env, current_scope)
37
36
  bindings = args[0].items
37
+ emit_error!(:accumulate_no_iterator) if bindings.length < 4
38
+
38
39
  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
40
+ init_code = emit_expr(bindings[1], env, current_scope)
41
+ iter_items = bindings[2..]
42
+ iter_expr = iter_items.last
43
+ binding_pats = iter_items[0...-1]
44
+
45
+ body_env = env.child
46
+ acc_var = define_local(body_env, acc_name.name)
47
+
48
+ inject_code = try_emit_inject(iter_expr, binding_pats, body_env, env, current_scope, acc_var,
49
+ init_code, args[1..])
50
+ return inject_code if inject_code
51
+
52
+ iter_code = emit_iteration(Vec.new(iter_items), body_env, current_scope) do |iter_env|
53
+ body_code, = emit_sequence(args[1..], iter_env, current_scope, allow_method_definitions: false)
54
+ emit_sequence_value_assignment(acc_var, body_code)
47
55
  end
48
56
  [
49
57
  '(-> do',
50
- indent("#{acc_var} = #{emit_expr(bindings[1], env, current_scope)}"),
58
+ indent("#{acc_var} = #{init_code}"),
51
59
  indent(iter_code),
52
60
  indent(acc_var),
53
61
  'end).call'
54
62
  ].join("\n")
55
63
  end
56
64
 
65
+ def try_emit_inject(iter_expr, binding_pats, body_env, env, current_scope, acc_var, init_code, body_forms)
66
+ return unless iter_expr.is_a?(List) && iter_expr.head.is_a?(Sym)
67
+
68
+ coll_code = emit_expr(iter_expr.items[1], env, current_scope)
69
+ case iter_expr.head.name
70
+ when 'ipairs'
71
+ value_var, value_bind = bind_iteration_param(binding_pats[1], 'value', body_env)
72
+ if ignored_pattern?(binding_pats[0])
73
+ body_code, = emit_sequence(body_forms, body_env, current_scope, allow_method_definitions: false)
74
+ return inject_block(coll_code, "#{acc_var}, #{value_var}", init_code, value_bind || '', body_code)
75
+ end
76
+ index_var, index_bind = bind_iteration_param(binding_pats[0], 'index', body_env)
77
+ bind_code = [index_bind, value_bind].compact.join("\n")
78
+ body_code, = emit_sequence(body_forms, body_env, current_scope, allow_method_definitions: false)
79
+ inject_block("#{coll_code}.each_with_index", "#{acc_var}, (#{value_var}, #{index_var})",
80
+ init_code, bind_code, body_code)
81
+ when 'pairs'
82
+ key_var, key_bind = bind_iteration_param(binding_pats[0], 'key', body_env)
83
+ value_var, value_bind = bind_iteration_param(binding_pats[1], 'value', body_env)
84
+ bind_code = [key_bind, value_bind].compact.join("\n")
85
+ body_code, = emit_sequence(body_forms, body_env, current_scope, allow_method_definitions: false)
86
+ inject_block(coll_code, "#{acc_var}, (#{key_var}, #{value_var})",
87
+ init_code, bind_code, body_code)
88
+ end
89
+ end
90
+
91
+ def inject_block(receiver, params, init_code, bind_code, body_code)
92
+ inner = join_code(bind_code, body_code)
93
+ ["#{receiver}.inject(#{init_code}) do |#{params}|", indent(inner), 'end'].join("\n")
94
+ end
95
+
57
96
  def emit_faccumulate(args, env, current_scope)
58
97
  bindings = args[0].items
98
+ emit_error!(:accumulate_no_iterator) if bindings.length < 5
99
+
59
100
  acc_name = bindings[0]
60
101
  loop_name = bindings[2]
61
102
  loop_env = env.child
@@ -112,7 +153,9 @@ module Kapusta
112
153
  end
113
154
  end
114
155
 
115
- def emit_iteration(bindings_vec, env, current_scope)
156
+ def emit_iteration(bindings_vec, env, current_scope, method: 'each')
157
+ emit_error!(:each_no_binding) unless bindings_vec.is_a?(Vec)
158
+
116
159
  items = bindings_vec.items
117
160
  iter_expr = items.last
118
161
  binding_pats = items[0...-1]
@@ -126,12 +169,13 @@ module Kapusta
126
169
  if ignored_pattern?(binding_pats[0])
127
170
  bind_code = value_bind || ''
128
171
  body_code = yield(body_env)
129
- return iteration_block("#{coll_code}.each do |#{value_var}|", bind_code, body_code)
172
+ return iteration_block("#{coll_code}.#{method} do |#{value_var}|", bind_code, body_code)
130
173
  end
131
174
  index_var, index_bind = bind_iteration_param(binding_pats[0], 'index', body_env)
132
175
  bind_code = [index_bind, value_bind].compact.join("\n")
133
176
  body_code = yield(body_env)
134
- header = "#{coll_code}.each_with_index do |#{value_var}, #{index_var}|"
177
+ receiver = method == 'each' ? "#{coll_code}.each_with_index" : "#{coll_code}.each_with_index.#{method}"
178
+ header = "#{receiver} do |#{value_var}, #{index_var}|"
135
179
  return iteration_block(header, bind_code, body_code)
136
180
  when 'pairs'
137
181
  body_env = env.child
@@ -139,7 +183,7 @@ module Kapusta
139
183
  value_var, value_bind = bind_iteration_param(binding_pats[1], 'value', body_env)
140
184
  bind_code = [key_bind, value_bind].compact.join("\n")
141
185
  body_code = yield(body_env)
142
- header = "#{emit_expr(iter_expr.items[1], env, current_scope)}.each do |#{key_var}, #{value_var}|"
186
+ header = "#{emit_expr(iter_expr.items[1], env, current_scope)}.#{method} do |#{key_var}, #{value_var}|"
143
187
  return iteration_block(header, bind_code, body_code)
144
188
  end
145
189
  end
@@ -149,14 +193,14 @@ module Kapusta
149
193
  body_env = env.child
150
194
  value_var, bind_code = bind_iteration_param(binding_pats[0], 'value', body_env)
151
195
  body_code = yield(body_env)
152
- iteration_block("#{coll_code}.each do |#{value_var}|", bind_code || '', body_code)
196
+ iteration_block("#{coll_code}.#{method} do |#{value_var}|", bind_code || '', body_code)
153
197
  else
154
198
  parts_var = temp('parts')
155
199
  body_env = env.child
156
200
  pairs = binding_pats.each_with_index.map { |pattern, i| [pattern, "#{parts_var}[#{i}]"] }
157
201
  bind_code, body_env = emit_iteration_bindings(pairs, body_env)
158
202
  body_code = yield(body_env)
159
- iteration_block("#{coll_code}.each do |*#{parts_var}|", bind_code, body_code)
203
+ iteration_block("#{coll_code}.#{method} do |*#{parts_var}|", bind_code, body_code)
160
204
  end
161
205
  end
162
206
 
@@ -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,9 +57,13 @@ module Kapusta
58
57
  end
59
58
 
60
59
  def emit_case(args, env, current_scope, mode)
60
+ clauses = args[1..]
61
+ emit_error!(:case_no_patterns) if clauses.empty?
62
+ emit_error!(:case_odd_patterns) if clauses.length.odd?
63
+
61
64
  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
65
+ body = try_emit_native_case(value_var, clauses, env, current_scope, mode)
66
+ emit_error!(:case_unsupported) unless body
64
67
  [
65
68
  '(-> do',
66
69
  indent("#{value_var} = #{emit_expr(args[0], env, current_scope)}"),
@@ -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,14 +104,51 @@ 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)
107
+ when 'quasi-sym' then "Kapusta::MacroSym.new(#{emit_expr(args[0], env, current_scope)})"
108
+ when 'quasi-list' then "Kapusta::List.new([#{args.map { |a| emit_expr(a, env, current_scope) }.join(', ')}])"
109
+ when 'quasi-list-tail' then emit_quasi_list_tail(args, env, current_scope)
110
+ when 'quasi-vec' then "Kapusta::Vec.new([#{args.map { |a| emit_expr(a, env, current_scope) }.join(', ')}])"
111
+ when 'quasi-vec-tail' then emit_quasi_vec_tail(args, env, current_scope)
112
+ when 'quasi-hash' then emit_quasi_hash(args, env, current_scope)
113
+ when 'quasi-gensym' then emit_quasi_gensym(args[0], env, current_scope)
114
+ when 'macro', 'macros', 'import-macros'
115
+ emit_error!(:special_must_be_toplevel, name:)
99
116
  else
100
- emit_error!("unknown special form: #{name}")
117
+ emit_error!(:unknown_special_form, name:)
118
+ end
119
+ end
120
+
121
+ def emit_quasi_list_tail(args, env, current_scope)
122
+ head_items = args[0]
123
+ tail_expr = emit_expr(args[1], env, current_scope)
124
+ head_code = head_items.items.map { |item| emit_expr(item, env, current_scope) }.join(', ')
125
+ "Kapusta::List.new([#{head_code}, *#{parenthesize(tail_expr)}])"
126
+ end
127
+
128
+ def emit_quasi_vec_tail(args, env, current_scope)
129
+ head_items = args[0]
130
+ tail_expr = emit_expr(args[1], env, current_scope)
131
+ head_code = head_items.items.map { |item| emit_expr(item, env, current_scope) }.join(', ')
132
+ "Kapusta::Vec.new([#{head_code}, *#{parenthesize(tail_expr)}])"
133
+ end
134
+
135
+ def emit_quasi_gensym(arg, env, current_scope)
136
+ "Kapusta::Compiler::MacroExpander.fresh_gensym(#{emit_expr(arg, env, current_scope)})"
137
+ end
138
+
139
+ def emit_quasi_hash(args, env, current_scope)
140
+ pairs = args.each_slice(2).map do |key, value|
141
+ "[#{emit_expr(key, env, current_scope)}, #{emit_expr(value, env, current_scope)}]"
101
142
  end
143
+ "Kapusta::HashLit.new([#{pairs.join(', ')}])"
102
144
  end
103
145
 
104
146
  def emit_concat(args, env, current_scope)
105
147
  return '""' if args.empty?
106
148
 
149
+ args.each do |arg|
150
+ emit_error!(:vararg_with_operator) if arg.is_a?(Sym) && arg.name == '...'
151
+ end
107
152
  args.map { |arg| emit_string_part(arg, env, current_scope) }.join(' + ')
108
153
  end
109
154
 
@@ -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")
@@ -434,7 +436,7 @@ module Kapusta
434
436
  return 'ARGV' if name == 'ARGV'
435
437
  return name if name.match?(/\A[A-Z]/)
436
438
 
437
- emit_error!("undefined symbol: #{name}")
439
+ emit_error!(:undefined_symbol, name:)
438
440
  end
439
441
 
440
442
  def emit_gvar(sym)
@@ -458,7 +460,7 @@ module Kapusta
458
460
  const_path << segments[idx]
459
461
  idx += 1
460
462
  end
461
- emit_error!("bad multisym: #{segments.join('.')}") if const_path.empty?
463
+ emit_error!(:bad_multisym, path: segments.join('.')) if const_path.empty?
462
464
 
463
465
  [const_path.join('::'), segments[idx..]]
464
466
  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
@@ -79,8 +100,7 @@ module Kapusta
79
100
  if sub.is_a?(Sym)
80
101
  raise PatternNotTranslatable if sub.name == '_'
81
102
 
82
- bind_name = sub.name.start_with?('?') ? sub.name.delete_prefix('?') : sub.name
83
- ruby_name = define_local(current_env, bind_name)
103
+ ruby_name = define_local(current_env, sub.name)
84
104
  lines << "#{ruby_name} = #{access}"
85
105
  else
86
106
  sub_code, current_env = try_emit_native_pattern_bind(sub, access, current_env) ||
@@ -96,8 +116,7 @@ module Kapusta
96
116
  when Sym
97
117
  return ['_', env, nil] if pattern.name == '_'
98
118
 
99
- bind_name = pattern.name.start_with?('?') ? pattern.name.delete_prefix('?') : pattern.name
100
- ruby_name = define_local(env, bind_name)
119
+ ruby_name = define_local(env, pattern.name)
101
120
  [ruby_name, env, nil]
102
121
  when Vec
103
122
  inner = []
@@ -137,8 +156,7 @@ module Kapusta
137
156
 
138
157
  return '*' if sym.name == '_'
139
158
 
140
- bind_name = sym.name.start_with?('?') ? sym.name.delete_prefix('?') : sym.name
141
- "*#{define_local(env, bind_name)}"
159
+ "*#{define_local(env, sym.name)}"
142
160
  end
143
161
 
144
162
  class PatternNotTranslatable < StandardError; end
@@ -179,12 +197,11 @@ module Kapusta
179
197
  return '_' if name == '_'
180
198
 
181
199
  if nil_allowing_pattern_name?(name)
182
- bind_name = name.start_with?('?') ? name.delete_prefix('?') : name
183
- raise PatternNotTranslatable if state[:bound_names].key?(bind_name)
200
+ raise PatternNotTranslatable if state[:bound_names].key?(name)
184
201
 
185
- state[:bound_names][bind_name] = true
186
- state[:binding_names] << bind_name
187
- sanitize_local(bind_name)
202
+ state[:bound_names][name] = true
203
+ state[:binding_names] << name
204
+ sanitize_local(name)
188
205
  else
189
206
  binding = mode == :match ? env.lookup_if_defined(name) : nil
190
207
  if state[:bound_names].key?(name)
@@ -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
 
@@ -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)
@@ -263,7 +285,8 @@ module Kapusta
263
285
 
264
286
  def sanitize_local(name)
265
287
  base = Kapusta.kebab_to_snake(name.respond_to?(:name) ? name.name : name)
266
- base = base.gsub('?', '_q').gsub('!', '_bang')
288
+ base = base.sub(/\A\?/, 'q_').gsub('?', '_q')
289
+ base = base.sub(/\A!/, 'bang_').gsub('!', '_bang')
267
290
  base = base.gsub(/[^a-zA-Z0-9_]/, '_')
268
291
  if base.empty? || base.match?(/\A\d/) || base.match?(/\A[A-Z]/) || self.class::RUBY_KEYWORDS.include?(base)
269
292
  base = "_#{base}"