kapusta 0.4.1 → 0.5.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/bin/fennel-parity +36 -6
- data/examples/even-squares.kap +22 -7
- data/examples/roman-to-integer.kap +3 -3
- data/lib/kapusta/ast.rb +38 -4
- data/lib/kapusta/cli.rb +3 -0
- data/lib/kapusta/compiler/emitter/bindings.rb +52 -6
- data/lib/kapusta/compiler/emitter/collections.rb +64 -20
- data/lib/kapusta/compiler/emitter/control_flow.rb +7 -4
- data/lib/kapusta/compiler/emitter/expressions.rb +26 -15
- data/lib/kapusta/compiler/emitter/interop.rb +6 -4
- data/lib/kapusta/compiler/emitter/patterns.rb +22 -1
- data/lib/kapusta/compiler/emitter/support.rb +44 -22
- data/lib/kapusta/compiler/macro_expander.rb +55 -25
- data/lib/kapusta/compiler/normalizer.rb +35 -9
- data/lib/kapusta/compiler.rb +6 -2
- data/lib/kapusta/error.rb +25 -1
- data/lib/kapusta/errors.rb +69 -0
- data/lib/kapusta/formatter.rb +12 -5
- data/lib/kapusta/reader.rb +34 -12
- data/lib/kapusta/version.rb +1 -1
- data/lib/kapusta.rb +1 -0
- data/spec/examples_errors_spec.rb +229 -0
- data/spec/formatter_spec.rb +7 -6
- metadata +3 -2
- data/spec/reader_spec.rb +0 -26
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: f58b2e1f81723470a819ba38e3e6e811a3aa3a97ba79443e6ad0c59457d0fa08
|
|
4
|
+
data.tar.gz: 9207c1491218f71eeeedf2495a22805b9e2d1a8336200d212437fb813005c1d8
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 9e0e2220a388a1a06ed86733e01fbc1eacf3a48536c503e7e8ccbcf85871fcda35cce16ddc9b434f3d1ac3c8dc23cb15af3d78528fc269fae62d25ef49a6fba2
|
|
7
|
+
data.tar.gz: 3a0f8628514de972b1def03cfd8f47798b630632fe728a41d46a0c9aade7587c011004695903697b929fd82c3302bf90c7efdc7d5e4edb7f0ab7bd3c9c1c7387
|
data/bin/fennel-parity
CHANGED
|
@@ -5,6 +5,7 @@ require 'open3'
|
|
|
5
5
|
|
|
6
6
|
ROOT = File.expand_path('..', __dir__)
|
|
7
7
|
EXAMPLES = File.join(ROOT, 'examples')
|
|
8
|
+
EXAMPLES_ERRORS = File.join(ROOT, 'examples-errors')
|
|
8
9
|
KAPUSTA = File.join(ROOT, 'exe', 'kapusta')
|
|
9
10
|
KAPFMT = File.join(ROOT, 'exe', 'kapfmt')
|
|
10
11
|
|
|
@@ -14,6 +15,7 @@ COMPATIBLE = %w[
|
|
|
14
15
|
climbing-stairs.kap
|
|
15
16
|
describe.kap
|
|
16
17
|
destructure.kap
|
|
18
|
+
even-squares.kap
|
|
17
19
|
factorial.kap
|
|
18
20
|
fib.kap
|
|
19
21
|
fizzbuzz.kap
|
|
@@ -40,8 +42,8 @@ COMPATIBLE = %w[
|
|
|
40
42
|
underscore-patterns.kap
|
|
41
43
|
].freeze
|
|
42
44
|
|
|
43
|
-
def run(cmd, file)
|
|
44
|
-
out, err, status = Open3.capture3(cmd, file, chdir:
|
|
45
|
+
def run(cmd, file, chdir: EXAMPLES)
|
|
46
|
+
out, err, status = Open3.capture3(cmd, file, chdir:)
|
|
45
47
|
[out, err, status]
|
|
46
48
|
rescue StandardError => e
|
|
47
49
|
['', e.message, nil]
|
|
@@ -125,6 +127,17 @@ def check(name)
|
|
|
125
127
|
[:ok, nil]
|
|
126
128
|
end
|
|
127
129
|
|
|
130
|
+
def check_error(name)
|
|
131
|
+
path = File.join(EXAMPLES_ERRORS, name)
|
|
132
|
+
_, _, k_status = run(KAPUSTA, path, chdir: EXAMPLES_ERRORS)
|
|
133
|
+
_, _, f_status = run('fennel', path, chdir: EXAMPLES_ERRORS)
|
|
134
|
+
|
|
135
|
+
return [:fail, 'fennel unexpectedly succeeded'] if f_status&.success?
|
|
136
|
+
return [:fail, "kapusta unexpectedly succeeded (exit #{k_status&.exitstatus})"] if k_status&.success?
|
|
137
|
+
|
|
138
|
+
[:ok, nil]
|
|
139
|
+
end
|
|
140
|
+
|
|
128
141
|
puts "Checking #{COMPATIBLE.size} compatible examples (kapfmt vs fnlfmt, kapusta vs fennel)...\n\n"
|
|
129
142
|
|
|
130
143
|
failures = []
|
|
@@ -142,13 +155,30 @@ COMPATIBLE.each do |name|
|
|
|
142
155
|
end
|
|
143
156
|
end
|
|
144
157
|
|
|
158
|
+
error_files = Dir.children(EXAMPLES_ERRORS).select { |n| n.end_with?('.kap') }.sort
|
|
159
|
+
puts "\nChecking #{error_files.size} error examples (both kapusta and fennel must fail)...\n\n"
|
|
160
|
+
|
|
161
|
+
error_failures = []
|
|
162
|
+
error_files.each do |name|
|
|
163
|
+
status, reason = check_error(name)
|
|
164
|
+
case status
|
|
165
|
+
when :ok
|
|
166
|
+
puts "OK #{name}"
|
|
167
|
+
when :fail
|
|
168
|
+
error_failures << [name, reason]
|
|
169
|
+
puts "FAIL #{name}"
|
|
170
|
+
end
|
|
171
|
+
end
|
|
172
|
+
|
|
145
173
|
puts
|
|
146
|
-
|
|
147
|
-
|
|
174
|
+
total_failures = failures + error_failures
|
|
175
|
+
total = COMPATIBLE.size + error_files.size
|
|
176
|
+
if total_failures.empty?
|
|
177
|
+
puts "All #{total} examples (#{COMPATIBLE.size} compatible + #{error_files.size} error) match expectations."
|
|
148
178
|
exit 0
|
|
149
179
|
else
|
|
150
|
-
puts "#{
|
|
151
|
-
|
|
180
|
+
puts "#{total_failures.size} of #{total} examples failed:\n\n"
|
|
181
|
+
total_failures.each do |name, reason|
|
|
152
182
|
puts " * #{name}"
|
|
153
183
|
reason.each_line { |l| puts " #{l.rstrip}" }
|
|
154
184
|
puts
|
data/examples/even-squares.kap
CHANGED
|
@@ -1,7 +1,22 @@
|
|
|
1
|
-
(
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
1
|
+
(fn even? [n] (= 0 (% n 2)))
|
|
2
|
+
|
|
3
|
+
(fn select [tbl pred]
|
|
4
|
+
(icollect [_ x (ipairs tbl)]
|
|
5
|
+
(when (pred x) x)))
|
|
6
|
+
|
|
7
|
+
(fn map [tbl f]
|
|
8
|
+
(icollect [_ x (ipairs tbl)]
|
|
9
|
+
(f x)))
|
|
10
|
+
|
|
11
|
+
(fn join [tbl sep]
|
|
12
|
+
(var s "")
|
|
13
|
+
(each [_ x (ipairs tbl)]
|
|
14
|
+
(if (= s "")
|
|
15
|
+
(set s (.. x))
|
|
16
|
+
(set s (.. s sep x))))
|
|
17
|
+
s)
|
|
18
|
+
|
|
19
|
+
(let [xs [1 2 3 4 5 6]
|
|
20
|
+
filtered (select xs even?)
|
|
21
|
+
squared (map filtered #(* $ $))]
|
|
22
|
+
(print (join squared ", ")))
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
(fn roman-to-integer [s]
|
|
2
|
-
(let [
|
|
2
|
+
(let [value-map {"I" 1 "V" 5 "X" 10 "L" 50 "C" 100 "D" 500 "M" 1000}
|
|
3
3
|
chars (s.chars)
|
|
4
4
|
n (length chars)]
|
|
5
5
|
(var total 0)
|
|
6
6
|
(var i 0)
|
|
7
7
|
(while (< i n)
|
|
8
|
-
(let [curr (.
|
|
9
|
-
ahead (if (< (+ i 1) n) (.
|
|
8
|
+
(let [curr (. value-map (. chars i))
|
|
9
|
+
ahead (if (< (+ i 1) n) (. value-map (. chars (+ i 1))) 0)
|
|
10
10
|
subtract? (< curr ahead)]
|
|
11
11
|
(set total (+ total (if subtract? (- ahead curr) curr)))
|
|
12
12
|
(set i (+ i (if subtract? 2 1)))))
|
data/lib/kapusta/ast.rb
CHANGED
|
@@ -13,6 +13,7 @@ module Kapusta
|
|
|
13
13
|
|
|
14
14
|
class Sym
|
|
15
15
|
attr_reader :name
|
|
16
|
+
attr_accessor :line, :column
|
|
16
17
|
|
|
17
18
|
def initialize(name)
|
|
18
19
|
@name = name.to_s
|
|
@@ -23,7 +24,7 @@ module Kapusta
|
|
|
23
24
|
end
|
|
24
25
|
|
|
25
26
|
def inspect
|
|
26
|
-
|
|
27
|
+
@name.to_s
|
|
27
28
|
end
|
|
28
29
|
|
|
29
30
|
def ==(other)
|
|
@@ -76,7 +77,7 @@ module Kapusta
|
|
|
76
77
|
|
|
77
78
|
class Vec
|
|
78
79
|
attr_reader :items
|
|
79
|
-
attr_accessor :multiline_source
|
|
80
|
+
attr_accessor :multiline_source, :line, :column
|
|
80
81
|
|
|
81
82
|
def initialize(items)
|
|
82
83
|
@items = items
|
|
@@ -86,17 +87,25 @@ module Kapusta
|
|
|
86
87
|
def to_ary
|
|
87
88
|
@items
|
|
88
89
|
end
|
|
90
|
+
|
|
91
|
+
def inspect
|
|
92
|
+
"[#{@items.map(&:inspect).join(' ')}]"
|
|
93
|
+
end
|
|
89
94
|
end
|
|
90
95
|
|
|
91
96
|
class HashLit
|
|
92
97
|
attr_reader :entries
|
|
93
|
-
attr_accessor :multiline_source
|
|
98
|
+
attr_accessor :multiline_source, :line, :column
|
|
94
99
|
|
|
95
100
|
def initialize(entries)
|
|
96
101
|
@entries = entries
|
|
97
102
|
@multiline_source = false
|
|
98
103
|
end
|
|
99
104
|
|
|
105
|
+
def inspect
|
|
106
|
+
pairs.map { |k, v| "#{k.inspect} #{v.inspect}" }.then { |p| "{#{p.join(' ')}}" }
|
|
107
|
+
end
|
|
108
|
+
|
|
100
109
|
def pairs
|
|
101
110
|
@entries.grep(Array)
|
|
102
111
|
end
|
|
@@ -108,7 +117,7 @@ module Kapusta
|
|
|
108
117
|
|
|
109
118
|
class List
|
|
110
119
|
attr_reader :items
|
|
111
|
-
attr_accessor :multiline_source
|
|
120
|
+
attr_accessor :multiline_source, :line, :column
|
|
112
121
|
|
|
113
122
|
def initialize(items)
|
|
114
123
|
@items = items
|
|
@@ -126,6 +135,10 @@ module Kapusta
|
|
|
126
135
|
def empty?
|
|
127
136
|
@items.empty?
|
|
128
137
|
end
|
|
138
|
+
|
|
139
|
+
def inspect
|
|
140
|
+
"(#{@items.map(&:inspect).join(' ')})"
|
|
141
|
+
end
|
|
129
142
|
end
|
|
130
143
|
|
|
131
144
|
class AutoGensym < Sym
|
|
@@ -134,27 +147,48 @@ module Kapusta
|
|
|
134
147
|
end
|
|
135
148
|
end
|
|
136
149
|
|
|
150
|
+
class MacroSym < Sym
|
|
151
|
+
def inspect
|
|
152
|
+
"#<MacroSym #{@name}>"
|
|
153
|
+
end
|
|
154
|
+
end
|
|
155
|
+
|
|
137
156
|
class Quasiquote
|
|
138
157
|
attr_reader :form
|
|
158
|
+
attr_accessor :line, :column
|
|
139
159
|
|
|
140
160
|
def initialize(form)
|
|
141
161
|
@form = form
|
|
142
162
|
end
|
|
163
|
+
|
|
164
|
+
def inspect
|
|
165
|
+
"`#{@form.inspect}"
|
|
166
|
+
end
|
|
143
167
|
end
|
|
144
168
|
|
|
145
169
|
class Unquote
|
|
146
170
|
attr_reader :form
|
|
171
|
+
attr_accessor :line, :column
|
|
147
172
|
|
|
148
173
|
def initialize(form)
|
|
149
174
|
@form = form
|
|
150
175
|
end
|
|
176
|
+
|
|
177
|
+
def inspect
|
|
178
|
+
",#{@form.inspect}"
|
|
179
|
+
end
|
|
151
180
|
end
|
|
152
181
|
|
|
153
182
|
class UnquoteSplice
|
|
154
183
|
attr_reader :form
|
|
184
|
+
attr_accessor :line, :column
|
|
155
185
|
|
|
156
186
|
def initialize(form)
|
|
157
187
|
@form = form
|
|
158
188
|
end
|
|
189
|
+
|
|
190
|
+
def inspect
|
|
191
|
+
",@#{@form.inspect}"
|
|
192
|
+
end
|
|
159
193
|
end
|
|
160
194
|
end
|
data/lib/kapusta/cli.rb
CHANGED
|
@@ -13,6 +13,8 @@ module Kapusta
|
|
|
13
13
|
name_sym = args[0]
|
|
14
14
|
pattern = args[1]
|
|
15
15
|
body = args[2..]
|
|
16
|
+
emit_error!(:fn_no_params) unless name_sym.is_a?(Sym) && pattern.is_a?(Vec)
|
|
17
|
+
|
|
16
18
|
fn_env = env.child
|
|
17
19
|
ruby_name = define_local(fn_env, name_sym.name)
|
|
18
20
|
<<~RUBY.chomp
|
|
@@ -59,7 +61,9 @@ module Kapusta
|
|
|
59
61
|
end
|
|
60
62
|
|
|
61
63
|
def simple_parameter_pattern?(pattern)
|
|
62
|
-
pattern.is_a?(Vec) && pattern.items.all?
|
|
64
|
+
pattern.is_a?(Vec) && pattern.items.all? do |item|
|
|
65
|
+
item.is_a?(Sym) && (!item.dotted? || item.name == '...') && item.name != '&'
|
|
66
|
+
end
|
|
63
67
|
end
|
|
64
68
|
|
|
65
69
|
def emit_definition_form(form, env, current_scope)
|
|
@@ -227,6 +231,9 @@ module Kapusta
|
|
|
227
231
|
|
|
228
232
|
def emit_let_parts(args, env, current_scope, result:)
|
|
229
233
|
bindings = args[0]
|
|
234
|
+
emit_error!(:let_odd_bindings) if bindings.items.length.odd?
|
|
235
|
+
emit_error!(:let_no_body) if args.length < 2
|
|
236
|
+
|
|
230
237
|
body = args[1..]
|
|
231
238
|
child_env = env.child
|
|
232
239
|
binding_codes = []
|
|
@@ -234,7 +241,9 @@ module Kapusta
|
|
|
234
241
|
i = 0
|
|
235
242
|
while i < items.length
|
|
236
243
|
pattern = items[i]
|
|
237
|
-
|
|
244
|
+
value_form = items[i + 1]
|
|
245
|
+
check_destructure_value!(pattern, value_form)
|
|
246
|
+
value_code = emit_expr(value_form, child_env, current_scope)
|
|
238
247
|
bind_code, child_env = emit_pattern_bind(pattern, value_code, child_env)
|
|
239
248
|
binding_codes << bind_code
|
|
240
249
|
i += 2
|
|
@@ -250,11 +259,15 @@ module Kapusta
|
|
|
250
259
|
end
|
|
251
260
|
|
|
252
261
|
def emit_local_form(form, env, current_scope)
|
|
262
|
+
emit_error!(:local_arity, form: form.head.name) unless form.items.length == 3
|
|
263
|
+
|
|
253
264
|
target = form.items[1]
|
|
254
265
|
value_code = emit_expr(form.items[2], env, current_scope)
|
|
255
266
|
|
|
256
267
|
if target.is_a?(Sym)
|
|
268
|
+
validate_binding_symbol!(target)
|
|
257
269
|
ruby_name = define_local(env, target.name)
|
|
270
|
+
mark_mutability(env, target.name, mutable: form.head.name == 'var')
|
|
258
271
|
["#{ruby_name} = #{value_code}\nnil", env]
|
|
259
272
|
else
|
|
260
273
|
bind_code, env = emit_pattern_bind(target, value_code, env)
|
|
@@ -262,11 +275,43 @@ module Kapusta
|
|
|
262
275
|
end
|
|
263
276
|
end
|
|
264
277
|
|
|
278
|
+
def check_destructure_value!(pattern, value_form)
|
|
279
|
+
return unless pattern.is_a?(Vec) || pattern.is_a?(HashLit)
|
|
280
|
+
|
|
281
|
+
case value_form
|
|
282
|
+
when String, Numeric, Symbol, true, false
|
|
283
|
+
emit_error!(:could_not_destructure_literal)
|
|
284
|
+
end
|
|
285
|
+
end
|
|
286
|
+
|
|
287
|
+
def mark_mutability(env, name, mutable:)
|
|
288
|
+
@binding_mutability ||= {}
|
|
289
|
+
ruby_name = env.lookup(name)
|
|
290
|
+
@binding_mutability[ruby_name] = mutable
|
|
291
|
+
end
|
|
292
|
+
|
|
293
|
+
def mutable_binding?(env, name)
|
|
294
|
+
ruby_name = env.lookup_if_defined(name)
|
|
295
|
+
return false unless ruby_name
|
|
296
|
+
|
|
297
|
+
(@binding_mutability ||= {}).fetch(ruby_name, true)
|
|
298
|
+
end
|
|
299
|
+
|
|
265
300
|
def emit_local_expr(args, env, current_scope)
|
|
266
301
|
code, = emit_local_form(List.new([Sym.new('local'), *args]), env.child, current_scope)
|
|
267
302
|
"(-> do\n#{indent(code)}\nend).call"
|
|
268
303
|
end
|
|
269
304
|
|
|
305
|
+
def emit_global_expr(args, _env, _current_scope)
|
|
306
|
+
emit_error!(:global_arity) unless args.length == 2
|
|
307
|
+
unless args[0].is_a?(Sym)
|
|
308
|
+
emit_error!(:global_non_symbol_name, type: args[0].class.name.downcase, value: args[0].inspect)
|
|
309
|
+
end
|
|
310
|
+
|
|
311
|
+
name = args[0].name
|
|
312
|
+
"$#{global_name(name)} = #{emit_expr(args[1], Env.new, :toplevel)}\nnil"
|
|
313
|
+
end
|
|
314
|
+
|
|
270
315
|
def emit_set_form(form, env, current_scope)
|
|
271
316
|
target = form.items[1]
|
|
272
317
|
value_code = emit_expr(form.items[2], env, current_scope)
|
|
@@ -275,7 +320,7 @@ module Kapusta
|
|
|
275
320
|
binding = env.lookup_if_defined(target.name)
|
|
276
321
|
ruby_name =
|
|
277
322
|
if binding
|
|
278
|
-
emit_error!(
|
|
323
|
+
emit_error!(:cannot_set_method_binding, name: target.name) if method_binding?(binding)
|
|
279
324
|
|
|
280
325
|
binding
|
|
281
326
|
else
|
|
@@ -319,7 +364,8 @@ module Kapusta
|
|
|
319
364
|
end
|
|
320
365
|
else
|
|
321
366
|
binding = env.lookup(target.name)
|
|
322
|
-
emit_error!(
|
|
367
|
+
emit_error!(:cannot_set_method_binding, name: target.name) if method_binding?(binding)
|
|
368
|
+
emit_error!(:expected_var, name: target.name) unless mutable_binding?(env, target.name)
|
|
323
369
|
|
|
324
370
|
emit_assignment(binding, value_code)
|
|
325
371
|
end
|
|
@@ -338,10 +384,10 @@ module Kapusta
|
|
|
338
384
|
elsif head.is_a?(Sym) && head.name == 'gvar'
|
|
339
385
|
emit_assignment("$#{global_name(target.items[1].name)}", value_code)
|
|
340
386
|
else
|
|
341
|
-
emit_error!(
|
|
387
|
+
emit_error!(:bad_set_target, target: target.inspect)
|
|
342
388
|
end
|
|
343
389
|
else
|
|
344
|
-
emit_error!(
|
|
390
|
+
emit_error!(:bad_set_target, target: target.inspect)
|
|
345
391
|
end
|
|
346
392
|
end
|
|
347
393
|
end
|
|
@@ -7,12 +7,11 @@ module Kapusta
|
|
|
7
7
|
private
|
|
8
8
|
|
|
9
9
|
def emit_icollect(args, env, current_scope)
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
10
|
+
emit_error!(:icollect_no_iterator) unless args[0].is_a?(Vec) && args[0].items.length >= 2
|
|
11
|
+
|
|
12
|
+
emit_iteration(args[0], env, current_scope, method: 'filter_map') do |iter_env|
|
|
13
|
+
emit_sequence(args[1..], iter_env, current_scope, allow_method_definitions: false).first
|
|
14
14
|
end
|
|
15
|
-
emit_collection_result(result_var, '[]', iter_code)
|
|
16
15
|
end
|
|
17
16
|
|
|
18
17
|
def emit_collect(args, env, current_scope)
|
|
@@ -35,27 +34,69 @@ module Kapusta
|
|
|
35
34
|
|
|
36
35
|
def emit_accumulate(args, env, current_scope)
|
|
37
36
|
bindings = args[0].items
|
|
37
|
+
emit_error!(:accumulate_no_iterator) if bindings.length < 4
|
|
38
|
+
|
|
38
39
|
acc_name = bindings[0]
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
40
|
+
init_code = emit_expr(bindings[1], env, current_scope)
|
|
41
|
+
iter_items = bindings[2..]
|
|
42
|
+
iter_expr = iter_items.last
|
|
43
|
+
binding_pats = iter_items[0...-1]
|
|
44
|
+
|
|
45
|
+
body_env = env.child
|
|
46
|
+
acc_var = define_local(body_env, acc_name.name)
|
|
47
|
+
|
|
48
|
+
inject_code = try_emit_inject(iter_expr, binding_pats, body_env, env, current_scope, acc_var,
|
|
49
|
+
init_code, args[1..])
|
|
50
|
+
return inject_code if inject_code
|
|
51
|
+
|
|
52
|
+
iter_code = emit_iteration(Vec.new(iter_items), body_env, current_scope) do |iter_env|
|
|
53
|
+
body_code, = emit_sequence(args[1..], iter_env, current_scope, allow_method_definitions: false)
|
|
54
|
+
emit_sequence_value_assignment(acc_var, body_code)
|
|
47
55
|
end
|
|
48
56
|
[
|
|
49
57
|
'(-> do',
|
|
50
|
-
indent("#{acc_var} = #{
|
|
58
|
+
indent("#{acc_var} = #{init_code}"),
|
|
51
59
|
indent(iter_code),
|
|
52
60
|
indent(acc_var),
|
|
53
61
|
'end).call'
|
|
54
62
|
].join("\n")
|
|
55
63
|
end
|
|
56
64
|
|
|
65
|
+
def try_emit_inject(iter_expr, binding_pats, body_env, env, current_scope, acc_var, init_code, body_forms)
|
|
66
|
+
return unless iter_expr.is_a?(List) && iter_expr.head.is_a?(Sym)
|
|
67
|
+
|
|
68
|
+
coll_code = emit_expr(iter_expr.items[1], env, current_scope)
|
|
69
|
+
case iter_expr.head.name
|
|
70
|
+
when 'ipairs'
|
|
71
|
+
value_var, value_bind = bind_iteration_param(binding_pats[1], 'value', body_env)
|
|
72
|
+
if ignored_pattern?(binding_pats[0])
|
|
73
|
+
body_code, = emit_sequence(body_forms, body_env, current_scope, allow_method_definitions: false)
|
|
74
|
+
return inject_block(coll_code, "#{acc_var}, #{value_var}", init_code, value_bind || '', body_code)
|
|
75
|
+
end
|
|
76
|
+
index_var, index_bind = bind_iteration_param(binding_pats[0], 'index', body_env)
|
|
77
|
+
bind_code = [index_bind, value_bind].compact.join("\n")
|
|
78
|
+
body_code, = emit_sequence(body_forms, body_env, current_scope, allow_method_definitions: false)
|
|
79
|
+
inject_block("#{coll_code}.each_with_index", "#{acc_var}, (#{value_var}, #{index_var})",
|
|
80
|
+
init_code, bind_code, body_code)
|
|
81
|
+
when 'pairs'
|
|
82
|
+
key_var, key_bind = bind_iteration_param(binding_pats[0], 'key', body_env)
|
|
83
|
+
value_var, value_bind = bind_iteration_param(binding_pats[1], 'value', body_env)
|
|
84
|
+
bind_code = [key_bind, value_bind].compact.join("\n")
|
|
85
|
+
body_code, = emit_sequence(body_forms, body_env, current_scope, allow_method_definitions: false)
|
|
86
|
+
inject_block(coll_code, "#{acc_var}, (#{key_var}, #{value_var})",
|
|
87
|
+
init_code, bind_code, body_code)
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def inject_block(receiver, params, init_code, bind_code, body_code)
|
|
92
|
+
inner = join_code(bind_code, body_code)
|
|
93
|
+
["#{receiver}.inject(#{init_code}) do |#{params}|", indent(inner), 'end'].join("\n")
|
|
94
|
+
end
|
|
95
|
+
|
|
57
96
|
def emit_faccumulate(args, env, current_scope)
|
|
58
97
|
bindings = args[0].items
|
|
98
|
+
emit_error!(:accumulate_no_iterator) if bindings.length < 5
|
|
99
|
+
|
|
59
100
|
acc_name = bindings[0]
|
|
60
101
|
loop_name = bindings[2]
|
|
61
102
|
loop_env = env.child
|
|
@@ -112,7 +153,9 @@ module Kapusta
|
|
|
112
153
|
end
|
|
113
154
|
end
|
|
114
155
|
|
|
115
|
-
def emit_iteration(bindings_vec, env, current_scope)
|
|
156
|
+
def emit_iteration(bindings_vec, env, current_scope, method: 'each')
|
|
157
|
+
emit_error!(:each_no_binding) unless bindings_vec.is_a?(Vec)
|
|
158
|
+
|
|
116
159
|
items = bindings_vec.items
|
|
117
160
|
iter_expr = items.last
|
|
118
161
|
binding_pats = items[0...-1]
|
|
@@ -126,12 +169,13 @@ module Kapusta
|
|
|
126
169
|
if ignored_pattern?(binding_pats[0])
|
|
127
170
|
bind_code = value_bind || ''
|
|
128
171
|
body_code = yield(body_env)
|
|
129
|
-
return iteration_block("#{coll_code}
|
|
172
|
+
return iteration_block("#{coll_code}.#{method} do |#{value_var}|", bind_code, body_code)
|
|
130
173
|
end
|
|
131
174
|
index_var, index_bind = bind_iteration_param(binding_pats[0], 'index', body_env)
|
|
132
175
|
bind_code = [index_bind, value_bind].compact.join("\n")
|
|
133
176
|
body_code = yield(body_env)
|
|
134
|
-
|
|
177
|
+
receiver = method == 'each' ? "#{coll_code}.each_with_index" : "#{coll_code}.each_with_index.#{method}"
|
|
178
|
+
header = "#{receiver} do |#{value_var}, #{index_var}|"
|
|
135
179
|
return iteration_block(header, bind_code, body_code)
|
|
136
180
|
when 'pairs'
|
|
137
181
|
body_env = env.child
|
|
@@ -139,7 +183,7 @@ module Kapusta
|
|
|
139
183
|
value_var, value_bind = bind_iteration_param(binding_pats[1], 'value', body_env)
|
|
140
184
|
bind_code = [key_bind, value_bind].compact.join("\n")
|
|
141
185
|
body_code = yield(body_env)
|
|
142
|
-
header = "#{emit_expr(iter_expr.items[1], env, current_scope)}
|
|
186
|
+
header = "#{emit_expr(iter_expr.items[1], env, current_scope)}.#{method} do |#{key_var}, #{value_var}|"
|
|
143
187
|
return iteration_block(header, bind_code, body_code)
|
|
144
188
|
end
|
|
145
189
|
end
|
|
@@ -149,14 +193,14 @@ module Kapusta
|
|
|
149
193
|
body_env = env.child
|
|
150
194
|
value_var, bind_code = bind_iteration_param(binding_pats[0], 'value', body_env)
|
|
151
195
|
body_code = yield(body_env)
|
|
152
|
-
iteration_block("#{coll_code}
|
|
196
|
+
iteration_block("#{coll_code}.#{method} do |#{value_var}|", bind_code || '', body_code)
|
|
153
197
|
else
|
|
154
198
|
parts_var = temp('parts')
|
|
155
199
|
body_env = env.child
|
|
156
200
|
pairs = binding_pats.each_with_index.map { |pattern, i| [pattern, "#{parts_var}[#{i}]"] }
|
|
157
201
|
bind_code, body_env = emit_iteration_bindings(pairs, body_env)
|
|
158
202
|
body_code = yield(body_env)
|
|
159
|
-
iteration_block("#{coll_code}
|
|
203
|
+
iteration_block("#{coll_code}.#{method} do |*#{parts_var}|", bind_code, body_code)
|
|
160
204
|
end
|
|
161
205
|
end
|
|
162
206
|
|
|
@@ -11,8 +11,7 @@ module Kapusta
|
|
|
11
11
|
end
|
|
12
12
|
|
|
13
13
|
def build_if(args, env, current_scope)
|
|
14
|
-
|
|
15
|
-
return emit_expr(args[0], env, current_scope) if args.length == 1
|
|
14
|
+
emit_error!(:if_no_body) if args.length < 2
|
|
16
15
|
|
|
17
16
|
cond = emit_expr(args[0], env, current_scope)
|
|
18
17
|
truthy = emit_if_branch(args[1], env, current_scope)
|
|
@@ -58,9 +57,13 @@ module Kapusta
|
|
|
58
57
|
end
|
|
59
58
|
|
|
60
59
|
def emit_case(args, env, current_scope, mode)
|
|
60
|
+
clauses = args[1..]
|
|
61
|
+
emit_error!(:case_no_patterns) if clauses.empty?
|
|
62
|
+
emit_error!(:case_odd_patterns) if clauses.length.odd?
|
|
63
|
+
|
|
61
64
|
value_var = temp('case_value')
|
|
62
|
-
body = try_emit_native_case(value_var,
|
|
63
|
-
emit_error!(
|
|
65
|
+
body = try_emit_native_case(value_var, clauses, env, current_scope, mode)
|
|
66
|
+
emit_error!(:case_unsupported) unless body
|
|
64
67
|
[
|
|
65
68
|
'(-> do',
|
|
66
69
|
indent("#{value_var} = #{emit_expr(args[0], env, current_scope)}"),
|
|
@@ -7,17 +7,19 @@ module Kapusta
|
|
|
7
7
|
private
|
|
8
8
|
|
|
9
9
|
def emit_expr(form, env, current_scope)
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
"#{
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
10
|
+
with_current_form(form) do
|
|
11
|
+
case form
|
|
12
|
+
when Sym then emit_sym(form, env)
|
|
13
|
+
when Vec then "[#{form.items.map { |item| emit_expr(item, env, current_scope) }.join(', ')}]"
|
|
14
|
+
when HashLit
|
|
15
|
+
"{#{form.pairs.map do |key, value|
|
|
16
|
+
"#{emit_hash_key(key, env, current_scope)} => #{emit_expr(value, env, current_scope)}"
|
|
17
|
+
end.join(', ')}}"
|
|
18
|
+
when List then emit_list(form, env, current_scope)
|
|
19
|
+
when String, Symbol, Numeric, true, false, nil then form.inspect
|
|
20
|
+
else
|
|
21
|
+
emit_error!(:cannot_emit_form, form: form.inspect)
|
|
22
|
+
end
|
|
21
23
|
end
|
|
22
24
|
end
|
|
23
25
|
|
|
@@ -29,7 +31,7 @@ module Kapusta
|
|
|
29
31
|
end
|
|
30
32
|
|
|
31
33
|
def emit_list(list, env, current_scope)
|
|
32
|
-
|
|
34
|
+
emit_error!(:empty_call) if list.empty?
|
|
33
35
|
|
|
34
36
|
head = list.head
|
|
35
37
|
args = list.rest
|
|
@@ -43,6 +45,11 @@ module Kapusta
|
|
|
43
45
|
return emit_self_call(head.name, args, env, current_scope)
|
|
44
46
|
end
|
|
45
47
|
|
|
48
|
+
case head
|
|
49
|
+
when Numeric, String, Symbol, true, false, nil
|
|
50
|
+
emit_error!(:cannot_call_literal, value: head.inspect)
|
|
51
|
+
end
|
|
52
|
+
|
|
46
53
|
emit_callable_call(emit_expr(head, env, current_scope), args, env, current_scope)
|
|
47
54
|
end
|
|
48
55
|
|
|
@@ -51,6 +58,7 @@ module Kapusta
|
|
|
51
58
|
when 'fn', 'lambda', 'λ' then emit_fn(args, env, current_scope)
|
|
52
59
|
when 'let' then emit_let(args, env, current_scope)
|
|
53
60
|
when 'local', 'var' then emit_local_expr(args, env, current_scope)
|
|
61
|
+
when 'global' then emit_global_expr(args, env, current_scope)
|
|
54
62
|
when 'set' then emit_set_expr(args, env, current_scope)
|
|
55
63
|
when 'if' then emit_if(args, env, current_scope)
|
|
56
64
|
when 'case' then emit_case(args, env, current_scope, :case)
|
|
@@ -96,7 +104,7 @@ module Kapusta
|
|
|
96
104
|
when '/' then emit_div(args, env, current_scope)
|
|
97
105
|
when '%' then args.map { |arg| parenthesize(emit_expr(arg, env, current_scope)) }.join(' % ')
|
|
98
106
|
when 'print' then emit_print(args, env, current_scope)
|
|
99
|
-
when 'quasi-sym' then "Kapusta::
|
|
107
|
+
when 'quasi-sym' then "Kapusta::MacroSym.new(#{emit_expr(args[0], env, current_scope)})"
|
|
100
108
|
when 'quasi-list' then "Kapusta::List.new([#{args.map { |a| emit_expr(a, env, current_scope) }.join(', ')}])"
|
|
101
109
|
when 'quasi-list-tail' then emit_quasi_list_tail(args, env, current_scope)
|
|
102
110
|
when 'quasi-vec' then "Kapusta::Vec.new([#{args.map { |a| emit_expr(a, env, current_scope) }.join(', ')}])"
|
|
@@ -104,9 +112,9 @@ module Kapusta
|
|
|
104
112
|
when 'quasi-hash' then emit_quasi_hash(args, env, current_scope)
|
|
105
113
|
when 'quasi-gensym' then emit_quasi_gensym(args[0], env, current_scope)
|
|
106
114
|
when 'macro', 'macros', 'import-macros'
|
|
107
|
-
emit_error!(
|
|
115
|
+
emit_error!(:special_must_be_toplevel, name:)
|
|
108
116
|
else
|
|
109
|
-
emit_error!(
|
|
117
|
+
emit_error!(:unknown_special_form, name:)
|
|
110
118
|
end
|
|
111
119
|
end
|
|
112
120
|
|
|
@@ -138,6 +146,9 @@ module Kapusta
|
|
|
138
146
|
def emit_concat(args, env, current_scope)
|
|
139
147
|
return '""' if args.empty?
|
|
140
148
|
|
|
149
|
+
args.each do |arg|
|
|
150
|
+
emit_error!(:vararg_with_operator) if arg.is_a?(Sym) && arg.name == '...'
|
|
151
|
+
end
|
|
141
152
|
args.map { |arg| emit_string_part(arg, env, current_scope) }.join(' + ')
|
|
142
153
|
end
|
|
143
154
|
|