kapusta 0.13.2 → 0.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (97) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +50 -11
  3. data/bin/check-all +6 -1
  4. data/bin/compile-examples +7 -2
  5. data/examples/anagram.kap +3 -3
  6. data/examples/app/args.kap +9 -0
  7. data/examples/arrange-coins.kap +3 -3
  8. data/examples/baseball-game.kap +5 -5
  9. data/examples/best-time-to-buy-sell-stock.kap +2 -2
  10. data/examples/binary-search.kap +2 -2
  11. data/examples/binary-to-decimal.kap +1 -1
  12. data/examples/blocks-and-kwargs.kap +2 -2
  13. data/examples/count-effects.kap +1 -1
  14. data/examples/count-items-matching-rule.kap +18 -0
  15. data/examples/doto-hygiene.kap +2 -2
  16. data/examples/doto.kap +2 -2
  17. data/examples/egg-count.kap +1 -1
  18. data/examples/exceptions.kap +1 -1
  19. data/examples/falling-drops.kap +7 -7
  20. data/examples/fennel-parity-examples.txt +3 -6
  21. data/examples/good-pairs.kap +18 -0
  22. data/examples/greet.kap +1 -1
  23. data/examples/happy-number.kap +2 -2
  24. data/examples/left-right-difference.kap +60 -0
  25. data/examples/length-of-last-word.kap +4 -4
  26. data/examples/manhattan-distance.kap +1 -1
  27. data/examples/maximum-subarray.kap +23 -7
  28. data/examples/minimum-start-value.kap +19 -0
  29. data/examples/move-zeroes.kap +2 -2
  30. data/examples/mruby-runtime-examples.txt +8 -2
  31. data/examples/non-constant-local.kap +1 -1
  32. data/examples/number-of-1-bits.kap +1 -1
  33. data/examples/number-of-steps.kap +1 -1
  34. data/examples/palindrome.kap +2 -2
  35. data/examples/pangram.kap +4 -4
  36. data/examples/pcall.kap +3 -3
  37. data/examples/pipeline.kap +4 -4
  38. data/examples/plus-one.kap +3 -3
  39. data/examples/raindrops.kap +1 -1
  40. data/examples/range-width.kap +14 -0
  41. data/examples/recent-counter.kap +6 -6
  42. data/examples/require-local-args.kap +9 -0
  43. data/examples/require-local.kap +7 -0
  44. data/examples/require-module-local.kap +4 -0
  45. data/examples/require-module.kap +4 -0
  46. data/examples/reverse-integer.kap +1 -1
  47. data/examples/roman-to-integer.kap +3 -3
  48. data/examples/running-sum.kap +20 -0
  49. data/examples/safe-lookup.kap +2 -2
  50. data/examples/single-number.kap +1 -1
  51. data/examples/stack.kap +14 -14
  52. data/examples/subtract-product-sum.kap +1 -1
  53. data/examples/summary-ranges.kap +45 -0
  54. data/examples/threading.kap +5 -5
  55. data/examples/tset.kap +1 -1
  56. data/examples/two-sum-hash.kap +3 -3
  57. data/examples/two-sum.kap +1 -1
  58. data/examples/ugly-number.kap +1 -1
  59. data/examples/underground-system.kap +39 -0
  60. data/examples/valid-parentheses-1.kap +6 -6
  61. data/exe/kapusta-ls +49 -2
  62. data/lib/kapusta/ast.rb +8 -0
  63. data/lib/kapusta/compiler/emitter/bindings.rb +111 -89
  64. data/lib/kapusta/compiler/emitter/collections.rb +32 -40
  65. data/lib/kapusta/compiler/emitter/control_flow.rb +33 -31
  66. data/lib/kapusta/compiler/emitter/expressions.rb +21 -5
  67. data/lib/kapusta/compiler/emitter/interop.rb +168 -48
  68. data/lib/kapusta/compiler/emitter/patterns.rb +12 -14
  69. data/lib/kapusta/compiler/emitter/support.rb +63 -81
  70. data/lib/kapusta/compiler/language.rb +522 -0
  71. data/lib/kapusta/compiler/lua_compat.rb +23 -28
  72. data/lib/kapusta/compiler/macro_expander.rb +30 -30
  73. data/lib/kapusta/compiler/macro_lowerer.rb +12 -24
  74. data/lib/kapusta/compiler/normalizer.rb +25 -17
  75. data/lib/kapusta/compiler.rb +3 -24
  76. data/lib/kapusta/env.rb +2 -2
  77. data/lib/kapusta/errors.rb +2 -1
  78. data/lib/kapusta/formatter/ast_helpers.rb +78 -0
  79. data/lib/kapusta/formatter/cli.rb +125 -0
  80. data/lib/kapusta/formatter/line_helpers.rb +44 -0
  81. data/lib/kapusta/formatter/validator.rb +32 -0
  82. data/lib/kapusta/formatter.rb +354 -325
  83. data/lib/kapusta/lsp/identifier.rb +1 -1
  84. data/lib/kapusta/lsp/rename.rb +21 -11
  85. data/lib/kapusta/lsp/scope_walker.rb +122 -212
  86. data/lib/kapusta/lsp/workspace_index.rb +17 -5
  87. data/lib/kapusta/reader.rb +4 -2
  88. data/lib/kapusta/version.rb +1 -1
  89. data/lib/kapusta.rb +39 -6
  90. data/spec/cli_spec.rb +13 -0
  91. data/spec/examples_errors_spec.rb +3 -1
  92. data/spec/examples_spec.rb +67 -15
  93. data/spec/formatter_spec.rb +246 -0
  94. data/spec/lsp_spec.rb +69 -0
  95. data/spec/require_spec.rb +294 -0
  96. metadata +20 -2
  97. data/examples/describe.kap +0 -9
