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
@@ -34,22 +34,52 @@ module Kapusta
34
34
  end
35
35
 
36
36
  def emit_forms_with_headers(forms, env, current_scope, result: true)
37
- i = 0
37
+ _ = result
38
+ code, _next = emit_form_run(forms, 0, env, current_scope)
39
+ code
40
+ end
41
+
42
+ def emit_form_run(forms, start, env, current_scope, header_form: nil)
43
+ i = start
38
44
  codes = []
39
45
  while i < forms.length
40
46
  form = forms[i]
47
+ if end_form?(form)
48
+ validate_end_form!(form)
49
+ with_current_form(form) { emit_error!(:end_outside_header) } unless header_form
50
+ return [codes.join("\n"), i + 1]
51
+ end
52
+
41
53
  if bodyless_header?(form)
42
- codes << emit_bodyless_header(form, forms[(i + 1)..], env, current_scope)
43
- break
44
- else
45
- code, env = emit_form_in_sequence(form, env, current_scope,
46
- allow_method_definitions: true,
47
- result_needed: result && i == forms.length - 1)
48
- codes << code
49
- i += 1
54
+ header_code, i = emit_bodyless_header(form, forms, i + 1, env)
55
+ codes << header_code
56
+ next
50
57
  end
58
+
59
+ code, env = emit_form_in_sequence(form, env, current_scope,
60
+ allow_method_definitions: true,
61
+ result_needed: false)
62
+ codes << code
63
+ i += 1
51
64
  end
52
- codes.join("\n")
65
+ with_current_form(header_form) { emit_error!(:unclosed_header) } if header_form
66
+ [codes.join("\n"), i]
67
+ end
68
+
69
+ def end_form?(form)
70
+ form.is_a?(List) && !form.empty? && form.head.is_a?(Sym) && form.head.name == 'end'
71
+ end
72
+
73
+ def validate_end_form!(form)
74
+ with_current_form(form) { emit_error!(:end_with_args) } if form.items.length > 1
75
+ end
76
+
77
+ def validate_header_name!(form, head)
78
+ name_sym = head == 'module' ? form.items[1] : split_class_args(form.items[1..])[0]
79
+ return if constant_segments(name_sym)
80
+
81
+ code = head == 'module' ? :invalid_module_name : :invalid_class_name
82
+ emit_error!(code, name: name_sym.respond_to?(:name) ? name_sym.name : name_sym.inspect)
53
83
  end
54
84
 
55
85
  def bodyless_header?(form)
@@ -69,51 +99,63 @@ module Kapusta
69
99
  end
70
100
  end
71
101
 
72
- def emit_bodyless_header(form, remaining_forms, env, _current_scope)
102
+ def emit_bodyless_header(form, forms, body_start, env)
73
103
  head = form.head.name
74
- name_sym = form.items[1]
75
-
104
+ validate_header_name!(form, head)
76
105
  if head == 'module'
106
+ name_sym = form.items[1]
77
107
  inner = form.items[2..] || []
78
- body =
79
- if inner.length == 1 && bodyless_header?(inner[0])
80
- emit_bodyless_header(inner[0], remaining_forms, env, :module)
81
- else
82
- emit_forms_with_headers(remaining_forms, env, :module, result: false)
83
- end
84
- emit_direct_module_header(name_sym, body) || emit_module_wrapper(name_sym, body)
108
+ if inner.length == 1 && bodyless_header?(inner[0])
109
+ inner_code, next_i = emit_bodyless_header(inner[0], forms, body_start, env)
110
+ [emit_direct_module_header(name_sym, inner_code) || emit_module_wrapper(name_sym, inner_code), next_i]
111
+ else
112
+ body, next_i = emit_form_run(forms, body_start, env, :module, header_form: form)
113
+ [emit_direct_module_header(name_sym, body) || emit_module_wrapper(name_sym, body), next_i]
114
+ end
85
115
  else
86
116
  name_sym, supers, = split_class_args(form.items[1..])
