kapusta 0.1.5 → 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/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 +35 -25
- data/lib/kapusta/compiler/emitter/control_flow.rb +4 -4
- data/lib/kapusta/compiler/emitter/expressions.rb +13 -13
- data/lib/kapusta/compiler/emitter/interop.rb +91 -40
- data/lib/kapusta/compiler/emitter/patterns.rb +14 -11
- data/lib/kapusta/compiler/emitter/support.rb +13 -11
- data/lib/kapusta/compiler/emitter.rb +1 -5
- data/lib/kapusta/compiler/normalizer.rb +41 -17
- data/lib/kapusta/compiler/runtime.rb +0 -152
- data/lib/kapusta/env.rb +21 -6
- data/lib/kapusta/reader.rb +27 -3
- 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 +16 -7
|
@@ -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)
|
|
@@ -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,9 +247,16 @@ 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
262
|
def emit_bound_call(binding, args, env, current_scope)
|
|
@@ -228,30 +275,31 @@ module Kapusta
|
|
|
228
275
|
args.empty? ? "#{method_name}()" : "#{method_name}(#{args})"
|
|
229
276
|
end
|
|
230
277
|
|
|
231
|
-
def emit_direct_callable_call(callee_code, positional)
|
|
232
|
-
rendered_args = positional.join(', ')
|
|
233
|
-
suffix = rendered_args.empty? ? '.call' : ".call(#{rendered_args})"
|
|
234
|
-
"#{parenthesize(callee_code)}#{suffix}"
|
|
235
|
-
end
|
|
236
|
-
|
|
237
278
|
def emit_multisym_call(head, args, env, current_scope)
|
|
238
279
|
base_code, segments = multisym_base(head.segments, env)
|
|
239
280
|
if segments.empty?
|
|
240
281
|
emit_callable_call(base_code, args, env, current_scope)
|
|
241
282
|
else
|
|
242
|
-
receiver =
|
|
243
|
-
if segments.length == 1
|
|
244
|
-
base_code
|
|
245
|
-
else
|
|
246
|
-
runtime_call(:method_path_value, base_code, segments[0...-1].inspect)
|
|
247
|
-
end
|
|
248
|
-
method_name = Kapusta.kebab_to_snake(segments.last).to_sym.inspect
|
|
283
|
+
receiver = emit_method_path(base_code, segments[0...-1])
|
|
249
284
|
positional, kwargs, block = split_call_args(args, env, current_scope)
|
|
250
|
-
if
|
|
285
|
+
if !kwargs && !block && direct_method_name?(segments.last)
|
|
251
286
|
return emit_direct_method_call(receiver, Kapusta.kebab_to_snake(segments.last), positional)
|
|
252
287
|
end
|
|
253
288
|
|
|
254
|
-
|
|
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
|
|
255
303
|
end
|
|
256
304
|
end
|
|
257
305
|
|
|
@@ -265,7 +313,8 @@ module Kapusta
|
|
|
265
313
|
def emit_self_call(name, args, env, current_scope)
|
|
266
314
|
positional, kwargs, block = split_call_args(args, env, current_scope)
|
|
267
315
|
method_name = Kapusta.kebab_to_snake(name).to_sym.inspect
|
|
268
|
-
|
|
316
|
+
parts = build_call_args([method_name, *positional], kwargs, block)
|
|
317
|
+
"send(#{parts})"
|
|
269
318
|
end
|
|
270
319
|
|
|
271
320
|
def split_call_args(args, env, current_scope)
|
|
@@ -301,33 +350,31 @@ module Kapusta
|
|
|
301
350
|
name = sym.name
|
|
302
351
|
return 'self' if name == 'self'
|
|
303
352
|
return 'Float::INFINITY' if name == 'math.huge'
|
|
304
|
-
|
|
353
|
+
|
|
354
|
+
if (binding = env.lookup_if_defined(sym))
|
|
355
|
+
return binding_value_code(binding)
|
|
356
|
+
end
|
|
305
357
|
return emit_multisym_value(sym, env) if sym.dotted?
|
|
306
358
|
return 'ARGV' if name == 'ARGV'
|
|
307
359
|
return name if name.match?(/\A[A-Z]/)
|
|
308
360
|
|
|
309
|
-
|
|
361
|
+
emit_error!("undefined symbol: #{name}")
|
|
310
362
|
end
|
|
311
363
|
|
|
312
364
|
def emit_gvar(sym)
|
|
313
|
-
|
|
314
|
-
return "$#{ruby_name}" if direct_global_name?(ruby_name)
|
|
315
|
-
|
|
316
|
-
runtime_call(:get_gvar, sym.name.inspect)
|
|
365
|
+
"$#{global_name(sym.name)}"
|
|
317
366
|
end
|
|
318
367
|
|
|
319
368
|
def emit_multisym_value(sym, env)
|
|
320
369
|
base_code, segments = multisym_base(sym.segments, env)
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
runtime_call(:method_path_value, base_code, segments.inspect)
|
|
370
|
+
emit_method_path(base_code, segments)
|
|
324
371
|
end
|
|
325
372
|
|
|
326
373
|
def multisym_base(segments, env)
|
|
327
374
|
if segments[0] == 'self'
|
|
328
375
|
['self', segments[1..]]
|
|
329
|
-
elsif env.
|
|
330
|
-
[binding_value_code(
|
|
376
|
+
elsif (binding = env.lookup_if_defined(segments[0]))
|
|
377
|
+
[binding_value_code(binding), segments[1..]]
|
|
331
378
|
else
|
|
332
379
|
idx = 0
|
|
333
380
|
const_path = []
|
|
@@ -335,7 +382,7 @@ module Kapusta
|
|
|
335
382
|
const_path << segments[idx]
|
|
336
383
|
idx += 1
|
|
337
384
|
end
|
|
338
|
-
|
|
385
|
+
emit_error!("bad multisym: #{segments.join('.')}") if const_path.empty?
|
|
339
386
|
|
|
340
387
|
[const_path.join('::'), segments[idx..]]
|
|
341
388
|
end
|
|
@@ -351,10 +398,6 @@ module Kapusta
|
|
|
351
398
|
Kapusta.kebab_to_snake(name).match?(/\A[a-z_]\w*[!?=]?\z/)
|
|
352
399
|
end
|
|
353
400
|
|
|
354
|
-
def direct_global_name?(name)
|
|
355
|
-
name.match?(/\A[a-z_]\w*\z/)
|
|
356
|
-
end
|
|
357
|
-
|
|
358
401
|
def global_name(name)
|
|
359
402
|
Kapusta.kebab_to_snake(name).gsub(/[^a-zA-Z0-9_]/, '_')
|
|
360
403
|
end
|
|
@@ -364,11 +407,19 @@ module Kapusta
|
|
|
364
407
|
code.match?(/\A[A-Z]\w*(?:::[A-Z]\w*)*\z/) ||
|
|
365
408
|
code.match?(/\A[a-z_]\w*[!?=]?\([^()\n]*\)\z/) ||
|
|
366
409
|
code.match?(/\A\d+(?:\.\d+)?\z/) ||
|
|
367
|
-
code.match?(/\A[a-z_]\w*(?:\.[a-z_]\w*[!?=]?(?:\([^()\n]*\))
|
|
410
|
+
code.match?(/\A[a-z_]\w*(?:\.[a-z_]\w*[!?=]?(?:\([^()\n]*\))?|\[[^\[\]]*\])+\z/) ||
|
|
368
411
|
code.match?(/\A:[a-zA-Z_]\w*[!?=]?\z/) ||
|
|
369
412
|
code.match?(/\A"(?:[^"\\]|\\.)*"\z/) ||
|
|
370
413
|
code.match?(/\A'(?:[^'\\]|\\.)*'\z/) ||
|
|
371
|
-
%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?(')'))
|
|
372
423
|
end
|
|
373
424
|
end
|
|
374
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, #{binding_value_code(
|
|
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 = []
|
|
@@ -191,7 +195,7 @@ module Kapusta
|
|
|
191
195
|
|
|
192
196
|
def local_name(source_name, env, shadow:)
|
|
193
197
|
base = sanitize_local(source_name)
|
|
194
|
-
base = "user_#{base}" if reserved_generated_name?(base)
|
|
198
|
+
base = "user_#{base}" if !generated_symbol?(source_name) && reserved_generated_name?(base)
|
|
195
199
|
return base unless ruby_name_defined?(env, base, shadow:)
|
|
196
200
|
|
|
197
201
|
index = 2
|
|
@@ -211,6 +215,10 @@ module Kapusta
|
|
|
211
215
|
name.start_with?('kap_', '__kap_')
|
|
212
216
|
end
|
|
213
217
|
|
|
218
|
+
def generated_symbol?(source_name)
|
|
219
|
+
source_name.is_a?(GeneratedSym)
|
|
220
|
+
end
|
|
221
|
+
|
|
214
222
|
def runtime_helper(name)
|
|
215
223
|
helper = name.to_sym
|
|
216
224
|
@runtime_helpers << helper unless @runtime_helpers.include?(helper)
|
|
@@ -255,24 +263,18 @@ module Kapusta
|
|
|
255
263
|
|
|
256
264
|
def emit_counted_loop(ruby_name:, start_code:, finish_code:, step_code:,
|
|
257
265
|
until_form:, loop_env:, current_scope:, body_code:)
|
|
258
|
-
finish_var = temp('finish')
|
|
259
|
-
step_var = temp('step')
|
|
260
|
-
cmp_var = temp('cmp')
|
|
261
266
|
until_code = until_form ? "break if #{emit_expr(until_form, loop_env, current_scope)}" : nil
|
|
262
|
-
body = [until_code, body_code
|
|
267
|
+
body = [until_code, body_code].compact.reject(&:empty?).join("\n")
|
|
268
|
+
step_part = step_code == '1' ? '' : ", #{step_code}"
|
|
263
269
|
[
|
|
264
|
-
"#{
|
|
265
|
-
"#{finish_var} = #{finish_code}",
|
|
266
|
-
"#{step_var} = #{step_code}",
|
|
267
|
-
"#{cmp_var} = #{step_var} >= 0 ? :<= : :>=",
|
|
268
|
-
"while #{ruby_name}.public_send(#{cmp_var}, #{finish_var})",
|
|
270
|
+
"#{parenthesize(start_code)}.step(#{finish_code}#{step_part}) do |#{ruby_name}|",
|
|
269
271
|
indent(body),
|
|
270
272
|
'end'
|
|
271
273
|
].join("\n")
|
|
272
274
|
end
|
|
273
275
|
|
|
274
276
|
def sanitize_local(name)
|
|
275
|
-
base = Kapusta.kebab_to_snake(name)
|
|
277
|
+
base = Kapusta.kebab_to_snake(name.respond_to?(:name) ? name.name : name)
|
|
276
278
|
base = base.gsub('?', '_q').gsub('!', '_bang')
|
|
277
279
|
base = base.gsub(/[^a-zA-Z0-9_]/, '_')
|
|
278
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'
|
|
@@ -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
|
|
@@ -4,106 +4,11 @@ module Kapusta
|
|
|
4
4
|
module Compiler
|
|
5
5
|
module Runtime
|
|
6
6
|
HELPER_DEPENDENCIES = {
|
|
7
|
-
stringify: %i[repr],
|
|
8
|
-
print_values: %i[stringify],
|
|
9
|
-
concat: %i[stringify],
|
|
10
|
-
method_path_value: %i[kebab_to_snake],
|
|
11
|
-
set_method_path: %i[kebab_to_snake],
|
|
12
|
-
get_ivar: %i[kebab_to_snake],
|
|
13
|
-
set_ivar: %i[kebab_to_snake],
|
|
14
|
-
get_cvar: %i[current_class_scope kebab_to_snake],
|
|
15
|
-
set_cvar: %i[current_class_scope kebab_to_snake],
|
|
16
|
-
get_gvar: %i[kebab_to_snake],
|
|
17
|
-
set_gvar: %i[kebab_to_snake],
|
|
18
7
|
destructure: %i[destructure_into],
|
|
19
8
|
match_pattern: %i[match_pattern_into]
|
|
20
9
|
}.freeze
|
|
21
10
|
|
|
22
11
|
HELPER_SOURCES = {
|
|
23
|
-
kebab_to_snake: <<~RUBY.chomp,
|
|
24
|
-
def kap_kebab_to_snake(name)
|
|
25
|
-
name.tr('-', '_')
|
|
26
|
-
end
|
|
27
|
-
RUBY
|
|
28
|
-
call: <<~'RUBY'.chomp,
|
|
29
|
-
def kap_call(callee, positional, kwargs = nil, block = nil)
|
|
30
|
-
raise "not callable: #{callee.inspect}" unless callee.respond_to?(:call)
|
|
31
|
-
|
|
32
|
-
if block
|
|
33
|
-
kwargs ? callee.call(*positional, **kwargs, &block) : callee.call(*positional, &block)
|
|
34
|
-
else
|
|
35
|
-
kwargs ? callee.call(*positional, **kwargs) : callee.call(*positional)
|
|
36
|
-
end
|
|
37
|
-
end
|
|
38
|
-
RUBY
|
|
39
|
-
send_call: <<~RUBY.chomp,
|
|
40
|
-
def kap_send_call(receiver, method_name, positional, kwargs = nil, block = nil)
|
|
41
|
-
if block
|
|
42
|
-
if kwargs
|
|
43
|
-
receiver.public_send(method_name, *positional, **kwargs, &block)
|
|
44
|
-
else
|
|
45
|
-
receiver.public_send(method_name, *positional, &block)
|
|
46
|
-
end
|
|
47
|
-
elsif kwargs
|
|
48
|
-
receiver.public_send(method_name, *positional, **kwargs)
|
|
49
|
-
else
|
|
50
|
-
receiver.public_send(method_name, *positional)
|
|
51
|
-
end
|
|
52
|
-
end
|
|
53
|
-
RUBY
|
|
54
|
-
invoke_self: <<~RUBY.chomp,
|
|
55
|
-
def kap_invoke_self(receiver, method_name, positional, kwargs = nil, block = nil)
|
|
56
|
-
if block
|
|
57
|
-
if kwargs
|
|
58
|
-
receiver.send(method_name, *positional, **kwargs, &block)
|
|
59
|
-
else
|
|
60
|
-
receiver.send(method_name, *positional, &block)
|
|
61
|
-
end
|
|
62
|
-
else
|
|
63
|
-
kwargs ? receiver.send(method_name, *positional, **kwargs) : receiver.send(method_name, *positional)
|
|
64
|
-
end
|
|
65
|
-
end
|
|
66
|
-
RUBY
|
|
67
|
-
stringify: <<~RUBY.chomp,
|
|
68
|
-
def kap_stringify(value)
|
|
69
|
-
case value
|
|
70
|
-
when nil then 'nil'
|
|
71
|
-
when Array, Hash then kap_repr(value)
|
|
72
|
-
else value.to_s
|
|
73
|
-
end
|
|
74
|
-
end
|
|
75
|
-
RUBY
|
|
76
|
-
repr: <<~'RUBY'.chomp,
|
|
77
|
-
def kap_repr(value)
|
|
78
|
-
case value
|
|
79
|
-
when nil then 'nil'
|
|
80
|
-
when true, false then value.to_s
|
|
81
|
-
when String, Symbol then value.inspect
|
|
82
|
-
when Array
|
|
83
|
-
"[#{value.map { |item| kap_repr(item) }.join(', ')}]"
|
|
84
|
-
when Hash
|
|
85
|
-
"{#{value.map { |key, item| "#{kap_repr(key)}=>#{kap_repr(item)}" }.join(', ')}}"
|
|
86
|
-
else
|
|
87
|
-
value.inspect
|
|
88
|
-
end
|
|
89
|
-
end
|
|
90
|
-
RUBY
|
|
91
|
-
print_values: <<~'RUBY'.chomp,
|
|
92
|
-
def kap_print_values(*values)
|
|
93
|
-
$stdout.puts(values.map { |value| kap_stringify(value) }.join("\t"))
|
|
94
|
-
nil
|
|
95
|
-
end
|
|
96
|
-
RUBY
|
|
97
|
-
concat: <<~RUBY.chomp,
|
|
98
|
-
def kap_concat(values)
|
|
99
|
-
values.map { |value| kap_stringify(value) }.join
|
|
100
|
-
end
|
|
101
|
-
RUBY
|
|
102
|
-
get_path: <<~RUBY.chomp,
|
|
103
|
-
def kap_get_path(obj, keys)
|
|
104
|
-
keys.reduce(obj) { |acc, key| acc[key] }
|
|
105
|
-
end
|
|
106
|
-
RUBY
|
|
107
12
|
qget_path: <<~RUBY.chomp,
|
|
108
13
|
def kap_qget_path(obj, keys)
|
|
109
14
|
keys.each do |key|
|
|
@@ -114,63 +19,6 @@ module Kapusta
|
|
|
114
19
|
obj
|
|
115
20
|
end
|
|
116
21
|
RUBY
|
|
117
|
-
set_path: <<~RUBY.chomp,
|
|
118
|
-
def kap_set_path(obj, keys, value)
|
|
119
|
-
target = obj
|
|
120
|
-
keys[0...-1].each { |key| target = target[key] }
|
|
121
|
-
target[keys.last] = value
|
|
122
|
-
end
|
|
123
|
-
RUBY
|
|
124
|
-
method_path_value: <<~RUBY.chomp,
|
|
125
|
-
def kap_method_path_value(base, segments)
|
|
126
|
-
segments.reduce(base) { |obj, segment| obj.public_send(kap_kebab_to_snake(segment).to_sym) }
|
|
127
|
-
end
|
|
128
|
-
RUBY
|
|
129
|
-
set_method_path: <<~'RUBY'.chomp,
|
|
130
|
-
def kap_set_method_path(base, segments, value)
|
|
131
|
-
target = base
|
|
132
|
-
segments[0...-1].each do |segment|
|
|
133
|
-
target = target.public_send(kap_kebab_to_snake(segment).to_sym)
|
|
134
|
-
end
|
|
135
|
-
setter = "#{kap_kebab_to_snake(segments.last)}="
|
|
136
|
-
target.public_send(setter.to_sym, value)
|
|
137
|
-
end
|
|
138
|
-
RUBY
|
|
139
|
-
current_class_scope: <<~RUBY.chomp,
|
|
140
|
-
def kap_current_class_scope(receiver)
|
|
141
|
-
receiver.is_a?(Module) ? receiver : receiver.class
|
|
142
|
-
end
|
|
143
|
-
RUBY
|
|
144
|
-
get_ivar: <<~'RUBY'.chomp,
|
|
145
|
-
def kap_get_ivar(receiver, name)
|
|
146
|
-
receiver.instance_variable_get("@#{kap_kebab_to_snake(name)}")
|
|
147
|
-
end
|
|
148
|
-
RUBY
|
|
149
|
-
set_ivar: <<~'RUBY'.chomp,
|
|
150
|
-
def kap_set_ivar(receiver, name, value)
|
|
151
|
-
receiver.instance_variable_set("@#{kap_kebab_to_snake(name)}", value)
|
|
152
|
-
end
|
|
153
|
-
RUBY
|
|
154
|
-
get_cvar: <<~'RUBY'.chomp,
|
|
155
|
-
def kap_get_cvar(receiver, name)
|
|
156
|
-
kap_current_class_scope(receiver).class_variable_get("@@#{kap_kebab_to_snake(name)}")
|
|
157
|
-
end
|
|
158
|
-
RUBY
|
|
159
|
-
set_cvar: <<~'RUBY'.chomp,
|
|
160
|
-
def kap_set_cvar(receiver, name, value)
|
|
161
|
-
kap_current_class_scope(receiver).class_variable_set("@@#{kap_kebab_to_snake(name)}", value)
|
|
162
|
-
end
|
|
163
|
-
RUBY
|
|
164
|
-
get_gvar: <<~'RUBY'.chomp,
|
|
165
|
-
def kap_get_gvar(name)
|
|
166
|
-
Kernel.eval("$#{kap_kebab_to_snake(name)}", binding, __FILE__, __LINE__)
|
|
167
|
-
end
|
|
168
|
-
RUBY
|
|
169
|
-
set_gvar: <<~'RUBY'.chomp,
|
|
170
|
-
def kap_set_gvar(name, value)
|
|
171
|
-
Kernel.eval("$#{kap_kebab_to_snake(name)} = value", binding, __FILE__, __LINE__)
|
|
172
|
-
end
|
|
173
|
-
RUBY
|
|
174
22
|
ensure_module: <<~RUBY.chomp,
|
|
175
23
|
def kap_ensure_module(holder, path)
|
|
176
24
|
segments = path.split('.')
|