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.
@@ -4,6 +4,7 @@ module Kapusta
4
4
  module Compiler
5
5
  module Runtime
6
6
  HELPER_DEPENDENCIES = {
7
+ stringify: %i[repr],
7
8
  print_values: %i[stringify],
8
9
  concat: %i[stringify],
9
10
  method_path_value: %i[kebab_to_snake],
@@ -20,12 +21,12 @@ module Kapusta
20
21
 
21
22
  HELPER_SOURCES = {
22
23
  kebab_to_snake: <<~RUBY.chomp,
23
- def __kap_kebab_to_snake(name)
24
+ def kap_kebab_to_snake(name)
24
25
  name.tr('-', '_')
25
26
  end
26
27
  RUBY
27
28
  call: <<~'RUBY'.chomp,
28
- def __kap_call(callee, positional, kwargs = nil, block = nil)
29
+ def kap_call(callee, positional, kwargs = nil, block = nil)
29
30
  raise "not callable: #{callee.inspect}" unless callee.respond_to?(:call)
30
31
 
31
32
  if block
@@ -36,7 +37,7 @@ module Kapusta
36
37
  end
37
38
  RUBY
38
39
  send_call: <<~RUBY.chomp,
39
- def __kap_send_call(receiver, method_name, positional, kwargs = nil, block = nil)
40
+ def kap_send_call(receiver, method_name, positional, kwargs = nil, block = nil)
40
41
  if block
41
42
  if kwargs
42
43
  receiver.public_send(method_name, *positional, **kwargs, &block)
@@ -51,7 +52,7 @@ module Kapusta
51
52
  end
52
53
  RUBY
53
54
  invoke_self: <<~RUBY.chomp,
54
- def __kap_invoke_self(receiver, method_name, positional, kwargs = nil, block = nil)
55
+ def kap_invoke_self(receiver, method_name, positional, kwargs = nil, block = nil)
55
56
  if block
56
57
  if kwargs
57
58
  receiver.send(method_name, *positional, **kwargs, &block)
@@ -63,53 +64,50 @@ module Kapusta
63
64
  end
64
65
  end
65
66
  RUBY
66
- stringify: <<~'RUBY'.chomp,
67
- def __kap_stringify(value)
68
- render = nil
69
- render = lambda do |item|
70
- case item
71
- when nil then 'nil'
72
- when true then 'true'
73
- when false then 'false'
74
- when String, Symbol then item.inspect
75
- when Array
76
- "[#{item.map { |child| render.call(child) }.join(', ')}]"
77
- when Hash
78
- "{#{item.map { |key, child| "#{render.call(key)}=>#{render.call(child)}" }.join(', ')}}"
79
- else
80
- item.inspect
81
- end
82
- end
83
-
67
+ stringify: <<~RUBY.chomp,
68
+ def kap_stringify(value)
84
69
  case value
85
70
  when nil then 'nil'
86
- when true then 'true'
87
- when false then 'false'
88
- when Array, Hash then render.call(value)
71
+ when Array, Hash then kap_repr(value)
89
72
  else value.to_s
90
73
  end
91
74
  end
92
75
  RUBY
76
+ repr: <<~'RUBY'.chomp,
77
+ def kap_repr(value)
78
+ case value
79
+ when nil then 'nil'
80
+ when true, false then value.to_s
81
+ when String, Symbol then value.inspect
82
+ when Array
83
+ "[#{value.map { |item| kap_repr(item) }.join(', ')}]"
84
+ when Hash
85
+ "{#{value.map { |key, item| "#{kap_repr(key)}=>#{kap_repr(item)}" }.join(', ')}}"
86
+ else
87
+ value.inspect
88
+ end
89
+ end
90
+ RUBY
93
91
  print_values: <<~'RUBY'.chomp,
94
- def __kap_print_values(*values)
95
- $stdout.puts(values.map { |value| __kap_stringify(value) }.join("\t"))
92
+ def kap_print_values(*values)
93
+ $stdout.puts(values.map { |value| kap_stringify(value) }.join("\t"))
96
94
  nil
97
95
  end
98
96
  RUBY
99
97
  concat: <<~RUBY.chomp,
100
- def __kap_concat(values)
101
- values.map { |value| __kap_stringify(value) }.join
98
+ def kap_concat(values)
99
+ values.map { |value| kap_stringify(value) }.join
102
100
  end
103
101
  RUBY
104
102
  get_path: <<~RUBY.chomp,
105
- def __kap_get_path(obj, keys)
103
+ def kap_get_path(obj, keys)
106
104
  keys.reduce(obj) { |acc, key| acc[key] }
107
105
  end
108
106
  RUBY
109
107
  qget_path: <<~RUBY.chomp,
110
- def __kap_qget_path(obj, keys)
108
+ def kap_qget_path(obj, keys)
111
109
  keys.each do |key|
112
- return nil if obj.nil?
110
+ return if obj.nil?
113
111
 
114
112
  obj = obj[key]
115
113
  end
@@ -117,64 +115,64 @@ module Kapusta
117
115
  end
118
116
  RUBY
119
117
  set_path: <<~RUBY.chomp,
120
- def __kap_set_path(obj, keys, value)
118
+ def kap_set_path(obj, keys, value)
121
119
  target = obj
122
120
  keys[0...-1].each { |key| target = target[key] }
123
121
  target[keys.last] = value
124
122
  end
125
123
  RUBY
126
124
  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) }
