kapusta 0.13.2 → 0.14.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 +50 -11
- data/bin/check-all +6 -1
- data/bin/compile-examples +7 -2
- data/examples/anagram.kap +3 -3
- data/examples/app/args.kap +9 -0
- data/examples/arrange-coins.kap +3 -3
- data/examples/baseball-game.kap +5 -5
- data/examples/best-time-to-buy-sell-stock.kap +2 -2
- data/examples/binary-search.kap +2 -2
- data/examples/binary-to-decimal.kap +1 -1
- data/examples/blocks-and-kwargs.kap +2 -2
- data/examples/count-effects.kap +1 -1
- data/examples/count-items-matching-rule.kap +18 -0
- data/examples/doto-hygiene.kap +2 -2
- data/examples/doto.kap +2 -2
- data/examples/egg-count.kap +1 -1
- data/examples/exceptions.kap +1 -1
- data/examples/falling-drops.kap +7 -7
- data/examples/fennel-parity-examples.txt +3 -6
- data/examples/good-pairs.kap +18 -0
- data/examples/greet.kap +1 -1
- data/examples/happy-number.kap +2 -2
- data/examples/left-right-difference.kap +60 -0
- data/examples/length-of-last-word.kap +4 -4
- data/examples/manhattan-distance.kap +1 -1
- data/examples/maximum-subarray.kap +23 -7
- data/examples/minimum-start-value.kap +19 -0
- data/examples/move-zeroes.kap +2 -2
- data/examples/mruby-runtime-examples.txt +8 -2
- data/examples/non-constant-local.kap +1 -1
- data/examples/number-of-1-bits.kap +1 -1
- data/examples/number-of-steps.kap +1 -1
- data/examples/palindrome.kap +2 -2
- data/examples/pangram.kap +4 -4
- data/examples/pcall.kap +3 -3
- data/examples/pipeline.kap +4 -4
- data/examples/plus-one.kap +3 -3
- data/examples/raindrops.kap +1 -1
- data/examples/range-width.kap +14 -0
- data/examples/recent-counter.kap +6 -6
- data/examples/require-local-args.kap +9 -0
- data/examples/require-local.kap +7 -0
- data/examples/require-module-local.kap +4 -0
- data/examples/require-module.kap +4 -0
- data/examples/reverse-integer.kap +1 -1
- data/examples/roman-to-integer.kap +3 -3
- data/examples/running-sum.kap +20 -0
- data/examples/safe-lookup.kap +2 -2
- data/examples/single-number.kap +1 -1
- data/examples/stack.kap +14 -14
- data/examples/subtract-product-sum.kap +1 -1
- data/examples/summary-ranges.kap +45 -0
- data/examples/threading.kap +5 -5
- data/examples/tset.kap +1 -1
- data/examples/two-sum-hash.kap +3 -3
- data/examples/two-sum.kap +1 -1
- data/examples/ugly-number.kap +1 -1
- data/examples/underground-system.kap +39 -0
- data/examples/valid-parentheses-1.kap +6 -6
- data/exe/kapusta-ls +49 -2
- data/lib/kapusta/ast.rb +8 -0
- data/lib/kapusta/compiler/emitter/bindings.rb +111 -89
- data/lib/kapusta/compiler/emitter/collections.rb +32 -40
- data/lib/kapusta/compiler/emitter/control_flow.rb +33 -31
- data/lib/kapusta/compiler/emitter/expressions.rb +21 -5
- data/lib/kapusta/compiler/emitter/interop.rb +168 -48
- data/lib/kapusta/compiler/emitter/patterns.rb +12 -14
- data/lib/kapusta/compiler/emitter/support.rb +63 -81
- data/lib/kapusta/compiler/language.rb +522 -0
- data/lib/kapusta/compiler/lua_compat.rb +23 -28
- data/lib/kapusta/compiler/macro_expander.rb +30 -30
- data/lib/kapusta/compiler/macro_lowerer.rb +12 -24
- data/lib/kapusta/compiler/normalizer.rb +25 -17
- data/lib/kapusta/compiler.rb +3 -24
- data/lib/kapusta/env.rb +2 -2
- data/lib/kapusta/errors.rb +2 -1
- data/lib/kapusta/formatter/ast_helpers.rb +78 -0
- data/lib/kapusta/formatter/cli.rb +125 -0
- data/lib/kapusta/formatter/line_helpers.rb +44 -0
- data/lib/kapusta/formatter/validator.rb +32 -0
- data/lib/kapusta/formatter.rb +354 -325
- data/lib/kapusta/lsp/identifier.rb +1 -1
- data/lib/kapusta/lsp/rename.rb +21 -11
- data/lib/kapusta/lsp/scope_walker.rb +122 -212
- data/lib/kapusta/lsp/workspace_index.rb +17 -5
- data/lib/kapusta/reader.rb +4 -2
- data/lib/kapusta/version.rb +1 -1
- data/lib/kapusta.rb +39 -6
- data/spec/cli_spec.rb +13 -0
- data/spec/examples_errors_spec.rb +3 -1
- data/spec/examples_spec.rb +67 -15
- data/spec/formatter_spec.rb +246 -0
- data/spec/lsp_spec.rb +69 -0
- data/spec/require_spec.rb +294 -0
- metadata +20 -2
- data/examples/describe.kap +0 -9
|
@@ -37,6 +37,7 @@ module Kapusta
|
|
|
37
37
|
args = list.rest
|
|
38
38
|
if head.is_a?(Sym)
|
|
39
39
|
return emit_special(head.name, args, env, current_scope) if special_form?(head.name)
|
|
40
|
+
return emit_multihash_call(head, args, env, current_scope) if head.colonized?
|
|
40
41
|
return emit_multisym_call(head, args, env, current_scope) if head.dotted?
|
|
41
42
|
if (binding = env.lookup_if_defined(head.name))
|
|
42
43
|
return emit_bound_call(binding, args, env, current_scope)
|
|
@@ -50,14 +51,29 @@ module Kapusta
|
|
|
50
51
|
emit_error!(:cannot_call_literal, value: head.inspect)
|
|
51
52
|
end
|
|
52
53
|
|
|
54
|
+
if (method_call = method_call_with_receiver_expression(head, args))
|
|
55
|
+
return emit_method_call(method_call, env, current_scope)
|
|
56
|
+
end
|
|
57
|
+
|
|
53
58
|
emit_callable_call(emit_expr(head, env, current_scope), args, env, current_scope)
|
|
54
59
|
end
|
|
55
60
|
|
|
61
|
+
def method_call_with_receiver_expression(head, args)
|
|
62
|
+
return unless head.is_a?(List)
|
|
63
|
+
return unless head.items.length == 3
|
|
64
|
+
return unless head.head.is_a?(Sym) && head.head.name == '.'
|
|
65
|
+
|
|
66
|
+
method = head.items[2]
|
|
67
|
+
return unless method.is_a?(Symbol) || method.is_a?(String)
|
|
68
|
+
|
|
69
|
+
[head.items[1], method, *args]
|
|
70
|
+
end
|
|
71
|
+
|
|
56
72
|
def emit_special(name, args, env, current_scope)
|
|
57
73
|
case name
|
|
58
|
-
when
|
|
74
|
+
when *Language::FUNCTION_HEADS then emit_fn(args, env, current_scope)
|
|
59
75
|
when 'let' then emit_let(args, env, current_scope)
|
|
60
|
-
when 'local', 'var' then emit_local_expr(args, env, current_scope)
|
|
76
|
+
when 'local', 'var' then emit_local_expr(name, args, env, current_scope)
|
|
61
77
|
when 'global' then emit_global_expr(args, env, current_scope)
|
|
62
78
|
when 'set' then emit_set_expr(args, env, current_scope)
|
|
63
79
|
when 'if' then emit_if(args, env, current_scope)
|
|
@@ -75,9 +91,9 @@ module Kapusta
|
|
|
75
91
|
when 'accumulate' then emit_accumulate(args, env, current_scope)
|
|
76
92
|
when 'faccumulate' then emit_faccumulate(args, env, current_scope)
|
|
77
93
|
when 'hashfn' then emit_hashfn(args, env, current_scope)
|
|
78
|
-
when '.' then
|
|
79
|
-
when '
|
|
80
|
-
when ':' then
|
|
94
|
+
when '.' then emit_method_call(args, env, current_scope)
|
|
95
|
+
when '?:' then emit_safe_lookup(args, env, current_scope)
|
|
96
|
+
when ':' then emit_lookup(args, env, current_scope)
|
|
81
97
|
when '..' then emit_concat(args, env, current_scope)
|
|
82
98
|
when 'length' then "#{parenthesize(emit_expr(args[0], env, current_scope))}.length"
|
|
83
99
|
when 'require' then emit_require(args[0], env, current_scope)
|
|
@@ -7,7 +7,7 @@ module Kapusta
|
|
|
7
7
|
private
|
|
8
8
|
|
|
9
9
|
def emit_lookup(args, env, current_scope)
|
|
10
|
-
emit_error!(:dot_no_args) if args.
|
|
10
|
+
emit_error!(:dot_no_args) if args.length < 2
|
|
11
11
|
|
|
12
12
|
object_code = emit_expr(args[0], env, current_scope)
|
|
13
13
|
keys = args[1..].map { |arg| emit_expr(arg, env, current_scope) }
|
|
@@ -27,10 +27,12 @@ module Kapusta
|
|
|
27
27
|
BINARY_OPERATOR_METHODS = %w[<=> ** << >> & | ^ === =~].freeze
|
|
28
28
|
private_constant :BINARY_OPERATOR_METHODS
|
|
29
29
|
|
|
30
|
-
def
|
|
30
|
+
def emit_method_call(args, env, current_scope)
|
|
31
|
+
emit_error!(:dot_no_args) if args.empty?
|
|
32
|
+
|
|
31
33
|
receiver = emit_expr(args[0], env, current_scope)
|
|
32
34
|
method_form = args[1]
|
|
33
|
-
positional, kwargs, block_form = split_call_args(args[2..], env, current_scope)
|
|
35
|
+
positional, kwargs, block_form = split_call_args(args[2..] || [], env, current_scope)
|
|
34
36
|
literal_name = method_form if method_form.is_a?(Symbol) || method_form.is_a?(String)
|
|
35
37
|
if literal_name && binary_operator_call?(literal_name.to_s, positional, kwargs, block_form)
|
|
36
38
|
return emit_binary_operator_call(receiver, literal_name.to_s, positional[0])
|
|
@@ -56,6 +58,13 @@ module Kapusta
|
|
|
56
58
|
end
|
|
57
59
|
|
|
58
60
|
def emit_require(arg, env, current_scope)
|
|
61
|
+
if arg.is_a?(Symbol)
|
|
62
|
+
feature = arg.to_s.tr('.', '/')
|
|
63
|
+
return "require_relative #{feature.inspect}" if kapusta_feature_source?(feature)
|
|
64
|
+
|
|
65
|
+
return "require #{feature.inspect}"
|
|
66
|
+
end
|
|
67
|
+
|
|
59
68
|
literal = require_path_literal(arg)
|
|
60
69
|
if literal&.match?(%r{\A\.\.?/})
|
|
61
70
|
cleaned = literal.delete_suffix('.kap').sub(%r{\A\./}, '')
|
|
@@ -71,6 +80,76 @@ module Kapusta
|
|
|
71
80
|
"require #{path_code}"
|
|
72
81
|
end
|
|
73
82
|
|
|
83
|
+
def kapusta_require_module_constant(form)
|
|
84
|
+
return unless form.is_a?(List)
|
|
85
|
+
return unless form.head.is_a?(Sym) && form.head.name == 'require'
|
|
86
|
+
return unless form.rest.length == 1
|
|
87
|
+
|
|
88
|
+
path = kapusta_require_source_path(form.rest[0])
|
|
89
|
+
return unless path
|
|
90
|
+
|
|
91
|
+
header = Reader.read_all(File.read(path)).first
|
|
92
|
+
return unless Language.header_form?(header)
|
|
93
|
+
|
|
94
|
+
segments = header_constant_segments(header)
|
|
95
|
+
segments&.join('::')
|
|
96
|
+
rescue Errno::ENOENT, Kapusta::Error
|
|
97
|
+
nil
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def kapusta_require_source_path(arg)
|
|
101
|
+
return kapusta_feature_source_path(arg.to_s.tr('.', '/')) if arg.is_a?(Symbol)
|
|
102
|
+
|
|
103
|
+
literal = require_path_literal(arg)
|
|
104
|
+
return unless literal&.match?(%r{\A\.\.?/})
|
|
105
|
+
|
|
106
|
+
kapusta_local_source_path(literal)
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def kapusta_feature_source?(feature)
|
|
110
|
+
!kapusta_feature_source_path(feature).nil?
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
def kapusta_feature_source_path(feature)
|
|
114
|
+
return if @path.nil? || @path.start_with?('(')
|
|
115
|
+
|
|
116
|
+
path = File.expand_path("#{feature}.kap", File.dirname(File.expand_path(@path)))
|
|
117
|
+
path if File.file?(path)
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
def kapusta_local_source?(feature)
|
|
121
|
+
!kapusta_local_source_path(feature).nil?
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
def kapusta_local_source_path(feature)
|
|
125
|
+
return if @path.nil? || @path.start_with?('(')
|
|
126
|
+
|
|
127
|
+
base = File.dirname(File.expand_path(@path))
|
|
128
|
+
path = File.absolute_path?(feature) ? feature : File.expand_path(feature, base)
|
|
129
|
+
candidates = File.extname(path).empty? ? ["#{path}.kap"] : [path]
|
|
130
|
+
candidates.find { |candidate| File.file?(candidate) && candidate.end_with?('.kap') }
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
def header_constant_segments(header)
|
|
134
|
+
parsed =
|
|
135
|
+
if header.head.name == 'module'
|
|
136
|
+
Language.parse_module_form(header)
|
|
137
|
+
else
|
|
138
|
+
Language.parse_class_form(header)
|
|
139
|
+
end
|
|
140
|
+
segments = constant_segments(parsed.name)
|
|
141
|
+
return unless segments
|
|
142
|
+
|
|
143
|
+
if header.head.name == 'module' &&
|
|
144
|
+
parsed.body.length == 1 &&
|
|
145
|
+
Language.bodyless_header?(parsed.body[0])
|
|
146
|
+
inner_segments = header_constant_segments(parsed.body[0])
|
|
147
|
+
return [*segments, *inner_segments] if inner_segments
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
segments
|
|
151
|
+
end
|
|
152
|
+
|
|
74
153
|
def require_path_literal(arg)
|
|
75
154
|
case arg
|
|
76
155
|
when Sym then arg.name
|
|
@@ -80,18 +159,19 @@ module Kapusta
|
|
|
80
159
|
end
|
|
81
160
|
|
|
82
161
|
def emit_module_expr(args, env)
|
|
162
|
+
parsed = Language.parse_module_args(args)
|
|
83
163
|
body = with_class_body do
|
|
84
|
-
emit_sequence(
|
|
164
|
+
emit_sequence(parsed.body, env.child, :module, allow_method_definitions: true, result: false).first
|
|
85
165
|
end
|
|
86
|
-
emit_module_wrapper(
|
|
166
|
+
emit_module_wrapper(parsed.name, body)
|
|
87
167
|
end
|
|
88
168
|
|
|
89
169
|
def emit_class_expr(args, env)
|
|
90
|
-
|
|
170
|
+
parsed = Language.parse_class_args(args)
|
|
91
171
|
body = with_class_body do
|
|
92
|
-
emit_sequence(
|
|
172
|
+
emit_sequence(parsed.body, env.child, :class, allow_method_definitions: true, result: false).first
|
|
93
173
|
end
|
|
94
|
-
emit_class_wrapper(
|
|
174
|
+
emit_class_wrapper(parsed.name, parsed.supers, env,
|
|
95
175
|
body)
|
|
96
176
|
end
|
|
97
177
|
|
|
@@ -158,51 +238,32 @@ module Kapusta
|
|
|
158
238
|
end
|
|
159
239
|
|
|
160
240
|
def emit_try(args, env, current_scope)
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
args[1..].each do |clause|
|
|
164
|
-
head = clause.head
|
|
165
|
-
if head.is_a?(Sym) && head.name == 'catch'
|
|
166
|
-
rest = clause.items[1..]
|
|
167
|
-
if rest[0].is_a?(Sym) && (rest[0].name.match?(/\A[A-Z]/) || rest[0].dotted?)
|
|
168
|
-
klass_form = rest[0]
|
|
169
|
-
bind_sym = rest[1]
|
|
170
|
-
body = rest[2..]
|
|
171
|
-
else
|
|
172
|
-
klass_form = nil
|
|
173
|
-
bind_sym = rest[0]
|
|
174
|
-
body = rest[1..]
|
|
175
|
-
end
|
|
176
|
-
catches << [klass_form, bind_sym, body]
|
|
177
|
-
elsif head.is_a?(Sym) && head.name == 'finally'
|
|
178
|
-
finally_bodies << clause.items[1..]
|
|
179
|
-
end
|
|
180
|
-
end
|
|
181
|
-
|
|
182
|
-
body_form = args[0]
|
|
241
|
+
parsed = Language.parse_try_args(args)
|
|
242
|
+
body_form = parsed.body
|
|
183
243
|
body_code =
|
|
184
|
-
if
|
|
244
|
+
if Language.do_form?(body_form)
|
|
185
245
|
emit_sequence(body_form.rest, env, current_scope, allow_method_definitions: false).first
|
|
186
246
|
else
|
|
187
247
|
emit_expr(body_form, env, current_scope)
|
|
188
248
|
end
|
|
189
249
|
lines = ['begin', indent(body_code)]
|
|
190
|
-
|
|
250
|
+
parsed.clauses.grep(Language::CatchClause).each do |clause|
|
|
191
251
|
rescue_env = env.child
|
|
192
|
-
rescue_name = define_local(rescue_env, bind_sym.name)
|
|
193
|
-
body_code, = emit_sequence(body, rescue_env, current_scope, allow_method_definitions: false)
|
|
252
|
+
rescue_name = define_local(rescue_env, clause.bind_sym.name)
|
|
253
|
+
body_code, = emit_sequence(clause.body, rescue_env, current_scope, allow_method_definitions: false)
|
|
194
254
|
rescue_line =
|
|
195
|
-
if
|
|
196
|
-
"rescue #{emit_expr(
|
|
255
|
+
if clause.klass
|
|
256
|
+
"rescue #{emit_expr(clause.klass, env, current_scope)} => #{rescue_name}"
|
|
197
257
|
else
|
|
198
258
|
"rescue StandardError => #{rescue_name}"
|
|
199
259
|
end
|
|
200
260
|
lines << rescue_line
|
|
201
261
|
lines << indent(body_code)
|
|
202
262
|
end
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
263
|
+
finally_clauses = parsed.clauses.grep(Language::FinallyClause)
|
|
264
|
+
unless finally_clauses.empty?
|
|
265
|
+
ensure_code = finally_clauses.map do |clause|
|
|
266
|
+
emit_sequence(clause.body, env, current_scope, allow_method_definitions: false).first
|
|
206
267
|
end.join("\n")
|
|
207
268
|
lines << 'ensure'
|
|
208
269
|
lines << indent(ensure_code)
|
|
@@ -310,7 +371,7 @@ module Kapusta
|
|
|
310
371
|
end
|
|
311
372
|
|
|
312
373
|
def emit_self_method_binding_call(binding, args, env, current_scope)
|
|
313
|
-
positional = args
|
|
374
|
+
positional = emit_call_args(args, env, current_scope)
|
|
314
375
|
emit_direct_self_method_call(binding.ruby_name, positional)
|
|
315
376
|
end
|
|
316
377
|
|
|
@@ -341,6 +402,11 @@ module Kapusta
|
|
|
341
402
|
end
|
|
342
403
|
end
|
|
343
404
|
|
|
405
|
+
def emit_multihash_call(head, args, env, current_scope)
|
|
406
|
+
lookup_code = emit_multihash_value(head, env)
|
|
407
|
+
emit_callable_call(lookup_code, args, env, current_scope)
|
|
408
|
+
end
|
|
409
|
+
|
|
344
410
|
def emit_method_path(base_code, segments)
|
|
345
411
|
segments.reduce(base_code) do |acc, segment|
|
|
346
412
|
snake = Kapusta.kebab_to_snake(segment)
|
|
@@ -394,27 +460,41 @@ module Kapusta
|
|
|
394
460
|
|
|
395
461
|
if !remaining.empty? && remaining.last.is_a?(HashLit) && remaining.last.all_sym_keys?
|
|
396
462
|
kwargs = emit_expr(remaining.last, env, current_scope)
|
|
397
|
-
positional = remaining[0...-1]
|
|
463
|
+
positional = emit_call_args(remaining[0...-1], env, current_scope)
|
|
398
464
|
else
|
|
399
465
|
kwargs = nil
|
|
400
|
-
positional = remaining
|
|
466
|
+
positional = emit_call_args(remaining, env, current_scope)
|
|
401
467
|
end
|
|
402
468
|
[positional, kwargs, block_form]
|
|
403
469
|
end
|
|
404
470
|
|
|
471
|
+
def emit_call_args(args, env, current_scope)
|
|
472
|
+
args.map.with_index do |arg, index|
|
|
473
|
+
code = emit_expr(arg, env, current_scope)
|
|
474
|
+
index == args.length - 1 && multi_value_call_arg?(arg, env) ? "*#{code}" : code
|
|
475
|
+
end
|
|
476
|
+
end
|
|
477
|
+
|
|
478
|
+
def multi_value_call_arg?(arg, env)
|
|
479
|
+
return true if Language.list_head?(arg, 'values')
|
|
480
|
+
return false unless arg.is_a?(List) && arg.head.is_a?(Sym)
|
|
481
|
+
|
|
482
|
+
binding = env.lookup_if_defined(arg.head.name)
|
|
483
|
+
callable_method_binding?(binding) && binding.multi_return
|
|
484
|
+
end
|
|
485
|
+
|
|
405
486
|
def emit_block_proc(block_form, env, current_scope)
|
|
406
487
|
block_form && emit_expr(block_form, env, current_scope)
|
|
407
488
|
end
|
|
408
489
|
|
|
409
490
|
def emit_attached_block(block_form, env, current_scope)
|
|
410
|
-
|
|
411
|
-
return unless
|
|
491
|
+
parsed = Language.parse_function_form(block_form)
|
|
492
|
+
return unless parsed&.anonymous?
|
|
412
493
|
|
|
413
|
-
pattern =
|
|
494
|
+
pattern = parsed.params
|
|
414
495
|
return unless pattern.is_a?(Vec) && simple_parameter_pattern?(pattern)
|
|
415
496
|
|
|
416
|
-
|
|
417
|
-
params, body_code = build_simple_block_parts(pattern, body, env, current_scope)
|
|
497
|
+
params, body_code = build_simple_block_parts(pattern, parsed.body, env, current_scope)
|
|
418
498
|
header = params.empty? ? 'do' : "do |#{params.join(', ')}|"
|
|
419
499
|
[header, indent(body_code), 'end'].join("\n")
|
|
420
500
|
end
|
|
@@ -439,6 +519,7 @@ module Kapusta
|
|
|
439
519
|
end
|
|
440
520
|
|
|
441
521
|
emit_error!(:unexpected_vararg) if name == '...'
|
|
522
|
+
return emit_multihash_value(sym, env) if sym.colonized?
|
|
442
523
|
return emit_multisym_value(sym, env) if sym.dotted?
|
|
443
524
|
return 'ARGV' if name == 'ARGV'
|
|
444
525
|
return name if name.match?(/\A[A-Z]/)
|
|
@@ -460,6 +541,33 @@ module Kapusta
|
|
|
460
541
|
emit_method_path(base_code, segments)
|
|
461
542
|
end
|
|
462
543
|
|
|
544
|
+
def emit_multihash_value(sym, env)
|
|
545
|
+
base_code, segments = multihash_base(sym.colon_segments, env)
|
|
546
|
+
emit_hash_lookup_path(base_code, segments)
|
|
547
|
+
end
|
|
548
|
+
|
|
549
|
+
def emit_hash_lookup_path(base_code, segments)
|
|
550
|
+
receiver = simple_expression?(base_code) ? base_code : parenthesize(base_code)
|
|
551
|
+
segments.reduce(receiver) do |acc, segment|
|
|
552
|
+
"#{acc}[#{Kapusta.kebab_to_snake(segment).to_sym.inspect}]"
|
|
553
|
+
end
|
|
554
|
+
end
|
|
555
|
+
|
|
556
|
+
def multihash_base(segments, env)
|
|
557
|
+
first = segments[0]
|
|
558
|
+
if first == 'self'
|
|
559
|
+
['self', segments[1..]]
|
|
560
|
+
elsif (binding = env.lookup_if_defined(first))
|
|
561
|
+
[binding_value_code(binding), segments[1..]]
|
|
562
|
+
elsif first == 'ARGV'
|
|
563
|
+
['ARGV', segments[1..]]
|
|
564
|
+
elsif first.match?(/\A[A-Z]/)
|
|
565
|
+
[first, segments[1..]]
|
|
566
|
+
else
|
|
567
|
+
emit_error!(:undefined_symbol, name: first)
|
|
568
|
+
end
|
|
569
|
+
end
|
|
570
|
+
|
|
463
571
|
def multisym_base(segments, env)
|
|
464
572
|
if segments[0] == 'self'
|
|
465
573
|
['self', segments[1..]]
|
|
@@ -472,12 +580,24 @@ module Kapusta
|
|
|
472
580
|
const_path << segments[idx]
|
|
473
581
|
idx += 1
|
|
474
582
|
end
|
|
475
|
-
|
|
583
|
+
emit_bad_multisym!(segments) if const_path.empty?
|
|
476
584
|
|
|
477
585
|
[const_path.join('::'), segments[idx..]]
|
|
478
586
|
end
|
|
479
587
|
end
|
|
480
588
|
|
|
589
|
+
def emit_bad_multisym!(segments)
|
|
590
|
+
root = segments[0]
|
|
591
|
+
emit_error!(:bad_multisym,
|
|
592
|
+
path: segments.join('.'),
|
|
593
|
+
segment: root,
|
|
594
|
+
suggestion: bad_multisym_suggestion(root))
|
|
595
|
+
end
|
|
596
|
+
|
|
597
|
+
def bad_multisym_suggestion(root)
|
|
598
|
+
"bind #{root} first, use a capitalized constant path, or call a method on an explicit receiver"
|
|
599
|
+
end
|
|
600
|
+
|
|
481
601
|
def parenthesize(code)
|
|
482
602
|
return code if simple_expression?(code)
|
|
483
603
|
|
|
@@ -23,7 +23,7 @@ module Kapusta
|
|
|
23
23
|
|
|
24
24
|
def validate_binding_symbol!(sym)
|
|
25
25
|
name = sym.name
|
|
26
|
-
emit_error!(:shadowed_special, name:) if
|
|
26
|
+
emit_error!(:shadowed_special, name:) if Language.special_form?(name)
|
|
27
27
|
return unless sym.is_a?(MacroSym)
|
|
28
28
|
|
|
29
29
|
emit_error!(:macro_unsafe_bind, name:)
|
|
@@ -42,8 +42,8 @@ module Kapusta
|
|
|
42
42
|
|
|
43
43
|
def try_emit_native_pattern_bind(pattern, value_code, env)
|
|
44
44
|
case pattern
|
|
45
|
-
when Vec
|
|
46
|
-
|
|
45
|
+
when Vec, List
|
|
46
|
+
try_emit_native_seq_bind(pattern, value_code, env)
|
|
47
47
|
when HashLit
|
|
48
48
|
try_emit_native_hash_bind(pattern, value_code, env)
|
|
49
49
|
end
|
|
@@ -51,7 +51,7 @@ module Kapusta
|
|
|
51
51
|
nil
|
|
52
52
|
end
|
|
53
53
|
|
|
54
|
-
def
|
|
54
|
+
def try_emit_native_seq_bind(pattern, value_code, env)
|
|
55
55
|
validate_destructure_pattern!(pattern)
|
|
56
56
|
parts = []
|
|
57
57
|
deferred = []
|
|
@@ -256,7 +256,7 @@ module Kapusta
|
|
|
256
256
|
def compile_compat_pin(pattern, value_code, env, mode:, allow_pins:, state:)
|
|
257
257
|
raise PatternNotTranslatable unless allow_pins && mode == :case
|
|
258
258
|
|
|
259
|
-
name_sym = pattern
|
|
259
|
+
name_sym = Language.parse_pin_pattern(pattern)&.name
|
|
260
260
|
raise PatternNotTranslatable unless name_sym.is_a?(Sym)
|
|
261
261
|
|
|
262
262
|
binding = env.lookup_if_defined(name_sym.name)
|
|
@@ -405,7 +405,7 @@ module Kapusta
|
|
|
405
405
|
def compile_native_pin(pattern, env, mode:, allow_pins:)
|
|
406
406
|
raise PatternNotTranslatable unless allow_pins && mode == :case
|
|
407
407
|
|
|
408
|
-
name_sym = pattern
|
|
408
|
+
name_sym = Language.parse_pin_pattern(pattern)&.name
|
|
409
409
|
raise PatternNotTranslatable unless name_sym.is_a?(Sym)
|
|
410
410
|
|
|
411
411
|
binding = env.lookup_if_defined(name_sym.name)
|
|
@@ -418,7 +418,7 @@ module Kapusta
|
|
|
418
418
|
initial_bound = state[:bound_names].dup
|
|
419
419
|
initial_names = state[:binding_names].length
|
|
420
420
|
initial_guards = state[:guards].length
|
|
421
|
-
variants = pattern.
|
|
421
|
+
variants = Language.parse_or_pattern(pattern).alternatives.map do |subpattern|
|
|
422
422
|
alt_state = {
|
|
423
423
|
bound_names: initial_bound.dup,
|
|
424
424
|
binding_names: state[:binding_names].dup,
|
|
@@ -446,25 +446,23 @@ module Kapusta
|
|
|
446
446
|
when HashLit
|
|
447
447
|
pattern.pairs.flat_map { |_key, value| pattern_names(value) }
|
|
448
448
|
when List
|
|
449
|
-
|
|
449
|
+
parsed = Language.parse_where_pattern(pattern)
|
|
450
|
+
parsed ? pattern_names(parsed.inner) : []
|
|
450
451
|
else
|
|
451
452
|
[]
|
|
452
453
|
end
|
|
453
454
|
end
|
|
454
455
|
|
|
455
456
|
def where_pattern?(pattern)
|
|
456
|
-
|
|
457
|
+
!Language.parse_where_pattern(pattern).nil?
|
|
457
458
|
end
|
|
458
459
|
|
|
459
460
|
def pin_pattern?(pattern)
|
|
460
|
-
pattern.
|
|
461
|
-
pattern.items.length == 2 &&
|
|
462
|
-
pattern.head.is_a?(Sym) &&
|
|
463
|
-
pattern.head.name == '='
|
|
461
|
+
!Language.parse_pin_pattern(pattern).nil?
|
|
464
462
|
end
|
|
465
463
|
|
|
466
464
|
def or_pattern?(pattern)
|
|
467
|
-
|
|
465
|
+
!Language.parse_or_pattern(pattern).nil?
|
|
468
466
|
end
|
|
469
467
|
|
|
470
468
|
def nil_allowing_pattern_name?(name)
|