kapusta 0.13.2 → 0.15.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 +50 -11
- data/bin/check-all +6 -1
- data/bin/compile-examples +7 -2
- data/examples/anagram.kap +3 -3
- data/examples/app/args.kap +9 -0
- data/examples/arrange-coins.kap +3 -3
- data/examples/baseball-game.kap +5 -5
- data/examples/best-time-to-buy-sell-stock.kap +2 -2
- data/examples/binary-search.kap +2 -2
- data/examples/binary-to-decimal.kap +1 -1
- data/examples/blocks-and-kwargs.kap +2 -2
- data/examples/count-effects.kap +1 -1
- data/examples/count-items-matching-rule.kap +18 -0
- data/examples/digit-tools.kap +24 -0
- data/examples/doto-hygiene.kap +2 -2
- data/examples/doto.kap +2 -2
- data/examples/egg-count.kap +1 -1
- data/examples/excel-column.kap +26 -0
- data/examples/exceptions.kap +1 -1
- data/examples/falling-drops.kap +7 -7
- data/examples/fennel-parity-examples.txt +3 -6
- data/examples/good-pairs.kap +18 -0
- data/examples/greet.kap +1 -1
- data/examples/happy-number.kap +2 -2
- data/examples/left-right-difference.kap +60 -0
- data/examples/length-of-last-word.kap +4 -4
- data/examples/logger-rate-limiter.kap +26 -0
- data/examples/manhattan-distance.kap +1 -1
- data/examples/maximum-subarray.kap +23 -7
- data/examples/minimum-start-value.kap +19 -0
- data/examples/move-zeroes.kap +2 -2
- data/examples/mruby-runtime-examples.txt +10 -2
- data/examples/non-constant-local.kap +1 -1
- data/examples/number-of-1-bits.kap +1 -1
- data/examples/number-of-steps.kap +1 -1
- data/examples/palindrome.kap +2 -2
- data/examples/pangram.kap +4 -4
- data/examples/pcall.kap +3 -3
- data/examples/pipeline.kap +4 -4
- data/examples/plus-one.kap +3 -3
- data/examples/raindrops.kap +1 -1
- data/examples/range-width.kap +14 -0
- data/examples/recent-counter.kap +6 -6
- data/examples/require-local-args.kap +9 -0
- data/examples/require-local.kap +7 -0
- data/examples/require-module-local.kap +4 -0
- data/examples/require-module.kap +4 -0
- data/examples/reverse-integer.kap +1 -1
- data/examples/roman-to-integer.kap +3 -3
- data/examples/running-sum.kap +20 -0
- data/examples/safe-lookup.kap +2 -2
- data/examples/single-number.kap +1 -1
- data/examples/stack.kap +14 -14
- data/examples/subtract-product-sum.kap +1 -1
- data/examples/summary-ranges.kap +45 -0
- data/examples/threading.kap +5 -5
- data/examples/tset.kap +1 -1
- data/examples/two-sum-hash.kap +3 -3
- data/examples/two-sum.kap +1 -1
- data/examples/ugly-number.kap +1 -1
- data/examples/underground-system.kap +39 -0
- data/examples/valid-parentheses-1.kap +6 -6
- data/exe/kapusta-ls +49 -2
- data/lib/kapusta/ast.rb +8 -0
- data/lib/kapusta/compiler/emitter/bindings.rb +111 -89
- data/lib/kapusta/compiler/emitter/collections.rb +32 -40
- data/lib/kapusta/compiler/emitter/control_flow.rb +33 -31
- data/lib/kapusta/compiler/emitter/expressions.rb +21 -5
- data/lib/kapusta/compiler/emitter/interop.rb +172 -48
- data/lib/kapusta/compiler/emitter/patterns.rb +12 -14
- data/lib/kapusta/compiler/emitter/support.rb +70 -81
- data/lib/kapusta/compiler/language.rb +533 -0
- data/lib/kapusta/compiler/lua_compat.rb +23 -28
- data/lib/kapusta/compiler/macro_expander.rb +30 -30
- data/lib/kapusta/compiler/macro_lowerer.rb +12 -24
- data/lib/kapusta/compiler/normalizer.rb +25 -17
- data/lib/kapusta/compiler.rb +3 -24
- data/lib/kapusta/env.rb +2 -2
- data/lib/kapusta/errors.rb +2 -1
- data/lib/kapusta/formatter/ast_helpers.rb +78 -0
- data/lib/kapusta/formatter/cli.rb +125 -0
- data/lib/kapusta/formatter/line_helpers.rb +44 -0
- data/lib/kapusta/formatter/validator.rb +32 -0
- data/lib/kapusta/formatter.rb +363 -325
- data/lib/kapusta/lsp/identifier.rb +1 -1
- data/lib/kapusta/lsp/rename.rb +21 -11
- data/lib/kapusta/lsp/scope_walker.rb +122 -212
- data/lib/kapusta/lsp/workspace_index.rb +17 -5
- data/lib/kapusta/reader.rb +4 -2
- data/lib/kapusta/version.rb +1 -1
- data/lib/kapusta.rb +39 -6
- data/spec/cli_spec.rb +13 -0
- data/spec/examples_errors_spec.rb +3 -1
- data/spec/examples_spec.rb +99 -15
- data/spec/formatter_spec.rb +246 -0
- data/spec/lsp_spec.rb +69 -0
- data/spec/require_spec.rb +294 -0
- metadata +23 -2
- data/examples/describe.kap +0 -9
|
@@ -50,7 +50,7 @@ module Kapusta
|
|
|
50
50
|
return [join_definition_aware(entries, current_scope), i + 1]
|
|
51
51
|
end
|
|
52
52
|
|
|
53
|
-
if bodyless_header?(form)
|
|
53
|
+
if Language.bodyless_header?(form)
|
|
54
54
|
header_code, i = emit_bodyless_header(form, forms, i + 1, env)
|
|
55
55
|
entries << [form, header_code]
|
|
56
56
|
next
|
|
@@ -67,7 +67,7 @@ module Kapusta
|
|
|
67
67
|
end
|
|
68
68
|
|
|
69
69
|
def end_form?(form)
|
|
70
|
-
|
|
70
|
+
Language.end_form?(form)
|
|
71
71
|
end
|
|
72
72
|
|
|
73
73
|
def validate_end_form!(form)
|
|
@@ -75,52 +75,36 @@ module Kapusta
|
|
|
75
75
|
end
|
|
76
76
|
|
|
77
77
|
def validate_header_name!(form, head)
|
|
78
|
-
name_sym = head == 'module' ? form.
|
|
78
|
+
name_sym = head == 'module' ? Language.parse_module_form(form).name : Language.parse_class_form(form).name
|
|
79
79
|
return if constant_segments(name_sym)
|
|
80
80
|
|
|
81
81
|
code = head == 'module' ? :invalid_module_name : :invalid_class_name
|
|
82
82
|
emit_error!(code, name: name_sym.respond_to?(:name) ? name_sym.name : name_sym.inspect)
|
|
83
83
|
end
|
|
84
84
|
|
|
85
|
-
def bodyless_header?(form)
|
|
86
|
-
return false unless form.is_a?(List) && !form.empty? && form.head.is_a?(Sym)
|
|
87
|
-
|
|
88
|
-
case form.head.name
|
|
89
|
-
when 'module'
|
|
90
|
-
body = form.items[2..] || []
|
|
91
|
-
return true if body.empty?
|
|
92
|
-
|
|
93
|
-
body.length == 1 && bodyless_header?(body[0])
|
|
94
|
-
when 'class'
|
|
95
|
-
_, _, body = split_class_args(form.items[1..])
|
|
96
|
-
body.empty?
|
|
97
|
-
else
|
|
98
|
-
false
|
|
99
|
-
end
|
|
100
|
-
end
|
|
101
|
-
|
|
102
85
|
def emit_bodyless_header(form, forms, body_start, env)
|
|
103
86
|
head = form.head.name
|
|
104
87
|
validate_header_name!(form, head)
|
|
105
88
|
if head == 'module'
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
89
|
+
parsed = Language.parse_module_form(form)
|
|
90
|
+
if parsed.body.length == 1 && Language.bodyless_header?(parsed.body[0])
|
|
91
|
+
inner_code, next_i = emit_bodyless_header(parsed.body[0], forms, body_start, env)
|
|
92
|
+
[emit_direct_module_header(parsed.name, inner_code) || emit_module_wrapper(parsed.name, inner_code),
|
|
93
|
+
next_i]
|
|
111
94
|
else
|
|
112
95
|
body, next_i = with_class_body do
|
|
113
96
|
emit_form_run(forms, body_start, env.child, :module, header_form: form)
|
|
114
97
|
end
|
|
115
|
-
|
|
98
|
+
body = wrap_singleton(body) if parsed.singleton
|
|
99
|
+
[emit_direct_module_header(parsed.name, body) || emit_module_wrapper(parsed.name, body), next_i]
|
|
116
100
|
end
|
|
117
101
|
else
|
|
118
|
-
|
|
102
|
+
parsed = Language.parse_class_form(form)
|
|
119
103
|
body, next_i = with_class_body do
|
|
120
104
|
emit_form_run(forms, body_start, env.child, :class, header_form: form)
|
|
121
105
|
end
|
|
122
|
-
code = emit_direct_class_header(
|
|
123
|
-
emit_class_wrapper(
|
|
106
|
+
code = emit_direct_class_header(parsed.name, parsed.supers, body, env) ||
|
|
107
|
+
emit_class_wrapper(parsed.name, parsed.supers, env, body)
|
|
124
108
|
[code, next_i]
|
|
125
109
|
end
|
|
126
110
|
end
|
|
@@ -133,18 +117,20 @@ module Kapusta
|
|
|
133
117
|
end
|
|
134
118
|
|
|
135
119
|
def lower_defn_in_sequence(form, current_scope)
|
|
136
|
-
emit_error!(:defn_outside_header) unless
|
|
120
|
+
emit_error!(:defn_outside_header) unless Language.header_scope?(current_scope)
|
|
137
121
|
lower_defn_to_fn(form)
|
|
138
122
|
end
|
|
139
123
|
|
|
140
124
|
def emit_form_body(form, env, current_scope, allow_method_definitions:, result_needed:)
|
|
141
125
|
if allow_method_definitions &&
|
|
142
126
|
method_definition_form?(form) &&
|
|
143
|
-
|
|
127
|
+
Language.definition_scope?(current_scope)
|
|
144
128
|
code, env = emit_definition_form(form, env, current_scope)
|
|
145
129
|
return [code, env] if code
|
|
146
130
|
end
|
|
147
131
|
|
|
132
|
+
validate_header_body_form!(form, env, current_scope, allow_method_definitions:)
|
|
133
|
+
|
|
148
134
|
if named_function_form?(form)
|
|
149
135
|
emit_named_fn_assignment(form, env, current_scope)
|
|
150
136
|
elsif local_form?(form)
|
|
@@ -165,28 +151,42 @@ module Kapusta
|
|
|
165
151
|
end
|
|
166
152
|
end
|
|
167
153
|
|
|
154
|
+
def validate_header_body_form!(form, env, current_scope, allow_method_definitions:)
|
|
155
|
+
return unless allow_method_definitions
|
|
156
|
+
return unless Language.header_scope?(current_scope)
|
|
157
|
+
return unless form.is_a?(List) && form.head.is_a?(Sym)
|
|
158
|
+
|
|
159
|
+
name = form.head.name
|
|
160
|
+
return if special_form?(name)
|
|
161
|
+
return if Language.class_body_declaration?(Kapusta.kebab_to_snake(name))
|
|
162
|
+
return if form.head.dotted?
|
|
163
|
+
return if env.lookup_if_defined(name)
|
|
164
|
+
|
|
165
|
+
emit_error!(:invalid_header_body_form, scope: current_scope, name:)
|
|
166
|
+
end
|
|
167
|
+
|
|
168
168
|
def class_or_module_form?(form)
|
|
169
|
-
|
|
170
|
-
%w[class module].include?(form.head.name)
|
|
169
|
+
Language.header_form?(form)
|
|
171
170
|
end
|
|
172
171
|
|
|
173
172
|
def emit_class_or_module_statement(form, env)
|
|
174
173
|
args = form.rest
|
|
175
174
|
if form.head.name == 'module'
|
|
176
|
-
|
|
175
|
+
parsed = Language.parse_module_args(args)
|
|
177
176
|
body = with_class_body do
|
|
178
|
-
emit_sequence(
|
|
179
|
-
|
|
177
|
+
emit_sequence(parsed.body, env.child, :module, allow_method_definitions: true,
|
|
178
|
+
result: false).first
|
|
180
179
|
end
|
|
181
|
-
|
|
180
|
+
body = wrap_singleton(body) if parsed.singleton
|
|
181
|
+
emit_direct_module_header(parsed.name, body) || emit_module_wrapper(parsed.name, body)
|
|
182
182
|
else
|
|
183
|
-
|
|
183
|
+
parsed = Language.parse_class_args(args)
|
|
184
184
|
body = with_class_body do
|
|
185
|
-
emit_sequence(
|
|
186
|
-
|
|
185
|
+
emit_sequence(parsed.body, env.child, :class, allow_method_definitions: true,
|
|
186
|
+
result: false).first
|
|
187
187
|
end
|
|
188
|
-
emit_direct_class_header(
|
|
189
|
-
emit_class_wrapper(
|
|
188
|
+
emit_direct_class_header(parsed.name, parsed.supers, body, env) ||
|
|
189
|
+
emit_class_wrapper(parsed.name, parsed.supers, env, body)
|
|
190
190
|
end
|
|
191
191
|
end
|
|
192
192
|
|
|
@@ -223,25 +223,21 @@ module Kapusta
|
|
|
223
223
|
end
|
|
224
224
|
|
|
225
225
|
def blank_between_definitions?(prev_form, curr_form, current_scope)
|
|
226
|
-
return false unless
|
|
226
|
+
return false unless Language.definition_scope?(current_scope)
|
|
227
227
|
|
|
228
228
|
definition_form?(prev_form) || definition_form?(curr_form)
|
|
229
229
|
end
|
|
230
230
|
|
|
231
231
|
def definition_form?(form)
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
case form.head.name
|
|
232
|
+
case Language.list_head_name(form)
|
|
235
233
|
when 'defn', 'class', 'module' then true
|
|
236
|
-
when
|
|
234
|
+
when *Language::FUNCTION_HEADS then Language.parse_function_form(form)&.named?
|
|
237
235
|
else false
|
|
238
236
|
end
|
|
239
237
|
end
|
|
240
238
|
|
|
241
239
|
def sequence_statement_form?(form)
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
%w[let while for each case match].include?(form.head.name)
|
|
240
|
+
Language.sequence_statement_form?(form)
|
|
245
241
|
end
|
|
246
242
|
|
|
247
243
|
def emit_sequence_statement_form(form, env, current_scope, result_needed:)
|
|
@@ -265,29 +261,19 @@ module Kapusta
|
|
|
265
261
|
end
|
|
266
262
|
|
|
267
263
|
def special_form?(name)
|
|
268
|
-
|
|
269
|
-
end
|
|
270
|
-
|
|
271
|
-
def split_class_args(args)
|
|
272
|
-
name_sym = args[0]
|
|
273
|
-
if args[1].is_a?(Vec)
|
|
274
|
-
[name_sym, args[1], args[2..] || []]
|
|
275
|
-
else
|
|
276
|
-
[name_sym, nil, args[1..] || []]
|
|
277
|
-
end
|
|
264
|
+
Language.special_form?(name)
|
|
278
265
|
end
|
|
279
266
|
|
|
280
267
|
def named_function_form?(form)
|
|
281
|
-
|
|
282
|
-
%w[fn lambda λ].include?(form.head.name) && form.items[1].is_a?(Sym)
|
|
268
|
+
Language.parse_function_form(form)&.named?
|
|
283
269
|
end
|
|
284
270
|
|
|
285
271
|
def defn_form?(form)
|
|
286
|
-
|
|
272
|
+
Language.defn_form?(form)
|
|
287
273
|
end
|
|
288
274
|
|
|
289
275
|
def lower_defn_to_fn(form)
|
|
290
|
-
name_sym = form
|
|
276
|
+
name_sym = Language.parse_function_form(form, heads: Language::FUNCTION_DEFINITION_HEADS)&.name
|
|
291
277
|
emit_error!(:fn_no_params) unless name_sym.is_a?(Sym)
|
|
292
278
|
|
|
293
279
|
fn_sym = Sym.new('fn')
|
|
@@ -307,21 +293,23 @@ module Kapusta
|
|
|
307
293
|
end
|
|
308
294
|
|
|
309
295
|
def block_form?(form)
|
|
310
|
-
|
|
296
|
+
name = Language.list_head_name(form)
|
|
297
|
+
!name.nil? && (Language.function_head?(name) || name == 'hashfn')
|
|
311
298
|
end
|
|
312
299
|
|
|
313
300
|
def local_form?(form)
|
|
314
|
-
|
|
301
|
+
Language.binding_form?(form)
|
|
315
302
|
end
|
|
316
303
|
|
|
317
304
|
def do_form?(form)
|
|
318
|
-
|
|
305
|
+
Language.do_form?(form)
|
|
319
306
|
end
|
|
320
307
|
|
|
321
308
|
def set_new_local_form?(form, env)
|
|
322
|
-
|
|
309
|
+
parsed = Language.parse_set_form(form)
|
|
310
|
+
return false unless parsed
|
|
323
311
|
|
|
324
|
-
target =
|
|
312
|
+
target = parsed.target
|
|
325
313
|
target.is_a?(Sym) && !target.dotted? && !env.defined?(target.name)
|
|
326
314
|
end
|
|
327
315
|
|
|
@@ -330,6 +318,10 @@ module Kapusta
|
|
|
330
318
|
text.lines.map { |line| line.strip.empty? ? line : "#{prefix}#{line}" }.join
|
|
331
319
|
end
|
|
332
320
|
|
|
321
|
+
def wrap_singleton(body)
|
|
322
|
+
['class << self', indent(body), 'end'].join("\n")
|
|
323
|
+
end
|
|
324
|
+
|
|
333
325
|
def temp(prefix)
|
|
334
326
|
@temp_index += 1
|
|
335
327
|
"kap_#{prefix}_#{@temp_index}"
|
|
@@ -397,23 +389,20 @@ module Kapusta
|
|
|
397
389
|
binding
|
|
398
390
|
end
|
|
399
391
|
|
|
400
|
-
def parse_counted_for_bindings(
|
|
401
|
-
emit_error!(:counted_no_range) if
|
|
402
|
-
name_sym =
|
|
392
|
+
def parse_counted_for_bindings(parsed, env, current_scope)
|
|
393
|
+
emit_error!(:counted_no_range) if parsed.items.length < 3
|
|
394
|
+
name_sym = parsed.counter
|
|
403
395
|
loop_env = env.child
|
|
404
396
|
ruby_name = define_local(loop_env, name_sym.name)
|
|
405
|
-
start_code = emit_expr(
|
|
406
|
-
finish_code = emit_expr(
|
|
397
|
+
start_code = emit_expr(parsed.start, env, current_scope)
|
|
398
|
+
finish_code = emit_expr(parsed.finish, env, current_scope)
|
|
407
399
|
step_code = '1'
|
|
408
400
|
until_form = nil
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
until_form = bindings[i + 1]
|
|
413
|
-
i += 2
|
|
401
|
+
parsed.each_extra do |kind, form|
|
|
402
|
+
if kind == :until
|
|
403
|
+
until_form = form
|
|
414
404
|
else
|
|
415
|
-
step_code = emit_expr(
|
|
416
|
-
i += 1
|
|
405
|
+
step_code = emit_expr(form, env, current_scope)
|
|
417
406
|
end
|
|
418
407
|
end
|
|
419
408
|
{ ruby_name:, loop_env:, start_code:, finish_code:, step_code:, until_form: }
|