kapusta 0.10.0 → 0.11.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +2 -0
- data/examples/accumulator.kap +2 -0
- data/examples/bank-account.kap +2 -0
- data/examples/bst-iterator.kap +52 -0
- data/examples/circle.kap +2 -0
- data/examples/counter.kap +2 -0
- data/examples/hit-counter.kap +2 -0
- data/examples/module-header.kap +4 -2
- data/examples/mruby-runtime-examples.txt +3 -0
- data/examples/parking-system.kap +2 -0
- data/examples/recent-counter.kap +17 -0
- data/examples/scopes.kap +2 -0
- data/examples/signal-harvest.kap +16 -0
- data/examples/stack.kap +2 -0
- data/examples/valid-parentheses-1.kap +2 -0
- data/lib/kapusta/compiler/emitter/bindings.rb +1 -4
- data/lib/kapusta/compiler/emitter/control_flow.rb +25 -30
- data/lib/kapusta/compiler/emitter/expressions.rb +2 -0
- data/lib/kapusta/compiler/emitter/interop.rb +23 -15
- data/lib/kapusta/compiler/emitter/patterns.rb +29 -52
- data/lib/kapusta/compiler/emitter/support.rb +106 -44
- data/lib/kapusta/compiler/macro_expander.rb +4 -12
- data/lib/kapusta/compiler/macro_lowerer.rb +4 -12
- data/lib/kapusta/compiler/normalizer.rb +9 -17
- data/lib/kapusta/compiler.rb +2 -2
- data/lib/kapusta/errors.rb +4 -0
- data/lib/kapusta/formatter.rb +1 -1
- data/lib/kapusta/lsp/definition.rb +17 -0
- data/lib/kapusta/lsp/rename.rb +3 -1
- data/lib/kapusta/lsp/scope_walker.rb +79 -46
- data/lib/kapusta/lsp/workspace_index.rb +2 -13
- data/lib/kapusta/lsp.rb +17 -16
- data/lib/kapusta/support.rb +8 -0
- data/lib/kapusta/version.rb +1 -1
- data/spec/examples_errors_spec.rb +25 -0
- data/spec/examples_spec.rb +21 -0
- data/spec/lsp_spec.rb +71 -3
- metadata +4 -1
|
@@ -35,7 +35,7 @@ module Kapusta
|
|
|
35
35
|
end
|
|
36
36
|
|
|
37
37
|
def remove(uri)
|
|
38
|
-
path = uri_to_path(uri)
|
|
38
|
+
path = LSP.uri_to_path(uri)
|
|
39
39
|
if path && File.file?(path)
|
|
40
40
|
store(uri, File.read(path))
|
|
41
41
|
else
|
|
@@ -178,7 +178,7 @@ module Kapusta
|
|
|
178
178
|
end
|
|
179
179
|
|
|
180
180
|
def resolve_module_uris(importing_uri, module_label)
|
|
181
|
-
importing_path = uri_to_path(importing_uri)
|
|
181
|
+
importing_path = LSP.uri_to_path(importing_uri)
|
|
182
182
|
return [] unless importing_path
|
|
183
183
|
|
|
184
184
|
base_dir = File.dirname(importing_path)
|
|
@@ -209,17 +209,6 @@ module Kapusta
|
|
|
209
209
|
def path_to_uri(path)
|
|
210
210
|
"file://#{URI::DEFAULT_PARSER.escape(File.expand_path(path))}"
|
|
211
211
|
end
|
|
212
|
-
|
|
213
|
-
def uri_to_path(uri)
|
|
214
|
-
return unless uri.is_a?(String)
|
|
215
|
-
|
|
216
|
-
parsed = URI.parse(uri)
|
|
217
|
-
return URI::DEFAULT_PARSER.unescape(parsed.path) if parsed.scheme == 'file'
|
|
218
|
-
|
|
219
|
-
uri
|
|
220
|
-
rescue URI::InvalidURIError
|
|
221
|
-
nil
|
|
222
|
-
end
|
|
223
212
|
end
|
|
224
213
|
end
|
|
225
214
|
end
|
data/lib/kapusta/lsp.rb
CHANGED
|
@@ -19,6 +19,17 @@ module Kapusta
|
|
|
19
19
|
new(input:, output:, log:).run
|
|
20
20
|
end
|
|
21
21
|
|
|
22
|
+
def self.uri_to_path(uri)
|
|
23
|
+
return unless uri.is_a?(String)
|
|
24
|
+
|
|
25
|
+
parsed = URI.parse(uri)
|
|
26
|
+
return URI::DEFAULT_PARSER.unescape(parsed.path) if parsed.scheme == 'file'
|
|
27
|
+
|
|
28
|
+
uri
|
|
29
|
+
rescue URI::InvalidURIError
|
|
30
|
+
nil
|
|
31
|
+
end
|
|
32
|
+
|
|
22
33
|
def initialize(input:, output:, log:)
|
|
23
34
|
@input = input.binmode
|
|
24
35
|
@output = output.binmode
|
|
@@ -150,8 +161,8 @@ module Kapusta
|
|
|
150
161
|
|
|
151
162
|
def on_initialize(params)
|
|
152
163
|
folders = params['workspaceFolders'] || []
|
|
153
|
-
roots = folders.filter_map { |f| uri_to_path(f['uri']) }
|
|
154
|
-
roots << uri_to_path(params['rootUri']) if params['rootUri']
|
|
164
|
+
roots = folders.filter_map { |f| LSP.uri_to_path(f['uri']) }
|
|
165
|
+
roots << LSP.uri_to_path(params['rootUri']) if params['rootUri']
|
|
155
166
|
roots.compact!
|
|
156
167
|
roots.uniq!
|
|
157
168
|
debug("initialize: roots=#{roots.inspect}")
|
|
@@ -213,7 +224,7 @@ module Kapusta
|
|
|
213
224
|
entry = @sources[uri]
|
|
214
225
|
return [] unless entry
|
|
215
226
|
|
|
216
|
-
Formatting.text_edits(entry[:text], uri_to_path(uri))
|
|
227
|
+
Formatting.text_edits(entry[:text], LSP.uri_to_path(uri))
|
|
217
228
|
end
|
|
218
229
|
|
|
219
230
|
def definition(params)
|
|
@@ -257,7 +268,8 @@ module Kapusta
|
|
|
257
268
|
new_name, workspace_index: @workspace_index)
|
|
258
269
|
if result[:error]
|
|
259
270
|
debug("rename error: #{result[:error].inspect}")
|
|
260
|
-
|
|
271
|
+
notify('window/showMessage', { type: 1, message: "Rename: #{result[:error][:message]}" })
|
|
272
|
+
reply(id, { documentChanges: [] })
|
|
261
273
|
else
|
|
262
274
|
edit = build_workspace_edit(result[:changes])
|
|
263
275
|
debug("rename ok: files=#{result[:changes].keys.length} edits=#{result[:changes].values.sum(&:length)}")
|
|
@@ -282,23 +294,12 @@ module Kapusta
|
|
|
282
294
|
end
|
|
283
295
|
|
|
284
296
|
def publish_diagnostics(uri, text, version)
|
|
285
|
-
diagnostics = Diagnostics.collect(text, uri_to_path(uri))
|
|
297
|
+
diagnostics = Diagnostics.collect(text, LSP.uri_to_path(uri))
|
|
286
298
|
params = { uri:, diagnostics: }
|
|
287
299
|
params[:version] = version unless version.nil?
|
|
288
300
|
notify('textDocument/publishDiagnostics', params)
|
|
289
301
|
end
|
|
290
302
|
|
|
291
|
-
def uri_to_path(uri)
|
|
292
|
-
return unless uri
|
|
293
|
-
|
|
294
|
-
parsed = URI.parse(uri)
|
|
295
|
-
return URI::DEFAULT_PARSER.unescape(parsed.path) if parsed.scheme == 'file'
|
|
296
|
-
|
|
297
|
-
uri
|
|
298
|
-
rescue URI::InvalidURIError
|
|
299
|
-
uri
|
|
300
|
-
end
|
|
301
|
-
|
|
302
303
|
def log(message)
|
|
303
304
|
@log.puts "kapusta-ls: #{message}"
|
|
304
305
|
end
|
data/lib/kapusta/support.rb
CHANGED
|
@@ -4,4 +4,12 @@ module Kapusta
|
|
|
4
4
|
def self.kebab_to_snake(name)
|
|
5
5
|
name.tr('-', '_')
|
|
6
6
|
end
|
|
7
|
+
|
|
8
|
+
def self.copy_position(target, source)
|
|
9
|
+
return target unless target.respond_to?(:line=) && source.respond_to?(:line)
|
|
10
|
+
|
|
11
|
+
target.line ||= source.line
|
|
12
|
+
target.column ||= source.column
|
|
13
|
+
target
|
|
14
|
+
end
|
|
7
15
|
end
|
data/lib/kapusta/version.rb
CHANGED
|
@@ -92,6 +92,31 @@ RSpec.describe 'examples-errors' do
|
|
|
92
92
|
.to eq("destructure-literal-table.kap:4:1: could not destructure literal\n")
|
|
93
93
|
end
|
|
94
94
|
|
|
95
|
+
it 'defn-outside-header.kap' do
|
|
96
|
+
expect(run_error_example('defn-outside-header.kap'))
|
|
97
|
+
.to eq("defn-outside-header.kap:1:1: defn outside class or module\n")
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
it 'end-outside-header.kap' do
|
|
101
|
+
expect(run_error_example('end-outside-header.kap'))
|
|
102
|
+
.to eq("end-outside-header.kap:1:1: end outside class or module\n")
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
it 'end-with-args.kap' do
|
|
106
|
+
expect(run_error_example('end-with-args.kap'))
|
|
107
|
+
.to eq("end-with-args.kap:5:1: end takes no arguments\n")
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
it 'extra-end.kap' do
|
|
111
|
+
expect(run_error_example('extra-end.kap'))
|
|
112
|
+
.to eq("extra-end.kap:7:1: end outside class or module\n")
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
it 'unclosed-header.kap' do
|
|
116
|
+
expect(run_error_example('unclosed-header.kap'))
|
|
117
|
+
.to eq("unclosed-header.kap:1:1: class or module not closed with (end)\n")
|
|
118
|
+
end
|
|
119
|
+
|
|
95
120
|
it 'destructure-rest-as-table.kap' do
|
|
96
121
|
expect(run_error_example('destructure-rest-as-table.kap'))
|
|
97
122
|
.to eq("destructure-rest-as-table.kap:6:3: unable to bind table ...\n")
|
data/spec/examples_spec.rb
CHANGED
|
@@ -487,6 +487,10 @@ RSpec.describe 'examples' do
|
|
|
487
487
|
OUT
|
|
488
488
|
end
|
|
489
489
|
|
|
490
|
+
it 'signal-harvest.kap' do
|
|
491
|
+
expect(run_example('signal-harvest.kap')).to eq("40\ntrue\nfalse\n")
|
|
492
|
+
end
|
|
493
|
+
|
|
490
494
|
it 'shapes.kap' do
|
|
491
495
|
expect(run_example('shapes.kap')).to eq("78.5\n9\n8\n0\n")
|
|
492
496
|
end
|
|
@@ -525,6 +529,19 @@ RSpec.describe 'examples' do
|
|
|
525
529
|
expect(run_example('two-sum-hash.kap')).to eq("[0, 1]\n[1, 2]\nnil\n")
|
|
526
530
|
end
|
|
527
531
|
|
|
532
|
+
it 'bst-iterator.kap' do
|
|
533
|
+
expect(run_example('bst-iterator.kap')).to eq(<<~OUT)
|
|
534
|
+
3
|
|
535
|
+
7
|
|
536
|
+
true
|
|
537
|
+
9
|
|
538
|
+
true
|
|
539
|
+
15
|
|
540
|
+
20
|
|
541
|
+
false
|
|
542
|
+
OUT
|
|
543
|
+
end
|
|
544
|
+
|
|
528
545
|
it 'baseball-game.kap' do
|
|
529
546
|
expect(run_example('baseball-game.kap')).to eq("30\n27\n")
|
|
530
547
|
end
|
|
@@ -556,6 +573,10 @@ RSpec.describe 'examples' do
|
|
|
556
573
|
OUT
|
|
557
574
|
end
|
|
558
575
|
|
|
576
|
+
it 'recent-counter.kap' do
|
|
577
|
+
expect(run_example('recent-counter.kap')).to eq("4\n5\n")
|
|
578
|
+
end
|
|
579
|
+
|
|
559
580
|
it 'reverse-integer.kap' do
|
|
560
581
|
expect(run_example('reverse-integer.kap')).to eq("321\n-321\n21\n0\n")
|
|
561
582
|
end
|
data/spec/lsp_spec.rb
CHANGED
|
@@ -268,7 +268,8 @@ RSpec.describe Kapusta::LSP do
|
|
|
268
268
|
frame_rename(uri: uri['a.kap'], **cursor_at(text_a, 'foo'), new_name: 'bar')
|
|
269
269
|
)
|
|
270
270
|
|
|
271
|
-
|
|
271
|
+
message = responses.find { |m| m['method'] == 'window/showMessage' }
|
|
272
|
+
expect(message['params']['message']).to include('already defined')
|
|
272
273
|
end
|
|
273
274
|
end
|
|
274
275
|
|
|
@@ -282,7 +283,8 @@ RSpec.describe Kapusta::LSP do
|
|
|
282
283
|
frame_rename(uri: uri['a.kap'], **cursor_at(text_a, 'Foo'), new_name: 'Bar')
|
|
283
284
|
)
|
|
284
285
|
|
|
285
|
-
|
|
286
|
+
message = responses.find { |m| m['method'] == 'window/showMessage' }
|
|
287
|
+
expect(message['params']['message']).to include('already defined')
|
|
286
288
|
end
|
|
287
289
|
end
|
|
288
290
|
|
|
@@ -304,6 +306,71 @@ RSpec.describe Kapusta::LSP do
|
|
|
304
306
|
)
|
|
305
307
|
end
|
|
306
308
|
|
|
309
|
+
it 'rejects renaming a class to a lowercase name with a clear message' do
|
|
310
|
+
text = "(class Accumulator)\n\n(end)\n"
|
|
311
|
+
responses = run(
|
|
312
|
+
frame_initialize,
|
|
313
|
+
frame_did_open('file:///x.kap', text),
|
|
314
|
+
frame_rename(uri: 'file:///x.kap', **cursor_at(text, 'Accumulator'), new_name: 'fff')
|
|
315
|
+
)
|
|
316
|
+
|
|
317
|
+
response = result_for(responses)
|
|
318
|
+
expect(response['error']).to be_nil
|
|
319
|
+
expect(response['result']).to eq('documentChanges' => [])
|
|
320
|
+
|
|
321
|
+
show_message = responses.find { |m| m['method'] == 'window/showMessage' }
|
|
322
|
+
expect(show_message).not_to be_nil
|
|
323
|
+
expect(show_message['params']['type']).to eq(1)
|
|
324
|
+
expect(show_message['params']['message']).to include('uppercase letter')
|
|
325
|
+
end
|
|
326
|
+
|
|
327
|
+
it 'renames a class declared with a bodyless header closed by (end) and its usages after (end)' do
|
|
328
|
+
text = "(class Accumulator)\n\n(fn add! [n] n)\n\n(end)\n\n(let [acc (Accumulator.new 10)]\n (acc.add! 5))\n"
|
|
329
|
+
with_workspace('a.kap' => text) do |root_uri, uri|
|
|
330
|
+
responses = run(
|
|
331
|
+
frame_initialize([root_uri]),
|
|
332
|
+
frame_did_open(uri['a.kap'], text),
|
|
333
|
+
frame_rename(uri: uri['a.kap'], **cursor_at(text, 'Accumulator'), new_name: 'Foo')
|
|
334
|
+
)
|
|
335
|
+
result = result_for(responses)['result']
|
|
336
|
+
|
|
337
|
+
expect(result).not_to be_nil
|
|
338
|
+
edits = result['documentChanges'].first['edits']
|
|
339
|
+
expect(edits.map { |e| e['range']['start']['line'] }).to contain_exactly(0, 6)
|
|
340
|
+
expect(edits.map { |e| e['newText'] }).to all(eq('Foo'))
|
|
341
|
+
end
|
|
342
|
+
end
|
|
343
|
+
|
|
344
|
+
it 'jumps from (end) to the class header that opened the file scope' do
|
|
345
|
+
text = "(class Foo)\n\n(fn hi [] 1)\n\n(end)\n"
|
|
346
|
+
responses = run(
|
|
347
|
+
frame_initialize,
|
|
348
|
+
frame_did_open('file:///x.kap', text),
|
|
349
|
+
frame_definition(uri: 'file:///x.kap', **cursor_at(text, 'end'))
|
|
350
|
+
)
|
|
351
|
+
result = result_for(responses)['result']
|
|
352
|
+
|
|
353
|
+
expect(result).to eq(
|
|
354
|
+
'uri' => 'file:///x.kap',
|
|
355
|
+
'range' => {
|
|
356
|
+
'start' => { 'line' => 0, 'character' => 7 },
|
|
357
|
+
'end' => { 'line' => 0, 'character' => 10 }
|
|
358
|
+
}
|
|
359
|
+
)
|
|
360
|
+
end
|
|
361
|
+
|
|
362
|
+
it 'jumps from (end) to the matching module header for nested headers' do
|
|
363
|
+
text = "(module Outer)\n\n(module Inner)\n(fn self.go [] 1)\n(end)\n\n(end)\n"
|
|
364
|
+
responses = run(
|
|
365
|
+
frame_initialize,
|
|
366
|
+
frame_did_open('file:///x.kap', text),
|
|
367
|
+
frame_definition(uri: 'file:///x.kap', line: 4, character: 1)
|
|
368
|
+
)
|
|
369
|
+
result = result_for(responses)['result']
|
|
370
|
+
|
|
371
|
+
expect(result['range']['start']).to eq('line' => 2, 'character' => 8)
|
|
372
|
+
end
|
|
373
|
+
|
|
307
374
|
it 'jumps to a top-level fn definition across files' do
|
|
308
375
|
text_a = "(fn greet [n] (print n))\n"
|
|
309
376
|
text_b = "(greet 42)\n"
|
|
@@ -583,7 +650,8 @@ RSpec.describe Kapusta::LSP do
|
|
|
583
650
|
frame_rename(uri: uri['a.kap'], **cursor_at(text_a, 'swap!'), new_name: 'flip!')
|
|
584
651
|
)
|
|
585
652
|
|
|
586
|
-
|
|
653
|
+
message = responses.find { |m| m['method'] == 'window/showMessage' }
|
|
654
|
+
expect(message['params']['message']).to include('already defined')
|
|
587
655
|
end
|
|
588
656
|
end
|
|
589
657
|
|
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.11.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Evgenii Morozov
|
|
@@ -38,6 +38,7 @@ files:
|
|
|
38
38
|
- examples/binary-to-decimal.kap
|
|
39
39
|
- examples/block-sort.kap
|
|
40
40
|
- examples/blocks-and-kwargs.kap
|
|
41
|
+
- examples/bst-iterator.kap
|
|
41
42
|
- examples/calc.kap
|
|
42
43
|
- examples/circle.kap
|
|
43
44
|
- examples/classify-wallet.kap
|
|
@@ -101,6 +102,7 @@ files:
|
|
|
101
102
|
- examples/power-of-three.kap
|
|
102
103
|
- examples/primes.kap
|
|
103
104
|
- examples/raindrops.kap
|
|
105
|
+
- examples/recent-counter.kap
|
|
104
106
|
- examples/record.kap
|
|
105
107
|
- examples/regex.kap
|
|
106
108
|
- examples/reverse-integer.kap
|
|
@@ -110,6 +112,7 @@ files:
|
|
|
110
112
|
- examples/scopes.kap
|
|
111
113
|
- examples/shapes.kap
|
|
112
114
|
- examples/shared-macros.kapm
|
|
115
|
+
- examples/signal-harvest.kap
|
|
113
116
|
- examples/single-number.kap
|
|
114
117
|
- examples/squares.kap
|
|
115
118
|
- examples/stack.kap
|