125
+ def kap_method_path_value(base, segments)
126
+ segments.reduce(base) { |obj, segment| obj.public_send(kap_kebab_to_snake(segment).to_sym) }
129
127
  end
130
128
  RUBY
131
129
  set_method_path: <<~'RUBY'.chomp,
132
- def __kap_set_method_path(base, segments, value)
130
+ def kap_set_method_path(base, segments, value)
133
131
  target = base
134
132
  segments[0...-1].each do |segment|
135
- target = target.public_send(__kap_kebab_to_snake(segment).to_sym)
133
+ target = target.public_send(kap_kebab_to_snake(segment).to_sym)
136
134
  end
137
- setter = "#{__kap_kebab_to_snake(segments.last)}="
135
+ setter = "#{kap_kebab_to_snake(segments.last)}="
138
136
  target.public_send(setter.to_sym, value)
139
137
  end
140
138
  RUBY
141
139
  current_class_scope: <<~RUBY.chomp,
142
- def __kap_current_class_scope(receiver)
140
+ def kap_current_class_scope(receiver)
143
141
  receiver.is_a?(Module) ? receiver : receiver.class
144
142
  end
145
143
  RUBY
146
144
  get_ivar: <<~'RUBY'.chomp,
147
- def __kap_get_ivar(receiver, name)
148
- receiver.instance_variable_get("@#{__kap_kebab_to_snake(name)}")
145
+ def kap_get_ivar(receiver, name)
146
+ receiver.instance_variable_get("@#{kap_kebab_to_snake(name)}")
149
147
  end
150
148
  RUBY
151
149
  set_ivar: <<~'RUBY'.chomp,
152
- def __kap_set_ivar(receiver, name, value)
153
- receiver.instance_variable_set("@#{__kap_kebab_to_snake(name)}", value)
150
+ def kap_set_ivar(receiver, name, value)
151
+ receiver.instance_variable_set("@#{kap_kebab_to_snake(name)}", value)
154
152
  end
155
153
  RUBY
156
154
  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)}")
155
+ def kap_get_cvar(receiver, name)
156
+ kap_current_class_scope(receiver).class_variable_get("@@#{kap_kebab_to_snake(name)}")
159
157
  end
160
158
  RUBY
161
159
  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)
160
+ def kap_set_cvar(receiver, name, value)
161
+ kap_current_class_scope(receiver).class_variable_set("@@#{kap_kebab_to_snake(name)}", value)
164
162
  end
165
163
  RUBY
166
164
  get_gvar: <<~'RUBY'.chomp,
167
- def __kap_get_gvar(name)
168
- Kernel.eval("$#{__kap_kebab_to_snake(name)}", binding, __FILE__, __LINE__)
165
+ def kap_get_gvar(name)
166
+ Kernel.eval("$#{kap_kebab_to_snake(name)}", binding, __FILE__, __LINE__)
169
167
  end
