kapusta 0.13.2 → 0.15.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 (100) 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/digit-tools.kap +24 -0
  16. data/examples/doto-hygiene.kap +2 -2
  17. data/examples/doto.kap +2 -2
  18. data/examples/egg-count.kap +1 -1
  19. data/examples/excel-column.kap +26 -0
  20. data/examples/exceptions.kap +1 -1
  21. data/examples/falling-drops.kap +7 -7
  22. data/examples/fennel-parity-examples.txt +3 -6
  23. data/examples/good-pairs.kap +18 -0
  24. data/examples/greet.kap +1 -1
  25. data/examples/happy-number.kap +2 -2
  26. data/examples/left-right-difference.kap +60 -0
  27. data/examples/length-of-last-word.kap +4 -4
  28. data/examples/logger-rate-limiter.kap +26 -0
  29. data/examples/manhattan-distance.kap +1 -1
  30. data/examples/maximum-subarray.kap +23 -7
  31. data/examples/minimum-start-value.kap +19 -0
  32. data/examples/move-zeroes.kap +2 -2
  33. data/examples/mruby-runtime-examples.txt +10 -2
  34. data/examples/non-constant-local.kap +1 -1
  35. data/examples/number-of-1-bits.kap +1 -1
  36. data/examples/number-of-steps.kap +1 -1
  37. data/examples/palindrome.kap +2 -2
  38. data/examples/pangram.kap +4 -4
  39. data/examples/pcall.kap +3 -3
  40. data/examples/pipeline.kap +4 -4
  41. data/examples/plus-one.kap +3 -3
  42. data/examples/raindrops.kap +1 -1
  43. data/examples/range-width.kap +14 -0
  44. data/examples/recent-counter.kap +6 -6
  45. data/examples/require-local-args.kap +9 -0
  46. data/examples/require-local.kap +7 -0
  47. data/examples/require-module-local.kap +4 -0
  48. data/examples/require-module.kap +4 -0
  49. data/examples/reverse-integer.kap +1 -1
  50. data/examples/roman-to-integer.kap +3 -3
  51. data/examples/running-sum.kap +20 -0
  52. data/examples/safe-lookup.kap +2 -2
  53. data/examples/single-number.kap +1 -1
  54. data/examples/stack.kap +14 -14
  55. data/examples/subtract-product-sum.kap +1 -1
  56. data/examples/summary-ranges.kap +45 -0
  57. data/examples/threading.kap +5 -5
  58. data/examples/tset.kap +1 -1
  59. data/examples/two-sum-hash.kap +3 -3
  60. data/examples/two-sum.kap +1 -1
  61. data/examples/ugly-number.kap +1 -1
  62. data/examples/underground-system.kap +39 -0
  63. data/examples/valid-parentheses-1.kap +6 -6
  64. data/exe/kapusta-ls +49 -2
  65. data/lib/kapusta/ast.rb +8 -0
  66. data/lib/kapusta/compiler/emitter/bindings.rb +111 -89
  67. data/lib/kapusta/compiler/emitter/collections.rb +32 -40
  68. data/lib/kapusta/compiler/emitter/control_flow.rb +33 -31
  69. data/lib/kapusta/compiler/emitter/expressions.rb +21 -5
  70. data/lib/kapusta/compiler/emitter/interop.rb +172 -48
  71. data/lib/kapusta/compiler/emitter/patterns.rb +12 -14
  72. data/lib/kapusta/compiler/emitter/support.rb +70 -81
  73. data/lib/kapusta/compiler/language.rb +533 -0
  74. data/lib/kapusta/compiler/lua_compat.rb +23 -28
  75. data/lib/kapusta/compiler/macro_expander.rb +30 -30
  76. data/lib/kapusta/compiler/macro_lowerer.rb +12 -24
  77. data/lib/kapusta/compiler/normalizer.rb +25 -17
  78. data/lib/kapusta/compiler.rb +3 -24
  79. data/lib/kapusta/env.rb +2 -2
  80. data/lib/kapusta/errors.rb +2 -1
  81. data/lib/kapusta/formatter/ast_helpers.rb +78 -0
  82. data/lib/kapusta/formatter/cli.rb +125 -0
  83. data/lib/kapusta/formatter/line_helpers.rb +44 -0
  84. data/lib/kapusta/formatter/validator.rb +32 -0
  85. data/lib/kapusta/formatter.rb +363 -325
  86. data/lib/kapusta/lsp/identifier.rb +1 -1
  87. data/lib/kapusta/lsp/rename.rb +21 -11
  88. data/lib/kapusta/lsp/scope_walker.rb +122 -212
  89. data/lib/kapusta/lsp/workspace_index.rb +17 -5
  90. data/lib/kapusta/reader.rb +4 -2
  91. data/lib/kapusta/version.rb +1 -1
  92. data/lib/kapusta.rb +39 -6
  93. data/spec/cli_spec.rb +13 -0
  94. data/spec/examples_errors_spec.rb +3 -1
  95. data/spec/examples_spec.rb +99 -15
  96. data/spec/formatter_spec.rb +246 -0
  97. data/spec/lsp_spec.rb +69 -0
  98. data/spec/require_spec.rb +294 -0
  99. metadata +23 -2
  100. 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,20 @@ 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
