kapusta 0.3.0 → 0.5.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 +4 -4
- data/README.md +1 -2
- data/bin/check-all +19 -0
- data/bin/fennel-parity +187 -0
- data/examples/even-squares.kap +22 -7
- data/examples/macros-dbg.kap +9 -0
- data/examples/macros-multi.kap +12 -0
- data/examples/macros-swap.kap +9 -0
- data/examples/macros-thrice-if.kap +18 -0
- data/examples/macros-unless.kap +7 -0
- data/examples/macros-when-let.kap +7 -0
- data/examples/packet-router.kap +2 -5
- data/examples/roman-to-integer.kap +3 -3
- data/examples/tic-tac-toe.kap +4 -9
- data/examples/ugly-number.kap +22 -0
- data/lib/kapusta/ast.rb +77 -1
- data/lib/kapusta/cli.rb +3 -0
- data/lib/kapusta/compiler/emitter/bindings.rb +52 -6
- data/lib/kapusta/compiler/emitter/collections.rb +64 -20
- data/lib/kapusta/compiler/emitter/control_flow.rb +7 -4
- data/lib/kapusta/compiler/emitter/expressions.rb +58 -13
- data/lib/kapusta/compiler/emitter/interop.rb +6 -4
- data/lib/kapusta/compiler/emitter/patterns.rb +29 -12
- data/lib/kapusta/compiler/emitter/support.rb +46 -23
- data/lib/kapusta/compiler/macro_expander.rb +286 -0
- data/lib/kapusta/compiler/normalizer.rb +35 -9
- data/lib/kapusta/compiler.rb +13 -1
- data/lib/kapusta/error.rb +25 -1
- data/lib/kapusta/errors.rb +69 -0
- data/lib/kapusta/formatter.rb +228 -92
- data/lib/kapusta/reader.rb +80 -22
- data/lib/kapusta/version.rb +1 -1
- data/lib/kapusta.rb +6 -5
- data/spec/examples_errors_spec.rb +229 -0
- data/spec/examples_spec.rb +51 -0
- data/spec/formatter_spec.rb +15 -16
- metadata +13 -2
- data/spec/reader_spec.rb +0 -26
data/lib/kapusta/reader.rb
CHANGED
|
@@ -6,8 +6,8 @@ module Kapusta
|
|
|
6
6
|
class Reader
|
|
7
7
|
class Error < Kapusta::Error; end
|
|
8
8
|
|
|
9
|
-
WHITESPACE = [' ', "\t", "\n", "\r", "\f", "\v"
|
|
10
|
-
DELIMS = ['(', ')', '[', ']', '{', '}', '"', ';'].freeze
|
|
9
|
+
WHITESPACE = [' ', "\t", "\n", "\r", "\f", "\v"].freeze
|
|
10
|
+
DELIMS = ['(', ')', '[', ']', '{', '}', '"', ';', '`', ','].freeze
|
|
11
11
|
CLOSING_DELIMS = [')', ']', '}'].freeze
|
|
12
12
|
|
|
13
13
|
def self.read_all(source, preserve_comments: false)
|
|
@@ -23,9 +23,10 @@ module Kapusta
|
|
|
23
23
|
def read_all
|
|
24
24
|
forms = []
|
|
25
25
|
loop do
|
|
26
|
-
skip_ws
|
|
26
|
+
had_blank = skip_ws
|
|
27
27
|
break if eof?
|
|
28
28
|
|
|
29
|
+
forms << BlankLine.new if @preserve_comments && had_blank && !forms.empty?
|
|
29
30
|
forms << read_next_item
|
|
30
31
|
end
|
|
31
32
|
forms
|
|
@@ -47,10 +48,14 @@ module Kapusta
|
|
|
47
48
|
char
|
|
48
49
|
end
|
|
49
50
|
|
|
50
|
-
def skip_ws
|
|
51
|
+
def skip_ws # rubocop:disable Naming/PredicateMethod
|
|
52
|
+
newlines = 0
|
|
51
53
|
until eof?
|
|
52
54
|
char = peek
|
|
53
|
-
if
|
|
55
|
+
if char == "\n"
|
|
56
|
+
newlines += 1
|
|
57
|
+
advance
|
|
58
|
+
elsif WHITESPACE.include?(char)
|
|
54
59
|
advance
|
|
55
60
|
elsif !@preserve_comments && char == ';'
|
|
56
61
|
advance until eof? || peek == "\n"
|
|
@@ -58,6 +63,7 @@ module Kapusta
|
|
|
58
63
|
break
|
|
59
64
|
end
|
|
60
65
|
end
|
|
66
|
+
newlines >= 2
|
|
61
67
|
end
|
|
62
68
|
|
|
63
69
|
def delim?(char)
|
|
@@ -66,7 +72,7 @@ module Kapusta
|
|
|
66
72
|
|
|
67
73
|
def read_next_item
|
|
68
74
|
skip_ws
|
|
69
|
-
raise
|
|
75
|
+
raise reader_error(:unexpected_eof, source_position) if eof?
|
|
70
76
|
|
|
71
77
|
return read_comment if @preserve_comments && peek == ';'
|
|
72
78
|
|
|
@@ -75,10 +81,11 @@ module Kapusta
|
|
|
75
81
|
|
|
76
82
|
def read_form
|
|
77
83
|
skip_ws
|
|
78
|
-
raise
|
|
84
|
+
raise reader_error(:unexpected_eof, source_position) if eof?
|
|
79
85
|
|
|
80
86
|
return read_comment if @preserve_comments && peek == ';'
|
|
81
87
|
|
|
88
|
+
position = source_position
|
|
82
89
|
form =
|
|
83
90
|
case peek
|
|
84
91
|
when '(' then read_list
|
|
@@ -86,27 +93,59 @@ module Kapusta
|
|
|
86
93
|
when '{' then read_hash
|
|
87
94
|
when '"' then read_string
|
|
88
95
|
when '#' then read_hashfn
|
|
96
|
+
when '`' then read_quasiquote
|
|
97
|
+
when ',' then read_unquote
|
|
89
98
|
when *CLOSING_DELIMS then raise unexpected_closing_delim(peek)
|
|
90
99
|
else
|
|
91
100
|
read_atom
|
|
92
101
|
end
|
|
93
102
|
|
|
103
|
+
attach_position(form, position)
|
|
94
104
|
read_postfix(form)
|
|
95
105
|
end
|
|
96
106
|
|
|
107
|
+
def attach_position(form, position)
|
|
108
|
+
return form unless form.respond_to?(:line=)
|
|
109
|
+
|
|
110
|
+
form.line ||= position[0]
|
|
111
|
+
form.column ||= position[1]
|
|
112
|
+
form
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def read_quasiquote
|
|
116
|
+
advance
|
|
117
|
+
Quasiquote.new(read_form)
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
def read_unquote
|
|
121
|
+
advance
|
|
122
|
+
if peek == '@'
|
|
123
|
+
advance
|
|
124
|
+
UnquoteSplice.new(read_form)
|
|
125
|
+
else
|
|
126
|
+
Unquote.new(read_form)
|
|
127
|
+
end
|
|
128
|
+
end
|
|
129
|
+
|
|
97
130
|
def read_list
|
|
98
131
|
opening_position = source_position
|
|
99
132
|
advance
|
|
100
133
|
items = []
|
|
101
134
|
loop do
|
|
102
|
-
skip_ws
|
|
135
|
+
had_blank = skip_ws
|
|
103
136
|
raise unclosed_opening_delim('(', opening_position) if eof?
|
|
104
137
|
break if peek == ')'
|
|
105
138
|
|
|
139
|
+
items << BlankLine.new if @preserve_comments && had_blank && !items.empty?
|
|
106
140
|
items << read_next_item
|
|
107
141
|
end
|
|
142
|
+
closing_position = source_position
|
|
108
143
|
advance
|
|
109
|
-
List.new(items)
|
|
144
|
+
list = List.new(items)
|
|
145
|
+
list.multiline_source = closing_position[0] != opening_position[0]
|
|
146
|
+
list.line = opening_position[0]
|
|
147
|
+
list.column = opening_position[1]
|
|
148
|
+
list
|
|
110
149
|
end
|
|
111
150
|
|
|
112
151
|
def read_vec
|
|
@@ -114,14 +153,20 @@ module Kapusta
|
|
|
114
153
|
advance
|
|
115
154
|
items = []
|
|
116
155
|
loop do
|
|
117
|
-
skip_ws
|
|
156
|
+
had_blank = skip_ws
|
|
118
157
|
raise unclosed_opening_delim('[', opening_position) if eof?
|
|
119
158
|
break if peek == ']'
|
|
120
159
|
|
|
160
|
+
items << BlankLine.new if @preserve_comments && had_blank && !items.empty?
|
|
121
161
|
items << read_next_item
|
|
122
162
|
end
|
|
163
|
+
closing_position = source_position
|
|
123
164
|
advance
|
|
124
|
-
Vec.new(items)
|
|
165
|
+
vec = Vec.new(items)
|
|
166
|
+
vec.multiline_source = closing_position[0] != opening_position[0]
|
|
167
|
+
vec.line = opening_position[0]
|
|
168
|
+
vec.column = opening_position[1]
|
|
169
|
+
vec
|
|
125
170
|
end
|
|
126
171
|
|
|
127
172
|
def read_hash
|
|
@@ -146,14 +191,20 @@ module Kapusta
|
|
|
146
191
|
entries << normalize_hash_pair(pending[0], pending[1])
|
|
147
192
|
pending.clear
|
|
148
193
|
end
|
|
194
|
+
closing_position = source_position
|
|
149
195
|
advance
|
|
150
196
|
|
|
151
|
-
raise
|
|
197
|
+
raise reader_error(:odd_forms_in_hash, opening_position) unless pending.empty?
|
|
152
198
|
|
|
153
|
-
HashLit.new(entries)
|
|
199
|
+
hash = HashLit.new(entries)
|
|
200
|
+
hash.multiline_source = closing_position[0] != opening_position[0]
|
|
201
|
+
hash.line = opening_position[0]
|
|
202
|
+
hash.column = opening_position[1]
|
|
203
|
+
hash
|
|
154
204
|
end
|
|
155
205
|
|
|
156
206
|
def read_string
|
|
207
|
+
opening_position = source_position
|
|
157
208
|
advance
|
|
158
209
|
buffer = +''
|
|
159
210
|
until eof? || peek == '"'
|
|
@@ -177,7 +228,7 @@ module Kapusta
|
|
|
177
228
|
buffer << advance
|
|
178
229
|
end
|
|
179
230
|
end
|
|
180
|
-
raise
|
|
231
|
+
raise reader_error(:unterminated_string, opening_position) if eof?
|
|
181
232
|
|
|
182
233
|
advance
|
|
183
234
|
buffer
|
|
@@ -215,22 +266,25 @@ module Kapusta
|
|
|
215
266
|
end
|
|
216
267
|
|
|
217
268
|
def read_atom
|
|
269
|
+
position = source_position
|
|
218
270
|
start = @pos
|
|
219
271
|
advance until delim?(peek)
|
|
220
272
|
token = @src[start...@pos]
|
|
221
|
-
raise
|
|
273
|
+
raise reader_error(:empty_token, position) if token.empty?
|
|
222
274
|
|
|
223
|
-
parse_atom(token)
|
|
275
|
+
parse_atom(token, position)
|
|
224
276
|
end
|
|
225
277
|
|
|
226
278
|
def unexpected_closing_delim(char)
|
|
227
|
-
|
|
228
|
-
Error.new("unexpected closing delimiter '#{char}' at line #{line}, column #{column}")
|
|
279
|
+
reader_error(:unexpected_closing_delimiter, source_position, char:)
|
|
229
280
|
end
|
|
230
281
|
|
|
231
282
|
def unclosed_opening_delim(char, position)
|
|
232
|
-
|
|
233
|
-
|
|
283
|
+
reader_error(:unclosed_delimiter, position, char:)
|
|
284
|
+
end
|
|
285
|
+
|
|
286
|
+
def reader_error(code, position, **args)
|
|
287
|
+
Error.new(Kapusta::Errors.format(code, **args), line: position[0], column: position[1])
|
|
234
288
|
end
|
|
235
289
|
|
|
236
290
|
def source_position
|
|
@@ -242,15 +296,19 @@ module Kapusta
|
|
|
242
296
|
[line, column]
|
|
243
297
|
end
|
|
244
298
|
|
|
245
|
-
def parse_atom(token)
|
|
299
|
+
def parse_atom(token, position)
|
|
246
300
|
return true if token == 'true'
|
|
247
301
|
return false if token == 'false'
|
|
248
302
|
return if token == 'nil'
|
|
249
303
|
return Integer(token, 10) if token.match?(/\A-?\d+\z/)
|
|
250
304
|
return Float(token) if token.match?(/\A-?\d+\.\d+\z/)
|
|
251
305
|
|
|
306
|
+
raise reader_error(:could_not_read_number, position, token:) if token.match?(/\A-?\d/)
|
|
307
|
+
|
|
252
308
|
if token.start_with?(':') && token.length > 1
|
|
253
309
|
Kapusta.kebab_to_snake(token[1..]).to_sym
|
|
310
|
+
elsif token.length > 1 && token.end_with?('#') && !token[0..-2].include?('#')
|
|
311
|
+
AutoGensym.new(token.chomp('#'))
|
|
254
312
|
else
|
|
255
313
|
Sym.new(token)
|
|
256
314
|
end
|
|
@@ -258,7 +316,7 @@ module Kapusta
|
|
|
258
316
|
|
|
259
317
|
def normalize_hash_pair(item, value)
|
|
260
318
|
if item.is_a?(Sym) && item.name == ':'
|
|
261
|
-
raise
|
|
319
|
+
raise reader_error(:bad_shorthand, source_position) unless value.is_a?(Sym)
|
|
262
320
|
|
|
263
321
|
key = Kapusta.kebab_to_snake(value.name).to_sym
|
|
264
322
|
[key, value]
|
data/lib/kapusta/version.rb
CHANGED
data/lib/kapusta.rb
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
require_relative 'kapusta/version'
|
|
4
4
|
require_relative 'kapusta/error'
|
|
5
|
+
require_relative 'kapusta/errors'
|
|
5
6
|
require_relative 'kapusta/support'
|
|
6
7
|
require_relative 'kapusta/ast'
|
|
7
8
|
require_relative 'kapusta/reader'
|
|
@@ -42,14 +43,14 @@ module Kapusta
|
|
|
42
43
|
|
|
43
44
|
@installed = true
|
|
44
45
|
Kernel.module_eval do
|
|
45
|
-
alias_method :__kapusta_original_require_relative, :require_relative
|
|
46
|
-
private :__kapusta_original_require_relative
|
|
47
|
-
|
|
48
46
|
def require_relative(path)
|
|
49
|
-
|
|
47
|
+
location = caller_locations(1, 1).first
|
|
48
|
+
kap_path = Kapusta.send(:resolve_kap_relative, path, location)
|
|
50
49
|
return Kapusta.send(:require_kapusta_file, kap_path) if kap_path
|
|
51
50
|
|
|
52
|
-
|
|
51
|
+
base_file = location&.absolute_path || location&.path
|
|
52
|
+
target = base_file ? File.expand_path(path, File.dirname(base_file)) : path
|
|
53
|
+
Kernel.require(target)
|
|
53
54
|
end
|
|
54
55
|
end
|
|
55
56
|
end
|
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'spec_helper'
|
|
4
|
+
require 'open3'
|
|
5
|
+
require 'rbconfig'
|
|
6
|
+
|
|
7
|
+
ERRORS_DIR = File.expand_path('../examples-errors', __dir__)
|
|
8
|
+
KAPUSTA_BIN = File.expand_path('../exe/kapusta', __dir__)
|
|
9
|
+
KAPFMT_BIN = File.expand_path('../exe/kapfmt', __dir__)
|
|
10
|
+
|
|
11
|
+
def run_error_example(name)
|
|
12
|
+
k_out, k_err, k_status = Open3.capture3(RbConfig.ruby, KAPUSTA_BIN, name, chdir: ERRORS_DIR)
|
|
13
|
+
f_out, f_err, f_status = Open3.capture3(RbConfig.ruby, KAPFMT_BIN, name, chdir: ERRORS_DIR)
|
|
14
|
+
|
|
15
|
+
raise "kapusta unexpectedly succeeded for #{name}" if k_status.success?
|
|
16
|
+
raise "kapfmt unexpectedly succeeded for #{name}" if f_status.success?
|
|
17
|
+
raise "kapusta wrote to stdout for #{name}: #{k_out.inspect}" unless k_out.empty?
|
|
18
|
+
raise "kapfmt wrote to stdout for #{name}: #{f_out.inspect}" unless f_out.empty?
|
|
19
|
+
raise "kapusta and kapfmt disagree for #{name}:\n kapusta: #{k_err} kapfmt: #{f_err}" unless k_err == f_err
|
|
20
|
+
|
|
21
|
+
k_err
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
RSpec.describe 'examples-errors' do
|
|
25
|
+
it 'accumulate-missing-iterator.kap' do
|
|
26
|
+
expect(run_error_example('accumulate-missing-iterator.kap'))
|
|
27
|
+
.to eq("accumulate-missing-iterator.kap:5:3: expected initial value and iterator binding table\n")
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
it 'call-empty-form.kap' do
|
|
31
|
+
expect(run_error_example('call-empty-form.kap'))
|
|
32
|
+
.to eq("call-empty-form.kap:7:8: expected a function, macro, or special to call\n")
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
it 'call-literal-number.kap' do
|
|
36
|
+
expect(run_error_example('call-literal-number.kap'))
|
|
37
|
+
.to eq("call-literal-number.kap:6:14: cannot call literal value 1\n")
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
it 'case-no-patterns.kap' do
|
|
41
|
+
expect(run_error_example('case-no-patterns.kap'))
|
|
42
|
+
.to eq("case-no-patterns.kap:3:5: expected at least one pattern/body pair\n")
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
it 'case-odd-pattern-body.kap' do
|
|
46
|
+
expect(run_error_example('case-odd-pattern-body.kap'))
|
|
47
|
+
.to eq("case-odd-pattern-body.kap:2:3: expected even number of pattern/body pairs\n")
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
it 'destructure-literal-number.kap' do
|
|
51
|
+
expect(run_error_example('destructure-literal-number.kap'))
|
|
52
|
+
.to eq("destructure-literal-number.kap:5:3: could not destructure literal\n")
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
it 'destructure-literal-table.kap' do
|
|
56
|
+
expect(run_error_example('destructure-literal-table.kap'))
|
|
57
|
+
.to eq("destructure-literal-table.kap:4:1: could not destructure literal\n")
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
it 'destructure-rest-as-table.kap' do
|
|
61
|
+
expect(run_error_example('destructure-rest-as-table.kap'))
|
|
62
|
+
.to eq("destructure-rest-as-table.kap:6:3: unable to bind table ...\n")
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
it 'dot-without-table.kap' do
|
|
66
|
+
expect(run_error_example('dot-without-table.kap'))
|
|
67
|
+
.to eq("dot-without-table.kap:5:15: expected table argument\n")
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
it 'each-not-binding-table.kap' do
|
|
71
|
+
expect(run_error_example('each-not-binding-table.kap'))
|
|
72
|
+
.to eq("each-not-binding-table.kap:6:3: expected binding table\n")
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
it 'faccumulate-missing-iterator.kap' do
|
|
76
|
+
expect(run_error_example('faccumulate-missing-iterator.kap'))
|
|
77
|
+
.to eq("faccumulate-missing-iterator.kap:7:3: expected initial value and iterator binding table\n")
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
it 'fcollect-missing-range.kap' do
|
|
81
|
+
expect(run_error_example('fcollect-missing-range.kap'))
|
|
82
|
+
.to eq("fcollect-missing-range.kap:6:3: expected range to include start and stop\n")
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
it 'fn-non-symbol-param.kap' do
|
|
86
|
+
expect(run_error_example('fn-non-symbol-param.kap'))
|
|
87
|
+
.to eq("fn-non-symbol-param.kap:4:1: destructure pattern this compiler cannot translate: [1 2]\n")
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
it 'fn-without-params.kap' do
|
|
91
|
+
expect(run_error_example('fn-without-params.kap'))
|
|
92
|
+
.to eq("fn-without-params.kap:4:11: expected parameters table\n")
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
it 'for-missing-stop.kap' do
|
|
96
|
+
expect(run_error_example('for-missing-stop.kap'))
|
|
97
|
+
.to eq("for-missing-stop.kap:6:3: expected range to include start and stop\n")
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
it 'global-non-symbol-name.kap' do
|
|
101
|
+
expect(run_error_example('global-non-symbol-name.kap'))
|
|
102
|
+
.to eq("global-non-symbol-name.kap:6:1: unable to bind integer 1\n")
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
it 'global-without-value.kap' do
|
|
106
|
+
expect(run_error_example('global-without-value.kap'))
|
|
107
|
+
.to eq("global-without-value.kap:6:1: expected name and value\n")
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
it 'icollect-missing-iterator.kap' do
|
|
111
|
+
expect(run_error_example('icollect-missing-iterator.kap'))
|
|
112
|
+
.to eq("icollect-missing-iterator.kap:6:3: expected iterator binding table\n")
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
it 'if-no-body.kap' do
|
|
116
|
+
expect(run_error_example('if-no-body.kap'))
|
|
117
|
+
.to eq("if-no-body.kap:8:5: expected condition and body\n")
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
it 'import-macros-missing-module.kap' do
|
|
121
|
+
expect(run_error_example('import-macros-missing-module.kap'))
|
|
122
|
+
.to eq("import-macros-missing-module.kap:4:1: import-macros is not yet supported\n")
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
it 'let-odd-bindings.kap' do
|
|
126
|
+
expect(run_error_example('let-odd-bindings.kap'))
|
|
127
|
+
.to eq("let-odd-bindings.kap:2:3: expected even number of name/value bindings\n")
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
it 'let-without-body-form.kap' do
|
|
131
|
+
expect(run_error_example('let-without-body-form.kap'))
|
|
132
|
+
.to eq("let-without-body-form.kap:4:5: expected body expression\n")
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
it 'local-with-extra-args.kap' do
|
|
136
|
+
expect(run_error_example('local-with-extra-args.kap'))
|
|
137
|
+
.to eq("local-with-extra-args.kap:6:3: local: expected name and value\n")
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
it 'local-without-value.kap' do
|
|
141
|
+
expect(run_error_example('local-without-value.kap'))
|
|
142
|
+
.to eq("local-without-value.kap:6:3: local: expected name and value\n")
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
it 'macro-unsafe-bind.kap' do
|
|
146
|
+
expect(run_error_example('macro-unsafe-bind.kap'))
|
|
147
|
+
.to eq("macro-unsafe-bind.kap:13:8: macro tried to bind unsafe without gensym\n")
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
it 'macro-vararg-with-operator.kap' do
|
|
151
|
+
expect(run_error_example('macro-vararg-with-operator.kap'))
|
|
152
|
+
.to eq("macro-vararg-with-operator.kap:5:3: tried to use vararg with operator\n")
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
it 'match-no-patterns.kap' do
|
|
156
|
+
expect(run_error_example('match-no-patterns.kap'))
|
|
157
|
+
.to eq("match-no-patterns.kap:3:5: expected at least one pattern/body pair\n")
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
it 'mismatched-brackets.kap' do
|
|
161
|
+
expect(run_error_example('mismatched-brackets.kap'))
|
|
162
|
+
.to eq("mismatched-brackets.kap:4:19: unexpected closing delimiter ')'\n")
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
it 'only-rest-param.kap' do
|
|
166
|
+
expect(run_error_example('only-rest-param.kap'))
|
|
167
|
+
.to eq("only-rest-param.kap:4:1: expected rest argument before last parameter\n")
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
it 'quote-runtime.kap' do
|
|
171
|
+
expect(run_error_example('quote-runtime.kap'))
|
|
172
|
+
.to eq("quote-runtime.kap:6:1: cannot emit form: `hello\n")
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
it 'rest-not-last.kap' do
|
|
176
|
+
expect(run_error_example('rest-not-last.kap'))
|
|
177
|
+
.to eq("rest-not-last.kap:6:3: expected rest argument before last parameter\n")
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
it 'set-immutable-local.kap' do
|
|
181
|
+
expect(run_error_example('set-immutable-local.kap'))
|
|
182
|
+
.to eq("set-immutable-local.kap:8:3: expected var counter\n")
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
it 'shadow-special-fn.kap' do
|
|
186
|
+
expect(run_error_example('shadow-special-fn.kap'))
|
|
187
|
+
.to eq("shadow-special-fn.kap:6:3: local fn was overshadowed by a special form or macro\n")
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
it 'shadow-special-if.kap' do
|
|
191
|
+
expect(run_error_example('shadow-special-if.kap'))
|
|
192
|
+
.to eq("shadow-special-if.kap:4:1: local if was overshadowed by a special form or macro\n")
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
it 'symbol-starting-with-digit.kap' do
|
|
196
|
+
expect(run_error_example('symbol-starting-with-digit.kap'))
|
|
197
|
+
.to eq("symbol-starting-with-digit.kap:6:10: could not read number \"5var\"\n")
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
it 'tset-missing-value.kap' do
|
|
201
|
+
expect(run_error_example('tset-missing-value.kap'))
|
|
202
|
+
.to eq("tset-missing-value.kap:5:5: tset: expected table, key, and value arguments\n")
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
it 'unbalanced-parens.kap' do
|
|
206
|
+
expect(run_error_example('unbalanced-parens.kap'))
|
|
207
|
+
.to eq("unbalanced-parens.kap:4:1: unclosed opening delimiter '('\n")
|
|
208
|
+
end
|
|
209
|
+
|
|
210
|
+
it 'unclosed-table.kap' do
|
|
211
|
+
expect(run_error_example('unclosed-table.kap'))
|
|
212
|
+
.to eq("unclosed-table.kap:4:21: unexpected closing delimiter ']'\n")
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
it 'unquote-outside-quote.kap' do
|
|
216
|
+
expect(run_error_example('unquote-outside-quote.kap'))
|
|
217
|
+
.to eq("unquote-outside-quote.kap:5:10: cannot emit form: ,x\n")
|
|
218
|
+
end
|
|
219
|
+
|
|
220
|
+
it 'var-without-value.kap' do
|
|
221
|
+
expect(run_error_example('var-without-value.kap'))
|
|
222
|
+
.to eq("var-without-value.kap:6:3: var: expected name and value\n")
|
|
223
|
+
end
|
|
224
|
+
|
|
225
|
+
it 'when-no-body.kap' do
|
|
226
|
+
expect(run_error_example('when-no-body.kap'))
|
|
227
|
+
.to eq("when-no-body.kap:4:3: when: expected body\n")
|
|
228
|
+
end
|
|
229
|
+
end
|
data/spec/examples_spec.rb
CHANGED
|
@@ -500,4 +500,55 @@ RSpec.describe 'examples' do
|
|
|
500
500
|
it 'subtract-product-sum.kap' do
|
|
501
501
|
expect(run_example('subtract-product-sum.kap')).to eq("15\n21\n0\n")
|
|
502
502
|
end
|
|
503
|
+
|
|
504
|
+
it 'ugly-number.kap' do
|
|
505
|
+
expect(run_example('ugly-number.kap')).to eq("true\ntrue\nfalse\nfalse\ntrue\n")
|
|
506
|
+
end
|
|
507
|
+
|
|
508
|
+
it 'macros-unless.kap' do
|
|
509
|
+
expect(run_example('macros-unless.kap')).to eq(<<~OUT)
|
|
510
|
+
"shown"
|
|
511
|
+
"also shown"
|
|
512
|
+
OUT
|
|
513
|
+
end
|
|
514
|
+
|
|
515
|
+
it 'macros-swap.kap' do
|
|
516
|
+
expect(run_example('macros-swap.kap')).to eq("2\n1\n")
|
|
517
|
+
end
|
|
518
|
+
|
|
519
|
+
it 'macros-when-let.kap' do
|
|
520
|
+
expect(run_example('macros-when-let.kap')).to eq(<<~OUT)
|
|
521
|
+
"got"
|
|
522
|
+
3
|
|
523
|
+
"done"
|
|
524
|
+
OUT
|
|
525
|
+
end
|
|
526
|
+
|
|
527
|
+
it 'macros-multi.kap' do
|
|
528
|
+
expect(run_example('macros-multi.kap')).to eq("10\n20\n7\n")
|
|
529
|
+
end
|
|
530
|
+
|
|
531
|
+
it 'macros-thrice-if.kap' do
|
|
532
|
+
expect(run_example('macros-thrice-if.kap')).to eq(<<~OUT)
|
|
533
|
+
"tick"
|
|
534
|
+
1
|
|
535
|
+
"tick"
|
|
536
|
+
2
|
|
537
|
+
"tick"
|
|
538
|
+
3
|
|
539
|
+
"final"
|
|
540
|
+
3
|
|
541
|
+
OUT
|
|
542
|
+
end
|
|
543
|
+
|
|
544
|
+
it 'macros-dbg.kap' do
|
|
545
|
+
expect(run_example('macros-dbg.kap')).to eq(<<~OUT)
|
|
546
|
+
"dbg"
|
|
547
|
+
6
|
|
548
|
+
"result"
|
|
549
|
+
6
|
|
550
|
+
"dbg"
|
|
551
|
+
50
|
|
552
|
+
OUT
|
|
553
|
+
end
|
|
503
554
|
end
|
data/spec/formatter_spec.rb
CHANGED
|
@@ -43,7 +43,7 @@ RSpec.describe Kapusta::Formatter do
|
|
|
43
43
|
Dir.mktmpdir do |dir|
|
|
44
44
|
path = File.join(dir, 'sample.kap')
|
|
45
45
|
File.write(path, <<~KAP)
|
|
46
|
-
(let [uri (URI.join (ivar base-uri) (.. "/posts/" id)) body (Net.HTTP.get uri) post (JSON.parse body {:symbolize-names true}) {: title : author} post] (values title author))
|
|
46
|
+
(fn fetch-post [id] (let [uri (URI.join (ivar base-uri) (.. "/posts/" id)) body (Net.HTTP.get uri) post (JSON.parse body {:symbolize-names true}) {: title : author} post] (values title author)))
|
|
47
47
|
KAP
|
|
48
48
|
|
|
49
49
|
previous_path = ENV.fetch('PATH', nil)
|
|
@@ -54,11 +54,12 @@ RSpec.describe Kapusta::Formatter do
|
|
|
54
54
|
end
|
|
55
55
|
|
|
56
56
|
expect(output).to eq(<<~KAP)
|
|
57
|
-
(
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
57
|
+
(fn fetch-post [id]
|
|
58
|
+
(let [uri (URI.join (ivar base-uri) (.. "/posts/" id))
|
|
59
|
+
body (Net.HTTP.get uri)
|
|
60
|
+
post (JSON.parse body {:symbolize-names true})
|
|
61
|
+
{: title : author} post]
|
|
62
|
+
(values title author)))
|
|
62
63
|
KAP
|
|
63
64
|
ensure
|
|
64
65
|
ENV['PATH'] = previous_path
|
|
@@ -210,16 +211,14 @@ RSpec.describe Kapusta::Formatter do
|
|
|
210
211
|
end
|
|
211
212
|
|
|
212
213
|
expect(output).to eq(<<~KAP)
|
|
213
|
-
(let
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
role
|
|
222
|
-
"Engineer"]
|
|
214
|
+
(let [
|
|
215
|
+
profile
|
|
216
|
+
{:name "Ada"
|
|
217
|
+
; active user
|
|
218
|
+
:active true}
|
|
219
|
+
; next binding
|
|
220
|
+
role
|
|
221
|
+
"Engineer"]
|
|
223
222
|
(print profile role))
|
|
224
223
|
KAP
|
|
225
224
|
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.
|
|
4
|
+
version: 0.5.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Evgenii Morozov
|
|
@@ -20,8 +20,10 @@ files:
|
|
|
20
20
|
- Gemfile
|
|
21
21
|
- README.md
|
|
22
22
|
- Rakefile
|
|
23
|
+
- bin/check-all
|
|
23
24
|
- bin/compile-examples
|
|
24
25
|
- bin/console
|
|
26
|
+
- bin/fennel-parity
|
|
25
27
|
- bin/setup
|
|
26
28
|
- examples/accumulator.kap
|
|
27
29
|
- examples/ackermann.kap
|
|
@@ -56,6 +58,12 @@ files:
|
|
|
56
58
|
- examples/kwargs.kap
|
|
57
59
|
- examples/leap-year.kap
|
|
58
60
|
- examples/length-of-last-word.kap
|
|
61
|
+
- examples/macros-dbg.kap
|
|
62
|
+
- examples/macros-multi.kap
|
|
63
|
+
- examples/macros-swap.kap
|
|
64
|
+
- examples/macros-thrice-if.kap
|
|
65
|
+
- examples/macros-unless.kap
|
|
66
|
+
- examples/macros-when-let.kap
|
|
59
67
|
- examples/majority-element.kap
|
|
60
68
|
- examples/manhattan-distance.kap
|
|
61
69
|
- examples/match.kap
|
|
@@ -92,6 +100,7 @@ files:
|
|
|
92
100
|
- examples/tset.kap
|
|
93
101
|
- examples/two-sum-hash.kap
|
|
94
102
|
- examples/two-sum.kap
|
|
103
|
+
- examples/ugly-number.kap
|
|
95
104
|
- examples/underscore-patterns.kap
|
|
96
105
|
- examples/use_bank_account.rb
|
|
97
106
|
- examples/valid-parentheses-1.kap
|
|
@@ -113,17 +122,19 @@ files:
|
|
|
113
122
|
- lib/kapusta/compiler/emitter/interop.rb
|
|
114
123
|
- lib/kapusta/compiler/emitter/patterns.rb
|
|
115
124
|
- lib/kapusta/compiler/emitter/support.rb
|
|
125
|
+
- lib/kapusta/compiler/macro_expander.rb
|
|
116
126
|
- lib/kapusta/compiler/normalizer.rb
|
|
117
127
|
- lib/kapusta/env.rb
|
|
118
128
|
- lib/kapusta/error.rb
|
|
129
|
+
- lib/kapusta/errors.rb
|
|
119
130
|
- lib/kapusta/formatter.rb
|
|
120
131
|
- lib/kapusta/reader.rb
|
|
121
132
|
- lib/kapusta/support.rb
|
|
122
133
|
- lib/kapusta/version.rb
|
|
123
134
|
- spec/cli_spec.rb
|
|
135
|
+
- spec/examples_errors_spec.rb
|
|
124
136
|
- spec/examples_spec.rb
|
|
125
137
|
- spec/formatter_spec.rb
|
|
126
|
-
- spec/reader_spec.rb
|
|
127
138
|
- spec/spec_helper.rb
|
|
128
139
|
homepage: https://github.com/evmorov/kapusta
|
|
129
140
|
licenses:
|