170
168
  RUBY
171
169
  set_gvar: <<~'RUBY'.chomp,
172
- def __kap_set_gvar(name, value)
173
- Kernel.eval("$#{__kap_kebab_to_snake(name)} = value", binding, __FILE__, __LINE__)
170
+ def kap_set_gvar(name, value)
171
+ Kernel.eval("$#{kap_kebab_to_snake(name)} = value", binding, __FILE__, __LINE__)
174
172
  end
175
173
  RUBY
176
174
  ensure_module: <<~RUBY.chomp,
177
- def __kap_ensure_module(holder, path)
175
+ def kap_ensure_module(holder, path)
178
176
  segments = path.split('.')
179
177
  last = segments.pop
180
178
  scope = holder.is_a?(Module) ? holder : Object
@@ -198,7 +196,7 @@ module Kapusta
198
196
  end
199
197
  RUBY
200
198
  ensure_class: <<~RUBY.chomp,
201
- def __kap_ensure_class(holder, path, super_class)
199
+ def kap_ensure_class(holder, path, super_class)
202
200
  segments = path.split('.')
203
201
  last = segments.pop
204
202
  scope = holder.is_a?(Module) ? holder : Object
@@ -222,14 +220,14 @@ module Kapusta
222
220
  end
223
221
  RUBY
224
222
  destructure: <<~RUBY.chomp,
225
- def __kap_destructure(pattern, value)
223
+ def kap_destructure(pattern, value)
226
224
  bindings = {}
227
- __kap_destructure_into(pattern, value, bindings)
225
+ kap_destructure_into(pattern, value, bindings)
228
226
  bindings
229
227
  end
230
228
  RUBY
231
229
  destructure_into: <<~'RUBY'.chomp,
232
- def __kap_destructure_into(pattern, value, bindings)
230
+ def kap_destructure_into(pattern, value, bindings)
233
231
  case pattern[0]
234
232
  when :sym
235
233
  name = pattern[1]
@@ -241,18 +239,18 @@ module Kapusta
241
239
  before = items[0...rest_idx]
242
240
  rest_pattern = items[rest_idx][1]
243
241
  before.each_with_index do |item, i|
244
- __kap_destructure_into(item, value ? value[i] : nil, bindings)
242
+ kap_destructure_into(item, value ? value[i] : nil, bindings)
245
243
  end
246
244
  rest_value = value ? (value[rest_idx..] || []) : []
247
- __kap_destructure_into(rest_pattern, rest_value, bindings)
245
+ kap_destructure_into(rest_pattern, rest_value, bindings)
248
246
  else
249
247
  items.each_with_index do |item, i|
250
- __kap_destructure_into(item, value ? value[i] : nil, bindings)
248
+ kap_destructure_into(item, value ? value[i] : nil, bindings)
251
249
  end
252
250
  end
253
251
  when :hash
254
252
  pattern[1].each do |key, subpattern|
255
- __kap_destructure_into(subpattern, value ? value[key] : nil, bindings)
253
+ kap_destructure_into(subpattern, value ? value[key] : nil, bindings)
256
254
  end
257
255
  when :ignore
258
256
  nil
@@ -262,13 +260,13 @@ module Kapusta
262
260
  end
263
261
  RUBY
264
262
  match_pattern: <<~RUBY.chomp,
265
- def __kap_match_pattern(pattern, value)
263
+ def kap_match_pattern(pattern, value)
266
264
  bindings = {}
267
- [__kap_match_pattern_into(pattern, value, bindings), bindings]
265
+ [kap_match_pattern_into(pattern, value, bindings), bindings]
268
266
  end
269
267
  RUBY
270
268
  match_pattern_into: <<~'RUBY'.chomp
271
- def __kap_match_pattern_into(pattern, value, bindings)
269
+ def kap_match_pattern_into(pattern, value, bindings)
272
270
  case pattern[0]
273
271
  when :bind
274
272
  name = pattern[1]