+ body = wrap_singleton(body) if parsed.singleton
167
+ emit_module_wrapper(parsed.name, body)
87
168
  end
88
169
 
89
170
  def emit_class_expr(args, env)
90
- name_sym, supers, body_forms = split_class_args(args)
171
+ parsed = Language.parse_class_args(args)
91
172
  body = with_class_body do
92
- emit_sequence(body_forms, env.child, :class, allow_method_definitions: true, result: false).first
173
+ emit_sequence(parsed.body, env.child, :class, allow_method_definitions: true, result: false).first
93
174
  end
94
- emit_class_wrapper(name_sym, supers, env,
175
+ emit_class_wrapper(parsed.name, parsed.supers, env,
95
176
  body)
96
177
  end
97
178
 
@@ -158,51 +239,32 @@ module Kapusta
158
239
  end
159
240
 
160
241
  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]
242
+ parsed = Language.parse_try_args(args)
243
+ body_form = parsed.body
183
244
  body_code =
184
- if body_form.is_a?(List) && body_form.head.is_a?(Sym) && body_form.head.name == 'do'
245
+ if Language.do_form?(body_form)
185
246
  emit_sequence(body_form.rest, env, current_scope, allow_method_definitions: false).first
186
247
  else
187
248
  emit_expr(body_form, env, current_scope)
188
249
  end
189
250
  lines = ['begin', indent(body_code)]
190
- catches.each do |klass_form, bind_sym, body|
251
+ parsed.clauses.grep(Language::CatchClause).each do |clause|
191
252
  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)
253
+ rescue_name = define_local(rescue_env, clause.bind_sym.name)
254
+ body_code, = emit_sequence(clause.body, rescue_env, current_scope, allow_method_definitions: false)
194
255
  rescue_line =
195
- if klass_form
196
- "rescue #{emit_expr(klass_form, env, current_scope)} => #{rescue_name}"
256
+ if clause.klass
257
+ "rescue #{emit_expr(clause.klass, env, current_scope)} => #{rescue_name}"
197
258
  else
198
259
  "rescue StandardError => #{rescue_name}"
199
260
  end
200
261
  lines << rescue_line
201
262
  lines << indent(body_code)
202
263
  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
264
+ finally_clauses = parsed.clauses.grep(Language::FinallyClause)
265
+ unless finally_clauses.empty?
266
+ ensure_code = finally_clauses.map do |clause|
267
+ emit_sequence(clause.body, env, current_scope, allow_method_definitions: false).first
206
268
  end.join("\n")
207
269
  lines << 'ensure'
208
270
  lines << indent(ensure_code)
@@ -310,7 +372,7 @@ module Kapusta
310
372
  end
311
373
 
312
374
  def emit_self_method_binding_call(binding, args, env, current_scope)
313
- positional = args.map { |arg| emit_expr(arg, env, current_scope) }
375
+ positional = emit_call_args(args, env, current_scope)
314
376
  emit_direct_self_method_call(binding.ruby_name, positional)
315
377
  end
316
378
 
@@ -341,6 +403,11 @@ module Kapusta
341
403
  end
342
404
  end
343
405
 
406
+ def emit_multihash_call(head, args, env, current_scope)
407
+ lookup_code = emit_multihash_value(head, env)
408
+ emit_callable_call(lookup_code, args, env, current_scope)
409
+ end
410
+
344
411
  def emit_method_path(base_code, segments)
345
412
  segments.reduce(base_code) do |acc, segment|
346
413
  snake = Kapusta.kebab_to_snake(segment)
@@ -366,6 +433,9 @@ module Kapusta
366
433
  def emit_self_call(name, args, env, current_scope)
367
434
  positional, kwargs, block_form = split_call_args(args, env, current_scope)
368
435
  snake = Kapusta.kebab_to_snake(name)
436
+ if Language.class_body_declaration?(snake) && kwargs.nil? && block_form.nil?
437
+ return positional.empty? ? snake : "#{snake} #{positional.join(', ')}"
438
+ end
369
439
  if direct_method_name?(snake)
370
440
  return emit_direct_self_call(snake, positional, kwargs, block_form, env, current_scope)
371
441
  end
@@ -394,27 +464,41 @@ module Kapusta
394
464
 
395
465
  if !remaining.empty? && remaining.last.is_a?(HashLit) && remaining.last.all_sym_keys?
396
466
  kwargs = emit_expr(remaining.last, env, current_scope)
