kapusta 0.1.4 → 0.2.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.
@@ -18,6 +18,15 @@ ensure
18
18
  ARGV.replace(previous_argv)
19
19
  end
20
20
 
21
+ def run_ruby_example(name)
22
+ previous_stdout = $stdout
23
+ $stdout = StringIO.new
24
+ load File.join(EXAMPLES_DIR, name)
25
+ $stdout.string
26
+ ensure
27
+ $stdout = previous_stdout
28
+ end
29
+
21
30
  RSpec.describe 'examples' do
22
31
  it 'ackermann.kap' do
23
32
  expect(run_example('ackermann.kap')).to eq("9\n61\n")
@@ -32,7 +41,21 @@ RSpec.describe 'examples' do
32
41
  end
33
42
 
34
43
  it 'anonymous-greeter.kap' do
35
- expect(run_example('anonymous-greeter.kap')).to eq("Hello, anonymous!\nHello, Ada!\n")
44
+ expect(run_example('anonymous-greeter.kap')).to eq(<<~OUT)
45
+ "Hello, anonymous!"
46
+ "Hello, Ada!"
47
+ OUT
48
+ end
49
+
50
+ it 'bank-account.kap' do
51
+ expect(run_example('bank-account.kap')).to eq('')
52
+ end
53
+
54
+ it 'use_bank_account.rb' do
55
+ expect(run_ruby_example('use_bank_account.rb')).to eq(<<~OUT)
56
+ Owner: Ada
57
+ Balance: 120
58
+ OUT
36
59
  end
37
60
 
38
61
  it 'calc.kap' do
@@ -51,12 +74,17 @@ RSpec.describe 'examples' do
51
74
  path = File.expand_path('../tmp/blocks-and-kwargs.txt', EXAMPLES_DIR)
52
75
  FileUtils.rm_f(path)
53
76
 
54
- expect(run_example('blocks-and-kwargs.kap')).to eq("Ada\nLin\n2\n")
77
+ expect(run_example('blocks-and-kwargs.kap')).to eq(<<~'OUT')
78
+ "Ada\nLin"
79
+ 2
80
+ OUT
55
81
  expect(File.exist?(path)).to eq(false)
56
82
  end
57
83
 
58
84
  it 'block-sort.kap' do
59
- expect(run_example('block-sort.kap')).to eq("3, 2, 1\n")
85
+ expect(run_example('block-sort.kap')).to eq(<<~OUT)
86
+ "3, 2, 1"
87
+ OUT
60
88
  end
61
89
 
62
90
  it 'counter.kap' do
@@ -67,16 +95,59 @@ RSpec.describe 'examples' do
67
95
  expect(run_example('contains-duplicate.kap')).to eq("true\nfalse\ntrue\n")
68
96
  end
69
97
 
98
+ it 'climbing-stairs.kap' do
99
+ expect(run_example('climbing-stairs.kap')).to eq("2\n3\n8\n89\n")
100
+ end
101
+
102
+ it 'maximum-subarray.kap' do
103
+ expect(run_example('maximum-subarray.kap')).to eq("6\n1\n23\n")
104
+ end
105
+
106
+ it 'happy-number.kap' do
107
+ expect(run_example('happy-number.kap')).to eq("true\nfalse\ntrue\n")
108
+ end
109
+
110
+ it 'move-zeroes.kap' do
111
+ expect(run_example('move-zeroes.kap')).to eq(<<~OUT)
112
+ [1, 3, 12, 0, 0]
113
+ [0]
114
+ [1, 2, 3]
115
+ OUT
116
+ end
117
+
70
118
  it 'doto.kap' do
71
- expect(run_example('doto.kap')).to eq("1, 2, 3\n")
119
+ expect(run_example('doto.kap')).to eq(<<~OUT)
120
+ "1, 2, 3"
121
+ OUT
122
+ end
123
+
124
+ it 'doto-hygiene.kap' do
125
+ expect(run_example('doto-hygiene.kap')).to eq(<<~OUT)
126
+ "[99]"
127
+ OUT
72
128
  end
73
129
 
74
130
  it 'describe.kap' do