@@ -7,24 +7,19 @@ module Kapusta
7
7
  private
8
8
 
9
9
  def emit_fn(args, env, current_scope)
10
- if args[0].is_a?(Vec)
11
- emit_lambda(args[0], args[1..], env, current_scope)
12
- else
13
- name_sym = args[0]
14
- pattern = args[1]
15
- body = args[2..]
16
- emit_error!(:fn_no_params) unless name_sym.is_a?(Sym) && pattern.is_a?(Vec)
17
-
18
- fn_env = env.child
19
- ruby_name = define_local(fn_env, name_sym.name)
20
- <<~RUBY.chomp
21
- lambda do
22
- #{ruby_name} = nil
23
- #{ruby_name} = #{emit_lambda(pattern, body, fn_env, current_scope)}
24
- #{ruby_name}
25
- end.call
26
- RUBY
27
- end
10
+ parsed = Language.parse_function_args(args)
11
+ emit_error!(:fn_no_params) unless parsed
12
+ return emit_lambda(parsed.params, parsed.body, env, current_scope) if parsed.anonymous?
13
+
14
+ fn_env = env.child
15
+ ruby_name = define_local(fn_env, parsed.name.name)
16
+ <<~RUBY.chomp
17
+ lambda do
18
+ #{ruby_name} = nil
19
+ #{ruby_name} = #{emit_lambda(parsed.params, parsed.body, fn_env, current_scope)}
20
+ #{ruby_name}
21
+ end.call
22
+ RUBY
28
23
  end
29
24
 
30
25
  def emit_lambda(pattern, body, env, current_scope)
@@ -89,19 +84,21 @@ module Kapusta
89
84
  end
90
85
 
91
86
  def register_self_method_binding(form, env)
92
- name_sym = form.items[1]
87
+ name_sym = Language.parse_function_form(form)&.name
93
88
  return unless name_sym.is_a?(Sym) && !name_sym.dotted?
94
89
 
95
90
  ruby_name = Kapusta.kebab_to_snake(name_sym.name)
96
91
  return unless direct_method_name?(ruby_name)
97
92
 
98
- env.define(name_sym.name, Env::SelfMethodBinding.new(ruby_name))
93
+ parsed = Language.parse_function_form(form)
94
+ env.define(name_sym.name, Env::SelfMethodBinding.new(ruby_name, multi_return_body?(parsed.body)))
99
95
  end
100
96
 
101
97
  def emit_toplevel_method_definition(form, env)
102
- name_sym = form.items[1]
103
- pattern = form.items[2]
104
- body = form.items[3..]
98
+ parsed = Language.parse_function_form(form)
99
+ name_sym = parsed.name
100
+ pattern = parsed.params
101
+ body = parsed.body
105
102
  return [nil, env] if name_sym.dotted?
106
103
  return [nil, env] unless simple_parameter_pattern?(pattern)
107
104
 
@@ -109,27 +106,30 @@ module Kapusta
109
106
  return [nil, env] unless ruby_name
110
107
  return [nil, env] if captures_outer_binding?(body, env, pattern_names(pattern))
111
108
 
112
- env.define(name_sym.name, Env::MethodBinding.new(ruby_name))
109
+ env.define(name_sym.name, Env::MethodBinding.new(ruby_name, multi_return_body?(body)))
113
110
  definition = emit_direct_method_definition(name_sym, pattern, body, env)
