kapusta 0.10.0 → 0.11.1

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 (39) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +2 -0
  3. data/examples/accumulator.kap +2 -0
  4. data/examples/bank-account.kap +2 -0
  5. data/examples/bst-iterator.kap +52 -0
  6. data/examples/circle.kap +2 -0
  7. data/examples/counter.kap +2 -0
  8. data/examples/hit-counter.kap +2 -0
  9. data/examples/module-header.kap +4 -2
  10. data/examples/mruby-runtime-examples.txt +3 -0
  11. data/examples/parking-system.kap +2 -0
  12. data/examples/recent-counter.kap +17 -0
  13. data/examples/scopes.kap +2 -0
  14. data/examples/signal-harvest.kap +16 -0
  15. data/examples/stack.kap +2 -0
  16. data/examples/valid-parentheses-1.kap +2 -0
  17. data/lib/kapusta/compiler/emitter/bindings.rb +1 -4
  18. data/lib/kapusta/compiler/emitter/control_flow.rb +25 -30
  19. data/lib/kapusta/compiler/emitter/expressions.rb +2 -0
  20. data/lib/kapusta/compiler/emitter/interop.rb +23 -15
  21. data/lib/kapusta/compiler/emitter/patterns.rb +29 -52
  22. data/lib/kapusta/compiler/emitter/support.rb +106 -44
  23. data/lib/kapusta/compiler/macro_expander.rb +4 -12
  24. data/lib/kapusta/compiler/macro_lowerer.rb +4 -12
  25. data/lib/kapusta/compiler/normalizer.rb +9 -17
  26. data/lib/kapusta/compiler.rb +2 -2
  27. data/lib/kapusta/errors.rb +4 -0
  28. data/lib/kapusta/formatter.rb +1 -1
  29. data/lib/kapusta/lsp/definition.rb +17 -0
  30. data/lib/kapusta/lsp/rename.rb +3 -1
  31. data/lib/kapusta/lsp/scope_walker.rb +79 -46
  32. data/lib/kapusta/lsp/workspace_index.rb +2 -13
  33. data/lib/kapusta/lsp.rb +17 -16
  34. data/lib/kapusta/support.rb +8 -0
  35. data/lib/kapusta/version.rb +1 -1
  36. data/spec/examples_errors_spec.rb +25 -0
  37. data/spec/examples_spec.rb +21 -0
  38. data/spec/lsp_spec.rb +71 -3
  39. metadata +4 -1
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 3cafef3668504ca08a40ba087c4c2b754bfda02e6f61161f19bcf8d56a254e00
4
- data.tar.gz: 501b99f6ec3bfa950865b63b26ec6fd7f5b64977bb86f41d4eff51957602cc84
3
+ metadata.gz: 85bcd0988a353fb851e5ff955c2b5196f39193e1c3c8fb6ca4a111d6260e774d
4
+ data.tar.gz: 80efbc138038947b866eaac96ef21d5cdccd04a461a25f2e17cbd7ce925ceaec
5
5
  SHA512:
6
- metadata.gz: b774d8f0bbd772e223743a2ec844bec4949a6f1a5a545767c3b608c8ec0c2da055fe9537c68a3cf57573ac80ed75d11d85884245b4fcbd401af16646aaab5283
7
- data.tar.gz: 10b6d3f30fdfc760569c40c70e22f2b63dcf124ea3de3cffca2f061642523bc7057c878b81460181950356193c5786a35df28d4fd2b4859c1c38bc2285392f40
6
+ metadata.gz: 16d02e652b9221990ac027423e677a8f27c285d582c6bfb368f585140305e1c48dff5add4a8491a7996b5be99e34efd761e7fb280d8932fc6548622939b166b4
7
+ data.tar.gz: 8ab10f9991b7029d9366e47d434a9e9166568b870369f1fe585007fbe2cf9b750c84e8eb3b8fb015db255977eef80fd3255750fa99006d577a9564b640d84319
data/README.md CHANGED
@@ -110,6 +110,8 @@ Kapusta keeps most core Fennel forms. The main differences come from Ruby's runt
110
110
  Kapusta-specific additions:
111
111
 
112
112
  - `module` and `class` for Ruby host structure, including file-header forms
113
+ - `(end)` closes a bodyless file-header
114
+ - `(defn name ...)` or `(fn class.name ..)`
113
115
  - `ivar` or `@var` / `cvar` or `@@var` / `gvar` or `$var`
114
116
  - `try` / `catch` / `finally` plus `raise` for exceptions
