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
@@ -37,6 +37,7 @@ module Kapusta
37
37
  args = list.rest
38
38
  if head.is_a?(Sym)
39
39
  return emit_special(head.name, args, env, current_scope) if special_form?(head.name)
40
+ return emit_multihash_call(head, args, env, current_scope) if head.colonized?
40
41
  return emit_multisym_call(head, args, env, current_scope) if head.dotted?
41
42
  if (binding = env.lookup_if_defined(head.name))
42
43
  return emit_bound_call(binding, args, env, current_scope)
@@ -50,14 +51,29 @@ module Kapusta
50
51
  emit_error!(:cannot_call_literal, value: head.inspect)
51
52
  end
52
53
 
54
+ if (method_call = method_call_with_receiver_expression(head, args))
55
+ return emit_method_call(method_call, env, current_scope)
56
+ end
57
+
53
58
  emit_callable_call(emit_expr(head, env, current_scope), args, env, current_scope)
54
59
  end
55
60
 
61
+ def method_call_with_receiver_expression(head, args)
62
+ return unless head.is_a?(List)
63
+ return unless head.items.length == 3
64
+ return unless head.head.is_a?(Sym) && head.head.name == '.'
65
+
66
+ method = head.items[2]
67
+ return unless method.is_a?(Symbol) || method.is_a?(String)
68
+
69
+ [head.items[1], method, *args]
70
+ end
71
+
56
72
  def emit_special(name, args, env, current_scope)
57
73
  case name
58
- when 'fn', 'lambda', 'λ' then emit_fn(args, env, current_scope)
74
+ when *Language::FUNCTION_HEADS then emit_fn(args, env, current_scope)
59
75
  when 'let' then emit_let(args, env, current_scope)
60
- when 'local', 'var' then emit_local_expr(args, env, current_scope)
76
+ when 'local', 'var' then emit_local_expr(name, args, env, current_scope)
61
77
  when 'global' then emit_global_expr(args, env, current_scope)
62
78
  when 'set' then emit_set_expr(args, env, current_scope)
63
79
  when 'if' then emit_if(args, env, current_scope)
@@ -75,9 +91,9 @@ module Kapusta
75
91
  when 'accumulate' then emit_accumulate(args, env, current_scope)
76
92
  when 'faccumulate' then emit_faccumulate(args, env, current_scope)
77
93
  when 'hashfn' then emit_hashfn(args, env, current_scope)
78
- when '.' then emit_lookup(args, env, current_scope)
79
- when '?.' then emit_safe_lookup(args, env, current_scope)
80
- when ':' then emit_colon(args, env, current_scope)
94
+ when '.' then emit_method_call(args, env, current_scope)
95
+ when '?:' then emit_safe_lookup(args, env, current_scope)
96
+ when ':' then emit_lookup(args, env, current_scope)
81
97
  when '..' then emit_concat(args, env, current_scope)
82
98
  when 'length' then "#{parenthesize(emit_expr(args[0], env, current_scope))}.length"
83
99
  when 'require' then emit_require(args[0], env, current_scope)
@@ -7,7 +7,7 @@ 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?
10
+ emit_error!(:dot_no_args) if args.length < 2
11
11
 
12
12
  object_code = emit_expr(args[0], env, current_scope)
13
13
  keys = args[1..].map { |arg| emit_expr(arg, env, current_scope) }
@@ -27,10 +27,12 @@ module Kapusta
27
27
  BINARY_OPERATOR_METHODS = %w[<=> ** << >> & | ^ === =~].freeze
28
28
  private_constant :BINARY_OPERATOR_METHODS
29
29
 
30
- def emit_colon(args, env, current_scope)
30
+ def emit_method_call(args, env, current_scope)
31
+ emit_error!(:dot_no_args) if args.empty?
32
+
31
33
  receiver = emit_expr(args[0], env, current_scope)
32
34
  method_form = args[1]
33
- positional, kwargs, block_form = split_call_args(args[2..], env, current_scope)
35
+ positional, kwargs, block_form = split_call_args(args[2..] || [], env, current_scope)
34
36
  literal_name = method_form if method_form.is_a?(Symbol) || method_form.is_a?(String)
35
37
  if literal_name && binary_operator_call?(literal_name.to_s, positional, kwargs, block_form)
36
38
  return emit_binary_operator_call(receiver, literal_name.to_s, positional[0])
@@ -56,6 +58,13 @@ module Kapusta
56
58
  end
57
59
 
58
60
  def emit_require(arg, env, current_scope)