75
- expect(run_example('describe.kap')).to eq("-3\tnegative\n0\tzero\n1\tone\n2\tmany\n99\tmany\n")
131
+ expect(run_example('describe.kap')).to eq(<<~OUT)
132
+ -3
133
+ "negative"
134
+ 0
135
+ "zero"
136
+ 1
137
+ "one"
138
+ 2
139
+ "many"
140
+ 99
141
+ "many"
142
+ OUT
76
143
  end
77
144
 
78
145
  it 'destructure.kap' do
79
- expect(run_example('destructure.kap')).to eq("6\nAda\t36\n")
146
+ expect(run_example('destructure.kap')).to eq(<<~OUT)
147
+ 6
148
+ "Ada"
149
+ 36
150
+ OUT
80
151
  end
81
152
 
82
153
  it 'egg-count.kap' do
@@ -84,22 +155,43 @@ RSpec.describe 'examples' do
84
155
  end
85
156
 
86
157
  it 'even-squares.kap' do
87
- expect(run_example('even-squares.kap')).to eq("4, 16, 36\n")
158
+ expect(run_example('even-squares.kap')).to eq(<<~OUT)
159
+ "4, 16, 36"
160
+ OUT
88
161
  end
89
162
 
90
163
  it 'exceptions.kap' do
91
- expect(run_example('exceptions.kap')).to eq("seen: 12\n12\nseen: oops\nbad: oops\n")
164
+ expect(run_example('exceptions.kap')).to eq(<<~OUT)
165
+ "seen: 12"
166
+ 12
167
+ "seen: oops"
168
+ "bad: oops"
169
+ OUT
92
170
  end
93
171
 
94
172
  it 'factorial.kap' do
95
- expect(run_example('factorial.kap')).to eq("0\t1\n1\t1\n5\t120\n6\t720\n10\t3628800\n")
173
+ expect(run_example('factorial.kap')).to eq(<<~OUT)
174
+ 0
175
+ 1
176
+ 1
177
+ 1
178
+ 5
179
+ 120
180
+ 6
181
+ 720
182
+ 10
183
+ 3628800
184
+ OUT
96
185
  end
97
186
 
98
187
  it 'files.kap' do
99
188
  path = File.expand_path('../tmp/file-io-example.txt', EXAMPLES_DIR)
100
189
  FileUtils.rm_f(path)
101
190
 
102
- expect(run_example('files.kap')).to eq("Ada\nLin\n2\n")
191
+ expect(run_example('files.kap')).to eq(<<~'OUT')
192
+ "Ada\nLin"
193
+ 2
194
+ OUT
103
195
  expect(File.exist?(path)).to eq(false)
104
196
  end
105
197
 
@@ -108,7 +200,28 @@ RSpec.describe 'examples' do
108
200
  end
109
201
 
110
202
  it 'fizzbuzz.kap' do
111
- expected = "1\n2\nFizz\n4\nBuzz\nFizz\n7\n8\nFizz\nBuzz\n11\nFizz\n13\n14\nFizzBuzz\n16\n17\nFizz\n19\nBuzz\n"
203
+ expected = <<~OUT
204
+ 1
205
+ 2
206
+ "Fizz"
207
+ 4
208
+ "Buzz"
209
+ "Fizz"
210
+ 7
211
+ 8
212
+ "Fizz"
213
+ "Buzz"
214
+ 11
215
+ "Fizz"
216
+ 13
217
+ 14
218
+ "FizzBuzz"
219
+ 16
220
+ 17
221
+ "Fizz"
222
+ 19
223
+ "Buzz"
224
+ OUT
112
225
  expect(run_example('fizzbuzz.kap')).to eq(expected)
113
226
  end
114
227
 
@@ -117,7 +230,9 @@ RSpec.describe 'examples' do
117
230
  end
118
231
 
119
232
  it 'greet.kap' do
120
- expect(run_example('greet.kap', argv: ['Ada'])).to eq("Hello, Ada!\n")
233
+ expect(run_example('greet.kap', argv: ['Ada'])).to eq(<<~OUT)
234
+ "Hello, Ada!"
235
+ OUT
121
236
  end
122
237
 
123
238
  it 'hashfn.kap' do
@@ -125,27 +240,49 @@ RSpec.describe 'examples' do
125
240
  end