397
- positional = remaining[0...-1].map { |arg| emit_expr(arg, env, current_scope) }
467
+ positional = emit_call_args(remaining[0...-1], env, current_scope)
398
468
  else
399
469
  kwargs = nil
400
- positional = remaining.map { |arg| emit_expr(arg, env, current_scope) }
470
+ positional = emit_call_args(remaining, env, current_scope)
401
471
  end
402
472
  [positional, kwargs, block_form]
403
473
  end
404
474
 
475
+ def emit_call_args(args, env, current_scope)
476
+ args.map.with_index do |arg, index|
477
+ code = emit_expr(arg, env, current_scope)
478
+ index == args.length - 1 && multi_value_call_arg?(arg, env) ? "*#{code}" : code
479
+ end
480
+ end
481
+
482
+ def multi_value_call_arg?(arg, env)
483
+ return true if Language.list_head?(arg, 'values')
484
+ return false unless arg.is_a?(List) && arg.head.is_a?(Sym)
485
+
486
+ binding = env.lookup_if_defined(arg.head.name)
487
+ callable_method_binding?(binding) && binding.multi_return
488
+ end
489
+
405
490
  def emit_block_proc(block_form, env, current_scope)
406
491
  block_form && emit_expr(block_form, env, current_scope)
407
492
  end
408
493
 
409
494
  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)
495
+ parsed = Language.parse_function_form(block_form)
496
+ return unless parsed&.anonymous?
412
497
 
413
- pattern = block_form.items[1]
498
+ pattern = parsed.params
414
499
  return unless pattern.is_a?(Vec) && simple_parameter_pattern?(pattern)
415
500
 
416
- body = block_form.items[2..]
417
- params, body_code = build_simple_block_parts(pattern, body, env, current_scope)
501
+ params, body_code = build_simple_block_parts(pattern, parsed.body, env, current_scope)
418
502
  header = params.empty? ? 'do' : "do |#{params.join(', ')}|"
419
503
  [header, indent(body_code), 'end'].join("\n")
420
504
  end
@@ -439,6 +523,7 @@ module Kapusta
439
523
  end
440
524
 
441
525
  emit_error!(:unexpected_vararg) if name == '...'
526
+ return emit_multihash_value(sym, env) if sym.colonized?
442
527
  return emit_multisym_value(sym, env) if sym.dotted?
443
528
  return 'ARGV' if name == 'ARGV'
444
529
  return name if name.match?(/\A[A-Z]/)
@@ -460,6 +545,33 @@ module Kapusta
460
545
  emit_method_path(base_code, segments)
461
546
  end
462
547
 
548
+ def emit_multihash_value(sym, env)
549
+ base_code, segments = multihash_base(sym.colon_segments, env)
550
+ emit_hash_lookup_path(base_code, segments)
551
+ end
552
+
553
+ def emit_hash_lookup_path(base_code, segments)
554
+ receiver = simple_expression?(base_code) ? base_code : parenthesize(base_code)
555
+ segments.reduce(receiver) do |acc, segment|
556
+ "#{acc}[#{Kapusta.kebab_to_snake(segment).to_sym.inspect}]"
557
+ end
558
+ end
559
+
560
+ def multihash_base(segments, env)
561
+ first = segments[0]
562
+ if first == 'self'
563
+ ['self', segments[1..]]
564
+ elsif (binding = env.lookup_if_defined(first))
565
+ [binding_value_code(binding), segments[1..]]
566
+ elsif first == 'ARGV'
567
+ ['ARGV', segments[1..]]
568
+ elsif first.match?(/\A[A-Z]/)
569
+ [first, segments[1..]]
570
+ else
571
+ emit_error!(:undefined_symbol, name: first)
572
+ end
573
+ end
574
+
463
575
  def multisym_base(segments, env)
464
576
  if segments[0] == 'self'
465
577
  ['self', segments[1..]]
@@ -472,12 +584,24 @@ module Kapusta
472
584
  const_path << segments[idx]
473
585
  idx += 1
474
586
  end
475
- emit_error!(:bad_multisym, path: segments.join('.')) if const_path.empty?
587
+ emit_bad_multisym!(segments) if const_path.empty?
476
588
 
477
589
  [const_path.join('::'), segments[idx..]]
478
590
  end
479
591
  end
480
592
 
593
+ def emit_bad_multisym!(segments)
594
+ root = segments[0]
595
+ emit_error!(:bad_multisym,
596
+ path: segments.join('.'),
597
+ segment: root,
598
+ suggestion: bad_multisym_suggestion(root))
599
+ end
600
+
601
+ def bad_multisym_suggestion(root)
602
+ "bind #{root} first, use a capitalized constant path, or call a method on an explicit receiver"
603
+ end
604
+
481
605
  def parenthesize(code)
482
606
  return code if simple_expression?(code)
483
607
 
@@ -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)