kapusta 0.1.3 → 0.1.5
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 +1 -0
- data/bin/compile-examples +24 -0
- data/examples/contains-duplicate.kap +7 -0
- data/lib/kapusta/compiler/emitter/bindings.rb +192 -41
- data/lib/kapusta/compiler/emitter/collections.rb +47 -49
- data/lib/kapusta/compiler/emitter/control_flow.rb +59 -18
- data/lib/kapusta/compiler/emitter/expressions.rb +24 -8
- data/lib/kapusta/compiler/emitter/interop.rb +123 -24
- data/lib/kapusta/compiler/emitter/patterns.rb +5 -8
- data/lib/kapusta/compiler/emitter/support.rb +108 -32
- data/lib/kapusta/compiler/normalizer.rb +1 -1
- data/lib/kapusta/compiler/runtime.rb +72 -71
- data/lib/kapusta/compiler.rb +2 -1
- data/lib/kapusta/env.rb +16 -0
- data/lib/kapusta/error.rb +5 -0
- data/lib/kapusta/formatter.rb +10 -20
- data/lib/kapusta/reader.rb +16 -12
- data/lib/kapusta/version.rb +1 -1
- data/lib/kapusta.rb +1 -0
- data/spec/cli_spec.rb +12 -21
- data/spec/examples_spec.rb +5 -1
- metadata +4 -1
|
@@ -36,7 +36,7 @@ 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
|
-
return
|
|
39
|
+
return emit_bound_call(env.lookup(head.name), args, env, current_scope) if env.defined?(head.name)
|
|
40
40
|
|
|
41
41
|
return emit_self_call(head.name, args, env, current_scope)
|
|
42
42
|
end
|
|
@@ -68,9 +68,7 @@ module Kapusta
|
|
|
68
68
|
when '.' then emit_lookup(args, env, current_scope)
|
|
69
69
|
when '?.' then emit_safe_lookup(args, env, current_scope)
|
|
70
70
|
when ':' then emit_colon(args, env, current_scope)
|
|
71
|
-
when '..' then
|
|
72
|
-
emit_expr(arg, env, current_scope)
|
|
73
|
-
end)
|
|
71
|
+
when '..' then emit_concat(args, env, current_scope)
|
|
74
72
|
when 'length' then "(#{emit_expr(args[0], env, current_scope)}).length"
|
|
75
73
|
when 'require' then emit_require(args[0], env, current_scope)
|
|
76
74
|
when 'module' then emit_module_expr(args, env)
|
|
@@ -79,7 +77,7 @@ module Kapusta
|
|
|
79
77
|
when 'raise' then emit_raise(args, env, current_scope)
|
|
80
78
|
when 'ivar' then runtime_call(:get_ivar, 'self', args[0].name.inspect)
|
|
81
79
|
when 'cvar' then runtime_call(:get_cvar, 'self', args[0].name.inspect)
|
|
82
|
-
when 'gvar' then
|
|
80
|
+
when 'gvar' then emit_gvar(args[0])
|
|
83
81
|
when 'ruby' then "Kernel.eval(#{emit_expr(args[0], env, current_scope)})"
|
|
84
82
|
when 'and' then emit_and(args, env, current_scope)
|
|
85
83
|
when 'or' then emit_or(args, env, current_scope)
|
|
@@ -95,13 +93,31 @@ module Kapusta
|
|
|
95
93
|
when '*' then emit_reduce(args, env, current_scope, '1', :*)
|
|
96
94
|
when '/' then emit_div(args, env, current_scope)
|
|
97
95
|
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)
|
|
96
|
+
when 'print' then emit_print(args, env, current_scope)
|
|
101
97
|
else
|
|
102
98
|
raise Error, "unknown special form: #{name}"
|
|
103
99
|
end
|
|
104
100
|
end
|
|
101
|
+
|
|
102
|
+
def emit_concat(args, env, current_scope)
|
|
103
|
+
return '""' if args.empty?
|
|
104
|
+
|
|
105
|
+
args.map { |arg| emit_string_part(arg, env, current_scope) }.join(' + ')
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def emit_print(args, env, current_scope)
|
|
109
|
+
return '$stdout.puts("")' if args.empty?
|
|
110
|
+
|
|
111
|
+
values = args.map { |arg| emit_string_part(arg, env, current_scope) }
|
|
112
|
+
output = values.length == 1 ? values.first : "[#{values.join(', ')}].join(\"\\t\")"
|
|
113
|
+
"$stdout.puts(#{output})"
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
def emit_string_part(arg, env, current_scope)
|
|
117
|
+
return arg.inspect if arg.is_a?(String)
|
|
118
|
+
|
|
119
|
+
runtime_call(:stringify, emit_expr(arg, env, current_scope))
|
|
120
|
+
end
|
|
105
121
|
end
|
|
106
122
|
end
|
|
107
123
|
end
|
|
@@ -37,26 +37,40 @@ module Kapusta
|
|
|
37
37
|
end
|
|
38
38
|
|
|
39
39
|
def emit_module_expr(args, env)
|
|
40
|
-
|
|
40
|
+
body = emit_sequence(args[1..], env, :module, allow_method_definitions: true, result: false).first
|
|
41
|
+
emit_module_wrapper(args[0], body)
|
|
41
42
|
end
|
|
42
43
|
|
|
43
44
|
def emit_class_expr(args, env)
|
|
44
45
|
name_sym, supers, body_forms = split_class_args(args)
|
|
46
|
+
body = emit_sequence(body_forms, env, :class, allow_method_definitions: true, result: false).first
|
|
45
47
|
emit_class_wrapper(name_sym, supers, env,
|
|
46
|
-
|
|
48
|
+
body)
|
|
47
49
|
end
|
|
48
50
|
|
|
49
51
|
def emit_module_wrapper(name_sym, body)
|
|
50
52
|
mod_var = temp('module')
|
|
51
|
-
|
|
52
|
-
(-> do
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
end).call
|
|
59
|
-
|
|
53
|
+
[
|
|
54
|
+
'(-> do',
|
|
55
|
+
indent("#{mod_var} = #{runtime_call(:ensure_module, 'self', name_sym.name.inspect)}"),
|
|
56
|
+
indent("#{mod_var}.module_eval do"),
|
|
57
|
+
indent(body, 2),
|
|
58
|
+
indent('end'),
|
|
59
|
+
indent(mod_var),
|
|
60
|
+
'end).call'
|
|
61
|
+
].join("\n")
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def emit_direct_module_header(name_sym, body)
|
|
65
|
+
const_name = simple_constant_name(name_sym)
|
|
66
|
+
return unless const_name
|
|
67
|
+
|
|
68
|
+
[
|
|
69
|
+
"module #{const_name}",
|
|
70
|
+
indent(body),
|
|
71
|
+
'end',
|
|
72
|
+
const_name
|
|
73
|
+
].join("\n")
|
|
60
74
|
end
|
|
61
75
|
|
|
62
76
|
def emit_class_wrapper(name_sym, supers, env, body)
|
|
@@ -67,15 +81,33 @@ module Kapusta
|
|
|
67
81
|
else
|
|
68
82
|
'Object'
|
|
69
83
|
end
|
|
70
|
-
|
|
71
|
-
(-> do
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
end).call
|
|
78
|
-
|
|
84
|
+
[
|
|
85
|
+
'(-> do',
|
|
86
|
+
indent("#{klass_var} = #{runtime_call(:ensure_class, 'self', name_sym.name.inspect, super_code)}"),
|
|
87
|
+
indent("#{klass_var}.class_eval do"),
|
|
88
|
+
indent(body, 2),
|
|
89
|
+
indent('end'),
|
|
90
|
+
indent(klass_var),
|
|
91
|
+
'end).call'
|
|
92
|
+
].join("\n")
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def emit_direct_class_header(name_sym, supers, body)
|
|
96
|
+
const_name = simple_constant_name(name_sym)
|
|
97
|
+
return unless const_name && supers.nil?
|
|
98
|
+
|
|
99
|
+
[
|
|
100
|
+
"class #{const_name}",
|
|
101
|
+
indent(body),
|
|
102
|
+
'end',
|
|
103
|
+
const_name
|
|
104
|
+
].join("\n")
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def simple_constant_name(name_sym)
|
|
108
|
+
return unless name_sym.is_a?(Sym) && name_sym.name.match?(/\A[A-Z]\w*\z/)
|
|
109
|
+
|
|
110
|
+
name_sym.name
|
|
79
111
|
end
|
|
80
112
|
|
|
81
113
|
def emit_try(args, env, current_scope)
|
|
@@ -103,8 +135,7 @@ module Kapusta
|
|
|
103
135
|
lines = ['begin', indent(emit_expr(args[0], env, current_scope))]
|
|
104
136
|
catches.each do |klass_form, bind_sym, body|
|
|
105
137
|
rescue_env = env.child
|
|
106
|
-
rescue_name =
|
|
107
|
-
rescue_env.define(bind_sym.name, rescue_name)
|
|
138
|
+
rescue_name = define_local(rescue_env, bind_sym.name)
|
|
108
139
|
body_code, = emit_sequence(body, rescue_env, current_scope, allow_method_definitions: false)
|
|
109
140
|
rescue_line =
|
|
110
141
|
if klass_form
|
|
@@ -176,9 +207,33 @@ module Kapusta
|
|
|
176
207
|
|
|
177
208
|
def emit_callable_call(callee_code, args, env, current_scope)
|
|
178
209
|
positional, kwargs, block = split_call_args(args, env, current_scope)
|
|
210
|
+
return emit_direct_callable_call(callee_code, positional) unless kwargs || block
|
|
211
|
+
|
|
179
212
|
runtime_call(:call, callee_code, "[#{positional.join(', ')}]", kwargs, block)
|
|
180
213
|
end
|
|
181
214
|
|
|
215
|
+
def emit_bound_call(binding, args, env, current_scope)
|
|
216
|
+
return emit_self_method_binding_call(binding, args, env, current_scope) if method_binding?(binding)
|
|
217
|
+
|
|
218
|
+
emit_callable_call(binding, args, env, current_scope)
|
|
219
|
+
end
|
|
220
|
+
|
|
221
|
+
def emit_self_method_binding_call(binding, args, env, current_scope)
|
|
222
|
+
positional = args.map { |arg| emit_expr(arg, env, current_scope) }
|
|
223
|
+
emit_direct_self_method_call(binding.ruby_name, positional)
|
|
224
|
+
end
|
|
225
|
+
|
|
226
|
+
def emit_direct_self_method_call(method_name, positional)
|
|
227
|
+
args = positional.join(', ')
|
|
228
|
+
args.empty? ? "#{method_name}()" : "#{method_name}(#{args})"
|
|
229
|
+
end
|
|
230
|
+
|
|
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
|
+
|
|
182
237
|
def emit_multisym_call(head, args, env, current_scope)
|
|
183
238
|
base_code, segments = multisym_base(head.segments, env)
|
|
184
239
|
if segments.empty?
|
|
@@ -192,10 +247,21 @@ module Kapusta
|
|
|
192
247
|
end
|
|
193
248
|
method_name = Kapusta.kebab_to_snake(segments.last).to_sym.inspect
|
|
194
249
|
positional, kwargs, block = split_call_args(args, env, current_scope)
|
|
250
|
+
if segments.length == 1 && !kwargs && !block && direct_method_name?(segments.last)
|
|
251
|
+
return emit_direct_method_call(receiver, Kapusta.kebab_to_snake(segments.last), positional)
|
|
252
|
+
end
|
|
253
|
+
|
|
195
254
|
runtime_call(:send_call, receiver, method_name, positional, kwargs, block)
|
|
196
255
|
end
|
|
197
256
|
end
|
|
198
257
|
|
|
258
|
+
def emit_direct_method_call(receiver, method_name, positional)
|
|
259
|
+
args = positional.join(', ')
|
|
260
|
+
rendered_receiver = simple_expression?(receiver) ? receiver : parenthesize(receiver)
|
|
261
|
+
suffix = args.empty? ? method_name : "#{method_name}(#{args})"
|
|
262
|
+
"#{rendered_receiver}.#{suffix}"
|
|
263
|
+
end
|
|
264
|
+
|
|
199
265
|
def emit_self_call(name, args, env, current_scope)
|
|
200
266
|
positional, kwargs, block = split_call_args(args, env, current_scope)
|
|
201
267
|
method_name = Kapusta.kebab_to_snake(name).to_sym.inspect
|
|
@@ -235,7 +301,7 @@ module Kapusta
|
|
|
235
301
|
name = sym.name
|
|
236
302
|
return 'self' if name == 'self'
|
|
237
303
|
return 'Float::INFINITY' if name == 'math.huge'
|
|
238
|
-
return env.lookup(name) if env.defined?(name)
|
|
304
|
+
return binding_value_code(env.lookup(name)) if env.defined?(name)
|
|
239
305
|
return emit_multisym_value(sym, env) if sym.dotted?
|
|
240
306
|
return 'ARGV' if name == 'ARGV'
|
|
241
307
|
return name if name.match?(/\A[A-Z]/)
|
|
@@ -243,6 +309,13 @@ module Kapusta
|
|
|
243
309
|
raise Error, "undefined symbol: #{name}"
|
|
244
310
|
end
|
|
245
311
|
|
|
312
|
+
def emit_gvar(sym)
|
|
313
|
+
ruby_name = global_name(sym.name)
|
|
314
|
+
return "$#{ruby_name}" if direct_global_name?(ruby_name)
|
|
315
|
+
|
|
316
|
+
runtime_call(:get_gvar, sym.name.inspect)
|
|
317
|
+
end
|
|
318
|
+
|
|
246
319
|
def emit_multisym_value(sym, env)
|
|
247
320
|
base_code, segments = multisym_base(sym.segments, env)
|
|
248
321
|
return base_code if segments.empty?
|
|
@@ -254,7 +327,7 @@ module Kapusta
|
|
|
254
327
|
if segments[0] == 'self'
|
|
255
328
|
['self', segments[1..]]
|
|
256
329
|
elsif env.defined?(segments[0])
|
|
257
|
-
[env.lookup(segments[0]), segments[1..]]
|
|
330
|
+
[binding_value_code(env.lookup(segments[0])), segments[1..]]
|
|
258
331
|
else
|
|
259
332
|
idx = 0
|
|
260
333
|
const_path = []
|
|
@@ -269,8 +342,34 @@ module Kapusta
|
|
|
269
342
|
end
|
|
270
343
|
|
|
271
344
|
def parenthesize(code)
|
|
345
|
+
return code if simple_expression?(code)
|
|
346
|
+
|
|
272
347
|
"(#{code})"
|
|
273
348
|
end
|
|
349
|
+
|
|
350
|
+
def direct_method_name?(name)
|
|
351
|
+
Kapusta.kebab_to_snake(name).match?(/\A[a-z_]\w*[!?=]?\z/)
|
|
352
|
+
end
|
|
353
|
+
|
|
354
|
+
def direct_global_name?(name)
|
|
355
|
+
name.match?(/\A[a-z_]\w*\z/)
|
|
356
|
+
end
|
|
357
|
+
|
|
358
|
+
def global_name(name)
|
|
359
|
+
Kapusta.kebab_to_snake(name).gsub(/[^a-zA-Z0-9_]/, '_')
|
|
360
|
+
end
|
|
361
|
+
|
|
362
|
+
def simple_expression?(code)
|
|
363
|
+
code.match?(/\A[a-z_]\w*\z/) ||
|
|
364
|
+
code.match?(/\A[A-Z]\w*(?:::[A-Z]\w*)*\z/) ||
|
|
365
|
+
code.match?(/\A[a-z_]\w*[!?=]?\([^()\n]*\)\z/) ||
|
|
366
|
+
code.match?(/\A\d+(?:\.\d+)?\z/) ||
|
|
367
|
+
code.match?(/\A[a-z_]\w*(?:\.[a-z_]\w*[!?=]?(?:\([^()\n]*\))?)+\z/) ||
|
|
368
|
+
code.match?(/\A:[a-zA-Z_]\w*[!?=]?\z/) ||
|
|
369
|
+
code.match?(/\A"(?:[^"\\]|\\.)*"\z/) ||
|
|
370
|
+
code.match?(/\A'(?:[^'\\]|\\.)*'\z/) ||
|
|
371
|
+
%w[nil true false self].include?(code)
|
|
372
|
+
end
|
|
274
373
|
end
|
|
275
374
|
end
|
|
276
375
|
end
|
|
@@ -10,8 +10,7 @@ module Kapusta
|
|
|
10
10
|
if pattern.is_a?(Sym)
|
|
11
11
|
return ['nil', env] if pattern.name == '_'
|
|
12
12
|
|
|
13
|
-
ruby_name =
|
|
14
|
-
env.define(pattern.name, ruby_name)
|
|
13
|
+
ruby_name = define_local(env, pattern.name)
|
|
15
14
|
["#{ruby_name} = #{value_code}", env]
|
|
16
15
|
else
|
|
17
16
|
bindings_var = temp('bindings')
|
|
@@ -20,8 +19,7 @@ module Kapusta
|
|
|
20
19
|
"#{bindings_var} = #{runtime_call(:destructure, emit_pattern(pattern), value_code)}"
|
|
21
20
|
]
|
|
22
21
|
pattern_names(pattern).each do |name|
|
|
23
|
-
ruby_name =
|
|
24
|
-
current_env.define(name, ruby_name)
|
|
22
|
+
ruby_name = define_local(current_env, name)
|
|
25
23
|
lines << "#{ruby_name} = #{bindings_var}.fetch(#{name.inspect})"
|
|
26
24
|
end
|
|
27
25
|
[lines.join("\n"), current_env]
|
|
@@ -32,8 +30,7 @@ module Kapusta
|
|
|
32
30
|
current_env = env
|
|
33
31
|
lines = []
|
|
34
32
|
binding_names.each do |name|
|
|
35
|
-
ruby_name =
|
|
36
|
-
current_env.define(name, ruby_name)
|
|
33
|
+
ruby_name = define_local(current_env, name)
|
|
37
34
|
lines << "#{ruby_name} = #{bindings_var}.fetch(#{name.inspect})"
|
|
38
35
|
end
|
|
39
36
|
[lines.join("\n"), current_env]
|
|
@@ -94,7 +91,7 @@ module Kapusta
|
|
|
94
91
|
if state[:bound_names].key?(name)
|
|
95
92
|
"[:ref, #{name.inspect}]"
|
|
96
93
|
elsif prefer_pin && mode == :match && env.defined?(name)
|
|
97
|
-
"[:pin, #{env.lookup(name)}]"
|
|
94
|
+
"[:pin, #{binding_value_code(env.lookup(name))}]"
|
|
98
95
|
else
|
|
99
96
|
state[:bound_names][name] = true
|
|
100
97
|
state[:binding_names] << name
|
|
@@ -124,7 +121,7 @@ module Kapusta
|
|
|
124
121
|
raise Error, "bad pin pattern: #{pattern.inspect}" unless name_sym.is_a?(Sym)
|
|
125
122
|
raise Error, "cannot pin undefined name: #{name_sym.name}" unless env.defined?(name_sym.name)
|
|
126
123
|
|
|
127
|
-
"[:pin, #{env.lookup(name_sym.name)}]"
|
|
124
|
+
"[:pin, #{binding_value_code(env.lookup(name_sym.name))}]"
|
|
128
125
|
end
|
|
129
126
|
|
|
130
127
|
def emit_or_match_pattern(pattern, env, mode:, allow_pins:, state:)
|
|
@@ -6,7 +6,7 @@ module Kapusta
|
|
|
6
6
|
module Support
|
|
7
7
|
private
|
|
8
8
|
|
|
9
|
-
def emit_forms_with_headers(forms, env, current_scope)
|
|
9
|
+
def emit_forms_with_headers(forms, env, current_scope, result: true)
|
|
10
10
|
i = 0
|
|
11
11
|
codes = []
|
|
12
12
|
while i < forms.length
|
|
@@ -15,7 +15,9 @@ module Kapusta
|
|
|
15
15
|
codes << emit_bodyless_header(form, forms[(i + 1)..], env, current_scope)
|
|
16
16
|
break
|
|
17
17
|
else
|
|
18
|
-
code, env = emit_form_in_sequence(form, env, current_scope,
|
|
18
|
+
code, env = emit_form_in_sequence(form, env, current_scope,
|
|
19
|
+
allow_method_definitions: true,
|
|
20
|
+
result_needed: result && i == forms.length - 1)
|
|
19
21
|
codes << code
|
|
20
22
|
i += 1
|
|
21
23
|
end
|
|
@@ -50,25 +52,34 @@ module Kapusta
|
|
|
50
52
|
if inner.length == 1 && bodyless_header?(inner[0])
|
|
51
53
|
emit_bodyless_header(inner[0], remaining_forms, env, :module)
|
|
52
54
|
else
|
|
53
|
-
emit_forms_with_headers(remaining_forms, env, :module)
|
|
55
|
+
emit_forms_with_headers(remaining_forms, env, :module, result: false)
|
|
54
56
|
end
|
|
55
|
-
emit_module_wrapper(name_sym, body)
|
|
57
|
+
emit_direct_module_header(name_sym, body) || emit_module_wrapper(name_sym, body)
|
|
56
58
|
else
|
|
57
59
|
name_sym, supers, = split_class_args(form.items[1..])
|
|
58
|
-
body = emit_forms_with_headers(remaining_forms, env, :class)
|
|
59
|
-
emit_class_wrapper(name_sym, supers, env, body)
|
|
60
|
+
body = emit_forms_with_headers(remaining_forms, env, :class, result: false)
|
|
61
|
+
emit_direct_class_header(name_sym, supers, body) || emit_class_wrapper(name_sym, supers, env, body)
|
|
60
62
|
end
|
|
61
63
|
end
|
|
62
64
|
|
|
63
|
-
def emit_form_in_sequence(form, env, current_scope, allow_method_definitions:)
|
|
64
|
-
if allow_method_definitions &&
|
|
65
|
-
|
|
66
|
-
|
|
65
|
+
def emit_form_in_sequence(form, env, current_scope, allow_method_definitions:, result_needed: true)
|
|
66
|
+
if allow_method_definitions &&
|
|
67
|
+
method_definition_form?(form) &&
|
|
68
|
+
%i[toplevel module class].include?(current_scope)
|
|
69
|
+
code, env = emit_definition_form(form, env, current_scope)
|
|
70
|
+
return [code, env] if code
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
if named_function_form?(form)
|
|
67
74
|
emit_named_fn_assignment(form, env, current_scope)
|
|
68
75
|
elsif local_form?(form)
|
|
69
|
-
emit_local_form(form, env, current_scope)
|
|
76
|
+
code, env = emit_local_form(form, env, current_scope)
|
|
77
|
+
code = code.delete_suffix("\nnil") unless result_needed
|
|
78
|
+
[code, env]
|
|
70
79
|
elsif do_form?(form)
|
|
71
|
-
emit_do_form(form.rest, env, current_scope)
|
|
80
|
+
emit_do_form(form.rest, env, current_scope, result_needed:)
|
|
81
|
+
elsif sequence_statement_form?(form)
|
|
82
|
+
emit_sequence_statement_form(form, env, current_scope, result_needed:)
|
|
72
83
|
elsif set_new_local_form?(form, env)
|
|
73
84
|
emit_set_form(form, env, current_scope)
|
|
74
85
|
else
|
|
@@ -76,22 +87,51 @@ module Kapusta
|
|
|
76
87
|
end
|
|
77
88
|
end
|
|
78
89
|
|
|
79
|
-
def emit_do_form(forms, env, current_scope)
|
|
80
|
-
body, new_env = emit_sequence(forms, env, current_scope,
|
|
90
|
+
def emit_do_form(forms, env, current_scope, result_needed: true)
|
|
91
|
+
body, new_env = emit_sequence(forms, env, current_scope,
|
|
92
|
+
allow_method_definitions: false,
|
|
93
|
+
result: result_needed)
|
|
94
|
+
return [body, new_env] unless result_needed
|
|
95
|
+
|
|
81
96
|
["begin\n#{indent(body)}\nend", new_env]
|
|
82
97
|
end
|
|
83
98
|
|
|
84
|
-
def emit_sequence(forms, env, current_scope, allow_method_definitions:)
|
|
99
|
+
def emit_sequence(forms, env, current_scope, allow_method_definitions:, result: true)
|
|
85
100
|
current_env = env
|
|
86
101
|
codes = []
|
|
87
|
-
forms.
|
|
102
|
+
forms.each_with_index do |form, index|
|
|
88
103
|
code, current_env = emit_form_in_sequence(form, current_env, current_scope,
|
|
89
|
-
allow_method_definitions
|
|
104
|
+
allow_method_definitions:,
|
|
105
|
+
result_needed: result && index == forms.length - 1)
|
|
90
106
|
codes << code
|
|
91
107
|
end
|
|
92
108
|
[codes.join("\n"), current_env]
|
|
93
109
|
end
|
|
94
110
|
|
|
111
|
+
def sequence_statement_form?(form)
|
|
112
|
+
return false unless form.is_a?(List) && form.head.is_a?(Sym)
|
|
113
|
+
|
|
114
|
+
%w[let while for each].include?(form.head.name)
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
def emit_sequence_statement_form(form, env, current_scope, result_needed:)
|
|
118
|
+
case form.head.name
|
|
119
|
+
when 'let'
|
|
120
|
+
return [emit_let_statement(form.rest, env, current_scope), env] unless result_needed
|
|
121
|
+
when 'while'
|
|
122
|
+
return [emit_while_statement(form.rest, env, current_scope), env]
|
|
123
|
+
when 'for'
|
|
124
|
+
return [emit_for_statement(form.rest, env, current_scope), env]
|
|
125
|
+
when 'each'
|
|
126
|
+
code = emit_each_statement(form.rest, env, current_scope)
|
|
127
|
+
return ["#{code}\nnil", env] if result_needed
|
|
128
|
+
|
|
129
|
+
return [code, env]
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
[emit_expr(form, env, current_scope), env]
|
|
133
|
+
end
|
|
134
|
+
|
|
95
135
|
def special_form?(name)
|
|
96
136
|
Compiler::SPECIAL_FORMS.include?(name)
|
|
97
137
|
end
|
|
@@ -140,7 +180,35 @@ module Kapusta
|
|
|
140
180
|
|
|
141
181
|
def temp(prefix)
|
|
142
182
|
@temp_index += 1
|
|
143
|
-
"
|
|
183
|
+
"kap_#{prefix}_#{@temp_index}"
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
def define_local(env, source_name, shadow: false)
|
|
187
|
+
ruby_name = local_name(source_name, env, shadow:)
|
|
188
|
+
env.define(source_name, ruby_name)
|
|
189
|
+
ruby_name
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
def local_name(source_name, env, shadow:)
|
|
193
|
+
base = sanitize_local(source_name)
|
|
194
|
+
base = "user_#{base}" if reserved_generated_name?(base)
|
|
195
|
+
return base unless ruby_name_defined?(env, base, shadow:)
|
|
196
|
+
|
|
197
|
+
index = 2
|
|
198
|
+
loop do
|
|
199
|
+
candidate = "#{base}_#{index}"
|
|
200
|
+
return candidate unless ruby_name_defined?(env, candidate, shadow:)
|
|
201
|
+
|
|
202
|
+
index += 1
|
|
203
|
+
end
|
|
204
|
+
end
|
|
205
|
+
|
|
206
|
+
def ruby_name_defined?(env, name, shadow:)
|
|
207
|
+
shadow ? env.local_ruby_name_defined?(name) : env.ruby_name_defined?(name)
|
|
208
|
+
end
|
|
209
|
+
|
|
210
|
+
def reserved_generated_name?(name)
|
|
211
|
+
name.start_with?('kap_', '__kap_')
|
|
144
212
|
end
|
|
145
213
|
|
|
146
214
|
def runtime_helper(name)
|
|
@@ -156,11 +224,18 @@ module Kapusta
|
|
|
156
224
|
"#{runtime_helper(name)}(#{rendered_args.join(', ')})"
|
|
157
225
|
end
|
|
158
226
|
|
|
227
|
+
def method_binding?(binding)
|
|
228
|
+
binding.is_a?(Env::MethodBinding)
|
|
229
|
+
end
|
|
230
|
+
|
|
231
|
+
def binding_value_code(binding)
|
|
232
|
+
method_binding?(binding) ? "method(#{binding.ruby_name.to_sym.inspect})" : binding
|
|
233
|
+
end
|
|
234
|
+
|
|
159
235
|
def parse_counted_for_bindings(bindings, env, current_scope)
|
|
160
236
|
name_sym = bindings[0]
|
|
161
|
-
ruby_name = temp(sanitize_local(name_sym.name))
|
|
162
237
|
loop_env = env.child
|
|
163
|
-
loop_env
|
|
238
|
+
ruby_name = define_local(loop_env, name_sym.name)
|
|
164
239
|
start_code = emit_expr(bindings[1], env, current_scope)
|
|
165
240
|
finish_code = emit_expr(bindings[2], env, current_scope)
|
|
166
241
|
step_code = '1'
|
|
@@ -184,24 +259,25 @@ module Kapusta
|
|
|
184
259
|
step_var = temp('step')
|
|
185
260
|
cmp_var = temp('cmp')
|
|
186
261
|
until_code = until_form ? "break if #{emit_expr(until_form, loop_env, current_scope)}" : nil
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
#{
|
|
190
|
-
#{
|
|
191
|
-
#{
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
RUBY
|
|
262
|
+
body = [until_code, body_code, "#{ruby_name} += #{step_var}"].compact.reject(&:empty?).join("\n")
|
|
263
|
+
[
|
|
264
|
+
"#{ruby_name} = #{start_code}",
|
|
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})",
|
|
269
|
+
indent(body),
|
|
270
|
+
'end'
|
|
271
|
+
].join("\n")
|
|
198
272
|
end
|
|
199
273
|
|
|
200
274
|
def sanitize_local(name)
|
|
201
275
|
base = Kapusta.kebab_to_snake(name)
|
|
202
276
|
base = base.gsub('?', '_q').gsub('!', '_bang')
|
|
203
277
|
base = base.gsub(/[^a-zA-Z0-9_]/, '_')
|
|
204
|
-
|
|
278
|
+
if base.empty? || base.match?(/\A\d/) || base.match?(/\A[A-Z]/) || self.class::RUBY_KEYWORDS.include?(base)
|
|
279
|
+
base = "_#{base}"
|
|
280
|
+
end
|
|
205
281
|
base
|
|
206
282
|
end
|
|
207
283
|
end
|