126
241
 
127
242
  it 'inheritance.kap' do
128
- expect(run_example('inheritance.kap')).to eq("true\tanimalia\tPoppy the dog\twoof\n")
243
+ expect(run_example('inheritance.kap')).to eq(<<~OUT)
244
+ true
245
+ "animalia"
246
+ "Poppy the dog"
247
+ "woof"
248
+ OUT
129
249
  end
130
250
 
131
251
  it 'leap-year.kap' do
132
252
  expect(run_example('leap-year.kap')).to eq("true\n")
133
253
  end
134
254
 
255
+ it 'length-of-last-word.kap' do
256
+ expect(run_example('length-of-last-word.kap')).to eq("5\n4\n6\n")
257
+ end
258
+
135
259
  it 'min-max.kap' do
136
- expect(run_example('min-max.kap')).to eq("1\t9\n")
260
+ expect(run_example('min-max.kap')).to eq(<<~OUT)
261
+ 1
262
+ 9
263
+ OUT
137
264
  end
138
265
 
139
266
  it 'module-header.kap' do
140
- expect(run_example('module-header.kap')).to eq("Hello, Ada!\n")
267
+ expect(run_example('module-header.kap')).to eq(<<~OUT)
268
+ "Hello, Ada!"
269
+ OUT
141
270
  end
142
271
 
143
272
  it 'pipeline.kap' do
144
- expect(run_example('pipeline.kap')).to eq("BLUE\nRED\n")
273
+ expect(run_example('pipeline.kap')).to eq(<<~OUT)
274
+ "BLUE"
275
+ "RED"
276
+ OUT
145
277
  end
146
278
 
147
279
  it 'points.kap' do
148
- expect(run_example('points.kap')).to eq("origin\ny-axis\nx-axis\npoint\n")
280
+ expect(run_example('points.kap')).to eq(<<~OUT)
281
+ "origin"
282
+ "y-axis"
283
+ "x-axis"
284
+ "point"
285
+ OUT
149
286
  end
150
287
 
151
288
  it 'primes.kap' do
@@ -153,44 +290,74 @@ RSpec.describe 'examples' do
153
290
  end
154
291
 
155
292
  it 'raindrops.kap' do
156
- expect(run_example('raindrops.kap')).to eq("PlingPlang\n")
293
+ expect(run_example('raindrops.kap')).to eq(<<~OUT)
294
+ "PlingPlang"
295
+ OUT
157
296
  end
158
297
 
159
298
  it 'record.kap' do
160
- expect(run_example('record.kap')).to eq("Ada / engineer / ruby, lisp\n")
299
+ expect(run_example('record.kap')).to eq(<<~OUT)
300
+ "Ada / engineer / ruby, lisp"
301
+ OUT
161
302
  end
162
303
 
163
304
  it 'regex.kap' do
164
- expected = <<~OUT
165
- 2026-04-23 -> {"year"=>"2026", "month"=>"04", "day"=>"23"}
166
- hello -> nil
167
- 1999-12-31 -> {"year"=>"1999", "month"=>"12", "day"=>"31"}
168
- OUT
305
+ first = { 'year' => '2026', 'month' => '04', 'day' => '23' }
306
+ last = { 'year' => '1999', 'month' => '12', 'day' => '31' }
307
+ expected = [
308
+ "2026-04-23 -> #{first}".inspect,
309
+ 'hello -> '.inspect,
310
+ "1999-12-31 -> #{last}".inspect
311
+ ].map { |line| "#{line}\n" }.join
169
312
  expect(run_example('regex.kap')).to eq(expected)
170
313
  end
171
314
 
172
315
  it 'ruby-eval.kap' do
173
- expect(run_example('ruby-eval.kap')).to eq("10-20-30\n")
316
+ expect(run_example('ruby-eval.kap')).to eq(<<~OUT)
317
+ "10-20-30"
318
+ OUT
174
319
  end
175
320
 
176
321
  it 'kwargs.kap' do
177
- expect(run_example('kwargs.kap')).to eq("Ada has 3 tasks\n")
322
+ expect(run_example('kwargs.kap')).to eq(<<~OUT)
323
+ "Ada has 3 tasks"
324
+ OUT
178
325
  end