115
117
  - `(ruby "...")` raw host escape hatch
@@ -10,6 +10,8 @@
10
10
  (fn value []
11
11
  (ivar total))
12
12
 
13
+ (end)
14
+
13
15
  (let [acc (Accumulator.new 10)]
14
16
  (acc.add! 5)
15
17
  (acc.add! 7)
@@ -19,3 +19,5 @@
19
19
  (set (ivar balance)
20
20
  (- (ivar balance) amount))
21
21
  self)
22
+
23
+ (end)
@@ -0,0 +1,52 @@
1
+ (class TreeNode)
2
+
3
+ (fn initialize [val left right]
4
+ (set @val val)
5
+ (set @left left)
6
+ (set @right right))
7
+
8
+ (fn val [] @val)
9
+ (fn left [] @left)
10
+ (fn right [] @right)
11
+
12
+ (end)
13
+
14
+ (class BSTIterator)
15
+
16
+ (fn initialize [root]
17
+ (set @stack [])
18
+ (self.push-left root))
19
+
20
+ (fn push-left [node]
21
+ (var n node)
22
+ (while n
23
+ (let [stack @stack]
24
+ (stack.push n))
25
+ (set n (n.left))))
26
+
27
+ (fn next []
28
+ (let [stack @stack
29
+ node (stack.pop)]
30
+ (self.push-left (node.right))
31
+ (node.val)))
32
+
33
+ (fn has-next? []
34
+ (let [stack @stack]
35
+ (not (stack.empty?))))
36
+
37
+ (end)
38
+
39
+ (let [root (TreeNode.new 7
40
+ (TreeNode.new 3 nil nil)
41
+ (TreeNode.new 15
42
+ (TreeNode.new 9 nil nil)
43
+ (TreeNode.new 20 nil nil)))
44
+ it (BSTIterator.new root)]
45
+ (print (it.next))
46
+ (print (it.next))
47
+ (print (it.has-next?))
48
+ (print (it.next))
49
+ (print (it.has-next?))
50
+ (print (it.next))
51
+ (print (it.next))
52
+ (print (it.has-next?)))
data/examples/circle.kap CHANGED
@@ -11,6 +11,8 @@
11
11
  (fn circumference []
12
12
  (* 2 pi @radius))
13
13
 
14
+ (end)
15
+
14
16
  (let [c (Circle.new 5)]
15
17
  (print (c.area))
16
18
  (print (c.circumference)))
data/examples/counter.kap CHANGED
@@ -12,6 +12,8 @@
12
12
  (fn self.zero []
13
13
  (Counter.new 0))
14
14
 
15
+ (end)
16
+
15
17
  (let [c (Counter.new 10)]
16
18
  (c.tick)
17
19
  (c.tick)
@@ -9,6 +9,8 @@
9
9
  (set $last-hitter @name)
10
10
  @@total)
11
11
 
12
+ (end)
13
+
12
14
  (let [a (HitCounter.new "alice")
13
15
  b (HitCounter.new "bob")]
14
16
  (print (a.hit))
@@ -1,7 +1,9 @@
1
1
  ; File header forms map directly to Ruby modules.
2
2
  (module HeaderDemo)
3
3
 
4
- (fn self.greet [name]
4
+ (defn greet [name]
5
5
  (.. "Hello, " name "!"))
6
6
 
7
- (print (self.greet "Ada"))
7
+ (end)
8
+
9
+ (print (HeaderDemo.greet "Ada"))
@@ -9,6 +9,7 @@ best-time-to-buy-sell-stock
9
9
  binary-search
10
10
  binary-to-decimal
11
11
  block-sort
12
+ bst-iterator
12
13
  calc
13
14
  circle
14
15
  classify-wallet
@@ -65,6 +66,7 @@ points
65
66
  power-of-three
66
67
  primes
67
68
  raindrops
69
+ recent-counter
68
70
  record
69
71
  reverse-integer
70
72
  roman-to-integer
@@ -72,6 +74,7 @@ ruby-eval
72
74
  safe-lookup
73
75
  scopes
74
76
  shapes
77
+ signal-harvest
75
78
  single-number
76
79
  squares
77
80
  stack
@@ -11,6 +11,8 @@
11
11
  (and (= car-type 3) (> @small 0)) (do (set @small (- @small 1)) true)
12
12
  false))
13
13
 