114
- if needs_toplevel_method_bridge?(ruby_name)
115
- definition = join_code(definition, emit_toplevel_method_bridge(ruby_name))
116
- end
117
111
  [definition, env]
118
112
  end
119
113
 
114
+ def multi_return_body?(body)
115
+ Language.list_head?(body.last, 'values')
116
+ end
117
+
120
118
  def emit_named_fn_assignment(form, env, current_scope)
121
- name_sym = form.items[1]
119
+ parsed = Language.parse_function_form(form)
120
+ name_sym = parsed.name
122
121
  ruby_name = define_local(env, name_sym.name)
123
122
  fn_env = env.child
124
123
  fn_env.define(name_sym.name, ruby_name)
125
- lambda_code = emit_lambda(form.items[2], form.items[3..], fn_env, current_scope)
124
+ lambda_code = emit_lambda(parsed.params, parsed.body, fn_env, current_scope)
126
125
  ["#{ruby_name} = nil\n#{ruby_name} = #{lambda_code}", env]
127
126
  end
128
127
 
129
128
  def emit_method_definition(form, env)
130
- name_sym = form.items[1]
131
- pattern = form.items[2]
132
- body = form.items[3..]
129
+ parsed = Language.parse_function_form(form)
130
+ name_sym = parsed.name
131
+ pattern = parsed.params
132
+ body = parsed.body
133
133
  direct_definition = emit_direct_method_definition(name_sym, pattern, body, env)
134
134
  return direct_definition if direct_definition
135
135
 
@@ -184,23 +184,6 @@ module Kapusta
184
184
  end
185
185
  end
186
186
 
187
- def needs_toplevel_method_bridge?(ruby_name)
188
- %w[context describe example it specify].include?(ruby_name)
189
- end
190
-
191
- def emit_toplevel_method_bridge(ruby_name)
192
- method_name = ruby_name.to_sym.inspect
193
- if mruby3_target?
194
- return [
195
- "define_singleton_method(#{method_name}) do |*args|",
196
- indent("Object.instance_method(#{method_name}).bind(self).call(*args)"),
197
- 'end'
198
- ].join("\n")
199
- end
200
-
201
- "define_singleton_method(#{method_name}, Object.instance_method(#{method_name}).bind(self))"
202
- end
203
-
204
187
  def emit_method_body(pattern, body, env)
205
188
  validate_fn_params!(pattern)
206
189
  return emit_simple_method_body(pattern, body, env) if simple_parameter_pattern?(pattern)
@@ -271,26 +254,29 @@ module Kapusta
271
254
  end
272
255
 
273
256
  def emit_let_parts(args, env, current_scope, result:)
274
- bindings = args[0]
257
+ parsed = Language.parse_let_args(args)
258
+ bindings = parsed.bindings
275
259
  emit_error!(:let_odd_bindings) if bindings.items.length.odd?
276
260
  emit_error!(:let_no_body) if args.length < 2
277
261
 
278
- body = args[1..]
279
262
  child_env = env.child
280
263
  binding_codes = []
281
- items = bindings.items
282
- i = 0
283
- while i < items.length
284
- pattern = items[i]
285
- value_form = items[i + 1]
264
+ parsed.binding_pairs.each do |pattern, value_form|
286
265
  check_destructure_value!(pattern, value_form)
266
+ if (constant_code = kapusta_require_module_constant(value_form)) && pattern.is_a?(Sym)
267
+ require_code = emit_expr(value_form, child_env, current_scope)
268
+ bind_code, child_env = emit_constant_require_bind(pattern, require_code, constant_code, child_env)
269
+ mark_mutability(child_env, pattern.name, mutable: false)
270
+ binding_codes << bind_code
271
+ next
272
+ end
273
+
287
274
  value_code = emit_expr(value_form, child_env, current_scope)
288
275
  bind_code, child_env = emit_pattern_bind(pattern, value_code, child_env)
289
276
  walk_pattern_syms(pattern) { |sym| mark_mutability(child_env, sym, mutable: false) }
290
277
  binding_codes << bind_code
291
- i += 2
292
278
  end