179
326
 
180
327
  it 'match.kap' do
181
- expect(run_example('match.kap')).to eq("Ada: 9\nLin: no score\nunknown\n")
328
+ expect(run_example('match.kap')).to eq(<<~OUT)
329
+ "Ada: 9"
330
+ "Lin: no score"
331
+ "unknown"
332
+ OUT
182
333
  end
183
334
 
184
335
  it 'packet-router.kap' do
185
- expect(run_example('packet-router.kap')).to eq("score:9\nother\ncity:nil\n5\n0\nping:7\n")
336
+ expect(run_example('packet-router.kap')).to eq(<<~OUT)
337
+ "score:9"
338
+ "other"
339
+ "city:nil"
340
+ 5
341
+ 0
342
+ "ping:7"
343
+ OUT
186
344
  end
187
345
 
188
346
  it 'or-patterns.kap' do
189
- expect(run_example('or-patterns.kap')).to eq("1:2\n2:1\nother\n")
347
+ expect(run_example('or-patterns.kap')).to eq(<<~OUT)
348
+ "1:2"
349
+ "2:1"
350
+ "other"
351
+ OUT
190
352
  end
191
353
 
192
354
  it 'underscore-patterns.kap' do
193
- expect(run_example('underscore-patterns.kap')).to eq("5\nnil\n5\nfallback\n")
355
+ expect(run_example('underscore-patterns.kap')).to eq(<<~OUT)
356
+ 5
357
+ nil
358
+ 5
359
+ "fallback"
360
+ OUT
194
361
  end
195
362
 
196
363
  it 'scopes.kap' do
@@ -198,13 +365,13 @@ RSpec.describe 'examples' do
198
365
  end
199
366
 
200
367
  it 'pcall.kap' do
201
- expected = <<~OUT
368
+ expected = <<~'OUT'
202
369
  true
203
370
  12
204
371
  false
205
372
  ArgumentError
206
373
  false
207
- invalid value for Integer(): "oops"
374
+ "invalid value for Integer(): \"oops\""
208
375
  OUT
209
376
  expect(run_example('pcall.kap')).to eq(expected)
210
377
  end
@@ -218,7 +385,10 @@ RSpec.describe 'examples' do
218
385
  end
219
386
 
220
387
  it 'safe-lookup.kap' do
221
- expect(run_example('safe-lookup.kap')).to eq("Ada\nnil\n")
388
+ expect(run_example('safe-lookup.kap')).to eq(<<~OUT)
389
+ "Ada"
390
+ nil
391
+ OUT
222
392
  end
223
393
 
224
394
  it 'shapes.kap' do
@@ -230,7 +400,12 @@ RSpec.describe 'examples' do
230
400
  end
231
401
 
232
402
  it 'stack.kap' do
233
- expect(run_example('stack.kap')).to eq('')
403
+ expect(run_example('stack.kap')).to eq(<<~OUT)
404
+ -3
405
+ 0
406
+ -2
407
+ true
408
+ OUT
234
409
  end
235
410
 
236
411
  it 'sum.kap' do
@@ -238,74 +413,46 @@ RSpec.describe 'examples' do
238
413
  end
239
414
 
240
415
  it 'tset.kap' do
241
- expect(run_example('tset.kap')).to eq("{:name=>\"Ada\", :city=>\"Amsterdam\"}\nAmsterdam\n")
416
+ person = { name: 'Ada', city: 'Amsterdam' }
417
+ expect(run_example('tset.kap')).to eq("#{person.inspect}\n#{'Amsterdam'.inspect}\n")
242
418
  end
243
419
 
244
420
  it 'two-sum.kap' do
245
421
  expect(run_example('two-sum.kap')).to eq("[0, 1]\n[1, 2]\nnil\n")
246
422
  end
247
423
 
248
- it 'threading.kap' do
249
- expect(run_example('threading.kap')).to eq("[Ada Lovelace]!\t<Ada!>\tnil\tATSUPAK\tnil\n")
250
- end
251
-
252
- it 'tic-tac-toe.kap' do
253
- expect(run_example('tic-tac-toe.kap')).to eq("X\nO\nX\ndraw\n")
424
+ it 'two-sum-hash.kap' do
425
+ expect(run_example('two-sum-hash.kap')).to eq("[0, 1]\n[1, 2]\nnil\n")
254
426
  end