@@ -293,14 +291,14 @@ module Kapusta
293
291
  return false if array.length < before.length
294
292
 
295
293
  before.each_with_index do |item, i|
296
- return false unless __kap_match_pattern_into(item, array[i], bindings)
294
+ return false unless kap_match_pattern_into(item, array[i], bindings)
297
295
  end
298
- __kap_match_pattern_into(rest_pattern, array[rest_idx..], bindings)
296
+ kap_match_pattern_into(rest_pattern, array[rest_idx..], bindings)
299
297
  else
300
298
  return false unless array.length >= items.length
301
299
 
302
300
  items.each_with_index do |item, i|
303
- return false unless __kap_match_pattern_into(item, array[i], bindings)
301
+ return false unless kap_match_pattern_into(item, array[i], bindings)
304
302
  end
305
303
  true
306
304
  end
@@ -309,7 +307,7 @@ module Kapusta
309
307
 
310
308
  pattern[1].each do |key, subpattern|
311
309
  return false unless value.key?(key)
312
- return false unless __kap_match_pattern_into(subpattern, value[key], bindings)
310
+ return false unless kap_match_pattern_into(subpattern, value[key], bindings)
313
311
  end
314
312
  true
315
313
  when :lit
@@ -319,7 +317,7 @@ module Kapusta
319
317
  when :or
320
318
  pattern[1].any? do |option|
321
319
  option_bindings = bindings.dup
322
- next false unless __kap_match_pattern_into(option, value, option_bindings)
320
+ next false unless kap_match_pattern_into(option, value, option_bindings)
323
321
 
324
322
  bindings.replace(option_bindings)
325
323
  true
@@ -334,7 +332,7 @@ module Kapusta
334
332
  module_function
335
333
 
336
334
  def helper_name(name)
337
- "__kap_#{name}"
335
+ "kap_#{name}"
338
336
  end
339
337
 
340
338
  def helper_source(helpers)
@@ -366,11 +364,14 @@ module Kapusta
366
364
  helper_methods = []
367
365
 
368
366
  HELPER_SOURCES.each_key do |name|
369
- helper_method = :"__kap_#{name}"
370
- define_singleton_method(name, instance_method(helper_method))
367
+ helper_method = :"kap_#{name}"
368
+ body = instance_method(helper_method)
369
+ define_singleton_method(helper_method, body)
370
+ define_singleton_method(name, body)
371
371
  helper_methods << helper_method
372
372
  end
373
373
 
374
+ private_class_method(*helper_methods)
374
375
  send(:private, *helper_methods)
375
376
  end
376
377
  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
@@ -2,6 +2,8 @@
2
2
 
3
3
  module Kapusta
4
4
  class Env
5
+ MethodBinding = Struct.new(:ruby_name)
6
+
5
7
  def initialize(parent = nil)
6
8
  @parent = parent
7
9
  @vars = {}
@@ -25,6 +27,14 @@ module Kapusta
25
27
  @vars.key?(name) || @parent&.defined?(name)
26
28
  end
27
29
 
30
+ def ruby_name_defined?(name)
31
+ @vars.any? { |_source_name, value| binding_ruby_name(value) == name } || @parent&.ruby_name_defined?(name)
32
+ end
33
+
34
+ def local_ruby_name_defined?(name)
35
+ @vars.any? { |_source_name, value| binding_ruby_name(value) == name }
36
+ end
37
+
28
38
  def set_existing!(name, value)
29
39
  if @vars.key?(name)
30
40
  @vars[name] = value
@@ -38,5 +48,11 @@ module Kapusta
38
48
  def child
39
49
  Env.new(self)
40
50
  end
51
+
52
+ private
53
+
54
+ def binding_ruby_name(value)
55
+ value.respond_to?(:ruby_name) ? value.ruby_name : value
56
+ end
41
57
  end
42
58
  end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Kapusta
4
+ class Error < StandardError; end
5
+ end
@@ -177,15 +177,15 @@ module Kapusta
177
177
  when Sym
178
178
  form.name
179
179
  when Vec