293
- body_code, = emit_sequence(body, child_env, current_scope,
279
+ body_code, = emit_sequence(parsed.body, child_env, current_scope,
294
280
  allow_method_definitions: false,
295
281
  result:)
296
282
  [join_binding_codes(binding_codes), body_code]
@@ -317,15 +303,24 @@ module Kapusta
317
303
  end
318
304
 
319
305
  def emit_local_form(form, env, current_scope, allow_constant: false)
320
- emit_error!(:local_arity, form: form.head.name) unless form.items.length == 3
306
+ parsed = Language.parse_binding_form(form)
307
+ emit_error!(:local_arity, form: form.head.name) unless parsed
321
308
 
322
- target = form.items[1]
323
- value_code = emit_expr(form.items[2], env, current_scope)
309
+ target = parsed.target
310
+ value_form = parsed.value
324
311
 
325
312
  if target.is_a?(Sym)
326
313
  validate_binding_symbol!(target)
327
- if allow_constant && form.head.name == 'local' &&
328
- constant_value?(form.items[2]) &&
314
+ if parsed.head == 'local' && (constant_code = kapusta_require_module_constant(value_form))
315
+ require_code = emit_expr(value_form, env, current_scope)
316
+ bind_code, env = emit_constant_require_bind(target, require_code, constant_code, env)
317
+ mark_mutability(env, target.name, mutable: false)
318
+ return ["#{bind_code}\nnil", env]
319
+ end
320
+
321
+ value_code = emit_expr(value_form, env, current_scope)
322
+ if allow_constant && parsed.head == 'local' &&
323
+ constant_value?(value_form) &&
329
324
  (constant_name = constant_name_for(target.name))
330
325
  env.define(target.name, constant_name)
331
326
  mark_mutability(env, target.name, mutable: false)
@@ -333,14 +328,23 @@ module Kapusta
333
328
  end
334
329
 
335
330
  ruby_name = define_local(env, target.name)
336
- mark_mutability(env, target.name, mutable: form.head.name == 'var')
331
+ mark_mutability(env, target.name, mutable: parsed.mutable?)
337
332
  ["#{ruby_name} = #{value_code}\nnil", env]
338
333
  else
334
+ value_code = emit_expr(value_form, env, current_scope)
339
335
  bind_code, env = emit_pattern_bind(target, value_code, env)
340
336
  [join_code(bind_code, 'nil'), env]
341
337
  end
342
338
  end
343
339
 
340
+ def emit_constant_require_bind(target, require_code, constant_code, env)
341
+ validate_binding_symbol!(target)
342
+ return [require_code, env] if target.name == '_'
343
+
344
+ ruby_name = define_local(env, target.name)
345
+ ["#{require_code}\n#{ruby_name} = #{constant_code}", env]
346
+ end
347
+
344
348
  def constant_name_for(source_name)
345
349
  candidate = source_name.tr('-', '_').upcase
346
350
  candidate if candidate.match?(/\A[A-Z][A-Z0-9_]*\z/)
@@ -354,7 +358,7 @@ module Kapusta
354
358
  end
355
359
 
356
360
  def check_destructure_value!(pattern, value_form)
357
- return unless pattern.is_a?(Vec) || pattern.is_a?(HashLit)
361
+ return unless pattern.is_a?(Vec) || pattern.is_a?(List) || pattern.is_a?(HashLit)
358
362
 
359
363
  case value_form
360
364
  when String, Numeric, Symbol, true, false
@@ -372,7 +376,7 @@ module Kapusta
372
376
  case pattern
373
377
  when Sym
374
378
  yield pattern unless pattern.name == '_'
375
- when Vec
379
+ when Vec, List
376
380
  pattern.items.each do |item|
377
381
  next if item.is_a?(Sym) && ['&', '...'].include?(item.name)
378
382
 
@@ -390,24 +394,29 @@ module Kapusta
390
394
  (@binding_mutability ||= {}).fetch(ruby_name, true)
391
395
  end
392
396
 
393
- def emit_local_expr(args, env, current_scope)
394
- code, = emit_local_form(List.new([Sym.new('local'), *args]), env.child, current_scope)
397
+ def emit_local_expr(head, args, env, current_scope)
398
+ parsed = Language.parse_binding_args(head, args)
399
+ emit_error!(:local_arity, form: head) unless parsed
400
+
401
+ synthetic = List.new([Sym.new(head), parsed.target, parsed.value])
402
+ code, = emit_local_form(synthetic, env.child, current_scope)
395
403
  "lambda do\n#{indent(code)}\nend.call"
396
404
  end
397
405
 
398
406
  def emit_global_expr(args, _env, _current_scope)
399
- emit_error!(:global_arity) unless args.length == 2
400
- unless args[0].is_a?(Sym)
401
- emit_error!(:global_non_symbol_name, type: args[0].class.name.downcase, value: args[0].inspect)
407
+ parsed = Language.parse_global_args(args)
408
+ emit_error!(:global_arity) unless parsed
409
+ unless parsed.name.is_a?(Sym)
410
+ emit_error!(:global_non_symbol_name, type: parsed.name.class.name.downcase, value: parsed.name.inspect)
402
411
  end
403
412
 
404
- name = args[0].name
405
- "$#{global_name(name)} = #{emit_expr(args[1], Env.new, :toplevel)}\nnil"
413
+ "$#{global_name(parsed.name.name)} = #{emit_expr(parsed.value, Env.new, :toplevel)}\nnil"
406
414
  end
407
415
 
408
416
  def emit_set_form(form, env, current_scope)
409
- target = form.items[1]
410
- value_code = emit_expr(form.items[2], env, current_scope)
417
+ parsed = Language.parse_set_form(form)
418
+ target = parsed.target
419
+ value_code = emit_expr(parsed.value, env, current_scope)
411
420
 
412
421
  if target.is_a?(Sym) && !target.dotted?
413
422
  binding = env.lookup_if_defined(target.name)
@@ -437,15 +446,22 @@ module Kapusta
437
446
  end
438
447
 
439
448
  def emit_set_expr(args, env, current_scope)
440
- target = args[0]
441
- value_code = emit_expr(args[1], env, current_scope)
442
- emit_set_target(target, value_code, env, current_scope)
449
+ parsed = Language.parse_set_args(args)
450
+ emit_set_target(parsed.target, emit_expr(parsed.value, env, current_scope), env, current_scope)
443
451
  end
444
452
 
445
453
  def emit_set_target(target, value_code, env, current_scope)
446
454
  case target
447
455
  when Sym
448
- if target.dotted?
456
+ if target.colonized?
457
+ base_code, segments = multihash_base(target.colon_segments, env)
458
+ receiver = simple_expression?(base_code) ? base_code : parenthesize(base_code)
459
+ prefix = segments[0...-1].map do |segment|
460
+ "[#{Kapusta.kebab_to_snake(segment).to_sym.inspect}]"
461
+ end.join
462
+ key = Kapusta.kebab_to_snake(segments.last).to_sym.inspect
463
+ emit_assignment("#{receiver}#{prefix}[#{key}]", value_code)
464
+ elsif target.dotted?
449
465
  base_code, segments = multisym_base(target.segments, env)
450
466
  receiver = emit_method_path(base_code, segments[0...-1])
451
467
  last = segments.last
@@ -463,19 +479,14 @@ module Kapusta
463
479
  emit_assignment(binding, value_code)
464
480
  end
465
481
  when List
466
- head = target.head
467
- if head.is_a?(Sym) && head.name == '.'
468
- object_code = emit_expr(target.items[1], env, current_scope)
469
- keys = target.items[2..].map { |item| emit_expr(item, env, current_scope) }
482
+ if (dot_target = Language.parse_dot_target(target))
483
+ object_code = emit_expr(dot_target.object, env, current_scope)
484
+ keys = dot_target.keys.map { |item| emit_expr(item, env, current_scope) }
470
485
  receiver = simple_expression?(object_code) ? object_code : parenthesize(object_code)
471
486
  prefix = keys[0...-1].map { |k| "[#{k}]" }.join
472
487
  emit_assignment("#{receiver}#{prefix}[#{keys.last}]", value_code)
473
- elsif head.is_a?(Sym) && head.name == 'ivar'
474
- emit_assignment("@#{Kapusta.kebab_to_snake(target.items[1].name)}", value_code)
475
- elsif head.is_a?(Sym) && head.name == 'cvar'
476
- emit_assignment("@@#{Kapusta.kebab_to_snake(target.items[1].name)}", value_code)
477
- elsif head.is_a?(Sym) && head.name == 'gvar'
478
- emit_assignment("$#{global_name(target.items[1].name)}", value_code)
488
+ elsif (sigil = Language.parse_sigil_form(target))
489
+ emit_sigil_assignment(sigil, value_code)
479
490
  else
480
491
  emit_error!(:bad_set_target, target: target.inspect)
481
492
  end
@@ -483,6 +494,17 @@ module Kapusta
483
494
  emit_error!(:bad_set_target, target: target.inspect)
484
495
  end
485
496
  end
497
+
498
+ def emit_sigil_assignment(sigil, value_code)
499
+ name = sigil.name
500
+ emit_error!(:bad_set_target, target: sigil.inspect) unless name.is_a?(Sym)
501
+
502
+ case sigil.kind
503
+ when :ivar then emit_assignment("@#{Kapusta.kebab_to_snake(name.name)}", value_code)
504
+ when :cvar then emit_assignment("@@#{Kapusta.kebab_to_snake(name.name)}", value_code)
505
+ when :gvar then emit_assignment("$#{global_name(name.name)}", value_code)
506
+ end
507
+ end
486
508
  end
487
509
  end
488
510
  end
@@ -9,37 +9,32 @@ module Kapusta
9
9
  private
10
10
 
11
11
  def emit_icollect(args, env, current_scope)
12
- emit_error!(:icollect_no_iterator) unless args[0].is_a?(Vec) && args[0].items.length >= 2
12
+ parsed = Language.parse_iteration_args(args)
13
+ emit_error!(:icollect_no_iterator) unless parsed.bindings.is_a?(Vec) && parsed.bindings.items.length >= 2
13
14
 
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
15
+ emit_iteration(parsed.bindings, env, current_scope, method: 'filter_map') do |iter_env|
16
+ emit_sequence(parsed.body, iter_env, current_scope, allow_method_definitions: false).first
16
17
  end
17
18
  end
18
19
 
19
20
  def emit_collect(args, env, current_scope)
21
+ parsed = Language.parse_iteration_args(args)
20
22
  result_var = temp('result')
21
- values_form = simple_values_call(args[1]) if args.length == 2
22
- emit_iteration(args[0], env, current_scope,
23
+ values_form = Language.parse_values_form(parsed.body[0]) if parsed.body.length == 1
24
+ emit_iteration(parsed.bindings, env, current_scope,
23
25
  method: 'each_with_object({})', extra_block_param: result_var) do |iter_env|
24
26
  if values_form
25
27
  emit_collect_values_step(result_var, values_form, iter_env, current_scope)
26
28
  else
27
- body = emit_sequence(args[1..], iter_env, current_scope, allow_method_definitions: false).first
29
+ body = emit_sequence(parsed.body, iter_env, current_scope, allow_method_definitions: false).first
28
30
  emit_hash_collection_step(result_var, body)
29
31
  end
30
32
  end
31
33
  end
32
34
 
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
35
  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]
36
+ key_form = values_form.key
37
+ val_form = values_form.value
43
38
  key_code = emit_expr(key_form, iter_env, current_scope)
44
39
  val_code = emit_expr(val_form, iter_env, current_scope)
45
40
  assignment = "#{result_var}[#{key_code}] = #{val_code}"
@@ -60,32 +55,29 @@ module Kapusta
60
55
 
61
56
  def emit_fcollect(args, env, current_scope)
62
57
  result_var = temp('result')
63
- parsed = parse_counted_for_bindings(args[0].items, env, current_scope)
64
- body_code, = emit_sequence(args[1..], parsed[:loop_env], current_scope, allow_method_definitions: false)
58
+ loop_form = Language.parse_counted_for_args(args)
59
+ parsed = parse_counted_for_bindings(loop_form, env, current_scope)
60
+ body_code, = emit_sequence(loop_form.body, parsed[:loop_env], current_scope,
61
+ allow_method_definitions: false)
65
62
  collecting_body = emit_array_collection_step(result_var, body_code)
66
63
  loop_code = emit_counted_loop(**parsed, current_scope:, body_code: collecting_body)
67
64
  emit_collection_result(result_var, '[]', loop_code)
68
65
  end
69
66
 
70
67
  def emit_accumulate(args, env, current_scope)
71
- bindings = args[0].items
72
- emit_error!(:accumulate_no_iterator) if bindings.length < 4
73
-
74
- acc_name = bindings[0]
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]
68
+ parsed = Language.parse_accumulate_args(args)
69
+ emit_error!(:accumulate_no_iterator) if parsed.items.length < 4
79
70
 
