kapusta 0.12.0 → 0.13.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.
- checksums.yaml +4 -4
- data/README.md +1 -0
- data/examples/bst-iterator.kap +2 -2
- data/examples/zoo-animal-1.kap +1 -1
- data/examples/zoo-animal-inheritance-2.kap +1 -1
- data/lib/kapusta/compiler/emitter/bindings.rb +39 -11
- data/lib/kapusta/compiler/emitter/collections.rb +4 -4
- data/lib/kapusta/compiler/emitter/control_flow.rb +8 -8
- data/lib/kapusta/compiler/emitter/interop.rb +20 -6
- data/lib/kapusta/compiler/emitter/support.rb +91 -11
- data/lib/kapusta/compiler/emitter.rb +1 -0
- data/lib/kapusta/env.rb +1 -0
- data/lib/kapusta/lsp/definition.rb +1 -1
- data/lib/kapusta/lsp/rename.rb +19 -3
- data/lib/kapusta/lsp/scope_walker.rb +19 -5
- data/lib/kapusta/version.rb +1 -1
- data/spec/lsp_spec.rb +55 -2
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 9324827934a0647689027ecf5fc6bf83708fe536cd904dc04d1f9c0594c37581
|
|
4
|
+
data.tar.gz: 736a8f7d8158fc4502415168f5791d7af8b98f392800255576c1d0182ef920c6
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 62e4118362c7fbe7ae034470a01ddf37a0bb826fb142e2611d72b09ded1777e52bf2b078cf48c0283dda6f0a8c5989ec31f60d0913cf16ec3ac9ccabf2c40626
|
|
7
|
+
data.tar.gz: 5fbd5b3a7809eff626c43203b1c36761efac10cb5663ca345ace26fbea731da63402909be6a04f22aedaca80945e1db3456209a6ac335b8fc4a2152bcc198395
|
data/README.md
CHANGED
data/examples/bst-iterator.kap
CHANGED
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
|
|
16
16
|
(fn initialize [root]
|
|
17
17
|
(set @stack [])
|
|
18
|
-
(
|
|
18
|
+
(push-left root))
|
|
19
19
|
|
|
20
20
|
(fn push-left [node]
|
|
21
21
|
(var n node)
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
(fn next []
|
|
28
28
|
(let [stack @stack
|
|
29
29
|
node (stack.pop)]
|
|
30
|
-
(
|
|
30
|
+
(push-left (node.right))
|
|
31
31
|
(node.val)))
|
|
32
32
|
|
|
33
33
|
(fn has-next? []
|
data/examples/zoo-animal-1.kap
CHANGED
|
@@ -18,11 +18,11 @@ module Kapusta
|
|
|
18
18
|
fn_env = env.child
|
|
19
19
|
ruby_name = define_local(fn_env, name_sym.name)
|
|
20
20
|
<<~RUBY.chomp
|
|
21
|
-
|
|
21
|
+
lambda do
|
|
22
22
|
#{ruby_name} = nil
|
|
23
23
|
#{ruby_name} = #{emit_lambda(pattern, body, fn_env, current_scope)}
|
|
24
24
|
#{ruby_name}
|
|
25
|
-
end
|
|
25
|
+
end.call
|
|
26
26
|
RUBY
|
|
27
27
|
end
|
|
28
28
|
end
|
|
@@ -81,9 +81,21 @@ module Kapusta
|
|
|
81
81
|
end
|
|
82
82
|
|
|
83
83
|
def emit_definition_form(form, env, current_scope)
|
|
84
|
-
return
|
|
84
|
+
return emit_toplevel_method_definition(form, env) if current_scope == :toplevel
|
|
85
85
|
|
|
86
|
-
|
|
86
|
+
code = emit_method_definition(form, env)
|
|
87
|
+
register_self_method_binding(form, env)
|
|
88
|
+
[code, env]
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def register_self_method_binding(form, env)
|
|
92
|
+
name_sym = form.items[1]
|
|
93
|
+
return unless name_sym.is_a?(Sym) && !name_sym.dotted?
|
|
94
|
+
|
|
95
|
+
ruby_name = Kapusta.kebab_to_snake(name_sym.name)
|
|
96
|
+
return unless direct_method_name?(ruby_name)
|
|
97
|
+
|
|
98
|
+
env.define(name_sym.name, Env::SelfMethodBinding.new(ruby_name))
|
|
87
99
|
end
|
|
88
100
|
|
|
89
101
|
def emit_toplevel_method_definition(form, env)
|
|
@@ -234,7 +246,7 @@ module Kapusta
|
|
|
234
246
|
|
|
235
247
|
binding = env.lookup_if_defined(name)
|
|
236
248
|
return false if binding.nil?
|
|
237
|
-
return false if
|
|
249
|
+
return false if callable_method_binding?(binding)
|
|
238
250
|
return false if constant_binding?(binding)
|
|
239
251
|
|
|
240
252
|
true
|
|
@@ -247,9 +259,9 @@ module Kapusta
|
|
|
247
259
|
def emit_let(args, env, current_scope)
|
|
248
260
|
binding_code, body_code = emit_let_parts(args, env, current_scope, result: true)
|
|
249
261
|
[
|
|
250
|
-
'
|
|
262
|
+
'lambda do',
|
|
251
263
|
indent(join_code(binding_code, body_code)),
|
|
252
|
-
'end
|
|
264
|
+
'end.call'
|
|
253
265
|
].join("\n")
|
|
254
266
|
end
|
|
255
267
|
|
|
@@ -281,7 +293,23 @@ module Kapusta
|
|
|
281
293
|
body_code, = emit_sequence(body, child_env, current_scope,
|
|
282
294
|
allow_method_definitions: false,
|
|
283
295
|
result:)
|
|
284
|
-
[binding_codes
|
|
296
|
+
[join_binding_codes(binding_codes), body_code]
|
|
297
|
+
end
|
|
298
|
+
|
|
299
|
+
def join_binding_codes(binding_codes)
|
|
300
|
+
codes = binding_codes.reject(&:empty?)
|
|
301
|
+
return '' if codes.empty?
|
|
302
|
+
|
|
303
|
+
result = codes.first.dup
|
|
304
|
+
codes.each_cons(2) do |prev, curr|
|
|
305
|
+
separator = block_binding?(prev) || block_binding?(curr) ? "\n\n" : "\n"
|
|
306
|
+
result << separator << curr
|
|
307
|
+
end
|
|
308
|
+
result
|
|
309
|
+
end
|
|
310
|
+
|
|
311
|
+
def block_binding?(code)
|
|
312
|
+
code.match?(/\bdo\b/) && code.match?(/\bend\b/)
|
|
285
313
|
end
|
|
286
314
|
|
|
287
315
|
def join_code(*chunks)
|
|
@@ -364,7 +392,7 @@ module Kapusta
|
|
|
364
392
|
|
|
365
393
|
def emit_local_expr(args, env, current_scope)
|
|
366
394
|
code, = emit_local_form(List.new([Sym.new('local'), *args]), env.child, current_scope)
|
|
367
|
-
"
|
|
395
|
+
"lambda do\n#{indent(code)}\nend.call"
|
|
368
396
|
end
|
|
369
397
|
|
|
370
398
|
def emit_global_expr(args, _env, _current_scope)
|
|
@@ -385,7 +413,7 @@ module Kapusta
|
|
|
385
413
|
binding = env.lookup_if_defined(target.name)
|
|
386
414
|
ruby_name =
|
|
387
415
|
if binding
|
|
388
|
-
emit_error!(:cannot_set_method_binding, name: target.name) if
|
|
416
|
+
emit_error!(:cannot_set_method_binding, name: target.name) if callable_method_binding?(binding)
|
|
389
417
|
|
|
390
418
|
binding
|
|
391
419
|
else
|
|
@@ -429,7 +457,7 @@ module Kapusta
|
|
|
429
457
|
end
|
|
430
458
|
else
|
|
431
459
|
binding = env.lookup(target.name)
|
|
432
|
-
emit_error!(:cannot_set_method_binding, name: target.name) if
|
|
460
|
+
emit_error!(:cannot_set_method_binding, name: target.name) if callable_method_binding?(binding)
|
|
433
461
|
emit_error!(:expected_var, name: target.name) unless mutable_binding?(env, target.name)
|
|
434
462
|
|
|
435
463
|
emit_assignment(binding, value_code)
|
|
@@ -89,11 +89,11 @@ module Kapusta
|
|
|
89
89
|
emit_sequence_value_assignment(acc_var, body_code)
|
|
90
90
|
end
|
|
91
91
|
[
|
|
92
|
-
'
|
|
92
|
+
'lambda do',
|
|
93
93
|
indent("#{acc_var} = #{init_code}"),
|
|
94
94
|
indent(iter_code),
|
|
95
95
|
indent(acc_var),
|
|
96
|
-
'end
|
|
96
|
+
'end.call'
|
|
97
97
|
].join("\n")
|
|
98
98
|
end
|
|
99
99
|
|
|
@@ -221,11 +221,11 @@ module Kapusta
|
|
|
221
221
|
|
|
222
222
|
def emit_collection_result(result_var, initial_code, iter_code)
|
|
223
223
|
[
|
|
224
|
-
'
|
|
224
|
+
'lambda do',
|
|
225
225
|
indent("#{result_var} = #{initial_code}"),
|
|
226
226
|
indent(iter_code),
|
|
227
227
|
indent(result_var),
|
|
228
|
-
'end
|
|
228
|
+
'end.call'
|
|
229
229
|
].join("\n")
|
|
230
230
|
end
|
|
231
231
|
|
|
@@ -61,10 +61,10 @@ module Kapusta
|
|
|
61
61
|
return body unless value_var
|
|
62
62
|
|
|
63
63
|
[
|
|
64
|
-
'
|
|
64
|
+
'lambda do',
|
|
65
65
|
indent("#{value_var} = #{value_code}"),
|
|
66
66
|
indent(body),
|
|
67
|
-
'end
|
|
67
|
+
'end.call'
|
|
68
68
|
].join("\n")
|
|
69
69
|
end
|
|
70
70
|
|
|
@@ -212,30 +212,30 @@ module Kapusta
|
|
|
212
212
|
|
|
213
213
|
def emit_while(args, env, current_scope)
|
|
214
214
|
<<~RUBY.chomp
|
|
215
|
-
|
|
215
|
+
lambda do
|
|
216
216
|
#{indent(emit_while_statement(args, env, current_scope))}
|
|
217
217
|
nil
|
|
218
|
-
end
|
|
218
|
+
end.call
|
|
219
219
|
RUBY
|
|
220
220
|
end
|
|
221
221
|
|
|
222
222
|
def emit_for(args, env, current_scope)
|
|
223
223
|
loop_code = emit_for_statement(args, env, current_scope)
|
|
224
224
|
<<~RUBY.chomp
|
|
225
|
-
|
|
225
|
+
lambda do
|
|
226
226
|
#{indent(loop_code)}
|
|
227
227
|
nil
|
|
228
|
-
end
|
|
228
|
+
end.call
|
|
229
229
|
RUBY
|
|
230
230
|
end
|
|
231
231
|
|
|
232
232
|
def emit_each(args, env, current_scope)
|
|
233
233
|
iter_code = emit_each_statement(args, env, current_scope)
|
|
234
234
|
<<~RUBY.chomp
|
|
235
|
-
|
|
235
|
+
lambda do
|
|
236
236
|
#{iter_code}
|
|
237
237
|
nil
|
|
238
|
-
end
|
|
238
|
+
end.call
|
|
239
239
|
RUBY
|
|
240
240
|
end
|
|
241
241
|
|
|
@@ -35,7 +35,7 @@ module Kapusta
|
|
|
35
35
|
if literal_name && binary_operator_call?(literal_name.to_s, positional, kwargs, block_form)
|
|
36
36
|
return emit_binary_operator_call(receiver, literal_name.to_s, positional[0])
|
|
37
37
|
end
|
|
38
|
-
if literal_name &&
|
|
38
|
+
if literal_name && colon_call_direct_method_name?(literal_name.to_s)
|
|
39
39
|
return emit_direct_method_call(receiver, Kapusta.kebab_to_snake(literal_name.to_s),
|
|
40
40
|
positional, kwargs, block_form, env, current_scope)
|
|
41
41
|
end
|
|
@@ -80,13 +80,17 @@ module Kapusta
|
|
|
80
80
|
end
|
|
81
81
|
|
|
82
82
|
def emit_module_expr(args, env)
|
|
83
|
-
body =
|
|
83
|
+
body = with_class_body do
|
|
84
|
+
emit_sequence(args[1..], env.child, :module, allow_method_definitions: true, result: false).first
|
|
85
|
+
end
|
|
84
86
|
emit_module_wrapper(args[0], body)
|
|
85
87
|
end
|
|
86
88
|
|
|
87
89
|
def emit_class_expr(args, env)
|
|
88
90
|
name_sym, supers, body_forms = split_class_args(args)
|
|
89
|
-
body =
|
|
91
|
+
body = with_class_body do
|
|
92
|
+
emit_sequence(body_forms, env.child, :class, allow_method_definitions: true, result: false).first
|
|
93
|
+
end
|
|
90
94
|
emit_class_wrapper(name_sym, supers, env,
|
|
91
95
|
body)
|
|
92
96
|
end
|
|
@@ -95,7 +99,7 @@ module Kapusta
|
|
|
95
99
|
segments = constant_segments(name_sym)
|
|
96
100
|
emit_error!(:invalid_module_name, name: name_sym.name) unless segments
|
|
97
101
|
inner = build_nested_module(segments, body)
|
|
98
|
-
['
|
|
102
|
+
['lambda do', indent(inner), indent(segments.join('::')), 'end.call'].join("\n")
|
|
99
103
|
end
|
|
100
104
|
|
|
101
105
|
def emit_direct_module_header(name_sym, body)
|
|
@@ -110,7 +114,7 @@ module Kapusta
|
|
|
110
114
|
emit_error!(:invalid_class_name, name: name_sym.name) unless segments
|
|
111
115
|
super_code = class_super_code(supers, env)
|
|
112
116
|
inner = build_nested_class(segments, super_code, body)
|
|
113
|
-
['
|
|
117
|
+
['lambda do', indent(inner), indent(segments.join('::')), 'end.call'].join("\n")
|
|
114
118
|
end
|
|
115
119
|
|
|
116
120
|
def emit_direct_class_header(name_sym, supers, body, env)
|
|
@@ -298,7 +302,7 @@ module Kapusta
|
|
|
298
302
|
end
|
|
299
303
|
|
|
300
304
|
def emit_bound_call(binding, args, env, current_scope)
|
|
301
|
-
return emit_self_method_binding_call(binding, args, env, current_scope) if
|
|
305
|
+
return emit_self_method_binding_call(binding, args, env, current_scope) if callable_method_binding?(binding)
|
|
302
306
|
|
|
303
307
|
emit_error!(:cannot_call_constant, name: binding) if constant_binding?(binding)
|
|
304
308
|
|
|
@@ -438,10 +442,15 @@ module Kapusta
|
|
|
438
442
|
return emit_multisym_value(sym, env) if sym.dotted?
|
|
439
443
|
return 'ARGV' if name == 'ARGV'
|
|
440
444
|
return name if name.match?(/\A[A-Z]/)
|
|
445
|
+
return Kapusta.kebab_to_snake(name) if implicit_self_method?(name)
|
|
441
446
|
|
|
442
447
|
emit_error!(:undefined_symbol, name:)
|
|
443
448
|
end
|
|
444
449
|
|
|
450
|
+
def implicit_self_method?(name)
|
|
451
|
+
in_class_body? && direct_method_name?(Kapusta.kebab_to_snake(name))
|
|
452
|
+
end
|
|
453
|
+
|
|
445
454
|
def emit_gvar(sym)
|
|
446
455
|
"$#{global_name(sym.name)}"
|
|
447
456
|
end
|
|
@@ -479,6 +488,10 @@ module Kapusta
|
|
|
479
488
|
Kapusta.kebab_to_snake(name).match?(/\A[a-z_]\w*[!?=]?\z/)
|
|
480
489
|
end
|
|
481
490
|
|
|
491
|
+
def colon_call_direct_method_name?(name)
|
|
492
|
+
Kapusta.kebab_to_snake(name).match?(/\A[a-zA-Z_]\w*[!?=]?\z/)
|
|
493
|
+
end
|
|
494
|
+
|
|
482
495
|
def global_name(name)
|
|
483
496
|
Kapusta.kebab_to_snake(name).gsub(/[^a-zA-Z0-9_]/, '_')
|
|
484
497
|
end
|
|
@@ -492,6 +505,7 @@ module Kapusta
|
|
|
492
505
|
/\A-?\d+(?:\.\d+)?\z/, # number
|
|
493
506
|
/\A[a-z_]\w*(?:\.[a-z_]\w*[!?=]?(?:\([^()\n]*\))?|\[[^\[\]]*\])+\z/, # local + .m/[k] chain
|
|
494
507
|
/\A[A-Z]\w*(?:::[A-Z]\w*)*(?:\.[a-z_]\w*[!?=]?(?:\([^()\n]*\))?|\[[^\[\]]*\])+\z/, # const + chain
|
|
508
|
+
/\A\([^()\n]*\)(?:\.[a-zA-Z_]\w*[!?=]?(?:\([^()\n]*\))?|\[[^\[\]]*\])+\z/, # (expr).chain
|
|
495
509
|
/\A:[a-zA-Z_]\w*[!?=]?\z/, # :symbol
|
|
496
510
|
/\A"(?:[^"\\]|\\.)*"\z/, # "string"
|
|
497
511
|
/\A'(?:[^'\\]|\\.)*'\z/, # 'string'
|
|
@@ -41,29 +41,29 @@ module Kapusta
|
|
|
41
41
|
|
|
42
42
|
def emit_form_run(forms, start, env, current_scope, header_form: nil)
|
|
43
43
|
i = start
|
|
44
|
-
|
|
44
|
+
entries = []
|
|
45
45
|
while i < forms.length
|
|
46
46
|
form = forms[i]
|
|
47
47
|
if end_form?(form)
|
|
48
48
|
validate_end_form!(form)
|
|
49
49
|
with_current_form(form) { emit_error!(:end_outside_header) } unless header_form
|
|
50
|
-
return [
|
|
50
|
+
return [join_definition_aware(entries, current_scope), i + 1]
|
|
51
51
|
end
|
|
52
52
|
|
|
53
53
|
if bodyless_header?(form)
|
|
54
54
|
header_code, i = emit_bodyless_header(form, forms, i + 1, env)
|
|
55
|
-
|
|
55
|
+
entries << [form, header_code]
|
|
56
56
|
next
|
|
57
57
|
end
|
|
58
58
|
|
|
59
59
|
code, env = emit_form_in_sequence(form, env, current_scope,
|
|
60
60
|
allow_method_definitions: true,
|
|
61
61
|
result_needed: false)
|
|
62
|
-
|
|
62
|
+
entries << [form, code]
|
|
63
63
|
i += 1
|
|
64
64
|
end
|
|
65
65
|
with_current_form(header_form) { emit_error!(:unclosed_header) } if header_form
|
|
66
|
-
[
|
|
66
|
+
[join_definition_aware(entries, current_scope), i]
|
|
67
67
|
end
|
|
68
68
|
|
|
69
69
|
def end_form?(form)
|
|
@@ -109,12 +109,16 @@ module Kapusta
|
|
|
109
109
|
inner_code, next_i = emit_bodyless_header(inner[0], forms, body_start, env)
|
|
110
110
|
[emit_direct_module_header(name_sym, inner_code) || emit_module_wrapper(name_sym, inner_code), next_i]
|
|
111
111
|
else
|
|
112
|
-
body, next_i =
|
|
112
|
+
body, next_i = with_class_body do
|
|
113
|
+
emit_form_run(forms, body_start, env.child, :module, header_form: form)
|
|
114
|
+
end
|
|
113
115
|
[emit_direct_module_header(name_sym, body) || emit_module_wrapper(name_sym, body), next_i]
|
|
114
116
|
end
|
|
115
117
|
else
|
|
116
118
|
name_sym, supers, = split_class_args(form.items[1..])
|
|
117
|
-
body, next_i =
|
|
119
|
+
body, next_i = with_class_body do
|
|
120
|
+
emit_form_run(forms, body_start, env.child, :class, header_form: form)
|
|
121
|
+
end
|
|
118
122
|
code = emit_direct_class_header(name_sym, supers, body, env) ||
|
|
119
123
|
emit_class_wrapper(name_sym, supers, env, body)
|
|
120
124
|
[code, next_i]
|
|
@@ -154,11 +158,38 @@ module Kapusta
|
|
|
154
158
|
emit_sequence_statement_form(form, env, current_scope, result_needed:)
|
|
155
159
|
elsif set_new_local_form?(form, env)
|
|
156
160
|
emit_set_form(form, env, current_scope)
|
|
161
|
+
elsif !result_needed && class_or_module_form?(form)
|
|
162
|
+
[emit_class_or_module_statement(form, env), env]
|
|
157
163
|
else
|
|
158
164
|
[emit_expr(form, env, current_scope), env]
|
|
159
165
|
end
|
|
160
166
|
end
|
|
161
167
|
|
|
168
|
+
def class_or_module_form?(form)
|
|
169
|
+
form.is_a?(List) && form.head.is_a?(Sym) &&
|
|
170
|
+
%w[class module].include?(form.head.name)
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
def emit_class_or_module_statement(form, env)
|
|
174
|
+
args = form.rest
|
|
175
|
+
if form.head.name == 'module'
|
|
176
|
+
name_sym = args[0]
|
|
177
|
+
body = with_class_body do
|
|
178
|
+
emit_sequence(args[1..], env.child, :module, allow_method_definitions: true,
|
|
179
|
+
result: false).first
|
|
180
|
+
end
|
|
181
|
+
emit_direct_module_header(name_sym, body) || emit_module_wrapper(name_sym, body)
|
|
182
|
+
else
|
|
183
|
+
name_sym, supers, body_forms = split_class_args(args)
|
|
184
|
+
body = with_class_body do
|
|
185
|
+
emit_sequence(body_forms, env.child, :class, allow_method_definitions: true,
|
|
186
|
+
result: false).first
|
|
187
|
+
end
|
|
188
|
+
emit_direct_class_header(name_sym, supers, body, env) ||
|
|
189
|
+
emit_class_wrapper(name_sym, supers, env, body)
|
|
190
|
+
end
|
|
191
|
+
end
|
|
192
|
+
|
|
162
193
|
def emit_do_form(forms, env, current_scope, result_needed: true)
|
|
163
194
|
body, new_env = emit_sequence(forms, env, current_scope,
|
|
164
195
|
allow_method_definitions: false,
|
|
@@ -170,14 +201,41 @@ module Kapusta
|
|
|
170
201
|
|
|
171
202
|
def emit_sequence(forms, env, current_scope, allow_method_definitions:, result: true)
|
|
172
203
|
current_env = env
|
|
173
|
-
|
|
204
|
+
entries = []
|
|
174
205
|
forms.each_with_index do |form, index|
|
|
175
206
|
code, current_env = emit_form_in_sequence(form, current_env, current_scope,
|
|
176
207
|
allow_method_definitions:,
|
|
177
208
|
result_needed: result && index == forms.length - 1)
|
|
178
|
-
|
|
209
|
+
entries << [form, code] unless code.empty?
|
|
210
|
+
end
|
|
211
|
+
[join_definition_aware(entries, current_scope), current_env]
|
|
212
|
+
end
|
|
213
|
+
|
|
214
|
+
def join_definition_aware(entries, current_scope)
|
|
215
|
+
return '' if entries.empty?
|
|
216
|
+
|
|
217
|
+
result = +entries.first[1]
|
|
218
|
+
entries.each_cons(2) do |(prev_form, _), (curr_form, curr_code)|
|
|
219
|
+
sep = blank_between_definitions?(prev_form, curr_form, current_scope) ? "\n\n" : "\n"
|
|
220
|
+
result << sep << curr_code
|
|
221
|
+
end
|
|
222
|
+
result
|
|
223
|
+
end
|
|
224
|
+
|
|
225
|
+
def blank_between_definitions?(prev_form, curr_form, current_scope)
|
|
226
|
+
return false unless %i[toplevel module class].include?(current_scope)
|
|
227
|
+
|
|
228
|
+
definition_form?(prev_form) || definition_form?(curr_form)
|
|
229
|
+
end
|
|
230
|
+
|
|
231
|
+
def definition_form?(form)
|
|
232
|
+
return false unless form.is_a?(List) && !form.empty? && form.head.is_a?(Sym)
|
|
233
|
+
|
|
234
|
+
case form.head.name
|
|
235
|
+
when 'defn', 'class', 'module' then true
|
|
236
|
+
when 'fn', 'lambda', 'λ' then form.items[1].is_a?(Sym)
|
|
237
|
+
else false
|
|
179
238
|
end
|
|
180
|
-
[codes.join("\n"), current_env]
|
|
181
239
|
end
|
|
182
240
|
|
|
183
241
|
def sequence_statement_form?(form)
|
|
@@ -313,8 +371,30 @@ module Kapusta
|
|
|
313
371
|
binding.is_a?(Env::MethodBinding)
|
|
314
372
|
end
|
|
315
373
|
|
|
374
|
+
def in_class_body?
|
|
375
|
+
@class_body_depth.positive?
|
|
376
|
+
end
|
|
377
|
+
|
|
378
|
+
def with_class_body
|
|
379
|
+
@class_body_depth += 1
|
|
380
|
+
yield
|
|
381
|
+
ensure
|
|
382
|
+
@class_body_depth -= 1
|
|
383
|
+
end
|
|
384
|
+
|
|
385
|
+
def self_method_binding?(binding)
|
|
386
|
+
binding.is_a?(Env::SelfMethodBinding)
|
|
387
|
+
end
|
|
388
|
+
|
|
389
|
+
def callable_method_binding?(binding)
|
|
390
|
+
method_binding?(binding) || self_method_binding?(binding)
|
|
391
|
+
end
|
|
392
|
+
|
|
316
393
|
def binding_value_code(binding)
|
|
317
|
-
|
|
394
|
+
return "method(#{binding.ruby_name.to_sym.inspect})" if method_binding?(binding)
|
|
395
|
+
return binding.ruby_name if self_method_binding?(binding)
|
|
396
|
+
|
|
397
|
+
binding
|
|
318
398
|
end
|
|
319
399
|
|
|
320
400
|
def parse_counted_for_bindings(bindings, env, current_scope)
|
data/lib/kapusta/env.rb
CHANGED
|
@@ -17,7 +17,7 @@ module Kapusta
|
|
|
17
17
|
return unless target
|
|
18
18
|
|
|
19
19
|
case target.kind
|
|
20
|
-
when :local, :toplevel_fn, :constant
|
|
20
|
+
when :local, :toplevel_fn, :constant, :method
|
|
21
21
|
location_for_binding(uri, target.binding) if target.binding
|
|
22
22
|
when :macro
|
|
23
23
|
locations_for_macro(uri, target.binding, workspace_index)
|
data/lib/kapusta/lsp/rename.rb
CHANGED
|
@@ -32,6 +32,8 @@ module Kapusta
|
|
|
32
32
|
case target.kind
|
|
33
33
|
when :local
|
|
34
34
|
rename_local(uri, target, new_name)
|
|
35
|
+
when :method
|
|
36
|
+
rename_method(uri, target, new_name)
|
|
35
37
|
when :toplevel_fn, :free_toplevel
|
|
36
38
|
return error('cross-file rename requires a workspace') unless workspace_index
|
|
37
39
|
|
|
@@ -128,8 +130,6 @@ module Kapusta
|
|
|
128
130
|
end
|
|
129
131
|
|
|
130
132
|
if binding
|
|
131
|
-
return if binding.kind == :method
|
|
132
|
-
|
|
133
133
|
return constant_target(walker, binding, seg) if %i[module class].include?(binding.kind)
|
|
134
134
|
|
|
135
135
|
return local_target(walker, binding, seg)
|
|
@@ -138,7 +138,6 @@ module Kapusta
|
|
|
138
138
|
if reference
|
|
139
139
|
target = reference.target
|
|
140
140
|
if target
|
|
141
|
-
return if target.kind == :method
|
|
142
141
|
return constant_target(walker, target, seg, sym:) if %i[module class].include?(target.kind)
|
|
143
142
|
|
|
144
143
|
return local_target(walker, target, seg, sym:)
|
|
@@ -164,6 +163,7 @@ module Kapusta
|
|
|
164
163
|
kind = case binding.kind
|
|
165
164
|
when :toplevel_fn then :toplevel_fn
|
|
166
165
|
when :macro, :macro_import then :macro
|
|
166
|
+
when :method then :method
|
|
167
167
|
else :local
|
|
168
168
|
end
|
|
169
169
|
Target.new(
|
|
@@ -207,6 +207,22 @@ module Kapusta
|
|
|
207
207
|
}
|
|
208
208
|
end
|
|
209
209
|
|
|
210
|
+
def rename_method(uri, target, new_name)
|
|
211
|
+
return error("invalid identifier: #{new_name}") unless Identifier.valid_local?(new_name)
|
|
212
|
+
return error('cannot resolve binding') unless target.binding
|
|
213
|
+
|
|
214
|
+
walker = target.walker
|
|
215
|
+
binding = target.binding
|
|
216
|
+
|
|
217
|
+
occs = [binding]
|
|
218
|
+
walker.references.each do |r|
|
|
219
|
+
occs << r if r.target.equal?(binding)
|
|
220
|
+
end
|
|
221
|
+
|
|
222
|
+
edits = occs.map { |o| text_edit_first_segment(o, new_name) }
|
|
223
|
+
{ changes: { uri => edits } }
|
|
224
|
+
end
|
|
225
|
+
|
|
210
226
|
def rename_local(uri, target, new_name)
|
|
211
227
|
return error("invalid identifier: #{new_name}") unless Identifier.valid_local?(new_name)
|
|
212
228
|
return error('cannot resolve binding') unless target.binding
|
|
@@ -76,6 +76,17 @@ module Kapusta
|
|
|
76
76
|
|
|
77
77
|
def walk_top(forms)
|
|
78
78
|
walk_form_run(forms, 0, @root_scope)
|
|
79
|
+
resolve_late_references
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def resolve_late_references
|
|
83
|
+
@references.each do |r|
|
|
84
|
+
next if r.target
|
|
85
|
+
next unless r.scope
|
|
86
|
+
|
|
87
|
+
target = r.scope.lookup(r.name)
|
|
88
|
+
r.target = target if target
|
|
89
|
+
end
|
|
79
90
|
end
|
|
80
91
|
|
|
81
92
|
def walk_form_run(forms, start, scope, header_target: nil)
|
|
@@ -164,7 +175,8 @@ module Kapusta
|
|
|
164
175
|
if body.length == 1 && bodyless_header?(body[0])
|
|
165
176
|
walk_bodyless_header(body[0], forms, body_start, scope)
|
|
166
177
|
else
|
|
167
|
-
|
|
178
|
+
body_scope = make_scope(scope, :module)
|
|
179
|
+
walk_form_run(forms, body_start, body_scope, header_target: binding)
|
|
168
180
|
end
|
|
169
181
|
end
|
|
170
182
|
when 'class'
|
|
@@ -172,7 +184,8 @@ module Kapusta
|
|
|
172
184
|
supers&.items&.each { |item| walk_form(item, scope) }
|
|
173
185
|
binding = name_sym.is_a?(Sym) ? add_constant_binding(name_sym, scope, :class) : nil
|
|
174
186
|
inside_class do
|
|
175
|
-
|
|
187
|
+
body_scope = make_scope(scope, :class)
|
|
188
|
+
walk_form_run(forms, body_start, body_scope, header_target: binding)
|
|
176
189
|
end
|
|
177
190
|
end
|
|
178
191
|
end
|
|
@@ -406,7 +419,7 @@ module Kapusta
|
|
|
406
419
|
else
|
|
407
420
|
(scope == @root_scope ? :toplevel_fn : :fn_local)
|
|
408
421
|
end
|
|
409
|
-
binding = add_binding(name_sym, scope, kind, lexical:
|
|
422
|
+
binding = add_binding(name_sym, scope, kind, lexical: true)
|
|
410
423
|
fn_scope.bindings[name_sym.name] = binding unless kind == :method
|
|
411
424
|
end
|
|
412
425
|
bind_param_vec(params, fn_scope)
|
|
@@ -558,10 +571,11 @@ module Kapusta
|
|
|
558
571
|
add_constant_binding(name_sym, scope, kind) if name_sym.is_a?(Sym)
|
|
559
572
|
|
|
560
573
|
body = list.items[body_start..] || []
|
|
574
|
+
body_scope = make_scope(scope, kind)
|
|
561
575
|
if kind == :class
|
|
562
|
-
inside_class { body.each { |form| walk_form(form,
|
|
576
|
+
inside_class { body.each { |form| walk_form(form, body_scope) } }
|
|
563
577
|
else
|
|
564
|
-
inside_module_or_class { body.each { |form| walk_form(form,
|
|
578
|
+
inside_module_or_class { body.each { |form| walk_form(form, body_scope) } }
|
|
565
579
|
end
|
|
566
580
|
end
|
|
567
581
|
|
data/lib/kapusta/version.rb
CHANGED
data/spec/lsp_spec.rb
CHANGED
|
@@ -234,7 +234,7 @@ RSpec.describe Kapusta::LSP do
|
|
|
234
234
|
end
|
|
235
235
|
end
|
|
236
236
|
|
|
237
|
-
it '
|
|
237
|
+
it 'offers rename for method definitions after a bodyless class header' do
|
|
238
238
|
text = "(class Counter)\n(fn initialize [start] start)\n"
|
|
239
239
|
responses = run(
|
|
240
240
|
frame_initialize,
|
|
@@ -242,7 +242,60 @@ RSpec.describe Kapusta::LSP do
|
|
|
242
242
|
frame_prepare_rename(uri: 'file:///x.kap', **cursor_at(text, 'initialize'))
|
|
243
243
|
)
|
|
244
244
|
|
|
245
|
-
expect(result_for(responses)['result']).to
|
|
245
|
+
expect(result_for(responses)['result']).to include('placeholder' => 'initialize')
|
|
246
|
+
end
|
|
247
|
+
|
|
248
|
+
it 'renames a class method and its bare-self call sites within a file' do
|
|
249
|
+
text = "(class Greeter)\n" \
|
|
250
|
+
"(fn initialize [name] (set @name name))\n" \
|
|
251
|
+
"(fn greeting [] (.. \"Hello, \" (label)))\n" \
|
|
252
|
+
"(fn label [] @name)\n"
|
|
253
|
+
responses = run(
|
|
254
|
+
frame_initialize,
|
|
255
|
+
frame_did_open('file:///x.kap', text),
|
|
256
|
+
frame_rename(uri: 'file:///x.kap', **cursor_at(text, 'label'), new_name: 'who')
|
|
257
|
+
)
|
|
258
|
+
edits = result_for(responses)['result']['documentChanges'].first['edits']
|
|
259
|
+
|
|
260
|
+
expect(edits.map { |e| e['newText'] }).to all(eq('who'))
|
|
261
|
+
expect(edits.map { |e| [e['range']['start']['line'], e['range']['start']['character']] })
|
|
262
|
+
.to contain_exactly([2, 31], [3, 4])
|
|
263
|
+
end
|
|
264
|
+
|
|
265
|
+
it 'jumps from a bare-self method call to the class method definition' do
|
|
266
|
+
text = "(class Greeter)\n" \
|
|
267
|
+
"(fn label [] 1)\n" \
|
|
268
|
+
"(fn greeting [] (label))\n"
|
|
269
|
+
responses = run(
|
|
270
|
+
frame_initialize,
|
|
271
|
+
frame_did_open('file:///x.kap', text),
|
|
272
|
+
frame_definition(uri: 'file:///x.kap', line: 2, character: 17)
|
|
273
|
+
)
|
|
274
|
+
result = result_for(responses)['result']
|
|
275
|
+
|
|
276
|
+
expect(result).to eq(
|
|
277
|
+
'uri' => 'file:///x.kap',
|
|
278
|
+
'range' => {
|
|
279
|
+
'start' => { 'line' => 1, 'character' => 4 },
|
|
280
|
+
'end' => { 'line' => 1, 'character' => 9 }
|
|
281
|
+
}
|
|
282
|
+
)
|
|
283
|
+
end
|
|
284
|
+
|
|
285
|
+
it 'renames a class method when the cursor is on a bare-self call site' do
|
|
286
|
+
text = "(class Greeter)\n" \
|
|
287
|
+
"(fn label [] 1)\n" \
|
|
288
|
+
"(fn greeting [] (label))\n"
|
|
289
|
+
responses = run(
|
|
290
|
+
frame_initialize,
|
|
291
|
+
frame_did_open('file:///x.kap', text),
|
|
292
|
+
frame_rename(uri: 'file:///x.kap', line: 2, character: 17, new_name: 'who')
|
|
293
|
+
)
|
|
294
|
+
edits = result_for(responses)['result']['documentChanges'].first['edits']
|
|
295
|
+
|
|
296
|
+
expect(edits.map { |e| e['newText'] }).to all(eq('who'))
|
|
297
|
+
expect(edits.map { |e| [e['range']['start']['line'], e['range']['start']['character']] })
|
|
298
|
+
.to contain_exactly([1, 4], [2, 17])
|
|
246
299
|
end
|
|
247
300
|
|
|
248
301
|
it 'does not let class methods shadow top-level function references' do
|