kapusta 0.1.4 → 0.2.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.
@@ -17,7 +17,7 @@ module Kapusta
17
17
  when List then emit_list(form, env, current_scope)
18
18
  when String, Symbol, Numeric, true, false, nil then form.inspect
19
19
  else
20
- raise Error, "cannot emit form: #{form.inspect}"
20
+ emit_error!("cannot emit form: #{form.inspect}")
21
21
  end
22
22
  end
23
23
 
@@ -36,7 +36,9 @@ module Kapusta
36
36
  if head.is_a?(Sym)
37
37
  return emit_special(head.name, args, env, current_scope) if special_form?(head.name)
38
38
  return emit_multisym_call(head, args, env, current_scope) if head.dotted?
39
- return emit_callable_call(env.lookup(head.name), args, env, current_scope) if env.defined?(head.name)
39
+ if (binding = env.lookup_if_defined(head.name))
40
+ return emit_bound_call(binding, args, env, current_scope)
41
+ end
40
42
 
41
43
  return emit_self_call(head.name, args, env, current_scope)
42
44
  end
@@ -68,24 +70,22 @@ module Kapusta
68
70
  when '.' then emit_lookup(args, env, current_scope)
69
71
  when '?.' then emit_safe_lookup(args, env, current_scope)
70
72
  when ':' then emit_colon(args, env, current_scope)
71
- when '..' then runtime_call(:concat, args.map do |arg|
72
- emit_expr(arg, env, current_scope)
73
- end)
74
- when 'length' then "(#{emit_expr(args[0], env, current_scope)}).length"
73
+ when '..' then emit_concat(args, env, current_scope)
74
+ when 'length' then "#{parenthesize(emit_expr(args[0], env, current_scope))}.length"
75
75
  when 'require' then emit_require(args[0], env, current_scope)
76
76
  when 'module' then emit_module_expr(args, env)
77
77
  when 'class' then emit_class_expr(args, env)
78
78
  when 'try' then emit_try(args, env, current_scope)
79
79
  when 'raise' then emit_raise(args, env, current_scope)
80
- when 'ivar' then runtime_call(:get_ivar, 'self', args[0].name.inspect)
81
- when 'cvar' then runtime_call(:get_cvar, 'self', args[0].name.inspect)
80
+ when 'ivar' then "@#{Kapusta.kebab_to_snake(args[0].name)}"
81
+ when 'cvar' then "@@#{Kapusta.kebab_to_snake(args[0].name)}"
82
82
  when 'gvar' then emit_gvar(args[0])
83
83
  when 'ruby' then "Kernel.eval(#{emit_expr(args[0], env, current_scope)})"
84
84
  when 'and' then emit_and(args, env, current_scope)
85
85
  when 'or' then emit_or(args, env, current_scope)
86
- when 'not' then "(!#{emit_expr(args[0], env, current_scope)})"
86
+ when 'not' then "!#{parenthesize(emit_expr(args[0], env, current_scope))}"
87
87
  when '=' then emit_compare(args, env, current_scope, '==')
88
- when 'not=' then "(!#{emit_special('=', args, env, current_scope)})"
88
+ when 'not=' then emit_compare_any(args, env, current_scope, '!=')
89
89
  when '<' then emit_compare(args, env, current_scope, '<')
90
90
  when '<=' then emit_compare(args, env, current_scope, '<=')
91
91
  when '>' then emit_compare(args, env, current_scope, '>')
@@ -95,13 +95,29 @@ module Kapusta
95
95
  when '*' then emit_reduce(args, env, current_scope, '1', :*)
96
96
  when '/' then emit_div(args, env, current_scope)
97
97
  when '%' then args.map { |arg| parenthesize(emit_expr(arg, env, current_scope)) }.join(' % ')
98
- when 'print' then runtime_call(:print_values, *args.map do |arg|
99
- emit_expr(arg, env, current_scope)
100
- end)
98
+ when 'print' then emit_print(args, env, current_scope)
101
99
  else
102
- raise Error, "unknown special form: #{name}"
100
+ emit_error!("unknown special form: #{name}")
103
101
  end
104
102
  end