180
- return nil if contains_comments?(form.items)
180
+ return if contains_comments?(form.items)
181
181
 
182
182
  "[#{form.items.map { |item| flat_render(item) }.join(' ')}]"
183
183
  when HashLit
184
- return nil if contains_comments?(form.entries)
184
+ return if contains_comments?(form.entries)
185
185
 
186
186
  "{#{form.pairs.map { |key, value| flat_hash_pair(key, value) }.join(' ')}}"
187
187
  when List
188
- return nil if contains_comments?(form.items)
188
+ return if contains_comments?(form.items)
189
189
  return "##{flat_render(semantic_items(form.items)[1])}" if hashfn_literal?(form)
190
190
 
191
191
  "(#{form.items.map { |item| flat_render(item) }.join(' ')})"
@@ -548,13 +548,12 @@ module Kapusta
548
548
 
549
549
  def render_hanging_pairwise_vec(vec)
550
550
  pairs = vec.items.each_slice(2).to_a
551
- rendered_pairs = pairs.map do |pair|
552
- left, right = pair
553
- return nil unless pair.length == 2
551
+ return unless pairs.all? { |pair| pair.length == 2 }
554
552
 
553
+ rendered_pairs = pairs.map do |left, right|
555
554
  render_binding_pair(left, right)
556
555
  end
557
- return nil if rendered_pairs.any?(&:nil?)
556
+ return if rendered_pairs.any?(&:nil?)
558
557
 
559
558
  lines = ["[#{rendered_pairs.first}"]
560
559
  continuation = ' ' * '(let ['.length
@@ -609,7 +608,7 @@ module Kapusta
609
608
  def render_pair(left, right, indent)
610
609
  left_rendered = flat_render(left) || render(left, indent)
611
610
  right_rendered = flat_render(right) || render(right, indent)
612
- return nil unless single_line?(left_rendered) && single_line?(right_rendered)
611
+ return unless single_line?(left_rendered) && single_line?(right_rendered)
613
612
 
614
613
  pair = "#{left_rendered} #{right_rendered}"
615
614
  fits?(pair, indent) ? pair : nil
@@ -617,12 +616,12 @@ module Kapusta
617
616
 
618
617
  def render_binding_pair(left, right)
619
618
  left_rendered = flat_render(left)
620
- return nil unless left_rendered
619
+ return unless left_rendered
621
620
 
622
621
  right_rendered = render(right, '(let ['.length + left_rendered.length + 1)
623
622
  first_line, *rest = right_rendered.lines(chomp: true)
624
623
  pair = "#{left_rendered} #{first_line}"
625
- return nil unless pair.length <= MAX_WIDTH
624
+ return unless pair.length <= MAX_WIDTH
626
625
 
627
626
  return pair if rest.empty?
628
627
 
@@ -675,15 +674,6 @@ module Kapusta
675
674
  end
676
675
  end
677
676
 
678
- def fn_body(form)
679
- args = list_rest(form)
680
- if args[0].is_a?(Sym) && args[1].is_a?(Vec)
681
- args.drop(2)
682
- else
683
- args.drop(1)
684
- end
685
- end
686
-
687
677
  def consecutive_requires?(previous, current)
688
678
  require_form?(previous) && require_form?(current)
689
679
  end
@@ -834,6 +824,6 @@ module Kapusta
834
824
  puts 'Formats Kapusta source using the built-in Kapusta reader and pretty-printer.'
835
825
  end
836
826
 
837
- class Error < StandardError; end
827
+ class Error < Kapusta::Error; end
838
828
  end
839
829
  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
@@ -217,9 +221,9 @@ module Kapusta
217
221
  def parse_atom(token)
218
222
  return true if token == 'true'
219
223
  return false if token == 'false'
220
- return nil if token == 'nil'
221
- return token.to_i if token.match?(/\A-?\d+\z/)
222
- return token.to_f if token.match?(/\A-?\d+\.\d+\z/)
224
+ return if token == 'nil'
225
+ return Integer(token, 10) if token.match?(/\A-?\d+\z/)
226
+ return Float(token) if token.match?(/\A-?\d+\.\d+\z/)
223
227
 
