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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d5e4a7dc8e3d044580b58064b4632706c2115964847d674c23bbe39a625e0b6e
4
- data.tar.gz: b17a395b81ab568829022273e5df9ee5a186c052d6a1e4c843df631dfe1d6f76
3
+ metadata.gz: 9324827934a0647689027ecf5fc6bf83708fe536cd904dc04d1f9c0594c37581
4
+ data.tar.gz: 736a8f7d8158fc4502415168f5791d7af8b98f392800255576c1d0182ef920c6
5
5
  SHA512:
6
- metadata.gz: d3e8ca629130a695a67a1c210ab06869518e2d232084e01a8cb6d180094e2b10b0cbc3f65c191a9a7a6b1e70727f1563f28e3637264cc7776a58c2e16e554991
7
- data.tar.gz: 71695c0adf8e37705c9b9b85263e495578bcdd0d442c2d56ad09b1a6386640003164adf98d932c499472ae81d5c3d4a909898b907417e6d6463384b855b2fa33
6
+ metadata.gz: 62e4118362c7fbe7ae034470a01ddf37a0bb826fb142e2611d72b09ded1777e52bf2b078cf48c0283dda6f0a8c5989ec31f60d0913cf16ec3ac9ccabf2c40626
7
+ data.tar.gz: 5fbd5b3a7809eff626c43203b1c36761efac10cb5663ca345ace26fbea731da63402909be6a04f22aedaca80945e1db3456209a6ac335b8fc4a2152bcc198395
data/README.md CHANGED
@@ -87,6 +87,7 @@ def ack(m, n)
87
87
  ack(m - 1, ack(m, n - 1))
88
88
  end
89
89
  end
90
+
90
91
  p ack(2, 3)
91
92
  p ack(3, 3)
92
93
  ```
@@ -15,7 +15,7 @@
15
15
 
16
16
  (fn initialize [root]
17
17
  (set @stack [])
18
- (self.push-left root))
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
- (self.push-left (node.right))
30
+ (push-left (node.right))
31
31
  (node.val)))
32
32
 
33
33
  (fn has-next? []
@@ -2,4 +2,4 @@
2
2
  (fn initialize [name] (set (ivar name) name))
3
3
  (fn name [] (ivar name))
4
4
  (fn kingdom [] "animalia")
5
- (fn label [] (.. (self.name) " the animal")))
5
+ (fn label [] (.. name " the animal")))
@@ -1,7 +1,7 @@
1
1
  (require "./zoo-animal-1")
2
2
 
3
3
  (class Zoo.Dog [Zoo.Animal]
4
- (fn label [] (.. (self.name) " the dog"))
4
+ (fn label [] (.. name " the dog"))
5
5
  (fn bark [] "woof"))
6
6
 
7
7
  (let [dog (Zoo.Dog.new "Poppy")]
@@ -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
- (-> do
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).call
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 [emit_method_definition(form, env), env] unless current_scope == :toplevel
84
+ return emit_toplevel_method_definition(form, env) if current_scope == :toplevel
85
85
 
86
- emit_toplevel_method_definition(form, env)
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 method_binding?(binding)
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
- '(-> do',
262
+ 'lambda do',
251
263
  indent(join_code(binding_code, body_code)),
252
- 'end).call'
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.reject(&:empty?).join("\n"), body_code]
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
- "(-> do\n#{indent(code)}\nend).call"
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 method_binding?(binding)
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 method_binding?(binding)
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
- '(-> do',
92
+ 'lambda do',
93
93
  indent("#{acc_var} = #{init_code}"),
94
94
  indent(iter_code),
95
95
  indent(acc_var),
96
- 'end).call'
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
- '(-> do',
224
+ 'lambda do',
225
225
  indent("#{result_var} = #{initial_code}"),
226
226
  indent(iter_code),
227
227
  indent(result_var),
228
- 'end).call'
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
- '(-> do',
64
+ 'lambda do',
65
65
  indent("#{value_var} = #{value_code}"),
66
66
  indent(body),
67
- 'end).call'
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
- (-> do
215
+ lambda do
216
216
  #{indent(emit_while_statement(args, env, current_scope))}
217
217
  nil
218
- end).call
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
- (-> do
225
+ lambda do
226
226
  #{indent(loop_code)}
227
227
  nil
228
- end).call
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
- (-> do
235
+ lambda do
236
236
  #{iter_code}
237
237
  nil
238
- end).call
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 && direct_method_name?(literal_name.to_s)
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 = emit_sequence(args[1..], env, :module, allow_method_definitions: true, result: false).first
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 = emit_sequence(body_forms, env, :class, allow_method_definitions: true, result: false).first
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
- ['(-> do', indent(inner), indent(segments.join('::')), 'end).call'].join("\n")
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
- ['(-> do', indent(inner), indent(segments.join('::')), 'end).call'].join("\n")
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 method_binding?(binding)
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
- codes = []
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 [codes.join("\n"), i + 1]
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
- codes << header_code
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
- codes << code
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
- [codes.join("\n"), i]
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 = emit_form_run(forms, body_start, env, :module, header_form: form)
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 = emit_form_run(forms, body_start, env, :class, header_form: form)
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
- codes = []
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
- codes << code unless code.empty?
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
- method_binding?(binding) ? "method(#{binding.ruby_name.to_sym.inspect})" : binding
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)
@@ -28,6 +28,7 @@ module Kapusta
28
28
  @path = path
29
29
  @target = target
30
30
  @temp_index = 0
31
+ @class_body_depth = 0
31
32
  end
32
33
 
33
34
  def emit_file(forms)
data/lib/kapusta/env.rb CHANGED
@@ -3,6 +3,7 @@
3
3
  module Kapusta
4
4
  class Env
5
5
  MethodBinding = Struct.new(:ruby_name)
6
+ SelfMethodBinding = Struct.new(:ruby_name)
6
7
 
7
8
  def initialize(parent = nil)
8
9
  @parent = parent
@@ -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)
@@ -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
- walk_form_run(forms, body_start, scope, header_target: binding)
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
- walk_form_run(forms, body_start, scope, header_target: binding)
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: kind != :method)
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, scope) } }
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, scope) } }
578
+ inside_module_or_class { body.each { |form| walk_form(form, body_scope) } }
565
579
  end
566
580
  end
567
581
 
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Kapusta
4
- VERSION = '0.12.0'
4
+ VERSION = '0.13.1'
5
5
  end
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 'does not offer rename for method definitions after a bodyless class header' do
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 be_nil
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
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: kapusta
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.12.0
4
+ version: 0.13.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Evgenii Morozov