kapusta 0.11.2 → 0.12.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 +1 -0
- data/examples/arrange-coins.kap +32 -0
- data/examples/array-sign.kap +24 -0
- 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/lib/kapusta/compiler/emitter/bindings.rb +11 -2
- data/lib/kapusta/compiler/emitter/collections.rb +17 -24
- data/lib/kapusta/compiler/normalizer.rb +4 -1
- data/lib/kapusta/formatter.rb +49 -20
- data/lib/kapusta/lsp.rb +62 -4
- data/lib/kapusta/version.rb +1 -1
- data/spec/examples_spec.rb +51 -9
- 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: d5e4a7dc8e3d044580b58064b4632706c2115964847d674c23bbe39a625e0b6e
|
|
4
|
+
data.tar.gz: b17a395b81ab568829022273e5df9ee5a186c052d6a1e4c843df631dfe1d6f76
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: d3e8ca629130a695a67a1c210ab06869518e2d232084e01a8cb6d180094e2b10b0cbc3f65c191a9a7a6b1e70727f1563f28e3637264cc7776a58c2e16e554991
|
|
7
|
+
data.tar.gz: 71695c0adf8e37705c9b9b85263e495578bcdd0d442c2d56ad09b1a6386640003164adf98d932c499472ae81d5c3d4a909898b907417e6d6463384b855b2fa33
|
data/README.md
CHANGED
|
@@ -103,6 +103,7 @@ Kapusta keeps most core Fennel forms. The main differences come from Ruby's runt
|
|
|
103
103
|
| `string.format`, `table.insert`, etc. | use Ruby methods and stdlib instead |
|
|
104
104
|
| `values` uses Lua multiple returns | `values` lowers to a Ruby array, usually destructured |
|
|
105
105
|
| `(print x)` is Lua's `print` (bare) | `(print x)` is Ruby's `p` (inspect-style) |
|
|
106
|
+
| `(.. "x: " nil)` errors at runtime | `(.. "x: " nil)` produces `"x: "` (Ruby `nil.to_s`) |
|
|
106
107
|
| `with-open`, `tail!` | not provided |
|
|
107
108
|
|
|
108
109
|
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]))
|
|
@@ -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))
|
|
@@ -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]
|
|
@@ -296,7 +296,9 @@ module Kapusta
|
|
|
296
296
|
|
|
297
297
|
if target.is_a?(Sym)
|
|
298
298
|
validate_binding_symbol!(target)
|
|
299
|
-
if allow_constant && form.head.name == 'local' &&
|
|
299
|
+
if allow_constant && form.head.name == 'local' &&
|
|
300
|
+
constant_value?(form.items[2]) &&
|
|
301
|
+
(constant_name = constant_name_for(target.name))
|
|
300
302
|
env.define(target.name, constant_name)
|
|
301
303
|
mark_mutability(env, target.name, mutable: false)
|
|
302
304
|
return ["#{constant_name} = #{value_code}\nnil", env]
|
|
@@ -316,6 +318,13 @@ module Kapusta
|
|
|
316
318
|
candidate if candidate.match?(/\A[A-Z][A-Z0-9_]*\z/)
|
|
317
319
|
end
|
|
318
320
|
|
|
321
|
+
def constant_value?(value_form)
|
|
322
|
+
case value_form
|
|
323
|
+
when Numeric, String, ::Symbol, true, false, nil then true
|
|
324
|
+
else false
|
|
325
|
+
end
|
|
326
|
+
end
|
|
327
|
+
|
|
319
328
|
def check_destructure_value!(pattern, value_form)
|
|
320
329
|
return unless pattern.is_a?(Vec) || pattern.is_a?(HashLit)
|
|
321
330
|
|
|
@@ -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)
|
|
@@ -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/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
|
|
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
|
|
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.12.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
|