kapusta 0.1.4 → 0.2.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 +13 -0
- data/bin/compile-examples +24 -0
- data/examples/bank-account.kap +21 -0
- data/examples/baseball-game.kap +11 -0
- data/examples/climbing-stairs.kap +13 -0
- data/examples/doto-hygiene.kap +5 -0
- data/examples/happy-number.kap +20 -0
- data/examples/length-of-last-word.kap +7 -0
- data/examples/maximum-subarray.kap +12 -0
- data/examples/move-zeroes.kap +13 -0
- data/examples/stack.kap +27 -10
- data/examples/two-sum-hash.kap +17 -0
- data/examples/use_bank_account.rb +13 -0
- data/examples/valid-parentheses-1.kap +19 -0
- data/examples/valid-parentheses-2.kap +8 -0
- data/lib/kapusta/ast.rb +33 -3
- data/lib/kapusta/compiler/emitter/bindings.rb +135 -22
- data/lib/kapusta/compiler/emitter/control_flow.rb +4 -4
- data/lib/kapusta/compiler/emitter/expressions.rb +30 -14
- data/lib/kapusta/compiler/emitter/interop.rb +108 -41
- data/lib/kapusta/compiler/emitter/patterns.rb +14 -11
- data/lib/kapusta/compiler/emitter/support.rb +29 -14
- data/lib/kapusta/compiler/emitter.rb +1 -5
- data/lib/kapusta/compiler/normalizer.rb +42 -18
- data/lib/kapusta/compiler/runtime.rb +5 -156
- data/lib/kapusta/env.rb +31 -8
- data/lib/kapusta/formatter.rb +9 -10
- data/lib/kapusta/reader.rb +30 -6
- data/lib/kapusta/version.rb +1 -1
- data/lib/kapusta.rb +62 -1
- data/spec/cli_spec.rb +25 -2
- data/spec/examples_spec.rb +234 -87
- data/spec/reader_spec.rb +26 -0
- metadata +17 -7
|
@@ -17,7 +17,7 @@ module Kapusta
|
|
|
17
17
|
when List then emit_list(form, env, current_scope)
|
|
18
18
|
when String, Symbol, Numeric, true, false, nil then form.inspect
|
|
19
19
|
else
|
|
20
|
-
|
|
20
|
+
emit_error!("cannot emit form: #{form.inspect}")
|
|
21
21
|
end
|
|
22
22
|
end
|
|
23
23
|
|
|
@@ -36,7 +36,9 @@ module Kapusta
|
|
|
36
36
|
if head.is_a?(Sym)
|
|
37
37
|
return emit_special(head.name, args, env, current_scope) if special_form?(head.name)
|
|
38
38
|
return emit_multisym_call(head, args, env, current_scope) if head.dotted?
|
|
39
|
-
|
|
39
|
+
if (binding = env.lookup_if_defined(head.name))
|
|
40
|
+
return emit_bound_call(binding, args, env, current_scope)
|
|
41
|
+
end
|
|
40
42
|
|
|
41
43
|
return emit_self_call(head.name, args, env, current_scope)
|
|
42
44
|
end
|
|
@@ -68,24 +70,22 @@ module Kapusta
|
|
|
68
70
|
when '.' then emit_lookup(args, env, current_scope)
|
|
69
71
|
when '?.' then emit_safe_lookup(args, env, current_scope)
|
|
70
72
|
when ':' then emit_colon(args, env, current_scope)
|
|
71
|
-
when '..' then
|
|
72
|
-
|
|
73
|
-
end)
|
|
74
|
-
when 'length' then "(#{emit_expr(args[0], env, current_scope)}).length"
|
|
73
|
+
when '..' then emit_concat(args, env, current_scope)
|
|
74
|
+
when 'length' then "#{parenthesize(emit_expr(args[0], env, current_scope))}.length"
|
|
75
75
|
when 'require' then emit_require(args[0], env, current_scope)
|
|
76
76
|
when 'module' then emit_module_expr(args, env)
|
|
77
77
|
when 'class' then emit_class_expr(args, env)
|
|
78
78
|
when 'try' then emit_try(args, env, current_scope)
|
|
79
79
|
when 'raise' then emit_raise(args, env, current_scope)
|
|
80
|
-
when 'ivar' then
|
|
81
|
-
when 'cvar' then
|
|
80
|
+
when 'ivar' then "@#{Kapusta.kebab_to_snake(args[0].name)}"
|
|
81
|
+
when 'cvar' then "@@#{Kapusta.kebab_to_snake(args[0].name)}"
|
|
82
82
|
when 'gvar' then emit_gvar(args[0])
|
|
83
83
|
when 'ruby' then "Kernel.eval(#{emit_expr(args[0], env, current_scope)})"
|
|
84
84
|
when 'and' then emit_and(args, env, current_scope)
|
|
85
85
|
when 'or' then emit_or(args, env, current_scope)
|
|
86
|
-
when 'not' then "
|
|
86
|
+
when 'not' then "!#{parenthesize(emit_expr(args[0], env, current_scope))}"
|
|
87
87
|
when '=' then emit_compare(args, env, current_scope, '==')
|
|
88
|
-
when 'not=' then
|
|
88
|
+
when 'not=' then emit_compare_any(args, env, current_scope, '!=')
|
|
89
89
|
when '<' then emit_compare(args, env, current_scope, '<')
|
|
90
90
|
when '<=' then emit_compare(args, env, current_scope, '<=')
|
|
91
91
|
when '>' then emit_compare(args, env, current_scope, '>')
|
|
@@ -95,13 +95,29 @@ module Kapusta
|
|
|
95
95
|
when '*' then emit_reduce(args, env, current_scope, '1', :*)
|
|
96
96
|
when '/' then emit_div(args, env, current_scope)
|
|
97
97
|
when '%' then args.map { |arg| parenthesize(emit_expr(arg, env, current_scope)) }.join(' % ')
|
|
98
|
-
when 'print' then
|
|
99
|
-
emit_expr(arg, env, current_scope)
|
|
100
|
-
end)
|
|
98
|
+
when 'print' then emit_print(args, env, current_scope)
|
|
101
99
|
else
|
|
102
|
-
|
|
100
|
+
emit_error!("unknown special form: #{name}")
|
|
103
101
|
end
|
|
104
102
|
end
|
|
103
|
+
|
|
104
|
+
def emit_concat(args, env, current_scope)
|
|
105
|
+
return '""' if args.empty?
|
|
106
|
+
|
|
107
|
+
args.map { |arg| emit_string_part(arg, env, current_scope) }.join(' + ')
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def emit_print(args, env, current_scope)
|
|
111
|
+
return 'p' if args.empty?
|
|
112
|
+
|
|
113
|
+
"p(#{args.map { |arg| emit_expr(arg, env, current_scope) }.join(', ')})"
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
def emit_string_part(arg, env, current_scope)
|
|
117
|
+
return arg.inspect if arg.is_a?(String)
|
|
118
|
+
|
|
119
|
+
"(#{emit_expr(arg, env, current_scope)}).to_s"
|
|
120
|
+
end
|
|
105
121
|
end
|
|
106
122
|
end
|
|
107
123
|
end
|
|
@@ -8,8 +8,11 @@ module Kapusta
|
|
|
8
8
|
|
|
9
9
|
def emit_lookup(args, env, current_scope)
|
|
10
10
|
object_code = emit_expr(args[0], env, current_scope)
|
|
11
|
-
keys = args[1..].map { |arg| emit_expr(arg, env, current_scope) }
|
|
12
|
-
|
|
11
|
+
keys = args[1..].map { |arg| emit_expr(arg, env, current_scope) }
|
|
12
|
+
return object_code if keys.empty?
|
|
13
|
+
|
|
14
|
+
receiver = simple_expression?(object_code) ? object_code : parenthesize(object_code)
|
|
15
|
+
"#{receiver}#{keys.map { |k| "[#{k}]" }.join}"
|
|
13
16
|
end
|
|
14
17
|
|
|
15
18
|
def emit_safe_lookup(args, env, current_scope)
|
|
@@ -20,9 +23,16 @@ module Kapusta
|
|
|
20
23
|
|
|
21
24
|
def emit_colon(args, env, current_scope)
|
|
22
25
|
receiver = emit_expr(args[0], env, current_scope)
|
|
23
|
-
|
|
26
|
+
method_form = args[1]
|
|
24
27
|
positional, kwargs, block = split_call_args(args[2..], env, current_scope)
|
|
25
|
-
|
|
28
|
+
literal_name = method_form if method_form.is_a?(Symbol) || method_form.is_a?(String)
|
|
29
|
+
if literal_name && !kwargs && !block && direct_method_name?(literal_name.to_s)
|
|
30
|
+
return emit_direct_method_call(receiver, Kapusta.kebab_to_snake(literal_name.to_s), positional)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
method_name = emit_method_name(method_form, env, current_scope)
|
|
34
|
+
parts = build_call_args([method_name, *positional], kwargs, block)
|
|
35
|
+
"#{parenthesize(receiver)}.public_send(#{parts})"
|
|
26
36
|
end
|
|
27
37
|
|
|
28
38
|
def emit_require(arg, env, current_scope)
|
|
@@ -33,9 +43,30 @@ module Kapusta
|
|
|
33
43
|
when String then arg.inspect
|
|
34
44
|
else "(#{emit_expr(arg, env, current_scope)}).to_s"
|
|
35
45
|
end
|
|
46
|
+
if kapusta_require?(arg)
|
|
47
|
+
return [
|
|
48
|
+
'unless defined?(Kapusta)',
|
|
49
|
+
indent('require "kapusta"'),
|
|
50
|
+
'end',
|
|
51
|
+
"Kapusta.require(#{path_code}, relative_to: #{@path.inspect})"
|
|
52
|
+
].join("\n")
|
|
53
|
+
end
|
|
54
|
+
|
|
36
55
|
"require #{path_code}"
|
|
37
56
|
end
|
|
38
57
|
|
|
58
|
+
def kapusta_require?(arg)
|
|
59
|
+
path =
|
|
60
|
+
case arg
|
|
61
|
+
when Sym then arg.name
|
|
62
|
+
when Symbol then arg.to_s
|
|
63
|
+
when String then arg
|
|
64
|
+
end
|
|
65
|
+
return false unless path
|
|
66
|
+
|
|
67
|
+
path.end_with?('.kap') || path.start_with?('./', '../') || File.absolute_path?(path)
|
|
68
|
+
end
|
|
69
|
+
|
|
39
70
|
def emit_module_expr(args, env)
|
|
40
71
|
body = emit_sequence(args[1..], env, :module, allow_method_definitions: true, result: false).first
|
|
41
72
|
emit_module_wrapper(args[0], body)
|
|
@@ -63,7 +94,7 @@ module Kapusta
|
|
|
63
94
|
|
|
64
95
|
def emit_direct_module_header(name_sym, body)
|
|
65
96
|
const_name = simple_constant_name(name_sym)
|
|
66
|
-
return
|
|
97
|
+
return unless const_name
|
|
67
98
|
|
|
68
99
|
[
|
|
69
100
|
"module #{const_name}",
|
|
@@ -94,7 +125,7 @@ module Kapusta
|
|
|
94
125
|
|
|
95
126
|
def emit_direct_class_header(name_sym, supers, body)
|
|
96
127
|
const_name = simple_constant_name(name_sym)
|
|
97
|
-
return
|
|
128
|
+
return unless const_name && supers.nil?
|
|
98
129
|
|
|
99
130
|
[
|
|
100
131
|
"class #{const_name}",
|
|
@@ -105,7 +136,7 @@ module Kapusta
|
|
|
105
136
|
end
|
|
106
137
|
|
|
107
138
|
def simple_constant_name(name_sym)
|
|
108
|
-
return
|
|
139
|
+
return unless name_sym.is_a?(Sym) && name_sym.name.match?(/\A[A-Z]\w*\z/)
|
|
109
140
|
|
|
110
141
|
name_sym.name
|
|
111
142
|
end
|
|
@@ -184,6 +215,15 @@ module Kapusta
|
|
|
184
215
|
end.join(' && ')
|
|
185
216
|
end
|
|
186
217
|
|
|
218
|
+
def emit_compare_any(args, env, current_scope, operator)
|
|
219
|
+
values = args.map { |arg| emit_expr(arg, env, current_scope) }
|
|
220
|
+
return 'false' if values.length <= 1
|
|
221
|
+
|
|
222
|
+
(0...(values.length - 1)).map do |i|
|
|
223
|
+
"#{parenthesize(values[i])} #{operator} #{parenthesize(values[i + 1])}"
|
|
224
|
+
end.join(' || ')
|
|
225
|
+
end
|
|
226
|
+
|
|
187
227
|
def emit_reduce(args, env, current_scope, empty_value, operator)
|
|
188
228
|
return empty_value if args.empty?
|
|
189
229
|
|
|
@@ -207,15 +247,32 @@ module Kapusta
|
|
|
207
247
|
|
|
208
248
|
def emit_callable_call(callee_code, args, env, current_scope)
|
|
209
249
|
positional, kwargs, block = split_call_args(args, env, current_scope)
|
|
210
|
-
|
|
250
|
+
rendered = build_call_args(positional, kwargs, block)
|
|
251
|
+
suffix = rendered.empty? ? '.call' : ".call(#{rendered})"
|
|
252
|
+
"#{parenthesize(callee_code)}#{suffix}"
|
|
253
|
+
end
|
|
211
254
|
|
|
212
|
-
|
|
255
|
+
def build_call_args(positional, kwargs, block)
|
|
256
|
+
parts = positional.dup
|
|
257
|
+
parts << "**#{kwargs}" if kwargs
|
|
258
|
+
parts << "&#{block}" if block
|
|
259
|
+
parts.join(', ')
|
|
213
260
|
end
|
|
214
261
|
|
|
215
|
-
def
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
262
|
+
def emit_bound_call(binding, args, env, current_scope)
|
|
263
|
+
return emit_self_method_binding_call(binding, args, env, current_scope) if method_binding?(binding)
|
|
264
|
+
|
|
265
|
+
emit_callable_call(binding, args, env, current_scope)
|
|
266
|
+
end
|
|
267
|
+
|
|
268
|
+
def emit_self_method_binding_call(binding, args, env, current_scope)
|
|
269
|
+
positional = args.map { |arg| emit_expr(arg, env, current_scope) }
|
|
270
|
+
emit_direct_self_method_call(binding.ruby_name, positional)
|
|
271
|
+
end
|
|
272
|
+
|
|
273
|
+
def emit_direct_self_method_call(method_name, positional)
|
|
274
|
+
args = positional.join(', ')
|
|
275
|
+
args.empty? ? "#{method_name}()" : "#{method_name}(#{args})"
|
|
219
276
|
end
|
|
220
277
|
|
|
221
278
|
def emit_multisym_call(head, args, env, current_scope)
|
|
@@ -223,19 +280,26 @@ module Kapusta
|
|
|
223
280
|
if segments.empty?
|
|
224
281
|
emit_callable_call(base_code, args, env, current_scope)
|
|
225
282
|
else
|
|
226
|
-
receiver =
|
|
227
|
-
if segments.length == 1
|
|
228
|
-
base_code
|
|
229
|
-
else
|
|
230
|
-
runtime_call(:method_path_value, base_code, segments[0...-1].inspect)
|
|
231
|
-
end
|
|
232
|
-
method_name = Kapusta.kebab_to_snake(segments.last).to_sym.inspect
|
|
283
|
+
receiver = emit_method_path(base_code, segments[0...-1])
|
|
233
284
|
positional, kwargs, block = split_call_args(args, env, current_scope)
|
|
234
|
-
if
|
|
285
|
+
if !kwargs && !block && direct_method_name?(segments.last)
|
|
235
286
|
return emit_direct_method_call(receiver, Kapusta.kebab_to_snake(segments.last), positional)
|
|
236
287
|
end
|
|
237
288
|
|
|
238
|
-
|
|
289
|
+
method_name = Kapusta.kebab_to_snake(segments.last).to_sym.inspect
|
|
290
|
+
parts = build_call_args([method_name, *positional], kwargs, block)
|
|
291
|
+
"#{receiver}.public_send(#{parts})"
|
|
292
|
+
end
|
|
293
|
+
end
|
|
294
|
+
|
|
295
|
+
def emit_method_path(base_code, segments)
|
|
296
|
+
segments.reduce(base_code) do |acc, segment|
|
|
297
|
+
snake = Kapusta.kebab_to_snake(segment)
|
|
298
|
+
if direct_method_name?(segment)
|
|
299
|
+
"#{acc}.#{snake}"
|
|
300
|
+
else
|
|
301
|
+
"#{acc}.public_send(#{snake.to_sym.inspect})"
|
|
302
|
+
end
|
|
239
303
|
end
|
|
240
304
|
end
|
|
241
305
|
|
|
@@ -249,7 +313,8 @@ module Kapusta
|
|
|
249
313
|
def emit_self_call(name, args, env, current_scope)
|
|
250
314
|
positional, kwargs, block = split_call_args(args, env, current_scope)
|
|
251
315
|
method_name = Kapusta.kebab_to_snake(name).to_sym.inspect
|
|
252
|
-
|
|
316
|
+
parts = build_call_args([method_name, *positional], kwargs, block)
|
|
317
|
+
"send(#{parts})"
|
|
253
318
|
end
|
|
254
319
|
|
|
255
320
|
def split_call_args(args, env, current_scope)
|
|
@@ -285,33 +350,31 @@ module Kapusta
|
|
|
285
350
|
name = sym.name
|
|
286
351
|
return 'self' if name == 'self'
|
|
287
352
|
return 'Float::INFINITY' if name == 'math.huge'
|
|
288
|
-
|
|
353
|
+
|
|
354
|
+
if (binding = env.lookup_if_defined(sym))
|
|
355
|
+
return binding_value_code(binding)
|
|
356
|
+
end
|
|
289
357
|
return emit_multisym_value(sym, env) if sym.dotted?
|
|
290
358
|
return 'ARGV' if name == 'ARGV'
|
|
291
359
|
return name if name.match?(/\A[A-Z]/)
|
|
292
360
|
|
|
293
|
-
|
|
361
|
+
emit_error!("undefined symbol: #{name}")
|
|
294
362
|
end
|
|
295
363
|
|
|
296
364
|
def emit_gvar(sym)
|
|
297
|
-
|
|
298
|
-
return "$#{ruby_name}" if direct_global_name?(ruby_name)
|
|
299
|
-
|
|
300
|
-
runtime_call(:get_gvar, sym.name.inspect)
|
|
365
|
+
"$#{global_name(sym.name)}"
|
|
301
366
|
end
|
|
302
367
|
|
|
303
368
|
def emit_multisym_value(sym, env)
|
|
304
369
|
base_code, segments = multisym_base(sym.segments, env)
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
runtime_call(:method_path_value, base_code, segments.inspect)
|
|
370
|
+
emit_method_path(base_code, segments)
|
|
308
371
|
end
|
|
309
372
|
|
|
310
373
|
def multisym_base(segments, env)
|
|
311
374
|
if segments[0] == 'self'
|
|
312
375
|
['self', segments[1..]]
|
|
313
|
-
elsif env.
|
|
314
|
-
[
|
|
376
|
+
elsif (binding = env.lookup_if_defined(segments[0]))
|
|
377
|
+
[binding_value_code(binding), segments[1..]]
|
|
315
378
|
else
|
|
316
379
|
idx = 0
|
|
317
380
|
const_path = []
|
|
@@ -319,7 +382,7 @@ module Kapusta
|
|
|
319
382
|
const_path << segments[idx]
|
|
320
383
|
idx += 1
|
|
321
384
|
end
|
|
322
|
-
|
|
385
|
+
emit_error!("bad multisym: #{segments.join('.')}") if const_path.empty?
|
|
323
386
|
|
|
324
387
|
[const_path.join('::'), segments[idx..]]
|
|
325
388
|
end
|
|
@@ -335,10 +398,6 @@ module Kapusta
|
|
|
335
398
|
Kapusta.kebab_to_snake(name).match?(/\A[a-z_]\w*[!?=]?\z/)
|
|
336
399
|
end
|
|
337
400
|
|
|
338
|
-
def direct_global_name?(name)
|
|
339
|
-
name.match?(/\A[a-z_]\w*\z/)
|
|
340
|
-
end
|
|
341
|
-
|
|
342
401
|
def global_name(name)
|
|
343
402
|
Kapusta.kebab_to_snake(name).gsub(/[^a-zA-Z0-9_]/, '_')
|
|
344
403
|
end
|
|
@@ -348,11 +407,19 @@ module Kapusta
|
|
|
348
407
|
code.match?(/\A[A-Z]\w*(?:::[A-Z]\w*)*\z/) ||
|
|
349
408
|
code.match?(/\A[a-z_]\w*[!?=]?\([^()\n]*\)\z/) ||
|
|
350
409
|
code.match?(/\A\d+(?:\.\d+)?\z/) ||
|
|
351
|
-
code.match?(/\A[a-z_]\w*(?:\.[a-z_]\w*[!?=]?(?:\([^()\n]*\))
|
|
410
|
+
code.match?(/\A[a-z_]\w*(?:\.[a-z_]\w*[!?=]?(?:\([^()\n]*\))?|\[[^\[\]]*\])+\z/) ||
|
|
352
411
|
code.match?(/\A:[a-zA-Z_]\w*[!?=]?\z/) ||
|
|
353
412
|
code.match?(/\A"(?:[^"\\]|\\.)*"\z/) ||
|
|
354
413
|
code.match?(/\A'(?:[^'\\]|\\.)*'\z/) ||
|
|
355
|
-
%w[nil true false self].include?(code)
|
|
414
|
+
%w[nil true false self].include?(code) ||
|
|
415
|
+
negation_simple?(code)
|
|
416
|
+
end
|
|
417
|
+
|
|
418
|
+
def negation_simple?(code)
|
|
419
|
+
return false unless code.start_with?('!') && code.length > 1
|
|
420
|
+
|
|
421
|
+
rest = code[1..]
|
|
422
|
+
simple_expression?(rest) || (rest.start_with?('(') && rest.end_with?(')'))
|
|
356
423
|
end
|
|
357
424
|
end
|
|
358
425
|
end
|
|
@@ -10,7 +10,7 @@ module Kapusta
|
|
|
10
10
|
if pattern.is_a?(Sym)
|
|
11
11
|
return ['nil', env] if pattern.name == '_'
|
|
12
12
|
|
|
13
|
-
ruby_name = define_local(env, pattern
|
|
13
|
+
ruby_name = define_local(env, pattern)
|
|
14
14
|
["#{ruby_name} = #{value_code}", env]
|
|
15
15
|
else
|
|
16
16
|
bindings_var = temp('bindings')
|
|
@@ -61,7 +61,7 @@ module Kapusta
|
|
|
61
61
|
elsif or_pattern?(pattern)
|
|
62
62
|
emit_or_match_pattern(pattern, env, mode:, allow_pins:, state:)
|
|
63
63
|
elsif where_pattern?(pattern)
|
|
64
|
-
|
|
64
|
+
emit_error!('`where` is only valid as a case/match clause head')
|
|
65
65
|
else
|
|
66
66
|
emit_sequence_match_pattern(pattern.items, env, mode:, allow_pins:, state:)
|
|
67
67
|
end
|
|
@@ -70,7 +70,7 @@ module Kapusta
|
|
|
70
70
|
when Symbol, String, Numeric, true, false
|
|
71
71
|
"[:lit, #{pattern.inspect}]"
|
|
72
72
|
else
|
|
73
|
-
|
|
73
|
+
emit_error!("bad pattern: #{pattern.inspect}")
|
|
74
74
|
end
|
|
75
75
|
end
|
|
76
76
|
|
|
@@ -88,10 +88,11 @@ module Kapusta
|
|
|
88
88
|
end
|
|
89
89
|
|
|
90
90
|
def emit_named_match_pattern(name, env, mode:, state:, allow_nil:, prefer_pin:)
|
|
91
|
+
binding = prefer_pin && mode == :match ? env.lookup_if_defined(name) : nil
|
|
91
92
|
if state[:bound_names].key?(name)
|
|
92
93
|
"[:ref, #{name.inspect}]"
|
|
93
|
-
elsif
|
|
94
|
-
"[:pin, #{
|
|
94
|
+
elsif binding
|
|
95
|
+
"[:pin, #{binding_value_code(binding)}]"
|
|
95
96
|
else
|
|
96
97
|
state[:bound_names][name] = true
|
|
97
98
|
state[:binding_names] << name
|
|
@@ -115,13 +116,15 @@ module Kapusta
|
|
|
115
116
|
end
|
|
116
117
|
|
|
117
118
|
def emit_pin_match_pattern(pattern, env, mode:, allow_pins:)
|
|
118
|
-
|
|
119
|
+
emit_error!('pin patterns are only supported inside `case` guards') unless allow_pins && mode == :case
|
|
119
120
|
|
|
120
121
|
name_sym = pattern.items[1]
|
|
121
|
-
|
|
122
|
-
raise Error, "cannot pin undefined name: #{name_sym.name}" unless env.defined?(name_sym.name)
|
|
122
|
+
emit_error!("bad pin pattern: #{pattern.inspect}") unless name_sym.is_a?(Sym)
|
|
123
123
|
|
|
124
|
-
|
|
124
|
+
binding = env.lookup_if_defined(name_sym.name)
|
|
125
|
+
emit_error!("cannot pin undefined name: #{name_sym.name}") unless binding
|
|
126
|
+
|
|
127
|
+
"[:pin, #{binding_value_code(binding)}]"
|
|
125
128
|
end
|
|
126
129
|
|
|
127
130
|
def emit_or_match_pattern(pattern, env, mode:, allow_pins:, state:)
|
|
@@ -136,7 +139,7 @@ module Kapusta
|
|
|
136
139
|
compiled = emit_match_pattern(subpattern, env, mode:, allow_pins:, state: alt_state)
|
|
137
140
|
alt_names = alt_state[:binding_names][initial_names..]
|
|
138
141
|
canonical_names ||= alt_names
|
|
139
|
-
|
|
142
|
+
emit_error!('all `or` patterns must bind the same names') if canonical_names.sort != alt_names.sort
|
|
140
143
|
|
|
141
144
|
compiled
|
|
142
145
|
end
|
|
@@ -174,7 +177,7 @@ module Kapusta
|
|
|
174
177
|
when Symbol, String, Numeric, true, false
|
|
175
178
|
"[:lit, #{pattern.inspect}]"
|
|
176
179
|
else
|
|
177
|
-
|
|
180
|
+
emit_error!("bad pattern: #{pattern.inspect}")
|
|
178
181
|
end
|
|
179
182
|
end
|
|
180
183
|
|
|
@@ -6,6 +6,10 @@ module Kapusta
|
|
|
6
6
|
module Support
|
|
7
7
|
private
|
|
8
8
|
|
|
9
|
+
def emit_error!(message)
|
|
10
|
+
raise Error, "#{@path}: #{message}"
|
|
11
|
+
end
|
|
12
|
+
|
|
9
13
|
def emit_forms_with_headers(forms, env, current_scope, result: true)
|
|
10
14
|
i = 0
|
|
11
15
|
codes = []
|
|
@@ -63,9 +67,14 @@ module Kapusta
|
|
|
63
67
|
end
|
|
64
68
|
|
|
65
69
|
def emit_form_in_sequence(form, env, current_scope, allow_method_definitions:, result_needed: true)
|
|
66
|
-
if allow_method_definitions &&
|
|
67
|
-
|
|
68
|
-
|
|
70
|
+
if allow_method_definitions &&
|
|
71
|
+
method_definition_form?(form) &&
|
|
72
|
+
%i[toplevel module class].include?(current_scope)
|
|
73
|
+
code, env = emit_definition_form(form, env, current_scope)
|
|
74
|
+
return [code, env] if code
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
if named_function_form?(form)
|
|
69
78
|
emit_named_fn_assignment(form, env, current_scope)
|
|
70
79
|
elsif local_form?(form)
|
|
71
80
|
code, env = emit_local_form(form, env, current_scope)
|
|
@@ -186,7 +195,7 @@ module Kapusta
|
|
|
186
195
|
|
|
187
196
|
def local_name(source_name, env, shadow:)
|
|
188
197
|
base = sanitize_local(source_name)
|
|
189
|
-
base = "user_#{base}" if reserved_generated_name?(base)
|
|
198
|
+
base = "user_#{base}" if !generated_symbol?(source_name) && reserved_generated_name?(base)
|
|
190
199
|
return base unless ruby_name_defined?(env, base, shadow:)
|
|
191
200
|
|
|
192
201
|
index = 2
|
|
@@ -206,6 +215,10 @@ module Kapusta
|
|
|
206
215
|
name.start_with?('kap_', '__kap_')
|
|
207
216
|
end
|
|
208
217
|
|
|
218
|
+
def generated_symbol?(source_name)
|
|
219
|
+
source_name.is_a?(GeneratedSym)
|
|
220
|
+
end
|
|
221
|
+
|
|
209
222
|
def runtime_helper(name)
|
|
210
223
|
helper = name.to_sym
|
|
211
224
|
@runtime_helpers << helper unless @runtime_helpers.include?(helper)
|
|
@@ -219,6 +232,14 @@ module Kapusta
|
|
|
219
232
|
"#{runtime_helper(name)}(#{rendered_args.join(', ')})"
|
|
220
233
|
end
|
|
221
234
|
|
|
235
|
+
def method_binding?(binding)
|
|
236
|
+
binding.is_a?(Env::MethodBinding)
|
|
237
|
+
end
|
|
238
|
+
|
|
239
|
+
def binding_value_code(binding)
|
|
240
|
+
method_binding?(binding) ? "method(#{binding.ruby_name.to_sym.inspect})" : binding
|
|
241
|
+
end
|
|
242
|
+
|
|
222
243
|
def parse_counted_for_bindings(bindings, env, current_scope)
|
|
223
244
|
name_sym = bindings[0]
|
|
224
245
|
loop_env = env.child
|
|
@@ -242,24 +263,18 @@ module Kapusta
|
|
|
242
263
|
|
|
243
264
|
def emit_counted_loop(ruby_name:, start_code:, finish_code:, step_code:,
|
|
244
265
|
until_form:, loop_env:, current_scope:, body_code:)
|
|
245
|
-
finish_var = temp('finish')
|
|
246
|
-
step_var = temp('step')
|
|
247
|
-
cmp_var = temp('cmp')
|
|
248
266
|
until_code = until_form ? "break if #{emit_expr(until_form, loop_env, current_scope)}" : nil
|
|
249
|
-
body = [until_code, body_code
|
|
267
|
+
body = [until_code, body_code].compact.reject(&:empty?).join("\n")
|
|
268
|
+
step_part = step_code == '1' ? '' : ", #{step_code}"
|
|
250
269
|
[
|
|
251
|
-
"#{
|
|
252
|
-
"#{finish_var} = #{finish_code}",
|
|
253
|
-
"#{step_var} = #{step_code}",
|
|
254
|
-
"#{cmp_var} = #{step_var} >= 0 ? :<= : :>=",
|
|
255
|
-
"while #{ruby_name}.public_send(#{cmp_var}, #{finish_var})",
|
|
270
|
+
"#{parenthesize(start_code)}.step(#{finish_code}#{step_part}) do |#{ruby_name}|",
|
|
256
271
|
indent(body),
|
|
257
272
|
'end'
|
|
258
273
|
].join("\n")
|
|
259
274
|
end
|
|
260
275
|
|
|
261
276
|
def sanitize_local(name)
|
|
262
|
-
base = Kapusta.kebab_to_snake(name)
|
|
277
|
+
base = Kapusta.kebab_to_snake(name.respond_to?(:name) ? name.name : name)
|
|
263
278
|
base = base.gsub('?', '_q').gsub('!', '_bang')
|
|
264
279
|
base = base.gsub(/[^a-zA-Z0-9_]/, '_')
|
|
265
280
|
if base.empty? || base.match?(/\A\d/) || base.match?(/\A[A-Z]/) || self.class::RUBY_KEYWORDS.include?(base)
|
|
@@ -34,11 +34,7 @@ module Kapusta
|
|
|
34
34
|
env = Env.new
|
|
35
35
|
body = emit_forms_with_headers(forms, env, :toplevel)
|
|
36
36
|
helpers = Runtime.helper_source(@runtime_helpers)
|
|
37
|
-
[
|
|
38
|
-
'# frozen_string_literal: true',
|
|
39
|
-
helpers,
|
|
40
|
-
body
|
|
41
|
-
].reject(&:empty?).join("\n\n") << "\n"
|
|
37
|
+
[helpers, body].reject(&:empty?).join("\n\n") << "\n"
|
|
42
38
|
end
|
|
43
39
|
end
|
|
44
40
|
end
|
|
@@ -38,11 +38,11 @@ module Kapusta
|
|
|
38
38
|
when 'when'
|
|
39
39
|
cond = items[1]
|
|
40
40
|
body = wrap_do(items[2..])
|
|
41
|
-
List.new([Sym.new('if'), cond, body
|
|
41
|
+
List.new([Sym.new('if'), cond, body])
|
|
42
42
|
when 'unless'
|
|
43
43
|
cond = items[1]
|
|
44
44
|
body = wrap_do(items[2..])
|
|
45
|
-
List.new([Sym.new('if'),
|
|
45
|
+
List.new([Sym.new('if'), List.new([Sym.new('not'), cond]), body])
|
|
46
46
|
when 'tset'
|
|
47
47
|
List.new([Sym.new('set'), List.new([Sym.new('.'), items[1], items[2]]), items[3]])
|
|
48
48
|
when 'pcall'
|
|
@@ -74,7 +74,7 @@ module Kapusta
|
|
|
74
74
|
end
|
|
75
75
|
|
|
76
76
|
def wrap_do(forms)
|
|
77
|
-
return
|
|
77
|
+
return if forms.empty?
|
|
78
78
|
return forms.first if forms.length == 1
|
|
79
79
|
|
|
80
80
|
List.new([Sym.new('do'), *forms])
|
|
@@ -85,29 +85,48 @@ module Kapusta
|
|
|
85
85
|
short = %w[-?> -?>>].include?(kind)
|
|
86
86
|
position = %w[-> -?>].include?(kind) ? :first : :last
|
|
87
87
|
|
|
88
|
+
return thread_short(forms, position) if short
|
|
89
|
+
|
|
88
90
|
forms[1..].reduce(value) do |memo, form|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
91
|
+
thread_step(memo, form, position)
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def thread_short(forms, position)
|
|
96
|
+
forms[1..].reduce(forms.first) do |memo, form|
|
|
97
|
+
temp = thread_temp
|
|
98
|
+
List.new([
|
|
99
|
+
Sym.new('let'),
|
|
100
|
+
Vec.new([temp, memo]),
|
|
101
|
+
List.new([
|
|
102
|
+
Sym.new('if'),
|
|
103
|
+
List.new([Sym.new('='), temp, nil]),
|
|
104
|
+
nil,
|
|
105
|
+
thread_step(temp, form, position)
|
|
106
|
+
])
|
|
107
|
+
])
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def thread_step(memo, form, position)
|
|
112
|
+
if form.is_a?(List)
|
|
113
|
+
if position == :first
|
|
114
|
+
List.new([form.items[0], memo, *form.items[1..]])
|
|
102
115
|
else
|
|
103
|
-
|
|
116
|
+
List.new([*form.items, memo])
|
|
104
117
|
end
|
|
118
|
+
else
|
|
119
|
+
List.new([form, memo])
|
|
105
120
|
end
|
|
106
121
|
end
|
|
107
122
|
|
|
123
|
+
def thread_temp
|
|
124
|
+
gensym('kap_thread')
|
|
125
|
+
end
|
|
126
|
+
|
|
108
127
|
def doto(forms)
|
|
109
128
|
value = forms.first
|
|
110
|
-
temp =
|
|
129
|
+
temp = gensym('kap_doto')
|
|
111
130
|
body = forms[1..].map do |form|
|
|
112
131
|
if form.is_a?(List)
|
|
113
132
|
List.new([form.items[0], temp, *form.items[1..]])
|
|
@@ -117,6 +136,11 @@ module Kapusta
|
|
|
117
136
|
end
|
|
118
137
|
List.new([Sym.new('let'), Vec.new([temp, value]), *body, temp])
|
|
119
138
|
end
|
|
139
|
+
|
|
140
|
+
def gensym(prefix)
|
|
141
|
+
@gensym_index = (@gensym_index || 0) + 1
|
|
142
|
+
GeneratedSym.new("#{prefix}_#{@gensym_index}", @gensym_index)
|
|
143
|
+
end
|
|
120
144
|
end
|
|
121
145
|
end
|
|
122
146
|
end
|