kapusta 0.1.2 → 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.
@@ -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, allow_method_definitions: true)
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, allow_method_definitions: false)
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.each do |form|
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
- "__kap_#{prefix}_#{@temp_index}"
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)
@@ -156,11 +219,52 @@ module Kapusta
156
219
  "#{runtime_helper(name)}(#{rendered_args.join(', ')})"
157
220
  end
158
221
 
222
+ def parse_counted_for_bindings(bindings, env, current_scope)
223
+ name_sym = bindings[0]
224
+ loop_env = env.child
225
+ ruby_name = define_local(loop_env, name_sym.name)
226
+ start_code = emit_expr(bindings[1], env, current_scope)
227
+ finish_code = emit_expr(bindings[2], env, current_scope)
228
+ step_code = '1'
229
+ until_form = nil
230
+ i = 3
231
+ while i < bindings.length
232
+ if bindings[i].is_a?(Sym) && bindings[i].name == '&until'
233
+ until_form = bindings[i + 1]
234
+ i += 2
235
+ else
236
+ step_code = emit_expr(bindings[i], env, current_scope)
237
+ i += 1
238
+ end
239
+ end
240
+ { ruby_name:, loop_env:, start_code:, finish_code:, step_code:, until_form: }
241
+ end
242
+
243
+ def emit_counted_loop(ruby_name:, start_code:, finish_code:, step_code:,
244
+ until_form:, loop_env:, current_scope:, body_code:)
245
+ finish_var = temp('finish')
246
+ step_var = temp('step')
247
+ cmp_var = temp('cmp')
248
+ until_code = until_form ? "break if #{emit_expr(until_form, loop_env, current_scope)}" : nil
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")
259
+ end
260
+
159
261
  def sanitize_local(name)
160
262
  base = Kapusta.kebab_to_snake(name)
161
263
  base = base.gsub('?', '_q').gsub('!', '_bang')
162
264
  base = base.gsub(/[^a-zA-Z0-9_]/, '_')
163
- base = "_#{base}" if base.empty? || base.match?(/\A\d/) || self.class::RUBY_KEYWORDS.include?(base)
265
+ if base.empty? || base.match?(/\A\d/) || base.match?(/\A[A-Z]/) || self.class::RUBY_KEYWORDS.include?(base)
266
+ base = "_#{base}"
267
+ end
164
268
  base
165
269
  end
166
270
  end
@@ -20,12 +20,12 @@ module Kapusta
20
20
 