14
+ (end)
15
+
14
16
  (let [parking (ParkingSystem.new 1 1 0)]
15
17
  (print (parking.add-car 1))
16
18
  (print (parking.add-car 2))
@@ -0,0 +1,17 @@
1
+ (class RecentCounter)
2
+ (fn initialize [] (set @pings []))
3
+ (fn ping [t]
4
+ (let [pings @pings]
5
+ (pings.push t)
6
+ (while (< (. pings 0) (- t 3000))
7
+ (pings.shift))
8
+ (length pings)))
9
+ (defn warm [history]
10
+ (let [c (RecentCounter.new)]
11
+ (each [_ t (ipairs history)] (c.ping t))
12
+ c))
13
+ (end)
14
+
15
+ (let [c (RecentCounter.warm [100 200 300])]
16
+ (print (c.ping 3001))
17
+ (print (c.ping 3002)))
data/examples/scopes.kap CHANGED
@@ -10,6 +10,8 @@
10
10
  (fn self.total []
11
11
  (cvar total))
12
12
 
13
+ (end)
14
+
13
15
  (let [a (ScopeCounter.new)
14
16
  b (ScopeCounter.new)]
15
17
  (print (a.add! 5))
@@ -0,0 +1,16 @@
1
+ (module SignalHarvest)
2
+
3
+ (local SCREEN-W 1280)
4
+ (local TARGET-SCORE 36)
5
+
6
+ (defn cell-width [columns]
7
+ (/ SCREEN-W columns))
8
+
9
+ (defn win? [score]
10
+ (>= score TARGET-SCORE))
11
+
12
+ (end)
13
+
14
+ (print (SignalHarvest.cell-width 32))
15
+ (print (SignalHarvest.win? 40))
16
+ (print (SignalHarvest.win? 12))
data/examples/stack.kap CHANGED
@@ -24,6 +24,8 @@
24
24
  (fn get-min []
25
25
  (. (ivar mins) -1))
26
26
 
27
+ (end)
28
+
27
29
  (let [s (MinStack.new)]
28
30
  (s.push -2)
29
31
  (s.push 0)
@@ -17,3 +17,5 @@
17
17
  (stack.push ch)))
18
18
  (set i (+ i 1)))
19
19
  (and ok (stack.empty?))))
20
+
21
+ (end)
@@ -148,10 +148,7 @@ module Kapusta
148
148
  return unless ruby_name
149
149
  return if captures_outer_binding?(body, env, pattern_names(pattern))
150
150
 
151
- body_env = env.child
152
- params = pattern.items.map { |sym| define_local(body_env, sym.name, shadow: true) }
153
- body_code, = emit_sequence(body, body_env, :toplevel,
154
- allow_method_definitions: false, result: false)
151
+ params, body_code = build_simple_block_parts(pattern, body, env, :toplevel)
155
152
  header = params.empty? ? "def #{ruby_name}" : "def #{ruby_name}(#{params.join(', ')})"
