kapusta 0.11.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/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 +1 -0
- data/lib/kapusta/compiler/emitter/interop.rb +21 -13
- data/lib/kapusta/compiler/emitter/patterns.rb +29 -52
- data/lib/kapusta/compiler/emitter/support.rb +52 -22
- 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 -23
- data/lib/kapusta/errors.rb +1 -0
- data/lib/kapusta/lsp/scope_walker.rb +39 -35
- data/lib/kapusta/lsp/workspace_index.rb +2 -13
- data/lib/kapusta/lsp.rb +15 -15
- data/lib/kapusta/support.rb +8 -0
- data/lib/kapusta/version.rb +1 -1
- data/spec/examples_errors_spec.rb +5 -0
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 85bcd0988a353fb851e5ff955c2b5196f39193e1c3c8fb6ca4a111d6260e774d
|
|
4
|
+
data.tar.gz: 80efbc138038947b866eaac96ef21d5cdccd04a461a25f2e17cbd7ce925ceaec
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 16d02e652b9221990ac027423e677a8f27c285d582c6bfb368f585140305e1c48dff5add4a8491a7996b5be99e34efd761e7fb280d8932fc6548622939b166b4
|
|
7
|
+
data.tar.gz: 8ab10f9991b7029d9366e47d434a9e9166568b870369f1fe585007fbe2cf9b750c84e8eb3b8fb015db255977eef80fd3255750fa99006d577a9564b640d84319
|
|
@@ -148,10 +148,7 @@ module Kapusta
|
|
|
148
148
|
return unless ruby_name
|
|
149
149
|
return if captures_outer_binding?(body, env, pattern_names(pattern))
|
|
150
150
|
|
|
151
|
-
|
|
152
|
-
params = pattern.items.map { |sym| define_local(body_env, sym.name, shadow: true) }
|
|
153
|
-
body_code, = emit_sequence(body, body_env, :toplevel,
|
|
154
|
-
allow_method_definitions: false, result: false)
|
|
151
|
+
params, body_code = build_simple_block_parts(pattern, body, env, :toplevel)
|
|
155
152
|
header = params.empty? ? "def #{ruby_name}" : "def #{ruby_name}(#{params.join(', ')})"
|
|
156
153
|
[
|
|
157
154
|
header,
|
|
@@ -111,27 +111,30 @@ module Kapusta
|
|
|
111
111
|
end
|
|
112
112
|
|
|
113
113
|
def try_emit_native_case(value_var, clauses, env, current_scope, mode)
|
|
114
|
+
arms = collect_case_arms(clauses) do |pattern, body, where_guards|
|
|
115
|
+
try_native_arm(pattern, body, where_guards, env, current_scope, mode)
|
|
116
|
+
end
|
|
117
|
+
return unless arms
|
|
118
|
+
|
|
119
|
+
arms << ['else', indent('nil')].join("\n") unless wildcard_last?(clauses)
|
|
120
|
+
["case #{value_var}", *arms, 'end'].join("\n")
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
def collect_case_arms(clauses)
|
|
114
124
|
arms = []
|
|
115
125
|
i = 0
|
|
116
126
|
while i < clauses.length
|
|
117
127
|
pattern = clauses[i]
|
|
118
128
|
body = clauses[i + 1]
|
|
119
|
-
inner, where_guards =
|
|
120
|
-
[pattern.items[1], pattern.items[2..]]
|
|
121
|
-
else
|
|
122
|
-
[pattern, []]
|
|
123
|
-
end
|
|
129
|
+
inner, where_guards = extract_pattern_and_guards(pattern)
|
|
124
130
|
sub_patterns = or_pattern?(inner) ? inner.items[1..] : [inner]
|
|
125
|
-
sub_arms = sub_patterns.map
|
|
126
|
-
try_native_arm(sub, body, where_guards, env, current_scope, mode)
|
|
127
|
-
end
|
|
131
|
+
sub_arms = sub_patterns.map { |sub| yield sub, body, where_guards }
|
|
128
132
|
return if sub_arms.any?(&:nil?)
|
|
129
133
|
|
|
130
134
|
arms.concat(sub_arms)
|
|
131
135
|
i += 2
|
|
132
136
|
end
|
|
133
|
-
arms
|
|
134
|
-
["case #{value_var}", *arms, 'end'].join("\n")
|
|
137
|
+
arms
|
|
135
138
|
end
|
|
136
139
|
|
|
137
140
|
def try_native_arm(pattern, body, where_guards, env, current_scope, mode)
|
|
@@ -149,25 +152,11 @@ module Kapusta
|
|
|
149
152
|
end
|
|
150
153
|
|
|
151
154
|
def try_emit_compat_case(value_var, clauses, env, current_scope, mode)
|
|
152
|
-
arms =
|
|
153
|
-
|
|
154
|
-
while i < clauses.length
|
|
155
|
-
pattern = clauses[i]
|
|
156
|
-
body = clauses[i + 1]
|
|
157
|
-
inner, where_guards = if where_pattern?(pattern)
|
|
158
|
-
[pattern.items[1], pattern.items[2..]]
|
|
159
|
-
else
|
|
160
|
-
[pattern, []]
|
|
161
|
-
end
|
|
162
|
-
sub_patterns = or_pattern?(inner) ? inner.items[1..] : [inner]
|
|
163
|
-
sub_arms = sub_patterns.map do |sub|
|
|
164
|
-
try_compat_arm(sub, body, where_guards, value_var, env, current_scope, mode)
|
|
165
|
-
end
|
|
166
|
-
return if sub_arms.any?(&:nil?)
|
|
167
|
-
|
|
168
|
-
arms.concat(sub_arms)
|
|
169
|
-
i += 2
|
|
155
|
+
arms = collect_case_arms(clauses) do |pattern, body, where_guards|
|
|
156
|
+
try_compat_arm(pattern, body, where_guards, value_var, env, current_scope, mode)
|
|
170
157
|
end
|
|
158
|
+
return unless arms
|
|
159
|
+
|
|
171
160
|
emit_compat_case_lines(arms)
|
|
172
161
|
end
|
|
173
162
|
|
|
@@ -176,6 +165,12 @@ module Kapusta
|
|
|
176
165
|
last_pattern.is_a?(Sym) && last_pattern.name == '_'
|
|
177
166
|
end
|
|
178
167
|
|
|
168
|
+
def extract_pattern_and_guards(pattern)
|
|
169
|
+
return [pattern, []] unless where_pattern?(pattern)
|
|
170
|
+
|
|
171
|
+
[pattern.items[1], pattern.items[2..]]
|
|
172
|
+
end
|
|
173
|
+
|
|
179
174
|
def try_compat_arm(pattern, body, where_guards, value_var, env, current_scope, mode)
|
|
180
175
|
allow_pins = !where_guards.empty? && mode == :case
|
|
181
176
|
arm_env = env.child
|
|
@@ -185,11 +180,11 @@ module Kapusta
|
|
|
185
180
|
where_guard_codes = where_guards.map { |g| emit_expr(g, arm_env, current_scope) }
|
|
186
181
|
if where_guard_codes.empty?
|
|
187
182
|
guard_codes = plan[:conditions]
|
|
188
|
-
prelude
|
|
183
|
+
prelude = plan[:prelude]
|
|
189
184
|
else
|
|
190
185
|
prelude_guards = plan[:prelude].map { |line| "begin #{line}; true end" }
|
|
191
186
|
guard_codes = plan[:conditions] + prelude_guards + where_guard_codes
|
|
192
|
-
prelude
|
|
187
|
+
prelude = []
|
|
193
188
|
end
|
|
194
189
|
body_code = emit_expr(body, arm_env, current_scope)
|
|
195
190
|
[guard_codes, prelude, body_code]
|
|
@@ -84,6 +84,7 @@ module Kapusta
|
|
|
84
84
|
when 'module' then emit_module_expr(args, env)
|
|
85
85
|
when 'class' then emit_class_expr(args, env)
|
|
86
86
|
when 'end' then emit_error!(:end_outside_header)
|
|
87
|
+
when 'defn' then emit_error!(:defn_outside_header)
|
|
87
88
|
when 'try' then emit_try(args, env, current_scope)
|
|
88
89
|
when 'raise' then emit_raise(args, env, current_scope)
|
|
89
90
|
when 'ivar' then "@#{Kapusta.kebab_to_snake(args[0].name)}"
|
|
@@ -483,20 +483,28 @@ module Kapusta
|
|
|
483
483
|
Kapusta.kebab_to_snake(name).gsub(/[^a-zA-Z0-9_]/, '_')
|
|
484
484
|
end
|
|
485
485
|
|
|
486
|
+
SIMPLE_EXPRESSION_PATTERNS = [
|
|
487
|
+
/\A[a-z_]\w*\z/, # local
|
|
488
|
+
/\A@@?[a-z_]\w*\z/, # @ivar / @@cvar
|
|
489
|
+
/\A\$[a-zA-Z_]\w*\z/, # $gvar
|
|
490
|
+
/\A[A-Z]\w*(?:::[A-Z]\w*)*\z/, # Constant / A::B::C
|
|
491
|
+
/\A[a-z_]\w*[!?=]?\([^()\n]*\)\z/, # bare call foo(...)
|
|
492
|
+
/\A-?\d+(?:\.\d+)?\z/, # number
|
|
493
|
+
/\A[a-z_]\w*(?:\.[a-z_]\w*[!?=]?(?:\([^()\n]*\))?|\[[^\[\]]*\])+\z/, # local + .m/[k] chain
|
|
494
|
+
/\A[A-Z]\w*(?:::[A-Z]\w*)*(?:\.[a-z_]\w*[!?=]?(?:\([^()\n]*\))?|\[[^\[\]]*\])+\z/, # const + chain
|
|
495
|
+
/\A:[a-zA-Z_]\w*[!?=]?\z/, # :symbol
|
|
496
|
+
/\A"(?:[^"\\]|\\.)*"\z/, # "string"
|
|
497
|
+
/\A'(?:[^'\\]|\\.)*'\z/, # 'string'
|
|
498
|
+
/\A\[[^\[\]\n]*\]\z/ # [flat, array]
|
|
499
|
+
].freeze
|
|
500
|
+
private_constant :SIMPLE_EXPRESSION_PATTERNS
|
|
501
|
+
|
|
502
|
+
SIMPLE_EXPRESSION_KEYWORDS = %w[nil true false self].freeze
|
|
503
|
+
private_constant :SIMPLE_EXPRESSION_KEYWORDS
|
|
504
|
+
|
|
486
505
|
def simple_expression?(code)
|
|
487
|
-
code.match?(
|
|
488
|
-
|
|
489
|
-
code.match?(/\A\$[a-zA-Z_]\w*\z/) ||
|
|
490
|
-
code.match?(/\A[A-Z]\w*(?:::[A-Z]\w*)*\z/) ||
|
|
491
|
-
code.match?(/\A[a-z_]\w*[!?=]?\([^()\n]*\)\z/) ||
|
|
492
|
-
code.match?(/\A-?\d+(?:\.\d+)?\z/) ||
|
|
493
|
-
code.match?(/\A[a-z_]\w*(?:\.[a-z_]\w*[!?=]?(?:\([^()\n]*\))?|\[[^\[\]]*\])+\z/) ||
|
|
494
|
-
code.match?(/\A[A-Z]\w*(?:::[A-Z]\w*)*(?:\.[a-z_]\w*[!?=]?(?:\([^()\n]*\))?|\[[^\[\]]*\])+\z/) ||
|
|
495
|
-
code.match?(/\A:[a-zA-Z_]\w*[!?=]?\z/) ||
|
|
496
|
-
code.match?(/\A"(?:[^"\\]|\\.)*"\z/) ||
|
|
497
|
-
code.match?(/\A'(?:[^'\\]|\\.)*'\z/) ||
|
|
498
|
-
code.match?(/\A\[[^\[\]\n]*\]\z/) ||
|
|
499
|
-
%w[nil true false self].include?(code) ||
|
|
506
|
+
SIMPLE_EXPRESSION_PATTERNS.any? { |re| code.match?(re) } ||
|
|
507
|
+
SIMPLE_EXPRESSION_KEYWORDS.include?(code) ||
|
|
500
508
|
negation_simple?(code)
|
|
501
509
|
end
|
|
502
510
|
|
|
@@ -56,20 +56,13 @@ module Kapusta
|
|
|
56
56
|
parts = []
|
|
57
57
|
deferred = []
|
|
58
58
|
current_env = env
|
|
59
|
-
items
|
|
60
|
-
|
|
61
|
-
while i < items.length
|
|
62
|
-
if items[i].is_a?(Sym) && items[i].name == '&'
|
|
63
|
-
sub = items[i + 1]
|
|
64
|
-
raise PatternNotTranslatable unless sub.is_a?(Sym)
|
|
65
|
-
|
|
59
|
+
each_pattern_item(pattern.items) do |kind, sub|
|
|
60
|
+
if kind == :rest
|
|
66
61
|
parts << native_rest_target(sub, current_env)
|
|
67
|
-
i += 2
|
|
68
62
|
else
|
|
69
|
-
code, current_env, follow_up = native_destructure_target(
|
|
63
|
+
code, current_env, follow_up = native_destructure_target(sub, current_env, allow_follow_up: true)
|
|
70
64
|
parts << code
|
|
71
65
|
deferred << follow_up if follow_up
|
|
72
|
-
i += 1
|
|
73
66
|
end
|
|
74
67
|
end
|
|
75
68
|
if deferred.empty?
|
|
@@ -122,17 +115,13 @@ module Kapusta
|
|
|
122
115
|
inner = []
|
|
123
116
|
current = env
|
|
124
117
|
deferred = []
|
|
125
|
-
items
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
if items[i].is_a?(Sym) && items[i].name == '&'
|
|
129
|
-
inner << native_rest_target(items[i + 1], current)
|
|
130
|
-
i += 2
|
|
118
|
+
each_pattern_item(pattern.items) do |kind, sub|
|
|
119
|
+
if kind == :rest
|
|
120
|
+
inner << native_rest_target(sub, current)
|
|
131
121
|
else
|
|
132
|
-
code, current, follow_up = native_destructure_target(
|
|
122
|
+
code, current, follow_up = native_destructure_target(sub, current)
|
|
133
123
|
inner << code
|
|
134
124
|
deferred << follow_up if follow_up
|
|
135
|
-
i += 1
|
|
136
125
|
end
|
|
137
126
|
end
|
|
138
127
|
raise PatternNotTranslatable unless deferred.empty?
|
|
@@ -225,36 +214,26 @@ module Kapusta
|
|
|
225
214
|
state[:conditions] << "#{value_code}.length >= #{min_length}"
|
|
226
215
|
|
|
227
216
|
index = 0
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
if rest_pattern_marker?(items, i)
|
|
231
|
-
sub = items[i + 1]
|
|
217
|
+
each_pattern_item(items) do |kind, sub|
|
|
218
|
+
if kind == :rest
|
|
232
219
|
raise PatternNotTranslatable unless sub.is_a?(Sym)
|
|
233
220
|
|
|
234
221
|
unless sub.name == '_'
|
|
235
222
|
ruby = define_local(arm_env, sub.name)
|
|
236
223
|
state[:prelude] << "#{ruby} = #{value_code}[#{index}..]"
|
|
237
224
|
end
|
|
238
|
-
i += 2
|
|
239
225
|
else
|
|
240
|
-
compile_compat_pattern(
|
|
226
|
+
compile_compat_pattern(sub, "#{value_code}[#{index}]", env, arm_env,
|
|
241
227
|
mode:, allow_pins:, state:)
|
|
242
228
|
index += 1
|
|
243
|
-
i += 1
|
|
244
229
|
end
|
|
245
230
|
end
|
|
246
231
|
end
|
|
247
232
|
|
|
248
233
|
def compat_sequence_min_length(items)
|
|
249
234
|
count = 0
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
if rest_pattern_marker?(items, i)
|
|
253
|
-
i += 2
|
|
254
|
-
else
|
|
255
|
-
count += 1
|
|
256
|
-
i += 1
|
|
257
|
-
end
|
|
235
|
+
each_pattern_item(items) do |kind, _sub|
|
|
236
|
+
count += 1 if kind == :item
|
|
258
237
|
end
|
|
259
238
|
count
|
|
260
239
|
end
|
|
@@ -346,11 +325,9 @@ module Kapusta
|
|
|
346
325
|
def compile_native_sequence(items, env, mode:, allow_pins:, state:)
|
|
347
326
|
parts = []
|
|
348
327
|
has_rest = false
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
if rest_pattern_marker?(items, i)
|
|
328
|
+
each_pattern_item(items) do |kind, sub|
|
|
329
|
+
if kind == :rest
|
|
352
330
|
has_rest = true
|
|
353
|
-
sub = items[i + 1]
|
|
354
331
|
raise PatternNotTranslatable unless sub.is_a?(Sym)
|
|
355
332
|
|
|
356
333
|
if sub.name == '_'
|
|
@@ -360,10 +337,8 @@ module Kapusta
|
|
|
360
337
|
state[:binding_names] << sub.name
|
|
361
338
|
parts << "*#{sanitize_local(sub.name)}"
|
|
362
339
|
end
|
|
363
|
-
i += 2
|
|
364
340
|
else
|
|
365
|
-
parts << compile_native_pattern(
|
|
366
|
-
i += 1
|
|
341
|
+
parts << compile_native_pattern(sub, env, mode:, allow_pins:, state:)
|
|
367
342
|
end
|
|
368
343
|
end
|
|
369
344
|
parts << '*' unless has_rest
|
|
@@ -464,16 +439,8 @@ module Kapusta
|
|
|
464
439
|
pattern.name == '_' ? [] : [pattern.name]
|
|
465
440
|
when Vec
|
|
466
441
|
names = []
|
|
467
|
-
items
|
|
468
|
-
|
|
469
|
-
while i < items.length
|
|
470
|
-
if items[i].is_a?(Sym) && items[i].name == '&'
|
|
471
|
-
names.concat(pattern_names(items[i + 1]))
|
|
472
|
-
i += 2
|
|
473
|
-
else
|
|
474
|
-
names.concat(pattern_names(items[i]))
|
|
475
|
-
i += 1
|
|
476
|
-
end
|
|
442
|
+
each_pattern_item(pattern.items) do |_kind, sub|
|
|
443
|
+
names.concat(pattern_names(sub))
|
|
477
444
|
end
|
|
478
445
|
names
|
|
479
446
|
when HashLit
|
|
@@ -504,8 +471,18 @@ module Kapusta
|
|
|
504
471
|
name.length > 1 && (name.start_with?('?') || name.start_with?('_'))
|
|
505
472
|
end
|
|
506
473
|
|
|
507
|
-
def
|
|
508
|
-
|
|
474
|
+
def each_pattern_item(items)
|
|
475
|
+
i = 0
|
|
476
|
+
while i < items.length
|
|
477
|
+
item = items[i]
|
|
478
|
+
if item.is_a?(Sym) && item.name == '&'
|
|
479
|
+
yield :rest, items[i + 1]
|
|
480
|
+
i += 2
|
|
481
|
+
else
|
|
482
|
+
yield :item, item
|
|
483
|
+
i += 1
|
|
484
|
+
end
|
|
485
|
+
end
|
|
509
486
|
end
|
|
510
487
|
end
|
|
511
488
|
end
|
|
@@ -123,29 +123,39 @@ module Kapusta
|
|
|
123
123
|
|
|
124
124
|
def emit_form_in_sequence(form, env, current_scope, allow_method_definitions:, result_needed: true)
|
|
125
125
|
with_current_form(form) do
|
|
126
|
-
if
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
return [code, env] if code
|
|
131
|
-
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
|
|
132
130
|
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
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]
|
|
149
159
|
end
|
|
150
160
|
end
|
|
151
161
|
|
|
@@ -214,6 +224,26 @@ module Kapusta
|
|
|
214
224
|
%w[fn lambda λ].include?(form.head.name) && form.items[1].is_a?(Sym)
|
|
215
225
|
end
|
|
216
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
|
+
|
|
217
247
|
def method_definition_form?(form)
|
|
218
248
|
named_function_form?(form)
|
|
219
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,53 +37,39 @@ 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
|
-
when 'defn'
|
|
44
|
-
name_sym = items[1]
|
|
45
|
-
raise compiler_error(:fn_no_params, list) unless name_sym.is_a?(Sym)
|
|
46
|
-
|
|
47
|
-
self_name = inherit_position(Sym.new("self.#{name_sym.name}"), name_sym)
|
|
48
|
-
inherit_position(List.new([inherit_position(Sym.new('fn'), head), self_name, *items[2..]]), list)
|
|
49
43
|
when 'when'
|
|
50
44
|
raise compiler_error(:when_no_body, list, form: head.name) if items[2..].empty?
|
|
51
45
|
|
|
52
46
|
cond = items[1]
|
|
53
47
|
body = wrap_do(items[2..])
|
|
54
|
-
|
|
48
|
+
Kapusta.copy_position(List.new([Sym.new('if'), cond, body]), list)
|
|
55
49
|
when 'unless'
|
|
56
50
|
raise compiler_error(:when_no_body, list, form: head.name) if items[2..].empty?
|
|
57
51
|
|
|
58
52
|
cond = items[1]
|
|
59
53
|
body = wrap_do(items[2..])
|
|
60
|
-
|
|
54
|
+
Kapusta.copy_position(List.new([Sym.new('if'), List.new([Sym.new('not'), cond]), body]), list)
|
|
61
55
|
when 'tset'
|
|
62
56
|
raise compiler_error(:tset_no_value, list) if items.length < 4
|
|
63
57
|
|
|
64
|
-
|
|
58
|
+
Kapusta.copy_position(
|
|
65
59
|
List.new([Sym.new('set'), List.new([Sym.new('.'), items[1], items[2]]), items[3]]),
|
|
66
60
|
list
|
|
67
61
|
)
|
|
68
62
|
when *LuaCompat::SPECIAL_FORMS
|
|
69
63
|
normalize_lua_compat_form(head.name, items)
|
|
70
64
|
when '->', '->>', '-?>', '-?>>'
|
|
71
|
-
|
|
65
|
+
Kapusta.copy_position(normalize(thread(items[1..], head.name)), list)
|
|
72
66
|
when 'doto'
|
|
73
|
-
|
|
67
|
+
Kapusta.copy_position(normalize(doto(items[1..])), list)
|
|
74
68
|
else
|
|
75
|
-
|
|
69
|
+
Kapusta.copy_position(List.new(items), list)
|
|
76
70
|
end
|
|
77
71
|
end
|
|
78
72
|
|
|
79
|
-
def inherit_position(target, source)
|
|
80
|
-
return target unless target.respond_to?(:line=) && source.respond_to?(:line)
|
|
81
|
-
|
|
82
|
-
target.line ||= source.line
|
|
83
|
-
target.column ||= source.column
|
|
84
|
-
target
|
|
85
|
-
end
|
|
86
|
-
|
|
87
73
|
def compiler_error(code, form, **args)
|
|
88
74
|
line = form.respond_to?(:line) ? form.line : nil
|
|
89
75
|
column = form.respond_to?(:column) ? form.column : nil
|
data/lib/kapusta/errors.rb
CHANGED
|
@@ -25,6 +25,7 @@ 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',
|
|
28
29
|
end_outside_header: 'end outside class or module',
|
|
29
30
|
end_with_args: 'end takes no arguments',
|
|
30
31
|
unclosed_header: 'class or module not closed with (end)',
|
|
@@ -17,10 +17,15 @@ module Kapusta
|
|
|
17
17
|
end
|
|
18
18
|
EndMarker = Struct.new(:line, :column, :end_column, :target, keyword_init: true)
|
|
19
19
|
|
|
20
|
-
SKIPPED_HEADS = %w[macros quasi-sym quasi-list
|
|
21
|
-
quasi-list-tail quasi-vec quasi-vec-tail quasi-hash quasi-gensym].freeze
|
|
22
|
-
|
|
23
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,
|
|
24
29
|
'let' => :walk_let,
|
|
25
30
|
'local' => :walk_local_var,
|
|
26
31
|
'var' => :walk_local_var,
|
|
@@ -246,10 +251,12 @@ module Kapusta
|
|
|
246
251
|
return
|
|
247
252
|
end
|
|
248
253
|
|
|
249
|
-
return if SKIPPED_HEADS.include?(head.name)
|
|
250
|
-
|
|
251
254
|
dispatcher = DISPATCHERS[head.name]
|
|
252
|
-
|
|
255
|
+
if dispatcher
|
|
256
|
+
return if dispatcher == :skip
|
|
257
|
+
|
|
258
|
+
return send(dispatcher, list, scope)
|
|
259
|
+
end
|
|
253
260
|
|
|
254
261
|
list.items.each { |item| walk_form(item, scope) }
|
|
255
262
|
end
|
|
@@ -380,28 +387,30 @@ module Kapusta
|
|
|
380
387
|
def walk_fn(list, scope)
|
|
381
388
|
items = list.items
|
|
382
389
|
if items[1].is_a?(Vec)
|
|
390
|
+
name_sym = nil
|
|
383
391
|
params = items[1]
|
|
384
392
|
body = items[2..]
|
|
385
|
-
fn_scope = make_scope(scope, :fn)
|
|
386
|
-
bind_param_vec(params, fn_scope)
|
|
387
|
-
body.each { |form| walk_form(form, fn_scope) }
|
|
388
393
|
elsif items[1].is_a?(Sym) && items[2].is_a?(Vec)
|
|
389
394
|
name_sym = items[1]
|
|
390
395
|
params = items[2]
|
|
391
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
|
|
392
404
|
kind = if method_definition_context?
|
|
393
405
|
:method
|
|
394
406
|
else
|
|
395
407
|
(scope == @root_scope ? :toplevel_fn : :fn_local)
|
|
396
408
|
end
|
|
397
|
-
fn_scope = make_scope(scope, :fn)
|
|
398
409
|
binding = add_binding(name_sym, scope, kind, lexical: kind != :method)
|
|
399
410
|
fn_scope.bindings[name_sym.name] = binding unless kind == :method
|
|
400
|
-
bind_param_vec(params, fn_scope)
|
|
401
|
-
body.each { |form| walk_form(form, fn_scope) }
|
|
402
|
-
else
|
|
403
|
-
items[1..]&.each { |item| walk_form(item, scope) }
|
|
404
411
|
end
|
|
412
|
+
bind_param_vec(params, fn_scope)
|
|
413
|
+
body.each { |form| walk_form(form, fn_scope) }
|
|
405
414
|
end
|
|
406
415
|
|
|
407
416
|
def method_definition_context?
|
|
@@ -586,40 +595,35 @@ module Kapusta
|
|
|
586
595
|
end
|
|
587
596
|
end
|
|
588
597
|
|
|
589
|
-
def
|
|
590
|
-
items = vec.items
|
|
598
|
+
def each_pattern_item(items)
|
|
591
599
|
i = 0
|
|
592
600
|
while i < items.length
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
rest = items[i + 1]
|
|
596
|
-
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]
|
|
597
603
|
i += 2
|
|
598
|
-
elsif item.is_a?(Sym) && ['...', '_'].include?(item.name)
|
|
599
|
-
i += 1
|
|
600
604
|
else
|
|
601
|
-
|
|
605
|
+
yield :item, items[i]
|
|
602
606
|
i += 1
|
|
603
607
|
end
|
|
604
608
|
end
|
|
605
609
|
end
|
|
606
610
|
|
|
607
|
-
def
|
|
608
|
-
items
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
item
|
|
612
|
-
|
|
613
|
-
rest = items[i + 1]
|
|
614
|
-
bind_pattern(rest, scope, kind) if rest
|
|
615
|
-
i += 2
|
|
616
|
-
else
|
|
617
|
-
bind_pattern(item, scope, kind)
|
|
618
|
-
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)
|
|
619
617
|
end
|
|
620
618
|
end
|
|
621
619
|
end
|
|
622
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
|
+
|
|
623
627
|
def bind_hash_pattern(hash, scope, kind)
|
|
624
628
|
hash.pairs.each do |pair|
|
|
625
629
|
bind_pattern(pair[1], scope, kind)
|
|
@@ -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)
|
|
@@ -283,23 +294,12 @@ module Kapusta
|
|
|
283
294
|
end
|
|
284
295
|
|
|
285
296
|
def publish_diagnostics(uri, text, version)
|
|
286
|
-
diagnostics = Diagnostics.collect(text, uri_to_path(uri))
|
|
297
|
+
diagnostics = Diagnostics.collect(text, LSP.uri_to_path(uri))
|
|
287
298
|
params = { uri:, diagnostics: }
|
|
288
299
|
params[:version] = version unless version.nil?
|
|
289
300
|
notify('textDocument/publishDiagnostics', params)
|
|
290
301
|
end
|
|
291
302
|
|
|
292
|
-
def uri_to_path(uri)
|
|
293
|
-
return unless uri
|
|
294
|
-
|
|
295
|
-
parsed = URI.parse(uri)
|
|
296
|
-
return URI::DEFAULT_PARSER.unescape(parsed.path) if parsed.scheme == 'file'
|
|
297
|
-
|
|
298
|
-
uri
|
|
299
|
-
rescue URI::InvalidURIError
|
|
300
|
-
uri
|
|
301
|
-
end
|
|
302
|
-
|
|
303
303
|
def log(message)
|
|
304
304
|
@log.puts "kapusta-ls: #{message}"
|
|
305
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,11 @@ 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
|
+
|
|
95
100
|
it 'end-outside-header.kap' do
|
|
96
101
|
expect(run_error_example('end-outside-header.kap'))
|
|
97
102
|
.to eq("end-outside-header.kap:1:1: end outside class or module\n")
|