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
|
@@ -4,24 +4,58 @@ module Kapusta
|
|
|
4
4
|
module Compiler
|
|
5
5
|
module EmitterModules
|
|
6
6
|
module Collections
|
|
7
|
+
include LuaCompat::Emission
|
|
8
|
+
|
|
7
9
|
private
|
|
8
10
|
|
|
9
11
|
def emit_icollect(args, env, current_scope)
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
12
|
+
emit_error!(:icollect_no_iterator) unless args[0].is_a?(Vec) && args[0].items.length >= 2
|
|
13
|
+
|
|
14
|
+
emit_iteration(args[0], env, current_scope, method: 'filter_map') do |iter_env|
|
|
15
|
+
emit_sequence(args[1..], iter_env, current_scope, allow_method_definitions: false).first
|
|
14
16
|
end
|
|
15
|
-
emit_collection_result(result_var, '[]', iter_code)
|
|
16
17
|
end
|
|
17
18
|
|
|
18
19
|
def emit_collect(args, env, current_scope)
|
|
19
20
|
result_var = temp('result')
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
21
|
+
values_form = simple_values_call(args[1]) if args.length == 2
|
|
22
|
+
emit_iteration(args[0], env, current_scope,
|
|
23
|
+
method: 'each_with_object({})', extra_block_param: result_var) do |iter_env|
|
|
24
|
+
if values_form
|
|
25
|
+
emit_collect_values_step(result_var, values_form, iter_env, current_scope)
|
|
26
|
+
else
|
|
27
|
+
body = emit_sequence(args[1..], iter_env, current_scope, allow_method_definitions: false).first
|
|
28
|
+
emit_hash_collection_step(result_var, body)
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def simple_values_call(form)
|
|
34
|
+
return unless form.is_a?(List) && form.items.length == 3
|
|
35
|
+
|
|
36
|
+
head = form.head
|
|
37
|
+
form if head.is_a?(Sym) && head.name == 'values'
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def emit_collect_values_step(result_var, values_form, iter_env, current_scope)
|
|
41
|
+
key_form = values_form.items[1]
|
|
42
|
+
val_form = values_form.items[2]
|
|
43
|
+
key_code = emit_expr(key_form, iter_env, current_scope)
|
|
44
|
+
val_code = emit_expr(val_form, iter_env, current_scope)
|
|
45
|
+
assignment = "#{result_var}[#{key_code}] = #{val_code}"
|
|
46
|
+
guards = []
|
|
47
|
+
guards << "#{key_code}.nil?" unless definitely_non_nil?(key_form)
|
|
48
|
+
guards << "#{val_code}.nil?" unless definitely_non_nil?(val_form)
|
|
49
|
+
return assignment if guards.empty?
|
|
50
|
+
|
|
51
|
+
"#{assignment} unless #{guards.join(' || ')}"
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def definitely_non_nil?(form)
|
|
55
|
+
case form
|
|
56
|
+
when Numeric, String, ::Symbol, TrueClass, FalseClass then true
|
|
57
|
+
else false
|
|
23
58
|
end
|
|
24
|
-
emit_collection_result(result_var, '{}', iter_code)
|
|
25
59
|
end
|
|
26
60
|
|
|
27
61
|
def emit_fcollect(args, env, current_scope)
|
|
@@ -35,27 +69,48 @@ module Kapusta
|
|
|
35
69
|
|
|
36
70
|
def emit_accumulate(args, env, current_scope)
|
|
37
71
|
bindings = args[0].items
|
|
72
|
+
emit_error!(:accumulate_no_iterator) if bindings.length < 4
|
|
73
|
+
|
|
38
74
|
acc_name = bindings[0]
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
75
|
+
init_code = emit_expr(bindings[1], env, current_scope)
|
|
76
|
+
iter_items = bindings[2..]
|
|
77
|
+
iter_expr = iter_items.last
|
|
78
|
+
binding_pats = iter_items[0...-1]
|
|
79
|
+
|
|
80
|
+
body_env = env.child
|
|
81
|
+
acc_var = define_local(body_env, acc_name.name)
|
|
82
|
+
|
|
83
|
+
inject_code = try_emit_inject(iter_expr, binding_pats, body_env, env, current_scope, acc_var,
|
|
84
|
+
init_code, args[1..])
|
|
85
|
+
return inject_code if inject_code
|
|
86
|
+
|
|
87
|
+
iter_code = emit_iteration(Vec.new(iter_items), body_env, current_scope) do |iter_env|
|
|
88
|
+
body_code, = emit_sequence(args[1..], iter_env, current_scope, allow_method_definitions: false)
|
|
89
|
+
emit_sequence_value_assignment(acc_var, body_code)
|
|
47
90
|
end
|
|
48
91
|
[
|
|
49
92
|
'(-> do',
|
|
50
|
-
indent("#{acc_var} = #{
|
|
93
|
+
indent("#{acc_var} = #{init_code}"),
|
|
51
94
|
indent(iter_code),
|
|
52
95
|
indent(acc_var),
|
|
53
96
|
'end).call'
|
|
54
97
|
].join("\n")
|
|
55
98
|
end
|
|
56
99
|
|
|
100
|
+
def try_emit_inject(iter_expr, binding_pats, body_env, env, current_scope, acc_var, init_code, body_forms)
|
|
101
|
+
emit_lua_compat_inject(iter_expr, binding_pats, body_env, env, current_scope, acc_var,
|
|
102
|
+
init_code, body_forms)
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def inject_block(receiver, params, init_code, bind_code, body_code)
|
|
106
|
+
inner = join_code(bind_code, body_code)
|
|
107
|
+
["#{receiver}.inject(#{init_code}) do |#{params}|", indent(inner), 'end'].join("\n")
|
|
108
|
+
end
|
|
109
|
+
|
|
57
110
|
def emit_faccumulate(args, env, current_scope)
|
|
58
111
|
bindings = args[0].items
|
|
112
|
+
emit_error!(:accumulate_no_iterator) if bindings.length < 5
|
|
113
|
+
|
|
59
114
|
acc_name = bindings[0]
|
|
60
115
|
loop_name = bindings[2]
|
|
61
116
|
loop_env = env.child
|
|
@@ -112,51 +167,32 @@ module Kapusta
|
|
|
112
167
|
end
|
|
113
168
|
end
|
|
114
169
|
|
|
115
|
-
def emit_iteration(bindings_vec, env, current_scope)
|
|
170
|
+
def emit_iteration(bindings_vec, env, current_scope, method: 'each', extra_block_param: nil, &block)
|
|
171
|
+
emit_error!(:each_no_binding) unless bindings_vec.is_a?(Vec)
|
|
172
|
+
|
|
116
173
|
items = bindings_vec.items
|
|
117
174
|
iter_expr = items.last
|
|
118
175
|
binding_pats = items[0...-1]
|
|
119
176
|
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
body_env = env.child
|
|
124
|
-
value_var, value_bind = bind_iteration_param(binding_pats[1], 'value', body_env)
|
|
125
|
-
coll_code = emit_expr(iter_expr.items[1], env, current_scope)
|
|
126
|
-
if ignored_pattern?(binding_pats[0])
|
|
127
|
-
bind_code = value_bind || ''
|
|
128
|
-
body_code = yield(body_env)
|
|
129
|
-
return iteration_block("#{coll_code}.each do |#{value_var}|", bind_code, body_code)
|
|
130
|
-
end
|
|
131
|
-
index_var, index_bind = bind_iteration_param(binding_pats[0], 'index', body_env)
|
|
132
|
-
bind_code = [index_bind, value_bind].compact.join("\n")
|
|
133
|
-
body_code = yield(body_env)
|
|
134
|
-
header = "#{coll_code}.each_with_index do |#{value_var}, #{index_var}|"
|
|
135
|
-
return iteration_block(header, bind_code, body_code)
|
|
136
|
-
when 'pairs'
|
|
137
|
-
body_env = env.child
|
|
138
|
-
key_var, key_bind = bind_iteration_param(binding_pats[0], 'key', body_env)
|
|
139
|
-
value_var, value_bind = bind_iteration_param(binding_pats[1], 'value', body_env)
|
|
140
|
-
bind_code = [key_bind, value_bind].compact.join("\n")
|
|
141
|
-
body_code = yield(body_env)
|
|
142
|
-
header = "#{emit_expr(iter_expr.items[1], env, current_scope)}.each do |#{key_var}, #{value_var}|"
|
|
143
|
-
return iteration_block(header, bind_code, body_code)
|
|
144
|
-
end
|
|
145
|
-
end
|
|
177
|
+
lua_iteration = emit_lua_compat_iteration(iter_expr, binding_pats, env, current_scope,
|
|
178
|
+
method:, extra_block_param:, &block)
|
|
179
|
+
return lua_iteration if lua_iteration
|
|
146
180
|
|
|
147
181
|
coll_code = emit_expr(iter_expr, env, current_scope)
|
|
148
182
|
if binding_pats.length == 1
|
|
149
183
|
body_env = env.child
|
|
150
184
|
value_var, bind_code = bind_iteration_param(binding_pats[0], 'value', body_env)
|
|
151
|
-
body_code =
|
|
152
|
-
|
|
185
|
+
body_code = block.call(body_env)
|
|
186
|
+
params = extra_block_param ? "#{value_var}, #{extra_block_param}" : value_var
|
|
187
|
+
iteration_block("#{coll_code}.#{method} do |#{params}|", bind_code || '', body_code)
|
|
153
188
|
else
|
|
154
189
|
parts_var = temp('parts')
|
|
155
190
|
body_env = env.child
|
|
156
191
|
pairs = binding_pats.each_with_index.map { |pattern, i| [pattern, "#{parts_var}[#{i}]"] }
|
|
157
192
|
bind_code, body_env = emit_iteration_bindings(pairs, body_env)
|
|
158
|
-
body_code =
|
|
159
|
-
|
|
193
|
+
body_code = block.call(body_env)
|
|
194
|
+
params = extra_block_param ? "#{parts_var}, #{extra_block_param}" : "*#{parts_var}"
|
|
195
|
+
iteration_block("#{coll_code}.#{method} do |#{params}|", bind_code, body_code)
|
|
160
196
|
end
|
|
161
197
|
end
|
|
162
198
|
|
|
@@ -11,8 +11,7 @@ module Kapusta
|
|
|
11
11
|
end
|
|
12
12
|
|
|
13
13
|
def build_if(args, env, current_scope)
|
|
14
|
-
|
|
15
|
-
return emit_expr(args[0], env, current_scope) if args.length == 1
|
|
14
|
+
emit_error!(:if_no_body) if args.length < 2
|
|
16
15
|
|
|
17
16
|
cond = emit_expr(args[0], env, current_scope)
|
|
18
17
|
truthy = emit_if_branch(args[1], env, current_scope)
|
|
@@ -58,17 +57,38 @@ module Kapusta
|
|
|
58
57
|
end
|
|
59
58
|
|
|
60
59
|
def emit_case(args, env, current_scope, mode)
|
|
60
|
+
emit_error!(:case_no_subject) if args.empty?
|
|
61
|
+
|
|
62
|
+
clauses = args[1..]
|
|
63
|
+
emit_error!(:case_no_patterns) if clauses.empty?
|
|
64
|
+
emit_error!(:case_odd_patterns) if clauses.length.odd?
|
|
65
|
+
|
|
66
|
+
value_code = emit_expr(args[0], env, current_scope)
|
|
67
|
+
if simple_case_subject?(args[0]) && simple_expression?(value_code)
|
|
68
|
+
body = try_emit_native_case(value_code, clauses, env, current_scope, mode)
|
|
69
|
+
emit_error!(:case_unsupported) unless body
|
|
70
|
+
return body
|
|
71
|
+
end
|
|
72
|
+
|
|
61
73
|
value_var = temp('case_value')
|
|
62
|
-
body = try_emit_native_case(value_var,
|
|
63
|
-
emit_error!(
|
|
74
|
+
body = try_emit_native_case(value_var, clauses, env, current_scope, mode)
|
|
75
|
+
emit_error!(:case_unsupported) unless body
|
|
64
76
|
[
|
|
65
77
|
'(-> do',
|
|
66
|
-
indent("#{value_var} = #{
|
|
78
|
+
indent("#{value_var} = #{value_code}"),
|
|
67
79
|
indent(body),
|
|
68
80
|
'end).call'
|
|
69
81
|
].join("\n")
|
|
70
82
|
end
|
|
71
83
|
|
|
84
|
+
def simple_case_subject?(form)
|
|
85
|
+
case form
|
|
86
|
+
when Sym then !form.dotted?
|
|
87
|
+
when Numeric, String, Symbol, true, false, nil then true
|
|
88
|
+
else false
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
|
|
72
92
|
def try_emit_native_case(value_var, clauses, env, current_scope, mode)
|
|
73
93
|
arms = []
|
|
74
94
|
i = 0
|
|
@@ -89,10 +109,15 @@ module Kapusta
|
|
|
89
109
|
arms.concat(sub_arms)
|
|
90
110
|
i += 2
|
|
91
111
|
end
|
|
92
|
-
arms << ['else', indent('nil')].join("\n")
|
|
112
|
+
arms << ['else', indent('nil')].join("\n") unless wildcard_last?(clauses)
|
|
93
113
|
["case #{value_var}", *arms, 'end'].join("\n")
|
|
94
114
|
end
|
|
95
115
|
|
|
116
|
+
def wildcard_last?(clauses)
|
|
117
|
+
last_pattern = clauses[-2]
|
|
118
|
+
last_pattern.is_a?(Sym) && last_pattern.name == '_'
|
|
119
|
+
end
|
|
120
|
+
|
|
96
121
|
def try_native_arm(pattern, body, where_guards, env, current_scope, mode)
|
|
97
122
|
allow_pins = !where_guards.empty? && mode == :case
|
|
98
123
|
plan = native_pattern_plan(pattern, env, mode:, allow_pins:)
|
|
@@ -7,17 +7,19 @@ module Kapusta
|
|
|
7
7
|
private
|
|
8
8
|
|
|
9
9
|
def emit_expr(form, env, current_scope)
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
"#{
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
10
|
+
with_current_form(form) do
|
|
11
|
+
case form
|
|
12
|
+
when Sym then emit_sym(form, env)
|
|
13
|
+
when Vec then "[#{form.items.map { |item| emit_expr(item, env, current_scope) }.join(', ')}]"
|
|
14
|
+
when HashLit
|
|
15
|
+
"{#{form.pairs.map do |key, value|
|
|
16
|
+
"#{emit_hash_key(key, env, current_scope)} => #{emit_expr(value, env, current_scope)}"
|
|
17
|
+
end.join(', ')}}"
|
|
18
|
+
when List then emit_list(form, env, current_scope)
|
|
19
|
+
when String, Symbol, Numeric, true, false, nil then form.inspect
|
|
20
|
+
else
|
|
21
|
+
emit_error!(:cannot_emit_form, form: form.inspect)
|
|
22
|
+
end
|
|
21
23
|
end
|
|
22
24
|
end
|
|
23
25
|
|
|
@@ -29,7 +31,7 @@ module Kapusta
|
|
|
29
31
|
end
|
|
30
32
|
|
|
31
33
|
def emit_list(list, env, current_scope)
|
|
32
|
-
|
|
34
|
+
emit_error!(:empty_call) if list.empty?
|
|
33
35
|
|
|
34
36
|
head = list.head
|
|
35
37
|
args = list.rest
|
|
@@ -43,6 +45,11 @@ module Kapusta
|
|
|
43
45
|
return emit_self_call(head.name, args, env, current_scope)
|
|
44
46
|
end
|
|
45
47
|
|
|
48
|
+
case head
|
|
49
|
+
when Numeric, String, Symbol, true, false, nil
|
|
50
|
+
emit_error!(:cannot_call_literal, value: head.inspect)
|
|
51
|
+
end
|
|
52
|
+
|
|
46
53
|
emit_callable_call(emit_expr(head, env, current_scope), args, env, current_scope)
|
|
47
54
|
end
|
|
48
55
|
|
|
@@ -51,6 +58,7 @@ module Kapusta
|
|
|
51
58
|
when 'fn', 'lambda', 'λ' then emit_fn(args, env, current_scope)
|
|
52
59
|
when 'let' then emit_let(args, env, current_scope)
|
|
53
60
|
when 'local', 'var' then emit_local_expr(args, env, current_scope)
|
|
61
|
+
when 'global' then emit_global_expr(args, env, current_scope)
|
|
54
62
|
when 'set' then emit_set_expr(args, env, current_scope)
|
|
55
63
|
when 'if' then emit_if(args, env, current_scope)
|
|
56
64
|
when 'case' then emit_case(args, env, current_scope, :case)
|
|
@@ -96,17 +104,15 @@ module Kapusta
|
|
|
96
104
|
when '/' then emit_div(args, env, current_scope)
|
|
97
105
|
when '%' then args.map { |arg| parenthesize(emit_expr(arg, env, current_scope)) }.join(' % ')
|
|
98
106
|
when 'print' then emit_print(args, env, current_scope)
|
|
99
|
-
when 'quasi-sym' then "Kapusta::
|
|
107
|
+
when 'quasi-sym' then "Kapusta::MacroSym.new(#{emit_expr(args[0], env, current_scope)})"
|
|
100
108
|
when 'quasi-list' then "Kapusta::List.new([#{args.map { |a| emit_expr(a, env, current_scope) }.join(', ')}])"
|
|
101
109
|
when 'quasi-list-tail' then emit_quasi_list_tail(args, env, current_scope)
|
|
102
110
|
when 'quasi-vec' then "Kapusta::Vec.new([#{args.map { |a| emit_expr(a, env, current_scope) }.join(', ')}])"
|
|
103
111
|
when 'quasi-vec-tail' then emit_quasi_vec_tail(args, env, current_scope)
|
|
104
112
|
when 'quasi-hash' then emit_quasi_hash(args, env, current_scope)
|
|
105
113
|
when 'quasi-gensym' then emit_quasi_gensym(args[0], env, current_scope)
|
|
106
|
-
when 'macro', 'macros', 'import-macros'
|
|
107
|
-
emit_error!("#{name} must appear at the top level and is consumed by the macro expander")
|
|
108
114
|
else
|
|
109
|
-
emit_error!(
|
|
115
|
+
emit_error!(:unknown_special_form, name:)
|
|
110
116
|
end
|
|
111
117
|
end
|
|
112
118
|
|
|
@@ -138,6 +144,9 @@ module Kapusta
|
|
|
138
144
|
def emit_concat(args, env, current_scope)
|
|
139
145
|
return '""' if args.empty?
|
|
140
146
|
|
|
147
|
+
args.each do |arg|
|
|
148
|
+
emit_error!(:vararg_with_operator) if arg.is_a?(Sym) && arg.name == '...'
|
|
149
|
+
end
|
|
141
150
|
args.map { |arg| emit_string_part(arg, env, current_scope) }.join(' + ')
|
|
142
151
|
end
|
|
143
152
|
|
|
@@ -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")
|
|
@@ -425,16 +427,17 @@ module Kapusta
|
|
|
425
427
|
def emit_sym(sym, env)
|
|
426
428
|
name = sym.name
|
|
427
429
|
return 'self' if name == 'self'
|
|
428
|
-
return 'Float::INFINITY' if name == 'math.huge'
|
|
429
430
|
|
|
430
431
|
if (binding = env.lookup_if_defined(sym))
|
|
431
432
|
return binding_value_code(binding)
|
|
432
433
|
end
|
|
434
|
+
|
|
435
|
+
emit_error!(:unexpected_vararg) if name == '...'
|
|
433
436
|
return emit_multisym_value(sym, env) if sym.dotted?
|
|
434
437
|
return 'ARGV' if name == 'ARGV'
|
|
435
438
|
return name if name.match?(/\A[A-Z]/)
|
|
436
439
|
|
|
437
|
-
emit_error!(
|
|
440
|
+
emit_error!(:undefined_symbol, name:)
|
|
438
441
|
end
|
|
439
442
|
|
|
440
443
|
def emit_gvar(sym)
|
|
@@ -458,7 +461,7 @@ module Kapusta
|
|
|
458
461
|
const_path << segments[idx]
|
|
459
462
|
idx += 1
|
|
460
463
|
end
|
|
461
|
-
emit_error!(
|
|
464
|
+
emit_error!(:bad_multisym, path: segments.join('.')) if const_path.empty?
|
|
462
465
|
|
|
463
466
|
[const_path.join('::'), segments[idx..]]
|
|
464
467
|
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
|
|
@@ -225,12 +246,60 @@ module Kapusta
|
|
|
225
246
|
end
|
|
226
247
|
|
|
227
248
|
def compile_native_hash(pattern, env, mode:, allow_pins:, state:)
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
249
|
+
sym_pairs, other_pairs = pattern.pairs.partition { |key, _| key.is_a?(Symbol) }
|
|
250
|
+
sym_parts = sym_pairs.map do |key, value|
|
|
231
251
|
"#{key}: #{compile_native_pattern(value, env, mode:, allow_pins:, state:)}"
|
|
232
252
|
end
|
|
233
|
-
"{#{
|
|
253
|
+
return "{#{sym_parts.join(', ')}}" if other_pairs.empty?
|
|
254
|
+
|
|
255
|
+
capture = temp('hash')
|
|
256
|
+
sym_pattern = sym_parts.empty? ? 'Hash' : "{#{sym_parts.join(', ')}}"
|
|
257
|
+
other_pairs.each do |key, value|
|
|
258
|
+
compile_native_hash_value(value, capture, compile_native_hash_key(key), env, mode:, state:)
|
|
259
|
+
end
|
|
260
|
+
"#{sym_pattern} => #{capture}"
|
|
261
|
+
end
|
|
262
|
+
|
|
263
|
+
def compile_native_hash_key(key)
|
|
264
|
+
case key
|
|
265
|
+
when Symbol, String, Numeric, true, false then key.inspect
|
|
266
|
+
when nil then 'nil'
|
|
267
|
+
else raise PatternNotTranslatable
|
|
268
|
+
end
|
|
269
|
+
end
|
|
270
|
+
|
|
271
|
+
def compile_native_hash_value(value, capture, key_code, env, mode:, state:)
|
|
272
|
+
lookup = "#{capture}[#{key_code}]"
|
|
273
|
+
case value
|
|
274
|
+
when Sym
|
|
275
|
+
name = value.name
|
|
276
|
+
return if name == '_'
|
|
277
|
+
|
|
278
|
+
if nil_allowing_pattern_name?(name)
|
|
279
|
+
raise PatternNotTranslatable if state[:bound_names].key?(name)
|
|
280
|
+
|
|
281
|
+
state[:bound_names][name] = true
|
|
282
|
+
state[:binding_names] << name
|
|
283
|
+
state[:guards] << "(#{sanitize_local(name)} = #{lookup}; true)"
|
|
284
|
+
else
|
|
285
|
+
binding = mode == :match ? env.lookup_if_defined(name) : nil
|
|
286
|
+
if state[:bound_names].key?(name)
|
|
287
|
+
raise PatternNotTranslatable
|
|
288
|
+
elsif binding
|
|
289
|
+
state[:guards] << "#{lookup} == #{binding_value_code(binding)}"
|
|
290
|
+
else
|
|
291
|
+
state[:bound_names][name] = true
|
|
292
|
+
state[:binding_names] << name
|
|
293
|
+
state[:guards] << "!(#{sanitize_local(name)} = #{lookup}).nil?"
|
|
294
|
+
end
|
|
295
|
+
end
|
|
296
|
+
when nil
|
|
297
|
+
state[:guards] << "#{lookup}.nil?"
|
|
298
|
+
when Symbol, String, Numeric, true, false
|
|
299
|
+
state[:guards] << "#{lookup} == #{value.inspect}"
|
|
300
|
+
else
|
|
301
|
+
raise PatternNotTranslatable
|
|
302
|
+
end
|
|
234
303
|
end
|
|
235
304
|
|
|
236
305
|
def compile_native_pin(pattern, env, mode:, allow_pins:)
|
|
@@ -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
|
|
|
@@ -107,7 +128,7 @@ module Kapusta
|
|
|
107
128
|
code, current_env = emit_form_in_sequence(form, current_env, current_scope,
|
|
108
129
|
allow_method_definitions:,
|
|
109
130
|
result_needed: result && index == forms.length - 1)
|
|
110
|
-
codes << code
|
|
131
|
+
codes << code unless code.empty?
|
|
111
132
|
end
|
|
112
133
|
[codes.join("\n"), current_env]
|
|
113
134
|
end
|
|
@@ -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)
|