kapusta 0.1.3 → 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.
@@ -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,13 +262,13 @@ 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
273
  when :bind
274
274
  name = pattern[1]
@@ -293,14 +293,14 @@ module Kapusta
293
293
  return false if array.length < before.length
294
294
 
295
295
  before.each_with_index do |item, i|
296
- return false unless __kap_match_pattern_into(item, array[i], bindings)
296
+ return false unless kap_match_pattern_into(item, array[i], bindings)
297
297
  end
298
- __kap_match_pattern_into(rest_pattern, array[rest_idx..], bindings)
298
+ kap_match_pattern_into(rest_pattern, array[rest_idx..], bindings)
299
299
  else
300
300
  return false unless array.length >= items.length
301
301
 
302
302
  items.each_with_index do |item, i|
303
- return false unless __kap_match_pattern_into(item, array[i], bindings)
303
+ return false unless kap_match_pattern_into(item, array[i], bindings)
304
304
  end
305
305
  true
306
306
  end
@@ -309,7 +309,7 @@ module Kapusta
309
309
 
310
310
  pattern[1].each do |key, subpattern|
311
311
  return false unless value.key?(key)
312
- return false unless __kap_match_pattern_into(subpattern, value[key], bindings)
312
+ return false unless kap_match_pattern_into(subpattern, value[key], bindings)
313
313
  end
314
314
  true
315
315
  when :lit
@@ -319,7 +319,7 @@ module Kapusta
319
319
  when :or
320
320
  pattern[1].any? do |option|
321
321
  option_bindings = bindings.dup
322
- next false unless __kap_match_pattern_into(option, value, option_bindings)
322
+ next false unless kap_match_pattern_into(option, value, option_bindings)
323
323
 
324
324
  bindings.replace(option_bindings)
325
325
  true
@@ -334,7 +334,7 @@ module Kapusta
334
334
  module_function
335
335
 
336
336
  def helper_name(name)
337
- "__kap_#{name}"
337
+ "kap_#{name}"
338
338
  end
339
339
 
340
340
  def helper_source(helpers)
@@ -366,7 +366,7 @@ module Kapusta
366
366
  helper_methods = []
367
367
 
368
368
  HELPER_SOURCES.each_key do |name|
369
- helper_method = :"__kap_#{name}"
369
+ helper_method = :"kap_#{name}"
370
370
  define_singleton_method(name, instance_method(helper_method))
371
371
  helper_methods << helper_method
372
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
@@ -675,15 +675,6 @@ module Kapusta
675
675
  end
676
676
  end
677
677
 
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
678
  def consecutive_requires?(previous, current)
688
679
  require_form?(previous) && require_form?(current)
689
680
  end
@@ -834,6 +825,6 @@ module Kapusta
834
825
  puts 'Formats Kapusta source using the built-in Kapusta reader and pretty-printer.'
835
826
  end
836
827
 
837
- class Error < StandardError; end
828
+ class Error < Kapusta::Error; end
838
829
  end
839
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]
@@ -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.4'
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
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: kapusta
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.3
4
+ version: 0.1.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Evgenii Morozov
@@ -33,6 +33,7 @@ files:
33
33
  - examples/block-sort.kap
34
34
  - examples/blocks-and-kwargs.kap
35
35
  - examples/calc.kap
36
+ - examples/contains-duplicate.kap
36
37
  - examples/counter.kap
37
38
  - examples/describe.kap
38
39
  - examples/destructure.kap
@@ -94,6 +95,7 @@ files:
94
95
  - lib/kapusta/compiler/normalizer.rb
95
96
  - lib/kapusta/compiler/runtime.rb
96
97
  - lib/kapusta/env.rb
98
+ - lib/kapusta/error.rb
97
99
  - lib/kapusta/formatter.rb
98
100
  - lib/kapusta/reader.rb
99
101
  - lib/kapusta/support.rb