kapusta 0.7.0 → 0.8.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.
- checksums.yaml +4 -4
- data/bin/fennel-parity +9 -4
- data/examples/import-helpers.kapm +9 -0
- data/examples/macros-import-helpers.kap +3 -0
- data/examples/macros-import-whole.kap +5 -0
- data/examples/macros-import.kap +6 -0
- data/examples/shared-macros.kapm +4 -0
- data/lib/kapusta/compiler/macro_expander.rb +54 -142
- data/lib/kapusta/compiler/macro_gensym.rb +21 -0
- data/lib/kapusta/compiler/macro_importer.rb +81 -0
- data/lib/kapusta/compiler/macro_lowerer.rb +184 -0
- data/lib/kapusta/errors.rb +6 -1
- data/lib/kapusta/lsp/definition.rb +67 -0
- data/lib/kapusta/lsp/diagnostics.rb +42 -0
- data/lib/kapusta/lsp/formatting.rb +30 -0
- data/lib/kapusta/lsp/identifier.rb +28 -0
- data/lib/kapusta/lsp/rename.rb +417 -0
- data/lib/kapusta/lsp/scope_walker.rb +643 -0
- data/lib/kapusta/lsp/workspace_index.rb +225 -0
- data/lib/kapusta/lsp.rb +102 -48
- data/lib/kapusta/version.rb +1 -1
- data/spec/examples_errors_spec.rb +17 -1
- data/spec/examples_spec.rb +12 -0
- data/spec/lsp_spec.rb +535 -15
- metadata +16 -1
|
@@ -0,0 +1,643 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative '../ast'
|
|
4
|
+
require_relative '../compiler'
|
|
5
|
+
|
|
6
|
+
module Kapusta
|
|
7
|
+
class LSP
|
|
8
|
+
class ScopeWalker
|
|
9
|
+
Binding = Struct.new(:kind, :name, :line, :column, :end_column, :scope, :segments,
|
|
10
|
+
:sym, :in_module_or_class, :import_module, :import_key, keyword_init: true)
|
|
11
|
+
Reference = Struct.new(:name, :line, :column, :end_column, :scope, :sym,
|
|
12
|
+
:target, keyword_init: true)
|
|
13
|
+
Scope = Struct.new(:id, :parent, :bindings, :kind) do
|
|
14
|
+
def lookup(name)
|
|
15
|
+
bindings[name] || parent&.lookup(name)
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
SKIPPED_HEADS = %w[macros ivar cvar gvar quasi-sym quasi-list
|
|
20
|
+
quasi-list-tail quasi-vec quasi-vec-tail quasi-hash quasi-gensym].freeze
|
|
21
|
+
|
|
22
|
+
DISPATCHERS = {
|
|
23
|
+
'let' => :walk_let,
|
|
24
|
+
'local' => :walk_local_var,
|
|
25
|
+
'var' => :walk_local_var,
|
|
26
|
+
'global' => :walk_global,
|
|
27
|
+
'set' => :walk_set,
|
|
28
|
+
'fn' => :walk_fn,
|
|
29
|
+
'lambda' => :walk_fn,
|
|
30
|
+
'λ' => :walk_fn,
|
|
31
|
+
'for' => :walk_for,
|
|
32
|
+
'each' => :walk_each_like,
|
|
33
|
+
'collect' => :walk_each_like,
|
|
34
|
+
'icollect' => :walk_each_like,
|
|
35
|
+
'fcollect' => :walk_for_like,
|
|
36
|
+
'accumulate' => :walk_accumulate,
|
|
37
|
+
'faccumulate' => :walk_faccumulate,
|
|
38
|
+
'case' => :walk_case_match,
|
|
39
|
+
'match' => :walk_case_match,
|
|
40
|
+
'try' => :walk_try,
|
|
41
|
+
'module' => :walk_module_class,
|
|
42
|
+
'class' => :walk_module_class,
|
|
43
|
+
'hashfn' => :walk_hashfn,
|
|
44
|
+
'macro' => :walk_macro_def,
|
|
45
|
+
'import-macros' => :walk_import_macros
|
|
46
|
+
}.freeze
|
|
47
|
+
|
|
48
|
+
attr_reader :bindings, :references, :root_scope
|
|
49
|
+
|
|
50
|
+
def self.analyze(forms)
|
|
51
|
+
walker = new
|
|
52
|
+
walker.walk_top(forms)
|
|
53
|
+
walker
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def initialize
|
|
57
|
+
@bindings = []
|
|
58
|
+
@references = []
|
|
59
|
+
@scope_seq = 0
|
|
60
|
+
@root_scope = make_scope(nil, :file)
|
|
61
|
+
@in_module_or_class = 0
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def walk_top(forms)
|
|
65
|
+
i = 0
|
|
66
|
+
while i < forms.length
|
|
67
|
+
form = forms[i]
|
|
68
|
+
if bodyless_header?(form)
|
|
69
|
+
walk_bodyless_header(form, forms[(i + 1)..] || [], @root_scope)
|
|
70
|
+
break
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
walk_form(form, @root_scope)
|
|
74
|
+
i += 1
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def binding_at(line, column)
|
|
79
|
+
@bindings.each do |b|
|
|
80
|
+
return b if b.line == line && column >= b.column && column <= b.end_column
|
|
81
|
+
end
|
|
82
|
+
nil
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def reference_at(line, column)
|
|
86
|
+
@references.each do |r|
|
|
87
|
+
return r if r.line == line && column >= r.column && column <= r.end_column
|
|
88
|
+
end
|
|
89
|
+
nil
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def sym_at(line, column)
|
|
93
|
+
binding_at(line, column) || reference_at(line, column)
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
private
|
|
97
|
+
|
|
98
|
+
def make_scope(parent, kind)
|
|
99
|
+
@scope_seq += 1
|
|
100
|
+
Scope.new(@scope_seq, parent, {}, kind)
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def bodyless_header?(form)
|
|
104
|
+
return false unless form.is_a?(List) && !form.empty? && form.head.is_a?(Sym)
|
|
105
|
+
|
|
106
|
+
case form.head.name
|
|
107
|
+
when 'module'
|
|
108
|
+
body = form.items[2..] || []
|
|
109
|
+
body.empty? || (body.length == 1 && bodyless_header?(body[0]))
|
|
110
|
+
when 'class'
|
|
111
|
+
_name_sym, _supers, body = split_class_args(form.items[1..] || [])
|
|
112
|
+
body.empty?
|
|
113
|
+
else
|
|
114
|
+
false
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
def walk_bodyless_header(form, remaining_forms, scope)
|
|
119
|
+
case form.head.name
|
|
120
|
+
when 'module'
|
|
121
|
+
name_sym = form.items[1]
|
|
122
|
+
add_constant_binding(name_sym, scope, :module) if name_sym.is_a?(Sym)
|
|
123
|
+
body = form.items[2..] || []
|
|
124
|
+
inside_module_or_class do
|
|
125
|
+
if body.length == 1 && bodyless_header?(body[0])
|
|
126
|
+
walk_bodyless_header(body[0], remaining_forms, scope)
|
|
127
|
+
else
|
|
128
|
+
remaining_forms.each { |item| walk_form(item, scope) }
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
when 'class'
|
|
132
|
+
name_sym, supers, = split_class_args(form.items[1..] || [])
|
|
133
|
+
supers&.items&.each { |item| walk_form(item, scope) }
|
|
134
|
+
add_constant_binding(name_sym, scope, :class) if name_sym.is_a?(Sym)
|
|
135
|
+
inside_module_or_class do
|
|
136
|
+
remaining_forms.each { |item| walk_form(item, scope) }
|
|
137
|
+
end
|
|
138
|
+
end
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
def split_class_args(args)
|
|
142
|
+
name_sym = args[0]
|
|
143
|
+
if args[1].is_a?(Vec)
|
|
144
|
+
[name_sym, args[1], args[2..] || []]
|
|
145
|
+
else
|
|
146
|
+
[name_sym, nil, args[1..] || []]
|
|
147
|
+
end
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
def inside_module_or_class
|
|
151
|
+
@in_module_or_class += 1
|
|
152
|
+
yield
|
|
153
|
+
ensure
|
|
154
|
+
@in_module_or_class -= 1
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
def walk_form(form, scope)
|
|
158
|
+
case form
|
|
159
|
+
when List then walk_list(form, scope)
|
|
160
|
+
when Vec then form.items.each { |item| walk_form(item, scope) }
|
|
161
|
+
when HashLit then walk_hash(form, scope)
|
|
162
|
+
when Sym then walk_reference(form, scope)
|
|
163
|
+
when Quasiquote then walk_quasi(form.form, scope)
|
|
164
|
+
when Unquote, UnquoteSplice then walk_form(form.form, scope)
|
|
165
|
+
end
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
def walk_hash(hash, scope)
|
|
169
|
+
hash.entries.each do |entry|
|
|
170
|
+
next unless entry.is_a?(Array)
|
|
171
|
+
|
|
172
|
+
_key, value = entry
|
|
173
|
+
walk_form(value, scope)
|
|
174
|
+
end
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
def walk_quasi(form, scope)
|
|
178
|
+
case form
|
|
179
|
+
when Unquote, UnquoteSplice then walk_form(form.form, scope)
|
|
180
|
+
when List, Vec then form.items.each { |item| walk_quasi(item, scope) }
|
|
181
|
+
when HashLit
|
|
182
|
+
form.entries.each do |entry|
|
|
183
|
+
next unless entry.is_a?(Array)
|
|
184
|
+
|
|
185
|
+
_key, value = entry
|
|
186
|
+
walk_quasi(value, scope)
|
|
187
|
+
end
|
|
188
|
+
end
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
def walk_list(list, scope)
|
|
192
|
+
return if list.empty?
|
|
193
|
+
|
|
194
|
+
head = list.head
|
|
195
|
+
unless head.is_a?(Sym)
|
|
196
|
+
list.items.each { |item| walk_form(item, scope) }
|
|
197
|
+
return
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
return if SKIPPED_HEADS.include?(head.name)
|
|
201
|
+
|
|
202
|
+
dispatcher = DISPATCHERS[head.name]
|
|
203
|
+
return send(dispatcher, list, scope) if dispatcher
|
|
204
|
+
|
|
205
|
+
list.items.each { |item| walk_form(item, scope) }
|
|
206
|
+
end
|
|
207
|
+
|
|
208
|
+
def walk_let(list, scope)
|
|
209
|
+
bindings_vec = list.items[1]
|
|
210
|
+
body = list.items[2..]
|
|
211
|
+
return unless bindings_vec.is_a?(Vec)
|
|
212
|
+
|
|
213
|
+
let_scope = make_scope(scope, :let)
|
|
214
|
+
items = bindings_vec.items
|
|
215
|
+
i = 0
|
|
216
|
+
while i < items.length
|
|
217
|
+
name_pat = items[i]
|
|
218
|
+
value = items[i + 1]
|
|
219
|
+
walk_form(value, let_scope) if value
|
|
220
|
+
bind_pattern(name_pat, let_scope, :let)
|
|
221
|
+
i += 2
|
|
222
|
+
end
|
|
223
|
+
body&.each { |form| walk_form(form, let_scope) }
|
|
224
|
+
end
|
|
225
|
+
|
|
226
|
+
def walk_local_var(list, scope)
|
|
227
|
+
kind = list.head.name == 'var' ? :var : :local
|
|
228
|
+
target = list.items[1]
|
|
229
|
+
value = list.items[2]
|
|
230
|
+
walk_form(value, scope) if value
|
|
231
|
+
bind_pattern(target, scope, kind)
|
|
232
|
+
end
|
|
233
|
+
|
|
234
|
+
def walk_global(list, _scope)
|
|
235
|
+
# Globals are not renamable; skip the binder name and walk only the value.
|
|
236
|
+
value = list.items[2]
|
|
237
|
+
walk_form(value, @root_scope) if value
|
|
238
|
+
end
|
|
239
|
+
|
|
240
|
+
def walk_hashfn(list, scope)
|
|
241
|
+
list.items[1..]&.each { |form| walk_form(form, scope) }
|
|
242
|
+
end
|
|
243
|
+
|
|
244
|
+
def walk_macro_def(list, scope)
|
|
245
|
+
items = list.items
|
|
246
|
+
name_sym = items[1]
|
|
247
|
+
params = items[2]
|
|
248
|
+
body = items[3..] || []
|
|
249
|
+
return unless name_sym.is_a?(Sym) && params.is_a?(Vec)
|
|
250
|
+
|
|
251
|
+
add_binding(name_sym, @root_scope, :macro)
|
|
252
|
+
fn_scope = make_scope(scope, :fn)
|
|
253
|
+
bind_param_vec(params, fn_scope)
|
|
254
|
+
body.each { |form| walk_form(form, fn_scope) }
|
|
255
|
+
end
|
|
256
|
+
|
|
257
|
+
def walk_import_macros(list, scope)
|
|
258
|
+
destructure = list.items[1]
|
|
259
|
+
module_arg = list.items[2]
|
|
260
|
+
return unless destructure.is_a?(HashLit)
|
|
261
|
+
return unless module_arg.is_a?(Symbol) || module_arg.is_a?(String)
|
|
262
|
+
|
|
263
|
+
module_label = module_arg.to_s.tr('_', '-')
|
|
264
|
+
destructure.pairs.each do |key, target|
|
|
265
|
+
next unless target.is_a?(Sym) && key.is_a?(Symbol)
|
|
266
|
+
|
|
267
|
+
add_import_macro_binding(target, scope, module_label, key)
|
|
268
|
+
end
|
|
269
|
+
end
|
|
270
|
+
|
|
271
|
+
def add_import_macro_binding(sym, _scope, module_label, import_key)
|
|
272
|
+
b = Binding.new(
|
|
273
|
+
kind: :macro_import,
|
|
274
|
+
name: sym.name,
|
|
275
|
+
line: sym.line,
|
|
276
|
+
column: sym.column,
|
|
277
|
+
end_column: sym.column + sym.name.length,
|
|
278
|
+
scope: @root_scope,
|
|
279
|
+
segments: sym.dotted? ? sym.segments : nil,
|
|
280
|
+
sym:,
|
|
281
|
+
in_module_or_class: false,
|
|
282
|
+
import_module: module_label,
|
|
283
|
+
import_key:
|
|
284
|
+
)
|
|
285
|
+
@bindings << b
|
|
286
|
+
@root_scope.bindings[sym.name] = b
|
|
287
|
+
b
|
|
288
|
+
end
|
|
289
|
+
|
|
290
|
+
def walk_set(list, scope)
|
|
291
|
+
target = list.items[1]
|
|
292
|
+
value = list.items[2]
|
|
293
|
+
walk_form(value, scope) if value
|
|
294
|
+
return unless target.is_a?(Sym) && !target.dotted?
|
|
295
|
+
|
|
296
|
+
existing = scope.lookup(target.name)
|
|
297
|
+
if existing
|
|
298
|
+
add_reference(target, scope, existing)
|
|
299
|
+
else
|
|
300
|
+
add_binding(target, scope, :set)
|
|
301
|
+
end
|
|
302
|
+
end
|
|
303
|
+
|
|
304
|
+
def walk_fn(list, scope)
|
|
305
|
+
items = list.items
|
|
306
|
+
if items[1].is_a?(Vec)
|
|
307
|
+
params = items[1]
|
|
308
|
+
body = items[2..]
|
|
309
|
+
fn_scope = make_scope(scope, :fn)
|
|
310
|
+
bind_param_vec(params, fn_scope)
|
|
311
|
+
body.each { |form| walk_form(form, fn_scope) }
|
|
312
|
+
elsif items[1].is_a?(Sym) && items[2].is_a?(Vec)
|
|
313
|
+
name_sym = items[1]
|
|
314
|
+
params = items[2]
|
|
315
|
+
body = items[3..]
|
|
316
|
+
kind = if method_definition_context?
|
|
317
|
+
:method
|
|
318
|
+
else
|
|
319
|
+
(scope == @root_scope ? :toplevel_fn : :fn_local)
|
|
320
|
+
end
|
|
321
|
+
fn_scope = make_scope(scope, :fn)
|
|
322
|
+
binding = add_binding(name_sym, scope, kind, lexical: kind != :method)
|
|
323
|
+
fn_scope.bindings[name_sym.name] = binding unless kind == :method
|
|
324
|
+
bind_param_vec(params, fn_scope)
|
|
325
|
+
body.each { |form| walk_form(form, fn_scope) }
|
|
326
|
+
else
|
|
327
|
+
items[1..]&.each { |item| walk_form(item, scope) }
|
|
328
|
+
end
|
|
329
|
+
end
|
|
330
|
+
|
|
331
|
+
def method_definition_context?
|
|
332
|
+
@in_module_or_class.positive?
|
|
333
|
+
end
|
|
334
|
+
|
|
335
|
+
def walk_for(list, scope)
|
|
336
|
+
bindings_vec = list.items[1]
|
|
337
|
+
body = list.items[2..]
|
|
338
|
+
return unless bindings_vec.is_a?(Vec)
|
|
339
|
+
|
|
340
|
+
for_scope = make_scope(scope, :for)
|
|
341
|
+
items = bindings_vec.items
|
|
342
|
+
counter = items[0]
|
|
343
|
+
i = 1
|
|
344
|
+
until_forms = []
|
|
345
|
+
while i < items.length
|
|
346
|
+
item = items[i]
|
|
347
|
+
if item.is_a?(Sym) && item.name == '&until'
|
|
348
|
+
until_forms << items[i + 1] if items[i + 1]
|
|
349
|
+
i += 2
|
|
350
|
+
else
|
|
351
|
+
walk_form(item, scope)
|
|
352
|
+
i += 1
|
|
353
|
+
end
|
|
354
|
+
end
|
|
355
|
+
bind_pattern(counter, for_scope, :for_counter) if counter
|
|
356
|
+
until_forms.each { |form| walk_form(form, for_scope) }
|
|
357
|
+
body&.each { |form| walk_form(form, for_scope) }
|
|
358
|
+
end
|
|
359
|
+
|
|
360
|
+
def walk_for_like(list, scope) = walk_for(list, scope)
|
|
361
|
+
|
|
362
|
+
def walk_each_like(list, scope)
|
|
363
|
+
bindings_vec = list.items[1]
|
|
364
|
+
body = list.items[2..]
|
|
365
|
+
return unless bindings_vec.is_a?(Vec)
|
|
366
|
+
|
|
367
|
+
items = bindings_vec.items
|
|
368
|
+
return if items.empty?
|
|
369
|
+
|
|
370
|
+
each_scope = make_scope(scope, :each)
|
|
371
|
+
iter_expr = items.last
|
|
372
|
+
binders = items[0..-2]
|
|
373
|
+
walk_form(iter_expr, scope)
|
|
374
|
+
binders.each { |b| bind_pattern(b, each_scope, :each_var) }
|
|
375
|
+
body&.each { |form| walk_form(form, each_scope) }
|
|
376
|
+
end
|
|
377
|
+
|
|
378
|
+
def walk_accumulate(list, scope)
|
|
379
|
+
bindings_vec = list.items[1]
|
|
380
|
+
body = list.items[2..]
|
|
381
|
+
return unless bindings_vec.is_a?(Vec)
|
|
382
|
+
|
|
383
|
+
items = bindings_vec.items
|
|
384
|
+
return if items.length < 4
|
|
385
|
+
|
|
386
|
+
acc_scope = make_scope(scope, :accumulate)
|
|
387
|
+
acc_name = items[0]
|
|
388
|
+
acc_init = items[1]
|
|
389
|
+
iter_items = items[2..]
|
|
390
|
+
iter_expr = iter_items.last
|
|
391
|
+
binders = iter_items[0...-1]
|
|
392
|
+
walk_form(acc_init, scope)
|
|
393
|
+
bind_pattern(acc_name, acc_scope, :accumulator)
|
|
394
|
+
walk_form(iter_expr, scope)
|
|
395
|
+
binders.each { |b| bind_pattern(b, acc_scope, :each_var) }
|
|
396
|
+
body&.each { |form| walk_form(form, acc_scope) }
|
|
397
|
+
end
|
|
398
|
+
|
|
399
|
+
def walk_faccumulate(list, scope)
|
|
400
|
+
bindings_vec = list.items[1]
|
|
401
|
+
body = list.items[2..]
|
|
402
|
+
return unless bindings_vec.is_a?(Vec)
|
|
403
|
+
|
|
404
|
+
items = bindings_vec.items
|
|
405
|
+
return if items.length < 5
|
|
406
|
+
|
|
407
|
+
acc_scope = make_scope(scope, :faccumulate)
|
|
408
|
+
acc_name = items[0]
|
|
409
|
+
acc_init = items[1]
|
|
410
|
+
counter = items[2]
|
|
411
|
+
walk_form(acc_init, scope)
|
|
412
|
+
items[3..]&.each { |form| walk_form(form, scope) }
|
|
413
|
+
bind_pattern(acc_name, acc_scope, :accumulator)
|
|
414
|
+
bind_pattern(counter, acc_scope, :for_counter)
|
|
415
|
+
body&.each { |form| walk_form(form, acc_scope) }
|
|
416
|
+
end
|
|
417
|
+
|
|
418
|
+
def walk_case_match(list, scope)
|
|
419
|
+
mode = list.head.name == 'match' ? :match : :case
|
|
420
|
+
subject = list.items[1]
|
|
421
|
+
arms = list.items[2..] || []
|
|
422
|
+
walk_form(subject, scope)
|
|
423
|
+
arms.each_slice(2) do |pattern, body|
|
|
424
|
+
arm_scope = make_scope(scope, :case_arm)
|
|
425
|
+
walk_pattern(pattern, arm_scope, scope, mode)
|
|
426
|
+
walk_form(body, arm_scope) if body
|
|
427
|
+
end
|
|
428
|
+
end
|
|
429
|
+
|
|
430
|
+
def walk_try(list, scope)
|
|
431
|
+
body = list.items[1]
|
|
432
|
+
clauses = list.items[2..] || []
|
|
433
|
+
walk_form(body, scope)
|
|
434
|
+
clauses.each do |clause|
|
|
435
|
+
next unless clause.is_a?(List)
|
|
436
|
+
|
|
437
|
+
head = clause.head
|
|
438
|
+
next unless head.is_a?(Sym)
|
|
439
|
+
|
|
440
|
+
if head.name == 'catch'
|
|
441
|
+
walk_catch(clause, scope)
|
|
442
|
+
elsif head.name == 'finally'
|
|
443
|
+
clause.items[1..]&.each { |form| walk_form(form, scope) }
|
|
444
|
+
end
|
|
445
|
+
end
|
|
446
|
+
end
|
|
447
|
+
|
|
448
|
+
def walk_catch(clause, scope)
|
|
449
|
+
rest = clause.items[1..]
|
|
450
|
+
if rest[0].is_a?(Sym) && (rest[0].name.match?(/\A[A-Z]/) || rest[0].dotted?)
|
|
451
|
+
klass = rest[0]
|
|
452
|
+
bind_sym = rest[1]
|
|
453
|
+
body = rest[2..]
|
|
454
|
+
walk_form(klass, scope)
|
|
455
|
+
else
|
|
456
|
+
bind_sym = rest[0]
|
|
457
|
+
body = rest[1..]
|
|
458
|
+
end
|
|
459
|
+
catch_scope = make_scope(scope, :catch)
|
|
460
|
+
bind_pattern(bind_sym, catch_scope, :catch) if bind_sym.is_a?(Sym)
|
|
461
|
+
body&.each { |form| walk_form(form, catch_scope) }
|
|
462
|
+
end
|
|
463
|
+
|
|
464
|
+
def walk_module_class(list, scope)
|
|
465
|
+
kind = list.head.name == 'module' ? :module : :class
|
|
466
|
+
name_sym = list.items[1]
|
|
467
|
+
body_start = 2
|
|
468
|
+
if kind == :class && list.items[2].is_a?(Vec)
|
|
469
|
+
list.items[2].items.each { |item| walk_form(item, scope) }
|
|
470
|
+
body_start = 3
|
|
471
|
+
end
|
|
472
|
+
|
|
473
|
+
add_constant_binding(name_sym, scope, kind) if name_sym.is_a?(Sym)
|
|
474
|
+
|
|
475
|
+
inside_module_or_class do
|
|
476
|
+
list.items[body_start..]&.each { |form| walk_form(form, scope) }
|
|
477
|
+
end
|
|
478
|
+
end
|
|
479
|
+
|
|
480
|
+
def walk_reference(sym, scope)
|
|
481
|
+
return if hashfn_synthetic?(sym.name)
|
|
482
|
+
return if sym.is_a?(MacroSym) || sym.is_a?(AutoGensym)
|
|
483
|
+
|
|
484
|
+
target_name = sym.dotted? ? sym.segments.first : sym.name
|
|
485
|
+
return if target_name.nil? || target_name.empty?
|
|
486
|
+
|
|
487
|
+
target = scope.lookup(target_name)
|
|
488
|
+
return if target.nil? && Compiler::SPECIAL_FORMS.include?(sym.name)
|
|
489
|
+
|
|
490
|
+
add_reference(sym, scope, target)
|
|
491
|
+
end
|
|
492
|
+
|
|
493
|
+
def hashfn_synthetic?(name)
|
|
494
|
+
name == '$' || name == '$...' || name.match?(/\A\$\d\z/)
|
|
495
|
+
end
|
|
496
|
+
|
|
497
|
+
def bind_pattern(pattern, scope, kind)
|
|
498
|
+
case pattern
|
|
499
|
+
when Sym
|
|
500
|
+
return if pattern.name == '_'
|
|
501
|
+
|
|
502
|
+
add_binding(pattern, scope, kind)
|
|
503
|
+
when Vec
|
|
504
|
+
bind_vec_pattern(pattern, scope, kind)
|
|
505
|
+
when HashLit
|
|
506
|
+
bind_hash_pattern(pattern, scope, kind)
|
|
507
|
+
end
|
|
508
|
+
end
|
|
509
|
+
|
|
510
|
+
def bind_param_vec(vec, scope)
|
|
511
|
+
items = vec.items
|
|
512
|
+
i = 0
|
|
513
|
+
while i < items.length
|
|
514
|
+
item = items[i]
|
|
515
|
+
if item.is_a?(Sym) && item.name == '&'
|
|
516
|
+
rest = items[i + 1]
|
|
517
|
+
bind_pattern(rest, scope, :fn_param) if rest.is_a?(Sym) && rest.name != '_'
|
|
518
|
+
i += 2
|
|
519
|
+
elsif item.is_a?(Sym) && ['...', '_'].include?(item.name)
|
|
520
|
+
i += 1
|
|
521
|
+
else
|
|
522
|
+
bind_pattern(item, scope, :fn_param)
|
|
523
|
+
i += 1
|
|
524
|
+
end
|
|
525
|
+
end
|
|
526
|
+
end
|
|
527
|
+
|
|
528
|
+
def bind_vec_pattern(vec, scope, kind)
|
|
529
|
+
items = vec.items
|
|
530
|
+
i = 0
|
|
531
|
+
while i < items.length
|
|
532
|
+
item = items[i]
|
|
533
|
+
if item.is_a?(Sym) && item.name == '&'
|
|
534
|
+
rest = items[i + 1]
|
|
535
|
+
bind_pattern(rest, scope, kind) if rest
|
|
536
|
+
i += 2
|
|
537
|
+
else
|
|
538
|
+
bind_pattern(item, scope, kind)
|
|
539
|
+
i += 1
|
|
540
|
+
end
|
|
541
|
+
end
|
|
542
|
+
end
|
|
543
|
+
|
|
544
|
+
def bind_hash_pattern(hash, scope, kind)
|
|
545
|
+
hash.pairs.each do |pair|
|
|
546
|
+
bind_pattern(pair[1], scope, kind)
|
|
547
|
+
end
|
|
548
|
+
end
|
|
549
|
+
|
|
550
|
+
def walk_pattern(pattern, scope, outer_scope, mode)
|
|
551
|
+
case pattern
|
|
552
|
+
when Sym then walk_pattern_symbol(pattern, scope, outer_scope, mode)
|
|
553
|
+
when Vec then pattern.items.each { |item| walk_pattern_seq_item(item, scope, outer_scope, mode) }
|
|
554
|
+
when HashLit then pattern.pairs.each { |pair| walk_pattern(pair[1], scope, outer_scope, mode) }
|
|
555
|
+
when List then walk_pattern_list(pattern, scope, outer_scope, mode)
|
|
556
|
+
end
|
|
557
|
+
end
|
|
558
|
+
|
|
559
|
+
def walk_pattern_symbol(sym, scope, outer_scope, mode)
|
|
560
|
+
return if sym.name == '_'
|
|
561
|
+
|
|
562
|
+
if mode == :match && (existing = outer_scope.lookup(sym.name))
|
|
563
|
+
add_reference(sym, outer_scope, existing)
|
|
564
|
+
else
|
|
565
|
+
bind_pattern(sym, scope, :case_pattern)
|
|
566
|
+
end
|
|
567
|
+
end
|
|
568
|
+
|
|
569
|
+
def walk_pattern_seq_item(item, scope, outer_scope, mode)
|
|
570
|
+
return if item.is_a?(Sym) && item.name == '&'
|
|
571
|
+
|
|
572
|
+
walk_pattern(item, scope, outer_scope, mode)
|
|
573
|
+
end
|
|
574
|
+
|
|
575
|
+
def walk_pattern_list(list, scope, outer_scope, mode)
|
|
576
|
+
head = list.head
|
|
577
|
+
if head.is_a?(Sym) && head.name == 'where'
|
|
578
|
+
inner = list.items[1]
|
|
579
|
+
guards = list.items[2..]
|
|
580
|
+
walk_pattern(inner, scope, outer_scope, mode)
|
|
581
|
+
guards&.each { |g| walk_form(g, scope) }
|
|
582
|
+
elsif head.is_a?(Sym) && head.name == 'or'
|
|
583
|
+
list.items[1..]&.each { |alt| walk_pattern(alt, scope, outer_scope, mode) }
|
|
584
|
+
elsif head.is_a?(Sym) && head.name == '=' && list.items.length == 2
|
|
585
|
+
name_sym = list.items[1]
|
|
586
|
+
if name_sym.is_a?(Sym) && (existing = outer_scope.lookup(name_sym.name))
|
|
587
|
+
add_reference(name_sym, outer_scope, existing)
|
|
588
|
+
end
|
|
589
|
+
else
|
|
590
|
+
list.items.each { |item| walk_pattern(item, scope, outer_scope, mode) }
|
|
591
|
+
end
|
|
592
|
+
end
|
|
593
|
+
|
|
594
|
+
def add_binding(sym, scope, kind, lexical: true)
|
|
595
|
+
return unless sym.is_a?(Sym)
|
|
596
|
+
|
|
597
|
+
b = Binding.new(
|
|
598
|
+
kind:,
|
|
599
|
+
name: sym.name,
|
|
600
|
+
line: sym.line,
|
|
601
|
+
column: sym.column,
|
|
602
|
+
end_column: sym.column + sym.name.length,
|
|
603
|
+
scope:,
|
|
604
|
+
segments: sym.dotted? ? sym.segments : nil,
|
|
605
|
+
sym:,
|
|
606
|
+
in_module_or_class: @in_module_or_class.positive?
|
|
607
|
+
)
|
|
608
|
+
@bindings << b
|
|
609
|
+
scope.bindings[sym.name] = b if lexical
|
|
610
|
+
b
|
|
611
|
+
end
|
|
612
|
+
|
|
613
|
+
def add_constant_binding(sym, scope, kind)
|
|
614
|
+
b = Binding.new(
|
|
615
|
+
kind:,
|
|
616
|
+
name: sym.name,
|
|
617
|
+
line: sym.line,
|
|
618
|
+
column: sym.column,
|
|
619
|
+
end_column: sym.column + sym.name.length,
|
|
620
|
+
scope:,
|
|
621
|
+
segments: sym.segments,
|
|
622
|
+
sym:,
|
|
623
|
+
in_module_or_class: @in_module_or_class.positive?
|
|
624
|
+
)
|
|
625
|
+
@bindings << b
|
|
626
|
+
# Constants stay out of scope.bindings: they resolve workspace-wide, not lexically.
|
|
627
|
+
b
|
|
628
|
+
end
|
|
629
|
+
|
|
630
|
+
def add_reference(sym, scope, target)
|
|
631
|
+
@references << Reference.new(
|
|
632
|
+
name: sym.name,
|
|
633
|
+
line: sym.line,
|
|
634
|
+
column: sym.column,
|
|
635
|
+
end_column: sym.column + sym.name.length,
|
|
636
|
+
scope:,
|
|
637
|
+
sym:,
|
|
638
|
+
target:
|
|
639
|
+
)
|
|
640
|
+
end
|
|
641
|
+
end
|
|
642
|
+
end
|
|
643
|
+
end
|