156
153
  [
157
154
  header,
@@ -111,27 +111,30 @@ module Kapusta
111
111
  end
112
112
 
113
113
  def try_emit_native_case(value_var, clauses, env, current_scope, mode)
114
+ arms = collect_case_arms(clauses) do |pattern, body, where_guards|
115
+ try_native_arm(pattern, body, where_guards, env, current_scope, mode)
116
+ end
117
+ return unless arms
118
+
119
+ arms << ['else', indent('nil')].join("\n") unless wildcard_last?(clauses)
120
+ ["case #{value_var}", *arms, 'end'].join("\n")
121
+ end
122
+
123
+ def collect_case_arms(clauses)
114
124
  arms = []
115
125
  i = 0
116
126
  while i < clauses.length
117
127
  pattern = clauses[i]
118
128
  body = clauses[i + 1]
119
- inner, where_guards = if where_pattern?(pattern)
120
- [pattern.items[1], pattern.items[2..]]
121
- else
122
- [pattern, []]
123
- end
129
+ inner, where_guards = extract_pattern_and_guards(pattern)
124
130
  sub_patterns = or_pattern?(inner) ? inner.items[1..] : [inner]
125
- sub_arms = sub_patterns.map do |sub|
126
- try_native_arm(sub, body, where_guards, env, current_scope, mode)
127
- end
131
+ sub_arms = sub_patterns.map { |sub| yield sub, body, where_guards }
128
132
  return if sub_arms.any?(&:nil?)
129
133
 
130
134
  arms.concat(sub_arms)
131
135
  i += 2
132
136
  end
133
- arms << ['else', indent('nil')].join("\n") unless wildcard_last?(clauses)
134
- ["case #{value_var}", *arms, 'end'].join("\n")
137
+ arms
135
138
  end
136
139
 
137
140
  def try_native_arm(pattern, body, where_guards, env, current_scope, mode)
@@ -149,25 +152,11 @@ module Kapusta
149
152
  end
150
153
 
151
154
  def try_emit_compat_case(value_var, clauses, env, current_scope, mode)
152
- arms = []
153
- i = 0
154
- while i < clauses.length
155
- pattern = clauses[i]
156
- body = clauses[i + 1]
157
- inner, where_guards = if where_pattern?(pattern)
158
- [pattern.items[1], pattern.items[2..]]
159
- else
160
- [pattern, []]
161
- end
162
- sub_patterns = or_pattern?(inner) ? inner.items[1..] : [inner]
163
- sub_arms = sub_patterns.map do |sub|
164
- try_compat_arm(sub, body, where_guards, value_var, env, current_scope, mode)
165
- end
166
- return if sub_arms.any?(&:nil?)
167
-
168
- arms.concat(sub_arms)
169
- i += 2
155
+ arms = collect_case_arms(clauses) do |pattern, body, where_guards|
156
+ try_compat_arm(pattern, body, where_guards, value_var, env, current_scope, mode)
170
157
  end
158
+ return unless arms
159
+
171
160
  emit_compat_case_lines(arms)
172
161
  end
173
162
 
@@ -176,6 +165,12 @@ module Kapusta
176
165
  last_pattern.is_a?(Sym) && last_pattern.name == '_'
177
166
  end
178
167
 
168
+ def extract_pattern_and_guards(pattern)
169
+ return [pattern, []] unless where_pattern?(pattern)
170
+
171
+ [pattern.items[1], pattern.items[2..]]
172
+ end
173
+
179
174
  def try_compat_arm(pattern, body, where_guards, value_var, env, current_scope, mode)
180
175
  allow_pins = !where_guards.empty? && mode == :case
181
176
  arm_env = env.child
@@ -185,11 +180,11 @@ module Kapusta
185
180
  where_guard_codes = where_guards.map { |g| emit_expr(g, arm_env, current_scope) }
186
181
  if where_guard_codes.empty?
187
182
  guard_codes = plan[:conditions]
188
- prelude = plan[:prelude]
183
+ prelude = plan[:prelude]
189
184
  else
190
185
  prelude_guards = plan[:prelude].map { |line| "begin #{line}; true end" }
191
186
  guard_codes = plan[:conditions] + prelude_guards + where_guard_codes
192
- prelude = []
187
+ prelude = []
193
188
  end
194
189
  body_code = emit_expr(body, arm_env, current_scope)
195
190
  [guard_codes, prelude, body_code]
@@ -83,6 +83,8 @@ module Kapusta
83
83
  when 'require' then emit_require(args[0], env, current_scope)
84
84
  when 'module' then emit_module_expr(args, env)
85
85
  when 'class' then emit_class_expr(args, env)
86
+ when 'end' then emit_error!(:end_outside_header)
87
+ when 'defn' then emit_error!(:defn_outside_header)
86
88
  when 'try' then emit_try(args, env, current_scope)
87
89
  when 'raise' then emit_raise(args, env, current_scope)
88
90
  when 'ivar' then "@#{Kapusta.kebab_to_snake(args[0].name)}"
@@ -102,7 +102,7 @@ module Kapusta
102
102
  segments = constant_segments(name_sym)
103
103
  return unless segments
104
104
 
105
- [build_nested_module(segments, body), segments.join('::')].join("\n")
105
+ build_nested_module(segments, body)
106
106
  end
107
107
 
108
108
  def emit_class_wrapper(name_sym, supers, env, body)
@@ -118,7 +118,7 @@ module Kapusta
118
118
  return unless segments
119
119
 
120
120
  super_code = class_super_code(supers, env)
121
- [build_nested_class(segments, super_code, body), segments.join('::')].join("\n")
121
+ build_nested_class(segments, super_code, body)
122
122
  end
123
123
 
124
124
  def constant_segments(name_sym)
@@ -483,20 +483,28 @@ module Kapusta
483
483
  Kapusta.kebab_to_snake(name).gsub(/[^a-zA-Z0-9_]/, '_')
484
484
  end
485
485
 
486
+ SIMPLE_EXPRESSION_PATTERNS = [
487
+ /\A[a-z_]\w*\z/, # local
488
+ /\A@@?[a-z_]\w*\z/, # @ivar / @@cvar
489
+ /\A\$[a-zA-Z_]\w*\z/, # $gvar
490
+ /\A[A-Z]\w*(?:::[A-Z]\w*)*\z/, # Constant / A::B::C
491
+ /\A[a-z_]\w*[!?=]?\([^()\n]*\)\z/, # bare call foo(...)
492
+ /\A-?\d+(?:\.\d+)?\z/, # number
493
+ /\A[a-z_]\w*(?:\.[a-z_]\w*[!?=]?(?:\([^()\n]*\))?|\[[^\[\]]*\])+\z/, # local + .m/[k] chain
494
+ /\A[A-Z]\w*(?:::[A-Z]\w*)*(?:\.[a-z_]\w*[!?=]?(?:\([^()\n]*\))?|\[[^\[\]]*\])+\z/, # const + chain
495
+ /\A:[a-zA-Z_]\w*[!?=]?\z/, # :symbol
496
+ /\A"(?:[^"\\]|\\.)*"\z/, # "string"
497
+ /\A'(?:[^'\\]|\\.)*'\z/, # 'string'
498
+ /\A\[[^\[\]\n]*\]\z/ # [flat, array]
499
+ ].freeze
500
+ private_constant :SIMPLE_EXPRESSION_PATTERNS
501
+
502
+ SIMPLE_EXPRESSION_KEYWORDS = %w[nil true false self].freeze
503
+ private_constant :SIMPLE_EXPRESSION_KEYWORDS
504
+
486
505
  def simple_expression?(code)