61
+ if arg.is_a?(Symbol)
62
+ feature = arg.to_s.tr('.', '/')
63
+ return "require_relative #{feature.inspect}" if kapusta_feature_source?(feature)
64
+
65
+ return "require #{feature.inspect}"
66
+ end
67
+
59
68
  literal = require_path_literal(arg)
60
69
  if literal&.match?(%r{\A\.\.?/})
61
70
  cleaned = literal.delete_suffix('.kap').sub(%r{\A\./}, '')
@@ -71,6 +80,76 @@ module Kapusta
71
80
  "require #{path_code}"
72
81
  end
73
82
 
83
+ def kapusta_require_module_constant(form)
84
+ return unless form.is_a?(List)
85
+ return unless form.head.is_a?(Sym) && form.head.name == 'require'
86
+ return unless form.rest.length == 1
87
+
88
+ path = kapusta_require_source_path(form.rest[0])
89
+ return unless path
90
+
91
+ header = Reader.read_all(File.read(path)).first
92
+ return unless Language.header_form?(header)
93
+
94
+ segments = header_constant_segments(header)
95
+ segments&.join('::')
96
+ rescue Errno::ENOENT, Kapusta::Error
97
+ nil
98
+ end
99
+
100
+ def kapusta_require_source_path(arg)
101
+ return kapusta_feature_source_path(arg.to_s.tr('.', '/')) if arg.is_a?(Symbol)
102
+
103
+ literal = require_path_literal(arg)
104
+ return unless literal&.match?(%r{\A\.\.?/})
105
+
106
+ kapusta_local_source_path(literal)
107
+ end
108
+
109
+ def kapusta_feature_source?(feature)
110
+ !kapusta_feature_source_path(feature).nil?
111
+ end
112
+
113
+ def kapusta_feature_source_path(feature)
114
+ return if @path.nil? || @path.start_with?('(')
115
+
116
+ path = File.expand_path("#{feature}.kap", File.dirname(File.expand_path(@path)))
117
+ path if File.file?(path)
118
+ end
119
+
120
+ def kapusta_local_source?(feature)
121
+ !kapusta_local_source_path(feature).nil?
122
+ end
123
+
124
+ def kapusta_local_source_path(feature)
125
+ return if @path.nil? || @path.start_with?('(')
126
+
127
+ base = File.dirname(File.expand_path(@path))
128
+ path = File.absolute_path?(feature) ? feature : File.expand_path(feature, base)
129
+ candidates = File.extname(path).empty? ? ["#{path}.kap"] : [path]
130
+ candidates.find { |candidate| File.file?(candidate) && candidate.end_with?('.kap') }
131
+ end
132
+
133
+ def header_constant_segments(header)
134
+ parsed =
135
+ if header.head.name == 'module'
136
+ Language.parse_module_form(header)
137
+ else
138
+ Language.parse_class_form(header)
139
+ end
140
+ segments = constant_segments(parsed.name)
141
+ return unless segments
142
+
143
+ if header.head.name == 'module' &&
144
+ parsed.body.length == 1 &&
145
+ Language.bodyless_header?(parsed.body[0])
146
+ inner_segments = header_constant_segments(parsed.body[0])
147
+ return [*segments, *inner_segments] if inner_segments
148
+ end
149
+
150
+ segments
151
+ end
152
+
74
153
  def require_path_literal(arg)
75
154
  case arg
76
155
  when Sym then arg.name
@@ -80,18 +159,19 @@ module Kapusta
80
159
  end
81
160
 
82
161
  def emit_module_expr(args, env)
162
+ parsed = Language.parse_module_args(args)
83
163
  body = with_class_body do
84
- emit_sequence(args[1..], env.child, :module, allow_method_definitions: true, result: false).first
164
+ emit_sequence(parsed.body, env.child, :module, allow_method_definitions: true, result: false).first
85
165
  end
86
- emit_module_wrapper(args[0], body)
166
+ emit_module_wrapper(parsed.name, body)
87
167
  end
88
168
 
89
169
  def emit_class_expr(args, env)
90
- name_sym, supers, body_forms = split_class_args(args)
170
+ parsed = Language.parse_class_args(args)
91
171
  body = with_class_body do
92
- emit_sequence(body_forms, env.child, :class, allow_method_definitions: true, result: false).first
172
+ emit_sequence(parsed.body, env.child, :class, allow_method_definitions: true, result: false).first
93
173
  end