103
+
104
+ def emit_concat(args, env, current_scope)
105
+ return '""' if args.empty?
106
+
107
+ args.map { |arg| emit_string_part(arg, env, current_scope) }.join(' + ')
108
+ end
109
+
110
+ def emit_print(args, env, current_scope)
111
+ return 'p' if args.empty?
112
+
113
+ "p(#{args.map { |arg| emit_expr(arg, env, current_scope) }.join(', ')})"
114
+ end
115
+
116
+ def emit_string_part(arg, env, current_scope)
117
+ return arg.inspect if arg.is_a?(String)
118
+
119
+ "(#{emit_expr(arg, env, current_scope)}).to_s"
120
+ end
105
121
  end
106
122
  end
107
123
  end
@@ -8,8 +8,11 @@ module Kapusta
8
8
 
9
9
  def emit_lookup(args, env, current_scope)
10
10
  object_code = emit_expr(args[0], env, current_scope)
11
- keys = args[1..].map { |arg| emit_expr(arg, env, current_scope) }.join(', ')
12
- runtime_call(:get_path, object_code, "[#{keys}]")
11
+ keys = args[1..].map { |arg| emit_expr(arg, env, current_scope) }
12
+ return object_code if keys.empty?
13
+
14
+ receiver = simple_expression?(object_code) ? object_code : parenthesize(object_code)
15
+ "#{receiver}#{keys.map { |k| "[#{k}]" }.join}"
13
16
  end
14
17
 
15
18
  def emit_safe_lookup(args, env, current_scope)
@@ -20,9 +23,16 @@ module Kapusta
20
23
 
21
24
  def emit_colon(args, env, current_scope)
22
25
  receiver = emit_expr(args[0], env, current_scope)
23
- method_name = emit_method_name(args[1], env, current_scope)
26
+ method_form = args[1]
24
27
  positional, kwargs, block = split_call_args(args[2..], env, current_scope)
25
- runtime_call(:send_call, receiver, method_name, positional, kwargs, block)
28
+ literal_name = method_form if method_form.is_a?(Symbol) || method_form.is_a?(String)
29
+ if literal_name && !kwargs && !block && direct_method_name?(literal_name.to_s)
30
+ return emit_direct_method_call(receiver, Kapusta.kebab_to_snake(literal_name.to_s), positional)
31
+ end
32
+
33
+ method_name = emit_method_name(method_form, env, current_scope)
34
+ parts = build_call_args([method_name, *positional], kwargs, block)
35
+ "#{parenthesize(receiver)}.public_send(#{parts})"
26
36
  end
27
37
 
28
38
  def emit_require(arg, env, current_scope)
@@ -33,9 +43,30 @@ module Kapusta
33
43
  when String then arg.inspect
34
44
  else "(#{emit_expr(arg, env, current_scope)}).to_s"
35
45
  end
46
+ if kapusta_require?(arg)
47
+ return [
48
+ 'unless defined?(Kapusta)',
49
+ indent('require "kapusta"'),
50
+ 'end',
51
+ "Kapusta.require(#{path_code}, relative_to: #{@path.inspect})"
52
+ ].join("\n")
53
+ end
54
+
36
55
  "require #{path_code}"
37
56
  end
38
57
 
58
+ def kapusta_require?(arg)
59
+ path =
60
+ case arg
61
+ when Sym then arg.name
62
+ when Symbol then arg.to_s
63
+ when String then arg
64
+ end
65
+ return false unless path
66
+
67
+ path.end_with?('.kap') || path.start_with?('./', '../') || File.absolute_path?(path)
68
+ end
69
+
39
70
  def emit_module_expr(args, env)
40
71
  body = emit_sequence(args[1..], env, :module, allow_method_definitions: true, result: false).first
41
72
  emit_module_wrapper(args[0], body)
@@ -63,7 +94,7 @@ module Kapusta
63
94
 
64
95
  def emit_direct_module_header(name_sym, body)
65
96
  const_name = simple_constant_name(name_sym)
66
- return nil unless const_name
97
+ return unless const_name
67
98
 
68
99
  [
69
100
  "module #{const_name}",
@@ -94,7 +125,7 @@ module Kapusta
94
125
 
95
126
  def emit_direct_class_header(name_sym, supers, body)
96
127
  const_name = simple_constant_name(name_sym)
97
- return nil unless const_name && supers.nil?
128
+ return unless const_name && supers.nil?
98
129
 
99
130
  [
100
131
  "class #{const_name}",
@@ -105,7 +136,7 @@ module Kapusta
105
136
  end
106
137
 
107
138
  def simple_constant_name(name_sym)
108
- return nil unless name_sym.is_a?(Sym) && name_sym.name.match?(/\A[A-Z]\w*\z/)
139
+ return unless name_sym.is_a?(Sym) && name_sym.name.match?(/\A[A-Z]\w*\z/)
109
140
 
110
141
  name_sym.name
111
142
  end
@@ -184,6 +215,15 @@ module Kapusta
184
215
  end.join(' && ')
185
216
  end
186
217
 
218
+ def emit_compare_any(args, env, current_scope, operator)
219
+ values = args.map { |arg| emit_expr(arg, env, current_scope) }
220
+ return 'false' if values.length <= 1
221
+
222
+ (0...(values.length - 1)).map do |i|
223
+ "#{parenthesize(values[i])} #{operator} #{parenthesize(values[i + 1])}"
224
+ end.join(' || ')
225
+ end
226
+
187
227
  def emit_reduce(args, env, current_scope, empty_value, operator)
188
228
  return empty_value if args.empty?
189
229
 
@@ -207,15 +247,32 @@ module Kapusta
207
247
 
208
248
  def emit_callable_call(callee_code, args, env, current_scope)
209
249
  positional, kwargs, block = split_call_args(args, env, current_scope)
210
- return emit_direct_callable_call(callee_code, positional) unless kwargs || block
250
+ rendered = build_call_args(positional, kwargs, block)
251
+ suffix = rendered.empty? ? '.call' : ".call(#{rendered})"
252
+ "#{parenthesize(callee_code)}#{suffix}"
253
+ end
211
254
 
212
- runtime_call(:call, callee_code, "[#{positional.join(', ')}]", kwargs, block)
255
+ def build_call_args(positional, kwargs, block)
256
+ parts = positional.dup
257
+ parts << "**#{kwargs}" if kwargs
258
+ parts << "&#{block}" if block
259
+ parts.join(', ')
213
260
  end
214
261
 
215
- def emit_direct_callable_call(callee_code, positional)
216
- rendered_args = positional.join(', ')
217
- suffix = rendered_args.empty? ? '.call' : ".call(#{rendered_args})"
218
- "#{parenthesize(callee_code)}#{suffix}"
262
+ def emit_bound_call(binding, args, env, current_scope)
263
+ return emit_self_method_binding_call(binding, args, env, current_scope) if method_binding?(binding)
264
+
265
+ emit_callable_call(binding, args, env, current_scope)
266
+ end
267
+
268
+ def emit_self_method_binding_call(binding, args, env, current_scope)
269
+ positional = args.map { |arg| emit_expr(arg, env, current_scope) }
270
+ emit_direct_self_method_call(binding.ruby_name, positional)
271
+ end
272
+
273
+ def emit_direct_self_method_call(method_name, positional)
274
+ args = positional.join(', ')
275
+ args.empty? ? "#{method_name}()" : "#{method_name}(#{args})"
219
276
  end
220
277
 
221
278
  def emit_multisym_call(head, args, env, current_scope)
@@ -223,19 +280,26 @@ module Kapusta
223
280
  if segments.empty?
224
281
  emit_callable_call(base_code, args, env, current_scope)
225
282
  else
226
- receiver =
227
- if segments.length == 1
228
- base_code
229
- else
230
- runtime_call(:method_path_value, base_code, segments[0...-1].inspect)
231
- end
232
- method_name = Kapusta.kebab_to_snake(segments.last).to_sym.inspect
283
+ receiver = emit_method_path(base_code, segments[0...-1])
233
284
  positional, kwargs, block = split_call_args(args, env, current_scope)
234
- if segments.length == 1 && !kwargs && !block && direct_method_name?(segments.last)
285
+ if !kwargs && !block && direct_method_name?(segments.last)
235
286
  return emit_direct_method_call(receiver, Kapusta.kebab_to_snake(segments.last), positional)
236
287
  end
237
288
 
238
- runtime_call(:send_call, receiver, method_name, positional, kwargs, block)
289
+ method_name = Kapusta.kebab_to_snake(segments.last).to_sym.inspect
290
+ parts = build_call_args([method_name, *positional], kwargs, block)
291
+ "#{receiver}.public_send(#{parts})"
292
+ end
293
+ end
294
+
295
+ def emit_method_path(base_code, segments)
296
+ segments.reduce(base_code) do |acc, segment|
297
+ snake = Kapusta.kebab_to_snake(segment)
298
+ if direct_method_name?(segment)
299
+ "#{acc}.#{snake}"
300
+ else
301
+ "#{acc}.public_send(#{snake.to_sym.inspect})"
302
+ end
239
303
  end
240
304
  end
241
305
 
@@ -249,7 +313,8 @@ module Kapusta
249
313
  def emit_self_call(name, args, env, current_scope)
250
314
  positional, kwargs, block = split_call_args(args, env, current_scope)
251
315
  method_name = Kapusta.kebab_to_snake(name).to_sym.inspect
252
- runtime_call(:invoke_self, 'self', method_name, positional, kwargs, block)
316
+ parts = build_call_args([method_name, *positional], kwargs, block)
317
+ "send(#{parts})"
253
318
  end
254
319
 
255
320
  def split_call_args(args, env, current_scope)
@@ -285,33 +350,31 @@ module Kapusta
285
350
  name = sym.name
286
351
  return 'self' if name == 'self'
287
352
  return 'Float::INFINITY' if name == 'math.huge'
288
- return env.lookup(name) if env.defined?(name)
353
+
354
+ if (binding = env.lookup_if_defined(sym))
355
+ return binding_value_code(binding)
356
+ end
289
357
  return emit_multisym_value(sym, env) if sym.dotted?
290
358
  return 'ARGV' if name == 'ARGV'
291
359
  return name if name.match?(/\A[A-Z]/)
292
360
 
293
- raise Error, "undefined symbol: #{name}"
361
+ emit_error!("undefined symbol: #{name}")
294
362
  end
295
363
 
296
364
  def emit_gvar(sym)
297
- ruby_name = global_name(sym.name)
298
- return "$#{ruby_name}" if direct_global_name?(ruby_name)
299
-
300
- runtime_call(:get_gvar, sym.name.inspect)
365
+ "$#{global_name(sym.name)}"
301
366
  end
302
367
 
303
368
  def emit_multisym_value(sym, env)
304
369
  base_code, segments = multisym_base(sym.segments, env)
305
- return base_code if segments.empty?
306
-
307
- runtime_call(:method_path_value, base_code, segments.inspect)
370
+ emit_method_path(base_code, segments)
308
371
  end
309
372
 
310
373
  def multisym_base(segments, env)
311
374
  if segments[0] == 'self'
312
375
  ['self', segments[1..]]
313
- elsif env.defined?(segments[0])
314
- [env.lookup(segments[0]), segments[1..]]
376
+ elsif (binding = env.lookup_if_defined(segments[0]))
377
+ [binding_value_code(binding), segments[1..]]
315
378
  else
316
379
  idx = 0
317
380
  const_path = []
@@ -319,7 +382,7 @@ module Kapusta
319
382
  const_path << segments[idx]
320
383
  idx += 1
321
384
  end
322
- raise Error, "bad multisym: #{segments.join('.')}" if const_path.empty?
385
+ emit_error!("bad multisym: #{segments.join('.')}") if const_path.empty?
323
386
 
324
387
  [const_path.join('::'), segments[idx..]]
325
388
  end
@@ -335,10 +398,6 @@ module Kapusta
335
398
  Kapusta.kebab_to_snake(name).match?(/\A[a-z_]\w*[!?=]?\z/)
336
399
  end
337
400
 
338
- def direct_global_name?(name)
339
- name.match?(/\A[a-z_]\w*\z/)
340
- end
341
-
342
401
  def global_name(name)
343
402
  Kapusta.kebab_to_snake(name).gsub(/[^a-zA-Z0-9_]/, '_')
344
403
  end
@@ -348,11 +407,19 @@ module Kapusta
348
407
  code.match?(/\A[A-Z]\w*(?:::[A-Z]\w*)*\z/) ||
349
408
  code.match?(/\A[a-z_]\w*[!?=]?\([^()\n]*\)\z/) ||
350
409
  code.match?(/\A\d+(?:\.\d+)?\z/) ||
351
- code.match?(/\A[a-z_]\w*(?:\.[a-z_]\w*[!?=]?(?:\([^()\n]*\))?)+\z/) ||
410
+ code.match?(/\A[a-z_]\w*(?:\.[a-z_]\w*[!?=]?(?:\([^()\n]*\))?|\[[^\[\]]*\])+\z/) ||
352
411
  code.match?(/\A:[a-zA-Z_]\w*[!?=]?\z/) ||
353
412
  code.match?(/\A"(?:[^"\\]|\\.)*"\z/) ||
354
413
  code.match?(/\A'(?:[^'\\]|\\.)*'\z/) ||
355
- %w[nil true false self].include?(code)
414
+ %w[nil true false self].include?(code) ||
415
+ negation_simple?(code)
416
+ end
417
+
418
+ def negation_simple?(code)
419
+ return false unless code.start_with?('!') && code.length > 1
420
+
421
+ rest = code[1..]
422
+ simple_expression?(rest) || (rest.start_with?('(') && rest.end_with?(')'))
356
423
  end
357
424
  end
358
425
  end
@@ -10,7 +10,7 @@ module Kapusta
10
10
  if pattern.is_a?(Sym)
11
11
  return ['nil', env] if pattern.name == '_'
12
12
 
13
- ruby_name = define_local(env, pattern.name)
13
+ ruby_name = define_local(env, pattern)
14
14
  ["#{ruby_name} = #{value_code}", env]
15
15
  else
16
16
  bindings_var = temp('bindings')
@@ -61,7 +61,7 @@ module Kapusta
61
61
  elsif or_pattern?(pattern)
62
62
  emit_or_match_pattern(pattern, env, mode:, allow_pins:, state:)
63
63
  elsif where_pattern?(pattern)
64
- raise Error, '`where` is only valid as a case/match clause head'
64
+ emit_error!('`where` is only valid as a case/match clause head')
65
65
  else
66
66
  emit_sequence_match_pattern(pattern.items, env, mode:, allow_pins:, state:)
67
67
  end
@@ -70,7 +70,7 @@ module Kapusta
70
70
  when Symbol, String, Numeric, true, false
71
71
  "[:lit, #{pattern.inspect}]"
72
72
  else
73
- raise Error, "bad pattern: #{pattern.inspect}"
73
+ emit_error!("bad pattern: #{pattern.inspect}")
74
74
  end
75
75
  end
76
76
 
@@ -88,10 +88,11 @@ module Kapusta
88
88
  end
89
89
 
90
90
  def emit_named_match_pattern(name, env, mode:, state:, allow_nil:, prefer_pin:)
91
+ binding = prefer_pin && mode == :match ? env.lookup_if_defined(name) : nil
91
92
  if state[:bound_names].key?(name)
92
93
  "[:ref, #{name.inspect}]"
93
- elsif prefer_pin && mode == :match && env.defined?(name)
94
- "[:pin, #{env.lookup(name)}]"
94
+ elsif binding
95
+ "[:pin, #{binding_value_code(binding)}]"
95
96
  else
96
97
  state[:bound_names][name] = true
97
98
  state[:binding_names] << name
@@ -115,13 +116,15 @@ module Kapusta
115
116
  end
116
117
 
117
118
  def emit_pin_match_pattern(pattern, env, mode:, allow_pins:)
118
- raise Error, 'pin patterns are only supported inside `case` guards' unless allow_pins && mode == :case
119
+ emit_error!('pin patterns are only supported inside `case` guards') unless allow_pins && mode == :case
119
120
 
120
121
  name_sym = pattern.items[1]
121
- raise Error, "bad pin pattern: #{pattern.inspect}" unless name_sym.is_a?(Sym)
122
- raise Error, "cannot pin undefined name: #{name_sym.name}" unless env.defined?(name_sym.name)
122
+ emit_error!("bad pin pattern: #{pattern.inspect}") unless name_sym.is_a?(Sym)
123
123
 
124
- "[:pin, #{env.lookup(name_sym.name)}]"
124
+ binding = env.lookup_if_defined(name_sym.name)
125
+ emit_error!("cannot pin undefined name: #{name_sym.name}") unless binding
126
+
127
+ "[:pin, #{binding_value_code(binding)}]"
125
128
  end
126
129
 
127
130
  def emit_or_match_pattern(pattern, env, mode:, allow_pins:, state:)
@@ -136,7 +139,7 @@ module Kapusta
136
139
  compiled = emit_match_pattern(subpattern, env, mode:, allow_pins:, state: alt_state)
137
140
  alt_names = alt_state[:binding_names][initial_names..]
138
141
  canonical_names ||= alt_names
139
- raise Error, 'all `or` patterns must bind the same names' if canonical_names.sort != alt_names.sort
142
+ emit_error!('all `or` patterns must bind the same names') if canonical_names.sort != alt_names.sort
140
143
 
141
144
  compiled
142
145
  end
@@ -174,7 +177,7 @@ module Kapusta
174
177
  when Symbol, String, Numeric, true, false
175
178
  "[:lit, #{pattern.inspect}]"
176
179
  else
177
- raise Error, "bad pattern: #{pattern.inspect}"
180
+ emit_error!("bad pattern: #{pattern.inspect}")
178
181
  end
179
182
  end
180
183
 
@@ -6,6 +6,10 @@ module Kapusta
6
6
  module Support
7
7
  private
8
8
 
9
+ def emit_error!(message)
10
+ raise Error, "#{@path}: #{message}"
11
+ end
12
+
9
13
  def emit_forms_with_headers(forms, env, current_scope, result: true)
10
14
  i = 0
11
15
  codes = []
@@ -63,9 +67,14 @@ module Kapusta
63
67
  end
64
68
 
65
69
  def emit_form_in_sequence(form, env, current_scope, allow_method_definitions:, result_needed: true)
66
- if allow_method_definitions && method_definition_form?(form) && %i[module class].include?(current_scope)
67
- [emit_method_definition(form, env), env]
68
- elsif named_function_form?(form)
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
76
+
77
+ if named_function_form?(form)
69
78
  emit_named_fn_assignment(form, env, current_scope)
70
79
  elsif local_form?(form)
71
80
  code, env = emit_local_form(form, env, current_scope)
@@ -186,7 +195,7 @@ module Kapusta
186
195
 
187
196
  def local_name(source_name, env, shadow:)
188
197
  base = sanitize_local(source_name)
189
- base = "user_#{base}" if reserved_generated_name?(base)
198
+ base = "user_#{base}" if !generated_symbol?(source_name) && reserved_generated_name?(base)
190
199
  return base unless ruby_name_defined?(env, base, shadow:)
191
200
 
192
201
  index = 2
@@ -206,6 +215,10 @@ module Kapusta
206
215
  name.start_with?('kap_', '__kap_')
207
216
  end
208
217
 
218
+ def generated_symbol?(source_name)
219
+ source_name.is_a?(GeneratedSym)
220
+ end
221
+
209
222
  def runtime_helper(name)
210
223
  helper = name.to_sym
211
224
  @runtime_helpers << helper unless @runtime_helpers.include?(helper)
@@ -219,6 +232,14 @@ module Kapusta
219
232
  "#{runtime_helper(name)}(#{rendered_args.join(', ')})"
220
233
  end
221
234
 
235
+ def method_binding?(binding)
236
+ binding.is_a?(Env::MethodBinding)
237
+ end
238
+
239
+ def binding_value_code(binding)
240
+ method_binding?(binding) ? "method(#{binding.ruby_name.to_sym.inspect})" : binding
241
+ end
242
+
222
243
  def parse_counted_for_bindings(bindings, env, current_scope)
223
244
  name_sym = bindings[0]
224
245
  loop_env = env.child
@@ -242,24 +263,18 @@ module Kapusta
242
263
 
243
264
  def emit_counted_loop(ruby_name:, start_code:, finish_code:, step_code:,
244
265
  until_form:, loop_env:, current_scope:, body_code:)
245
- finish_var = temp('finish')
246
- step_var = temp('step')
247
- cmp_var = temp('cmp')
248
266
  until_code = until_form ? "break if #{emit_expr(until_form, loop_env, current_scope)}" : nil
249
- body = [until_code, body_code, "#{ruby_name} += #{step_var}"].compact.reject(&:empty?).join("\n")
267
+ body = [until_code, body_code].compact.reject(&:empty?).join("\n")
268
+ step_part = step_code == '1' ? '' : ", #{step_code}"
250
269
  [
251
- "#{ruby_name} = #{start_code}",
252
- "#{finish_var} = #{finish_code}",
253
- "#{step_var} = #{step_code}",
254
- "#{cmp_var} = #{step_var} >= 0 ? :<= : :>=",
255
- "while #{ruby_name}.public_send(#{cmp_var}, #{finish_var})",
270
+ "#{parenthesize(start_code)}.step(#{finish_code}#{step_part}) do |#{ruby_name}|",
256
271
  indent(body),
257
272
  'end'
258
273
  ].join("\n")
259
274
  end
260
275
 
261
276
  def sanitize_local(name)
262
- base = Kapusta.kebab_to_snake(name)
277
+ base = Kapusta.kebab_to_snake(name.respond_to?(:name) ? name.name : name)
263
278
  base = base.gsub('?', '_q').gsub('!', '_bang')
264
279
  base = base.gsub(/[^a-zA-Z0-9_]/, '_')
265
280
  if base.empty? || base.match?(/\A\d/) || base.match?(/\A[A-Z]/) || self.class::RUBY_KEYWORDS.include?(base)
@@ -34,11 +34,7 @@ module Kapusta
34
34
  env = Env.new
35
35
  body = emit_forms_with_headers(forms, env, :toplevel)
36
36
  helpers = Runtime.helper_source(@runtime_helpers)
37
- [
38
- '# frozen_string_literal: true',
39
- helpers,
40
- body
41
- ].reject(&:empty?).join("\n\n") << "\n"
37
+ [helpers, body].reject(&:empty?).join("\n\n") << "\n"
42
38
  end
43
39
  end
44
40
  end
@@ -38,11 +38,11 @@ module Kapusta
38
38
  when 'when'
39
39
  cond = items[1]
40
40
  body = wrap_do(items[2..])
41
- List.new([Sym.new('if'), cond, body, nil])
41
+ List.new([Sym.new('if'), cond, body])
42
42
  when 'unless'
43
43
  cond = items[1]
44
44
  body = wrap_do(items[2..])
45
- List.new([Sym.new('if'), cond, nil, body])
45
+ List.new([Sym.new('if'), List.new([Sym.new('not'), cond]), body])
46
46
  when 'tset'
47
47
  List.new([Sym.new('set'), List.new([Sym.new('.'), items[1], items[2]]), items[3]])
48
48
  when 'pcall'
@@ -74,7 +74,7 @@ module Kapusta
74
74
  end
75
75
 
76
76
  def wrap_do(forms)
77
- return nil if forms.empty?
77
+ return if forms.empty?
78
78
  return forms.first if forms.length == 1
79
79
 
80
80
  List.new([Sym.new('do'), *forms])
@@ -85,29 +85,48 @@ module Kapusta
85
85
  short = %w[-?> -?>>].include?(kind)
86
86
  position = %w[-> -?>].include?(kind) ? :first : :last
87
87
 
88
+ return thread_short(forms, position) if short
89
+
88
90
  forms[1..].reduce(value) do |memo, form|
89
- threaded =
90
- if form.is_a?(List)
91
- if position == :first
92
- List.new([form.items[0], memo, *form.items[1..]])
93
- else
94
- List.new([*form.items, memo])
95
- end
96
- else
97
- List.new([form, memo])
98
- end
99
-
100
- if short
101
- List.new([Sym.new('if'), List.new([Sym.new('='), memo, nil]), nil, threaded])
91
+ thread_step(memo, form, position)
92
+ end
93
+ end
94
+
95
+ def thread_short(forms, position)
96
+ forms[1..].reduce(forms.first) do |memo, form|
97
+ temp = thread_temp
98
+ List.new([
99
+ Sym.new('let'),
100
+ Vec.new([temp, memo]),
101
+ List.new([
102
+ Sym.new('if'),
103
+ List.new([Sym.new('='), temp, nil]),
104
+ nil,
105
+ thread_step(temp, form, position)
106
+ ])
107
+ ])
108
+ end
109
+ end
110
+
111
+ def thread_step(memo, form, position)
112
+ if form.is_a?(List)
113
+ if position == :first
114
+ List.new([form.items[0], memo, *form.items[1..]])
102
115
  else
103
- threaded
116
+ List.new([*form.items, memo])
104
117
  end
118
+ else
119
+ List.new([form, memo])
105
120
  end
106
121
  end
107
122
 
123
+ def thread_temp
124
+ gensym('kap_thread')
125
+ end
126
+
108
127
  def doto(forms)
109
128
  value = forms.first
110
- temp = Sym.new('__doto__')
129
+ temp = gensym('kap_doto')
111
130
  body = forms[1..].map do |form|
112
131
  if form.is_a?(List)
113
132
  List.new([form.items[0], temp, *form.items[1..]])
@@ -117,6 +136,11 @@ module Kapusta
117
136
  end
118
137
  List.new([Sym.new('let'), Vec.new([temp, value]), *body, temp])
119
138
  end
139
+
140
+ def gensym(prefix)
141
+ @gensym_index = (@gensym_index || 0) + 1
142
+ GeneratedSym.new("#{prefix}_#{@gensym_index}", @gensym_index)
143
+ end
120
144
  end
121
145
  end
122
146
  end