255
- end
256
427
 
257
- RSpec.describe Kapusta do
258
- it 'exposes a gem version' do
259
- expect(Kapusta::VERSION).to match(/\A\d+\.\d+\.\d+\z/)
428
+ it 'baseball-game.kap' do
429
+ expect(run_example('baseball-game.kap')).to eq("30\n27\n")
260
430
  end
261
431
 
262
- it 'defaults classes to Object when the superclass is omitted' do
263
- source = <<~KAP
264
- (let [klass (class Stack
265
- (fn initialize []
266
- nil))]
267
- (values (= Stack.superclass Object)
268
- (= (Stack.new.class) Stack)
269
- (= klass Stack)))
270
- KAP
271
-
272
- expect(Kapusta.eval(source)).to eq([true, true, true])
432
+ it 'valid-parentheses-1.kap' do
433
+ expect(run_example('valid-parentheses-1.kap')).to eq('')
273
434
  end
274
435
 
275
- it 'still accepts an explicit superclass vector' do
276
- source = <<~KAP
277
- (let [klass (class KapustaError [StandardError])]
278
- (values (= KapustaError.superclass StandardError)
279
- (= klass KapustaError)))
280
- KAP
281
-
282
- expect(Kapusta.eval(source)).to eq([true, true])
436
+ it 'valid-parentheses-2.kap' do
437
+ expect(run_example('valid-parentheses-2.kap')).to eq("true\ntrue\ntrue\nfalse\nfalse\n")
283
438
  end
284
439
 
285
- it 'preserves nested arithmetic precedence' do
286
- source = <<~KAP
287
- (values (/ (+ 3 5) 2)
288
- (* (+ 1 2) (- 10 4))
289
- (% (+ 10 5) 4))
290
- KAP
291
-
292
- expect(Kapusta.eval(source)).to eq([4, 18, 3])
293
- end
294
-
295
- it 'supports postfix zero-arg method calls on non-symbol expressions' do
296
- source = <<~KAP
297
- (values [1 2].inspect
298
- (+ 1 2).inspect
299
- "Listen".downcase.chars.sort.join)
300
- KAP
301
-
302
- expect(Kapusta.eval(source)).to eq(['[1, 2]', '3', 'eilnst'])
440
+ it 'threading.kap' do
441
+ expect(run_example('threading.kap')).to eq(<<~OUT)
442
+ "[Ada Lovelace]!"
443
+ "<Ada!>"
444
+ nil
445
+ "ATSUPAK"
446
+ nil
447
+ OUT
303
448
  end
304
- end
305
449
 
