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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 01cc980a37ff63b56db021edb64f7ed9b4381569bb82e8e4ada099a2b17ba19e
4
- data.tar.gz: 035f0f4a38d5d9fad65f4a53e8343bbcad4b681bd33dfa975f7c9c638a83ccb4
3
+ metadata.gz: f58b2e1f81723470a819ba38e3e6e811a3aa3a97ba79443e6ad0c59457d0fa08
4
+ data.tar.gz: 9207c1491218f71eeeedf2495a22805b9e2d1a8336200d212437fb813005c1d8
5
5
  SHA512:
6
- metadata.gz: caaeb31630a5c80607fe6c2907548c2874f177add0bdbd3555e72039dd598aaf481f91778043f6b81cd511c9ac8609e48f61c474766dac9b3e8a2fa41d408970
7
- data.tar.gz: 5417ad8088be602618c3662660af820be6dbf15eb27d8300b1c542e3889fbfe1813d51ca7306f988a630616d1857f333141bf20b964d0869c2caa8c3eba869e3
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: EXAMPLES)
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
- if failures.empty?
147
- puts "All #{COMPATIBLE.size} compatible examples produce matching output."
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 "#{failures.size} of #{COMPATIBLE.size} examples failed:\n\n"
151
- failures.each do |name, reason|
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
@@ -1,7 +1,22 @@
1
- (let [
2
- even-squares
3
- (-> [1 2 3 4 5 6]
4
- (: :select (fn [n] (n.even?)))
5
- (: :map (fn [n] (* n n))))
6
- ]
7
- (print (even-squares.join ", ")))
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 [values {"I" 1 "V" 5 "X" 10 "L" 50 "C" 100 "D" 500 "M" 1000}
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 (. values (. chars i))
9
- ahead (if (< (+ i 1) n) (. values (. chars (+ i 1))) 0)
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
- "#<Sym #{@name}>"
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
@@ -26,6 +26,9 @@ module Kapusta
26
26
  else
27
27
  run_file(args)
28
28
  end
29
+ rescue Kapusta::Error => e
30
+ warn e.formatted
31
+ exit 1
29
32
  end
30
33
 
31
34
  def self.parse_options(args)
@@ -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? { |item| item.is_a?(Sym) && !item.dotted? && item.name != '&' }
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
- value_code = emit_expr(items[i + 1], child_env, current_scope)
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!("cannot set method binding: #{target.name}") if method_binding?(binding)
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!("cannot set method binding: #{target.name}") if method_binding?(binding)
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!("bad set target: #{target.inspect}")
387
+ emit_error!(:bad_set_target, target: target.inspect)
342
388
  end
343
389
  else
344
- emit_error!("bad set target: #{target.inspect}")
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
- result_var = temp('result')
11
- iter_code = emit_iteration(args[0], env, current_scope) do |iter_env|
12
- body = emit_sequence(args[1..], iter_env, current_scope, allow_method_definitions: false).first
13
- emit_array_collection_step(result_var, body)
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
- iter_bindings = Vec.new(bindings[2..])
40
- loop_env = env.child
41
- acc_var = define_local(loop_env, acc_name.name)
42
- iter_code = emit_iteration(iter_bindings, loop_env, current_scope) do |iter_env|
43
- iter_env.define(acc_name.name, acc_var)
44
- emit_sequence(args[1..], iter_env, current_scope, allow_method_definitions: false).first.then do |body|
45
- emit_sequence_value_assignment(acc_var, body)
46
- end
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} = #{emit_expr(bindings[1], env, current_scope)}"),
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}.each do |#{value_var}|", bind_code, body_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
- header = "#{coll_code}.each_with_index do |#{value_var}, #{index_var}|"
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)}.each do |#{key_var}, #{value_var}|"
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}.each do |#{value_var}|", bind_code || '', body_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}.each do |*#{parts_var}|", bind_code, body_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
- return 'nil' if args.empty?
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, args[1..], env, current_scope, mode)
63
- emit_error!('case/match clauses use patterns this compiler cannot translate') unless body
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
- case form
11
- when Sym then emit_sym(form, env)
12
- when Vec then "[#{form.items.map { |item| emit_expr(item, env, current_scope) }.join(', ')}]"
13
- when HashLit
14
- "{#{form.pairs.map do |key, value|
15
- "#{emit_hash_key(key, env, current_scope)} => #{emit_expr(value, env, current_scope)}"
16
- end.join(', ')}}"
17
- when List then emit_list(form, env, current_scope)
18
- when String, Symbol, Numeric, true, false, nil then form.inspect
19
- else
20
- emit_error!("cannot emit form: #{form.inspect}")
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
- return 'nil' if list.empty?
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::Sym.new(#{emit_expr(args[0], env, current_scope)})"
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!("#{name} must appear at the top level and is consumed by the macro expander")
115
+ emit_error!(:special_must_be_toplevel, name:)
108
116
  else
109
- emit_error!("unknown special form: #{name}")
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