94
- emit_class_wrapper(name_sym, supers, env,
174
+ emit_class_wrapper(parsed.name, parsed.supers, env,
95
175
  body)
96
176
  end
97
177
 
@@ -158,51 +238,32 @@ module Kapusta
158
238
  end
159
239
 
160
240
  def emit_try(args, env, current_scope)
161
- catches = []
162
- finally_bodies = []
163
- args[1..].each do |clause|
164
- head = clause.head
165
- if head.is_a?(Sym) && head.name == 'catch'
166
- rest = clause.items[1..]
167
- if rest[0].is_a?(Sym) && (rest[0].name.match?(/\A[A-Z]/) || rest[0].dotted?)
168
- klass_form = rest[0]
169
- bind_sym = rest[1]
170
- body = rest[2..]
171
- else
172
- klass_form = nil
173
- bind_sym = rest[0]
174
- body = rest[1..]
175
- end
176
- catches << [klass_form, bind_sym, body]
177
- elsif head.is_a?(Sym) && head.name == 'finally'
178
- finally_bodies << clause.items[1..]
179
- end
180
- end
181
-
182
- body_form = args[0]
241
+ parsed = Language.parse_try_args(args)
242
+ body_form = parsed.body
183
243
  body_code =
184
- if body_form.is_a?(List) && body_form.head.is_a?(Sym) && body_form.head.name == 'do'
244
+ if Language.do_form?(body_form)
185
245
  emit_sequence(body_form.rest, env, current_scope, allow_method_definitions: false).first
186
246
  else
187
247
  emit_expr(body_form, env, current_scope)
188
248
  end
189
249
  lines = ['begin', indent(body_code)]
190
- catches.each do |klass_form, bind_sym, body|
250
+ parsed.clauses.grep(Language::CatchClause).each do |clause|
191
251
  rescue_env = env.child
192
- rescue_name = define_local(rescue_env, bind_sym.name)
193
- body_code, = emit_sequence(body, rescue_env, current_scope, allow_method_definitions: false)
252
+ rescue_name = define_local(rescue_env, clause.bind_sym.name)
253
+ body_code, = emit_sequence(clause.body, rescue_env, current_scope, allow_method_definitions: false)
194
254
  rescue_line =
195
- if klass_form
196
- "rescue #{emit_expr(klass_form, env, current_scope)} => #{rescue_name}"
255
+ if clause.klass
256
+ "rescue #{emit_expr(clause.klass, env, current_scope)} => #{rescue_name}"
197
257
  else
198
258
  "rescue StandardError => #{rescue_name}"
199
259
  end
200
260
  lines << rescue_line
201
261
  lines << indent(body_code)
202
262
  end
203
- unless finally_bodies.empty?
204
- ensure_code = finally_bodies.map do |body|
205
- emit_sequence(body, env, current_scope, allow_method_definitions: false).first
263
+ finally_clauses = parsed.clauses.grep(Language::FinallyClause)
264
+ unless finally_clauses.empty?
265
+ ensure_code = finally_clauses.map do |clause|
266
+ emit_sequence(clause.body, env, current_scope, allow_method_definitions: false).first
206
267
  end.join("\n")
207
268
  lines << 'ensure'
208
269
  lines << indent(ensure_code)
@@ -310,7 +371,7 @@ module Kapusta
310
371
  end
311
372
 
312
373
  def emit_self_method_binding_call(binding, args, env, current_scope)
313
- positional = args.map { |arg| emit_expr(arg, env, current_scope) }
374
+ positional = emit_call_args(args, env, current_scope)
314
375
  emit_direct_self_method_call(binding.ruby_name, positional)
315
376
  end
316
377
 
@@ -341,6 +402,11 @@ module Kapusta
341
402
  end
342
403
  end
343
404
 
405
+ def emit_multihash_call(head, args, env, current_scope)
406
+ lookup_code = emit_multihash_value(head, env)
407
+ emit_callable_call(lookup_code, args, env, current_scope)
408
+ end
409
+
344
410
  def emit_method_path(base_code, segments)
345
411
  segments.reduce(base_code) do |acc, segment|
346
412
  snake = Kapusta.kebab_to_snake(segment)
@@ -394,27 +460,41 @@ module Kapusta
394
460
 
395
461
  if !remaining.empty? && remaining.last.is_a?(HashLit) && remaining.last.all_sym_keys?
396
462
  kwargs = emit_expr(remaining.last, env, current_scope)
397
- positional = remaining[0...-1].map { |arg| emit_expr(arg, env, current_scope) }
463
+ positional = emit_call_args(remaining[0...-1], env, current_scope)
398
464
  else
399
465
  kwargs = nil
400
- positional = remaining.map { |arg| emit_expr(arg, env, current_scope) }
466
+ positional = emit_call_args(remaining, env, current_scope)
401
467
  end
402
468
  [positional, kwargs, block_form]
403
469
  end
404
470
 
471
+ def emit_call_args(args, env, current_scope)
472
+ args.map.with_index do |arg, index|
473
+ code = emit_expr(arg, env, current_scope)
474
+ index == args.length - 1 && multi_value_call_arg?(arg, env) ? "*#{code}" : code
475
+ end
476
+ end
477
+
478
+ def multi_value_call_arg?(arg, env)
479
+ return true if Language.list_head?(arg, 'values')
480
+ return false unless arg.is_a?(List) && arg.head.is_a?(Sym)
481
+
482
+ binding = env.lookup_if_defined(arg.head.name)
483
+ callable_method_binding?(binding) && binding.multi_return
484
+ end
485
+
405
486
  def emit_block_proc(block_form, env, current_scope)
406
487
  block_form && emit_expr(block_form, env, current_scope)
407
488
  end
408
489
 
409
490
  def emit_attached_block(block_form, env, current_scope)
410
- return unless block_form.is_a?(List) && block_form.head.is_a?(Sym)
411
- return unless %w[fn lambda λ].include?(block_form.head.name)
491
+ parsed = Language.parse_function_form(block_form)
492
+ return unless parsed&.anonymous?
412
493
 
413
- pattern = block_form.items[1]
494
+ pattern = parsed.params
414
495
  return unless pattern.is_a?(Vec) && simple_parameter_pattern?(pattern)
415
496
 
416
- body = block_form.items[2..]
417
- params, body_code = build_simple_block_parts(pattern, body, env, current_scope)
497
+ params, body_code = build_simple_block_parts(pattern, parsed.body, env, current_scope)
418
498
  header = params.empty? ? 'do' : "do |#{params.join(', ')}|"
419
499
  [header, indent(body_code), 'end'].join("\n")
420
500
  end
@@ -439,6 +519,7 @@ module Kapusta
439
519
  end
440
520
 
441
521
  emit_error!(:unexpected_vararg) if name == '...'
522
+ return emit_multihash_value(sym, env) if sym.colonized?
442
523
  return emit_multisym_value(sym, env) if sym.dotted?
443
524
  return 'ARGV' if name == 'ARGV'
444
525
  return name if name.match?(/\A[A-Z]/)
@@ -460,6 +541,33 @@ module Kapusta
460
541
  emit_method_path(base_code, segments)
461
542
  end
462
543
 
544
+ def emit_multihash_value(sym, env)
545
+ base_code, segments = multihash_base(sym.colon_segments, env)
546
+ emit_hash_lookup_path(base_code, segments)
547
+ end
548
+
549
+ def emit_hash_lookup_path(base_code, segments)
550
+ receiver = simple_expression?(base_code) ? base_code : parenthesize(base_code)
551
+ segments.reduce(receiver) do |acc, segment|
552
+ "#{acc}[#{Kapusta.kebab_to_snake(segment).to_sym.inspect}]"
553
+ end
554
+ end
555
+
556
+ def multihash_base(segments, env)
557
+ first = segments[0]
558
+ if first == 'self'
559
+ ['self', segments[1..]]
560
+ elsif (binding = env.lookup_if_defined(first))
561
+ [binding_value_code(binding), segments[1..]]
562
+ elsif first == 'ARGV'
563
+ ['ARGV', segments[1..]]
564
+ elsif first.match?(/\A[A-Z]/)
565
+ [first, segments[1..]]
566
+ else
567
+ emit_error!(:undefined_symbol, name: first)
568
+ end
569
+ end
570
+
463
571
  def multisym_base(segments, env)
464
572
  if segments[0] == 'self'
465
573
  ['self', segments[1..]]
@@ -472,12 +580,24 @@ module Kapusta
472
580
  const_path << segments[idx]
473
581
  idx += 1
474
582
  end
475
- emit_error!(:bad_multisym, path: segments.join('.')) if const_path.empty?
583
+ emit_bad_multisym!(segments) if const_path.empty?
476
584
 
477
585
  [const_path.join('::'), segments[idx..]]
478
586
  end
479
587
  end
480
588
 
589
+ def emit_bad_multisym!(segments)
590
+ root = segments[0]
591
+ emit_error!(:bad_multisym,
592
+ path: segments.join('.'),
593
+ segment: root,
594
+ suggestion: bad_multisym_suggestion(root))
595
+ end
596
+
597
+ def bad_multisym_suggestion(root)
598
+ "bind #{root} first, use a capitalized constant path, or call a method on an explicit receiver"
599
+ end
600
+
481
601
  def parenthesize(code)
482
602
  return code if simple_expression?(code)
483
603
 
@@ -23,7 +23,7 @@ module Kapusta
23
23
 
24
24
  def validate_binding_symbol!(sym)
25
25
  name = sym.name
26
- emit_error!(:shadowed_special, name:) if Compiler::SPECIAL_FORMS.include?(name)
26
+ emit_error!(:shadowed_special, name:) if Language.special_form?(name)
27
27
  return unless sym.is_a?(MacroSym)
28
28
 
29
29
  emit_error!(:macro_unsafe_bind, name:)
@@ -42,8 +42,8 @@ module Kapusta
42
42
 
43
43
  def try_emit_native_pattern_bind(pattern, value_code, env)
44
44
  case pattern
45
- when Vec
46
- try_emit_native_vec_bind(pattern, value_code, env)
45
+ when Vec, List
46
+ try_emit_native_seq_bind(pattern, value_code, env)
47
47
  when HashLit
48
48
  try_emit_native_hash_bind(pattern, value_code, env)
49
49
  end
@@ -51,7 +51,7 @@ module Kapusta
51
51
  nil
52
52
  end
53
53
 
54
- def try_emit_native_vec_bind(pattern, value_code, env)
54
+ def try_emit_native_seq_bind(pattern, value_code, env)
55
55
  validate_destructure_pattern!(pattern)
56
56
  parts = []
57
57
  deferred = []
@@ -256,7 +256,7 @@ module Kapusta
256
256
  def compile_compat_pin(pattern, value_code, env, mode:, allow_pins:, state:)
257
257
  raise PatternNotTranslatable unless allow_pins && mode == :case
258
258
 
259
- name_sym = pattern.items[1]
259
+ name_sym = Language.parse_pin_pattern(pattern)&.name
260
260
  raise PatternNotTranslatable unless name_sym.is_a?(Sym)
261
261
 
262
262
  binding = env.lookup_if_defined(name_sym.name)
@@ -405,7 +405,7 @@ module Kapusta
405
405
  def compile_native_pin(pattern, env, mode:, allow_pins:)
406
406
  raise PatternNotTranslatable unless allow_pins && mode == :case
407
407
 
408
- name_sym = pattern.items[1]
408
+ name_sym = Language.parse_pin_pattern(pattern)&.name
409
409
  raise PatternNotTranslatable unless name_sym.is_a?(Sym)
410
410
 
411
411
  binding = env.lookup_if_defined(name_sym.name)
@@ -418,7 +418,7 @@ module Kapusta
418
418
  initial_bound = state[:bound_names].dup
419
419
  initial_names = state[:binding_names].length
420
420
  initial_guards = state[:guards].length
421
- variants = pattern.items[1..].map do |subpattern|
421
+ variants = Language.parse_or_pattern(pattern).alternatives.map do |subpattern|
422
422
  alt_state = {
423
423
  bound_names: initial_bound.dup,
424
424
  binding_names: state[:binding_names].dup,
@@ -446,25 +446,23 @@ module Kapusta
446
446
  when HashLit
447
447
  pattern.pairs.flat_map { |_key, value| pattern_names(value) }
448
448
  when List
449
- where_pattern?(pattern) ? pattern_names(pattern.items[1]) : []
449
+ parsed = Language.parse_where_pattern(pattern)
450
+ parsed ? pattern_names(parsed.inner) : []
450
451
  else
451
452
  []
452
453
  end
453
454
  end
454
455
 
455
456
  def where_pattern?(pattern)
456
- pattern.is_a?(List) && pattern.head.is_a?(Sym) && pattern.head.name == 'where'
457
+ !Language.parse_where_pattern(pattern).nil?
457
458
  end
458
459
 
459
460
  def pin_pattern?(pattern)
460
- pattern.is_a?(List) &&
461
- pattern.items.length == 2 &&
462
- pattern.head.is_a?(Sym) &&
463
- pattern.head.name == '='
461
+ !Language.parse_pin_pattern(pattern).nil?
464
462
  end
465
463
 
466
464
  def or_pattern?(pattern)
467
- pattern.is_a?(List) && pattern.head.is_a?(Sym) && pattern.head.name == 'or'
465
+ !Language.parse_or_pattern(pattern).nil?
468
466
  end
469
467
 
470
468
  def nil_allowing_pattern_name?(name)