kapusta 0.4.1 → 0.7.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 +24 -6
- data/bin/fennel-parity +38 -6
- data/examples/classify-wallet.kap +11 -0
- data/examples/even-squares.kap +22 -7
- data/examples/power-of-three.kap +12 -0
- data/examples/roman-to-integer.kap +3 -3
- data/exe/kapusta-ls +14 -0
- data/kapusta.gemspec +2 -2
- data/lib/kapusta/ast.rb +38 -4
- data/lib/kapusta/cli.rb +3 -0
- data/lib/kapusta/compiler/emitter/bindings.rb +90 -10
- data/lib/kapusta/compiler/emitter/collections.rb +85 -49
- data/lib/kapusta/compiler/emitter/control_flow.rb +31 -6
- data/lib/kapusta/compiler/emitter/expressions.rb +25 -16
- data/lib/kapusta/compiler/emitter/interop.rb +8 -5
- data/lib/kapusta/compiler/emitter/patterns.rb +74 -5
- data/lib/kapusta/compiler/emitter/support.rb +45 -23
- data/lib/kapusta/compiler/emitter.rb +1 -1
- data/lib/kapusta/compiler/lua_compat.rb +149 -0
- data/lib/kapusta/compiler/macro_expander.rb +57 -25
- data/lib/kapusta/compiler/normalizer.rb +39 -28
- data/lib/kapusta/compiler.rb +10 -4
- data/lib/kapusta/error.rb +25 -1
- data/lib/kapusta/errors.rb +70 -0
- data/lib/kapusta/formatter.rb +16 -5
- data/lib/kapusta/lsp.rb +258 -0
- data/lib/kapusta/reader.rb +33 -13
- data/lib/kapusta/version.rb +1 -1
- data/lib/kapusta.rb +1 -0
- data/spec/examples_errors_spec.rb +354 -0
- data/spec/formatter_spec.rb +7 -6
- data/spec/lsp_spec.rb +83 -0
- metadata +10 -2
- data/spec/reader_spec.rb +0 -26
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Kapusta
|
|
4
|
+
module Compiler
|
|
5
|
+
module LuaCompat
|
|
6
|
+
SPECIAL_FORMS = %w[pcall xpcall].freeze
|
|
7
|
+
ITERATOR_FORMS = %w[ipairs pairs].freeze
|
|
8
|
+
|
|
9
|
+
def self.special_form?(name)
|
|
10
|
+
SPECIAL_FORMS.include?(name)
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def self.iterator_form?(name)
|
|
14
|
+
ITERATOR_FORMS.include?(name)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
module Normalization
|
|
18
|
+
private
|
|
19
|
+
|
|
20
|
+
def normalize_lua_compat_form(name, items)
|
|
21
|
+
case name
|
|
22
|
+
when 'pcall' then normalize_lua_pcall(items)
|
|
23
|
+
when 'xpcall' then normalize_lua_xpcall(items)
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def normalize_lua_pcall(items)
|
|
28
|
+
fn = items[1]
|
|
29
|
+
args = items[2..]
|
|
30
|
+
List.new([
|
|
31
|
+
Sym.new('try'),
|
|
32
|
+
List.new([Sym.new('values'), true, List.new([fn, *args])]),
|
|
33
|
+
List.new([Sym.new('catch'), Sym.new('StandardError'), Sym.new('e'),
|
|
34
|
+
List.new([Sym.new('values'), false, Sym.new('e')])])
|
|
35
|
+
])
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def normalize_lua_xpcall(items)
|
|
39
|
+
fn = items[1]
|
|
40
|
+
handler = items[2]
|
|
41
|
+
args = items[3..]
|
|
42
|
+
List.new([
|
|
43
|
+
Sym.new('try'),
|
|
44
|
+
List.new([Sym.new('values'), true, List.new([fn, *args])]),
|
|
45
|
+
List.new([Sym.new('catch'), Sym.new('StandardError'), Sym.new('e'),
|
|
46
|
+
List.new([Sym.new('values'), false, List.new([handler, Sym.new('e')])])])
|
|
47
|
+
])
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
module Emission
|
|
52
|
+
private
|
|
53
|
+
|
|
54
|
+
def emit_lua_compat_inject(iter_expr, binding_pats, body_env, env, current_scope, acc_var,
|
|
55
|
+
init_code, body_forms)
|
|
56
|
+
return unless lua_iterator_expr?(iter_expr)
|
|
57
|
+
|
|
58
|
+
case iter_expr.head.name
|
|
59
|
+
when 'ipairs'
|
|
60
|
+
emit_lua_ipairs_inject(iter_expr, binding_pats, body_env, env, current_scope,
|
|
61
|
+
acc_var, init_code, body_forms)
|
|
62
|
+
when 'pairs'
|
|
63
|
+
emit_lua_pairs_inject(iter_expr, binding_pats, body_env, env, current_scope,
|
|
64
|
+
acc_var, init_code, body_forms)
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def emit_lua_compat_iteration(iter_expr, binding_pats, env, current_scope, method:,
|
|
69
|
+
extra_block_param: nil, &block)
|
|
70
|
+
return unless lua_iterator_expr?(iter_expr)
|
|
71
|
+
|
|
72
|
+
case iter_expr.head.name
|
|
73
|
+
when 'ipairs'
|
|
74
|
+
emit_lua_ipairs_iteration(iter_expr, binding_pats, env, current_scope,
|
|
75
|
+
method:, extra_block_param:, &block)
|
|
76
|
+
when 'pairs'
|
|
77
|
+
emit_lua_pairs_iteration(iter_expr, binding_pats, env, current_scope,
|
|
78
|
+
method:, extra_block_param:, &block)
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def lua_iterator_expr?(expr)
|
|
83
|
+
expr.is_a?(List) && expr.head.is_a?(Sym) && LuaCompat.iterator_form?(expr.head.name)
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def emit_lua_ipairs_inject(iter_expr, binding_pats, body_env, env, current_scope, acc_var,
|
|
87
|
+
init_code, body_forms)
|
|
88
|
+
coll_code = emit_expr(iter_expr.items[1], env, current_scope)
|
|
89
|
+
value_var, value_bind = bind_iteration_param(binding_pats[1], 'value', body_env)
|
|
90
|
+
if ignored_pattern?(binding_pats[0])
|
|
91
|
+
body_code, = emit_sequence(body_forms, body_env, current_scope, allow_method_definitions: false)
|
|
92
|
+
return inject_block(coll_code, "#{acc_var}, #{value_var}", init_code, value_bind || '', body_code)
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
index_var, index_bind = bind_iteration_param(binding_pats[0], 'index', body_env)
|
|
96
|
+
bind_code = [index_bind, value_bind].compact.join("\n")
|
|
97
|
+
body_code, = emit_sequence(body_forms, body_env, current_scope, allow_method_definitions: false)
|
|
98
|
+
inject_block("#{coll_code}.each_with_index", "#{acc_var}, (#{value_var}, #{index_var})",
|
|
99
|
+
init_code, bind_code, body_code)
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def emit_lua_pairs_inject(iter_expr, binding_pats, body_env, env, current_scope, acc_var,
|
|
103
|
+
init_code, body_forms)
|
|
104
|
+
key_var, key_bind = bind_iteration_param(binding_pats[0], 'key', body_env)
|
|
105
|
+
value_var, value_bind = bind_iteration_param(binding_pats[1], 'value', body_env)
|
|
106
|
+
bind_code = [key_bind, value_bind].compact.join("\n")
|
|
107
|
+
body_code, = emit_sequence(body_forms, body_env, current_scope, allow_method_definitions: false)
|
|
108
|
+
coll_code = emit_expr(iter_expr.items[1], env, current_scope)
|
|
109
|
+
inject_block(coll_code, "#{acc_var}, (#{key_var}, #{value_var})",
|
|
110
|
+
init_code, bind_code, body_code)
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
def emit_lua_ipairs_iteration(iter_expr, binding_pats, env, current_scope, method:,
|
|
114
|
+
extra_block_param: nil, &block)
|
|
115
|
+
body_env = env.child
|
|
116
|
+
value_var, value_bind = bind_iteration_param(binding_pats[1], 'value', body_env)
|
|
117
|
+
coll_code = emit_expr(iter_expr.items[1], env, current_scope)
|
|
118
|
+
if ignored_pattern?(binding_pats[0])
|
|
119
|
+
bind_code = value_bind || ''
|
|
120
|
+
body_code = block.call(body_env)
|
|
121
|
+
params = extra_block_param ? "#{value_var}, #{extra_block_param}" : value_var
|
|
122
|
+
return iteration_block("#{coll_code}.#{method} do |#{params}|", bind_code, body_code)
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
index_var, index_bind = bind_iteration_param(binding_pats[0], 'index', body_env)
|
|
126
|
+
bind_code = [index_bind, value_bind].compact.join("\n")
|
|
127
|
+
body_code = block.call(body_env)
|
|
128
|
+
receiver = method == 'each' ? "#{coll_code}.each_with_index" : "#{coll_code}.each_with_index.#{method}"
|
|
129
|
+
inner_params = "#{value_var}, #{index_var}"
|
|
130
|
+
params = extra_block_param ? "(#{inner_params}), #{extra_block_param}" : inner_params
|
|
131
|
+
iteration_block("#{receiver} do |#{params}|", bind_code, body_code)
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
def emit_lua_pairs_iteration(iter_expr, binding_pats, env, current_scope, method:,
|
|
135
|
+
extra_block_param: nil, &block)
|
|
136
|
+
body_env = env.child
|
|
137
|
+
key_var, key_bind = bind_iteration_param(binding_pats[0], 'key', body_env)
|
|
138
|
+
value_var, value_bind = bind_iteration_param(binding_pats[1], 'value', body_env)
|
|
139
|
+
bind_code = [key_bind, value_bind].compact.join("\n")
|
|
140
|
+
body_code = block.call(body_env)
|
|
141
|
+
coll_code = emit_expr(iter_expr.items[1], env, current_scope)
|
|
142
|
+
inner_params = "#{key_var}, #{value_var}"
|
|
143
|
+
params = extra_block_param ? "(#{inner_params}), #{extra_block_param}" : inner_params
|
|
144
|
+
iteration_block("#{coll_code}.#{method} do |#{params}|", bind_code, body_code)
|
|
145
|
+
end
|
|
146
|
+
end
|
|
147
|
+
end
|
|
148
|
+
end
|
|
149
|
+
end
|
|
@@ -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
|
|
|
@@ -71,17 +89,19 @@ module Kapusta
|
|
|
71
89
|
when 'macros'
|
|
72
90
|
register_macros_form(list.rest)
|
|
73
91
|
return List.new([Sym.new('do')])
|
|
92
|
+
when 'import-macros'
|
|
93
|
+
raise macro_error(:import_macros_unsupported, list)
|
|
74
94
|
end
|
|
75
95
|
|
|
76
96
|
key = lookup_key(name)
|
|
77
97
|
if @macros.key?(key)
|
|
78
98
|
args = list.rest
|
|
79
99
|
result = invoke_macro(key, args)
|
|
80
|
-
return expand(result)
|
|
100
|
+
return copy_position(expand(result), list)
|
|
81
101
|
end
|
|
82
102
|
end
|
|
83
103
|
|
|
84
|
-
List.new(list.items.map { |item| expand(item) })
|
|
104
|
+
copy_position(List.new(list.items.map { |item| expand(item) }), list)
|
|
85
105
|
end
|
|
86
106
|
|
|
87
107
|
def lookup_key(name)
|
|
@@ -90,23 +110,23 @@ module Kapusta
|
|
|
90
110
|
|
|
91
111
|
def register_macro_form(args)
|
|
92
112
|
name_sym, params, *body = args
|
|
93
|
-
raise
|
|
94
|
-
raise
|
|
113
|
+
raise macro_error(:macro_name_must_be_symbol, name_sym) unless name_sym.is_a?(Sym)
|
|
114
|
+
raise macro_error(:macro_params_must_be_vector, params) unless params.is_a?(Vec)
|
|
95
115
|
|
|
96
116
|
register(name_sym.name, params, body)
|
|
97
117
|
end
|
|
98
118
|
|
|
99
119
|
def register_macros_form(args)
|
|
100
120
|
hash_lit = args[0]
|
|
101
|
-
raise
|
|
121
|
+
raise macro_error(:macros_expects_hash, hash_lit) unless hash_lit.is_a?(HashLit)
|
|
102
122
|
|
|
103
123
|
hash_lit.pairs.each do |key, value|
|
|
104
|
-
raise
|
|
124
|
+
raise macro_error(:macros_entry_must_be_fn, value, form: value.inspect) unless fn_form?(value)
|
|
105
125
|
|
|
106
126
|
name = key.to_s
|
|
107
127
|
params = value.items[1]
|
|
108
128
|
body = value.items[2..]
|
|
109
|
-
raise
|
|
129
|
+
raise macro_error(:macros_entry_params_must_be_vector, params) unless params.is_a?(Vec)
|
|
110
130
|
|
|
111
131
|
register(name, params, body)
|
|
112
132
|
end
|
|
@@ -137,8 +157,9 @@ module Kapusta
|
|
|
137
157
|
List.new([Sym.new('fn'), params, List.new([Sym.new('let'), Vec.new(let_bindings), inner])])
|
|
138
158
|
end
|
|
139
159
|
|
|
140
|
-
|
|
141
|
-
|
|
160
|
+
macro_path = @path || "(macro #{name})"
|
|
161
|
+
ruby = Compiler.compile_forms([wrapped], path: macro_path)
|
|
162
|
+
TOPLEVEL_BINDING.eval(ruby, macro_path, 1)
|
|
142
163
|
end
|
|
143
164
|
|
|
144
165
|
def invoke_macro(key, args)
|
|
@@ -157,22 +178,33 @@ module Kapusta
|
|
|
157
178
|
|
|
158
179
|
def lower(form)
|
|
159
180
|
case form
|
|
160
|
-
when Quasiquote then lower_quasi(form.form)
|
|
181
|
+
when Quasiquote then copy_position(lower_quasi(form.form), form)
|
|
161
182
|
when Unquote, UnquoteSplice
|
|
162
|
-
raise Error,
|
|
183
|
+
raise Error, Kapusta::Errors.format(:unquote_outside_quasiquote)
|
|
163
184
|
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) })
|
|
185
|
+
raise Error, Kapusta::Errors.format(:auto_gensym_outside_quasiquote, name: form.name)
|
|
186
|
+
when List then copy_position(List.new(form.items.map { |item| lower(item) }), form)
|
|
187
|
+
when Vec then copy_position(Vec.new(form.items.map { |item| lower(item) }), form)
|
|
167
188
|
when HashLit
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
189
|
+
copy_position(
|
|
190
|
+
HashLit.new(form.entries.map do |entry|
|
|
191
|
+
entry.is_a?(Array) ? [lower(entry[0]), lower(entry[1])] : entry
|
|
192
|
+
end),
|
|
193
|
+
form
|
|
194
|
+
)
|
|
171
195
|
else
|
|
172
196
|
form
|
|
173
197
|
end
|
|
174
198
|
end
|
|
175
199
|
|
|
200
|
+
def copy_position(target, source)
|
|
201
|
+
return target unless target.respond_to?(:line=) && source.respond_to?(:line)
|
|
202
|
+
|
|
203
|
+
target.line ||= source.line
|
|
204
|
+
target.column ||= source.column
|
|
205
|
+
target
|
|
206
|
+
end
|
|
207
|
+
|
|
176
208
|
def lower_quasi(form)
|
|
177
209
|
case form
|
|
178
210
|
when AutoGensym then gensym_local_for(form.name)
|
|
@@ -182,9 +214,9 @@ module Kapusta
|
|
|
182
214
|
when HashLit then lower_quasi_hash(form)
|
|
183
215
|
when Unquote then lower(form.form)
|
|
184
216
|
when UnquoteSplice
|
|
185
|
-
raise Error,
|
|
217
|
+
raise Error, Kapusta::Errors.format(:unquote_splice_outside_list)
|
|
186
218
|
when Quasiquote
|
|
187
|
-
raise Error,
|
|
219
|
+
raise Error, Kapusta::Errors.format(:nested_quasiquote)
|
|
188
220
|
else
|
|
189
221
|
form
|
|
190
222
|
end
|
|
@@ -3,6 +3,8 @@
|
|
|
3
3
|
module Kapusta
|
|
4
4
|
module Compiler
|
|
5
5
|
class Normalizer
|
|
6
|
+
include LuaCompat::Normalization
|
|
7
|
+
|
|
6
8
|
def normalize_all(forms)
|
|
7
9
|
forms.map { |form| normalize(form) }
|
|
8
10
|
end
|
|
@@ -10,9 +12,12 @@ module Kapusta
|
|
|
10
12
|
def normalize(form)
|
|
11
13
|
case form
|
|
12
14
|
when List then normalize_list(form)
|
|
13
|
-
when Vec then Vec.new(form.items.map { |item| normalize(item) })
|
|
15
|
+
when Vec then inherit_position(Vec.new(form.items.map { |item| normalize(item) }), form)
|
|
14
16
|
when HashLit
|
|
15
|
-
|
|
17
|
+
inherit_position(
|
|
18
|
+
HashLit.new(form.pairs.map { |key, value| [normalize_hash_key(key), normalize(value)] }),
|
|
19
|
+
form
|
|
20
|
+
)
|
|
16
21
|
else
|
|
17
22
|
form
|
|
18
23
|
end
|
|
@@ -32,47 +37,53 @@ module Kapusta
|
|
|
32
37
|
|
|
33
38
|
head = list.head
|
|
34
39
|
items = list.items.map { |item| normalize(item) }
|
|
35
|
-
return List.new(items) unless head.is_a?(Sym)
|
|
40
|
+
return inherit_position(List.new(items), list) unless head.is_a?(Sym)
|
|
36
41
|
|
|
37
42
|
case head.name
|
|
38
43
|
when 'when'
|
|
44
|
+
raise compiler_error(:when_no_body, list, form: head.name) if items[2..].empty?
|
|
45
|
+
|
|
39
46
|
cond = items[1]
|
|
40
47
|
body = wrap_do(items[2..])
|
|
41
|
-
List.new([Sym.new('if'), cond, body])
|
|
48
|
+
inherit_position(List.new([Sym.new('if'), cond, body]), list)
|
|
42
49
|
when 'unless'
|
|
50
|
+
raise compiler_error(:when_no_body, list, form: head.name) if items[2..].empty?
|
|
51
|
+
|
|
43
52
|
cond = items[1]
|
|
44
53
|
body = wrap_do(items[2..])
|
|
45
|
-
List.new([Sym.new('if'), List.new([Sym.new('not'), cond]), body])
|
|
54
|
+
inherit_position(List.new([Sym.new('if'), List.new([Sym.new('not'), cond]), body]), list)
|
|
46
55
|
when 'tset'
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
List.new([Sym.new('values'), false, Sym.new('e')])])
|
|
56
|
-
])
|
|
57
|
-
when 'xpcall'
|
|
58
|
-
fn = items[1]
|
|
59
|
-
handler = items[2]
|
|
60
|
-
args = items[3..]
|
|
61
|
-
List.new([
|
|
62
|
-
Sym.new('try'),
|
|
63
|
-
List.new([Sym.new('values'), true, List.new([fn, *args])]),
|
|
64
|
-
List.new([Sym.new('catch'), Sym.new('StandardError'), Sym.new('e'),
|
|
65
|
-
List.new([Sym.new('values'), false, List.new([handler, Sym.new('e')])])])
|
|
66
|
-
])
|
|
56
|
+
raise compiler_error(:tset_no_value, list) if items.length < 4
|
|
57
|
+
|
|
58
|
+
inherit_position(
|
|
59
|
+
List.new([Sym.new('set'), List.new([Sym.new('.'), items[1], items[2]]), items[3]]),
|
|
60
|
+
list
|
|
61
|
+
)
|
|
62
|
+
when *LuaCompat::SPECIAL_FORMS
|
|
63
|
+
normalize_lua_compat_form(head.name, items)
|
|
67
64
|
when '->', '->>', '-?>', '-?>>'
|
|
68
|
-
normalize(thread(items[1..], head.name))
|
|
65
|
+
inherit_position(normalize(thread(items[1..], head.name)), list)
|
|
69
66
|
when 'doto'
|
|
70
|
-
normalize(doto(items[1..]))
|
|
67
|
+
inherit_position(normalize(doto(items[1..])), list)
|
|
71
68
|
else
|
|
72
|
-
List.new(items)
|
|
69
|
+
inherit_position(List.new(items), list)
|
|
73
70
|
end
|
|
74
71
|
end
|
|
75
72
|
|
|
73
|
+
def inherit_position(target, source)
|
|
74
|
+
return target unless target.respond_to?(:line=) && source.respond_to?(:line)
|
|
75
|
+
|
|
76
|
+
target.line ||= source.line
|
|
77
|
+
target.column ||= source.column
|
|
78
|
+
target
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def compiler_error(code, form, **args)
|
|
82
|
+
line = form.respond_to?(:line) ? form.line : nil
|
|
83
|
+
column = form.respond_to?(:column) ? form.column : nil
|
|
84
|
+
Compiler::Error.new(Kapusta::Errors.format(code, **args), line:, column:)
|
|
85
|
+
end
|
|
86
|
+
|
|
76
87
|
def wrap_do(forms)
|
|
77
88
|
return if forms.empty?
|
|
78
89
|
return forms.first if forms.length == 1
|
data/lib/kapusta/compiler.rb
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require_relative 'error'
|
|
4
|
+
require_relative 'compiler/lua_compat'
|
|
4
5
|
require_relative 'compiler/normalizer'
|
|
5
6
|
require_relative 'compiler/emitter'
|
|
6
7
|
require_relative 'compiler/macro_expander'
|
|
@@ -8,8 +9,8 @@ require_relative 'compiler/macro_expander'
|
|
|
8
9
|
module Kapusta
|
|
9
10
|
module Compiler
|
|
10
11
|
class Error < Kapusta::Error; end
|
|
11
|
-
|
|
12
|
-
fn lambda λ let local var set if when unless case match
|
|
12
|
+
CORE_SPECIAL_FORMS = %w[
|
|
13
|
+
fn lambda λ let local var global set if when unless case match
|
|
13
14
|
while for each do values
|
|
14
15
|
-> ->> -?> -?>> doto
|
|
15
16
|
icollect collect fcollect accumulate faccumulate
|
|
@@ -23,7 +24,7 @@ module Kapusta
|
|
|
23
24
|
raise
|
|
24
25
|
ivar cvar gvar
|
|
25
26
|
ruby
|
|
26
|
-
tset
|
|
27
|
+
tset
|
|
27
28
|
and or not
|
|
28
29
|
= not= < <= > >=
|
|
29
30
|
+ - * / %
|
|
@@ -31,16 +32,21 @@ module Kapusta
|
|
|
31
32
|
macro macros import-macros
|
|
32
33
|
quasi-sym quasi-list quasi-list-tail quasi-vec quasi-vec-tail quasi-hash quasi-gensym
|
|
33
34
|
].freeze
|
|
35
|
+
SPECIAL_FORMS = (CORE_SPECIAL_FORMS + LuaCompat::SPECIAL_FORMS).freeze
|
|
34
36
|
|
|
35
37
|
def self.compile(source, path: '(kapusta)')
|
|
36
38
|
forms = Reader.read_all(source)
|
|
37
|
-
expanded = MacroExpander.new.expand_all(forms)
|
|
39
|
+
expanded = MacroExpander.new(path:).expand_all(forms)
|
|
38
40
|
compile_forms(expanded, path:)
|
|
41
|
+
rescue Kapusta::Error => e
|
|
42
|
+
raise e.with_defaults(path:)
|
|
39
43
|
end
|
|
40
44
|
|
|
41
45
|
def self.compile_forms(forms, path: '(kapusta)')
|
|
42
46
|
normalized = Normalizer.new.normalize_all(forms)
|
|
43
47
|
Emitter.new(path:).emit_file(normalized)
|
|
48
|
+
rescue Kapusta::Error => e
|
|
49
|
+
raise e.with_defaults(path:)
|
|
44
50
|
end
|
|
45
51
|
|
|
46
52
|
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,70 @@
|
|
|
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_no_subject: 'missing subject',
|
|
18
|
+
case_odd_patterns: 'expected even number of pattern/body pairs',
|
|
19
|
+
case_unsupported: 'case/match clauses use patterns this compiler cannot translate',
|
|
20
|
+
could_not_destructure_literal: 'could not destructure literal',
|
|
21
|
+
could_not_read_number: 'could not read number "%{token}"',
|
|
22
|
+
counted_no_range: 'expected range to include start and stop',
|
|
23
|
+
destructure_unsupported: 'destructure pattern this compiler cannot translate: %{pattern}',
|
|
24
|
+
dot_no_args: 'expected table argument',
|
|
25
|
+
each_no_binding: 'expected binding table',
|
|
26
|
+
empty_call: 'expected a function, macro, or special to call',
|
|
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
|
+
tset_no_value: 'tset: expected table, key, and value arguments',
|
|
50
|
+
unclosed_delimiter: "unclosed opening delimiter '%{char}'",
|
|
51
|
+
undefined_symbol: 'undefined symbol: %{name}',
|
|
52
|
+
unexpected_closing_delimiter: "unexpected closing delimiter '%{char}'",
|
|
53
|
+
unexpected_eof: 'unexpected eof',
|
|
54
|
+
unexpected_vararg: 'unexpected vararg',
|
|
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_not_last: 'expected vararg as last parameter',
|
|
60
|
+
vararg_with_operator: 'tried to use vararg with operator',
|
|
61
|
+
when_no_body: '%{form}: expected body'
|
|
62
|
+
}.freeze
|
|
63
|
+
|
|
64
|
+
def self.format(code, **args)
|
|
65
|
+
template = MESSAGES.fetch(code) { raise ArgumentError, "unknown error code: #{code.inspect}" }
|
|
66
|
+
args.empty? ? template.dup : (template % args)
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
# rubocop:enable Style/FormatStringToken
|
|
70
|
+
end
|
data/lib/kapusta/formatter.rb
CHANGED
|
@@ -10,6 +10,10 @@ module Kapusta
|
|
|
10
10
|
|
|
11
11
|
PIPELINE_FORMS = %w[-> ->> -?> -?>> doto].freeze
|
|
12
12
|
|
|
13
|
+
def self.format(source, path: nil)
|
|
14
|
+
new([]).send(:format_source, source, path)
|
|
15
|
+
end
|
|
16
|
+
|
|
13
17
|
def initialize(argv)
|
|
14
18
|
@mode = :stdout
|
|
15
19
|
@files = []
|
|
@@ -27,7 +31,8 @@ module Kapusta
|
|
|
27
31
|
|
|
28
32
|
formatted = @files.map do |path|
|
|
29
33
|
original = read_source(path)
|
|
30
|
-
|
|
34
|
+
validate_kapusta_source(original, path)
|
|
35
|
+
[path, original, format_source(original, path)]
|
|
31
36
|
end
|
|
32
37
|
|
|
33
38
|
case @mode
|
|
@@ -48,13 +53,17 @@ module Kapusta
|
|
|
48
53
|
end
|
|
49
54
|
|
|
50
55
|
0
|
|
51
|
-
rescue Error => e
|
|
52
|
-
warn e.
|
|
56
|
+
rescue Kapusta::Error => e
|
|
57
|
+
warn e.formatted
|
|
53
58
|
1
|
|
54
59
|
end
|
|
55
60
|
|
|
56
61
|
private
|
|
57
62
|
|
|
63
|
+
def validate_kapusta_source(source, path)
|
|
64
|
+
Kapusta::Compiler.compile(source, path:)
|
|
65
|
+
end
|
|
66
|
+
|
|
58
67
|
def parse_args(argv)
|
|
59
68
|
argv.each do |arg|
|
|
60
69
|
case arg
|
|
@@ -99,7 +108,7 @@ module Kapusta
|
|
|
99
108
|
$stdin.read
|
|
100
109
|
end
|
|
101
110
|
|
|
102
|
-
def format_source(source)
|
|
111
|
+
def format_source(source, path = nil)
|
|
103
112
|
forms = Reader.read_all(source, preserve_comments: true)
|
|
104
113
|
entries = top_level_entries(forms)
|
|
105
114
|
return '' if entries.empty?
|
|
@@ -110,8 +119,10 @@ module Kapusta
|
|
|
110
119
|
output << render_top_level_entry(entry)
|
|
111
120
|
end
|
|
112
121
|
output << "\n"
|
|
122
|
+
rescue Kapusta::Error => e
|
|
123
|
+
raise e.with_defaults(path:)
|
|
113
124
|
rescue StandardError => e
|
|
114
|
-
raise Error
|
|
125
|
+
raise Error.new(e.message, path:)
|
|
115
126
|
end
|
|
116
127
|
|
|
117
128
|
def separator_for(_previous, _current)
|