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
|
@@ -34,22 +34,52 @@ module Kapusta
|
|
|
34
34
|
end
|
|
35
35
|
|
|
36
36
|
def emit_forms_with_headers(forms, env, current_scope, result: true)
|
|
37
|
-
|
|
37
|
+
_ = result
|
|
38
|
+
code, _next = emit_form_run(forms, 0, env, current_scope)
|
|
39
|
+
code
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def emit_form_run(forms, start, env, current_scope, header_form: nil)
|
|
43
|
+
i = start
|
|
38
44
|
codes = []
|
|
39
45
|
while i < forms.length
|
|
40
46
|
form = forms[i]
|
|
47
|
+
if end_form?(form)
|
|
48
|
+
validate_end_form!(form)
|
|
49
|
+
with_current_form(form) { emit_error!(:end_outside_header) } unless header_form
|
|
50
|
+
return [codes.join("\n"), i + 1]
|
|
51
|
+
end
|
|
52
|
+
|
|
41
53
|
if bodyless_header?(form)
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
code, env = emit_form_in_sequence(form, env, current_scope,
|
|
46
|
-
allow_method_definitions: true,
|
|
47
|
-
result_needed: result && i == forms.length - 1)
|
|
48
|
-
codes << code
|
|
49
|
-
i += 1
|
|
54
|
+
header_code, i = emit_bodyless_header(form, forms, i + 1, env)
|
|
55
|
+
codes << header_code
|
|
56
|
+
next
|
|
50
57
|
end
|
|
58
|
+
|
|
59
|
+
code, env = emit_form_in_sequence(form, env, current_scope,
|
|
60
|
+
allow_method_definitions: true,
|
|
61
|
+
result_needed: false)
|
|
62
|
+
codes << code
|
|
63
|
+
i += 1
|
|
51
64
|
end
|
|
52
|
-
|
|
65
|
+
with_current_form(header_form) { emit_error!(:unclosed_header) } if header_form
|
|
66
|
+
[codes.join("\n"), i]
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def end_form?(form)
|
|
70
|
+
form.is_a?(List) && !form.empty? && form.head.is_a?(Sym) && form.head.name == 'end'
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def validate_end_form!(form)
|
|
74
|
+
with_current_form(form) { emit_error!(:end_with_args) } if form.items.length > 1
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def validate_header_name!(form, head)
|
|
78
|
+
name_sym = head == 'module' ? form.items[1] : split_class_args(form.items[1..])[0]
|
|
79
|
+
return if constant_segments(name_sym)
|
|
80
|
+
|
|
81
|
+
code = head == 'module' ? :invalid_module_name : :invalid_class_name
|
|
82
|
+
emit_error!(code, name: name_sym.respond_to?(:name) ? name_sym.name : name_sym.inspect)
|
|
53
83
|
end
|
|
54
84
|
|
|
55
85
|
def bodyless_header?(form)
|
|
@@ -69,51 +99,63 @@ module Kapusta
|
|
|
69
99
|
end
|
|
70
100
|
end
|
|
71
101
|
|
|
72
|
-
def emit_bodyless_header(form,
|
|
102
|
+
def emit_bodyless_header(form, forms, body_start, env)
|
|
73
103
|
head = form.head.name
|
|
74
|
-
|
|
75
|
-
|
|
104
|
+
validate_header_name!(form, head)
|
|
76
105
|
if head == 'module'
|
|
106
|
+
name_sym = form.items[1]
|
|
77
107
|
inner = form.items[2..] || []
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
108
|
+
if inner.length == 1 && bodyless_header?(inner[0])
|
|
109
|
+
inner_code, next_i = emit_bodyless_header(inner[0], forms, body_start, env)
|
|
110
|
+
[emit_direct_module_header(name_sym, inner_code) || emit_module_wrapper(name_sym, inner_code), next_i]
|
|
111
|
+
else
|
|
112
|
+
body, next_i = emit_form_run(forms, body_start, env, :module, header_form: form)
|
|
113
|
+
[emit_direct_module_header(name_sym, body) || emit_module_wrapper(name_sym, body), next_i]
|
|
114
|
+
end
|
|
85
115
|
else
|
|
86
116
|
name_sym, supers, = split_class_args(form.items[1..])
|
|
87
|
-
body =
|
|
88
|
-
emit_direct_class_header(name_sym, supers, body, env) ||
|
|
117
|
+
body, next_i = emit_form_run(forms, body_start, env, :class, header_form: form)
|
|
118
|
+
code = emit_direct_class_header(name_sym, supers, body, env) ||
|
|
119
|
+
emit_class_wrapper(name_sym, supers, env, body)
|
|
120
|
+
[code, next_i]
|
|
89
121
|
end
|
|
90
122
|
end
|
|
91
123
|
|
|
92
124
|
def emit_form_in_sequence(form, env, current_scope, allow_method_definitions:, result_needed: true)
|
|
93
125
|
with_current_form(form) do
|
|
94
|
-
if
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
return [code, env] if code
|
|
99
|
-
end
|
|
126
|
+
form = lower_defn_in_sequence(form, current_scope) if defn_form?(form)
|
|
127
|
+
emit_form_body(form, env, current_scope, allow_method_definitions:, result_needed:)
|
|
128
|
+
end
|
|
129
|
+
end
|
|
100
130
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
131
|
+
def lower_defn_in_sequence(form, current_scope)
|
|
132
|
+
emit_error!(:defn_outside_header) unless %i[module class].include?(current_scope)
|
|
133
|
+
lower_defn_to_fn(form)
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
def emit_form_body(form, env, current_scope, allow_method_definitions:, result_needed:)
|
|
137
|
+
if allow_method_definitions &&
|
|
138
|
+
method_definition_form?(form) &&
|
|
139
|
+
%i[toplevel module class].include?(current_scope)
|
|
140
|
+
code, env = emit_definition_form(form, env, current_scope)
|
|
141
|
+
return [code, env] if code
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
if named_function_form?(form)
|
|
145
|
+
emit_named_fn_assignment(form, env, current_scope)
|
|
146
|
+
elsif local_form?(form)
|
|
147
|
+
code, env = emit_local_form(form, env, current_scope,
|
|
148
|
+
allow_constant: allow_method_definitions)
|
|
149
|
+
code = code.delete_suffix("\nnil") unless result_needed
|
|
150
|
+
[code, env]
|
|
151
|
+
elsif do_form?(form)
|
|
152
|
+
emit_do_form(form.rest, env, current_scope, result_needed:)
|
|
153
|
+
elsif sequence_statement_form?(form)
|
|
154
|
+
emit_sequence_statement_form(form, env, current_scope, result_needed:)
|
|
155
|
+
elsif set_new_local_form?(form, env)
|
|
156
|
+
emit_set_form(form, env, current_scope)
|
|
157
|
+
else
|
|
158
|
+
[emit_expr(form, env, current_scope), env]
|
|
117
159
|
end
|
|
118
160
|
end
|
|
119
161
|
|
|
@@ -182,6 +224,26 @@ module Kapusta
|
|
|
182
224
|
%w[fn lambda λ].include?(form.head.name) && form.items[1].is_a?(Sym)
|
|
183
225
|
end
|
|
184
226
|
|
|
227
|
+
def defn_form?(form)
|
|
228
|
+
form.is_a?(List) && !form.empty? && form.head.is_a?(Sym) && form.head.name == 'defn'
|
|
229
|
+
end
|
|
230
|
+
|
|
231
|
+
def lower_defn_to_fn(form)
|
|
232
|
+
name_sym = form.items[1]
|
|
233
|
+
emit_error!(:fn_no_params) unless name_sym.is_a?(Sym)
|
|
234
|
+
|
|
235
|
+
fn_sym = Sym.new('fn')
|
|
236
|
+
fn_sym.line = form.head.line
|
|
237
|
+
fn_sym.column = form.head.column
|
|
238
|
+
self_sym = Sym.new("self.#{name_sym.name}")
|
|
239
|
+
self_sym.line = name_sym.line
|
|
240
|
+
self_sym.column = name_sym.column
|
|
241
|
+
new_list = List.new([fn_sym, self_sym, *form.items[2..]])
|
|
242
|
+
new_list.line = form.line
|
|
243
|
+
new_list.column = form.column
|
|
244
|
+
new_list
|
|
245
|
+
end
|
|
246
|
+
|
|
185
247
|
def method_definition_form?(form)
|
|
186
248
|
named_function_form?(form)
|
|
187
249
|
end
|
|
@@ -57,9 +57,9 @@ module Kapusta
|
|
|
57
57
|
def expand(form)
|
|
58
58
|
case form
|
|
59
59
|
when List then expand_list(form)
|
|
60
|
-
when Vec then copy_position(Vec.new(form.items.map { |item| expand(item) }), form)
|
|
60
|
+
when Vec then Kapusta.copy_position(Vec.new(form.items.map { |item| expand(item) }), form)
|
|
61
61
|
when HashLit
|
|
62
|
-
copy_position(
|
|
62
|
+
Kapusta.copy_position(
|
|
63
63
|
HashLit.new(form.entries.map do |entry|
|
|
64
64
|
entry.is_a?(Array) ? [expand(entry[0]), expand(entry[1])] : entry
|
|
65
65
|
end),
|
|
@@ -70,14 +70,6 @@ module Kapusta
|
|
|
70
70
|
end
|
|
71
71
|
end
|
|
72
72
|
|
|
73
|
-
def copy_position(target, source)
|
|
74
|
-
return target unless target.respond_to?(:line=) && source.respond_to?(:line)
|
|
75
|
-
|
|
76
|
-
target.line ||= source.line
|
|
77
|
-
target.column ||= source.column
|
|
78
|
-
target
|
|
79
|
-
end
|
|
80
|
-
|
|
81
73
|
def expand_list(list)
|
|
82
74
|
return list if list.empty?
|
|
83
75
|
|
|
@@ -100,11 +92,11 @@ module Kapusta
|
|
|
100
92
|
if @macros.key?(key)
|
|
101
93
|
args = list.rest
|
|
102
94
|
result = invoke_macro(key, args)
|
|
103
|
-
return copy_position(expand(result), list)
|
|
95
|
+
return Kapusta.copy_position(expand(result), list)
|
|
104
96
|
end
|
|
105
97
|
end
|
|
106
98
|
|
|
107
|
-
copy_position(List.new(list.items.map { |item| expand(item) }), list)
|
|
99
|
+
Kapusta.copy_position(List.new(list.items.map { |item| expand(item) }), list)
|
|
108
100
|
end
|
|
109
101
|
|
|
110
102
|
def lookup_key(name)
|
|
@@ -34,15 +34,15 @@ module Kapusta
|
|
|
34
34
|
|
|
35
35
|
def lower(form)
|
|
36
36
|
case form
|
|
37
|
-
when Quasiquote then copy_position(lower_quasi(form.form), form)
|
|
37
|
+
when Quasiquote then Kapusta.copy_position(lower_quasi(form.form), form)
|
|
38
38
|
when Unquote, UnquoteSplice
|
|
39
39
|
raise @error_class, Kapusta::Errors.format(:unquote_outside_quasiquote)
|
|
40
40
|
when AutoGensym
|
|
41
41
|
raise @error_class, Kapusta::Errors.format(:auto_gensym_outside_quasiquote, name: form.name)
|
|
42
|
-
when List then copy_position(List.new(form.items.map { |item| lower(item) }), form)
|
|
43
|
-
when Vec then copy_position(Vec.new(form.items.map { |item| lower(item) }), form)
|
|
42
|
+
when List then Kapusta.copy_position(List.new(form.items.map { |item| lower(item) }), form)
|
|
43
|
+
when Vec then Kapusta.copy_position(Vec.new(form.items.map { |item| lower(item) }), form)
|
|
44
44
|
when HashLit
|
|
45
|
-
copy_position(
|
|
45
|
+
Kapusta.copy_position(
|
|
46
46
|
HashLit.new(form.entries.map do |entry|
|
|
47
47
|
entry.is_a?(Array) ? [lower(entry[0]), lower(entry[1])] : entry
|
|
48
48
|
end),
|
|
@@ -94,14 +94,6 @@ module Kapusta
|
|
|
94
94
|
form.is_a?(List) && form.head.is_a?(Sym) && FN_HEADS.include?(form.head.name)
|
|
95
95
|
end
|
|
96
96
|
|
|
97
|
-
def copy_position(target, source)
|
|
98
|
-
return target unless target.respond_to?(:line=) && source.respond_to?(:line)
|
|
99
|
-
|
|
100
|
-
target.line ||= source.line
|
|
101
|
-
target.column ||= source.column
|
|
102
|
-
target
|
|
103
|
-
end
|
|
104
|
-
|
|
105
97
|
def lower_quasi(form)
|
|
106
98
|
case form
|
|
107
99
|
when AutoGensym then gensym_local_for(form.name)
|
|
@@ -12,9 +12,9 @@ module Kapusta
|
|
|
12
12
|
def normalize(form)
|
|
13
13
|
case form
|
|
14
14
|
when List then normalize_list(form)
|
|
15
|
-
when Vec then
|
|
15
|
+
when Vec then Kapusta.copy_position(Vec.new(form.items.map { |item| normalize(item) }), form)
|
|
16
16
|
when HashLit
|
|
17
|
-
|
|
17
|
+
Kapusta.copy_position(
|
|
18
18
|
HashLit.new(form.pairs.map { |key, value| [normalize_hash_key(key), normalize(value)] }),
|
|
19
19
|
form
|
|
20
20
|
)
|
|
@@ -37,7 +37,7 @@ module Kapusta
|
|
|
37
37
|
|
|
38
38
|
head = list.head
|
|
39
39
|
items = list.items.map { |item| normalize(item) }
|
|
40
|
-
return
|
|
40
|
+
return Kapusta.copy_position(List.new(items), list) unless head.is_a?(Sym)
|
|
41
41
|
|
|
42
42
|
case head.name
|
|
43
43
|
when 'when'
|
|
@@ -45,39 +45,31 @@ module Kapusta
|
|
|
45
45
|
|
|
46
46
|
cond = items[1]
|
|
47
47
|
body = wrap_do(items[2..])
|
|
48
|
-
|
|
48
|
+
Kapusta.copy_position(List.new([Sym.new('if'), cond, body]), list)
|
|
49
49
|
when 'unless'
|
|
50
50
|
raise compiler_error(:when_no_body, list, form: head.name) if items[2..].empty?
|
|
51
51
|
|
|
52
52
|
cond = items[1]
|
|
53
53
|
body = wrap_do(items[2..])
|
|
54
|
-
|
|
54
|
+
Kapusta.copy_position(List.new([Sym.new('if'), List.new([Sym.new('not'), cond]), body]), list)
|
|
55
55
|
when 'tset'
|
|
56
56
|
raise compiler_error(:tset_no_value, list) if items.length < 4
|
|
57
57
|
|
|
58
|
-
|
|
58
|
+
Kapusta.copy_position(
|
|
59
59
|
List.new([Sym.new('set'), List.new([Sym.new('.'), items[1], items[2]]), items[3]]),
|
|
60
60
|
list
|
|
61
61
|
)
|
|
62
62
|
when *LuaCompat::SPECIAL_FORMS
|
|
63
63
|
normalize_lua_compat_form(head.name, items)
|
|
64
64
|
when '->', '->>', '-?>', '-?>>'
|
|
65
|
-
|
|
65
|
+
Kapusta.copy_position(normalize(thread(items[1..], head.name)), list)
|
|
66
66
|
when 'doto'
|
|
67
|
-
|
|
67
|
+
Kapusta.copy_position(normalize(doto(items[1..])), list)
|
|
68
68
|
else
|
|
69
|
-
|
|
69
|
+
Kapusta.copy_position(List.new(items), list)
|
|
70
70
|
end
|
|
71
71
|
end
|
|
72
72
|
|
|
73
|
-
def inherit_position(target, source)
|
|
74
|
-
return target unless target.respond_to?(:line=) && source.respond_to?(:line)
|
|
75
|
-
|
|
76
|
-
target.line ||= source.line
|
|
77
|
-
target.column ||= source.column
|
|
78
|
-
target
|
|
79
|
-
end
|
|
80
|
-
|
|
81
73
|
def compiler_error(code, form, **args)
|
|
82
74
|
line = form.respond_to?(:line) ? form.line : nil
|
|
83
75
|
column = form.respond_to?(:column) ? form.column : nil
|
data/lib/kapusta/compiler.rb
CHANGED
|
@@ -10,7 +10,7 @@ module Kapusta
|
|
|
10
10
|
module Compiler
|
|
11
11
|
class Error < Kapusta::Error; end
|
|
12
12
|
CORE_SPECIAL_FORMS = %w[
|
|
13
|
-
fn lambda λ let local var global set if when unless case match
|
|
13
|
+
fn defn lambda λ let local var global set if when unless case match
|
|
14
14
|
while for each do values
|
|
15
15
|
-> ->> -?> -?>> doto
|
|
16
16
|
icollect collect fcollect accumulate faccumulate
|
|
@@ -19,7 +19,7 @@ module Kapusta
|
|
|
19
19
|
..
|
|
20
20
|
length
|
|
21
21
|
require
|
|
22
|
-
module class
|
|
22
|
+
module class end
|
|
23
23
|
try catch finally
|
|
24
24
|
raise
|
|
25
25
|
ivar cvar gvar
|
data/lib/kapusta/errors.rb
CHANGED
|
@@ -25,6 +25,10 @@ module Kapusta
|
|
|
25
25
|
dot_no_args: 'expected table argument',
|
|
26
26
|
each_no_binding: 'expected binding table',
|
|
27
27
|
empty_call: 'expected a function, macro, or special to call',
|
|
28
|
+
defn_outside_header: 'defn outside class or module',
|
|
29
|
+
end_outside_header: 'end outside class or module',
|
|
30
|
+
end_with_args: 'end takes no arguments',
|
|
31
|
+
unclosed_header: 'class or module not closed with (end)',
|
|
28
32
|
expected_var: 'expected var %{name}',
|
|
29
33
|
fn_no_params: 'expected parameters table',
|
|
30
34
|
global_arity: 'expected name and value',
|
data/lib/kapusta/formatter.rb
CHANGED
|
@@ -266,7 +266,7 @@ module Kapusta
|
|
|
266
266
|
raw_args = list_raw_rest(list)
|
|
267
267
|
|
|
268
268
|
case head_name
|
|
269
|
-
when 'fn', 'lambda', 'λ', 'macro' then render_fn(head_name, list, indent, top_level:)
|
|
269
|
+
when 'fn', 'defn', 'lambda', 'λ', 'macro' then render_fn(head_name, list, indent, top_level:)
|
|
270
270
|
when 'let' then render_let(list, indent)
|
|
271
271
|
when 'do', 'finally' then render_prefix_body_form(head_name, [], raw_args, indent)
|
|
272
272
|
when 'try' then render_try(list, indent)
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require_relative 'rename'
|
|
4
|
+
require_relative '../reader'
|
|
5
|
+
require_relative 'scope_walker'
|
|
4
6
|
|
|
5
7
|
module Kapusta
|
|
6
8
|
class LSP
|
|
@@ -8,6 +10,9 @@ module Kapusta
|
|
|
8
10
|
module_function
|
|
9
11
|
|
|
10
12
|
def find(uri, text, line_zero, character, workspace_index:)
|
|
13
|
+
marker = end_marker_at(text, line_zero, character)
|
|
14
|
+
return location_for_binding(uri, marker.target) if marker&.target
|
|
15
|
+
|
|
11
16
|
target = Rename.locate(text, line_zero, character)
|
|
12
17
|
return unless target
|
|
13
18
|
|
|
@@ -23,6 +28,18 @@ module Kapusta
|
|
|
23
28
|
end
|
|
24
29
|
end
|
|
25
30
|
|
|
31
|
+
def end_marker_at(text, line_zero, character)
|
|
32
|
+
forms = Reader.read_all(text)
|
|
33
|
+
walker = ScopeWalker.analyze(forms)
|
|
34
|
+
line = line_zero + 1
|
|
35
|
+
col = character + 1
|
|
36
|
+
walker.end_markers.find do |m|
|
|
37
|
+
m.line == line && col >= m.column && col <= m.end_column
|
|
38
|
+
end
|
|
39
|
+
rescue Kapusta::Error
|
|
40
|
+
nil
|
|
41
|
+
end
|
|
42
|
+
|
|
26
43
|
def locations_for_macro(uri, binding, workspace_index)
|
|
27
44
|
return unless binding
|
|
28
45
|
|
data/lib/kapusta/lsp/rename.rb
CHANGED
|
@@ -344,7 +344,9 @@ module Kapusta
|
|
|
344
344
|
end
|
|
345
345
|
|
|
346
346
|
def rename_constant(target, new_name, workspace_index)
|
|
347
|
-
|
|
347
|
+
unless Identifier.valid_constant_segment?(new_name)
|
|
348
|
+
return error("class and module names must start with an uppercase letter (got #{new_name.inspect})")
|
|
349
|
+
end
|
|
348
350
|
|
|
349
351
|
prefix = target.segment_prefix
|
|
350
352
|
seg_index = target.segment_index
|
|
@@ -15,17 +15,24 @@ module Kapusta
|
|
|
15
15
|
bindings[name] || parent&.lookup(name)
|
|
16
16
|
end
|
|
17
17
|
end
|
|
18
|
-
|
|
19
|
-
SKIPPED_HEADS = %w[macros quasi-sym quasi-list
|
|
20
|
-
quasi-list-tail quasi-vec quasi-vec-tail quasi-hash quasi-gensym].freeze
|
|
18
|
+
EndMarker = Struct.new(:line, :column, :end_column, :target, keyword_init: true)
|
|
21
19
|
|
|
22
20
|
DISPATCHERS = {
|
|
21
|
+
'macros' => :skip,
|
|
22
|
+
'quasi-sym' => :skip,
|
|
23
|
+
'quasi-list' => :skip,
|
|
24
|
+
'quasi-list-tail' => :skip,
|
|
25
|
+
'quasi-vec' => :skip,
|
|
26
|
+
'quasi-vec-tail' => :skip,
|
|
27
|
+
'quasi-hash' => :skip,
|
|
28
|
+
'quasi-gensym' => :skip,
|
|
23
29
|
'let' => :walk_let,
|
|
24
30
|
'local' => :walk_local_var,
|
|
25
31
|
'var' => :walk_local_var,
|
|
26
32
|
'global' => :walk_global,
|
|
27
33
|
'set' => :walk_set,
|
|
28
34
|
'fn' => :walk_fn,
|
|
35
|
+
'defn' => :walk_fn,
|
|
29
36
|
'lambda' => :walk_fn,
|
|
30
37
|
'λ' => :walk_fn,
|
|
31
38
|
'for' => :walk_for,
|
|
@@ -48,7 +55,7 @@ module Kapusta
|
|
|
48
55
|
'gvar' => :walk_sigil_form
|
|
49
56
|
}.freeze
|
|
50
57
|
|
|
51
|
-
attr_reader :bindings, :references, :root_scope
|
|
58
|
+
attr_reader :bindings, :references, :root_scope, :end_markers
|
|
52
59
|
|
|
53
60
|
def self.analyze(forms)
|
|
54
61
|
walker = new
|
|
@@ -59,6 +66,7 @@ module Kapusta
|
|
|
59
66
|
def initialize
|
|
60
67
|
@bindings = []
|
|
61
68
|
@references = []
|
|
69
|
+
@end_markers = []
|
|
62
70
|
@scope_seq = 0
|
|
63
71
|
@root_scope = make_scope(nil, :file)
|
|
64
72
|
@gvar_scope = make_scope(nil, :gvars)
|
|
@@ -67,17 +75,43 @@ module Kapusta
|
|
|
67
75
|
end
|
|
68
76
|
|
|
69
77
|
def walk_top(forms)
|
|
70
|
-
|
|
78
|
+
walk_form_run(forms, 0, @root_scope)
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def walk_form_run(forms, start, scope, header_target: nil)
|
|
82
|
+
i = start
|
|
71
83
|
while i < forms.length
|
|
72
84
|
form = forms[i]
|
|
85
|
+
if end_form?(form)
|
|
86
|
+
record_end_marker(form, header_target) if header_target
|
|
87
|
+
return i + 1
|
|
88
|
+
end
|
|
89
|
+
|
|
73
90
|
if bodyless_header?(form)
|
|
74
|
-
walk_bodyless_header(form, forms
|
|
75
|
-
|
|
91
|
+
i = walk_bodyless_header(form, forms, i + 1, scope)
|
|
92
|
+
next
|
|
76
93
|
end
|
|
77
94
|
|
|
78
|
-
walk_form(form,
|
|
95
|
+
walk_form(form, scope)
|
|
79
96
|
i += 1
|
|
80
97
|
end
|
|
98
|
+
i
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def record_end_marker(form, target)
|
|
102
|
+
head = form.head
|
|
103
|
+
return unless head.is_a?(Sym) && head.respond_to?(:line) && head.line
|
|
104
|
+
|
|
105
|
+
@end_markers << EndMarker.new(
|
|
106
|
+
line: head.line,
|
|
107
|
+
column: head.column,
|
|
108
|
+
end_column: head.column + head.name.length,
|
|
109
|
+
target:
|
|
110
|
+
)
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
def end_form?(form)
|
|
114
|
+
form.is_a?(List) && !form.empty? && form.head.is_a?(Sym) && form.head.name == 'end'
|
|
81
115
|
end
|
|
82
116
|
|
|
83
117
|
def binding_at(line, column)
|
|
@@ -120,25 +154,25 @@ module Kapusta
|
|
|
120
154
|
end
|
|
121
155
|
end
|
|
122
156
|
|
|
123
|
-
def walk_bodyless_header(form,
|
|
157
|
+
def walk_bodyless_header(form, forms, body_start, scope)
|
|
124
158
|
case form.head.name
|
|
125
159
|
when 'module'
|
|
126
160
|
name_sym = form.items[1]
|
|
127
|
-
add_constant_binding(name_sym, scope, :module)
|
|
161
|
+
binding = name_sym.is_a?(Sym) ? add_constant_binding(name_sym, scope, :module) : nil
|
|
128
162
|
body = form.items[2..] || []
|
|
129
163
|
inside_module_or_class do
|
|
130
164
|
if body.length == 1 && bodyless_header?(body[0])
|
|
131
|
-
walk_bodyless_header(body[0],
|
|
165
|
+
walk_bodyless_header(body[0], forms, body_start, scope)
|
|
132
166
|
else
|
|
133
|
-
|
|
167
|
+
walk_form_run(forms, body_start, scope, header_target: binding)
|
|
134
168
|
end
|
|
135
169
|
end
|
|
136
170
|
when 'class'
|
|
137
171
|
name_sym, supers, = split_class_args(form.items[1..] || [])
|
|
138
172
|
supers&.items&.each { |item| walk_form(item, scope) }
|
|
139
|
-
add_constant_binding(name_sym, scope, :class)
|
|
173
|
+
binding = name_sym.is_a?(Sym) ? add_constant_binding(name_sym, scope, :class) : nil
|
|
140
174
|
inside_class do
|
|
141
|
-
|
|
175
|
+
walk_form_run(forms, body_start, scope, header_target: binding)
|
|
142
176
|
end
|
|
143
177
|
end
|
|
144
178
|
end
|
|
@@ -217,10 +251,12 @@ module Kapusta
|
|
|
217
251
|
return
|
|
218
252
|
end
|
|
219
253
|
|
|
220
|
-
return if SKIPPED_HEADS.include?(head.name)
|
|
221
|
-
|
|
222
254
|
dispatcher = DISPATCHERS[head.name]
|
|
223
|
-
|
|
255
|
+
if dispatcher
|
|
256
|
+
return if dispatcher == :skip
|
|
257
|
+
|
|
258
|
+
return send(dispatcher, list, scope)
|
|
259
|
+
end
|
|
224
260
|
|
|
225
261
|
list.items.each { |item| walk_form(item, scope) }
|
|
226
262
|
end
|
|
@@ -351,28 +387,30 @@ module Kapusta
|
|
|
351
387
|
def walk_fn(list, scope)
|
|
352
388
|
items = list.items
|
|
353
389
|
if items[1].is_a?(Vec)
|
|
390
|
+
name_sym = nil
|
|
354
391
|
params = items[1]
|
|
355
392
|
body = items[2..]
|
|
356
|
-
fn_scope = make_scope(scope, :fn)
|
|
357
|
-
bind_param_vec(params, fn_scope)
|
|
358
|
-
body.each { |form| walk_form(form, fn_scope) }
|
|
359
393
|
elsif items[1].is_a?(Sym) && items[2].is_a?(Vec)
|
|
360
394
|
name_sym = items[1]
|
|
361
395
|
params = items[2]
|
|
362
396
|
body = items[3..]
|
|
397
|
+
else
|
|
398
|
+
items[1..]&.each { |item| walk_form(item, scope) }
|
|
399
|
+
return
|
|
400
|
+
end
|
|
401
|
+
|
|
402
|
+
fn_scope = make_scope(scope, :fn)
|
|
403
|
+
if name_sym
|
|
363
404
|
kind = if method_definition_context?
|
|
364
405
|
:method
|
|
365
406
|
else
|
|
366
407
|
(scope == @root_scope ? :toplevel_fn : :fn_local)
|
|
367
408
|
end
|
|
368
|
-
fn_scope = make_scope(scope, :fn)
|
|
369
409
|
binding = add_binding(name_sym, scope, kind, lexical: kind != :method)
|
|
370
410
|
fn_scope.bindings[name_sym.name] = binding unless kind == :method
|
|
371
|
-
bind_param_vec(params, fn_scope)
|
|
372
|
-
body.each { |form| walk_form(form, fn_scope) }
|
|
373
|
-
else
|
|
374
|
-
items[1..]&.each { |item| walk_form(item, scope) }
|
|
375
411
|
end
|
|
412
|
+
bind_param_vec(params, fn_scope)
|
|
413
|
+
body.each { |form| walk_form(form, fn_scope) }
|
|
376
414
|
end
|
|
377
415
|
|
|
378
416
|
def method_definition_context?
|
|
@@ -557,40 +595,35 @@ module Kapusta
|
|
|
557
595
|
end
|
|
558
596
|
end
|
|
559
597
|
|
|
560
|
-
def
|
|
561
|
-
items = vec.items
|
|
598
|
+
def each_pattern_item(items)
|
|
562
599
|
i = 0
|
|
563
600
|
while i < items.length
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
rest = items[i + 1]
|
|
567
|
-
bind_pattern(rest, scope, :fn_param) if rest.is_a?(Sym) && rest.name != '_'
|
|
601
|
+
if items[i].is_a?(Sym) && items[i].name == '&'
|
|
602
|
+
yield :rest, items[i + 1]
|
|
568
603
|
i += 2
|
|
569
|
-
elsif item.is_a?(Sym) && ['...', '_'].include?(item.name)
|
|
570
|
-
i += 1
|
|
571
604
|
else
|
|
572
|
-
|
|
605
|
+
yield :item, items[i]
|
|
573
606
|
i += 1
|
|
574
607
|
end
|
|
575
608
|
end
|
|
576
609
|
end
|
|
577
610
|
|
|
578
|
-
def
|
|
579
|
-
items
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
item
|
|
583
|
-
|
|
584
|
-
rest = items[i + 1]
|
|
585
|
-
bind_pattern(rest, scope, kind) if rest
|
|
586
|
-
i += 2
|
|
587
|
-
else
|
|
588
|
-
bind_pattern(item, scope, kind)
|
|
589
|
-
i += 1
|
|
611
|
+
def bind_param_vec(vec, scope)
|
|
612
|
+
each_pattern_item(vec.items) do |kind, item|
|
|
613
|
+
if kind == :rest
|
|
614
|
+
bind_pattern(item, scope, :fn_param) if item.is_a?(Sym) && item.name != '_'
|
|
615
|
+
elsif !(item.is_a?(Sym) && ['...', '_'].include?(item.name))
|
|
616
|
+
bind_pattern(item, scope, :fn_param)
|
|
590
617
|
end
|
|
591
618
|
end
|
|
592
619
|
end
|
|
593
620
|
|
|
621
|
+
def bind_vec_pattern(vec, scope, kind)
|
|
622
|
+
each_pattern_item(vec.items) do |item_kind, item|
|
|
623
|
+
bind_pattern(item, scope, kind) if item_kind == :item || item
|
|
624
|
+
end
|
|
625
|
+
end
|
|
626
|
+
|
|
594
627
|
def bind_hash_pattern(hash, scope, kind)
|
|
595
628
|
hash.pairs.each do |pair|
|
|
596
629
|
bind_pattern(pair[1], scope, kind)
|