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
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 52369f7a4cd658385e7ddc69f552477d94b55340cb9f796ac635550a7def6e6a
|
|
4
|
+
data.tar.gz: 6d673c9c387b52b0e4cd9e55b8ae2cf1d0a7e2c5f741dc7705f405b0ddaf25c6
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 53510507ba41a4d67ec73184eaef665fee182220407046eb9db64cf05cae7d28a315b5b8cf4ace6071b359a83801bd85d7e45ce5e4aefd3c608fef614f116fee
|
|
7
|
+
data.tar.gz: ff1b88fc11dc10ccffc1459686b17754bd39dc6a941756d7cab304253aec3b63bc36967c9edcf50cf4c29eadee3ae2afb0467383b6428dcb74f41c452173ab4a
|
data/README.md
CHANGED
|
@@ -41,6 +41,7 @@ Kapusta keeps most core Fennel forms. The main differences come from Ruby's runt
|
|
|
41
41
|
| `string.format`, `table.insert`, etc. | use Ruby methods and stdlib instead |
|
|
42
42
|
| `values` uses Lua multiple returns | `values` lowers to a Ruby array, usually destructured |
|
|
43
43
|
| `with-open`, `tail!` | not provided |
|
|
44
|
+
| macros | not provided for now |
|
|
44
45
|
|
|
45
46
|
Kapusta-specific additions:
|
|
46
47
|
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -euo pipefail
|
|
3
|
+
|
|
4
|
+
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
|
5
|
+
SOURCE_DIR="$ROOT_DIR/examples"
|
|
6
|
+
TARGET_DIR="$ROOT_DIR/examples-compiled"
|
|
7
|
+
|
|
8
|
+
mkdir -p "$TARGET_DIR"
|
|
9
|
+
|
|
10
|
+
shopt -s nullglob
|
|
11
|
+
kap_files=("$SOURCE_DIR"/*.kap)
|
|
12
|
+
|
|
13
|
+
if ((${#kap_files[@]} == 0)); then
|
|
14
|
+
printf 'No .kap files found in %s\n' "$SOURCE_DIR" >&2
|
|
15
|
+
exit 0
|
|
16
|
+
fi
|
|
17
|
+
|
|
18
|
+
for kap_file in "${kap_files[@]}"; do
|
|
19
|
+
name="$(basename "$kap_file" .kap)"
|
|
20
|
+
ruby_file="$TARGET_DIR/$name.rb"
|
|
21
|
+
|
|
22
|
+
"$ROOT_DIR/exe/kapusta" --compile "$kap_file" > "$ruby_file"
|
|
23
|
+
printf 'Compiled %s -> %s\n' "$kap_file" "$ruby_file"
|
|
24
|
+
done
|
|
@@ -13,9 +13,8 @@ module Kapusta
|
|
|
13
13
|
name_sym = args[0]
|
|
14
14
|
pattern = args[1]
|
|
15
15
|
body = args[2..]
|
|
16
|
-
ruby_name = temp(sanitize_local(name_sym.name))
|
|
17
16
|
fn_env = env.child
|
|
18
|
-
fn_env
|
|
17
|
+
ruby_name = define_local(fn_env, name_sym.name)
|
|
19
18
|
<<~RUBY.chomp
|
|
20
19
|
(-> do
|
|
21
20
|
#{ruby_name} = nil
|
|
@@ -27,22 +26,65 @@ module Kapusta
|
|
|
27
26
|
end
|
|
28
27
|
|
|
29
28
|
def emit_lambda(pattern, body, env, current_scope)
|
|
29
|
+
return emit_simple_lambda(pattern, body, env, current_scope) if simple_parameter_pattern?(pattern)
|
|
30
|
+
|
|
30
31
|
args_var = temp('args')
|
|
31
32
|
body_env = env.child
|
|
32
33
|
bindings_code, body_env = emit_pattern_bind(pattern, args_var, body_env)
|
|
33
34
|
body_code, = emit_sequence(body, body_env, current_scope, allow_method_definitions: false)
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
35
|
+
block_locals = pattern_names(pattern).map { |name| body_env.lookup(name) }.uniq
|
|
36
|
+
block_locals_clause = block_locals.empty? ? '' : "; #{block_locals.join(', ')}"
|
|
37
|
+
[
|
|
38
|
+
"->(*#{args_var}#{block_locals_clause}) do",
|
|
39
|
+
indent(join_code(bindings_code, body_code)),
|
|
40
|
+
'end'
|
|
41
|
+
].join("\n")
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def emit_simple_lambda(pattern, body, env, current_scope)
|
|
45
|
+
body_env = env.child
|
|
46
|
+
params = pattern.items.map { |sym| define_local(body_env, sym.name, shadow: true) }
|
|
47
|
+
body_code, = emit_sequence(body, body_env, current_scope, allow_method_definitions: false)
|
|
48
|
+
header = params.empty? ? 'proc do' : "proc do |#{params.join(', ')}|"
|
|
49
|
+
[
|
|
50
|
+
header,
|
|
51
|
+
indent(body_code),
|
|
52
|
+
'end'
|
|
53
|
+
].join("\n")
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def simple_parameter_pattern?(pattern)
|
|
57
|
+
pattern.is_a?(Vec) && pattern.items.all? { |item| item.is_a?(Sym) && !item.dotted? && item.name != '&' }
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def emit_definition_form(form, env, current_scope)
|
|
61
|
+
return [emit_method_definition(form, env), env] unless current_scope == :toplevel
|
|
62
|
+
|
|
63
|
+
emit_toplevel_method_definition(form, env)
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def emit_toplevel_method_definition(form, env)
|
|
67
|
+
name_sym = form.items[1]
|
|
68
|
+
pattern = form.items[2]
|
|
69
|
+
body = form.items[3..]
|
|
70
|
+
return [nil, env] if name_sym.dotted?
|
|
71
|
+
return [nil, env] unless simple_parameter_pattern?(pattern)
|
|
72
|
+
|
|
73
|
+
ruby_name = direct_method_definition_name(name_sym)
|
|
74
|
+
return [nil, env] unless ruby_name
|
|
75
|
+
return [nil, env] if captures_outer_binding?(body, env, pattern_names(pattern))
|
|
76
|
+
|
|
77
|
+
env.define(name_sym.name, Env::MethodBinding.new(ruby_name))
|
|
78
|
+
definition = emit_direct_method_definition(name_sym, pattern, body, env)
|
|
79
|
+
if needs_toplevel_method_bridge?(ruby_name)
|
|
80
|
+
definition = join_code(definition, emit_toplevel_method_bridge(ruby_name))
|
|
81
|
+
end
|
|
82
|
+
[definition, env]
|
|
40
83
|
end
|
|
41
84
|
|
|
42
85
|
def emit_named_fn_assignment(form, env, current_scope)
|
|
43
86
|
name_sym = form.items[1]
|
|
44
|
-
ruby_name =
|
|
45
|
-
env.define(name_sym.name, ruby_name)
|
|
87
|
+
ruby_name = define_local(env, name_sym.name)
|
|
46
88
|
fn_env = env.child
|
|
47
89
|
fn_env.define(name_sym.name, ruby_name)
|
|
48
90
|
lambda_code = emit_lambda(form.items[2], form.items[3..], fn_env, current_scope)
|
|
@@ -53,31 +95,131 @@ module Kapusta
|
|
|
53
95
|
name_sym = form.items[1]
|
|
54
96
|
pattern = form.items[2]
|
|
55
97
|
body = form.items[3..]
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
98
|
+
direct_definition = emit_direct_method_definition(name_sym, pattern, body, env)
|
|
99
|
+
return direct_definition if direct_definition
|
|
100
|
+
|
|
101
|
+
block_header, body_code = emit_method_body(pattern, body, env)
|
|
60
102
|
|
|
61
103
|
if name_sym.name.start_with?('self.')
|
|
62
104
|
ruby_name = Kapusta.kebab_to_snake(name_sym.name.delete_prefix('self.')).to_sym.inspect
|
|
63
|
-
|
|
64
|
-
define_singleton_method(#{ruby_name})
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
RUBY
|
|
105
|
+
[
|
|
106
|
+
"define_singleton_method(#{ruby_name}) #{block_header}",
|
|
107
|
+
indent(body_code),
|
|
108
|
+
'end'
|
|
109
|
+
].join("\n")
|
|
69
110
|
else
|
|
70
111
|
ruby_name = Kapusta.kebab_to_snake(name_sym.name).to_sym.inspect
|
|
71
|
-
|
|
72
|
-
define_method(#{ruby_name})
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
112
|
+
[
|
|
113
|
+
"define_method(#{ruby_name}) #{block_header}",
|
|
114
|
+
indent(body_code),
|
|
115
|
+
'end'
|
|
116
|
+
].join("\n")
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
def emit_direct_method_definition(name_sym, pattern, body, env)
|
|
121
|
+
return unless simple_parameter_pattern?(pattern)
|
|
122
|
+
|
|
123
|
+
ruby_name = direct_method_definition_name(name_sym)
|
|
124
|
+
return unless ruby_name
|
|
125
|
+
return if captures_outer_binding?(body, env, pattern_names(pattern))
|
|
126
|
+
|
|
127
|
+
body_env = env.child
|
|
128
|
+
params = pattern.items.map { |sym| define_local(body_env, sym.name, shadow: true) }
|
|
129
|
+
body_code, = emit_sequence(body, body_env, :toplevel, allow_method_definitions: false)
|
|
130
|
+
header = params.empty? ? "def #{ruby_name}" : "def #{ruby_name}(#{params.join(', ')})"
|
|
131
|
+
[
|
|
132
|
+
header,
|
|
133
|
+
indent(body_code),
|
|
134
|
+
'end'
|
|
135
|
+
].join("\n")
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
def direct_method_definition_name(name_sym)
|
|
139
|
+
source_name = name_sym.name
|
|
140
|
+
if source_name.start_with?('self.')
|
|
141
|
+
method_name = Kapusta.kebab_to_snake(source_name.delete_prefix('self.'))
|
|
142
|
+
return unless direct_method_name?(method_name)
|
|
143
|
+
|
|
144
|
+
"self.#{method_name}"
|
|
145
|
+
else
|
|
146
|
+
method_name = Kapusta.kebab_to_snake(source_name)
|
|
147
|
+
return unless direct_method_name?(method_name)
|
|
148
|
+
|
|
149
|
+
method_name
|
|
77
150
|
end
|
|
78
151
|
end
|
|
79
152
|
|
|
153
|
+
def needs_toplevel_method_bridge?(ruby_name)
|
|
154
|
+
%w[context describe example it specify].include?(ruby_name)
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
def emit_toplevel_method_bridge(ruby_name)
|
|
158
|
+
method_name = ruby_name.to_sym.inspect
|
|
159
|
+
"define_singleton_method(#{method_name}, Object.instance_method(#{method_name}).bind(self))"
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
def emit_method_body(pattern, body, env)
|
|
163
|
+
return emit_simple_method_body(pattern, body, env) if simple_parameter_pattern?(pattern)
|
|
164
|
+
|
|
165
|
+
args_var = temp('args')
|
|
166
|
+
method_env = env.child
|
|
167
|
+
bindings_code, body_env = emit_pattern_bind(pattern, args_var, method_env)
|
|
168
|
+
body_code, = emit_sequence(body, body_env, :toplevel, allow_method_definitions: false)
|
|
169
|
+
block_locals = pattern_names(pattern).map { |name| body_env.lookup(name) }.uniq
|
|
170
|
+
block_locals_clause = block_locals.empty? ? '' : "; #{block_locals.join(', ')}"
|
|
171
|
+
["do |*#{args_var}#{block_locals_clause}|", join_code(bindings_code, body_code)]
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
def emit_simple_method_body(pattern, body, env)
|
|
175
|
+
body_env = env.child
|
|
176
|
+
params = pattern.items.map { |sym| define_local(body_env, sym.name, shadow: true) }
|
|
177
|
+
body_code, = emit_sequence(body, body_env, :toplevel, allow_method_definitions: false)
|
|
178
|
+
[params.empty? ? 'do' : "do |#{params.join(', ')}|", body_code]
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
def captures_outer_binding?(forms, env, local_names)
|
|
182
|
+
forms.any? { |form| form_captures_outer_binding?(form, env, local_names) }
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
def form_captures_outer_binding?(form, env, local_names)
|
|
186
|
+
case form
|
|
187
|
+
when Sym
|
|
188
|
+
sym_captures_outer_binding?(form, env, local_names)
|
|
189
|
+
when Vec, List
|
|
190
|
+
form.items.any? { |item| form_captures_outer_binding?(item, env, local_names) }
|
|
191
|
+
when HashLit
|
|
192
|
+
form.pairs.any? do |key, value|
|
|
193
|
+
form_captures_outer_binding?(key, env, local_names) ||
|
|
194
|
+
form_captures_outer_binding?(value, env, local_names)
|
|
195
|
+
end
|
|
196
|
+
else
|
|
197
|
+
false
|
|
198
|
+
end
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
def sym_captures_outer_binding?(sym, env, local_names)
|
|
202
|
+
name = sym.dotted? ? sym.segments.first : sym.name
|
|
203
|
+
return false if local_names.include?(name) || !env.defined?(name)
|
|
204
|
+
|
|
205
|
+
!method_binding?(env.lookup(name))
|
|
206
|
+
end
|
|
207
|
+
|
|
80
208
|
def emit_let(args, env, current_scope)
|
|
209
|
+
binding_code, body_code = emit_let_parts(args, env, current_scope, result: true)
|
|
210
|
+
<<~RUBY.chomp
|
|
211
|
+
(-> do
|
|
212
|
+
#{indent(join_code(binding_code, body_code))}
|
|
213
|
+
end).call
|
|
214
|
+
RUBY
|
|
215
|
+
end
|
|
216
|
+
|
|
217
|
+
def emit_let_statement(args, env, current_scope)
|
|
218
|
+
binding_code, body_code = emit_let_parts(args, env, current_scope, result: false)
|
|
219
|
+
join_code(binding_code, body_code)
|
|
220
|
+
end
|
|
221
|
+
|
|
222
|
+
def emit_let_parts(args, env, current_scope, result:)
|
|
81
223
|
bindings = args[0]
|
|
82
224
|
body = args[1..]
|
|
83
225
|
child_env = env.child
|
|
@@ -91,13 +233,14 @@ module Kapusta
|
|
|
91
233
|
binding_codes << bind_code
|
|
92
234
|
i += 2
|
|
93
235
|
end
|
|
94
|
-
body_code, = emit_sequence(body, child_env, current_scope,
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
236
|
+
body_code, = emit_sequence(body, child_env, current_scope,
|
|
237
|
+
allow_method_definitions: false,
|
|
238
|
+
result:)
|
|
239
|
+
[binding_codes.join("\n"), body_code]
|
|
240
|
+
end
|
|
241
|
+
|
|
242
|
+
def join_code(*chunks)
|
|
243
|
+
chunks.reject(&:empty?).join("\n")
|
|
101
244
|
end
|
|
102
245
|
|
|
103
246
|
def emit_local_form(form, env, current_scope)
|
|
@@ -105,8 +248,7 @@ module Kapusta
|
|
|
105
248
|
value_code = emit_expr(form.items[2], env, current_scope)
|
|
106
249
|
|
|
107
250
|
if target.is_a?(Sym)
|
|
108
|
-
ruby_name =
|
|
109
|
-
env.define(target.name, ruby_name)
|
|
251
|
+
ruby_name = define_local(env, target.name)
|
|
110
252
|
["#{ruby_name} = #{value_code}\nnil", env]
|
|
111
253
|
else
|
|
112
254
|
bind_code, env = emit_pattern_bind(target, value_code, env)
|
|
@@ -126,11 +268,12 @@ module Kapusta
|
|
|
126
268
|
if target.is_a?(Sym) && !target.dotted?
|
|
127
269
|
ruby_name =
|
|
128
270
|
if env.defined?(target.name)
|
|
129
|
-
env.lookup(target.name)
|
|
271
|
+
binding = env.lookup(target.name)
|
|
272
|
+
raise Error, "cannot set method binding: #{target.name}" if method_binding?(binding)
|
|
273
|
+
|
|
274
|
+
binding
|
|
130
275
|
else
|
|
131
|
-
|
|
132
|
-
env.define(target.name, fresh)
|
|
133
|
-
fresh
|
|
276
|
+
define_local(env, target.name)
|
|
134
277
|
end
|
|
135
278
|
["#{ruby_name} = #{value_code}", env]
|
|
136
279
|
else
|
|
@@ -151,7 +294,10 @@ module Kapusta
|
|
|
151
294
|
base_code, segments = multisym_base(target.segments, env)
|
|
152
295
|
runtime_call(:set_method_path, base_code, segments.inspect, value_code)
|
|
153
296
|
else
|
|
154
|
-
|
|
297
|
+
binding = env.lookup(target.name)
|
|
298
|
+
raise Error, "cannot set method binding: #{target.name}" if method_binding?(binding)
|
|
299
|
+
|
|
300
|
+
"#{binding} = #{value_code}"
|
|
155
301
|
end
|
|
156
302
|
when List
|
|
157
303
|
head = target.head
|
|
@@ -164,7 +310,12 @@ module Kapusta
|
|
|
164
310
|
elsif head.is_a?(Sym) && head.name == 'cvar'
|
|
165
311
|
runtime_call(:set_cvar, 'self', target.items[1].name.inspect, value_code)
|
|
166
312
|
elsif head.is_a?(Sym) && head.name == 'gvar'
|
|
167
|
-
|
|
313
|
+
ruby_name = global_name(target.items[1].name)
|
|
314
|
+
if direct_global_name?(ruby_name)
|
|
315
|
+
"$#{ruby_name} = #{value_code}"
|
|
316
|
+
else
|
|
317
|
+
runtime_call(:set_gvar, target.items[1].name.inspect, value_code)
|
|
318
|
+
end
|
|
168
319
|
else
|
|
169
320
|
raise Error, "bad set target: #{target.inspect}"
|
|
170
321
|
end
|
|
@@ -10,75 +10,39 @@ module Kapusta
|
|
|
10
10
|
result_var = temp('result')
|
|
11
11
|
iter_code = emit_iteration(args[0], env, current_scope) do |iter_env|
|
|
12
12
|
body = emit_sequence(args[1..], iter_env, current_scope, allow_method_definitions: false).first
|
|
13
|
-
|
|
14
|
-
__kap_value = begin
|
|
15
|
-
#{indent(body)}
|
|
16
|
-
end
|
|
17
|
-
#{result_var} << __kap_value unless __kap_value.nil?
|
|
18
|
-
RUBY
|
|
13
|
+
emit_array_collection_step(result_var, body)
|
|
19
14
|
end
|
|
20
|
-
|
|
21
|
-
(-> do
|
|
22
|
-
#{result_var} = []
|
|
23
|
-
#{iter_code}
|
|
24
|
-
#{result_var}
|
|
25
|
-
end).call
|
|
26
|
-
RUBY
|
|
15
|
+
emit_collection_result(result_var, '[]', iter_code)
|
|
27
16
|
end
|
|
28
17
|
|
|
29
18
|
def emit_collect(args, env, current_scope)
|
|
30
19
|
result_var = temp('result')
|
|
31
20
|
iter_code = emit_iteration(args[0], env, current_scope) do |iter_env|
|
|
32
21
|
body = emit_sequence(args[1..], iter_env, current_scope, allow_method_definitions: false).first
|
|
33
|
-
|
|
34
|
-
__kap_pair = begin
|
|
35
|
-
#{indent(body)}
|
|
36
|
-
end
|
|
37
|
-
if __kap_pair.is_a?(Array) && __kap_pair.length == 2 && !__kap_pair[0].nil? && !__kap_pair[1].nil?
|
|
38
|
-
#{result_var}[__kap_pair[0]] = __kap_pair[1]
|
|
39
|
-
end
|
|
40
|
-
RUBY
|
|
22
|
+
emit_hash_collection_step(result_var, body)
|
|
41
23
|
end
|
|
42
|
-
|
|
43
|
-
(-> do
|
|
44
|
-
#{result_var} = {}
|
|
45
|
-
#{iter_code}
|
|
46
|
-
#{result_var}
|
|
47
|
-
end).call
|
|
48
|
-
RUBY
|
|
24
|
+
emit_collection_result(result_var, '{}', iter_code)
|
|
49
25
|
end
|
|
50
26
|
|
|
51
27
|
def emit_fcollect(args, env, current_scope)
|
|
52
28
|
result_var = temp('result')
|
|
53
29
|
parsed = parse_counted_for_bindings(args[0].items, env, current_scope)
|
|
54
30
|
body_code, = emit_sequence(args[1..], parsed[:loop_env], current_scope, allow_method_definitions: false)
|
|
55
|
-
collecting_body =
|
|
56
|
-
__kap_value = begin
|
|
57
|
-
#{indent(body_code)}
|
|
58
|
-
end
|
|
59
|
-
#{result_var} << __kap_value unless __kap_value.nil?
|
|
60
|
-
RUBY
|
|
31
|
+
collecting_body = emit_array_collection_step(result_var, body_code)
|
|
61
32
|
loop_code = emit_counted_loop(**parsed, current_scope:, body_code: collecting_body)
|
|
62
|
-
|
|
63
|
-
(-> do
|
|
64
|
-
#{result_var} = []
|
|
65
|
-
#{indent(loop_code)}
|
|
66
|
-
#{result_var}
|
|
67
|
-
end).call
|
|
68
|
-
RUBY
|
|
33
|
+
emit_collection_result(result_var, '[]', loop_code)
|
|
69
34
|
end
|
|
70
35
|
|
|
71
36
|
def emit_accumulate(args, env, current_scope)
|
|
72
37
|
bindings = args[0].items
|
|
73
38
|
acc_name = bindings[0]
|
|
74
|
-
acc_var = temp(sanitize_local(acc_name.name))
|
|
75
39
|
iter_bindings = Vec.new(bindings[2..])
|
|
76
40
|
loop_env = env.child
|
|
77
|
-
loop_env
|
|
41
|
+
acc_var = define_local(loop_env, acc_name.name)
|
|
78
42
|
iter_code = emit_iteration(iter_bindings, loop_env, current_scope) do |iter_env|
|
|
79
43
|
iter_env.define(acc_name.name, acc_var)
|
|
80
44
|
emit_sequence(args[1..], iter_env, current_scope, allow_method_definitions: false).first.then do |body|
|
|
81
|
-
|
|
45
|
+
emit_sequence_value_assignment(acc_var, body)
|
|
82
46
|
end
|
|
83
47
|
end
|
|
84
48
|
<<~RUBY.chomp
|
|
@@ -93,14 +57,12 @@ module Kapusta
|
|
|
93
57
|
def emit_faccumulate(args, env, current_scope)
|
|
94
58
|
bindings = args[0].items
|
|
95
59
|
acc_name = bindings[0]
|
|
96
|
-
acc_var = temp(sanitize_local(acc_name.name))
|
|
97
60
|
loop_name = bindings[2]
|
|
98
|
-
loop_var = temp(sanitize_local(loop_name.name))
|
|
99
61
|
loop_env = env.child
|
|
100
|
-
loop_env
|
|
101
|
-
loop_env
|
|
62
|
+
acc_var = define_local(loop_env, acc_name.name)
|
|
63
|
+
loop_var = define_local(loop_env, loop_name.name)
|
|
102
64
|
body_code, = emit_sequence(args[1..], loop_env, current_scope, allow_method_definitions: false)
|
|
103
|
-
accumulating_body =
|
|
65
|
+
accumulating_body = emit_sequence_value_assignment(acc_var, body_code)
|
|
104
66
|
loop_code = emit_counted_loop(
|
|
105
67
|
ruby_name: loop_var,
|
|
106
68
|
start_code: emit_expr(bindings[3], env, current_scope),
|
|
@@ -210,6 +172,42 @@ module Kapusta
|
|
|
210
172
|
end.compact
|
|
211
173
|
[codes.join("\n"), current_env]
|
|
212
174
|
end
|
|
175
|
+
|
|
176
|
+
def emit_collection_result(result_var, initial_code, iter_code)
|
|
177
|
+
<<~RUBY.chomp
|
|
178
|
+
(-> do
|
|
179
|
+
#{result_var} = #{initial_code}
|
|
180
|
+
#{indent(iter_code)}
|
|
181
|
+
#{result_var}
|
|
182
|
+
end).call
|
|
183
|
+
RUBY
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
def emit_array_collection_step(result_var, body_code)
|
|
187
|
+
value_var = temp('value')
|
|
188
|
+
<<~RUBY.chomp
|
|
189
|
+
#{emit_sequence_value_assignment(value_var, body_code)}
|
|
190
|
+
#{result_var} << #{value_var} unless #{value_var}.nil?
|
|
191
|
+
RUBY
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
def emit_hash_collection_step(result_var, body_code)
|
|
195
|
+
pair_var = temp('pair')
|
|
196
|
+
<<~RUBY.chomp
|
|
197
|
+
#{emit_sequence_value_assignment(pair_var, body_code)}
|
|
198
|
+
if #{pair_var}.is_a?(Array) && #{pair_var}.length == 2 && !#{pair_var}[0].nil? && !#{pair_var}[1].nil?
|
|
199
|
+
#{result_var}[#{pair_var}[0]] = #{pair_var}[1]
|
|
200
|
+
end
|
|
201
|
+
RUBY
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
def emit_sequence_value_assignment(target_var, body_code)
|
|
205
|
+
<<~RUBY.chomp
|
|
206
|
+
#{target_var} = begin
|
|
207
|
+
#{indent(body_code)}
|
|
208
|
+
end
|
|
209
|
+
RUBY
|
|
210
|
+
end
|
|
213
211
|
end
|
|
214
212
|
end
|
|
215
213
|
end
|
|
@@ -16,14 +16,35 @@ module Kapusta
|
|
|
16
16
|
|
|
17
17
|
cond = emit_expr(args[0], env, current_scope)
|
|
18
18
|
truthy = emit_expr(args[1], env, current_scope)
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
19
|
+
lines = ["if #{cond}", indent(truthy)]
|
|
20
|
+
append_else_lines(lines, args[2..], env, current_scope)
|
|
21
|
+
lines << 'end'
|
|
22
|
+
lines.join("\n")
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def append_else_lines(lines, args, env, current_scope)
|
|
26
|
+
return if args.empty?
|
|
27
|
+
|
|
28
|
+
if args.length == 1 && if_form?(args[0])
|
|
29
|
+
append_elsif_lines(lines, args[0].rest, env, current_scope)
|
|
30
|
+
elsif args.length >= 2
|
|
31
|
+
append_elsif_lines(lines, args, env, current_scope)
|
|
32
|
+
else
|
|
33
|
+
lines << 'else'
|
|
34
|
+
lines << indent(emit_expr(args[0], env, current_scope))
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def append_elsif_lines(lines, args, env, current_scope)
|
|
39
|
+
return append_else_lines(lines, args, env, current_scope) if args.length < 2
|
|
40
|
+
|
|
41
|
+
lines << "elsif #{emit_expr(args[0], env, current_scope)}"
|
|
42
|
+
lines << indent(emit_expr(args[1], env, current_scope))
|
|
43
|
+
append_else_lines(lines, args[2..], env, current_scope)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def if_form?(form)
|
|
47
|
+
form.is_a?(List) && form.head.is_a?(Sym) && form.head.name == 'if'
|
|
27
48
|
end
|
|
28
49
|
|
|
29
50
|
def emit_case(args, env, current_scope, mode)
|
|
@@ -106,21 +127,16 @@ module Kapusta
|
|
|
106
127
|
end
|
|
107
128
|
|
|
108
129
|
def emit_while(args, env, current_scope)
|
|
109
|
-
body_code, = emit_sequence(args[1..], env, current_scope, allow_method_definitions: false)
|
|
110
130
|
<<~RUBY.chomp
|
|
111
131
|
(-> do
|
|
112
|
-
|
|
113
|
-
#{indent(body_code)}
|
|
114
|
-
end
|
|
132
|
+
#{indent(emit_while_statement(args, env, current_scope))}
|
|
115
133
|
nil
|
|
116
134
|
end).call
|
|
117
135
|
RUBY
|
|
118
136
|
end
|
|
119
137
|
|
|
120
138
|
def emit_for(args, env, current_scope)
|
|
121
|
-
|
|
122
|
-
body_code, = emit_sequence(args[1..], parsed[:loop_env], current_scope, allow_method_definitions: false)
|
|
123
|
-
loop_code = emit_counted_loop(**parsed, current_scope:, body_code:)
|
|
139
|
+
loop_code = emit_for_statement(args, env, current_scope)
|
|
124
140
|
<<~RUBY.chomp
|
|
125
141
|
(-> do
|
|
126
142
|
#{indent(loop_code)}
|
|
@@ -130,9 +146,7 @@ module Kapusta
|
|
|
130
146
|
end
|
|
131
147
|
|
|
132
148
|
def emit_each(args, env, current_scope)
|
|
133
|
-
iter_code =
|
|
134
|
-
emit_sequence(args[1..], iter_env, current_scope, allow_method_definitions: false).first
|
|
135
|
-
end
|
|
149
|
+
iter_code = emit_each_statement(args, env, current_scope)
|
|
136
150
|
<<~RUBY.chomp
|
|
137
151
|
(-> do
|
|
138
152
|
#{iter_code}
|
|
@@ -140,6 +154,33 @@ module Kapusta
|
|
|
140
154
|
end).call
|
|
141
155
|
RUBY
|
|
142
156
|
end
|
|
157
|
+
|
|
158
|
+
def emit_while_statement(args, env, current_scope)
|
|
159
|
+
body_code, = emit_sequence(args[1..], env, current_scope,
|
|
160
|
+
allow_method_definitions: false,
|
|
161
|
+
result: false)
|
|
162
|
+
[
|
|
163
|
+
"while #{emit_expr(args[0], env, current_scope)}",
|
|
164
|
+
indent(body_code),
|
|
165
|
+
'end'
|
|
166
|
+
].join("\n")
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
def emit_for_statement(args, env, current_scope)
|
|
170
|
+
parsed = parse_counted_for_bindings(args[0].items, env, current_scope)
|
|
171
|
+
body_code, = emit_sequence(args[1..], parsed[:loop_env], current_scope,
|
|
172
|
+
allow_method_definitions: false,
|
|
173
|
+
result: false)
|
|
174
|
+
emit_counted_loop(**parsed, current_scope:, body_code:)
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
def emit_each_statement(args, env, current_scope)
|
|
178
|
+
emit_iteration(args[0], env, current_scope) do |iter_env|
|
|
179
|
+
emit_sequence(args[1..], iter_env, current_scope,
|
|
180
|
+
allow_method_definitions: false,
|
|
181
|
+
result: false).first
|
|
182
|
+
end
|
|
183
|
+
end
|
|
143
184
|
end
|
|
144
185
|
end
|
|
145
186
|
end
|