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
|
@@ -7,12 +7,11 @@ module Kapusta
|
|
|
7
7
|
private
|
|
8
8
|
|
|
9
9
|
def emit_icollect(args, env, current_scope)
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
10
|
+
emit_error!(:icollect_no_iterator) unless args[0].is_a?(Vec) && args[0].items.length >= 2
|
|
11
|
+
|
|
12
|
+
emit_iteration(args[0], env, current_scope, method: 'filter_map') do |iter_env|
|
|
13
|
+
emit_sequence(args[1..], iter_env, current_scope, allow_method_definitions: false).first
|
|
14
14
|
end
|
|
15
|
-
emit_collection_result(result_var, '[]', iter_code)
|
|
16
15
|
end
|
|
17
16
|
|
|
18
17
|
def emit_collect(args, env, current_scope)
|
|
@@ -35,27 +34,69 @@ module Kapusta
|
|
|
35
34
|
|
|
36
35
|
def emit_accumulate(args, env, current_scope)
|
|
37
36
|
bindings = args[0].items
|
|
37
|
+
emit_error!(:accumulate_no_iterator) if bindings.length < 4
|
|
38
|
+
|
|
38
39
|
acc_name = bindings[0]
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
40
|
+
init_code = emit_expr(bindings[1], env, current_scope)
|
|
41
|
+
iter_items = bindings[2..]
|
|
42
|
+
iter_expr = iter_items.last
|
|
43
|
+
binding_pats = iter_items[0...-1]
|
|
44
|
+
|
|
45
|
+
body_env = env.child
|
|
46
|
+
acc_var = define_local(body_env, acc_name.name)
|
|
47
|
+
|
|
48
|
+
inject_code = try_emit_inject(iter_expr, binding_pats, body_env, env, current_scope, acc_var,
|
|
49
|
+
init_code, args[1..])
|
|
50
|
+
return inject_code if inject_code
|
|
51
|
+
|
|
52
|
+
iter_code = emit_iteration(Vec.new(iter_items), body_env, current_scope) do |iter_env|
|
|
53
|
+
body_code, = emit_sequence(args[1..], iter_env, current_scope, allow_method_definitions: false)
|
|
54
|
+
emit_sequence_value_assignment(acc_var, body_code)
|
|
47
55
|
end
|
|
48
56
|
[
|
|
49
57
|
'(-> do',
|
|
50
|
-
indent("#{acc_var} = #{
|
|
58
|
+
indent("#{acc_var} = #{init_code}"),
|
|
51
59
|
indent(iter_code),
|
|
52
60
|
indent(acc_var),
|
|
53
61
|
'end).call'
|
|
54
62
|
].join("\n")
|
|
55
63
|
end
|
|
56
64
|
|
|
65
|
+
def try_emit_inject(iter_expr, binding_pats, body_env, env, current_scope, acc_var, init_code, body_forms)
|
|
66
|
+
return unless iter_expr.is_a?(List) && iter_expr.head.is_a?(Sym)
|
|
67
|
+
|
|
68
|
+
coll_code = emit_expr(iter_expr.items[1], env, current_scope)
|
|
69
|
+
case iter_expr.head.name
|
|
70
|
+
when 'ipairs'
|
|
71
|
+
value_var, value_bind = bind_iteration_param(binding_pats[1], 'value', body_env)
|
|
72
|
+
if ignored_pattern?(binding_pats[0])
|
|
73
|
+
body_code, = emit_sequence(body_forms, body_env, current_scope, allow_method_definitions: false)
|
|
74
|
+
return inject_block(coll_code, "#{acc_var}, #{value_var}", init_code, value_bind || '', body_code)
|
|
75
|
+
end
|
|
76
|
+
index_var, index_bind = bind_iteration_param(binding_pats[0], 'index', body_env)
|
|
77
|
+
bind_code = [index_bind, value_bind].compact.join("\n")
|
|
78
|
+
body_code, = emit_sequence(body_forms, body_env, current_scope, allow_method_definitions: false)
|
|
79
|
+
inject_block("#{coll_code}.each_with_index", "#{acc_var}, (#{value_var}, #{index_var})",
|
|
80
|
+
init_code, bind_code, body_code)
|
|
81
|
+
when 'pairs'
|
|
82
|
+
key_var, key_bind = bind_iteration_param(binding_pats[0], 'key', body_env)
|
|
83
|
+
value_var, value_bind = bind_iteration_param(binding_pats[1], 'value', body_env)
|
|
84
|
+
bind_code = [key_bind, value_bind].compact.join("\n")
|
|
85
|
+
body_code, = emit_sequence(body_forms, body_env, current_scope, allow_method_definitions: false)
|
|
86
|
+
inject_block(coll_code, "#{acc_var}, (#{key_var}, #{value_var})",
|
|
87
|
+
init_code, bind_code, body_code)
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def inject_block(receiver, params, init_code, bind_code, body_code)
|
|
92
|
+
inner = join_code(bind_code, body_code)
|
|
93
|
+
["#{receiver}.inject(#{init_code}) do |#{params}|", indent(inner), 'end'].join("\n")
|
|
94
|
+
end
|
|
95
|
+
|
|
57
96
|
def emit_faccumulate(args, env, current_scope)
|
|
58
97
|
bindings = args[0].items
|
|
98
|
+
emit_error!(:accumulate_no_iterator) if bindings.length < 5
|
|
99
|
+
|
|
59
100
|
acc_name = bindings[0]
|
|
60
101
|
loop_name = bindings[2]
|
|
61
102
|
loop_env = env.child
|
|
@@ -112,7 +153,9 @@ module Kapusta
|
|
|
112
153
|
end
|
|
113
154
|
end
|
|
114
155
|
|
|
115
|
-
def emit_iteration(bindings_vec, env, current_scope)
|
|
156
|
+
def emit_iteration(bindings_vec, env, current_scope, method: 'each')
|
|
157
|
+
emit_error!(:each_no_binding) unless bindings_vec.is_a?(Vec)
|
|
158
|
+
|
|
116
159
|
items = bindings_vec.items
|
|
117
160
|
iter_expr = items.last
|
|
118
161
|
binding_pats = items[0...-1]
|
|
@@ -126,12 +169,13 @@ module Kapusta
|
|
|
126
169
|
if ignored_pattern?(binding_pats[0])
|
|
127
170
|
bind_code = value_bind || ''
|
|
128
171
|
body_code = yield(body_env)
|
|
129
|
-
return iteration_block("#{coll_code}
|
|
172
|
+
return iteration_block("#{coll_code}.#{method} do |#{value_var}|", bind_code, body_code)
|
|
130
173
|
end
|
|
131
174
|
index_var, index_bind = bind_iteration_param(binding_pats[0], 'index', body_env)
|
|
132
175
|
bind_code = [index_bind, value_bind].compact.join("\n")
|
|
133
176
|
body_code = yield(body_env)
|
|
134
|
-
|
|
177
|
+
receiver = method == 'each' ? "#{coll_code}.each_with_index" : "#{coll_code}.each_with_index.#{method}"
|
|
178
|
+
header = "#{receiver} do |#{value_var}, #{index_var}|"
|
|
135
179
|
return iteration_block(header, bind_code, body_code)
|
|
136
180
|
when 'pairs'
|
|
137
181
|
body_env = env.child
|
|
@@ -139,7 +183,7 @@ module Kapusta
|
|
|
139
183
|
value_var, value_bind = bind_iteration_param(binding_pats[1], 'value', body_env)
|
|
140
184
|
bind_code = [key_bind, value_bind].compact.join("\n")
|
|
141
185
|
body_code = yield(body_env)
|
|
142
|
-
header = "#{emit_expr(iter_expr.items[1], env, current_scope)}
|
|
186
|
+
header = "#{emit_expr(iter_expr.items[1], env, current_scope)}.#{method} do |#{key_var}, #{value_var}|"
|
|
143
187
|
return iteration_block(header, bind_code, body_code)
|
|
144
188
|
end
|
|
145
189
|
end
|
|
@@ -149,14 +193,14 @@ module Kapusta
|
|
|
149
193
|
body_env = env.child
|
|
150
194
|
value_var, bind_code = bind_iteration_param(binding_pats[0], 'value', body_env)
|
|
151
195
|
body_code = yield(body_env)
|
|
152
|
-
iteration_block("#{coll_code}
|
|
196
|
+
iteration_block("#{coll_code}.#{method} do |#{value_var}|", bind_code || '', body_code)
|
|
153
197
|
else
|
|
154
198
|
parts_var = temp('parts')
|
|
155
199
|
body_env = env.child
|
|
156
200
|
pairs = binding_pats.each_with_index.map { |pattern, i| [pattern, "#{parts_var}[#{i}]"] }
|
|
157
201
|
bind_code, body_env = emit_iteration_bindings(pairs, body_env)
|
|
158
202
|
body_code = yield(body_env)
|
|
159
|
-
iteration_block("#{coll_code}
|
|
203
|
+
iteration_block("#{coll_code}.#{method} do |*#{parts_var}|", bind_code, body_code)
|
|
160
204
|
end
|
|
161
205
|
end
|
|
162
206
|
|
|
@@ -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,9 +57,13 @@ module Kapusta
|
|
|
58
57
|
end
|
|
59
58
|
|
|
60
59
|
def emit_case(args, env, current_scope, mode)
|
|
60
|
+
clauses = args[1..]
|
|
61
|
+
emit_error!(:case_no_patterns) if clauses.empty?
|
|
62
|
+
emit_error!(:case_odd_patterns) if clauses.length.odd?
|
|
63
|
+
|
|
61
64
|
value_var = temp('case_value')
|
|
62
|
-
body = try_emit_native_case(value_var,
|
|
63
|
-
emit_error!(
|
|
65
|
+
body = try_emit_native_case(value_var, clauses, env, current_scope, mode)
|
|
66
|
+
emit_error!(:case_unsupported) unless body
|
|
64
67
|
[
|
|
65
68
|
'(-> do',
|
|
66
69
|
indent("#{value_var} = #{emit_expr(args[0], env, current_scope)}"),
|
|
@@ -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,14 +104,51 @@ 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)
|
|
107
|
+
when 'quasi-sym' then "Kapusta::MacroSym.new(#{emit_expr(args[0], env, current_scope)})"
|
|
108
|
+
when 'quasi-list' then "Kapusta::List.new([#{args.map { |a| emit_expr(a, env, current_scope) }.join(', ')}])"
|
|
109
|
+
when 'quasi-list-tail' then emit_quasi_list_tail(args, env, current_scope)
|
|
110
|
+
when 'quasi-vec' then "Kapusta::Vec.new([#{args.map { |a| emit_expr(a, env, current_scope) }.join(', ')}])"
|
|
111
|
+
when 'quasi-vec-tail' then emit_quasi_vec_tail(args, env, current_scope)
|
|
112
|
+
when 'quasi-hash' then emit_quasi_hash(args, env, current_scope)
|
|
113
|
+
when 'quasi-gensym' then emit_quasi_gensym(args[0], env, current_scope)
|
|
114
|
+
when 'macro', 'macros', 'import-macros'
|
|
115
|
+
emit_error!(:special_must_be_toplevel, name:)
|
|
99
116
|
else
|
|
100
|
-
emit_error!(
|
|
117
|
+
emit_error!(:unknown_special_form, name:)
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
def emit_quasi_list_tail(args, env, current_scope)
|
|
122
|
+
head_items = args[0]
|
|
123
|
+
tail_expr = emit_expr(args[1], env, current_scope)
|
|
124
|
+
head_code = head_items.items.map { |item| emit_expr(item, env, current_scope) }.join(', ')
|
|
125
|
+
"Kapusta::List.new([#{head_code}, *#{parenthesize(tail_expr)}])"
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
def emit_quasi_vec_tail(args, env, current_scope)
|
|
129
|
+
head_items = args[0]
|
|
130
|
+
tail_expr = emit_expr(args[1], env, current_scope)
|
|
131
|
+
head_code = head_items.items.map { |item| emit_expr(item, env, current_scope) }.join(', ')
|
|
132
|
+
"Kapusta::Vec.new([#{head_code}, *#{parenthesize(tail_expr)}])"
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
def emit_quasi_gensym(arg, env, current_scope)
|
|
136
|
+
"Kapusta::Compiler::MacroExpander.fresh_gensym(#{emit_expr(arg, env, current_scope)})"
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
def emit_quasi_hash(args, env, current_scope)
|
|
140
|
+
pairs = args.each_slice(2).map do |key, value|
|
|
141
|
+
"[#{emit_expr(key, env, current_scope)}, #{emit_expr(value, env, current_scope)}]"
|
|
101
142
|
end
|
|
143
|
+
"Kapusta::HashLit.new([#{pairs.join(', ')}])"
|
|
102
144
|
end
|
|
103
145
|
|
|
104
146
|
def emit_concat(args, env, current_scope)
|
|
105
147
|
return '""' if args.empty?
|
|
106
148
|
|
|
149
|
+
args.each do |arg|
|
|
150
|
+
emit_error!(:vararg_with_operator) if arg.is_a?(Sym) && arg.name == '...'
|
|
151
|
+
end
|
|
107
152
|
args.map { |arg| emit_string_part(arg, env, current_scope) }.join(' + ')
|
|
108
153
|
end
|
|
109
154
|
|
|
@@ -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
|
|
@@ -79,8 +100,7 @@ module Kapusta
|
|
|
79
100
|
if sub.is_a?(Sym)
|
|
80
101
|
raise PatternNotTranslatable if sub.name == '_'
|
|
81
102
|
|
|
82
|
-
|
|
83
|
-
ruby_name = define_local(current_env, bind_name)
|
|
103
|
+
ruby_name = define_local(current_env, sub.name)
|
|
84
104
|
lines << "#{ruby_name} = #{access}"
|
|
85
105
|
else
|
|
86
106
|
sub_code, current_env = try_emit_native_pattern_bind(sub, access, current_env) ||
|
|
@@ -96,8 +116,7 @@ module Kapusta
|
|
|
96
116
|
when Sym
|
|
97
117
|
return ['_', env, nil] if pattern.name == '_'
|
|
98
118
|
|
|
99
|
-
|
|
100
|
-
ruby_name = define_local(env, bind_name)
|
|
119
|
+
ruby_name = define_local(env, pattern.name)
|
|
101
120
|
[ruby_name, env, nil]
|
|
102
121
|
when Vec
|
|
103
122
|
inner = []
|
|
@@ -137,8 +156,7 @@ module Kapusta
|
|
|
137
156
|
|
|
138
157
|
return '*' if sym.name == '_'
|
|
139
158
|
|
|
140
|
-
|
|
141
|
-
"*#{define_local(env, bind_name)}"
|
|
159
|
+
"*#{define_local(env, sym.name)}"
|
|
142
160
|
end
|
|
143
161
|
|
|
144
162
|
class PatternNotTranslatable < StandardError; end
|
|
@@ -179,12 +197,11 @@ module Kapusta
|
|
|
179
197
|
return '_' if name == '_'
|
|
180
198
|
|
|
181
199
|
if nil_allowing_pattern_name?(name)
|
|
182
|
-
|
|
183
|
-
raise PatternNotTranslatable if state[:bound_names].key?(bind_name)
|
|
200
|
+
raise PatternNotTranslatable if state[:bound_names].key?(name)
|
|
184
201
|
|
|
185
|
-
state[:bound_names][
|
|
186
|
-
state[:binding_names] <<
|
|
187
|
-
sanitize_local(
|
|
202
|
+
state[:bound_names][name] = true
|
|
203
|
+
state[:binding_names] << name
|
|
204
|
+
sanitize_local(name)
|
|
188
205
|
else
|
|
189
206
|
binding = mode == :match ? env.lookup_if_defined(name) : nil
|
|
190
207
|
if state[:bound_names].key?(name)
|
|
@@ -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)
|
|
@@ -263,7 +285,8 @@ module Kapusta
|
|
|
263
285
|
|
|
264
286
|
def sanitize_local(name)
|
|
265
287
|
base = Kapusta.kebab_to_snake(name.respond_to?(:name) ? name.name : name)
|
|
266
|
-
base = base.
|
|
288
|
+
base = base.sub(/\A\?/, 'q_').gsub('?', '_q')
|
|
289
|
+
base = base.sub(/\A!/, 'bang_').gsub('!', '_bang')
|
|
267
290
|
base = base.gsub(/[^a-zA-Z0-9_]/, '_')
|
|
268
291
|
if base.empty? || base.match?(/\A\d/) || base.match?(/\A[A-Z]/) || self.class::RUBY_KEYWORDS.include?(base)
|
|
269
292
|
base = "_#{base}"
|