224
228
  if token.start_with?(':') && token.length > 1
225
229
  Kapusta.kebab_to_snake(token[1..]).to_sym
@@ -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]
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Kapusta
4
- VERSION = '0.1.3'
4
+ VERSION = '0.1.5'
5
5
  end
data/lib/kapusta.rb CHANGED
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative 'kapusta/version'
4
+ require_relative 'kapusta/error'
4
5
  require_relative 'kapusta/support'
5
6
  require_relative 'kapusta/ast'
6
7
  require_relative 'kapusta/reader'
data/spec/cli_spec.rb CHANGED
@@ -26,17 +26,23 @@ ensure
26
26
  end
27
27
 
28
28
  RSpec.describe Kapusta::CLI do
29
- it 'compiles a .kap file to stdout with --compile' do
29
+ it 'compiles a .kap file to runnable Ruby with --compile' do
30
30
  path = File.expand_path('../examples/fizzbuzz.kap', __dir__)
31
31
 
32
- output = capture_stdout do
32
+ ruby = capture_stdout do
33
33
  described_class.start(['--compile', path])
34
34
  end
35
35
 
36
- expect(output).to include('__kap_print_values("FizzBuzz")')
37
- expect(output).not_to include('Kapusta::Compiler::Runtime')
38
- expect(output).not_to include('module Kapusta')
39
- expect(output).not_to include('def __kap_get_path')
36
+ Dir.mktmpdir do |dir|
37
+ output_path = File.join(dir, 'fizzbuzz.rb')
38
+ File.write(output_path, ruby)
39
+
40
+ stdout, stderr, status = Open3.capture3(RbConfig.ruby, output_path)
41
+
42
+ expected = "1\n2\nFizz\n4\nBuzz\nFizz\n7\n8\nFizz\nBuzz\n11\nFizz\n13\n14\nFizzBuzz\n16\n17\nFizz\n19\nBuzz\n"
43
+ expect(status.success?).to eq(true), stderr
44
+ expect(stdout).to eq(expected)
45
+ end
40
46
  end
41
47
 
42
48
  it 'rejects extra positional arguments in compile mode' do
@@ -50,21 +56,6 @@ RSpec.describe Kapusta::CLI do
50
56
  expect(error_output).to include('usage: kapusta')
51
57
  end
52
58
 
53
- it 'emits standalone Ruby that runs with plain ruby' do
54
- source_path = File.expand_path('../examples/fizzbuzz.kap', __dir__)
55
-
56
- Dir.mktmpdir do |dir|
57
- output_path = File.join(dir, 'fizzbuzz.rb')
58
- ruby = Kapusta.compile(File.read(source_path), path: source_path)
59
- File.write(output_path, ruby)
60
-
61
- stdout, stderr, status = Open3.capture3(RbConfig.ruby, output_path)
62
-
63
- expect(status.success?).to eq(true), stderr
64
- expect(stdout).to include("FizzBuzz\n")
65
- end
66
- end
67
-
68
59
  it 'passes remaining arguments through to the Kapusta program' do
69
60
  path = File.expand_path('../examples/greet.kap', __dir__)
70
61
 
@@ -63,6 +63,10 @@ RSpec.describe 'examples' do
63
63
  expect(run_example('counter.kap')).to eq("12\n")
64
64
  end
65
65
 
66
+ it 'contains-duplicate.kap' do
67
+ expect(run_example('contains-duplicate.kap')).to eq("true\nfalse\ntrue\n")
68
+ end
69
+
66
70
  it 'doto.kap' do
67
71
  expect(run_example('doto.kap')).to eq("1, 2, 3\n")
68
72
  end
@@ -302,6 +306,6 @@ end
302
306
  RSpec.describe 'errors' do
303
307
  it 'raises on unclosed list' do
304
308
  expect { Kapusta.eval('(fn hello [name] (.. "Hi " name "!")') }
305
- .to raise_error(/unclosed \(/)
309
+ .to raise_error(Kapusta::Reader::Error, /unclosed \(/)
306
310
  end
307
311
  end