21
21
  HELPER_SOURCES = {
22
22
  kebab_to_snake: <<~RUBY.chomp,
23
- def __kap_kebab_to_snake(name)
23
+ def kap_kebab_to_snake(name)
24
24
  name.tr('-', '_')
25
25
  end
26
26
  RUBY
27
27
  call: <<~'RUBY'.chomp,
28
- def __kap_call(callee, positional, kwargs = nil, block = nil)
28
+ def kap_call(callee, positional, kwargs = nil, block = nil)
29
29
  raise "not callable: #{callee.inspect}" unless callee.respond_to?(:call)
30
30
 
31
31
  if block
@@ -36,7 +36,7 @@ module Kapusta
36
36
  end
37
37
  RUBY
38
38
  send_call: <<~RUBY.chomp,
39
- def __kap_send_call(receiver, method_name, positional, kwargs = nil, block = nil)
39
+ def kap_send_call(receiver, method_name, positional, kwargs = nil, block = nil)
40
40
  if block
41
41
  if kwargs
42
42
  receiver.public_send(method_name, *positional, **kwargs, &block)
@@ -51,7 +51,7 @@ module Kapusta
51
51
  end
52
52
  RUBY
53
53
  invoke_self: <<~RUBY.chomp,
54
- def __kap_invoke_self(receiver, method_name, positional, kwargs = nil, block = nil)
54
+ def kap_invoke_self(receiver, method_name, positional, kwargs = nil, block = nil)
55
55
  if block
56
56
  if kwargs
57
57
  receiver.send(method_name, *positional, **kwargs, &block)
@@ -64,7 +64,7 @@ module Kapusta
64
64
  end
65
65
  RUBY
66
66
  stringify: <<~'RUBY'.chomp,
67
- def __kap_stringify(value)
67
+ def kap_stringify(value)
68
68
  render = nil
69
69
  render = lambda do |item|
70
70
  case item
@@ -91,23 +91,23 @@ module Kapusta
91
91
  end
92
92
  RUBY
93
93
  print_values: <<~'RUBY'.chomp,
94
- def __kap_print_values(*values)
95
- $stdout.puts(values.map { |value| __kap_stringify(value) }.join("\t"))
94
+ def kap_print_values(*values)
95
+ $stdout.puts(values.map { |value| kap_stringify(value) }.join("\t"))
96
96
  nil
97
97
  end
98
98
  RUBY
99
99
  concat: <<~RUBY.chomp,
100
- def __kap_concat(values)
101
- values.map { |value| __kap_stringify(value) }.join
100
+ def kap_concat(values)
101
+ values.map { |value| kap_stringify(value) }.join
102
102
  end
103
103
  RUBY
104
104
  get_path: <<~RUBY.chomp,
105
- def __kap_get_path(obj, keys)
105
+ def kap_get_path(obj, keys)
106
106
  keys.reduce(obj) { |acc, key| acc[key] }
107
107
  end
108
108
  RUBY
109
109
  qget_path: <<~RUBY.chomp,
110
- def __kap_qget_path(obj, keys)
110
+ def kap_qget_path(obj, keys)
111
111
  keys.each do |key|
112
112
  return nil if obj.nil?
113
113
 
@@ -117,64 +117,64 @@ module Kapusta
117
117
  end
118
118
  RUBY
119
119
  set_path: <<~RUBY.chomp,
120
- def __kap_set_path(obj, keys, value)
120
+ def kap_set_path(obj, keys, value)
121
121
  target = obj
122
122
  keys[0...-1].each { |key| target = target[key] }
123
123
  target[keys.last] = value
124
124
  end
125
125
  RUBY
126
126
  method_path_value: <<~RUBY.chomp,
127
- def __kap_method_path_value(base, segments)
128
- segments.reduce(base) { |obj, segment| obj.public_send(__kap_kebab_to_snake(segment).to_sym) }
127
+ def kap_method_path_value(base, segments)
128
+ segments.reduce(base) { |obj, segment| obj.public_send(kap_kebab_to_snake(segment).to_sym) }
129
129
  end
130
130
  RUBY
131
131
  set_method_path: <<~'RUBY'.chomp,
132
- def __kap_set_method_path(base, segments, value)
132
+ def kap_set_method_path(base, segments, value)
133
133
  target = base
134
134
  segments[0...-1].each do |segment|
135
- target = target.public_send(__kap_kebab_to_snake(segment).to_sym)
135
+ target = target.public_send(kap_kebab_to_snake(segment).to_sym)
136
136
  end
137
- setter = "#{__kap_kebab_to_snake(segments.last)}="
137
+ setter = "#{kap_kebab_to_snake(segments.last)}="
138
138
  target.public_send(setter.to_sym, value)
139
139
  end
140
140
  RUBY
141
141
  current_class_scope: <<~RUBY.chomp,
142
- def __kap_current_class_scope(receiver)
142
+ def kap_current_class_scope(receiver)
143
143
  receiver.is_a?(Module) ? receiver : receiver.class
144
144
  end
145
145
  RUBY
146
146
  get_ivar: <<~'RUBY'.chomp,
147
- def __kap_get_ivar(receiver, name)
148
- receiver.instance_variable_get("@#{__kap_kebab_to_snake(name)}")
147
+ def kap_get_ivar(receiver, name)
148
+ receiver.instance_variable_get("@#{kap_kebab_to_snake(name)}")
149
149
  end
150
150
  RUBY
151
151
  set_ivar: <<~'RUBY'.chomp,
152
- def __kap_set_ivar(receiver, name, value)
153
- receiver.instance_variable_set("@#{__kap_kebab_to_snake(name)}", value)
152
+ def kap_set_ivar(receiver, name, value)
153
+ receiver.instance_variable_set("@#{kap_kebab_to_snake(name)}", value)
154
154
  end
155
155
  RUBY
156
156
  get_cvar: <<~'RUBY'.chomp,
157
- def __kap_get_cvar(receiver, name)
158
- __kap_current_class_scope(receiver).class_variable_get("@@#{__kap_kebab_to_snake(name)}")
157
+ def kap_get_cvar(receiver, name)
158
+ kap_current_class_scope(receiver).class_variable_get("@@#{kap_kebab_to_snake(name)}")
159
159
  end
160
160
  RUBY
161
161
  set_cvar: <<~'RUBY'.chomp,
162
- def __kap_set_cvar(receiver, name, value)
163
- __kap_current_class_scope(receiver).class_variable_set("@@#{__kap_kebab_to_snake(name)}", value)
162
+ def kap_set_cvar(receiver, name, value)
163
+ kap_current_class_scope(receiver).class_variable_set("@@#{kap_kebab_to_snake(name)}", value)
164
164
  end
165
165
  RUBY
166
166
  get_gvar: <<~'RUBY'.chomp,
167
- def __kap_get_gvar(name)
168
- Kernel.eval("$#{__kap_kebab_to_snake(name)}", binding, __FILE__, __LINE__)
167
+ def kap_get_gvar(name)
168
+ Kernel.eval("$#{kap_kebab_to_snake(name)}", binding, __FILE__, __LINE__)
169
169
  end
170
170
  RUBY
171
171
  set_gvar: <<~'RUBY'.chomp,
172
- def __kap_set_gvar(name, value)
173
- Kernel.eval("$#{__kap_kebab_to_snake(name)} = value", binding, __FILE__, __LINE__)
172
+ def kap_set_gvar(name, value)
173
+ Kernel.eval("$#{kap_kebab_to_snake(name)} = value", binding, __FILE__, __LINE__)
174
174
  end
175
175
  RUBY
176
176
  ensure_module: <<~RUBY.chomp,
177
- def __kap_ensure_module(holder, path)
177
+ def kap_ensure_module(holder, path)
178
178
  segments = path.split('.')
179
179
  last = segments.pop
180
180
  scope = holder.is_a?(Module) ? holder : Object
@@ -198,7 +198,7 @@ module Kapusta
198
198
  end
199
199
  RUBY
200
200
  ensure_class: <<~RUBY.chomp,
201
- def __kap_ensure_class(holder, path, super_class)
201
+ def kap_ensure_class(holder, path, super_class)
202
202
  segments = path.split('.')
203
203
  last = segments.pop
204
204
  scope = holder.is_a?(Module) ? holder : Object
@@ -222,14 +222,14 @@ module Kapusta
222
222
  end
223
223
  RUBY
224
224
  destructure: <<~RUBY.chomp,
225
- def __kap_destructure(pattern, value)
225
+ def kap_destructure(pattern, value)
226
226
  bindings = {}
227
- __kap_destructure_into(pattern, value, bindings)
227
+ kap_destructure_into(pattern, value, bindings)
228
228
  bindings
229
229
  end
230
230
  RUBY
231
231
  destructure_into: <<~'RUBY'.chomp,
232
- def __kap_destructure_into(pattern, value, bindings)
232
+ def kap_destructure_into(pattern, value, bindings)
233
233
  case pattern[0]
234
234
  when :sym
235
235
  name = pattern[1]
@@ -241,18 +241,18 @@ module Kapusta
241
241
  before = items[0...rest_idx]
242
242
  rest_pattern = items[rest_idx][1]
243
243
  before.each_with_index do |item, i|
244
- __kap_destructure_into(item, value ? value[i] : nil, bindings)
244
+ kap_destructure_into(item, value ? value[i] : nil, bindings)
245
245
  end
246
246
  rest_value = value ? (value[rest_idx..] || []) : []
247
- __kap_destructure_into(rest_pattern, rest_value, bindings)
247
+ kap_destructure_into(rest_pattern, rest_value, bindings)
248
248
  else
249
249
  items.each_with_index do |item, i|
250
- __kap_destructure_into(item, value ? value[i] : nil, bindings)
250
+ kap_destructure_into(item, value ? value[i] : nil, bindings)
251
251
  end
252
252
  end
253
253
  when :hash
254
254
  pattern[1].each do |key, subpattern|
255
- __kap_destructure_into(subpattern, value ? value[key] : nil, bindings)
255
+ kap_destructure_into(subpattern, value ? value[key] : nil, bindings)
256
256
  end
257
257
  when :ignore
258
258
  nil
@@ -262,17 +262,24 @@ module Kapusta
262
262
  end
263
263
  RUBY
264
264
  match_pattern: <<~RUBY.chomp,
265
- def __kap_match_pattern(pattern, value)
265
+ def kap_match_pattern(pattern, value)
266
266
  bindings = {}
267
- [__kap_match_pattern_into(pattern, value, bindings), bindings]
267
+ [kap_match_pattern_into(pattern, value, bindings), bindings]
268
268
  end
269
269
  RUBY
270
270
  match_pattern_into: <<~'RUBY'.chomp
271
- def __kap_match_pattern_into(pattern, value, bindings)
271
+ def kap_match_pattern_into(pattern, value, bindings)
272
272
  case pattern[0]
273
- when :sym
273
+ when :bind
274
274
  name = pattern[1]
275
- bindings[name] = value unless name == '_'
275
+ allow_nil = pattern[2]
276
+ return false if value.nil? && !allow_nil
277
+
278
+ bindings[name] = value
279
+ true
280
+ when :ref
281
+ bindings.key?(pattern[1]) && bindings[pattern[1]] == value
282
+ when :wild
276
283
  true
277
284
  when :vec
278
285
  return false unless value.is_a?(Array) || value.respond_to?(:to_ary)
@@ -286,14 +293,14 @@ module Kapusta
286
293
  return false if array.length < before.length
287
294
 
288
295
  before.each_with_index do |item, i|
289
- return false unless __kap_match_pattern_into(item, array[i], bindings)
296
+ return false unless kap_match_pattern_into(item, array[i], bindings)
290
297
  end
291
- __kap_match_pattern_into(rest_pattern, array[rest_idx..], bindings)
298
+ kap_match_pattern_into(rest_pattern, array[rest_idx..], bindings)
292
299
  else
293
- return false unless array.length == items.length
300
+ return false unless array.length >= items.length
294
301
 
295
302
  items.each_with_index do |item, i|
296
- return false unless __kap_match_pattern_into(item, array[i], bindings)
303
+ return false unless kap_match_pattern_into(item, array[i], bindings)
297
304
  end
298
305
  true
299
306
  end
@@ -302,13 +309,21 @@ module Kapusta
302
309
 
303
310
  pattern[1].each do |key, subpattern|
304
311
  return false unless value.key?(key)
305
- return false unless __kap_match_pattern_into(subpattern, value[key], bindings)
312
+ return false unless kap_match_pattern_into(subpattern, value[key], bindings)
306
313
  end
307
314
  true
308
315
  when :lit
309
316
  value == pattern[1]
310
- when :nil
311
- value.nil?
317
+ when :pin
318
+ value == pattern[1]
319
+ when :or
320
+ pattern[1].any? do |option|
321
+ option_bindings = bindings.dup
322
+ next false unless kap_match_pattern_into(option, value, option_bindings)
323
+
324
+ bindings.replace(option_bindings)
325
+ true
326
+ end
312
327
  else
313
328
  raise "bad pattern: #{pattern.inspect}"
314
329
  end
@@ -319,7 +334,7 @@ module Kapusta
319
334
  module_function
320
335
 
321
336
  def helper_name(name)
322
- "__kap_#{name}"
337
+ "kap_#{name}"
323
338
  end
324
339
 
325
340
  def helper_source(helpers)
@@ -351,7 +366,7 @@ module Kapusta
351
366
  helper_methods = []
352
367
 
353
368
  HELPER_SOURCES.each_key do |name|
354
- helper_method = :"__kap_#{name}"
369
+ helper_method = :"kap_#{name}"
355
370
  define_singleton_method(name, instance_method(helper_method))
356
371
  helper_methods << helper_method
357
372
  end
@@ -1,12 +1,13 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative 'error'
3
4
  require_relative 'compiler/runtime'
4
5
  require_relative 'compiler/normalizer'
5
6
  require_relative 'compiler/emitter'
6
7
 
7
8
  module Kapusta
8
9
  module Compiler
9
- class Error < StandardError; end
10
+ class Error < Kapusta::Error; end
10
11
  SPECIAL_FORMS = %w[
11
12
  fn lambda λ let local var set if when unless case match
12
13
  while for each do values
data/lib/kapusta/env.rb CHANGED
@@ -25,6 +25,14 @@ module Kapusta
25
25
  @vars.key?(name) || @parent&.defined?(name)
26
26
  end
27
27
 
28
+ def ruby_name_defined?(name)
29
+ @vars.value?(name) || @parent&.ruby_name_defined?(name)
30
+ end
31
+
32
+ def local_ruby_name_defined?(name)
33
+ @vars.value?(name)
34
+ end
35
+
28
36
  def set_existing!(name, value)
29
37
  if @vars.key?(name)
30
38
  @vars[name] = value
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Kapusta
4
+ class Error < StandardError; end
5
+ end
@@ -519,11 +519,12 @@ module Kapusta
519
519
  def render_pairwise_vec(vec, indent)
520
520
  lines = ['[']
521
521
 
522
- vec.items.each_slice(2) do |left, right|
523
- if right
524
- pair = render_pair(left, right, indent + INDENT)
525
- if pair
526
- lines << indent_block(pair, INDENT)
522
+ vec.items.each_slice(2) do |pair|
523
+ left, right = pair
524
+ if pair.length == 2
525
+ rendered_pair = render_pair(left, right, indent + INDENT)
526
+ if rendered_pair
527
+ lines << indent_block(rendered_pair, INDENT)
527
528
  else
528
529
  lines << indent_block(render(left, indent + INDENT), INDENT)
529
530
  lines << indent_block(render(right, indent + INDENT), INDENT)
@@ -547,8 +548,9 @@ module Kapusta
547
548
 
548
549
  def render_hanging_pairwise_vec(vec)
549
550
  pairs = vec.items.each_slice(2).to_a
550
- rendered_pairs = pairs.map do |left, right|
551
- return nil unless right
551
+ rendered_pairs = pairs.map do |pair|
552
+ left, right = pair
553
+ return nil unless pair.length == 2
552
554
 
553
555
  render_binding_pair(left, right)
554
556
  end
@@ -673,15 +675,6 @@ module Kapusta
673
675
  end
674
676
  end
675
677
 
676
- def fn_body(form)
677
- args = list_rest(form)
678
- if args[0].is_a?(Sym) && args[1].is_a?(Vec)
679
- args.drop(2)
680
- else
681
- args.drop(1)
682
- end
683
- end
684
-
685
678
  def consecutive_requires?(previous, current)
686
679
  require_form?(previous) && require_form?(current)
687
680
  end
@@ -832,6 +825,6 @@ module Kapusta
832
825
  puts 'Formats Kapusta source using the built-in Kapusta reader and pretty-printer.'
833
826
  end
834
827
 
835
- class Error < StandardError; end
828
+ class Error < Kapusta::Error; end
836
829
  end
837
830
  end
@@ -1,7 +1,11 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative 'error'
4
+
3
5
  module Kapusta
4
6
  class Reader
7
+ class Error < Kapusta::Error; end
8
+
5
9
  WHITESPACE = [' ', "\t", "\n", "\r", "\f", "\v", ','].freeze
6
10
  DELIMS = ['(', ')', '[', ']', '{', '}', '"', ';'].freeze
7
11
 
@@ -61,7 +65,7 @@ module Kapusta
61
65
 
62
66
  def read_next_item
63
67
  skip_ws
64
- raise 'unexpected eof' if eof?
68
+ raise Error, 'unexpected eof' if eof?
65
69
 
66
70
  return read_comment if @preserve_comments && peek == ';'
67
71
 
@@ -70,7 +74,7 @@ module Kapusta
70
74
 
71
75
  def read_form
72
76
  skip_ws
73
- raise 'unexpected eof' if eof?
77
+ raise Error, 'unexpected eof' if eof?
74
78
 
75
79
  return read_comment if @preserve_comments && peek == ';'
76
80
 
@@ -93,7 +97,7 @@ module Kapusta
93
97
  items = []
94
98
  loop do
95
99
  skip_ws
96
- raise 'unclosed (' if eof?
100
+ raise Error, 'unclosed (' if eof?
97
101
  break if peek == ')'
98
102
 
99
103
  items << read_next_item
@@ -107,7 +111,7 @@ module Kapusta
107
111
  items = []
108
112
  loop do
109
113
  skip_ws
110
- raise 'unclosed [' if eof?
114
+ raise Error, 'unclosed [' if eof?
111
115
  break if peek == ']'
112
116
 
113
117
  items << read_next_item
@@ -122,7 +126,7 @@ module Kapusta
122
126
  pending = []
123
127
  loop do
124
128
  skip_ws
125
- raise 'unclosed {' if eof?
129
+ raise Error, 'unclosed {' if eof?
126
130
  break if peek == '}'
127
131
 
128
132
  item = read_next_item
@@ -139,7 +143,7 @@ module Kapusta
139
143
  end
140
144
  advance
141
145
 
142
- raise 'odd number of forms in hash' unless pending.empty?
146
+ raise Error, 'odd number of forms in hash' unless pending.empty?
143
147
 
144
148
  HashLit.new(entries)
145
149
  end
@@ -168,7 +172,7 @@ module Kapusta
168
172
  buffer << advance
169
173
  end
170
174
  end
171
- raise 'unterminated string' if eof?
175
+ raise Error, 'unterminated string' if eof?
172
176
 
173
177
  advance
174
178
  buffer
@@ -209,7 +213,7 @@ module Kapusta
209
213
  start = @pos
210
214
  advance until delim?(peek)
211
215
  token = @src[start...@pos]
212
- raise 'empty token' if token.empty?
216
+ raise Error, 'empty token' if token.empty?
213
217
 
214
218
  parse_atom(token)
215
219
  end
@@ -230,7 +234,7 @@ module Kapusta
230
234
 
231
235
  def normalize_hash_pair(item, value)
232
236
  if item.is_a?(Sym) && item.name == ':'
233
- raise 'bad shorthand' unless value.is_a?(Sym)
237
+ raise Error, 'bad shorthand' unless value.is_a?(Sym)
234
238
 
235
239
  key = Kapusta.kebab_to_snake(value.name).to_sym
236
240
  [key, value]