kapusta 0.13.2 → 0.15.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/digit-tools.kap +24 -0
- data/examples/doto-hygiene.kap +2 -2
- data/examples/doto.kap +2 -2
- data/examples/egg-count.kap +1 -1
- data/examples/excel-column.kap +26 -0
- 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/logger-rate-limiter.kap +26 -0
- 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 +10 -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 +172 -48
- data/lib/kapusta/compiler/emitter/patterns.rb +12 -14
- data/lib/kapusta/compiler/emitter/support.rb +70 -81
- data/lib/kapusta/compiler/language.rb +533 -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 +363 -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 +99 -15
- data/spec/formatter_spec.rb +246 -0
- data/spec/lsp_spec.rb +69 -0
- data/spec/require_spec.rb +294 -0
- metadata +23 -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,20 @@ 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
|
-
|
|
166
|
+
body = wrap_singleton(body) if parsed.singleton
|
|
167
|
+
emit_module_wrapper(parsed.name, body)
|
|
87
168
|
end
|
|
88
169
|
|
|
89
170
|
def emit_class_expr(args, env)
|
|
90
|
-
|
|
171
|
+
parsed = Language.parse_class_args(args)
|
|
91
172
|
body = with_class_body do
|
|
92
|
-
emit_sequence(
|
|
173
|
+
emit_sequence(parsed.body, env.child, :class, allow_method_definitions: true, result: false).first
|
|
93
174
|
end
|
|
94
|
-
emit_class_wrapper(
|
|
175
|
+
emit_class_wrapper(parsed.name, parsed.supers, env,
|
|
95
176
|
body)
|
|
96
177
|
end
|
|
97
178
|
|
|
@@ -158,51 +239,32 @@ module Kapusta
|
|
|
158
239
|
end
|
|
159
240
|
|
|
160
241
|
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]
|
|
242
|
+
parsed = Language.parse_try_args(args)
|
|
243
|
+
body_form = parsed.body
|
|
183
244
|
body_code =
|
|
184
|
-
if
|
|
245
|
+
if Language.do_form?(body_form)
|
|
185
246
|
emit_sequence(body_form.rest, env, current_scope, allow_method_definitions: false).first
|
|
186
247
|
else
|
|
187
248
|
emit_expr(body_form, env, current_scope)
|
|
188
249
|
end
|
|
189
250
|
lines = ['begin', indent(body_code)]
|
|
190
|
-
|
|
251
|
+
parsed.clauses.grep(Language::CatchClause).each do |clause|
|
|
191
252
|
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)
|
|
253
|
+
rescue_name = define_local(rescue_env, clause.bind_sym.name)
|
|
254
|
+
body_code, = emit_sequence(clause.body, rescue_env, current_scope, allow_method_definitions: false)
|
|
194
255
|
rescue_line =
|
|
195
|
-
if
|
|
196
|
-
"rescue #{emit_expr(
|
|
256
|
+
if clause.klass
|
|
257
|
+
"rescue #{emit_expr(clause.klass, env, current_scope)} => #{rescue_name}"
|
|
197
258
|
else
|
|
198
259
|
"rescue StandardError => #{rescue_name}"
|
|
199
260
|
end
|
|
200
261
|
lines << rescue_line
|
|
201
262
|
lines << indent(body_code)
|
|
202
263
|
end
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
264
|
+
finally_clauses = parsed.clauses.grep(Language::FinallyClause)
|
|
265
|
+
unless finally_clauses.empty?
|
|
266
|
+
ensure_code = finally_clauses.map do |clause|
|
|
267
|
+
emit_sequence(clause.body, env, current_scope, allow_method_definitions: false).first
|
|
206
268
|
end.join("\n")
|
|
207
269
|
lines << 'ensure'
|
|
208
270
|
lines << indent(ensure_code)
|
|
@@ -310,7 +372,7 @@ module Kapusta
|
|
|
310
372
|
end
|
|
311
373
|
|
|
312
374
|
def emit_self_method_binding_call(binding, args, env, current_scope)
|
|
313
|
-
positional = args
|
|
375
|
+
positional = emit_call_args(args, env, current_scope)
|
|
314
376
|
emit_direct_self_method_call(binding.ruby_name, positional)
|
|
315
377
|
end
|
|
316
378
|
|
|
@@ -341,6 +403,11 @@ module Kapusta
|
|
|
341
403
|
end
|
|
342
404
|
end
|
|
343
405
|
|
|
406
|
+
def emit_multihash_call(head, args, env, current_scope)
|
|
407
|
+
lookup_code = emit_multihash_value(head, env)
|
|
408
|
+
emit_callable_call(lookup_code, args, env, current_scope)
|
|
409
|
+
end
|
|
410
|
+
|
|
344
411
|
def emit_method_path(base_code, segments)
|
|
345
412
|
segments.reduce(base_code) do |acc, segment|
|
|
346
413
|
snake = Kapusta.kebab_to_snake(segment)
|
|
@@ -366,6 +433,9 @@ module Kapusta
|
|
|
366
433
|
def emit_self_call(name, args, env, current_scope)
|
|
367
434
|
positional, kwargs, block_form = split_call_args(args, env, current_scope)
|
|
368
435
|
snake = Kapusta.kebab_to_snake(name)
|
|
436
|
+
if Language.class_body_declaration?(snake) && kwargs.nil? && block_form.nil?
|
|
437
|
+
return positional.empty? ? snake : "#{snake} #{positional.join(', ')}"
|
|
438
|
+
end
|
|
369
439
|
if direct_method_name?(snake)
|
|
370
440
|
return emit_direct_self_call(snake, positional, kwargs, block_form, env, current_scope)
|
|
371
441
|
end
|
|
@@ -394,27 +464,41 @@ module Kapusta
|
|
|
394
464
|
|
|
395
465
|
if !remaining.empty? && remaining.last.is_a?(HashLit) && remaining.last.all_sym_keys?
|
|
396
466
|
kwargs = emit_expr(remaining.last, env, current_scope)
|
|
397
|
-
positional = remaining[0...-1]
|
|
467
|
+
positional = emit_call_args(remaining[0...-1], env, current_scope)
|
|
398
468
|
else
|
|
399
469
|
kwargs = nil
|
|
400
|
-
positional = remaining
|
|
470
|
+
positional = emit_call_args(remaining, env, current_scope)
|
|
401
471
|
end
|
|
402
472
|
[positional, kwargs, block_form]
|
|
403
473
|
end
|
|
404
474
|
|
|
475
|
+
def emit_call_args(args, env, current_scope)
|
|
476
|
+
args.map.with_index do |arg, index|
|
|
477
|
+
code = emit_expr(arg, env, current_scope)
|
|
478
|
+
index == args.length - 1 && multi_value_call_arg?(arg, env) ? "*#{code}" : code
|
|
479
|
+
end
|
|
480
|
+
end
|
|
481
|
+
|
|
482
|
+
def multi_value_call_arg?(arg, env)
|
|
483
|
+
return true if Language.list_head?(arg, 'values')
|
|
484
|
+
return false unless arg.is_a?(List) && arg.head.is_a?(Sym)
|
|
485
|
+
|
|
486
|
+
binding = env.lookup_if_defined(arg.head.name)
|
|
487
|
+
callable_method_binding?(binding) && binding.multi_return
|
|
488
|
+
end
|
|
489
|
+
|
|
405
490
|
def emit_block_proc(block_form, env, current_scope)
|
|
406
491
|
block_form && emit_expr(block_form, env, current_scope)
|
|
407
492
|
end
|
|
408
493
|
|
|
409
494
|
def emit_attached_block(block_form, env, current_scope)
|
|
410
|
-
|
|
411
|
-
return unless
|
|
495
|
+
parsed = Language.parse_function_form(block_form)
|
|
496
|
+
return unless parsed&.anonymous?
|
|
412
497
|
|
|
413
|
-
pattern =
|
|
498
|
+
pattern = parsed.params
|
|
414
499
|
return unless pattern.is_a?(Vec) && simple_parameter_pattern?(pattern)
|
|
415
500
|
|
|
416
|
-
|
|
417
|
-
params, body_code = build_simple_block_parts(pattern, body, env, current_scope)
|
|
501
|
+
params, body_code = build_simple_block_parts(pattern, parsed.body, env, current_scope)
|
|
418
502
|
header = params.empty? ? 'do' : "do |#{params.join(', ')}|"
|
|
419
503
|
[header, indent(body_code), 'end'].join("\n")
|
|
420
504
|
end
|
|
@@ -439,6 +523,7 @@ module Kapusta
|
|
|
439
523
|
end
|
|
440
524
|
|
|
441
525
|
emit_error!(:unexpected_vararg) if name == '...'
|
|
526
|
+
return emit_multihash_value(sym, env) if sym.colonized?
|
|
442
527
|
return emit_multisym_value(sym, env) if sym.dotted?
|
|
443
528
|
return 'ARGV' if name == 'ARGV'
|
|
444
529
|
return name if name.match?(/\A[A-Z]/)
|
|
@@ -460,6 +545,33 @@ module Kapusta
|
|
|
460
545
|
emit_method_path(base_code, segments)
|
|
461
546
|
end
|
|
462
547
|
|
|
548
|
+
def emit_multihash_value(sym, env)
|
|
549
|
+
base_code, segments = multihash_base(sym.colon_segments, env)
|
|
550
|
+
emit_hash_lookup_path(base_code, segments)
|
|
551
|
+
end
|
|
552
|
+
|
|
553
|
+
def emit_hash_lookup_path(base_code, segments)
|
|
554
|
+
receiver = simple_expression?(base_code) ? base_code : parenthesize(base_code)
|
|
555
|
+
segments.reduce(receiver) do |acc, segment|
|
|
556
|
+
"#{acc}[#{Kapusta.kebab_to_snake(segment).to_sym.inspect}]"
|
|
557
|
+
end
|
|
558
|
+
end
|
|
559
|
+
|
|
560
|
+
def multihash_base(segments, env)
|
|
561
|
+
first = segments[0]
|
|
562
|
+
if first == 'self'
|
|
563
|
+
['self', segments[1..]]
|
|
564
|
+
elsif (binding = env.lookup_if_defined(first))
|
|
565
|
+
[binding_value_code(binding), segments[1..]]
|
|
566
|
+
elsif first == 'ARGV'
|
|
567
|
+
['ARGV', segments[1..]]
|
|
568
|
+
elsif first.match?(/\A[A-Z]/)
|
|
569
|
+
[first, segments[1..]]
|
|
570
|
+
else
|
|
571
|
+
emit_error!(:undefined_symbol, name: first)
|
|
572
|
+
end
|
|
573
|
+
end
|
|
574
|
+
|
|
463
575
|
def multisym_base(segments, env)
|
|
464
576
|
if segments[0] == 'self'
|
|
465
577
|
['self', segments[1..]]
|
|
@@ -472,12 +584,24 @@ module Kapusta
|
|
|
472
584
|
const_path << segments[idx]
|
|
473
585
|
idx += 1
|
|
474
586
|
end
|
|
475
|
-
|
|
587
|
+
emit_bad_multisym!(segments) if const_path.empty?
|
|
476
588
|
|
|
477
589
|
[const_path.join('::'), segments[idx..]]
|
|
478
590
|
end
|
|
479
591
|
end
|
|
480
592
|
|
|
593
|
+
def emit_bad_multisym!(segments)
|
|
594
|
+
root = segments[0]
|
|
595
|
+
emit_error!(:bad_multisym,
|
|
596
|
+
path: segments.join('.'),
|
|
597
|
+
segment: root,
|
|
598
|
+
suggestion: bad_multisym_suggestion(root))
|
|
599
|
+
end
|
|
600
|
+
|
|
601
|
+
def bad_multisym_suggestion(root)
|
|
602
|
+
"bind #{root} first, use a capitalized constant path, or call a method on an explicit receiver"
|
|
603
|
+
end
|
|
604
|
+
|
|
481
605
|
def parenthesize(code)
|
|
482
606
|
return code if simple_expression?(code)
|
|
483
607
|
|
|
@@ -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)
|