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
|
@@ -0,0 +1,286 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Kapusta
|
|
4
|
+
module Compiler
|
|
5
|
+
class MacroExpander
|
|
6
|
+
class Error < Kapusta::Error; end
|
|
7
|
+
|
|
8
|
+
@gensym_counter = 0
|
|
9
|
+
|
|
10
|
+
class << self
|
|
11
|
+
def fresh_gensym(prefix)
|
|
12
|
+
@gensym_counter += 1
|
|
13
|
+
GeneratedSym.new("#{prefix}_g#{@gensym_counter}", @gensym_counter)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def fresh_local_gensym(prefix)
|
|
17
|
+
@gensym_counter += 1
|
|
18
|
+
GeneratedSym.new("#{prefix}_local_#{@gensym_counter}", @gensym_counter)
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def initialize(path: nil)
|
|
23
|
+
@macros = {}
|
|
24
|
+
@path = path
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def expand_all(forms)
|
|
28
|
+
forms.flat_map { |form| expand_top(form) }
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
private
|
|
32
|
+
|
|
33
|
+
def expand_top(form)
|
|
34
|
+
if form.is_a?(List) && form.head.is_a?(Sym)
|
|
35
|
+
case form.head.name
|
|
36
|
+
when 'macro'
|
|
37
|
+
register_macro_form(form.rest)
|
|
38
|
+
return []
|
|
39
|
+
when 'macros'
|
|
40
|
+
register_macros_form(form.rest)
|
|
41
|
+
return []
|
|
42
|
+
when 'import-macros'
|
|
43
|
+
raise macro_error(:import_macros_unsupported, form)
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
[expand(form)]
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def macro_error(code, form, **args)
|
|
50
|
+
line = form.respond_to?(:line) ? form.line : nil
|
|
51
|
+
column = form.respond_to?(:column) ? form.column : nil
|
|
52
|
+
Error.new(Kapusta::Errors.format(code, **args), path: @path, line:, column:)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def expand(form)
|
|
56
|
+
case form
|
|
57
|
+
when List then expand_list(form)
|
|
58
|
+
when Vec then copy_position(Vec.new(form.items.map { |item| expand(item) }), form)
|
|
59
|
+
when HashLit
|
|
60
|
+
copy_position(
|
|
61
|
+
HashLit.new(form.entries.map do |entry|
|
|
62
|
+
entry.is_a?(Array) ? [expand(entry[0]), expand(entry[1])] : entry
|
|
63
|
+
end),
|
|
64
|
+
form
|
|
65
|
+
)
|
|
66
|
+
else
|
|
67
|
+
form
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def copy_position(target, source)
|
|
72
|
+
return target unless target.respond_to?(:line=) && source.respond_to?(:line)
|
|
73
|
+
|
|
74
|
+
target.line ||= source.line
|
|
75
|
+
target.column ||= source.column
|
|
76
|
+
target
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def expand_list(list)
|
|
80
|
+
return list if list.empty?
|
|
81
|
+
|
|
82
|
+
head = list.head
|
|
83
|
+
if head.is_a?(Sym) && !head.is_a?(AutoGensym)
|
|
84
|
+
name = head.name
|
|
85
|
+
case name
|
|
86
|
+
when 'macro'
|
|
87
|
+
register_macro_form(list.rest)
|
|
88
|
+
return List.new([Sym.new('do')])
|
|
89
|
+
when 'macros'
|
|
90
|
+
register_macros_form(list.rest)
|
|
91
|
+
return List.new([Sym.new('do')])
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
key = lookup_key(name)
|
|
95
|
+
if @macros.key?(key)
|
|
96
|
+
args = list.rest
|
|
97
|
+
result = invoke_macro(key, args)
|
|
98
|
+
return copy_position(expand(result), list)
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
copy_position(List.new(list.items.map { |item| expand(item) }), list)
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def lookup_key(name)
|
|
106
|
+
Kapusta.kebab_to_snake(name).to_sym
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def register_macro_form(args)
|
|
110
|
+
name_sym, params, *body = args
|
|
111
|
+
raise macro_error(:macro_name_must_be_symbol, name_sym) unless name_sym.is_a?(Sym)
|
|
112
|
+
raise macro_error(:macro_params_must_be_vector, params) unless params.is_a?(Vec)
|
|
113
|
+
|
|
114
|
+
register(name_sym.name, params, body)
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
def register_macros_form(args)
|
|
118
|
+
hash_lit = args[0]
|
|
119
|
+
raise macro_error(:macros_expects_hash, hash_lit) unless hash_lit.is_a?(HashLit)
|
|
120
|
+
|
|
121
|
+
hash_lit.pairs.each do |key, value|
|
|
122
|
+
raise macro_error(:macros_entry_must_be_fn, value, form: value.inspect) unless fn_form?(value)
|
|
123
|
+
|
|
124
|
+
name = key.to_s
|
|
125
|
+
params = value.items[1]
|
|
126
|
+
body = value.items[2..]
|
|
127
|
+
raise macro_error(:macros_entry_params_must_be_vector, params) unless params.is_a?(Vec)
|
|
128
|
+
|
|
129
|
+
register(name, params, body)
|
|
130
|
+
end
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
def fn_form?(value)
|
|
134
|
+
value.is_a?(List) && value.head.is_a?(Sym) && %w[fn lambda λ].include?(value.head.name)
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
def register(source_name, params, body)
|
|
138
|
+
proc_obj = compile_macro(source_name, params, body)
|
|
139
|
+
@macros[lookup_key(source_name)] = proc_obj
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
def compile_macro(name, params, body)
|
|
143
|
+
lowering = Lowering.new
|
|
144
|
+
lowered_body = body.map { |form| lowering.lower(form) }
|
|
145
|
+
gensym_locals = lowering.collected_gensyms
|
|
146
|
+
|
|
147
|
+
wrapped =
|
|
148
|
+
if gensym_locals.empty?
|
|
149
|
+
List.new([Sym.new('fn'), params, *lowered_body])
|
|
150
|
+
else
|
|
151
|
+
let_bindings = gensym_locals.flat_map do |gensym_sym, prefix|
|
|
152
|
+
[gensym_sym, List.new([Sym.new('quasi-gensym'), prefix])]
|
|
153
|
+
end
|
|
154
|
+
inner = lowered_body.length == 1 ? lowered_body.first : List.new([Sym.new('do'), *lowered_body])
|
|
155
|
+
List.new([Sym.new('fn'), params, List.new([Sym.new('let'), Vec.new(let_bindings), inner])])
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
macro_path = @path || "(macro #{name})"
|
|
159
|
+
ruby = Compiler.compile_forms([wrapped], path: macro_path)
|
|
160
|
+
TOPLEVEL_BINDING.eval(ruby, macro_path, 1)
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
def invoke_macro(key, args)
|
|
164
|
+
proc_obj = @macros[key]
|
|
165
|
+
proc_obj.call(*args)
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
class Lowering
|
|
169
|
+
def initialize
|
|
170
|
+
@gensyms = {}
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
def collected_gensyms
|
|
174
|
+
@gensyms.map { |prefix, sym| [sym, prefix] }
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
def lower(form)
|
|
178
|
+
case form
|
|
179
|
+
when Quasiquote then copy_position(lower_quasi(form.form), form)
|
|
180
|
+
when Unquote, UnquoteSplice
|
|
181
|
+
raise Error, Kapusta::Errors.format(:unquote_outside_quasiquote)
|
|
182
|
+
when AutoGensym
|
|
183
|
+
raise Error, Kapusta::Errors.format(:auto_gensym_outside_quasiquote, name: form.name)
|
|
184
|
+
when List then copy_position(List.new(form.items.map { |item| lower(item) }), form)
|
|
185
|
+
when Vec then copy_position(Vec.new(form.items.map { |item| lower(item) }), form)
|
|
186
|
+
when HashLit
|
|
187
|
+
copy_position(
|
|
188
|
+
HashLit.new(form.entries.map do |entry|
|
|
189
|
+
entry.is_a?(Array) ? [lower(entry[0]), lower(entry[1])] : entry
|
|
190
|
+
end),
|
|
191
|
+
form
|
|
192
|
+
)
|
|
193
|
+
else
|
|
194
|
+
form
|
|
195
|
+
end
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
def copy_position(target, source)
|
|
199
|
+
return target unless target.respond_to?(:line=) && source.respond_to?(:line)
|
|
200
|
+
|
|
201
|
+
target.line ||= source.line
|
|
202
|
+
target.column ||= source.column
|
|
203
|
+
target
|
|
204
|
+
end
|
|
205
|
+
|
|
206
|
+
def lower_quasi(form)
|
|
207
|
+
case form
|
|
208
|
+
when AutoGensym then gensym_local_for(form.name)
|
|
209
|
+
when Sym then List.new([Sym.new('quasi-sym'), form.name])
|
|
210
|
+
when List then lower_quasi_list(form)
|
|
211
|
+
when Vec then lower_quasi_vec(form)
|
|
212
|
+
when HashLit then lower_quasi_hash(form)
|
|
213
|
+
when Unquote then lower(form.form)
|
|
214
|
+
when UnquoteSplice
|
|
215
|
+
raise Error, Kapusta::Errors.format(:unquote_splice_outside_list)
|
|
216
|
+
when Quasiquote
|
|
217
|
+
raise Error, Kapusta::Errors.format(:nested_quasiquote)
|
|
218
|
+
else
|
|
219
|
+
form
|
|
220
|
+
end
|
|
221
|
+
end
|
|
222
|
+
|
|
223
|
+
def lower_quasi_list(list)
|
|
224
|
+
items = list.items
|
|
225
|
+
return List.new([Sym.new('quasi-list')]) if items.empty?
|
|
226
|
+
|
|
227
|
+
if (tail_expr = splice_tail(items))
|
|
228
|
+
head_items = items[0...-1].map { |item| lower_quasi(item) }
|
|
229
|
+
return List.new([Sym.new('quasi-list-tail'), Vec.new(head_items), tail_expr])
|
|
230
|
+
end
|
|
231
|
+
|
|
232
|
+
lowered_items = items.map { |item| lower_quasi_item(item) }
|
|
233
|
+
List.new([Sym.new('quasi-list'), *lowered_items])
|
|
234
|
+
end
|
|
235
|
+
|
|
236
|
+
def lower_quasi_vec(vec)
|
|
237
|
+
items = vec.items
|
|
238
|
+
if (tail_expr = splice_tail(items))
|
|
239
|
+
head_items = items[0...-1].map { |item| lower_quasi(item) }
|
|
240
|
+
return List.new([Sym.new('quasi-vec-tail'), Vec.new(head_items), tail_expr])
|
|
241
|
+
end
|
|
242
|
+
|
|
243
|
+
lowered_items = items.map { |item| lower_quasi_item(item) }
|
|
244
|
+
List.new([Sym.new('quasi-vec'), *lowered_items])
|
|
245
|
+
end
|
|
246
|
+
|
|
247
|
+
def lower_quasi_hash(hash)
|
|
248
|
+
parts = []
|
|
249
|
+
hash.entries.each do |entry|
|
|
250
|
+
next unless entry.is_a?(Array)
|
|
251
|
+
|
|
252
|
+
key, value = entry
|
|
253
|
+
parts << lower_quasi(key) << lower_quasi(value)
|
|
254
|
+
end
|
|
255
|
+
List.new([Sym.new('quasi-hash'), *parts])
|
|
256
|
+
end
|
|
257
|
+
|
|
258
|
+
def lower_quasi_item(item)
|
|
259
|
+
if item.is_a?(Unquote) && unpack_call?(item.form)
|
|
260
|
+
inner = lower(item.form.items[1])
|
|
261
|
+
List.new([Sym.new('.'), inner, 0])
|
|
262
|
+
else
|
|
263
|
+
lower_quasi(item)
|
|
264
|
+
end
|
|
265
|
+
end
|
|
266
|
+
|
|
267
|
+
def splice_tail(items)
|
|
268
|
+
last = items.last
|
|
269
|
+
return unless last
|
|
270
|
+
return lower(last.form) if last.is_a?(UnquoteSplice)
|
|
271
|
+
return lower(last.form.items[1]) if last.is_a?(Unquote) && unpack_call?(last.form)
|
|
272
|
+
|
|
273
|
+
nil
|
|
274
|
+
end
|
|
275
|
+
|
|
276
|
+
def unpack_call?(form)
|
|
277
|
+
form.is_a?(List) && form.head.is_a?(Sym) && form.head.name == 'unpack'
|
|
278
|
+
end
|
|
279
|
+
|
|
280
|
+
def gensym_local_for(prefix)
|
|
281
|
+
@gensyms[prefix] ||= MacroExpander.fresh_local_gensym(prefix)
|
|
282
|
+
end
|
|
283
|
+
end
|
|
284
|
+
end
|
|
285
|
+
end
|
|
286
|
+
end
|
|
@@ -10,9 +10,12 @@ module Kapusta
|
|
|
10
10
|
def normalize(form)
|
|
11
11
|
case form
|
|
12
12
|
when List then normalize_list(form)
|
|
13
|
-
when Vec then Vec.new(form.items.map { |item| normalize(item) })
|
|
13
|
+
when Vec then inherit_position(Vec.new(form.items.map { |item| normalize(item) }), form)
|
|
14
14
|
when HashLit
|
|
15
|
-
|
|
15
|
+
inherit_position(
|
|
16
|
+
HashLit.new(form.pairs.map { |key, value| [normalize_hash_key(key), normalize(value)] }),
|
|
17
|
+
form
|
|
18
|
+
)
|
|
16
19
|
else
|
|
17
20
|
form
|
|
18
21
|
end
|
|
@@ -32,19 +35,28 @@ module Kapusta
|
|
|
32
35
|
|
|
33
36
|
head = list.head
|
|
34
37
|
items = list.items.map { |item| normalize(item) }
|
|
35
|
-
return List.new(items) unless head.is_a?(Sym)
|
|
38
|
+
return inherit_position(List.new(items), list) unless head.is_a?(Sym)
|
|
36
39
|
|
|
37
40
|
case head.name
|
|
38
41
|
when 'when'
|
|
42
|
+
raise compiler_error(:when_no_body, list, form: head.name) if items[2..].empty?
|
|
43
|
+
|
|
39
44
|
cond = items[1]
|
|
40
45
|
body = wrap_do(items[2..])
|
|
41
|
-
List.new([Sym.new('if'), cond, body])
|
|
46
|
+
inherit_position(List.new([Sym.new('if'), cond, body]), list)
|
|
42
47
|
when 'unless'
|
|
48
|
+
raise compiler_error(:when_no_body, list, form: head.name) if items[2..].empty?
|
|
49
|
+
|
|
43
50
|
cond = items[1]
|
|
44
51
|
body = wrap_do(items[2..])
|
|
45
|
-
List.new([Sym.new('if'), List.new([Sym.new('not'), cond]), body])
|
|
52
|
+
inherit_position(List.new([Sym.new('if'), List.new([Sym.new('not'), cond]), body]), list)
|
|
46
53
|
when 'tset'
|
|
47
|
-
|
|
54
|
+
raise compiler_error(:tset_no_value, list) if items.length < 4
|
|
55
|
+
|
|
56
|
+
inherit_position(
|
|
57
|
+
List.new([Sym.new('set'), List.new([Sym.new('.'), items[1], items[2]]), items[3]]),
|
|
58
|
+
list
|
|
59
|
+
)
|
|
48
60
|
when 'pcall'
|
|
49
61
|
fn = items[1]
|
|
50
62
|
args = items[2..]
|
|
@@ -65,14 +77,28 @@ module Kapusta
|
|
|
65
77
|
List.new([Sym.new('values'), false, List.new([handler, Sym.new('e')])])])
|
|
66
78
|
])
|
|
67
79
|
when '->', '->>', '-?>', '-?>>'
|
|
68
|
-
normalize(thread(items[1..], head.name))
|
|
80
|
+
inherit_position(normalize(thread(items[1..], head.name)), list)
|
|
69
81
|
when 'doto'
|
|
70
|
-
normalize(doto(items[1..]))
|
|
82
|
+
inherit_position(normalize(doto(items[1..])), list)
|
|
71
83
|
else
|
|
72
|
-
List.new(items)
|
|
84
|
+
inherit_position(List.new(items), list)
|
|
73
85
|
end
|
|
74
86
|
end
|
|
75
87
|
|
|
88
|
+
def inherit_position(target, source)
|
|
89
|
+
return target unless target.respond_to?(:line=) && source.respond_to?(:line)
|
|
90
|
+
|
|
91
|
+
target.line ||= source.line
|
|
92
|
+
target.column ||= source.column
|
|
93
|
+
target
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def compiler_error(code, form, **args)
|
|
97
|
+
line = form.respond_to?(:line) ? form.line : nil
|
|
98
|
+
column = form.respond_to?(:column) ? form.column : nil
|
|
99
|
+
Compiler::Error.new(Kapusta::Errors.format(code, **args), line:, column:)
|
|
100
|
+
end
|
|
101
|
+
|
|
76
102
|
def wrap_do(forms)
|
|
77
103
|
return if forms.empty?
|
|
78
104
|
return forms.first if forms.length == 1
|
data/lib/kapusta/compiler.rb
CHANGED
|
@@ -3,12 +3,13 @@
|
|
|
3
3
|
require_relative 'error'
|
|
4
4
|
require_relative 'compiler/normalizer'
|
|
5
5
|
require_relative 'compiler/emitter'
|
|
6
|
+
require_relative 'compiler/macro_expander'
|
|
6
7
|
|
|
7
8
|
module Kapusta
|
|
8
9
|
module Compiler
|
|
9
10
|
class Error < Kapusta::Error; end
|
|
10
11
|
SPECIAL_FORMS = %w[
|
|
11
|
-
fn lambda λ let local var set if when unless case match
|
|
12
|
+
fn lambda λ let local var global set if when unless case match
|
|
12
13
|
while for each do values
|
|
13
14
|
-> ->> -?> -?>> doto
|
|
14
15
|
icollect collect fcollect accumulate faccumulate
|
|
@@ -27,12 +28,23 @@ module Kapusta
|
|
|
27
28
|
= not= < <= > >=
|
|
28
29
|
+ - * / %
|
|
29
30
|
print
|
|
31
|
+
macro macros import-macros
|
|
32
|
+
quasi-sym quasi-list quasi-list-tail quasi-vec quasi-vec-tail quasi-hash quasi-gensym
|
|
30
33
|
].freeze
|
|
31
34
|
|
|
32
35
|
def self.compile(source, path: '(kapusta)')
|
|
33
36
|
forms = Reader.read_all(source)
|
|
37
|
+
expanded = MacroExpander.new(path:).expand_all(forms)
|
|
38
|
+
compile_forms(expanded, path:)
|
|
39
|
+
rescue Kapusta::Error => e
|
|
40
|
+
raise e.with_defaults(path:)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def self.compile_forms(forms, path: '(kapusta)')
|
|
34
44
|
normalized = Normalizer.new.normalize_all(forms)
|
|
35
45
|
Emitter.new(path:).emit_file(normalized)
|
|
46
|
+
rescue Kapusta::Error => e
|
|
47
|
+
raise e.with_defaults(path:)
|
|
36
48
|
end
|
|
37
49
|
|
|
38
50
|
def self.run(source, path: '(kapusta)')
|
data/lib/kapusta/error.rb
CHANGED
|
@@ -1,5 +1,29 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module Kapusta
|
|
4
|
-
class Error < StandardError
|
|
4
|
+
class Error < StandardError
|
|
5
|
+
attr_reader :path, :line, :column, :reason
|
|
6
|
+
|
|
7
|
+
def initialize(reason, path: nil, line: nil, column: nil)
|
|
8
|
+
@reason = reason
|
|
9
|
+
@path = path
|
|
10
|
+
@line = line
|
|
11
|
+
@column = column
|
|
12
|
+
super(formatted)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def formatted
|
|
16
|
+
prefix = [path, line, column].compact.join(':')
|
|
17
|
+
prefix.empty? ? reason : "#{prefix}: #{reason}"
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def with_defaults(path: nil, line: nil, column: nil)
|
|
21
|
+
copy = self.class.new(@reason,
|
|
22
|
+
path: @path || path,
|
|
23
|
+
line: @line || line,
|
|
24
|
+
column: @column || column)
|
|
25
|
+
copy.set_backtrace(backtrace) if backtrace
|
|
26
|
+
copy
|
|
27
|
+
end
|
|
28
|
+
end
|
|
5
29
|
end
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Kapusta
|
|
4
|
+
# rubocop:disable Style/FormatStringToken
|
|
5
|
+
module Errors
|
|
6
|
+
MESSAGES = {
|
|
7
|
+
accumulate_no_iterator: 'expected initial value and iterator binding table',
|
|
8
|
+
auto_gensym_outside_quasiquote: 'auto-gensym %{name}# outside quasiquote',
|
|
9
|
+
bad_multisym: 'bad multisym: %{path}',
|
|
10
|
+
bad_set_target: 'bad set target: %{target}',
|
|
11
|
+
bad_shorthand: 'bad shorthand',
|
|
12
|
+
bind_table_dots: 'unable to bind table ...',
|
|
13
|
+
cannot_call_literal: 'cannot call literal value %{value}',
|
|
14
|
+
cannot_emit_form: 'cannot emit form: %{form}',
|
|
15
|
+
cannot_set_method_binding: 'cannot set method binding: %{name}',
|
|
16
|
+
case_no_patterns: 'expected at least one pattern/body pair',
|
|
17
|
+
case_odd_patterns: 'expected even number of pattern/body pairs',
|
|
18
|
+
case_unsupported: 'case/match clauses use patterns this compiler cannot translate',
|
|
19
|
+
could_not_destructure_literal: 'could not destructure literal',
|
|
20
|
+
could_not_read_number: 'could not read number "%{token}"',
|
|
21
|
+
counted_no_range: 'expected range to include start and stop',
|
|
22
|
+
destructure_unsupported: 'destructure pattern this compiler cannot translate: %{pattern}',
|
|
23
|
+
dot_no_args: 'expected table argument',
|
|
24
|
+
each_no_binding: 'expected binding table',
|
|
25
|
+
empty_call: 'expected a function, macro, or special to call',
|
|
26
|
+
empty_token: 'empty token',
|
|
27
|
+
expected_var: 'expected var %{name}',
|
|
28
|
+
fn_no_params: 'expected parameters table',
|
|
29
|
+
global_arity: 'expected name and value',
|
|
30
|
+
global_non_symbol_name: 'unable to bind %{type} %{value}',
|
|
31
|
+
icollect_no_iterator: 'expected iterator binding table',
|
|
32
|
+
if_no_body: 'expected condition and body',
|
|
33
|
+
import_macros_unsupported: 'import-macros is not yet supported',
|
|
34
|
+
invalid_class_name: 'invalid class name: %{name}',
|
|
35
|
+
invalid_module_name: 'invalid module name: %{name}',
|
|
36
|
+
let_no_body: 'expected body expression',
|
|
37
|
+
let_odd_bindings: 'expected even number of name/value bindings',
|
|
38
|
+
local_arity: '%{form}: expected name and value',
|
|
39
|
+
macro_name_must_be_symbol: 'macro name must be a symbol',
|
|
40
|
+
macro_params_must_be_vector: 'macro params must be a vector',
|
|
41
|
+
macro_unsafe_bind: 'macro tried to bind %{name} without gensym',
|
|
42
|
+
macros_entry_must_be_fn: 'macros entry value must be a fn form, got %{form}',
|
|
43
|
+
macros_entry_params_must_be_vector: 'macros entry params must be a vector',
|
|
44
|
+
macros_expects_hash: 'macros expects a hash literal',
|
|
45
|
+
nested_quasiquote: 'nested quasiquote is not supported',
|
|
46
|
+
odd_forms_in_hash: 'odd number of forms in hash',
|
|
47
|
+
rest_not_last: 'expected rest argument before last parameter',
|
|
48
|
+
shadowed_special: 'local %{name} was overshadowed by a special form or macro',
|
|
49
|
+
special_must_be_toplevel: '%{name} must appear at the top level and is consumed by the macro expander',
|
|
50
|
+
tset_no_value: 'tset: expected table, key, and value arguments',
|
|
51
|
+
unclosed_delimiter: "unclosed opening delimiter '%{char}'",
|
|
52
|
+
undefined_symbol: 'undefined symbol: %{name}',
|
|
53
|
+
unexpected_closing_delimiter: "unexpected closing delimiter '%{char}'",
|
|
54
|
+
unexpected_eof: 'unexpected eof',
|
|
55
|
+
unknown_special_form: 'unknown special form: %{name}',
|
|
56
|
+
unquote_outside_quasiquote: 'unquote outside quasiquote',
|
|
57
|
+
unquote_splice_outside_list: 'unquote-splice must appear inside a quoted list/vec',
|
|
58
|
+
unterminated_string: 'unterminated string',
|
|
59
|
+
vararg_with_operator: 'tried to use vararg with operator',
|
|
60
|
+
when_no_body: '%{form}: expected body'
|
|
61
|
+
}.freeze
|
|
62
|
+
|
|
63
|
+
def self.format(code, **args)
|
|
64
|
+
template = MESSAGES.fetch(code) { raise ArgumentError, "unknown error code: #{code.inspect}" }
|
|
65
|
+
args.empty? ? template.dup : (template % args)
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
# rubocop:enable Style/FormatStringToken
|
|
69
|
+
end
|