87
- body = emit_forms_with_headers(remaining_forms, env, :class, result: false)
88
- emit_direct_class_header(name_sym, supers, body, env) || emit_class_wrapper(name_sym, supers, env, body)
117
+ body, next_i = emit_form_run(forms, body_start, env, :class, header_form: form)
118
+ code = emit_direct_class_header(name_sym, supers, body, env) ||
119
+ emit_class_wrapper(name_sym, supers, env, body)
120
+ [code, next_i]
89
121
  end
90
122
  end
91
123
 
92
124
  def emit_form_in_sequence(form, env, current_scope, allow_method_definitions:, result_needed: true)
93
125
  with_current_form(form) do
94
- if allow_method_definitions &&
95
- method_definition_form?(form) &&
96
- %i[toplevel module class].include?(current_scope)
97
- code, env = emit_definition_form(form, env, current_scope)
98
- return [code, env] if code
99
- end
126
+ form = lower_defn_in_sequence(form, current_scope) if defn_form?(form)
127
+ emit_form_body(form, env, current_scope, allow_method_definitions:, result_needed:)
128
+ end
129
+ end
100
130
 
101
- if named_function_form?(form)
102
- emit_named_fn_assignment(form, env, current_scope)
103
- elsif local_form?(form)
104
- code, env = emit_local_form(form, env, current_scope,
105
- allow_constant: allow_method_definitions)
106
- code = code.delete_suffix("\nnil") unless result_needed
107
- [code, env]
108
- elsif do_form?(form)
109
- emit_do_form(form.rest, env, current_scope, result_needed:)
110
- elsif sequence_statement_form?(form)
111
- emit_sequence_statement_form(form, env, current_scope, result_needed:)
112
- elsif set_new_local_form?(form, env)
113
- emit_set_form(form, env, current_scope)
114
- else
115
- [emit_expr(form, env, current_scope), env]
116
- end
131
+ def lower_defn_in_sequence(form, current_scope)
132
+ emit_error!(:defn_outside_header) unless %i[module class].include?(current_scope)
133
+ lower_defn_to_fn(form)
134
+ end
135
+
136
+ def emit_form_body(form, env, current_scope, allow_method_definitions:, result_needed:)
137
+ if allow_method_definitions &&
138
+ method_definition_form?(form) &&
139
+ %i[toplevel module class].include?(current_scope)
140
+ code, env = emit_definition_form(form, env, current_scope)
141
+ return [code, env] if code
142
+ end
143
+
144
+ if named_function_form?(form)
145
+ emit_named_fn_assignment(form, env, current_scope)
146
+ elsif local_form?(form)
147
+ code, env = emit_local_form(form, env, current_scope,
148
+ allow_constant: allow_method_definitions)
149
+ code = code.delete_suffix("\nnil") unless result_needed
150
+ [code, env]
151
+ elsif do_form?(form)
152
+ emit_do_form(form.rest, env, current_scope, result_needed:)
153
+ elsif sequence_statement_form?(form)
154
+ emit_sequence_statement_form(form, env, current_scope, result_needed:)
155
+ elsif set_new_local_form?(form, env)
156
+ emit_set_form(form, env, current_scope)
157
+ else
158
+ [emit_expr(form, env, current_scope), env]
117
159
  end
118
160
  end
119
161
 
@@ -182,6 +224,26 @@ module Kapusta
182
224
  %w[fn lambda λ].include?(form.head.name) && form.items[1].is_a?(Sym)
183
225
  end
184
226
 
227
+ def defn_form?(form)
228
+ form.is_a?(List) && !form.empty? && form.head.is_a?(Sym) && form.head.name == 'defn'
229
+ end
230
+
231
+ def lower_defn_to_fn(form)
232
+ name_sym = form.items[1]
233
+ emit_error!(:fn_no_params) unless name_sym.is_a?(Sym)
234
+
235
+ fn_sym = Sym.new('fn')
236
+ fn_sym.line = form.head.line
237
+ fn_sym.column = form.head.column
238
+ self_sym = Sym.new("self.#{name_sym.name}")
239
+ self_sym.line = name_sym.line
240
+ self_sym.column = name_sym.column
241
+ new_list = List.new([fn_sym, self_sym, *form.items[2..]])
242
+ new_list.line = form.line
243
+ new_list.column = form.column
244
+ new_list
245
+ end
246
+
185
247
  def method_definition_form?(form)
