kapusta 0.11.2 → 0.13.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/README.md +2 -0
- data/examples/arrange-coins.kap +32 -0
- data/examples/array-sign.kap +24 -0
- data/examples/bst-iterator.kap +2 -2
- data/examples/case-vs-match.kap +23 -0
- data/examples/divisibility-stats.kap +16 -0
- data/examples/equal-sums.kap +7 -0
- data/examples/fennel-parity-examples.txt +9 -2
- data/examples/mruby-runtime-examples.txt +7 -3
- data/examples/nested-nil-pattern.kap +7 -0
- data/examples/non-constant-local.kap +11 -0
- data/examples/zoo-animal-1.kap +1 -1
- data/examples/zoo-animal-inheritance-2.kap +1 -1
- data/lib/kapusta/compiler/emitter/bindings.rb +28 -7
- data/lib/kapusta/compiler/emitter/collections.rb +17 -24
- data/lib/kapusta/compiler/emitter/interop.rb +12 -3
- data/lib/kapusta/compiler/emitter/support.rb +64 -11
- data/lib/kapusta/compiler/emitter.rb +1 -0
- data/lib/kapusta/compiler/normalizer.rb +4 -1
- data/lib/kapusta/env.rb +1 -0
- data/lib/kapusta/formatter.rb +49 -20
- 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/lsp.rb +62 -4
- data/lib/kapusta/version.rb +1 -1
- data/spec/examples_spec.rb +51 -9
- data/spec/lsp_spec.rb +55 -2
- metadata +8 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: b547075d7dcb73121e61dc17577cb8262265eaffb6ee0d74b88a74ce83c8e09a
|
|
4
|
+
data.tar.gz: 877f490eca3f0d4419e38b4619f77470aff18ddd8fede555f2963beb8db5994f
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: cddc220382f4c7f59788f4624f9e86f55bc58fe2b3fa6978f4e6d8bebaa870ff73be57786ecf2db94f14d6db460b3f485b6f82195e7fd21e255c7a7612ab50c2
|
|
7
|
+
data.tar.gz: 6c653e095b1c3d406f95e7f1659b615a2a59eaa21ef77e709cfc2dfc398d3526fb493f5054cb541acd36b203e690dbc28626e951e3e8f9d5e27366c0d5885834
|
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
|
```
|
|
@@ -103,6 +104,7 @@ Kapusta keeps most core Fennel forms. The main differences come from Ruby's runt
|
|
|
103
104
|
| `string.format`, `table.insert`, etc. | use Ruby methods and stdlib instead |
|
|
104
105
|
| `values` uses Lua multiple returns | `values` lowers to a Ruby array, usually destructured |
|
|
105
106
|
| `(print x)` is Lua's `print` (bare) | `(print x)` is Ruby's `p` (inspect-style) |
|
|
107
|
+
| `(.. "x: " nil)` errors at runtime | `(.. "x: " nil)` produces `"x: "` (Ruby `nil.to_s`) |
|
|
106
108
|
| `with-open`, `tail!` | not provided |
|
|
107
109
|
|
|
108
110
|
Kapusta-specific additions:
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
(fn arrange-coins [n]
|
|
2
|
+
(let [rows (faccumulate [acc {:sum 0 :rows 0} i 1 n]
|
|
3
|
+
(let [new-sum (+ (. acc :sum) i)]
|
|
4
|
+
(if (<= new-sum n)
|
|
5
|
+
{:sum new-sum :rows i}
|
|
6
|
+
acc)))
|
|
7
|
+
used (faccumulate [acc {:sum 0 :rows 0} i 1 n]
|
|
8
|
+
(let [new-sum (+ (. acc :sum) i)]
|
|
9
|
+
(if (<= new-sum n)
|
|
10
|
+
{:sum new-sum :rows i}
|
|
11
|
+
acc)))]
|
|
12
|
+
[(. rows :rows) (. used :sum)]))
|
|
13
|
+
|
|
14
|
+
(let [[r u] (arrange-coins 0)]
|
|
15
|
+
(print r)
|
|
16
|
+
(print u))
|
|
17
|
+
|
|
18
|
+
(let [[r u] (arrange-coins 1)]
|
|
19
|
+
(print r)
|
|
20
|
+
(print u))
|
|
21
|
+
|
|
22
|
+
(let [[r u] (arrange-coins 5)]
|
|
23
|
+
(print r)
|
|
24
|
+
(print u))
|
|
25
|
+
|
|
26
|
+
(let [[r u] (arrange-coins 8)]
|
|
27
|
+
(print r)
|
|
28
|
+
(print u))
|
|
29
|
+
|
|
30
|
+
(let [[r u] (arrange-coins 10)]
|
|
31
|
+
(print r)
|
|
32
|
+
(print u))
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
(fn sign [n]
|
|
2
|
+
(if (> n 0) 1
|
|
3
|
+
(< n 0) -1
|
|
4
|
+
0))
|
|
5
|
+
|
|
6
|
+
(fn array-sign [nums]
|
|
7
|
+
(accumulate [acc 1 _ n (ipairs nums)]
|
|
8
|
+
(* acc (sign n))))
|
|
9
|
+
|
|
10
|
+
(fn join [tbl sep]
|
|
11
|
+
(var s "")
|
|
12
|
+
(each [_ x (ipairs tbl)]
|
|
13
|
+
(if (= s "")
|
|
14
|
+
(set s (.. x))
|
|
15
|
+
(set s (.. s sep x))))
|
|
16
|
+
s)
|
|
17
|
+
|
|
18
|
+
(fn debug-sign [label nums]
|
|
19
|
+
(local pretty #(.. "[" (join $ ", ") "]"))
|
|
20
|
+
(.. "case[" label "] in " (pretty nums) " out " (array-sign nums)))
|
|
21
|
+
|
|
22
|
+
(print (debug-sign "mixed" [-1 -2 -3 -4 3 2 1]))
|
|
23
|
+
(print (debug-sign "withzero" [1 5 0 2 -3]))
|
|
24
|
+
(print (debug-sign "allneg" [-1 1 -1 1 -1]))
|
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? []
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
(fn debug-data [packet seq]
|
|
2
|
+
(local no-nil #(or $ "nil"))
|
|
3
|
+
(let [[_ packet-seq] packet]
|
|
4
|
+
(.. "packet[:ping, " (no-nil packet-seq) "] seq " (no-nil seq))))
|
|
5
|
+
|
|
6
|
+
(fn show-case [packet seq]
|
|
7
|
+
(case packet
|
|
8
|
+
[:ping seq] (debug-data packet seq)
|
|
9
|
+
_ "other"))
|
|
10
|
+
|
|
11
|
+
(fn show-match [packet seq]
|
|
12
|
+
(match packet
|
|
13
|
+
[:ping seq] (debug-data packet seq)
|
|
14
|
+
_ "other"))
|
|
15
|
+
|
|
16
|
+
(print (.. "case: " (show-case [:ping 42] 7)))
|
|
17
|
+
(print (.. "case: " (show-case [:ping 42] nil)))
|
|
18
|
+
(print (.. "case: " (show-case [:ping nil] nil)))
|
|
19
|
+
(print (.. "case: " (show-case [:ping 42] 42)))
|
|
20
|
+
(print (.. "match: " (show-match [:ping 42] 7)))
|
|
21
|
+
(print (.. "match: " (show-match [:ping 42] nil)))
|
|
22
|
+
(print (.. "match: " (show-match [:ping nil] nil)))
|
|
23
|
+
(print (.. "match: " (show-match [:ping 42] 42)))
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
(fn divisibility-stats [n]
|
|
2
|
+
(let [threes (faccumulate [acc 0 i 1 n]
|
|
3
|
+
(let [step (if (= 0 (% i 3)) 1 0)]
|
|
4
|
+
(+ acc step)))
|
|
5
|
+
fives (faccumulate [acc 0 i 1 n]
|
|
6
|
+
(let [step (if (= 0 (% i 5)) 1 0)]
|
|
7
|
+
(+ acc step)))]
|
|
8
|
+
[threes fives]))
|
|
9
|
+
|
|
10
|
+
(let [[t f] (divisibility-stats 30)]
|
|
11
|
+
(print t)
|
|
12
|
+
(print f))
|
|
13
|
+
|
|
14
|
+
(let [[t f] (divisibility-stats 100)]
|
|
15
|
+
(print t)
|
|
16
|
+
(print f))
|
|
@@ -1,11 +1,16 @@
|
|
|
1
1
|
ackermann
|
|
2
2
|
anonymous-greeter
|
|
3
|
+
array-sign
|
|
4
|
+
arrange-coins
|
|
5
|
+
case-vs-match
|
|
3
6
|
classify-wallet
|
|
4
7
|
climbing-stairs
|
|
5
8
|
convert-temperature
|
|
6
9
|
count-effects
|
|
7
10
|
describe
|
|
8
11
|
destructure
|
|
12
|
+
divisibility-stats
|
|
13
|
+
equal-sums
|
|
9
14
|
even-squares
|
|
10
15
|
factorial
|
|
11
16
|
falling-drops
|
|
@@ -15,9 +20,9 @@ gcd
|
|
|
15
20
|
hashfn
|
|
16
21
|
leap-year
|
|
17
22
|
macros-dbg
|
|
23
|
+
macros-import
|
|
18
24
|
macros-import-helpers
|
|
19
25
|
macros-import-whole
|
|
20
|
-
macros-import
|
|
21
26
|
macros-multi
|
|
22
27
|
macros-swap
|
|
23
28
|
macros-thrice-if
|
|
@@ -26,10 +31,12 @@ macros-when-let
|
|
|
26
31
|
match
|
|
27
32
|
max-achievable
|
|
28
33
|
min-max
|
|
34
|
+
nested-nil-pattern
|
|
35
|
+
non-constant-local
|
|
29
36
|
or-patterns
|
|
30
|
-
power-of-three
|
|
31
37
|
packet-router
|
|
32
38
|
points
|
|
39
|
+
power-of-three
|
|
33
40
|
primes
|
|
34
41
|
safe-lookup
|
|
35
42
|
shapes
|
|
@@ -3,6 +3,7 @@ accumulator
|
|
|
3
3
|
ackermann
|
|
4
4
|
anagram
|
|
5
5
|
anonymous-greeter
|
|
6
|
+
arrange-coins
|
|
6
7
|
bank-account
|
|
7
8
|
baseball-game
|
|
8
9
|
best-time-to-buy-sell-stock
|
|
@@ -11,6 +12,7 @@ binary-to-decimal
|
|
|
11
12
|
block-sort
|
|
12
13
|
bst-iterator
|
|
13
14
|
calc
|
|
15
|
+
case-vs-match
|
|
14
16
|
circle
|
|
15
17
|
classify-wallet
|
|
16
18
|
climbing-stairs
|
|
@@ -20,8 +22,9 @@ count-effects
|
|
|
20
22
|
counter
|
|
21
23
|
describe
|
|
22
24
|
destructure
|
|
23
|
-
|
|
25
|
+
divisibility-stats
|
|
24
26
|
doto
|
|
27
|
+
doto-hygiene
|
|
25
28
|
egg-count
|
|
26
29
|
even-squares
|
|
27
30
|
exceptions
|
|
@@ -38,9 +41,9 @@ kwargs
|
|
|
38
41
|
leap-year
|
|
39
42
|
length-of-last-word
|
|
40
43
|
macros-dbg
|
|
44
|
+
macros-import
|
|
41
45
|
macros-import-helpers
|
|
42
46
|
macros-import-whole
|
|
43
|
-
macros-import
|
|
44
47
|
macros-multi
|
|
45
48
|
macros-swap
|
|
46
49
|
macros-thrice-if
|
|
@@ -53,6 +56,7 @@ max-achievable
|
|
|
53
56
|
maximum-subarray
|
|
54
57
|
min-max
|
|
55
58
|
module-header
|
|
59
|
+
nested-nil-pattern
|
|
56
60
|
move-zeroes
|
|
57
61
|
number-of-1-bits
|
|
58
62
|
number-of-steps
|
|
@@ -84,8 +88,8 @@ thread-styles
|
|
|
84
88
|
threading
|
|
85
89
|
tic-tac-toe
|
|
86
90
|
tset
|
|
87
|
-
two-sum-hash
|
|
88
91
|
two-sum
|
|
92
|
+
two-sum-hash
|
|
89
93
|
ugly-number
|
|
90
94
|
underscore-patterns
|
|
91
95
|
valid-parentheses-1
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
(local hash-fn #(or $ "nil"))
|
|
2
|
+
(local regular-fn (fn [x] (+ x 1)))
|
|
3
|
+
(local lambda-fn (λ [x] (* x 2)))
|
|
4
|
+
(local vec-binding [10 20 30])
|
|
5
|
+
(local hash-binding {:a 1})
|
|
6
|
+
|
|
7
|
+
(print (hash-fn nil))
|
|
8
|
+
(print (regular-fn 5))
|
|
9
|
+
(print (lambda-fn 5))
|
|
10
|
+
(print (length vec-binding))
|
|
11
|
+
(print (. hash-binding :a))
|
data/examples/zoo-animal-1.kap
CHANGED
|
@@ -57,7 +57,7 @@ module Kapusta
|
|
|
57
57
|
|
|
58
58
|
def build_simple_block_parts(pattern, body, env, current_scope)
|
|
59
59
|
body_env = env.child
|
|
60
|
-
params = pattern.items.map { |sym| define_local(body_env, sym
|
|
60
|
+
params = pattern.items.map { |sym| define_local(body_env, sym, shadow: true) }
|
|
61
61
|
body_code, = emit_sequence(body, body_env, current_scope,
|
|
62
62
|
allow_method_definitions: false, result: false)
|
|
63
63
|
[params, body_code]
|
|
@@ -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
|
|
@@ -296,7 +308,9 @@ module Kapusta
|
|
|
296
308
|
|
|
297
309
|
if target.is_a?(Sym)
|
|
298
310
|
validate_binding_symbol!(target)
|
|
299
|
-
if allow_constant && form.head.name == 'local' &&
|
|
311
|
+
if allow_constant && form.head.name == 'local' &&
|
|
312
|
+
constant_value?(form.items[2]) &&
|
|
313
|
+
(constant_name = constant_name_for(target.name))
|
|
300
314
|
env.define(target.name, constant_name)
|
|
301
315
|
mark_mutability(env, target.name, mutable: false)
|
|
302
316
|
return ["#{constant_name} = #{value_code}\nnil", env]
|
|
@@ -316,6 +330,13 @@ module Kapusta
|
|
|
316
330
|
candidate if candidate.match?(/\A[A-Z][A-Z0-9_]*\z/)
|
|
317
331
|
end
|
|
318
332
|
|
|
333
|
+
def constant_value?(value_form)
|
|
334
|
+
case value_form
|
|
335
|
+
when Numeric, String, ::Symbol, true, false, nil then true
|
|
336
|
+
else false
|
|
337
|
+
end
|
|
338
|
+
end
|
|
339
|
+
|
|
319
340
|
def check_destructure_value!(pattern, value_form)
|
|
320
341
|
return unless pattern.is_a?(Vec) || pattern.is_a?(HashLit)
|
|
321
342
|
|
|
@@ -376,7 +397,7 @@ module Kapusta
|
|
|
376
397
|
binding = env.lookup_if_defined(target.name)
|
|
377
398
|
ruby_name =
|
|
378
399
|
if binding
|
|
379
|
-
emit_error!(:cannot_set_method_binding, name: target.name) if
|
|
400
|
+
emit_error!(:cannot_set_method_binding, name: target.name) if callable_method_binding?(binding)
|
|
380
401
|
|
|
381
402
|
binding
|
|
382
403
|
else
|
|
@@ -420,7 +441,7 @@ module Kapusta
|
|
|
420
441
|
end
|
|
421
442
|
else
|
|
422
443
|
binding = env.lookup(target.name)
|
|
423
|
-
emit_error!(:cannot_set_method_binding, name: target.name) if
|
|
444
|
+
emit_error!(:cannot_set_method_binding, name: target.name) if callable_method_binding?(binding)
|
|
424
445
|
emit_error!(:expected_var, name: target.name) unless mutable_binding?(env, target.name)
|
|
425
446
|
|
|
426
447
|
emit_assignment(binding, value_code)
|
|
@@ -111,30 +111,23 @@ module Kapusta
|
|
|
111
111
|
bindings = args[0].items
|
|
112
112
|
emit_error!(:accumulate_no_iterator) if bindings.length < 5
|
|
113
113
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
step_code
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
)
|
|
131
|
-
[
|
|
132
|
-
'(-> do',
|
|
133
|
-
indent("#{acc_var} = #{emit_expr(bindings[1], env, current_scope)}"),
|
|
134
|
-
indent(loop_code),
|
|
135
|
-
indent(acc_var),
|
|
136
|
-
'end).call'
|
|
137
|
-
].join("\n")
|
|
114
|
+
body_env = env.child
|
|
115
|
+
acc_var = define_local(body_env, bindings[0].name)
|
|
116
|
+
loop_var = define_local(body_env, bindings[2].name)
|
|
117
|
+
|
|
118
|
+
init_code = emit_expr(bindings[1], env, current_scope)
|
|
119
|
+
start_code = emit_expr(bindings[3], env, current_scope)
|
|
120
|
+
finish_code = emit_expr(bindings[4], env, current_scope)
|
|
121
|
+
step_code = bindings[5] ? emit_expr(bindings[5], env, current_scope) : nil
|
|
122
|
+
body_code, = emit_sequence(args[1..], body_env, current_scope, allow_method_definitions: false)
|
|
123
|
+
|
|
124
|
+
receiver =
|
|
125
|
+
if step_code
|
|
126
|
+
"#{parenthesize(start_code)}.step(#{finish_code}, #{step_code})"
|
|
127
|
+
else
|
|
128
|
+
"(#{start_code}..#{finish_code})"
|
|
129
|
+
end
|
|
130
|
+
inject_block(receiver, "#{acc_var}, #{loop_var}", init_code, '', body_code)
|
|
138
131
|
end
|
|
139
132
|
|
|
140
133
|
def emit_hashfn(args, env, current_scope)
|
|
@@ -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
|
|
@@ -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
|
|
@@ -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]
|
|
@@ -170,14 +174,41 @@ module Kapusta
|
|
|
170
174
|
|
|
171
175
|
def emit_sequence(forms, env, current_scope, allow_method_definitions:, result: true)
|
|
172
176
|
current_env = env
|
|
173
|
-
|
|
177
|
+
entries = []
|
|
174
178
|
forms.each_with_index do |form, index|
|
|
175
179
|
code, current_env = emit_form_in_sequence(form, current_env, current_scope,
|
|
176
180
|
allow_method_definitions:,
|
|
177
181
|
result_needed: result && index == forms.length - 1)
|
|
178
|
-
|
|
182
|
+
entries << [form, code] unless code.empty?
|
|
183
|
+
end
|
|
184
|
+
[join_definition_aware(entries, current_scope), current_env]
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
def join_definition_aware(entries, current_scope)
|
|
188
|
+
return '' if entries.empty?
|
|
189
|
+
|
|
190
|
+
result = +entries.first[1]
|
|
191
|
+
entries.each_cons(2) do |(prev_form, _), (curr_form, curr_code)|
|
|
192
|
+
sep = blank_between_definitions?(prev_form, curr_form, current_scope) ? "\n\n" : "\n"
|
|
193
|
+
result << sep << curr_code
|
|
194
|
+
end
|
|
195
|
+
result
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
def blank_between_definitions?(prev_form, curr_form, current_scope)
|
|
199
|
+
return false unless %i[toplevel module class].include?(current_scope)
|
|
200
|
+
|
|
201
|
+
definition_form?(prev_form) || definition_form?(curr_form)
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
def definition_form?(form)
|
|
205
|
+
return false unless form.is_a?(List) && !form.empty? && form.head.is_a?(Sym)
|
|
206
|
+
|
|
207
|
+
case form.head.name
|
|
208
|
+
when 'defn', 'class', 'module' then true
|
|
209
|
+
when 'fn', 'lambda', 'λ' then form.items[1].is_a?(Sym)
|
|
210
|
+
else false
|
|
179
211
|
end
|
|
180
|
-
[codes.join("\n"), current_env]
|
|
181
212
|
end
|
|
182
213
|
|
|
183
214
|
def sequence_statement_form?(form)
|
|
@@ -313,8 +344,30 @@ module Kapusta
|
|
|
313
344
|
binding.is_a?(Env::MethodBinding)
|
|
314
345
|
end
|
|
315
346
|
|
|
347
|
+
def in_class_body?
|
|
348
|
+
@class_body_depth.positive?
|
|
349
|
+
end
|
|
350
|
+
|
|
351
|
+
def with_class_body
|
|
352
|
+
@class_body_depth += 1
|
|
353
|
+
yield
|
|
354
|
+
ensure
|
|
355
|
+
@class_body_depth -= 1
|
|
356
|
+
end
|
|
357
|
+
|
|
358
|
+
def self_method_binding?(binding)
|
|
359
|
+
binding.is_a?(Env::SelfMethodBinding)
|
|
360
|
+
end
|
|
361
|
+
|
|
362
|
+
def callable_method_binding?(binding)
|
|
363
|
+
method_binding?(binding) || self_method_binding?(binding)
|
|
364
|
+
end
|
|
365
|
+
|
|
316
366
|
def binding_value_code(binding)
|
|
317
|
-
|
|
367
|
+
return "method(#{binding.ruby_name.to_sym.inspect})" if method_binding?(binding)
|
|
368
|
+
return binding.ruby_name if self_method_binding?(binding)
|
|
369
|
+
|
|
370
|
+
binding
|
|
318
371
|
end
|
|
319
372
|
|
|
320
373
|
def parse_counted_for_bindings(bindings, env, current_scope)
|
|
@@ -139,6 +139,8 @@ module Kapusta
|
|
|
139
139
|
|
|
140
140
|
def doto(forms)
|
|
141
141
|
value = forms.first
|
|
142
|
+
return value if forms[1..].empty?
|
|
143
|
+
|
|
142
144
|
temp = gensym('doto')
|
|
143
145
|
body = forms[1..].map do |form|
|
|
144
146
|
if form.is_a?(List)
|
|
@@ -147,7 +149,8 @@ module Kapusta
|
|
|
147
149
|
List.new([form, temp])
|
|
148
150
|
end
|
|
149
151
|
end
|
|
150
|
-
List.new([Sym.new('
|
|
152
|
+
fn = List.new([Sym.new('fn'), Vec.new([temp]), *body])
|
|
153
|
+
List.new([Sym.new(':'), value, :tap, fn])
|
|
151
154
|
end
|
|
152
155
|
|
|
153
156
|
def gensym(prefix)
|
data/lib/kapusta/env.rb
CHANGED
data/lib/kapusta/formatter.rb
CHANGED
|
@@ -555,30 +555,18 @@ module Kapusta
|
|
|
555
555
|
lines = [base]
|
|
556
556
|
args = list_raw_rest(list)
|
|
557
557
|
semantic_length = semantic_items(args).length
|
|
558
|
+
hang_subsequent_args = hang_call_args?(list, indent)
|
|
558
559
|
|
|
559
560
|
semantic_index = 0
|
|
561
|
+
hanging = nil
|
|
560
562
|
args.each do |arg|
|
|
561
563
|
if comment?(arg)
|
|
562
564
|
lines << indent_block(render(arg, indent + INDENT), INDENT)
|
|
563
565
|
next
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
arg,
|
|
569
|
-
indent + base.length + 1,
|
|
570
|
-
force_expand: semantic_length == 1 && fn_form?(arg)
|
|
571
|
-
)
|
|
572
|
-
first_line, *rest = first.lines(chomp: true)
|
|
573
|
-
candidate = "#{base} #{first_line}"
|
|
574
|
-
|
|
575
|
-
if lines.length == 1 && fits?(candidate, indent)
|
|
576
|
-
lines[0] = candidate
|
|
577
|
-
hanging = ' ' * (base.length + 1)
|
|
578
|
-
rest.each { |line| lines << "#{hanging}#{line}" }
|
|
579
|
-
else
|
|
580
|
-
lines << indent_block(first, INDENT)
|
|
581
|
-
end
|
|
566
|
+
elsif semantic_index.zero?
|
|
567
|
+
hanging = append_first_call_arg(lines, arg, base, indent, semantic_length)
|
|
568
|
+
elsif hanging && hang_subsequent_args
|
|
569
|
+
lines << prefix_continuation(hanging, render(arg, indent + hanging.length))
|
|
582
570
|
else
|
|
583
571
|
lines << indent_block(render(arg, indent + INDENT), INDENT)
|
|
584
572
|
end
|
|
@@ -589,6 +577,48 @@ module Kapusta
|
|
|
589
577
|
append_suffix(lines, ')')
|
|
590
578
|
end
|
|
591
579
|
|
|
580
|
+
def append_first_call_arg(lines, arg, base, indent, semantic_length)
|
|
581
|
+
first = render(
|
|
582
|
+
arg,
|
|
583
|
+
indent + base.length + 1,
|
|
584
|
+
force_expand: semantic_length == 1 && fn_form?(arg)
|
|
585
|
+
)
|
|
586
|
+
first_line, *rest = first.lines(chomp: true)
|
|
587
|
+
candidate = "#{base} #{first_line}"
|
|
588
|
+
|
|
589
|
+
unless lines.length == 1 && fits?(candidate, indent)
|
|
590
|
+
lines << indent_block(first, INDENT)
|
|
591
|
+
return
|
|
592
|
+
end
|
|
593
|
+
|
|
594
|
+
hanging = ' ' * (base.length + 1)
|
|
595
|
+
lines[0] = candidate
|
|
596
|
+
rest.each { |line| lines << "#{hanging}#{line}" }
|
|
597
|
+
hanging
|
|
598
|
+
end
|
|
599
|
+
|
|
600
|
+
def hang_call_args?(list, indent)
|
|
601
|
+
return false unless operator_call?(list)
|
|
602
|
+
|
|
603
|
+
flat = flat_call_render(list)
|
|
604
|
+
flat && !fits?(flat, indent)
|
|
605
|
+
end
|
|
606
|
+
|
|
607
|
+
def operator_call?(list)
|
|
608
|
+
head = list_head(list)
|
|
609
|
+
head.is_a?(Sym) && head.name.match?(/\A[^\w.]+\z/)
|
|
610
|
+
end
|
|
611
|
+
|
|
612
|
+
def flat_call_render(list)
|
|
613
|
+
head = flat_render(list_head(list))
|
|
614
|
+
return unless head
|
|
615
|
+
|
|
616
|
+
rendered_args = semantic_items(list_raw_rest(list)).map { |arg| flat_render(arg) }
|
|
617
|
+
return if rendered_args.any?(&:nil?)
|
|
618
|
+
|
|
619
|
+
"(#{[head, *rendered_args].join(' ')})"
|
|
620
|
+
end
|
|
621
|
+
|
|
592
622
|
def render_vec(vec, indent, layout: nil, top_level: false, force_expand: false)
|
|
593
623
|
flat = flat_render(vec)
|
|
594
624
|
return flat if !force_expand && flat && fits?(flat, indent) && allow_flat?(vec, top_level:, layout:)
|
|
@@ -800,8 +830,7 @@ module Kapusta
|
|
|
800
830
|
'fn', 'lambda', 'λ', 'macro'
|
|
801
831
|
true
|
|
802
832
|
else
|
|
803
|
-
|
|
804
|
-
flat && flat.length > 40
|
|
833
|
+
false
|
|
805
834
|
end
|
|
806
835
|
end
|
|
807
836
|
|
|
@@ -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/lsp.rb
CHANGED
|
@@ -15,8 +15,38 @@ module Kapusta
|
|
|
15
15
|
METHOD_NOT_FOUND = -32_601
|
|
16
16
|
FULL_SYNC = 1
|
|
17
17
|
|
|
18
|
+
def self.debug?
|
|
19
|
+
%w[1 true yes on].include?(ENV['KAPUSTA_LS_DEBUG'].to_s.downcase)
|
|
20
|
+
end
|
|
21
|
+
|
|
18
22
|
def self.start(input: $stdin, output: $stdout, log: $stderr)
|
|
19
|
-
new(input:, output:, log:)
|
|
23
|
+
server = new(input:, output:, log:)
|
|
24
|
+
install_signal_handlers(log)
|
|
25
|
+
server.run
|
|
26
|
+
debug_log(log, 'run returned, calling exit!(0)')
|
|
27
|
+
exit!(0)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def self.install_signal_handlers(log)
|
|
31
|
+
%w[TERM INT HUP].each do |sig|
|
|
32
|
+
Signal.trap(sig) do
|
|
33
|
+
debug_log(log, "signal #{sig} received, exiting")
|
|
34
|
+
exit!(0)
|
|
35
|
+
rescue StandardError
|
|
36
|
+
exit!(0)
|
|
37
|
+
end
|
|
38
|
+
rescue ArgumentError
|
|
39
|
+
nil
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def self.debug_log(log, message)
|
|
44
|
+
return unless debug?
|
|
45
|
+
|
|
46
|
+
log.write("kapusta-ls[debug pid=#{Process.pid}]: #{message}\n")
|
|
47
|
+
log.flush
|
|
48
|
+
rescue StandardError
|
|
49
|
+
nil
|
|
20
50
|
end
|
|
21
51
|
|
|
22
52
|
def self.uri_to_path(uri)
|
|
@@ -34,7 +64,7 @@ module Kapusta
|
|
|
34
64
|
@input = input.binmode
|
|
35
65
|
@output = output.binmode
|
|
36
66
|
@log = log
|
|
37
|
-
@debug =
|
|
67
|
+
@debug = LSP.debug?
|
|
38
68
|
@sources = {}
|
|
39
69
|
@workspace_index = WorkspaceIndex.new
|
|
40
70
|
@initialized = false
|
|
@@ -42,9 +72,11 @@ module Kapusta
|
|
|
42
72
|
end
|
|
43
73
|
|
|
44
74
|
def run
|
|
75
|
+
debug('run loop start')
|
|
45
76
|
until (message = read_message).nil?
|
|
46
77
|
handle(message)
|
|
47
78
|
end
|
|
79
|
+
debug('stdin EOF, run loop exiting')
|
|
48
80
|
end
|
|
49
81
|
|
|
50
82
|
private
|
|
@@ -83,8 +115,14 @@ module Kapusta
|
|
|
83
115
|
|
|
84
116
|
def write_message(payload)
|
|
85
117
|
body = JSON.generate(payload)
|
|
118
|
+
tag = payload[:id] ? "response id=#{payload[:id]}" : "notify #{payload[:method]}"
|
|
119
|
+
debug("write begin (#{tag}, #{body.bytesize} bytes)")
|
|
86
120
|
@output.write("Content-Length: #{body.bytesize}\r\n\r\n#{body}")
|
|
87
121
|
@output.flush
|
|
122
|
+
debug("write done (#{tag})")
|
|
123
|
+
rescue Errno::EPIPE, IOError => e
|
|
124
|
+
debug("write failed (#{e.class}: #{e.message}), exiting")
|
|
125
|
+
exit!(0)
|
|
88
126
|
end
|
|
89
127
|
|
|
90
128
|
def handle(message)
|
|
@@ -92,9 +130,11 @@ module Kapusta
|
|
|
92
130
|
id = message['id']
|
|
93
131
|
params = message['params'] || {}
|
|
94
132
|
|
|
133
|
+
debug("dispatch begin method=#{method.inspect} id=#{id.inspect}")
|
|
95
134
|
return handle_pre_init(method, id, params) unless @initialized || method == 'initialize' || method == 'exit'
|
|
96
135
|
|
|
97
136
|
dispatch(method, id, params)
|
|
137
|
+
debug("dispatch end method=#{method.inspect} id=#{id.inspect}")
|
|
98
138
|
rescue StandardError => e
|
|
99
139
|
log("#{e.class}: #{e.message}")
|
|
100
140
|
log(e.backtrace.first(5).join("\n"))
|
|
@@ -116,8 +156,12 @@ module Kapusta
|
|
|
116
156
|
when 'initialized' then nil
|
|
117
157
|
when 'shutdown'
|
|
118
158
|
@shutdown = true
|
|
159
|
+
debug('shutdown received, replying and arming watchdog')
|
|
119
160
|
reply(id, nil)
|
|
120
|
-
|
|
161
|
+
arm_shutdown_watchdog
|
|
162
|
+
when 'exit'
|
|
163
|
+
debug("exit notification received (shutdown=#{@shutdown}), calling exit!")
|
|
164
|
+
exit!(@shutdown ? 0 : 1)
|
|
121
165
|
when 'textDocument/didOpen' then on_did_open(params)
|
|
122
166
|
when 'textDocument/didChange' then on_did_change(params)
|
|
123
167
|
when 'textDocument/didSave' then on_did_save(params)
|
|
@@ -300,14 +344,28 @@ module Kapusta
|
|
|
300
344
|
notify('textDocument/publishDiagnostics', params)
|
|
301
345
|
end
|
|
302
346
|
|
|
347
|
+
def arm_shutdown_watchdog(seconds: 2)
|
|
348
|
+
Thread.new do
|
|
349
|
+
sleep seconds
|
|
350
|
+
debug("shutdown watchdog firing after #{seconds}s, forcing exit")
|
|
351
|
+
exit!(0)
|
|
352
|
+
end
|
|
353
|
+
end
|
|
354
|
+
|
|
303
355
|
def log(message)
|
|
304
356
|
@log.puts "kapusta-ls: #{message}"
|
|
357
|
+
@log.flush
|
|
358
|
+
rescue StandardError
|
|
359
|
+
nil
|
|
305
360
|
end
|
|
306
361
|
|
|
307
362
|
def debug(message)
|
|
308
363
|
return unless @debug
|
|
309
364
|
|
|
310
|
-
@log.
|
|
365
|
+
@log.write("kapusta-ls[debug pid=#{Process.pid}]: #{message}\n")
|
|
366
|
+
@log.flush
|
|
367
|
+
rescue StandardError
|
|
368
|
+
nil
|
|
311
369
|
end
|
|
312
370
|
end
|
|
313
371
|
end
|
data/lib/kapusta/version.rb
CHANGED
data/spec/examples_spec.rb
CHANGED
|
@@ -76,6 +76,29 @@ RSpec.describe 'examples' do
|
|
|
76
76
|
expect(run_example('ackermann.kap')).to eq("9\n61\n")
|
|
77
77
|
end
|
|
78
78
|
|
|
79
|
+
it 'non-constant-local.kap' do
|
|
80
|
+
expect(run_example('non-constant-local.kap')).to eq(<<~OUT)
|
|
81
|
+
"nil"
|
|
82
|
+
6
|
|
83
|
+
10
|
|
84
|
+
3
|
|
85
|
+
1
|
|
86
|
+
OUT
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
it 'case-vs-match.kap' do
|
|
90
|
+
expect(run_example('case-vs-match.kap')).to eq(<<~OUT)
|
|
91
|
+
"case: packet[:ping, 42] seq 42"
|
|
92
|
+
"case: packet[:ping, 42] seq 42"
|
|
93
|
+
"case: other"
|
|
94
|
+
"case: packet[:ping, 42] seq 42"
|
|
95
|
+
"match: other"
|
|
96
|
+
"match: other"
|
|
97
|
+
"match: packet[:ping, nil] seq nil"
|
|
98
|
+
"match: packet[:ping, 42] seq 42"
|
|
99
|
+
OUT
|
|
100
|
+
end
|
|
101
|
+
|
|
79
102
|
it 'accumulator.kap' do
|
|
80
103
|
expect(run_example('accumulator.kap')).to eq("22\n")
|
|
81
104
|
end
|
|
@@ -92,6 +115,30 @@ RSpec.describe 'examples' do
|
|
|
92
115
|
expect(run_example('circle.kap')).to eq("78.53975\n31.4159\n")
|
|
93
116
|
end
|
|
94
117
|
|
|
118
|
+
it 'arrange-coins.kap' do
|
|
119
|
+
expect(run_example('arrange-coins.kap')).to eq(<<~OUT)
|
|
120
|
+
0
|
|
121
|
+
0
|
|
122
|
+
1
|
|
123
|
+
1
|
|
124
|
+
2
|
|
125
|
+
3
|
|
126
|
+
3
|
|
127
|
+
6
|
|
128
|
+
4
|
|
129
|
+
10
|
|
130
|
+
OUT
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
it 'divisibility-stats.kap' do
|
|
134
|
+
expect(run_example('divisibility-stats.kap')).to eq(<<~OUT)
|
|
135
|
+
10
|
|
136
|
+
6
|
|
137
|
+
33
|
|
138
|
+
20
|
|
139
|
+
OUT
|
|
140
|
+
end
|
|
141
|
+
|
|
95
142
|
it 'anagram.kap' do
|
|
96
143
|
expect(run_example('anagram.kap')).to eq("true\ntrue\nfalse\n")
|
|
97
144
|
end
|
|
@@ -444,15 +491,10 @@ RSpec.describe 'examples' do
|
|
|
444
491
|
OUT
|
|
445
492
|
end
|
|
446
493
|
|
|
447
|
-
it '
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
expect(run_mruby_source(ruby, path:)).to eq(<<~OUT)
|
|
452
|
-
5
|
|
453
|
-
nil
|
|
454
|
-
5
|
|
455
|
-
"fallback"
|
|
494
|
+
it 'nested-nil-pattern.kap' do
|
|
495
|
+
expect(run_example('nested-nil-pattern.kap')).to eq(<<~OUT)
|
|
496
|
+
"got 42"
|
|
497
|
+
"other"
|
|
456
498
|
OUT
|
|
457
499
|
end
|
|
458
500
|
|
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
|
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.
|
|
4
|
+
version: 0.13.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Evgenii Morozov
|
|
@@ -31,6 +31,8 @@ files:
|
|
|
31
31
|
- examples/ackermann.kap
|
|
32
32
|
- examples/anagram.kap
|
|
33
33
|
- examples/anonymous-greeter.kap
|
|
34
|
+
- examples/arrange-coins.kap
|
|
35
|
+
- examples/array-sign.kap
|
|
34
36
|
- examples/bank-account.kap
|
|
35
37
|
- examples/baseball-game.kap
|
|
36
38
|
- examples/best-time-to-buy-sell-stock.kap
|
|
@@ -40,6 +42,7 @@ files:
|
|
|
40
42
|
- examples/blocks-and-kwargs.kap
|
|
41
43
|
- examples/bst-iterator.kap
|
|
42
44
|
- examples/calc.kap
|
|
45
|
+
- examples/case-vs-match.kap
|
|
43
46
|
- examples/circle.kap
|
|
44
47
|
- examples/classify-wallet.kap
|
|
45
48
|
- examples/climbing-stairs.kap
|
|
@@ -49,9 +52,11 @@ files:
|
|
|
49
52
|
- examples/counter.kap
|
|
50
53
|
- examples/describe.kap
|
|
51
54
|
- examples/destructure.kap
|
|
55
|
+
- examples/divisibility-stats.kap
|
|
52
56
|
- examples/doto-hygiene.kap
|
|
53
57
|
- examples/doto.kap
|
|
54
58
|
- examples/egg-count.kap
|
|
59
|
+
- examples/equal-sums.kap
|
|
55
60
|
- examples/even-squares.kap
|
|
56
61
|
- examples/exceptions.kap
|
|
57
62
|
- examples/factorial.kap
|
|
@@ -87,6 +92,8 @@ files:
|
|
|
87
92
|
- examples/module-header.kap
|
|
88
93
|
- examples/move-zeroes.kap
|
|
89
94
|
- examples/mruby-runtime-examples.txt
|
|
95
|
+
- examples/nested-nil-pattern.kap
|
|
96
|
+
- examples/non-constant-local.kap
|
|
90
97
|
- examples/number-of-1-bits.kap
|
|
91
98
|
- examples/number-of-steps.kap
|
|
92
99
|
- examples/or-patterns.kap
|