kapusta 0.4.1 → 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/bin/fennel-parity +36 -6
- data/examples/even-squares.kap +22 -7
- data/examples/roman-to-integer.kap +3 -3
- data/lib/kapusta/ast.rb +38 -4
- 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 +26 -15
- data/lib/kapusta/compiler/emitter/interop.rb +6 -4
- data/lib/kapusta/compiler/emitter/patterns.rb +22 -1
- data/lib/kapusta/compiler/emitter/support.rb +44 -22
- data/lib/kapusta/compiler/macro_expander.rb +55 -25
- data/lib/kapusta/compiler/normalizer.rb +35 -9
- data/lib/kapusta/compiler.rb +6 -2
- data/lib/kapusta/error.rb +25 -1
- data/lib/kapusta/errors.rb +69 -0
- data/lib/kapusta/formatter.rb +12 -5
- data/lib/kapusta/reader.rb +34 -12
- data/lib/kapusta/version.rb +1 -1
- data/lib/kapusta.rb +1 -0
- data/spec/examples_errors_spec.rb +229 -0
- data/spec/formatter_spec.rb +7 -6
- metadata +3 -2
- data/spec/reader_spec.rb +0 -26
|
@@ -7,6 +7,8 @@ module Kapusta
|
|
|
7
7
|
private
|
|
8
8
|
|
|
9
9
|
def emit_lookup(args, env, current_scope)
|
|
10
|
+
emit_error!(:dot_no_args) if args.empty?
|
|
11
|
+
|
|
10
12
|
object_code = emit_expr(args[0], env, current_scope)
|
|
11
13
|
keys = args[1..].map { |arg| emit_expr(arg, env, current_scope) }
|
|
12
14
|
return object_code if keys.empty?
|
|
@@ -91,7 +93,7 @@ module Kapusta
|
|
|
91
93
|
|
|
92
94
|
def emit_module_wrapper(name_sym, body)
|
|
93
95
|
segments = constant_segments(name_sym)
|
|
94
|
-
emit_error!(
|
|
96
|
+
emit_error!(:invalid_module_name, name: name_sym.name) unless segments
|
|
95
97
|
inner = build_nested_module(segments, body)
|
|
96
98
|
['(-> do', indent(inner), indent(segments.join('::')), 'end).call'].join("\n")
|
|
97
99
|
end
|
|
@@ -105,7 +107,7 @@ module Kapusta
|
|
|
105
107
|
|
|
106
108
|
def emit_class_wrapper(name_sym, supers, env, body)
|
|
107
109
|
segments = constant_segments(name_sym)
|
|
108
|
-
emit_error!(
|
|
110
|
+
emit_error!(:invalid_class_name, name: name_sym.name) unless segments
|
|
109
111
|
super_code = class_super_code(supers, env)
|
|
110
112
|
inner = build_nested_class(segments, super_code, body)
|
|
111
113
|
['(-> do', indent(inner), indent(segments.join('::')), 'end).call'].join("\n")
|
|
@@ -434,7 +436,7 @@ module Kapusta
|
|
|
434
436
|
return 'ARGV' if name == 'ARGV'
|
|
435
437
|
return name if name.match?(/\A[A-Z]/)
|
|
436
438
|
|
|
437
|
-
emit_error!(
|
|
439
|
+
emit_error!(:undefined_symbol, name:)
|
|
438
440
|
end
|
|
439
441
|
|
|
440
442
|
def emit_gvar(sym)
|
|
@@ -458,7 +460,7 @@ module Kapusta
|
|
|
458
460
|
const_path << segments[idx]
|
|
459
461
|
idx += 1
|
|
460
462
|
end
|
|
461
|
-
emit_error!(
|
|
463
|
+
emit_error!(:bad_multisym, path: segments.join('.')) if const_path.empty?
|
|
462
464
|
|
|
463
465
|
[const_path.join('::'), segments[idx..]]
|
|
464
466
|
end
|
|
@@ -10,6 +10,7 @@ module Kapusta
|
|
|
10
10
|
if pattern.is_a?(Sym)
|
|
11
11
|
return ['', env] if pattern.name == '_'
|
|
12
12
|
|
|
13
|
+
validate_binding_symbol!(pattern)
|
|
13
14
|
ruby_name = define_local(env, pattern)
|
|
14
15
|
return ["#{ruby_name} = #{value_code}", env]
|
|
15
16
|
end
|
|
@@ -17,7 +18,26 @@ module Kapusta
|
|
|
17
18
|
native = try_emit_native_pattern_bind(pattern, value_code, env)
|
|
18
19
|
return native if native
|
|
19
20
|
|
|
20
|
-
emit_error!(
|
|
21
|
+
emit_error!(:destructure_unsupported, pattern: pattern.inspect)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def validate_binding_symbol!(sym)
|
|
25
|
+
name = sym.name
|
|
26
|
+
emit_error!(:shadowed_special, name:) if Compiler::SPECIAL_FORMS.include?(name)
|
|
27
|
+
return unless sym.is_a?(MacroSym)
|
|
28
|
+
|
|
29
|
+
emit_error!(:macro_unsafe_bind, name:)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def validate_destructure_pattern!(pattern)
|
|
33
|
+
items = pattern.items
|
|
34
|
+
items.each_with_index do |item, idx|
|
|
35
|
+
emit_error!(:bind_table_dots) if item.is_a?(Sym) && item.name == '...'
|
|
36
|
+
next unless item.is_a?(Sym) && item.name == '&'
|
|
37
|
+
|
|
38
|
+
emit_error!(:rest_not_last) if idx + 2 < items.length
|
|
39
|
+
emit_error!(:rest_not_last) if idx + 1 >= items.length
|
|
40
|
+
end
|
|
21
41
|
end
|
|
22
42
|
|
|
23
43
|
def try_emit_native_pattern_bind(pattern, value_code, env)
|
|
@@ -32,6 +52,7 @@ module Kapusta
|
|
|
32
52
|
end
|
|
33
53
|
|
|
34
54
|
def try_emit_native_vec_bind(pattern, value_code, env)
|
|
55
|
+
validate_destructure_pattern!(pattern)
|
|
35
56
|
parts = []
|
|
36
57
|
deferred = []
|
|
37
58
|
current_env = env
|
|
@@ -6,8 +6,27 @@ module Kapusta
|
|
|
6
6
|
module Support
|
|
7
7
|
private
|
|
8
8
|
|
|
9
|
-
def emit_error!(
|
|
10
|
-
|
|
9
|
+
def emit_error!(code, **args)
|
|
10
|
+
form = current_form
|
|
11
|
+
line = form.respond_to?(:line) ? form.line : nil
|
|
12
|
+
column = form.respond_to?(:column) ? form.column : nil
|
|
13
|
+
raise Error.new(Kapusta::Errors.format(code, **args), path: @path, line:, column:)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def with_current_form(form)
|
|
17
|
+
@form_stack ||= []
|
|
18
|
+
@form_stack.push(form) if positionable?(form)
|
|
19
|
+
yield
|
|
20
|
+
ensure
|
|
21
|
+
@form_stack.pop if positionable?(form)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def current_form
|
|
25
|
+
(@form_stack ||= []).last
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def positionable?(form)
|
|
29
|
+
form.respond_to?(:line) && form.respond_to?(:column)
|
|
11
30
|
end
|
|
12
31
|
|
|
13
32
|
def emit_forms_with_headers(forms, env, current_scope, result: true)
|
|
@@ -67,27 +86,29 @@ module Kapusta
|
|
|
67
86
|
end
|
|
68
87
|
|
|
69
88
|
def emit_form_in_sequence(form, env, current_scope, allow_method_definitions:, result_needed: true)
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
89
|
+
with_current_form(form) do
|
|
90
|
+
if allow_method_definitions &&
|
|
91
|
+
method_definition_form?(form) &&
|
|
92
|
+
%i[toplevel module class].include?(current_scope)
|
|
93
|
+
code, env = emit_definition_form(form, env, current_scope)
|
|
94
|
+
return [code, env] if code
|
|
95
|
+
end
|
|
76
96
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
97
|
+
if named_function_form?(form)
|
|
98
|
+
emit_named_fn_assignment(form, env, current_scope)
|
|
99
|
+
elsif local_form?(form)
|
|
100
|
+
code, env = emit_local_form(form, env, current_scope)
|
|
101
|
+
code = code.delete_suffix("\nnil") unless result_needed
|
|
102
|
+
[code, env]
|
|
103
|
+
elsif do_form?(form)
|
|
104
|
+
emit_do_form(form.rest, env, current_scope, result_needed:)
|
|
105
|
+
elsif sequence_statement_form?(form)
|
|
106
|
+
emit_sequence_statement_form(form, env, current_scope, result_needed:)
|
|
107
|
+
elsif set_new_local_form?(form, env)
|
|
108
|
+
emit_set_form(form, env, current_scope)
|
|
109
|
+
else
|
|
110
|
+
[emit_expr(form, env, current_scope), env]
|
|
111
|
+
end
|
|
91
112
|
end
|
|
92
113
|
end
|
|
93
114
|
|
|
@@ -228,6 +249,7 @@ module Kapusta
|
|
|
228
249
|
end
|
|
229
250
|
|
|
230
251
|
def parse_counted_for_bindings(bindings, env, current_scope)
|
|
252
|
+
emit_error!(:counted_no_range) if bindings.length < 3
|
|
231
253
|
name_sym = bindings[0]
|
|
232
254
|
loop_env = env.child
|
|
233
255
|
ruby_name = define_local(loop_env, name_sym.name)
|
|
@@ -19,8 +19,9 @@ module Kapusta
|
|
|
19
19
|
end
|
|
20
20
|
end
|
|
21
21
|
|
|
22
|
-
def initialize
|
|
22
|
+
def initialize(path: nil)
|
|
23
23
|
@macros = {}
|
|
24
|
+
@path = path
|
|
24
25
|
end
|
|
25
26
|
|
|
26
27
|
def expand_all(forms)
|
|
@@ -39,25 +40,42 @@ module Kapusta
|
|
|
39
40
|
register_macros_form(form.rest)
|
|
40
41
|
return []
|
|
41
42
|
when 'import-macros'
|
|
42
|
-
raise
|
|
43
|
+
raise macro_error(:import_macros_unsupported, form)
|
|
43
44
|
end
|
|
44
45
|
end
|
|
45
46
|
[expand(form)]
|
|
46
47
|
end
|
|
47
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
|
+
|
|
48
55
|
def expand(form)
|
|
49
56
|
case form
|
|
50
57
|
when List then expand_list(form)
|
|
51
|
-
when Vec then Vec.new(form.items.map { |item| expand(item) })
|
|
58
|
+
when Vec then copy_position(Vec.new(form.items.map { |item| expand(item) }), form)
|
|
52
59
|
when HashLit
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
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
|
+
)
|
|
56
66
|
else
|
|
57
67
|
form
|
|
58
68
|
end
|
|
59
69
|
end
|
|
60
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
|
+
|
|
61
79
|
def expand_list(list)
|
|
62
80
|
return list if list.empty?
|
|
63
81
|
|
|
@@ -77,11 +95,11 @@ module Kapusta
|
|
|
77
95
|
if @macros.key?(key)
|
|
78
96
|
args = list.rest
|
|
79
97
|
result = invoke_macro(key, args)
|
|
80
|
-
return expand(result)
|
|
98
|
+
return copy_position(expand(result), list)
|
|
81
99
|
end
|
|
82
100
|
end
|
|
83
101
|
|
|
84
|
-
List.new(list.items.map { |item| expand(item) })
|
|
102
|
+
copy_position(List.new(list.items.map { |item| expand(item) }), list)
|
|
85
103
|
end
|
|
86
104
|
|
|
87
105
|
def lookup_key(name)
|
|
@@ -90,23 +108,23 @@ module Kapusta
|
|
|
90
108
|
|
|
91
109
|
def register_macro_form(args)
|
|
92
110
|
name_sym, params, *body = args
|
|
93
|
-
raise
|
|
94
|
-
raise
|
|
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)
|
|
95
113
|
|
|
96
114
|
register(name_sym.name, params, body)
|
|
97
115
|
end
|
|
98
116
|
|
|
99
117
|
def register_macros_form(args)
|
|
100
118
|
hash_lit = args[0]
|
|
101
|
-
raise
|
|
119
|
+
raise macro_error(:macros_expects_hash, hash_lit) unless hash_lit.is_a?(HashLit)
|
|
102
120
|
|
|
103
121
|
hash_lit.pairs.each do |key, value|
|
|
104
|
-
raise
|
|
122
|
+
raise macro_error(:macros_entry_must_be_fn, value, form: value.inspect) unless fn_form?(value)
|
|
105
123
|
|
|
106
124
|
name = key.to_s
|
|
107
125
|
params = value.items[1]
|
|
108
126
|
body = value.items[2..]
|
|
109
|
-
raise
|
|
127
|
+
raise macro_error(:macros_entry_params_must_be_vector, params) unless params.is_a?(Vec)
|
|
110
128
|
|
|
111
129
|
register(name, params, body)
|
|
112
130
|
end
|
|
@@ -137,8 +155,9 @@ module Kapusta
|
|
|
137
155
|
List.new([Sym.new('fn'), params, List.new([Sym.new('let'), Vec.new(let_bindings), inner])])
|
|
138
156
|
end
|
|
139
157
|
|
|
140
|
-
|
|
141
|
-
|
|
158
|
+
macro_path = @path || "(macro #{name})"
|
|
159
|
+
ruby = Compiler.compile_forms([wrapped], path: macro_path)
|
|
160
|
+
TOPLEVEL_BINDING.eval(ruby, macro_path, 1)
|
|
142
161
|
end
|
|
143
162
|
|
|
144
163
|
def invoke_macro(key, args)
|
|
@@ -157,22 +176,33 @@ module Kapusta
|
|
|
157
176
|
|
|
158
177
|
def lower(form)
|
|
159
178
|
case form
|
|
160
|
-
when Quasiquote then lower_quasi(form.form)
|
|
179
|
+
when Quasiquote then copy_position(lower_quasi(form.form), form)
|
|
161
180
|
when Unquote, UnquoteSplice
|
|
162
|
-
raise Error,
|
|
181
|
+
raise Error, Kapusta::Errors.format(:unquote_outside_quasiquote)
|
|
163
182
|
when AutoGensym
|
|
164
|
-
raise Error,
|
|
165
|
-
when List then List.new(form.items.map { |item| lower(item) })
|
|
166
|
-
when Vec then Vec.new(form.items.map { |item| lower(item) })
|
|
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)
|
|
167
186
|
when HashLit
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
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
|
+
)
|
|
171
193
|
else
|
|
172
194
|
form
|
|
173
195
|
end
|
|
174
196
|
end
|
|
175
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
|
+
|
|
176
206
|
def lower_quasi(form)
|
|
177
207
|
case form
|
|
178
208
|
when AutoGensym then gensym_local_for(form.name)
|
|
@@ -182,9 +212,9 @@ module Kapusta
|
|
|
182
212
|
when HashLit then lower_quasi_hash(form)
|
|
183
213
|
when Unquote then lower(form.form)
|
|
184
214
|
when UnquoteSplice
|
|
185
|
-
raise Error,
|
|
215
|
+
raise Error, Kapusta::Errors.format(:unquote_splice_outside_list)
|
|
186
216
|
when Quasiquote
|
|
187
|
-
raise Error,
|
|
217
|
+
raise Error, Kapusta::Errors.format(:nested_quasiquote)
|
|
188
218
|
else
|
|
189
219
|
form
|
|
190
220
|
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
|
@@ -9,7 +9,7 @@ module Kapusta
|
|
|
9
9
|
module Compiler
|
|
10
10
|
class Error < Kapusta::Error; end
|
|
11
11
|
SPECIAL_FORMS = %w[
|
|
12
|
-
fn lambda λ let local var set if when unless case match
|
|
12
|
+
fn lambda λ let local var global set if when unless case match
|
|
13
13
|
while for each do values
|
|
14
14
|
-> ->> -?> -?>> doto
|
|
15
15
|
icollect collect fcollect accumulate faccumulate
|
|
@@ -34,13 +34,17 @@ module Kapusta
|
|
|
34
34
|
|
|
35
35
|
def self.compile(source, path: '(kapusta)')
|
|
36
36
|
forms = Reader.read_all(source)
|
|
37
|
-
expanded = MacroExpander.new.expand_all(forms)
|
|
37
|
+
expanded = MacroExpander.new(path:).expand_all(forms)
|
|
38
38
|
compile_forms(expanded, path:)
|
|
39
|
+
rescue Kapusta::Error => e
|
|
40
|
+
raise e.with_defaults(path:)
|
|
39
41
|
end
|
|
40
42
|
|
|
41
43
|
def self.compile_forms(forms, path: '(kapusta)')
|
|
42
44
|
normalized = Normalizer.new.normalize_all(forms)
|
|
43
45
|
Emitter.new(path:).emit_file(normalized)
|
|
46
|
+
rescue Kapusta::Error => e
|
|
47
|
+
raise e.with_defaults(path:)
|
|
44
48
|
end
|
|
45
49
|
|
|
46
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
|
data/lib/kapusta/formatter.rb
CHANGED
|
@@ -27,7 +27,8 @@ module Kapusta
|
|
|
27
27
|
|
|
28
28
|
formatted = @files.map do |path|
|
|
29
29
|
original = read_source(path)
|
|
30
|
-
|
|
30
|
+
validate_kapusta_source(original, path)
|
|
31
|
+
[path, original, format_source(original, path)]
|
|
31
32
|
end
|
|
32
33
|
|
|
33
34
|
case @mode
|
|
@@ -48,13 +49,17 @@ module Kapusta
|
|
|
48
49
|
end
|
|
49
50
|
|
|
50
51
|
0
|
|
51
|
-
rescue Error => e
|
|
52
|
-
warn e.
|
|
52
|
+
rescue Kapusta::Error => e
|
|
53
|
+
warn e.formatted
|
|
53
54
|
1
|
|
54
55
|
end
|
|
55
56
|
|
|
56
57
|
private
|
|
57
58
|
|
|
59
|
+
def validate_kapusta_source(source, path)
|
|
60
|
+
Kapusta::Compiler.compile(source, path:)
|
|
61
|
+
end
|
|
62
|
+
|
|
58
63
|
def parse_args(argv)
|
|
59
64
|
argv.each do |arg|
|
|
60
65
|
case arg
|
|
@@ -99,7 +104,7 @@ module Kapusta
|
|
|
99
104
|
$stdin.read
|
|
100
105
|
end
|
|
101
106
|
|
|
102
|
-
def format_source(source)
|
|
107
|
+
def format_source(source, path = nil)
|
|
103
108
|
forms = Reader.read_all(source, preserve_comments: true)
|
|
104
109
|
entries = top_level_entries(forms)
|
|
105
110
|
return '' if entries.empty?
|
|
@@ -110,8 +115,10 @@ module Kapusta
|
|
|
110
115
|
output << render_top_level_entry(entry)
|
|
111
116
|
end
|
|
112
117
|
output << "\n"
|
|
118
|
+
rescue Kapusta::Error => e
|
|
119
|
+
raise e.with_defaults(path:)
|
|
113
120
|
rescue StandardError => e
|
|
114
|
-
raise Error
|
|
121
|
+
raise Error.new(e.message, path:)
|
|
115
122
|
end
|
|
116
123
|
|
|
117
124
|
def separator_for(_previous, _current)
|