487
- code.match?(/\A[a-z_]\w*\z/) ||
488
- code.match?(/\A@@?[a-z_]\w*\z/) ||
489
- code.match?(/\A\$[a-zA-Z_]\w*\z/) ||
490
- code.match?(/\A[A-Z]\w*(?:::[A-Z]\w*)*\z/) ||
491
- code.match?(/\A[a-z_]\w*[!?=]?\([^()\n]*\)\z/) ||
492
- code.match?(/\A-?\d+(?:\.\d+)?\z/) ||
493
- code.match?(/\A[a-z_]\w*(?:\.[a-z_]\w*[!?=]?(?:\([^()\n]*\))?|\[[^\[\]]*\])+\z/) ||
494
- code.match?(/\A[A-Z]\w*(?:::[A-Z]\w*)*(?:\.[a-z_]\w*[!?=]?(?:\([^()\n]*\))?|\[[^\[\]]*\])+\z/) ||
495
- code.match?(/\A:[a-zA-Z_]\w*[!?=]?\z/) ||
496
- code.match?(/\A"(?:[^"\\]|\\.)*"\z/) ||
497
- code.match?(/\A'(?:[^'\\]|\\.)*'\z/) ||
498
- code.match?(/\A\[[^\[\]\n]*\]\z/) ||
499
- %w[nil true false self].include?(code) ||
506
+ SIMPLE_EXPRESSION_PATTERNS.any? { |re| code.match?(re) } ||
507
+ SIMPLE_EXPRESSION_KEYWORDS.include?(code) ||
500
508
  negation_simple?(code)
501
509
  end
502
510
 
@@ -56,20 +56,13 @@ module Kapusta
56
56
  parts = []
57
57
  deferred = []
58
58
  current_env = env
59
- items = pattern.items
60
- i = 0
61
- while i < items.length
62
- if items[i].is_a?(Sym) && items[i].name == '&'
63
- sub = items[i + 1]
64
- raise PatternNotTranslatable unless sub.is_a?(Sym)
65
-
59
+ each_pattern_item(pattern.items) do |kind, sub|
60
+ if kind == :rest
66
61
  parts << native_rest_target(sub, current_env)
67
- i += 2
68
62
  else
69
- code, current_env, follow_up = native_destructure_target(items[i], current_env, allow_follow_up: true)
63
+ code, current_env, follow_up = native_destructure_target(sub, current_env, allow_follow_up: true)
70
64
  parts << code
71
65
  deferred << follow_up if follow_up
72
- i += 1
73
66
  end
74
67
  end
75
68
  if deferred.empty?
@@ -122,17 +115,13 @@ module Kapusta
122
115
  inner = []
123
116
  current = env
124
117
  deferred = []
125
- items = pattern.items
126
- i = 0
127
- while i < items.length
128
- if items[i].is_a?(Sym) && items[i].name == '&'
129
- inner << native_rest_target(items[i + 1], current)
130
- i += 2
118
+ each_pattern_item(pattern.items) do |kind, sub|
119
+ if kind == :rest
120
+ inner << native_rest_target(sub, current)
131
121
  else