186
248
  named_function_form?(form)
187
249
  end
@@ -57,9 +57,9 @@ module Kapusta
57
57
  def expand(form)
58
58
  case form
59
59
  when List then expand_list(form)
60
- when Vec then copy_position(Vec.new(form.items.map { |item| expand(item) }), form)
60
+ when Vec then Kapusta.copy_position(Vec.new(form.items.map { |item| expand(item) }), form)
61
61
  when HashLit
62
- copy_position(
62
+ Kapusta.copy_position(
63
63
  HashLit.new(form.entries.map do |entry|
64
64
  entry.is_a?(Array) ? [expand(entry[0]), expand(entry[1])] : entry
65
65
  end),
@@ -70,14 +70,6 @@ module Kapusta
70
70
  end
71
71
  end
72
72
 
73
- def copy_position(target, source)
74
- return target unless target.respond_to?(:line=) && source.respond_to?(:line)
75
-
76
- target.line ||= source.line
77
- target.column ||= source.column
78
- target
79
- end
80
-
81
73
  def expand_list(list)
82
74
  return list if list.empty?
83
75
 
@@ -100,11 +92,11 @@ module Kapusta
100
92
  if @macros.key?(key)
101
93
  args = list.rest
102
94
  result = invoke_macro(key, args)
103
- return copy_position(expand(result), list)
95
+ return Kapusta.copy_position(expand(result), list)
104
96
  end
105
97
  end
106
98
 
107
- copy_position(List.new(list.items.map { |item| expand(item) }), list)
99
+ Kapusta.copy_position(List.new(list.items.map { |item| expand(item) }), list)
108
100
  end
109
101
 
110
102
  def lookup_key(name)
@@ -34,15 +34,15 @@ module Kapusta
34
34
 
35
35
  def lower(form)
36
36
  case form
37
- when Quasiquote then copy_position(lower_quasi(form.form), form)
37
+ when Quasiquote then Kapusta.copy_position(lower_quasi(form.form), form)
38
38
  when Unquote, UnquoteSplice
39
39
  raise @error_class, Kapusta::Errors.format(:unquote_outside_quasiquote)
40
40
  when AutoGensym
41
41
  raise @error_class, Kapusta::Errors.format(:auto_gensym_outside_quasiquote, name: form.name)
42
- when List then copy_position(List.new(form.items.map { |item| lower(item) }), form)
43
- when Vec then copy_position(Vec.new(form.items.map { |item| lower(item) }), form)
42
+ when List then Kapusta.copy_position(List.new(form.items.map { |item| lower(item) }), form)
43
+ when Vec then Kapusta.copy_position(Vec.new(form.items.map { |item| lower(item) }), form)
44
44
  when HashLit
45
- copy_position(
45
+ Kapusta.copy_position(
46
46
  HashLit.new(form.entries.map do |entry|
47
47
  entry.is_a?(Array) ? [lower(entry[0]), lower(entry[1])] : entry
48
48
  end),
@@ -94,14 +94,6 @@ module Kapusta
94
94
  form.is_a?(List) && form.head.is_a?(Sym) && FN_HEADS.include?(form.head.name)
95
95
  end
96
96
 
97
- def copy_position(target, source)
98
- return target unless target.respond_to?(:line=) && source.respond_to?(:line)
99
-
100
- target.line ||= source.line
101
- target.column ||= source.column
102
- target
103
- end
104
-
105
97
  def lower_quasi(form)
106
98
  case form
107
99
  when AutoGensym then gensym_local_for(form.name)
@@ -12,9 +12,9 @@ module Kapusta
12
12
  def normalize(form)
13
13
  case form
14
14
  when List then normalize_list(form)
15
- when Vec then inherit_position(Vec.new(form.items.map { |item| normalize(item) }), form)
15
+ when Vec then Kapusta.copy_position(Vec.new(form.items.map { |item| normalize(item) }), form)
16
16
  when HashLit
17
- inherit_position(
17
+ Kapusta.copy_position(
18
18
  HashLit.new(form.pairs.map { |key, value| [normalize_hash_key(key), normalize(value)] }),
19
19
  form
20
20
  )
@@ -37,7 +37,7 @@ module Kapusta
37
37
 
38
38
  head = list.head
39
39
  items = list.items.map { |item| normalize(item) }
40
- return inherit_position(List.new(items), list) unless head.is_a?(Sym)
40
+ return Kapusta.copy_position(List.new(items), list) unless head.is_a?(Sym)
41
41
 
42
42
  case head.name
43
43
  when 'when'
@@ -45,39 +45,31 @@ module Kapusta
45
45
 
46
46
  cond = items[1]
47
47
  body = wrap_do(items[2..])
48
- inherit_position(List.new([Sym.new('if'), cond, body]), list)
48
+ Kapusta.copy_position(List.new([Sym.new('if'), cond, body]), list)
49
49
  when 'unless'
50
50
  raise compiler_error(:when_no_body, list, form: head.name) if items[2..].empty?
51
51
 
52
52
  cond = items[1]
53
53
  body = wrap_do(items[2..])
54
- inherit_position(List.new([Sym.new('if'), List.new([Sym.new('not'), cond]), body]), list)
54
+ Kapusta.copy_position(List.new([Sym.new('if'), List.new([Sym.new('not'), cond]), body]), list)
55
55
  when 'tset'
56
56
  raise compiler_error(:tset_no_value, list) if items.length < 4
57
57
 
58
- inherit_position(
58
+ Kapusta.copy_position(
59
59
  List.new([Sym.new('set'), List.new([Sym.new('.'), items[1], items[2]]), items[3]]),
60
60
  list
61
61
  )
62
62
  when *LuaCompat::SPECIAL_FORMS
63
63
  normalize_lua_compat_form(head.name, items)
64
64
  when '->', '->>', '-?>', '-?>>'
65
- inherit_position(normalize(thread(items[1..], head.name)), list)
65
+ Kapusta.copy_position(normalize(thread(items[1..], head.name)), list)
66
66
  when 'doto'
67
- inherit_position(normalize(doto(items[1..])), list)
67
+ Kapusta.copy_position(normalize(doto(items[1..])), list)
68
68
  else
69
- inherit_position(List.new(items), list)
69
+ Kapusta.copy_position(List.new(items), list)
70
70
  end
71
71
  end
72
72
 
73
- def inherit_position(target, source)
74
- return target unless target.respond_to?(:line=) && source.respond_to?(:line)
75
-
76
- target.line ||= source.line
77
- target.column ||= source.column
78
- target
79
- end
80
-
81
73
  def compiler_error(code, form, **args)
82
74
  line = form.respond_to?(:line) ? form.line : nil
83
75
  column = form.respond_to?(:column) ? form.column : nil
@@ -10,7 +10,7 @@ module Kapusta
10
10
  module Compiler
11
11
  class Error < Kapusta::Error; end
12
12
  CORE_SPECIAL_FORMS = %w[
13
- fn lambda λ let local var global set if when unless case match
13
+ fn defn lambda λ let local var global set if when unless case match
14
14
  while for each do values
15
15
  -> ->> -?> -?>> doto
16
16
  icollect collect fcollect accumulate faccumulate
@@ -19,7 +19,7 @@ module Kapusta
19
19
  ..
20
20
  length
21
21
  require
22
- module class
22
+ module class end
23
23
  try catch finally
24
24
  raise
25
25
  ivar cvar gvar
@@ -25,6 +25,10 @@ module Kapusta
25
25
  dot_no_args: 'expected table argument',
26
26
  each_no_binding: 'expected binding table',
27
27
  empty_call: 'expected a function, macro, or special to call',
28
+ defn_outside_header: 'defn outside class or module',
29
+ end_outside_header: 'end outside class or module',
30
+ end_with_args: 'end takes no arguments',
31
+ unclosed_header: 'class or module not closed with (end)',
28
32
  expected_var: 'expected var %{name}',
29
33
  fn_no_params: 'expected parameters table',
30
34
  global_arity: 'expected name and value',
@@ -266,7 +266,7 @@ module Kapusta
266
266
  raw_args = list_raw_rest(list)
267
267
 
268
268
  case head_name
269
- when 'fn', 'lambda', 'λ', 'macro' then render_fn(head_name, list, indent, top_level:)
269
+ when 'fn', 'defn', 'lambda', 'λ', 'macro' then render_fn(head_name, list, indent, top_level:)
270
270
  when 'let' then render_let(list, indent)
271
271
  when 'do', 'finally' then render_prefix_body_form(head_name, [], raw_args, indent)
272
272
  when 'try' then render_try(list, indent)
@@ -1,6 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative 'rename'
4
+ require_relative '../reader'
5
+ require_relative 'scope_walker'
4
6
 
5
7
  module Kapusta
6
8
  class LSP
@@ -8,6 +10,9 @@ module Kapusta
8
10
  module_function
9
11
 
10
12
  def find(uri, text, line_zero, character, workspace_index:)
13
+ marker = end_marker_at(text, line_zero, character)
14
+ return location_for_binding(uri, marker.target) if marker&.target
15
+
11
16
  target = Rename.locate(text, line_zero, character)
12
17
  return unless target
13
18
 
@@ -23,6 +28,18 @@ module Kapusta
23
28
  end
24
29
  end
25
30
 
31
+ def end_marker_at(text, line_zero, character)
32
+ forms = Reader.read_all(text)
33
+ walker = ScopeWalker.analyze(forms)
34
+ line = line_zero + 1
35
+ col = character + 1
36
+ walker.end_markers.find do |m|
37
+ m.line == line && col >= m.column && col <= m.end_column
38
+ end
39
+ rescue Kapusta::Error
40
+ nil
41
+ end
42
+
26
43
  def locations_for_macro(uri, binding, workspace_index)
27
44
  return unless binding
28
45
 
@@ -344,7 +344,9 @@ module Kapusta
344
344
  end
345
345
 
346
346
  def rename_constant(target, new_name, workspace_index)
347
- return error("invalid constant segment: #{new_name}") unless Identifier.valid_constant_segment?(new_name)
347
+ unless Identifier.valid_constant_segment?(new_name)
348
+ return error("class and module names must start with an uppercase letter (got #{new_name.inspect})")
349
+ end
348
350
 
349
351
  prefix = target.segment_prefix
350
352
  seg_index = target.segment_index
@@ -15,17 +15,24 @@ module Kapusta
15
15
  bindings[name] || parent&.lookup(name)
16
16
  end
17
17
  end
18
-
19
- SKIPPED_HEADS = %w[macros quasi-sym quasi-list
20
- quasi-list-tail quasi-vec quasi-vec-tail quasi-hash quasi-gensym].freeze
18
+ EndMarker = Struct.new(:line, :column, :end_column, :target, keyword_init: true)
21
19
 
22
20
  DISPATCHERS = {
21
+ 'macros' => :skip,
22
+ 'quasi-sym' => :skip,
23
+ 'quasi-list' => :skip,
24
+ 'quasi-list-tail' => :skip,
25
+ 'quasi-vec' => :skip,
26
+ 'quasi-vec-tail' => :skip,
27
+ 'quasi-hash' => :skip,
28
+ 'quasi-gensym' => :skip,
23
29
  'let' => :walk_let,
24
30
  'local' => :walk_local_var,
25
31
  'var' => :walk_local_var,
26
32
  'global' => :walk_global,
27
33
  'set' => :walk_set,
28
34
  'fn' => :walk_fn,
35
+ 'defn' => :walk_fn,
29
36
  'lambda' => :walk_fn,
30
37
  'λ' => :walk_fn,
31
38
  'for' => :walk_for,
@@ -48,7 +55,7 @@ module Kapusta
48
55
  'gvar' => :walk_sigil_form
49
56
  }.freeze
50
57
 
51
- attr_reader :bindings, :references, :root_scope
58
+ attr_reader :bindings, :references, :root_scope, :end_markers
52
59
 
53
60
  def self.analyze(forms)
54
61
  walker = new
@@ -59,6 +66,7 @@ module Kapusta
59
66
  def initialize
60
67
  @bindings = []
61
68
  @references = []
69
+ @end_markers = []
62
70
  @scope_seq = 0
63
71
  @root_scope = make_scope(nil, :file)
64
72
  @gvar_scope = make_scope(nil, :gvars)
@@ -67,17 +75,43 @@ module Kapusta
67
75
  end
68
76
 
69
77
  def walk_top(forms)
70
- i = 0
78
+ walk_form_run(forms, 0, @root_scope)
79
+ end
80
+
81
+ def walk_form_run(forms, start, scope, header_target: nil)
82
+ i = start
71
83
  while i < forms.length
72
84
  form = forms[i]
85
+ if end_form?(form)
86
+ record_end_marker(form, header_target) if header_target
87
+ return i + 1
88
+ end
89
+
73
90
  if bodyless_header?(form)
74
- walk_bodyless_header(form, forms[(i + 1)..] || [], @root_scope)
75
- break
91
+ i = walk_bodyless_header(form, forms, i + 1, scope)
92
+ next
76
93
  end
77
94
 
78
- walk_form(form, @root_scope)
95
+ walk_form(form, scope)
79
96
  i += 1
80
97
  end
98
+ i
99
+ end
100
+
101
+ def record_end_marker(form, target)
102
+ head = form.head
103
+ return unless head.is_a?(Sym) && head.respond_to?(:line) && head.line
104
+
105
+ @end_markers << EndMarker.new(
106
+ line: head.line,
107
+ column: head.column,
108
+ end_column: head.column + head.name.length,
109
+ target:
110
+ )
111
+ end
112
+
113
+ def end_form?(form)
114
+ form.is_a?(List) && !form.empty? && form.head.is_a?(Sym) && form.head.name == 'end'
81
115
  end
82
116
 
83
117
  def binding_at(line, column)
@@ -120,25 +154,25 @@ module Kapusta
120
154
  end
121
155
  end
122
156
 
123
- def walk_bodyless_header(form, remaining_forms, scope)
157
+ def walk_bodyless_header(form, forms, body_start, scope)
124
158
  case form.head.name
125
159
  when 'module'
126
160
  name_sym = form.items[1]
127
- add_constant_binding(name_sym, scope, :module) if name_sym.is_a?(Sym)
161
+ binding = name_sym.is_a?(Sym) ? add_constant_binding(name_sym, scope, :module) : nil
128
162
  body = form.items[2..] || []
129
163
  inside_module_or_class do
130
164
  if body.length == 1 && bodyless_header?(body[0])
131
- walk_bodyless_header(body[0], remaining_forms, scope)
165
+ walk_bodyless_header(body[0], forms, body_start, scope)
132
166
  else
133
- remaining_forms.each { |item| walk_form(item, scope) }
167
+ walk_form_run(forms, body_start, scope, header_target: binding)
134
168
  end
135
169
  end
136
170
  when 'class'
137
171
  name_sym, supers, = split_class_args(form.items[1..] || [])
138
172
  supers&.items&.each { |item| walk_form(item, scope) }
139
- add_constant_binding(name_sym, scope, :class) if name_sym.is_a?(Sym)
173
+ binding = name_sym.is_a?(Sym) ? add_constant_binding(name_sym, scope, :class) : nil
140
174
  inside_class do
141
- remaining_forms.each { |item| walk_form(item, scope) }
175
+ walk_form_run(forms, body_start, scope, header_target: binding)
142
176
  end
143
177
  end
144
178
  end
@@ -217,10 +251,12 @@ module Kapusta
217
251
  return
218
252
  end
219
253
 
220
- return if SKIPPED_HEADS.include?(head.name)
221
-
222
254
  dispatcher = DISPATCHERS[head.name]
223
- return send(dispatcher, list, scope) if dispatcher
255
+ if dispatcher
256
+ return if dispatcher == :skip
257
+
258
+ return send(dispatcher, list, scope)
259
+ end
224
260
 
225
261
  list.items.each { |item| walk_form(item, scope) }
226
262
  end
@@ -351,28 +387,30 @@ module Kapusta
351
387
  def walk_fn(list, scope)
352
388
  items = list.items
353
389
  if items[1].is_a?(Vec)
390
+ name_sym = nil
354
391
  params = items[1]
355
392
  body = items[2..]
356
- fn_scope = make_scope(scope, :fn)
357
- bind_param_vec(params, fn_scope)
358
- body.each { |form| walk_form(form, fn_scope) }
359
393
  elsif items[1].is_a?(Sym) && items[2].is_a?(Vec)
360
394
  name_sym = items[1]
361
395
  params = items[2]
362
396
  body = items[3..]
397
+ else
398
+ items[1..]&.each { |item| walk_form(item, scope) }
399
+ return
400
+ end
401
+
402
+ fn_scope = make_scope(scope, :fn)
403
+ if name_sym
363
404
  kind = if method_definition_context?
364
405
  :method
365
406
  else
366
407
  (scope == @root_scope ? :toplevel_fn : :fn_local)
367
408
  end
368
- fn_scope = make_scope(scope, :fn)
369
409
  binding = add_binding(name_sym, scope, kind, lexical: kind != :method)
370
410
  fn_scope.bindings[name_sym.name] = binding unless kind == :method
371
- bind_param_vec(params, fn_scope)
372
- body.each { |form| walk_form(form, fn_scope) }
373
- else
374
- items[1..]&.each { |item| walk_form(item, scope) }
375
411
  end
412
+ bind_param_vec(params, fn_scope)
413
+ body.each { |form| walk_form(form, fn_scope) }
376
414
  end
377
415
 
378
416
  def method_definition_context?
@@ -557,40 +595,35 @@ module Kapusta
557
595
  end
558
596
  end
559
597
 
560
- def bind_param_vec(vec, scope)
561
- items = vec.items
598
+ def each_pattern_item(items)
562
599
  i = 0
563
600
  while i < items.length
564
- item = items[i]
565
- if item.is_a?(Sym) && item.name == '&'
566
- rest = items[i + 1]
567
- bind_pattern(rest, scope, :fn_param) if rest.is_a?(Sym) && rest.name != '_'
601
+ if items[i].is_a?(Sym) && items[i].name == '&'
602
+ yield :rest, items[i + 1]
568
603
  i += 2
569
- elsif item.is_a?(Sym) && ['...', '_'].include?(item.name)
570
- i += 1
571
604
  else
572
- bind_pattern(item, scope, :fn_param)
605
+ yield :item, items[i]
573
606
  i += 1
574
607
  end
575
608
  end
576
609
  end
577
610
 
578
- def bind_vec_pattern(vec, scope, kind)
579
- items = vec.items
580
- i = 0
581
- while i < items.length
582
- item = items[i]
583
- if item.is_a?(Sym) && item.name == '&'
584
- rest = items[i + 1]
585
- bind_pattern(rest, scope, kind) if rest
586
- i += 2
587
- else
588
- bind_pattern(item, scope, kind)
589
- i += 1
611
+ def bind_param_vec(vec, scope)
612
+ each_pattern_item(vec.items) do |kind, item|
613
+ if kind == :rest
614
+ bind_pattern(item, scope, :fn_param) if item.is_a?(Sym) && item.name != '_'
615
+ elsif !(item.is_a?(Sym) && ['...', '_'].include?(item.name))
616
+ bind_pattern(item, scope, :fn_param)
590
617
  end
591
618
  end
592
619
  end
593
620
 
621
+ def bind_vec_pattern(vec, scope, kind)
622
+ each_pattern_item(vec.items) do |item_kind, item|
623
+ bind_pattern(item, scope, kind) if item_kind == :item || item
624
+ end
625
+ end
626
+
594
627
  def bind_hash_pattern(hash, scope, kind)
595
628
  hash.pairs.each do |pair|
596
629
  bind_pattern(pair[1], scope, kind)