kapusta 0.1.3 → 0.1.4
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/examples/contains-duplicate.kap +7 -0
- data/lib/kapusta/compiler/emitter/bindings.rb +87 -39
- 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 +1 -1
- data/lib/kapusta/compiler/emitter/interop.rb +105 -22
- data/lib/kapusta/compiler/emitter/patterns.rb +3 -6
- data/lib/kapusta/compiler/emitter/support.rb +92 -29
- data/lib/kapusta/compiler/runtime.rb +49 -49
- data/lib/kapusta/compiler.rb +2 -1
- data/lib/kapusta/env.rb +8 -0
- data/lib/kapusta/error.rb +5 -0
- data/lib/kapusta/formatter.rb +1 -10
- data/lib/kapusta/reader.rb +13 -9
- 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 +3 -1
|
@@ -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 nil 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 nil 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 nil 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,17 @@ 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_direct_callable_call(callee_code, positional)
|
|
216
|
+
rendered_args = positional.join(', ')
|
|
217
|
+
suffix = rendered_args.empty? ? '.call' : ".call(#{rendered_args})"
|
|
218
|
+
"#{parenthesize(callee_code)}#{suffix}"
|
|
219
|
+
end
|
|
220
|
+
|
|
182
221
|
def emit_multisym_call(head, args, env, current_scope)
|
|
183
222
|
base_code, segments = multisym_base(head.segments, env)
|
|
184
223
|
if segments.empty?
|
|
@@ -192,10 +231,21 @@ module Kapusta
|
|
|
192
231
|
end
|
|
193
232
|
method_name = Kapusta.kebab_to_snake(segments.last).to_sym.inspect
|
|
194
233
|
positional, kwargs, block = split_call_args(args, env, current_scope)
|
|
234
|
+
if segments.length == 1 && !kwargs && !block && direct_method_name?(segments.last)
|
|
235
|
+
return emit_direct_method_call(receiver, Kapusta.kebab_to_snake(segments.last), positional)
|
|
236
|
+
end
|
|
237
|
+
|
|
195
238
|
runtime_call(:send_call, receiver, method_name, positional, kwargs, block)
|
|
196
239
|
end
|
|
197
240
|
end
|
|
198
241
|
|
|
242
|
+
def emit_direct_method_call(receiver, method_name, positional)
|
|
243
|
+
args = positional.join(', ')
|
|
244
|
+
rendered_receiver = simple_expression?(receiver) ? receiver : parenthesize(receiver)
|
|
245
|
+
suffix = args.empty? ? method_name : "#{method_name}(#{args})"
|
|
246
|
+
"#{rendered_receiver}.#{suffix}"
|
|
247
|
+
end
|
|
248
|
+
|
|
199
249
|
def emit_self_call(name, args, env, current_scope)
|
|
200
250
|
positional, kwargs, block = split_call_args(args, env, current_scope)
|
|
201
251
|
method_name = Kapusta.kebab_to_snake(name).to_sym.inspect
|
|
@@ -243,6 +293,13 @@ module Kapusta
|
|
|
243
293
|
raise Error, "undefined symbol: #{name}"
|
|
244
294
|
end
|
|
245
295
|
|
|
296
|
+
def emit_gvar(sym)
|
|
297
|
+
ruby_name = global_name(sym.name)
|
|
298
|
+
return "$#{ruby_name}" if direct_global_name?(ruby_name)
|
|
299
|
+
|
|
300
|
+
runtime_call(:get_gvar, sym.name.inspect)
|
|
301
|
+
end
|
|
302
|
+
|
|
246
303
|
def emit_multisym_value(sym, env)
|
|
247
304
|
base_code, segments = multisym_base(sym.segments, env)
|
|
248
305
|
return base_code if segments.empty?
|
|
@@ -269,8 +326,34 @@ module Kapusta
|
|
|
269
326
|
end
|
|
270
327
|
|
|
271
328
|
def parenthesize(code)
|
|
329
|
+
return code if simple_expression?(code)
|
|
330
|
+
|
|
272
331
|
"(#{code})"
|
|
273
332
|
end
|
|
333
|
+
|
|
334
|
+
def direct_method_name?(name)
|
|
335
|
+
Kapusta.kebab_to_snake(name).match?(/\A[a-z_]\w*[!?=]?\z/)
|
|
336
|
+
end
|
|
337
|
+
|
|
338
|
+
def direct_global_name?(name)
|
|
339
|
+
name.match?(/\A[a-z_]\w*\z/)
|
|
340
|
+
end
|
|
341
|
+
|
|
342
|
+
def global_name(name)
|
|
343
|
+
Kapusta.kebab_to_snake(name).gsub(/[^a-zA-Z0-9_]/, '_')
|
|
344
|
+
end
|
|
345
|
+
|
|
346
|
+
def simple_expression?(code)
|
|
347
|
+
code.match?(/\A[a-z_]\w*\z/) ||
|
|
348
|
+
code.match?(/\A[A-Z]\w*(?:::[A-Z]\w*)*\z/) ||
|
|
349
|
+
code.match?(/\A[a-z_]\w*[!?=]?\([^()\n]*\)\z/) ||
|
|
350
|
+
code.match?(/\A\d+(?:\.\d+)?\z/) ||
|
|
351
|
+
code.match?(/\A[a-z_]\w*(?:\.[a-z_]\w*[!?=]?(?:\([^()\n]*\))?)+\z/) ||
|
|
352
|
+
code.match?(/\A:[a-zA-Z_]\w*[!?=]?\z/) ||
|
|
353
|
+
code.match?(/\A"(?:[^"\\]|\\.)*"\z/) ||
|
|
354
|
+
code.match?(/\A'(?:[^'\\]|\\.)*'\z/) ||
|
|
355
|
+
%w[nil true false self].include?(code)
|
|
356
|
+
end
|
|
274
357
|
end
|
|
275
358
|
end
|
|
276
359
|
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]
|
|
@@ -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,29 @@ 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:)
|
|
65
|
+
def emit_form_in_sequence(form, env, current_scope, allow_method_definitions:, result_needed: true)
|
|
64
66
|
if allow_method_definitions && method_definition_form?(form) && %i[module class].include?(current_scope)
|
|
65
67
|
[emit_method_definition(form, env), env]
|
|
66
68
|
elsif named_function_form?(form)
|
|
67
69
|
emit_named_fn_assignment(form, env, current_scope)
|
|
68
70
|
elsif local_form?(form)
|
|
69
|
-
emit_local_form(form, env, current_scope)
|
|
71
|
+
code, env = emit_local_form(form, env, current_scope)
|
|
72
|
+
code = code.delete_suffix("\nnil") unless result_needed
|
|
73
|
+
[code, env]
|
|
70
74
|
elsif do_form?(form)
|
|
71
|
-
emit_do_form(form.rest, env, current_scope)
|
|
75
|
+
emit_do_form(form.rest, env, current_scope, result_needed:)
|
|
76
|
+
elsif sequence_statement_form?(form)
|
|
77
|
+
emit_sequence_statement_form(form, env, current_scope, result_needed:)
|
|
72
78
|
elsif set_new_local_form?(form, env)
|
|
73
79
|
emit_set_form(form, env, current_scope)
|
|
74
80
|
else
|
|
@@ -76,22 +82,51 @@ module Kapusta
|
|
|
76
82
|
end
|
|
77
83
|
end
|
|
78
84
|
|
|
79
|
-
def emit_do_form(forms, env, current_scope)
|
|
80
|
-
body, new_env = emit_sequence(forms, env, current_scope,
|
|
85
|
+
def emit_do_form(forms, env, current_scope, result_needed: true)
|
|
86
|
+
body, new_env = emit_sequence(forms, env, current_scope,
|
|
87
|
+
allow_method_definitions: false,
|
|
88
|
+
result: result_needed)
|
|
89
|
+
return [body, new_env] unless result_needed
|
|
90
|
+
|
|
81
91
|
["begin\n#{indent(body)}\nend", new_env]
|
|
82
92
|
end
|
|
83
93
|
|
|
84
|
-
def emit_sequence(forms, env, current_scope, allow_method_definitions:)
|
|
94
|
+
def emit_sequence(forms, env, current_scope, allow_method_definitions:, result: true)
|
|
85
95
|
current_env = env
|
|
86
96
|
codes = []
|
|
87
|
-
forms.
|
|
97
|
+
forms.each_with_index do |form, index|
|
|
88
98
|
code, current_env = emit_form_in_sequence(form, current_env, current_scope,
|
|
89
|
-
allow_method_definitions
|
|
99
|
+
allow_method_definitions:,
|
|
100
|
+
result_needed: result && index == forms.length - 1)
|
|
90
101
|
codes << code
|
|
91
102
|
end
|
|
92
103
|
[codes.join("\n"), current_env]
|
|
93
104
|
end
|
|
94
105
|
|
|
106
|
+
def sequence_statement_form?(form)
|
|
107
|
+
return false unless form.is_a?(List) && form.head.is_a?(Sym)
|
|
108
|
+
|
|
109
|
+
%w[let while for each].include?(form.head.name)
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def emit_sequence_statement_form(form, env, current_scope, result_needed:)
|
|
113
|
+
case form.head.name
|
|
114
|
+
when 'let'
|
|
115
|
+
return [emit_let_statement(form.rest, env, current_scope), env] unless result_needed
|
|
116
|
+
when 'while'
|
|
117
|
+
return [emit_while_statement(form.rest, env, current_scope), env]
|
|
118
|
+
when 'for'
|
|
119
|
+
return [emit_for_statement(form.rest, env, current_scope), env]
|
|
120
|
+
when 'each'
|
|
121
|
+
code = emit_each_statement(form.rest, env, current_scope)
|
|
122
|
+
return ["#{code}\nnil", env] if result_needed
|
|
123
|
+
|
|
124
|
+
return [code, env]
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
[emit_expr(form, env, current_scope), env]
|
|
128
|
+
end
|
|
129
|
+
|
|
95
130
|
def special_form?(name)
|
|
96
131
|
Compiler::SPECIAL_FORMS.include?(name)
|
|
97
132
|
end
|
|
@@ -140,7 +175,35 @@ module Kapusta
|
|
|
140
175
|
|
|
141
176
|
def temp(prefix)
|
|
142
177
|
@temp_index += 1
|
|
143
|
-
"
|
|
178
|
+
"kap_#{prefix}_#{@temp_index}"
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
def define_local(env, source_name, shadow: false)
|
|
182
|
+
ruby_name = local_name(source_name, env, shadow:)
|
|
183
|
+
env.define(source_name, ruby_name)
|
|
184
|
+
ruby_name
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
def local_name(source_name, env, shadow:)
|
|
188
|
+
base = sanitize_local(source_name)
|
|
189
|
+
base = "user_#{base}" if reserved_generated_name?(base)
|
|
190
|
+
return base unless ruby_name_defined?(env, base, shadow:)
|
|
191
|
+
|
|
192
|
+
index = 2
|
|
193
|
+
loop do
|
|
194
|
+
candidate = "#{base}_#{index}"
|
|
195
|
+
return candidate unless ruby_name_defined?(env, candidate, shadow:)
|
|
196
|
+
|
|
197
|
+
index += 1
|
|
198
|
+
end
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
def ruby_name_defined?(env, name, shadow:)
|
|
202
|
+
shadow ? env.local_ruby_name_defined?(name) : env.ruby_name_defined?(name)
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
def reserved_generated_name?(name)
|
|
206
|
+
name.start_with?('kap_', '__kap_')
|
|
144
207
|
end
|
|
145
208
|
|
|
146
209
|
def runtime_helper(name)
|
|
@@ -158,9 +221,8 @@ module Kapusta
|
|
|
158
221
|
|
|
159
222
|
def parse_counted_for_bindings(bindings, env, current_scope)
|
|
160
223
|
name_sym = bindings[0]
|
|
161
|
-
ruby_name = temp(sanitize_local(name_sym.name))
|
|
162
224
|
loop_env = env.child
|
|
163
|
-
loop_env
|
|
225
|
+
ruby_name = define_local(loop_env, name_sym.name)
|
|
164
226
|
start_code = emit_expr(bindings[1], env, current_scope)
|
|
165
227
|
finish_code = emit_expr(bindings[2], env, current_scope)
|
|
166
228
|
step_code = '1'
|
|
@@ -184,24 +246,25 @@ module Kapusta
|
|
|
184
246
|
step_var = temp('step')
|
|
185
247
|
cmp_var = temp('cmp')
|
|
186
248
|
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
|
|
249
|
+
body = [until_code, body_code, "#{ruby_name} += #{step_var}"].compact.reject(&:empty?).join("\n")
|
|
250
|
+
[
|
|
251
|
+
"#{ruby_name} = #{start_code}",
|
|
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})",
|
|
256
|
+
indent(body),
|
|
257
|
+
'end'
|
|
258
|
+
].join("\n")
|
|
198
259
|
end
|
|
199
260
|
|
|
200
261
|
def sanitize_local(name)
|
|
201
262
|
base = Kapusta.kebab_to_snake(name)
|
|
202
263
|
base = base.gsub('?', '_q').gsub('!', '_bang')
|
|
203
264
|
base = base.gsub(/[^a-zA-Z0-9_]/, '_')
|
|
204
|
-
|
|
265
|
+
if base.empty? || base.match?(/\A\d/) || base.match?(/\A[A-Z]/) || self.class::RUBY_KEYWORDS.include?(base)
|
|
266
|
+
base = "_#{base}"
|
|
267
|
+
end
|
|
205
268
|
base
|
|
206
269
|
end
|
|
207
270
|
end
|