132
- code, current, follow_up = native_destructure_target(items[i], current)
122
+ code, current, follow_up = native_destructure_target(sub, current)
133
123
  inner << code
134
124
  deferred << follow_up if follow_up
135
- i += 1
136
125
  end
137
126
  end
138
127
  raise PatternNotTranslatable unless deferred.empty?
@@ -225,36 +214,26 @@ module Kapusta
225
214
  state[:conditions] << "#{value_code}.length >= #{min_length}"
226
215
 
227
216
  index = 0
228
- i = 0
229
- while i < items.length
230
- if rest_pattern_marker?(items, i)
231
- sub = items[i + 1]
217
+ each_pattern_item(items) do |kind, sub|
218
+ if kind == :rest
232
219
  raise PatternNotTranslatable unless sub.is_a?(Sym)
233
220
 
234
221
  unless sub.name == '_'
235
222
  ruby = define_local(arm_env, sub.name)
236
223
  state[:prelude] << "#{ruby} = #{value_code}[#{index}..]"
237
224
  end
238
- i += 2
239
225
  else
240
- compile_compat_pattern(items[i], "#{value_code}[#{index}]", env, arm_env,
226
+ compile_compat_pattern(sub, "#{value_code}[#{index}]", env, arm_env,
241
227
  mode:, allow_pins:, state:)
242
228
  index += 1
243
- i += 1
244
229
  end
245
230
  end
246
231
  end
247
232
 
248
233
  def compat_sequence_min_length(items)
249
234
  count = 0
250
- i = 0
251
- while i < items.length
252
- if rest_pattern_marker?(items, i)
253
- i += 2
254
- else
255
- count += 1
256
- i += 1
257
- end
235
+ each_pattern_item(items) do |kind, _sub|
236
+ count += 1 if kind == :item
258
237
  end
259
238
  count
260
239
  end
@@ -346,11 +325,9 @@ module Kapusta
346
325
  def compile_native_sequence(items, env, mode:, allow_pins:, state:)
347
326
  parts = []
348
327
  has_rest = false
349
- i = 0
350
- while i < items.length
351
- if rest_pattern_marker?(items, i)
328
+ each_pattern_item(items) do |kind, sub|
329
+ if kind == :rest
352
330
  has_rest = true
353
- sub = items[i + 1]
354
331
  raise PatternNotTranslatable unless sub.is_a?(Sym)
355
332
 
356
333
  if sub.name == '_'
@@ -360,10 +337,8 @@ module Kapusta
360
337
  state[:binding_names] << sub.name
361
338
  parts << "*#{sanitize_local(sub.name)}"
362
339
  end
363
- i += 2
364
340
  else
365
- parts << compile_native_pattern(items[i], env, mode:, allow_pins:, state:)
366
- i += 1
341
+ parts << compile_native_pattern(sub, env, mode:, allow_pins:, state:)
367
342
  end
368
343
  end
369
344
  parts << '*' unless has_rest
@@ -464,16 +439,8 @@ module Kapusta
464
439
  pattern.name == '_' ? [] : [pattern.name]
465
440
  when Vec
466
441
  names = []
467
- items = pattern.items
468
- i = 0
469
- while i < items.length
470
- if items[i].is_a?(Sym) && items[i].name == '&'
471
- names.concat(pattern_names(items[i + 1]))
472
- i += 2
473
- else
474
- names.concat(pattern_names(items[i]))
475
- i += 1
476
- end
442
+ each_pattern_item(pattern.items) do |_kind, sub|
443
+ names.concat(pattern_names(sub))
477
444
  end
478
445
  names
479
446
  when HashLit
@@ -504,8 +471,18 @@ module Kapusta
504
471
  name.length > 1 && (name.start_with?('?') || name.start_with?('_'))
505
472
  end
506
473
 
507
- def rest_pattern_marker?(items, index)
508
- items[index].is_a?(Sym) && items[index].name == '&'
474
+ def each_pattern_item(items)
475
+ i = 0
476
+ while i < items.length
477
+ item = items[i]
478
+ if item.is_a?(Sym) && item.name == '&'
479
+ yield :rest, items[i + 1]
480
+ i += 2
481
+ else
482
+ yield :item, item
483
+ i += 1
484
+ end
485
+ end
509
486
  end
510
487
  end
511
488
  end