kapusta 0.11.1 → 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 +9 -10
- 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/cli.rb +2 -2
- data/lib/kapusta/compiler/emitter/bindings.rb +12 -3
- data/lib/kapusta/compiler/emitter/collections.rb +17 -24
- data/lib/kapusta/compiler/emitter/control_flow.rb +1 -1
- data/lib/kapusta/compiler/emitter/support.rb +2 -2
- data/lib/kapusta/compiler/normalizer.rb +4 -1
- data/lib/kapusta/compiler.rb +1 -1
- data/lib/kapusta/errors.rb +1 -1
- data/lib/kapusta/formatter.rb +49 -20
- data/lib/kapusta/lsp.rb +62 -4
- data/lib/kapusta/version.rb +1 -1
- data/spec/cli_spec.rb +4 -4
- data/spec/examples_spec.rb +52 -10
- 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
|
@@ -2,11 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
Kapusta is a Lisp for the Ruby runtime.
|
|
4
4
|
|
|
5
|
-
It is inspired by [Fennel](https://fennel-lang.org).
|
|
5
|
+
It is inspired by [Fennel](https://fennel-lang.org). Kapusta aims to bring the simplicity and joy of Lisp to Ruby. Where Fennel uses Lua's stdlib and runtime, Kapusta uses Ruby's.
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
For more information about Kapusta, see the official Fennel documentation and tutorials.
|
|
7
|
+
For more information about Kapusta, see the official Fennel documentation and tutorials, but replace Lua with Ruby.
|
|
10
8
|
|
|
11
9
|
## Features
|
|
12
10
|
|
|
@@ -41,14 +39,14 @@ exe/kapusta examples/fizzbuzz.kap
|
|
|
41
39
|
or
|
|
42
40
|
|
|
43
41
|
```
|
|
44
|
-
|
|
42
|
+
kapusta --compile examples/fizzbuzz.kap > examples/fizzbuzz.rb
|
|
45
43
|
ruby examples/fizzbuzz.rb
|
|
46
44
|
```
|
|
47
45
|
|
|
48
|
-
For mruby
|
|
46
|
+
For mruby 3 compatible output, such as DragonRuby, use:
|
|
49
47
|
|
|
50
48
|
```
|
|
51
|
-
|
|
49
|
+
kapusta --compile --target=mruby3 examples/match.kap > examples/match-mruby3.rb
|
|
52
50
|
```
|
|
53
51
|
|
|
54
52
|
## Use from Ruby
|
|
@@ -105,6 +103,7 @@ Kapusta keeps most core Fennel forms. The main differences come from Ruby's runt
|
|
|
105
103
|
| `string.format`, `table.insert`, etc. | use Ruby methods and stdlib instead |
|
|
106
104
|
| `values` uses Lua multiple returns | `values` lowers to a Ruby array, usually destructured |
|
|
107
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`) |
|
|
108
107
|
| `with-open`, `tail!` | not provided |
|
|
109
108
|
|
|
110
109
|
Kapusta-specific additions:
|
|
@@ -115,8 +114,8 @@ Kapusta-specific additions:
|
|
|
115
114
|
- `ivar` or `@var` / `cvar` or `@@var` / `gvar` or `$var`
|
|
116
115
|
- `try` / `catch` / `finally` plus `raise` for exceptions
|
|
117
116
|
- `(ruby "...")` raw host escape hatch
|
|
118
|
-
- a
|
|
119
|
-
- a
|
|
117
|
+
- pass Ruby keyword arguments by ending a call with a symbol-keyed hash: `(File.open path "r" {:encoding "UTF-8"})`
|
|
118
|
+
- pass a Ruby block by ending a call with a `(fn ...)` or `#(...)` literal: `(File.open path "r" (fn [io] (: io :read)))`
|
|
120
119
|
|
|
121
120
|
## Format
|
|
122
121
|
|
|
@@ -130,7 +129,7 @@ Use `kapusta-ls` in the editor of your choice.
|
|
|
130
129
|
|
|
131
130
|
## Syntax highlight
|
|
132
131
|
|
|
133
|
-
For Vim, you can use https://
|
|
132
|
+
For Vim, you can use [`vim-syntax/`](https://github.com/evmorov/kapusta/tree/main/vim-syntax/).
|
|
134
133
|
|
|
135
134
|
## License
|
|
136
135
|
|
|
@@ -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))
|
data/lib/kapusta/cli.rb
CHANGED
|
@@ -39,7 +39,7 @@ module Kapusta
|
|
|
39
39
|
OptionParser.new do |parser|
|
|
40
40
|
parser.banner = usage
|
|
41
41
|
parser.on('-c', '--compile', 'Compile .kap to Ruby') { options.compile = true }
|
|
42
|
-
parser.on('--target=TARGET', 'Compile for
|
|
42
|
+
parser.on('--target=TARGET', 'Compile for mruby3') do |target|
|
|
43
43
|
options.target = Kapusta::Compiler.normalize_target(target)
|
|
44
44
|
end
|
|
45
45
|
parser.on('-h', '--help', 'Show this help') { options.help = true }
|
|
@@ -69,7 +69,7 @@ module Kapusta
|
|
|
69
69
|
end
|
|
70
70
|
|
|
71
71
|
def self.usage
|
|
72
|
-
'usage: kapusta [--compile|-c] [--target=
|
|
72
|
+
'usage: kapusta [--compile|-c] [--target=mruby3] <file.kap> | kapusta <file.kap> [args...]'
|
|
73
73
|
end
|
|
74
74
|
end
|
|
75
75
|
end
|
|
@@ -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]
|
|
@@ -178,7 +178,7 @@ module Kapusta
|
|
|
178
178
|
|
|
179
179
|
def emit_toplevel_method_bridge(ruby_name)
|
|
180
180
|
method_name = ruby_name.to_sym.inspect
|
|
181
|
-
if
|
|
181
|
+
if mruby3_target?
|
|
182
182
|
return [
|
|
183
183
|
"define_singleton_method(#{method_name}) do |*args|",
|
|
184
184
|
indent("Object.instance_method(#{method_name}).bind(self).call(*args)"),
|
|
@@ -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)
|
|
@@ -96,7 +96,7 @@ module Kapusta
|
|
|
96
96
|
end
|
|
97
97
|
|
|
98
98
|
def emit_case_body(value_var, clauses, env, current_scope, mode)
|
|
99
|
-
return try_emit_compat_case(value_var, clauses, env, current_scope, mode) if
|
|
99
|
+
return try_emit_compat_case(value_var, clauses, env, current_scope, mode) if mruby3_target?
|
|
100
100
|
|
|
101
101
|
try_emit_native_case(value_var, clauses, env, current_scope, mode) ||
|
|
102
102
|
try_emit_compat_case(value_var, clauses, env, current_scope, mode)
|
|
@@ -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/compiler.rb
CHANGED
|
@@ -61,7 +61,7 @@ module Kapusta
|
|
|
61
61
|
def self.normalize_target(target)
|
|
62
62
|
case target
|
|
63
63
|
when nil then nil
|
|
64
|
-
when :mruby, 'mruby' then :
|
|
64
|
+
when :mruby3, 'mruby3', :mruby, 'mruby' then :mruby3
|
|
65
65
|
else
|
|
66
66
|
raise Error, Kapusta::Errors.format(:unknown_target, target: target.inspect)
|
|
67
67
|
end
|
data/lib/kapusta/errors.rb
CHANGED
|
@@ -64,7 +64,7 @@ module Kapusta
|
|
|
64
64
|
unexpected_eof: 'unexpected eof',
|
|
65
65
|
unexpected_vararg: 'unexpected vararg',
|
|
66
66
|
unknown_special_form: 'unknown special form: %{name}',
|
|
67
|
-
unknown_target: 'unknown target %{target}; only
|
|
67
|
+
unknown_target: 'unknown target %{target}; only mruby3 is supported',
|
|
68
68
|
unquote_outside_quasiquote: 'unquote outside quasiquote',
|
|
69
69
|
unquote_splice_outside_list: 'unquote-splice must appear inside a quoted list/vec',
|
|
70
70
|
unterminated_string: 'unterminated string',
|
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/cli_spec.rb
CHANGED
|
@@ -66,11 +66,11 @@ RSpec.describe Kapusta::CLI do
|
|
|
66
66
|
end
|
|
67
67
|
end
|
|
68
68
|
|
|
69
|
-
it 'compiles case and match forms for
|
|
69
|
+
it 'compiles case and match forms for mruby3 with --target=mruby3' do
|
|
70
70
|
path = File.expand_path('../examples/match.kap', __dir__)
|
|
71
71
|
|
|
72
72
|
ruby = capture_stdout do
|
|
73
|
-
described_class.start(['--compile', '--target=
|
|
73
|
+
described_class.start(['--compile', '--target=mruby3', path])
|
|
74
74
|
end
|
|
75
75
|
|
|
76
76
|
expect(ruby).not_to match(/^\s*in\b/)
|
|
@@ -98,14 +98,14 @@ RSpec.describe Kapusta::CLI do
|
|
|
98
98
|
.to raise_error(SystemExit)
|
|
99
99
|
end
|
|
100
100
|
|
|
101
|
-
expect(error_output).to include('unknown target "mri"; only
|
|
101
|
+
expect(error_output).to include('unknown target "mri"; only mruby3 is supported')
|
|
102
102
|
end
|
|
103
103
|
|
|
104
104
|
it 'rejects target without compile mode' do
|
|
105
105
|
path = File.expand_path('../examples/fizzbuzz.kap', __dir__)
|
|
106
106
|
|
|
107
107
|
error_output = capture_stderr do
|
|
108
|
-
expect { described_class.start(['--target=
|
|
108
|
+
expect { described_class.start(['--target=mruby3', path]) }
|
|
109
109
|
.to raise_error(SystemExit)
|
|
110
110
|
end
|
|
111
111
|
|
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
|
|
|
@@ -699,7 +741,7 @@ RSpec.describe 'mruby runtime examples' do
|
|
|
699
741
|
if mruby_status.success? && mruby_stdout == expected
|
|
700
742
|
expect(run_mruby_source(ruby, path:)).to eq(expected)
|
|
701
743
|
else
|
|
702
|
-
mruby_ruby = compile_example(name, target: :
|
|
744
|
+
mruby_ruby = compile_example(name, target: :mruby3)
|
|
703
745
|
|
|
704
746
|
if mruby_ruby == ruby
|
|
705
747
|
expect(mruby_status).to be_success
|
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
|