kapusta 0.1.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 +7 -0
- data/.rspec +2 -0
- data/Gemfile +10 -0
- data/README.md +58 -0
- data/Rakefile +10 -0
- data/bin/console +8 -0
- data/bin/setup +4 -0
- data/examples/accumulator.kap +16 -0
- data/examples/ackermann.kap +7 -0
- data/examples/anagram.kap +13 -0
- data/examples/binary-search.kap +16 -0
- data/examples/block-sort.kap +3 -0
- data/examples/calc.kap +10 -0
- data/examples/counter.kap +19 -0
- data/examples/describe.kap +9 -0
- data/examples/destructure.kap +4 -0
- data/examples/doto.kap +2 -0
- data/examples/egg-count.kap +10 -0
- data/examples/even-squares.kap +7 -0
- data/examples/exceptions.kap +14 -0
- data/examples/factorial.kap +8 -0
- data/examples/fib.kap +4 -0
- data/examples/fizzbuzz.kap +7 -0
- data/examples/gcd.kap +5 -0
- data/examples/greet.kap +2 -0
- data/examples/hashfn.kap +4 -0
- data/examples/kwargs.kap +1 -0
- data/examples/leap-year.kap +5 -0
- data/examples/match.kap +9 -0
- data/examples/min-max.kap +11 -0
- data/examples/module-header.kap +6 -0
- data/examples/palindrome.kap +8 -0
- data/examples/pangram.kap +9 -0
- data/examples/pcall.kap +9 -0
- data/examples/pipeline.kap +6 -0
- data/examples/points.kap +9 -0
- data/examples/primes.kap +8 -0
- data/examples/raindrops.kap +13 -0
- data/examples/record.kap +6 -0
- data/examples/regex.kap +9 -0
- data/examples/ruby-eval.kap +1 -0
- data/examples/safe-lookup.kap +6 -0
- data/examples/scopes.kap +18 -0
- data/examples/shapes.kap +9 -0
- data/examples/squares.kap +3 -0
- data/examples/stack.kap +19 -0
- data/examples/sum.kap +3 -0
- data/examples/tset.kap +4 -0
- data/examples/two-sum.kap +17 -0
- data/exe/kapfmt +6 -0
- data/exe/kapusta +6 -0
- data/kapfmt +4 -0
- data/kapusta.gemspec +25 -0
- data/lib/kapusta/ast.rb +76 -0
- data/lib/kapusta/cli.rb +61 -0
- data/lib/kapusta/compiler/emitter/bindings.rb +178 -0
- data/lib/kapusta/compiler/emitter/collections.rb +245 -0
- data/lib/kapusta/compiler/emitter/control_flow.rb +168 -0
- data/lib/kapusta/compiler/emitter/expressions.rb +107 -0
- data/lib/kapusta/compiler/emitter/interop.rb +277 -0
- data/lib/kapusta/compiler/emitter/patterns.rb +105 -0
- data/lib/kapusta/compiler/emitter/support.rb +169 -0
- data/lib/kapusta/compiler/emitter.rb +45 -0
- data/lib/kapusta/compiler/normalizer.rb +122 -0
- data/lib/kapusta/compiler/runtime.rb +583 -0
- data/lib/kapusta/compiler.rb +47 -0
- data/lib/kapusta/env.rb +42 -0
- data/lib/kapusta/formatter.rb +685 -0
- data/lib/kapusta/reader.rb +215 -0
- data/lib/kapusta/support.rb +7 -0
- data/lib/kapusta/version.rb +5 -0
- data/lib/kapusta.rb +30 -0
- data/spec/cli_spec.rb +77 -0
- data/spec/examples_spec.rb +258 -0
- data/spec/formatter_spec.rb +176 -0
- data/spec/spec_helper.rb +12 -0
- metadata +119 -0
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Kapusta
|
|
4
|
+
class Reader
|
|
5
|
+
WHITESPACE = [' ', "\t", "\n", "\r", "\f", "\v", ','].freeze
|
|
6
|
+
DELIMS = ['(', ')', '[', ']', '{', '}', '"', ';'].freeze
|
|
7
|
+
|
|
8
|
+
def self.read_all(source)
|
|
9
|
+
new(source).read_all
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def initialize(source)
|
|
13
|
+
@src = source
|
|
14
|
+
@pos = 0
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def read_all
|
|
18
|
+
forms = []
|
|
19
|
+
loop do
|
|
20
|
+
skip_ws
|
|
21
|
+
break if eof?
|
|
22
|
+
|
|
23
|
+
forms << read_form
|
|
24
|
+
end
|
|
25
|
+
forms
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
private
|
|
29
|
+
|
|
30
|
+
def eof?
|
|
31
|
+
@pos >= @src.length
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def peek
|
|
35
|
+
@src[@pos]
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def advance
|
|
39
|
+
char = @src[@pos]
|
|
40
|
+
@pos += 1
|
|
41
|
+
char
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def skip_ws
|
|
45
|
+
until eof?
|
|
46
|
+
char = peek
|
|
47
|
+
if WHITESPACE.include?(char)
|
|
48
|
+
advance
|
|
49
|
+
elsif char == ';'
|
|
50
|
+
advance until eof? || peek == "\n"
|
|
51
|
+
else
|
|
52
|
+
break
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def delim?(char)
|
|
58
|
+
char.nil? || WHITESPACE.include?(char) || DELIMS.include?(char)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def read_form
|
|
62
|
+
skip_ws
|
|
63
|
+
raise 'unexpected eof' if eof?
|
|
64
|
+
|
|
65
|
+
form =
|
|
66
|
+
case peek
|
|
67
|
+
when '(' then read_list
|
|
68
|
+
when '[' then read_vec
|
|
69
|
+
when '{' then read_hash
|
|
70
|
+
when '"' then read_string
|
|
71
|
+
when '#' then read_hashfn
|
|
72
|
+
else
|
|
73
|
+
read_atom
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
read_postfix(form)
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def read_list
|
|
80
|
+
advance
|
|
81
|
+
items = []
|
|
82
|
+
loop do
|
|
83
|
+
skip_ws
|
|
84
|
+
raise 'unclosed (' if eof?
|
|
85
|
+
break if peek == ')'
|
|
86
|
+
|
|
87
|
+
items << read_form
|
|
88
|
+
end
|
|
89
|
+
advance
|
|
90
|
+
List.new(items)
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def read_vec
|
|
94
|
+
advance
|
|
95
|
+
items = []
|
|
96
|
+
loop do
|
|
97
|
+
skip_ws
|
|
98
|
+
raise 'unclosed [' if eof?
|
|
99
|
+
break if peek == ']'
|
|
100
|
+
|
|
101
|
+
items << read_form
|
|
102
|
+
end
|
|
103
|
+
advance
|
|
104
|
+
Vec.new(items)
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def read_hash
|
|
108
|
+
advance
|
|
109
|
+
items = []
|
|
110
|
+
loop do
|
|
111
|
+
skip_ws
|
|
112
|
+
raise 'unclosed {' if eof?
|
|
113
|
+
break if peek == '}'
|
|
114
|
+
|
|
115
|
+
items << read_form
|
|
116
|
+
end
|
|
117
|
+
advance
|
|
118
|
+
|
|
119
|
+
pairs = []
|
|
120
|
+
i = 0
|
|
121
|
+
while i < items.length
|
|
122
|
+
item = items[i]
|
|
123
|
+
if item.is_a?(Sym) && item.name == ':'
|
|
124
|
+
sym = items[i + 1]
|
|
125
|
+
raise 'bad shorthand' unless sym.is_a?(Sym)
|
|
126
|
+
|
|
127
|
+
key = Kapusta.kebab_to_snake(sym.name).to_sym
|
|
128
|
+
pairs << [key, sym]
|
|
129
|
+
else
|
|
130
|
+
pairs << [item, items[i + 1]]
|
|
131
|
+
end
|
|
132
|
+
i += 2
|
|
133
|
+
end
|
|
134
|
+
HashLit.new(pairs)
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
def read_string
|
|
138
|
+
advance
|
|
139
|
+
buffer = +''
|
|
140
|
+
until eof? || peek == '"'
|
|
141
|
+
if peek == '\\'
|
|
142
|
+
advance
|
|
143
|
+
escaped = advance
|
|
144
|
+
buffer << case escaped
|
|
145
|
+
when 'n' then "\n"
|
|
146
|
+
when 't' then "\t"
|
|
147
|
+
when 'r' then "\r"
|
|
148
|
+
when '\\' then '\\'
|
|
149
|
+
when '"' then '"'
|
|
150
|
+
when '0' then "\0"
|
|
151
|
+
when 'a' then "\a"
|
|
152
|
+
when 'b' then "\b"
|
|
153
|
+
when 'f' then "\f"
|
|
154
|
+
when 'v' then "\v"
|
|
155
|
+
else escaped
|
|
156
|
+
end
|
|
157
|
+
else
|
|
158
|
+
buffer << advance
|
|
159
|
+
end
|
|
160
|
+
end
|
|
161
|
+
raise 'unterminated string' if eof?
|
|
162
|
+
|
|
163
|
+
advance
|
|
164
|
+
buffer
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
def read_hashfn
|
|
168
|
+
advance
|
|
169
|
+
form = read_form
|
|
170
|
+
List.new([Sym.new('hashfn'), form])
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
def read_postfix(form)
|
|
174
|
+
current = form
|
|
175
|
+
|
|
176
|
+
loop do
|
|
177
|
+
break unless peek == '.'
|
|
178
|
+
|
|
179
|
+
start = @pos
|
|
180
|
+
advance until delim?(peek)
|
|
181
|
+
token = @src[start...@pos]
|
|
182
|
+
break unless token.start_with?('.') && token.length > 1
|
|
183
|
+
|
|
184
|
+
token[1..].split('.').each do |name|
|
|
185
|
+
current = List.new([Sym.new(':'), current, Kapusta.kebab_to_snake(name).to_sym])
|
|
186
|
+
end
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
current
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
def read_atom
|
|
193
|
+
start = @pos
|
|
194
|
+
advance until delim?(peek)
|
|
195
|
+
token = @src[start...@pos]
|
|
196
|
+
raise 'empty token' if token.empty?
|
|
197
|
+
|
|
198
|
+
parse_atom(token)
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
def parse_atom(token)
|
|
202
|
+
return true if token == 'true'
|
|
203
|
+
return false if token == 'false'
|
|
204
|
+
return nil if token == 'nil'
|
|
205
|
+
return token.to_i if token.match?(/\A-?\d+\z/)
|
|
206
|
+
return token.to_f if token.match?(/\A-?\d+\.\d+\z/)
|
|
207
|
+
|
|
208
|
+
if token.start_with?(':') && token.length > 1
|
|
209
|
+
Kapusta.kebab_to_snake(token[1..]).to_sym
|
|
210
|
+
else
|
|
211
|
+
Sym.new(token)
|
|
212
|
+
end
|
|
213
|
+
end
|
|
214
|
+
end
|
|
215
|
+
end
|
data/lib/kapusta.rb
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'kapusta/version'
|
|
4
|
+
require_relative 'kapusta/support'
|
|
5
|
+
require_relative 'kapusta/ast'
|
|
6
|
+
require_relative 'kapusta/reader'
|
|
7
|
+
require_relative 'kapusta/env'
|
|
8
|
+
require_relative 'kapusta/compiler'
|
|
9
|
+
|
|
10
|
+
module Kapusta
|
|
11
|
+
def self.eval(source, path: '(eval)', **_opts)
|
|
12
|
+
Compiler.run(source, path:)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def self.dofile(path, **_opts)
|
|
16
|
+
source = File.read(path)
|
|
17
|
+
self.eval(source, path:)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def self.compile(source, path: '(eval)', **_opts)
|
|
21
|
+
Compiler.compile(source, path:)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def self.install!
|
|
25
|
+
@install ||= begin
|
|
26
|
+
require 'rubygems'
|
|
27
|
+
true
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
data/spec/cli_spec.rb
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'spec_helper'
|
|
4
|
+
require 'kapusta/cli'
|
|
5
|
+
require 'open3'
|
|
6
|
+
require 'rbconfig'
|
|
7
|
+
require 'stringio'
|
|
8
|
+
require 'tmpdir'
|
|
9
|
+
|
|
10
|
+
def capture_stdout
|
|
11
|
+
previous_stdout = $stdout
|
|
12
|
+
$stdout = StringIO.new
|
|
13
|
+
yield
|
|
14
|
+
$stdout.string
|
|
15
|
+
ensure
|
|
16
|
+
$stdout = previous_stdout
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def capture_stderr
|
|
20
|
+
previous_stderr = $stderr
|
|
21
|
+
$stderr = StringIO.new
|
|
22
|
+
yield
|
|
23
|
+
$stderr.string
|
|
24
|
+
ensure
|
|
25
|
+
$stderr = previous_stderr
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
RSpec.describe Kapusta::CLI do
|
|
29
|
+
it 'compiles a .kap file to stdout with --compile' do
|
|
30
|
+
path = File.expand_path('../examples/fizzbuzz.kap', __dir__)
|
|
31
|
+
|
|
32
|
+
output = capture_stdout do
|
|
33
|
+
described_class.start(['--compile', path])
|
|
34
|
+
end
|
|
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')
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
it 'rejects extra positional arguments in compile mode' do
|
|
43
|
+
path = File.expand_path('../examples/fizzbuzz.kap', __dir__)
|
|
44
|
+
|
|
45
|
+
error_output = capture_stderr do
|
|
46
|
+
expect { described_class.start(['--compile', path, 'fizzbuzz.rb']) }
|
|
47
|
+
.to raise_error(SystemExit)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
expect(error_output).to include('usage: kapusta')
|
|
51
|
+
end
|
|
52
|
+
|
|
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
|
+
it 'passes remaining arguments through to the Kapusta program' do
|
|
69
|
+
path = File.expand_path('../examples/greet.kap', __dir__)
|
|
70
|
+
|
|
71
|
+
output = capture_stdout do
|
|
72
|
+
described_class.start([path, 'Ada'])
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
expect(output).to eq("Hello, Ada!\n")
|
|
76
|
+
end
|
|
77
|
+
end
|
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'spec_helper'
|
|
4
|
+
require 'stringio'
|
|
5
|
+
|
|
6
|
+
EXAMPLES_DIR = File.expand_path('../examples', __dir__)
|
|
7
|
+
|
|
8
|
+
def run_example(name, argv: [])
|
|
9
|
+
previous_argv = ARGV.dup
|
|
10
|
+
previous_stdout = $stdout
|
|
11
|
+
ARGV.replace(argv)
|
|
12
|
+
$stdout = StringIO.new
|
|
13
|
+
Kapusta.dofile(File.join(EXAMPLES_DIR, name))
|
|
14
|
+
$stdout.string
|
|
15
|
+
ensure
|
|
16
|
+
$stdout = previous_stdout
|
|
17
|
+
ARGV.replace(previous_argv)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
RSpec.describe 'examples' do
|
|
21
|
+
it 'ackermann.kap' do
|
|
22
|
+
expect(run_example('ackermann.kap')).to eq("9\n61\n")
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
it 'accumulator.kap' do
|
|
26
|
+
expect(run_example('accumulator.kap')).to eq("22\n")
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
it 'anagram.kap' do
|
|
30
|
+
expect(run_example('anagram.kap')).to eq("true\ntrue\nfalse\n")
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
it 'calc.kap' do
|
|
34
|
+
expect(run_example('calc.kap')).to eq("14\n")
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
it 'binary-search.kap' do
|
|
38
|
+
expect(run_example('binary-search.kap')).to eq("3\nnil\n")
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
it 'block-sort.kap' do
|
|
42
|
+
expect(run_example('block-sort.kap')).to eq("3, 2, 1\n")
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
it 'counter.kap' do
|
|
46
|
+
expect(run_example('counter.kap')).to eq("12\n")
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
it 'doto.kap' do
|
|
50
|
+
expect(run_example('doto.kap')).to eq("1, 2, 3\n")
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
it 'describe.kap' do
|
|
54
|
+
expect(run_example('describe.kap')).to eq("-3\tnegative\n0\tzero\n1\tone\n2\tmany\n99\tmany\n")
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
it 'destructure.kap' do
|
|
58
|
+
expect(run_example('destructure.kap')).to eq("6\nAda\t36\n")
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
it 'egg-count.kap' do
|
|
62
|
+
expect(run_example('egg-count.kap')).to eq("4\n")
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
it 'even-squares.kap' do
|
|
66
|
+
expect(run_example('even-squares.kap')).to eq("4, 16, 36\n")
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
it 'exceptions.kap' do
|
|
70
|
+
expect(run_example('exceptions.kap')).to eq("seen: 12\n12\nseen: oops\nbad: oops\n")
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
it 'factorial.kap' do
|
|
74
|
+
expect(run_example('factorial.kap')).to eq("0\t1\n1\t1\n5\t120\n6\t720\n10\t3628800\n")
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
it 'fib.kap' do
|
|
78
|
+
expect(run_example('fib.kap')).to eq("55\n")
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
it 'fizzbuzz.kap' do
|
|
82
|
+
expected = "1\n2\nFizz\n4\nBuzz\nFizz\n7\n8\nFizz\nBuzz\n11\nFizz\n13\n14\nFizzBuzz\n16\n17\nFizz\n19\nBuzz\n"
|
|
83
|
+
expect(run_example('fizzbuzz.kap')).to eq(expected)
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
it 'gcd.kap' do
|
|
87
|
+
expect(run_example('gcd.kap')).to eq("12\n6\n")
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
it 'greet.kap' do
|
|
91
|
+
expect(run_example('greet.kap', argv: ['Ada'])).to eq("Hello, Ada!\n")
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
it 'hashfn.kap' do
|
|
95
|
+
expect(run_example('hashfn.kap')).to eq("5\n21\n")
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
it 'leap-year.kap' do
|
|
99
|
+
expect(run_example('leap-year.kap')).to eq("true\n")
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
it 'min-max.kap' do
|
|
103
|
+
expect(run_example('min-max.kap')).to eq("1\t9\n")
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
it 'module-header.kap' do
|
|
107
|
+
expect(run_example('module-header.kap')).to eq("Hello, Ada!\n")
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
it 'pipeline.kap' do
|
|
111
|
+
expect(run_example('pipeline.kap')).to eq("BLUE\nRED\n")
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
it 'points.kap' do
|
|
115
|
+
expect(run_example('points.kap')).to eq("origin\ny-axis\nx-axis\npoint\n")
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
it 'primes.kap' do
|
|
119
|
+
expect(run_example('primes.kap')).to eq("2\n3\n5\n7\n11\n13\n17\n19\n23\n29\n")
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
it 'raindrops.kap' do
|
|
123
|
+
expect(run_example('raindrops.kap')).to eq("PlingPlang\n")
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
it 'record.kap' do
|
|
127
|
+
expect(run_example('record.kap')).to eq("Ada / engineer / ruby, lisp\n")
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
it 'regex.kap' do
|
|
131
|
+
expected = <<~OUT
|
|
132
|
+
2026-04-23 -> {"year"=>"2026", "month"=>"04", "day"=>"23"}
|
|
133
|
+
hello -> nil
|
|
134
|
+
1999-12-31 -> {"year"=>"1999", "month"=>"12", "day"=>"31"}
|
|
135
|
+
OUT
|
|
136
|
+
expect(run_example('regex.kap')).to eq(expected)
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
it 'ruby-eval.kap' do
|
|
140
|
+
expect(run_example('ruby-eval.kap')).to eq("10-20-30\n")
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
it 'kwargs.kap' do
|
|
144
|
+
expect(run_example('kwargs.kap')).to eq("Ada has 3 tasks\n")
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
it 'match.kap' do
|
|
148
|
+
expect(run_example('match.kap')).to eq("Ada: 9\nLin: no score\nunknown\n")
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
it 'scopes.kap' do
|
|
152
|
+
expect(run_example('scopes.kap')).to eq("5\n9\n9\n9\n")
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
it 'pcall.kap' do
|
|
156
|
+
expected = <<~OUT
|
|
157
|
+
true
|
|
158
|
+
12
|
|
159
|
+
false
|
|
160
|
+
ArgumentError
|
|
161
|
+
false
|
|
162
|
+
invalid value for Integer(): "oops"
|
|
163
|
+
OUT
|
|
164
|
+
expect(run_example('pcall.kap')).to eq(expected)
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
it 'palindrome.kap' do
|
|
168
|
+
expect(run_example('palindrome.kap')).to eq("true\ntrue\nfalse\n")
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
it 'pangram.kap' do
|
|
172
|
+
expect(run_example('pangram.kap')).to eq("true\nfalse\n")
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
it 'safe-lookup.kap' do
|
|
176
|
+
expect(run_example('safe-lookup.kap')).to eq("Ada\nnil\n")
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
it 'shapes.kap' do
|
|
180
|
+
expect(run_example('shapes.kap')).to eq("78.5\n9\n8\n0\n")
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
it 'squares.kap' do
|
|
184
|
+
expect(run_example('squares.kap')).to eq("1\n4\n9\n16\n25\n")
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
it 'stack.kap' do
|
|
188
|
+
expect(run_example('stack.kap')).to eq('')
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
it 'sum.kap' do
|
|
192
|
+
expect(run_example('sum.kap')).to eq("100\n")
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
it 'tset.kap' do
|
|
196
|
+
expect(run_example('tset.kap')).to eq("{:name=>\"Ada\", :city=>\"Amsterdam\"}\nAmsterdam\n")
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
it 'two-sum.kap' do
|
|
200
|
+
expect(run_example('two-sum.kap')).to eq("[0, 1]\n[1, 2]\nnil\n")
|
|
201
|
+
end
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
RSpec.describe Kapusta do
|
|
205
|
+
it 'exposes a gem version' do
|
|
206
|
+
expect(Kapusta::VERSION).to match(/\A\d+\.\d+\.\d+\z/)
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
it 'defaults classes to Object when the superclass is omitted' do
|
|
210
|
+
source = <<~KAP
|
|
211
|
+
(let [klass (class Stack
|
|
212
|
+
(fn initialize []
|
|
213
|
+
nil))]
|
|
214
|
+
(values (= Stack.superclass Object)
|
|
215
|
+
(= (Stack.new.class) Stack)
|
|
216
|
+
(= klass Stack)))
|
|
217
|
+
KAP
|
|
218
|
+
|
|
219
|
+
expect(Kapusta.eval(source)).to eq([true, true, true])
|
|
220
|
+
end
|
|
221
|
+
|
|
222
|
+
it 'still accepts an explicit superclass vector' do
|
|
223
|
+
source = <<~KAP
|
|
224
|
+
(let [klass (class KapustaError [StandardError])]
|
|
225
|
+
(values (= KapustaError.superclass StandardError)
|
|
226
|
+
(= klass KapustaError)))
|
|
227
|
+
KAP
|
|
228
|
+
|
|
229
|
+
expect(Kapusta.eval(source)).to eq([true, true])
|
|
230
|
+
end
|
|
231
|
+
|
|
232
|
+
it 'preserves nested arithmetic precedence' do
|
|
233
|
+
source = <<~KAP
|
|
234
|
+
(values (/ (+ 3 5) 2)
|
|
235
|
+
(* (+ 1 2) (- 10 4))
|
|
236
|
+
(% (+ 10 5) 4))
|
|
237
|
+
KAP
|
|
238
|
+
|
|
239
|
+
expect(Kapusta.eval(source)).to eq([4, 18, 3])
|
|
240
|
+
end
|
|
241
|
+
|
|
242
|
+
it 'supports postfix zero-arg method calls on non-symbol expressions' do
|
|
243
|
+
source = <<~KAP
|
|
244
|
+
(values [1 2].inspect
|
|
245
|
+
(+ 1 2).inspect
|
|
246
|
+
"Listen".downcase.chars.sort.join)
|
|
247
|
+
KAP
|
|
248
|
+
|
|
249
|
+
expect(Kapusta.eval(source)).to eq(['[1, 2]', '3', 'eilnst'])
|
|
250
|
+
end
|
|
251
|
+
end
|
|
252
|
+
|
|
253
|
+
RSpec.describe 'errors' do
|
|
254
|
+
it 'raises on unclosed list' do
|
|
255
|
+
expect { Kapusta.eval('(fn hello [name] (.. "Hi " name "!")') }
|
|
256
|
+
.to raise_error(/unclosed \(/)
|
|
257
|
+
end
|
|
258
|
+
end
|