71
+ init_code = emit_expr(parsed.initial, env, current_scope)
80
72
  body_env = env.child
81
- acc_var = define_local(body_env, acc_name.name)
73
+ acc_var = define_local(body_env, parsed.acc_name.name)
82
74
 
83
- inject_code = try_emit_inject(iter_expr, binding_pats, body_env, env, current_scope, acc_var,
84
- init_code, args[1..])
75
+ inject_code = try_emit_inject(parsed.iter_expr, parsed.binding_pats, body_env, env, current_scope,
76
+ acc_var, init_code, parsed.body)
85
77
  return inject_code if inject_code
86
78
 
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)
79
+ iter_code = emit_iteration(Vec.new(parsed.iter_items), body_env, current_scope) do |iter_env|
80
+ body_code, = emit_sequence(parsed.body, iter_env, current_scope, allow_method_definitions: false)
89
81
  emit_sequence_value_assignment(acc_var, body_code)
90
82
  end
91
83
  [
@@ -108,18 +100,18 @@ module Kapusta
108
100
  end
109
101
 
110
102
  def emit_faccumulate(args, env, current_scope)
111
- bindings = args[0].items
112
- emit_error!(:accumulate_no_iterator) if bindings.length < 5
103
+ parsed = Language.parse_faccumulate_args(args)
104
+ emit_error!(:accumulate_no_iterator) if parsed.items.length < 5
113
105
 
114
106
  body_env = env.child
115
- acc_var = define_local(body_env, bindings[0].name)
116
- loop_var = define_local(body_env, bindings[2].name)
117
-
118
- init_code = emit_expr(bindings[1], env, current_scope)
119
- start_code = emit_expr(bindings[3], env, current_scope)
120
- finish_code = emit_expr(bindings[4], env, current_scope)
121
- step_code = bindings[5] ? emit_expr(bindings[5], env, current_scope) : nil
122
- body_code, = emit_sequence(args[1..], body_env, current_scope, allow_method_definitions: false)
107
+ acc_var = define_local(body_env, parsed.acc_name.name)
108
+ loop_var = define_local(body_env, parsed.counter.name)
109
+
110
+ init_code = emit_expr(parsed.initial, env, current_scope)
111
+ start_code = emit_expr(parsed.start, env, current_scope)
112
+ finish_code = emit_expr(parsed.finish, env, current_scope)
113
+ step_code = parsed.step ? emit_expr(parsed.step, env, current_scope) : nil
114
+ body_code, = emit_sequence(parsed.body, body_env, current_scope, allow_method_definitions: false)
123
115
 
124
116
  receiver =
125
117
  if step_code
@@ -53,7 +53,7 @@ module Kapusta
53
53
  end
54
54
 
55
55
  def if_form?(form)
56
- form.is_a?(List) && form.head.is_a?(Sym) && form.head.name == 'if'
56
+ Language.list_head?(form, 'if')
57
57
  end
58
58
 
59
59
  def emit_case(args, env, current_scope, mode)
@@ -78,28 +78,28 @@ module Kapusta
78
78
  def build_case_parts(args, env, current_scope, mode)
79
79
  emit_error!(:case_no_subject) if args.empty?
80
80
 
81
- clauses = args[1..]
82
- emit_error!(:case_no_patterns) if clauses.empty?
83
- emit_error!(:case_odd_patterns) if clauses.length.odd?
81
+ parsed = Language.parse_case_args(args)
82
+ emit_error!(:case_no_patterns) if parsed.clauses.empty?
83
+ emit_error!(:case_odd_patterns) if parsed.clauses.length.odd?
84
84
 
85
- value_code = emit_expr(args[0], env, current_scope)
86
- if simple_case_subject?(args[0]) && simple_expression?(value_code)
87
- body = emit_case_body(value_code, clauses, env, current_scope, mode)
85
+ value_code = emit_expr(parsed.subject, env, current_scope)
86
+ if simple_case_subject?(parsed.subject) && simple_expression?(value_code)
87
+ body = emit_case_body(value_code, parsed, env, current_scope, mode)
88
88
  emit_error!(:case_unsupported) unless body
89
89
  return [value_code, nil, body]
90
90
  end
91
91
 
92
92
  value_var = temp('case_value')
93
- body = emit_case_body(value_var, clauses, env, current_scope, mode)
93
+ body = emit_case_body(value_var, parsed, env, current_scope, mode)
94
94
  emit_error!(:case_unsupported) unless body
95
95
  [value_code, value_var, body]
96
96
  end
97
97
 
98
- def emit_case_body(value_var, clauses, env, current_scope, mode)
99
- return try_emit_compat_case(value_var, clauses, env, current_scope, mode) if mruby3_target?
98
+ def emit_case_body(value_var, parsed, env, current_scope, mode)
99
+ return try_emit_compat_case(value_var, parsed, env, current_scope, mode) if mruby3_target?
100
100
 
101
- try_emit_native_case(value_var, clauses, env, current_scope, mode) ||
102
- try_emit_compat_case(value_var, clauses, env, current_scope, mode)
101
+ try_emit_native_case(value_var, parsed, env, current_scope, mode) ||
102
+ try_emit_compat_case(value_var, parsed, env, current_scope, mode)
103
103
  end
104
104
 
105
105
  def simple_case_subject?(form)
@@ -110,29 +110,28 @@ module Kapusta
110
110
  end
111
111
  end
112
112
 
113
- def try_emit_native_case(value_var, clauses, env, current_scope, mode)
114
- arms = collect_case_arms(clauses) do |pattern, body, where_guards|
113
+ def try_emit_native_case(value_var, parsed, env, current_scope, mode)
114
+ arms = collect_case_arms(parsed.complete_arms) do |pattern, body, where_guards|
115
115
  try_native_arm(pattern, body, where_guards, env, current_scope, mode)
116
116
  end
117
117
  return unless arms
118
118
 
119
- arms << ['else', indent('nil')].join("\n") unless wildcard_last?(clauses)
119
+ arms << ['else', indent('nil')].join("\n") unless wildcard_last?(parsed.complete_arms)
120
120
  ["case #{value_var}", *arms, 'end'].join("\n")
121
121
  end
122
122
 
123
- def collect_case_arms(clauses)
123
+ def collect_case_arms(arm_pairs)
124
124
  arms = []
125
125
  i = 0
126
- while i < clauses.length
127
- pattern = clauses[i]
128
- body = clauses[i + 1]
126
+ while i < arm_pairs.length
127
+ pattern, body = arm_pairs[i]
129
128
  inner, where_guards = extract_pattern_and_guards(pattern)
130
- sub_patterns = or_pattern?(inner) ? inner.items[1..] : [inner]
129
+ sub_patterns = Language.parse_or_pattern(inner)&.alternatives || [inner]
131
130
  sub_arms = sub_patterns.map { |sub| yield sub, body, where_guards }
132
131
  return if sub_arms.any?(&:nil?)
133
132
 
134
133
  arms.concat(sub_arms)
135
- i += 2
134
+ i += 1
136
135
  end
137
136
  arms
138
137
  end
@@ -151,8 +150,8 @@ module Kapusta
151
150
  ["in #{plan[:pattern]}#{guard_clause}", indent(body_code)].join("\n")
152
151
  end
153
152
 
154
- def try_emit_compat_case(value_var, clauses, env, current_scope, mode)
155
- arms = collect_case_arms(clauses) do |pattern, body, where_guards|
153
+ def try_emit_compat_case(value_var, parsed, env, current_scope, mode)
154
+ arms = collect_case_arms(parsed.complete_arms) do |pattern, body, where_guards|
156
155
  try_compat_arm(pattern, body, where_guards, value_var, env, current_scope, mode)
157
156
  end
158
157
  return unless arms
@@ -160,15 +159,16 @@ module Kapusta
160
159
  emit_compat_case_lines(arms)
161
160
  end
162
161
 
163
- def wildcard_last?(clauses)
164
- last_pattern = clauses[-2]
162
+ def wildcard_last?(arm_pairs)
163
+ last_pattern = arm_pairs.last&.first
165
164
  last_pattern.is_a?(Sym) && last_pattern.name == '_'
166
165
  end
167
166
 
168
167
  def extract_pattern_and_guards(pattern)
169
- return [pattern, []] unless where_pattern?(pattern)
168
+ parsed = Language.parse_where_pattern(pattern)
169
+ return [pattern, []] unless parsed
170
170
 
171
- [pattern.items[1], pattern.items[2..]]
171
+ [parsed.inner, parsed.guards]
172
172
  end
173
173
 
174
174
  def try_compat_arm(pattern, body, where_guards, value_var, env, current_scope, mode)
@@ -251,16 +251,18 @@ module Kapusta
251
251
  end
252
252
 
253
253
  def emit_for_statement(args, env, current_scope)
254
- parsed = parse_counted_for_bindings(args[0].items, env, current_scope)
255
- body_code, = emit_sequence(args[1..], parsed[:loop_env], current_scope,
254
+ loop_form = Language.parse_counted_for_args(args)
255
+ parsed = parse_counted_for_bindings(loop_form, env, current_scope)
256
+ body_code, = emit_sequence(loop_form.body, parsed[:loop_env], current_scope,
256
257
  allow_method_definitions: false,
257
258
  result: false)
258
259
  emit_counted_loop(**parsed, current_scope:, body_code:)
259
260
  end
260
261
 
261
262
  def emit_each_statement(args, env, current_scope)
262
- emit_iteration(args[0], env, current_scope) do |iter_env|
263
- emit_sequence(args[1..], iter_env, current_scope,
263
+ parsed = Language.parse_iteration_args(args)
264
+ emit_iteration(parsed.bindings, env, current_scope) do |iter_env|
265
+ emit_sequence(parsed.body, iter_env, current_scope,
264
266
  allow_method_definitions: false,
265
267
  result: false).first
266
268
  end