306
- RSpec.describe 'errors' do
307
- it 'raises on unclosed list' do
308
- expect { Kapusta.eval('(fn hello [name] (.. "Hi " name "!")') }
309
- .to raise_error(Kapusta::Reader::Error, /unclosed \(/)
450
+ it 'tic-tac-toe.kap' do
451
+ expect(run_example('tic-tac-toe.kap')).to eq(<<~OUT)
452
+ "X"
453
+ "O"
454
+ "X"
455
+ "draw"
456
+ OUT
310
457
  end
311
458
  end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ RSpec.describe Kapusta::Reader do
6
+ describe 'error messages' do
7
+ it 'reports unexpected closing delimiters with their source position' do
8
+ expect { Kapusta.eval('(print 1))') }
9
+ .to raise_error(Kapusta::Reader::Error,
10
+ /unexpected closing delimiter '\)' at line 1, column 10/)
11
+ end
12
+
13
+ it 'reports unclosed opening delimiters with their source position' do
14
+ cases = {
15
+ '(print 1' => /unclosed opening delimiter '\(' at line 1, column 1/,
16
+ '[1 2' => /unclosed opening delimiter '\[' at line 1, column 1/,
17
+ '{:name "A"' => /unclosed opening delimiter '\{' at line 1, column 1/
18
+ }
19
+
20
+ cases.each do |source, message|
21
+ expect { Kapusta.eval(source) }
22
+ .to raise_error(Kapusta::Reader::Error, message)
23
+ end
24
+ end
25
+ end
26
+ end
metadata CHANGED
@@ -1,17 +1,15 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: kapusta
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.4
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Evgenii Morozov
8
- autorequire:
9
8
  bindir: exe
10
9
  cert_chain: []
11
- date: 2026-04-24 00:00:00.000000000 Z
10
+ date: 1980-01-02 00:00:00.000000000 Z
12
11
  dependencies: []
13
12
  description: Kapusta is a Lisp for the Ruby runtime.
14
- email:
15
13
  executables:
16
14
  - kapfmt
17
15
  - kapusta
@@ -22,21 +20,26 @@ files:
22
20
  - Gemfile
23
21
  - README.md
24
22
  - Rakefile
23
+ - bin/compile-examples
25
24
  - bin/console
26
25
  - bin/setup
27
26
  - examples/accumulator.kap
28
27
  - examples/ackermann.kap
29
28
  - examples/anagram.kap
30
29
  - examples/anonymous-greeter.kap
30
+ - examples/bank-account.kap
31
+ - examples/baseball-game.kap
31
32
  - examples/binary-search.kap
32
33
  - examples/binary-to-decimal.kap
33
34
  - examples/block-sort.kap
34
35
  - examples/blocks-and-kwargs.kap
35
36
  - examples/calc.kap
37
+ - examples/climbing-stairs.kap
36
38
  - examples/contains-duplicate.kap
37
39
  - examples/counter.kap
38
40
  - examples/describe.kap
39
41
  - examples/destructure.kap
42
+ - examples/doto-hygiene.kap
40
43
  - examples/doto.kap
41
44
  - examples/egg-count.kap
42
45
  - examples/even-squares.kap
@@ -47,13 +50,17 @@ files:
47
50
  - examples/fizzbuzz.kap
48
51
  - examples/gcd.kap
49
52
  - examples/greet.kap
53
+ - examples/happy-number.kap
50
54
  - examples/hashfn.kap
51
55
  - examples/inheritance.kap
52
56
  - examples/kwargs.kap
53
57
  - examples/leap-year.kap
58
+ - examples/length-of-last-word.kap
54
59
  - examples/match.kap
60
+ - examples/maximum-subarray.kap
55
61
  - examples/min-max.kap
56
62
  - examples/module-header.kap
63
+ - examples/move-zeroes.kap
57
64
  - examples/or-patterns.kap
58
65
  - examples/packet-router.kap
59
66
  - examples/palindrome.kap
@@ -75,8 +82,12 @@ files:
75
82
  - examples/threading.kap
76
83
  - examples/tic-tac-toe.kap
77
84
  - examples/tset.kap
85
+ - examples/two-sum-hash.kap
78
86
  - examples/two-sum.kap
79
87
  - examples/underscore-patterns.kap
88
+ - examples/use_bank_account.rb
89
+ - examples/valid-parentheses-1.kap
90
+ - examples/valid-parentheses-2.kap
80
91
  - exe/kapfmt
81
92
  - exe/kapusta
82
93
  - kapusta.gemspec
@@ -103,6 +114,7 @@ files:
103
114
  - spec/cli_spec.rb
104
115
  - spec/examples_spec.rb
105
116
  - spec/formatter_spec.rb
117
+ - spec/reader_spec.rb
106
118
  - spec/spec_helper.rb
107
119
  homepage: https://github.com/evmorov/kapusta
108
120
  licenses: []
@@ -111,7 +123,6 @@ metadata:
111
123
  homepage_uri: https://github.com/evmorov/kapusta
112
124
  source_code_uri: https://github.com/evmorov/kapusta
113
125
  bug_tracker_uri: https://github.com/evmorov/kapusta/issues
114
- post_install_message:
115
126
  rdoc_options: []
116
127
  require_paths:
117
128
  - lib
@@ -126,8 +137,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
126
137
  - !ruby/object:Gem::Version
127
138
  version: '0'
128
139
  requirements: []
129
- rubygems_version: 3.5.22
130
- signing_key:
140
+ rubygems_version: 3.6.9
131
141
  specification_version: 4
132
142
  summary: A Lisp for the Ruby runtime
133
143
  test_files: []