kapusta 0.5.0 → 0.8.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 +24 -6
- data/bin/fennel-parity +11 -4
- data/examples/classify-wallet.kap +11 -0
- data/examples/import-helpers.kapm +9 -0
- data/examples/macros-import-helpers.kap +3 -0
- data/examples/macros-import-whole.kap +5 -0
- data/examples/macros-import.kap +6 -0
- data/examples/power-of-three.kap +12 -0
- data/examples/shared-macros.kapm +4 -0
- data/exe/kapusta-ls +14 -0
- data/kapusta.gemspec +2 -2
- data/lib/kapusta/compiler/emitter/bindings.rb +38 -4
- data/lib/kapusta/compiler/emitter/collections.rb +51 -59
- data/lib/kapusta/compiler/emitter/control_flow.rb +24 -2
- data/lib/kapusta/compiler/emitter/expressions.rb +0 -2
- data/lib/kapusta/compiler/emitter/interop.rb +2 -1
- data/lib/kapusta/compiler/emitter/patterns.rb +52 -4
- data/lib/kapusta/compiler/emitter/support.rb +1 -1
- data/lib/kapusta/compiler/emitter.rb +1 -1
- data/lib/kapusta/compiler/lua_compat.rb +149 -0
- data/lib/kapusta/compiler/macro_expander.rb +55 -141
- data/lib/kapusta/compiler/macro_gensym.rb +21 -0
- data/lib/kapusta/compiler/macro_importer.rb +81 -0
- data/lib/kapusta/compiler/macro_lowerer.rb +184 -0
- data/lib/kapusta/compiler/normalizer.rb +4 -19
- data/lib/kapusta/compiler.rb +4 -2
- data/lib/kapusta/errors.rb +9 -3
- data/lib/kapusta/formatter.rb +4 -0
- data/lib/kapusta/lsp/definition.rb +67 -0
- data/lib/kapusta/lsp/diagnostics.rb +42 -0
- data/lib/kapusta/lsp/formatting.rb +30 -0
- data/lib/kapusta/lsp/identifier.rb +28 -0
- data/lib/kapusta/lsp/rename.rb +417 -0
- data/lib/kapusta/lsp/scope_walker.rb +643 -0
- data/lib/kapusta/lsp/workspace_index.rb +225 -0
- data/lib/kapusta/lsp.rb +312 -0
- data/lib/kapusta/reader.rb +0 -2
- data/lib/kapusta/version.rb +1 -1
- data/spec/examples_errors_spec.rb +142 -1
- data/spec/examples_spec.rb +12 -0
- data/spec/lsp_spec.rb +603 -0
- metadata +23 -1
|
@@ -3,6 +3,8 @@
|
|
|
3
3
|
module Kapusta
|
|
4
4
|
module Compiler
|
|
5
5
|
class Normalizer
|
|
6
|
+
include LuaCompat::Normalization
|
|
7
|
+
|
|
6
8
|
def normalize_all(forms)
|
|
7
9
|
forms.map { |form| normalize(form) }
|
|
8
10
|
end
|
|
@@ -57,25 +59,8 @@ module Kapusta
|
|
|
57
59
|
List.new([Sym.new('set'), List.new([Sym.new('.'), items[1], items[2]]), items[3]]),
|
|
58
60
|
list
|
|
59
61
|
)
|
|
60
|
-
when
|
|
61
|
-
|
|
62
|
-
args = items[2..]
|
|
63
|
-
List.new([
|
|
64
|
-
Sym.new('try'),
|
|
65
|
-
List.new([Sym.new('values'), true, List.new([fn, *args])]),
|
|
66
|
-
List.new([Sym.new('catch'), Sym.new('StandardError'), Sym.new('e'),
|
|
67
|
-
List.new([Sym.new('values'), false, Sym.new('e')])])
|
|
68
|
-
])
|
|
69
|
-
when 'xpcall'
|
|
70
|
-
fn = items[1]
|
|
71
|
-
handler = items[2]
|
|
72
|
-
args = items[3..]
|
|
73
|
-
List.new([
|
|
74
|
-
Sym.new('try'),
|
|
75
|
-
List.new([Sym.new('values'), true, List.new([fn, *args])]),
|
|
76
|
-
List.new([Sym.new('catch'), Sym.new('StandardError'), Sym.new('e'),
|
|
77
|
-
List.new([Sym.new('values'), false, List.new([handler, Sym.new('e')])])])
|
|
78
|
-
])
|
|
62
|
+
when *LuaCompat::SPECIAL_FORMS
|
|
63
|
+
normalize_lua_compat_form(head.name, items)
|
|
79
64
|
when '->', '->>', '-?>', '-?>>'
|
|
80
65
|
inherit_position(normalize(thread(items[1..], head.name)), list)
|
|
81
66
|
when 'doto'
|
data/lib/kapusta/compiler.rb
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require_relative 'error'
|
|
4
|
+
require_relative 'compiler/lua_compat'
|
|
4
5
|
require_relative 'compiler/normalizer'
|
|
5
6
|
require_relative 'compiler/emitter'
|
|
6
7
|
require_relative 'compiler/macro_expander'
|
|
@@ -8,7 +9,7 @@ require_relative 'compiler/macro_expander'
|
|
|
8
9
|
module Kapusta
|
|
9
10
|
module Compiler
|
|
10
11
|
class Error < Kapusta::Error; end
|
|
11
|
-
|
|
12
|
+
CORE_SPECIAL_FORMS = %w[
|
|
12
13
|
fn lambda λ let local var global set if when unless case match
|
|
13
14
|
while for each do values
|
|
14
15
|
-> ->> -?> -?>> doto
|
|
@@ -23,7 +24,7 @@ module Kapusta
|
|
|
23
24
|
raise
|
|
24
25
|
ivar cvar gvar
|
|
25
26
|
ruby
|
|
26
|
-
tset
|
|
27
|
+
tset
|
|
27
28
|
and or not
|
|
28
29
|
= not= < <= > >=
|
|
29
30
|
+ - * / %
|
|
@@ -31,6 +32,7 @@ module Kapusta
|
|
|
31
32
|
macro macros import-macros
|
|
32
33
|
quasi-sym quasi-list quasi-list-tail quasi-vec quasi-vec-tail quasi-hash quasi-gensym
|
|
33
34
|
].freeze
|
|
35
|
+
SPECIAL_FORMS = (CORE_SPECIAL_FORMS + LuaCompat::SPECIAL_FORMS).freeze
|
|
34
36
|
|
|
35
37
|
def self.compile(source, path: '(kapusta)')
|
|
36
38
|
forms = Reader.read_all(source)
|
data/lib/kapusta/errors.rb
CHANGED
|
@@ -14,6 +14,7 @@ module Kapusta
|
|
|
14
14
|
cannot_emit_form: 'cannot emit form: %{form}',
|
|
15
15
|
cannot_set_method_binding: 'cannot set method binding: %{name}',
|
|
16
16
|
case_no_patterns: 'expected at least one pattern/body pair',
|
|
17
|
+
case_no_subject: 'missing subject',
|
|
17
18
|
case_odd_patterns: 'expected even number of pattern/body pairs',
|
|
18
19
|
case_unsupported: 'case/match clauses use patterns this compiler cannot translate',
|
|
19
20
|
could_not_destructure_literal: 'could not destructure literal',
|
|
@@ -23,14 +24,18 @@ module Kapusta
|
|
|
23
24
|
dot_no_args: 'expected table argument',
|
|
24
25
|
each_no_binding: 'expected binding table',
|
|
25
26
|
empty_call: 'expected a function, macro, or special to call',
|
|
26
|
-
empty_token: 'empty token',
|
|
27
27
|
expected_var: 'expected var %{name}',
|
|
28
28
|
fn_no_params: 'expected parameters table',
|
|
29
29
|
global_arity: 'expected name and value',
|
|
30
30
|
global_non_symbol_name: 'unable to bind %{type} %{value}',
|
|
31
31
|
icollect_no_iterator: 'expected iterator binding table',
|
|
32
32
|
if_no_body: 'expected condition and body',
|
|
33
|
-
|
|
33
|
+
import_macros_cycle: 'import-macros cycle detected for module %{module}',
|
|
34
|
+
import_macros_destructure_invalid: 'import-macros expects a hash literal as first argument',
|
|
35
|
+
import_macros_macro_not_found: 'import-macros: macro %{macro} not exported by module %{module}',
|
|
36
|
+
import_macros_module_invalid: 'import-macros expects a symbol or string module name',
|
|
37
|
+
import_macros_module_no_exports: 'import-macros: module %{module} has no export table',
|
|
38
|
+
import_macros_module_not_found: 'import-macros: module %{module} not found',
|
|
34
39
|
invalid_class_name: 'invalid class name: %{name}',
|
|
35
40
|
invalid_module_name: 'invalid module name: %{name}',
|
|
36
41
|
let_no_body: 'expected body expression',
|
|
@@ -46,16 +51,17 @@ module Kapusta
|
|
|
46
51
|
odd_forms_in_hash: 'odd number of forms in hash',
|
|
47
52
|
rest_not_last: 'expected rest argument before last parameter',
|
|
48
53
|
shadowed_special: 'local %{name} was overshadowed by a special form or macro',
|
|
49
|
-
special_must_be_toplevel: '%{name} must appear at the top level and is consumed by the macro expander',
|
|
50
54
|
tset_no_value: 'tset: expected table, key, and value arguments',
|
|
51
55
|
unclosed_delimiter: "unclosed opening delimiter '%{char}'",
|
|
52
56
|
undefined_symbol: 'undefined symbol: %{name}',
|
|
53
57
|
unexpected_closing_delimiter: "unexpected closing delimiter '%{char}'",
|
|
54
58
|
unexpected_eof: 'unexpected eof',
|
|
59
|
+
unexpected_vararg: 'unexpected vararg',
|
|
55
60
|
unknown_special_form: 'unknown special form: %{name}',
|
|
56
61
|
unquote_outside_quasiquote: 'unquote outside quasiquote',
|
|
57
62
|
unquote_splice_outside_list: 'unquote-splice must appear inside a quoted list/vec',
|
|
58
63
|
unterminated_string: 'unterminated string',
|
|
64
|
+
vararg_not_last: 'expected vararg as last parameter',
|
|
59
65
|
vararg_with_operator: 'tried to use vararg with operator',
|
|
60
66
|
when_no_body: '%{form}: expected body'
|
|
61
67
|
}.freeze
|
data/lib/kapusta/formatter.rb
CHANGED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'rename'
|
|
4
|
+
|
|
5
|
+
module Kapusta
|
|
6
|
+
class LSP
|
|
7
|
+
module Definition
|
|
8
|
+
module_function
|
|
9
|
+
|
|
10
|
+
def find(uri, text, line_zero, character, workspace_index:)
|
|
11
|
+
target = Rename.locate(text, line_zero, character)
|
|
12
|
+
return unless target
|
|
13
|
+
|
|
14
|
+
case target.kind
|
|
15
|
+
when :local, :toplevel_fn, :constant
|
|
16
|
+
location_for_binding(uri, target.binding) if target.binding
|
|
17
|
+
when :macro
|
|
18
|
+
locations_for_macro(uri, target.binding, workspace_index)
|
|
19
|
+
when :free_toplevel
|
|
20
|
+
locations_for_toplevel(target.name, workspace_index)
|
|
21
|
+
when :free_constant
|
|
22
|
+
locations_for_constant(target.segment_prefix, workspace_index)
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def locations_for_macro(uri, binding, workspace_index)
|
|
27
|
+
return unless binding
|
|
28
|
+
|
|
29
|
+
case binding.kind
|
|
30
|
+
when :macro
|
|
31
|
+
location_for_binding(uri, binding)
|
|
32
|
+
when :macro_import
|
|
33
|
+
def_uri, def_binding = workspace_index.find_macro_definition(
|
|
34
|
+
uri, binding.import_module, binding.import_key
|
|
35
|
+
)
|
|
36
|
+
location_for_binding(def_uri, def_binding) if def_uri && def_binding
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def location_for_binding(uri, binding)
|
|
41
|
+
{ uri:, range: binding_range(binding) }
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def locations_for_toplevel(name, workspace_index)
|
|
45
|
+
defs = workspace_index.toplevel_fn_definitions(name)
|
|
46
|
+
return if defs.empty?
|
|
47
|
+
|
|
48
|
+
defs.map { |uri, b| { uri:, range: binding_range(b) } }
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def locations_for_constant(prefix, workspace_index)
|
|
52
|
+
defs = workspace_index.constant_definitions_with_prefix(prefix)
|
|
53
|
+
return if defs.empty?
|
|
54
|
+
|
|
55
|
+
defs.map { |uri, b| { uri:, range: binding_range(b) } }
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def binding_range(binding)
|
|
59
|
+
line = binding.line - 1
|
|
60
|
+
{
|
|
61
|
+
start: { line:, character: binding.column - 1 },
|
|
62
|
+
end: { line:, character: binding.end_column - 1 }
|
|
63
|
+
}
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Kapusta
|
|
4
|
+
class LSP
|
|
5
|
+
module Diagnostics
|
|
6
|
+
SEVERITY_ERROR = 1
|
|
7
|
+
|
|
8
|
+
module_function
|
|
9
|
+
|
|
10
|
+
def collect(text, path)
|
|
11
|
+
Kapusta.compile(text, path: path || '(buffer)')
|
|
12
|
+
[]
|
|
13
|
+
rescue Kapusta::Error => e
|
|
14
|
+
[diagnostic_from(e, text)]
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def diagnostic_from(error, text)
|
|
18
|
+
line = [(error.line || 1) - 1, 0].max
|
|
19
|
+
column = [(error.column || 1) - 1, 0].max
|
|
20
|
+
|
|
21
|
+
{
|
|
22
|
+
range: {
|
|
23
|
+
start: { line:, character: column },
|
|
24
|
+
end: { line:, character: column + token_length(text, line, column) }
|
|
25
|
+
},
|
|
26
|
+
severity: SEVERITY_ERROR,
|
|
27
|
+
source: 'kapusta-ls',
|
|
28
|
+
message: error.reason
|
|
29
|
+
}
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def token_length(text, line, column)
|
|
33
|
+
source_line = text.lines[line]
|
|
34
|
+
return 1 unless source_line
|
|
35
|
+
|
|
36
|
+
tail = source_line[column..] || ''
|
|
37
|
+
match = tail.match(/\A[^\s()\[\]{}";`,]+/)
|
|
38
|
+
match && match[0].length.positive? ? match[0].length : 1
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative '../formatter'
|
|
4
|
+
|
|
5
|
+
module Kapusta
|
|
6
|
+
class LSP
|
|
7
|
+
module Formatting
|
|
8
|
+
module_function
|
|
9
|
+
|
|
10
|
+
def text_edits(text, path)
|
|
11
|
+
formatted = Kapusta::Formatter.format(text, path:)
|
|
12
|
+
return [] if formatted == text
|
|
13
|
+
|
|
14
|
+
[{ range: full_range(text), newText: formatted }]
|
|
15
|
+
rescue Kapusta::Error
|
|
16
|
+
[]
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def full_range(text)
|
|
20
|
+
lines = text.split("\n", -1)
|
|
21
|
+
end_line = [lines.length - 1, 0].max
|
|
22
|
+
end_character = lines.last ? lines.last.length : 0
|
|
23
|
+
{
|
|
24
|
+
start: { line: 0, character: 0 },
|
|
25
|
+
end: { line: end_line, character: end_character }
|
|
26
|
+
}
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative '../compiler'
|
|
4
|
+
|
|
5
|
+
module Kapusta
|
|
6
|
+
class LSP
|
|
7
|
+
module Identifier
|
|
8
|
+
DELIM_CHARS = '()[]{}";`,'
|
|
9
|
+
|
|
10
|
+
module_function
|
|
11
|
+
|
|
12
|
+
def valid_local?(name)
|
|
13
|
+
return false if name.nil? || name.empty?
|
|
14
|
+
return false if name.match?(/\s/)
|
|
15
|
+
return false if name.match?(/[#{Regexp.escape(DELIM_CHARS)}]/o)
|
|
16
|
+
return false if name.match?(/\A-?\d/)
|
|
17
|
+
return false if name.include?('.')
|
|
18
|
+
return false if Kapusta::Compiler::SPECIAL_FORMS.include?(name)
|
|
19
|
+
|
|
20
|
+
true
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def valid_constant_segment?(segment)
|
|
24
|
+
!segment.nil? && segment.match?(/\A[A-Z]\w*\z/)
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|