ruby-next-core 0.15.3 → 1.0.0.rc.1
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/CHANGELOG.md +28 -0
- data/README.md +118 -48
- data/bin/mspec +11 -0
- data/lib/.rbnext/2.1/ruby-next/commands/nextify.rb +295 -0
- data/lib/.rbnext/2.1/ruby-next/core.rb +10 -2
- data/lib/.rbnext/2.1/ruby-next/language.rb +54 -10
- data/lib/.rbnext/2.3/ruby-next/commands/nextify.rb +82 -2
- data/lib/.rbnext/2.3/ruby-next/config.rb +79 -0
- data/lib/.rbnext/2.3/ruby-next/core/data.rb +159 -0
- data/lib/.rbnext/2.3/ruby-next/language/rewriters/2.7/pattern_matching.rb +2 -2
- data/lib/.rbnext/2.3/ruby-next/language/rewriters/base.rb +6 -32
- data/lib/.rbnext/2.3/ruby-next/utils.rb +3 -22
- data/lib/.rbnext/2.6/ruby-next/core/data.rb +159 -0
- data/lib/.rbnext/2.7/ruby-next/core/data.rb +159 -0
- data/lib/.rbnext/2.7/ruby-next/core.rb +10 -2
- data/lib/.rbnext/2.7/ruby-next/language/paco_parsers/string_literals.rb +109 -0
- data/lib/.rbnext/2.7/ruby-next/language/rewriters/2.7/pattern_matching.rb +2 -2
- data/lib/.rbnext/2.7/ruby-next/language/rewriters/text.rb +132 -0
- data/lib/.rbnext/3.2/ruby-next/commands/base.rb +55 -0
- data/lib/.rbnext/3.2/ruby-next/language/rewriters/2.7/pattern_matching.rb +1095 -0
- data/lib/.rbnext/3.2/ruby-next/rubocop.rb +210 -0
- data/lib/ruby-next/commands/nextify.rb +84 -2
- data/lib/ruby-next/config.rb +27 -0
- data/lib/ruby-next/core/data.rb +159 -0
- data/lib/ruby-next/core/matchdata/deconstruct.rb +9 -0
- data/lib/ruby-next/core/matchdata/deconstruct_keys.rb +20 -0
- data/lib/ruby-next/core/matchdata/named_captures.rb +11 -0
- data/lib/ruby-next/core/refinement/import.rb +44 -36
- data/lib/ruby-next/core/time/deconstruct_keys.rb +30 -0
- data/lib/ruby-next/core.rb +10 -2
- data/lib/ruby-next/irb.rb +2 -2
- data/lib/ruby-next/language/bootsnap.rb +2 -25
- data/lib/ruby-next/language/paco_parser.rb +7 -0
- data/lib/ruby-next/language/paco_parsers/base.rb +47 -0
- data/lib/ruby-next/language/paco_parsers/comments.rb +26 -0
- data/lib/ruby-next/language/paco_parsers/string_literals.rb +109 -0
- data/lib/ruby-next/language/parser.rb +24 -2
- data/lib/ruby-next/language/rewriters/3.0/args_forward_leading.rb +2 -2
- data/lib/ruby-next/language/rewriters/3.1/oneline_pattern_parensless.rb +1 -1
- data/lib/ruby-next/language/rewriters/3.2/anonymous_restargs.rb +104 -0
- data/lib/ruby-next/language/rewriters/abstract.rb +57 -0
- data/lib/ruby-next/language/rewriters/base.rb +6 -32
- data/lib/ruby-next/language/rewriters/edge/it_param.rb +58 -0
- data/lib/ruby-next/language/rewriters/edge.rb +12 -0
- data/lib/ruby-next/language/rewriters/proposed/bind_vars_pattern.rb +3 -0
- data/lib/ruby-next/language/rewriters/proposed/method_reference.rb +9 -20
- data/lib/ruby-next/language/rewriters/text.rb +132 -0
- data/lib/ruby-next/language/runtime.rb +9 -86
- data/lib/ruby-next/language/setup.rb +5 -2
- data/lib/ruby-next/language/unparser.rb +5 -0
- data/lib/ruby-next/language.rb +54 -10
- data/lib/ruby-next/pry.rb +1 -1
- data/lib/ruby-next/rubocop.rb +2 -0
- data/lib/ruby-next/utils.rb +3 -22
- data/lib/ruby-next/version.rb +1 -1
- data/lib/uby-next.rb +2 -2
- metadata +65 -12
@@ -0,0 +1,1095 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RubyNext
|
4
|
+
module Language
|
5
|
+
module Rewriters
|
6
|
+
using RubyNext
|
7
|
+
|
8
|
+
using(Module.new do
|
9
|
+
refine ::Parser::AST::Node do
|
10
|
+
def to_ast_node
|
11
|
+
self
|
12
|
+
end
|
13
|
+
|
14
|
+
# Useful to generate simple operation nodes
|
15
|
+
# (e.g., 'a + b')
|
16
|
+
def -(other)
|
17
|
+
::Parser::AST::Node.new(:send, [self, :-, other.to_ast_node])
|
18
|
+
end
|
19
|
+
|
20
|
+
def +(other)
|
21
|
+
::Parser::AST::Node.new(:send, [self, :+, other.to_ast_node])
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
refine String do
|
26
|
+
def to_ast_node
|
27
|
+
::Parser::AST::Node.new(:str, [self])
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
refine Symbol do
|
32
|
+
def to_ast_node
|
33
|
+
::Parser::AST::Node.new(:sym, [self])
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
refine Integer do
|
38
|
+
def to_ast_node
|
39
|
+
::Parser::AST::Node.new(:int, [self])
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end)
|
43
|
+
|
44
|
+
# We can memoize structural predicates to avoid double calculation.
|
45
|
+
#
|
46
|
+
# For example, consider the following case and the corresponding predicate chains:
|
47
|
+
#
|
48
|
+
# case val
|
49
|
+
# in [:ok, 200] #=> [:respond_to_deconstruct, :deconstruct_type, :arr_size_is_2]
|
50
|
+
# in [:created, 201] #=> [:respond_to_deconstruct, :deconstruct_type, :arr_size_is_2]
|
51
|
+
# in [401 | 403] #=> [:respond_to_deconstruct, :deconstruct_type, :arr_size_is_1]
|
52
|
+
# end
|
53
|
+
#
|
54
|
+
# We can minimize the number of predicate calls by storing the intermediate values (prefixed with `p_`) and using them
|
55
|
+
# in the subsequent calls:
|
56
|
+
#
|
57
|
+
# case val
|
58
|
+
# in [:ok, 200] #=> [:respond_to_deconstruct, :deconstruct_type, :arr_size_is_2]
|
59
|
+
# in [:created, 201] #=> [:p_deconstructed, :p_arr_size_2]
|
60
|
+
# in [401 | 403] #=> [:p_deconstructed, :arr_size_is_1]
|
61
|
+
# end
|
62
|
+
#
|
63
|
+
# This way we mimic a naive decision tree algorithim.
|
64
|
+
module Predicates
|
65
|
+
class Processor < ::Parser::TreeRewriter
|
66
|
+
attr_reader :predicates
|
67
|
+
|
68
|
+
def initialize(predicates)
|
69
|
+
@predicates = predicates
|
70
|
+
super()
|
71
|
+
end
|
72
|
+
|
73
|
+
def on_lvasgn(node)
|
74
|
+
lvar, val = *node.children
|
75
|
+
if predicates.store[lvar] == false
|
76
|
+
process(val)
|
77
|
+
else
|
78
|
+
node
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
def on_and(node)
|
83
|
+
left, right = *node.children
|
84
|
+
|
85
|
+
if truthy(left)
|
86
|
+
process(right)
|
87
|
+
elsif truthy(right)
|
88
|
+
process(left)
|
89
|
+
else
|
90
|
+
node.updated(
|
91
|
+
:and,
|
92
|
+
[
|
93
|
+
process(left),
|
94
|
+
process(right)
|
95
|
+
]
|
96
|
+
)
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
private
|
101
|
+
|
102
|
+
def truthy(node)
|
103
|
+
return false unless node.is_a?(::Parser::AST::Node)
|
104
|
+
return true if node.type == :true
|
105
|
+
return false if node.children.empty?
|
106
|
+
|
107
|
+
node.children.all? { |child| truthy(child) }
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
class Base
|
112
|
+
attr_reader :store, :predicates_by_path, :count, :terminated, :current_path
|
113
|
+
alias terminated? terminated
|
114
|
+
|
115
|
+
def initialize
|
116
|
+
# total number of predicates
|
117
|
+
@count = 0
|
118
|
+
# cache of all predicates by path
|
119
|
+
@predicates_by_path = {}
|
120
|
+
# all predicates and their dirty state
|
121
|
+
@store = {}
|
122
|
+
|
123
|
+
@current_path = []
|
124
|
+
end
|
125
|
+
|
126
|
+
def reset!
|
127
|
+
@current_path = []
|
128
|
+
@terminated = false
|
129
|
+
end
|
130
|
+
|
131
|
+
def push(path)
|
132
|
+
current_path << path
|
133
|
+
end
|
134
|
+
|
135
|
+
def pop
|
136
|
+
current_path.pop
|
137
|
+
end
|
138
|
+
|
139
|
+
def terminate!
|
140
|
+
@terminated = true
|
141
|
+
end
|
142
|
+
|
143
|
+
def predicate_clause(name, node)
|
144
|
+
if pred?(name)
|
145
|
+
read_pred(name)
|
146
|
+
else
|
147
|
+
write_pred(name, node)
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
def pred?(name)
|
152
|
+
predicates_by_path.key?(current_path + [name])
|
153
|
+
end
|
154
|
+
|
155
|
+
def read_pred(name)
|
156
|
+
lvar = predicates_by_path.fetch(current_path + [name])
|
157
|
+
# mark as used
|
158
|
+
store[lvar] = true
|
159
|
+
s(:lvar, lvar)
|
160
|
+
end
|
161
|
+
|
162
|
+
def write_pred(name, node)
|
163
|
+
return node if terminated?
|
164
|
+
@count += 1
|
165
|
+
lvar = :"__p_#{count}__"
|
166
|
+
predicates_by_path[current_path + [name]] = lvar
|
167
|
+
store[lvar] = false
|
168
|
+
|
169
|
+
s(:lvasgn,
|
170
|
+
lvar,
|
171
|
+
node)
|
172
|
+
end
|
173
|
+
|
174
|
+
def process(ast)
|
175
|
+
Processor.new(self).process(ast)
|
176
|
+
end
|
177
|
+
|
178
|
+
private
|
179
|
+
|
180
|
+
def s(type, *children)
|
181
|
+
::Parser::AST::Node.new(type, children)
|
182
|
+
end
|
183
|
+
end
|
184
|
+
|
185
|
+
# rubocop:disable Style/MissingRespondToMissing
|
186
|
+
class Noop < Base
|
187
|
+
# Return node itself, no memoization
|
188
|
+
def method_missing(mid, node, *__rest__)
|
189
|
+
node
|
190
|
+
end
|
191
|
+
end
|
192
|
+
# rubocop:enable Style/MethodMissingSuper
|
193
|
+
# rubocop:enable Style/MissingRespondToMissing
|
194
|
+
|
195
|
+
class CaseIn < Base
|
196
|
+
def const(node, const)
|
197
|
+
node
|
198
|
+
end
|
199
|
+
|
200
|
+
def respond_to_deconstruct(node)
|
201
|
+
predicate_clause(:respond_to_deconstruct, node)
|
202
|
+
end
|
203
|
+
|
204
|
+
def array_size(node, size)
|
205
|
+
predicate_clause(:"array_size_#{size}", node)
|
206
|
+
end
|
207
|
+
|
208
|
+
def array_deconstructed(node)
|
209
|
+
predicate_clause(:array_deconstructed, node)
|
210
|
+
end
|
211
|
+
|
212
|
+
def hash_deconstructed(node, keys)
|
213
|
+
predicate_clause(:"hash_deconstructed_#{keys.join("_p_")}", node)
|
214
|
+
end
|
215
|
+
|
216
|
+
def respond_to_deconstruct_keys(node)
|
217
|
+
predicate_clause(:respond_to_deconstruct_keys, node)
|
218
|
+
end
|
219
|
+
|
220
|
+
def hash_keys(node, keys)
|
221
|
+
keys = keys.map { |key| key.is_a?(::Parser::AST::Node) ? key.children.first : key }
|
222
|
+
|
223
|
+
predicate_clause(:"hash_keys_#{keys.join("_p_")}", node)
|
224
|
+
end
|
225
|
+
end
|
226
|
+
end
|
227
|
+
|
228
|
+
class PatternMatching < Base
|
229
|
+
NAME = "pattern-matching"
|
230
|
+
SYNTAX_PROBE = "case 0; in 0; true; else; 1; end"
|
231
|
+
MIN_SUPPORTED_VERSION = Gem::Version.new("2.7.0")
|
232
|
+
|
233
|
+
MATCHEE = :__m__
|
234
|
+
MATCHEE_ARR = :__m_arr__
|
235
|
+
MATCHEE_HASH = :__m_hash__
|
236
|
+
|
237
|
+
ALTERNATION_MARKER = :__alt__
|
238
|
+
CURRENT_HASH_KEY = :__chk__
|
239
|
+
|
240
|
+
def on_case_match(node)
|
241
|
+
context.track! self
|
242
|
+
|
243
|
+
@deconstructed_keys = {}
|
244
|
+
@predicates = Predicates::CaseIn.new
|
245
|
+
@lvars = []
|
246
|
+
|
247
|
+
matchee_ast =
|
248
|
+
s(:begin, s(:lvasgn, MATCHEE, node.children[0]))
|
249
|
+
|
250
|
+
patterns = locals.with(
|
251
|
+
matchee: MATCHEE,
|
252
|
+
arr: MATCHEE_ARR,
|
253
|
+
hash: MATCHEE_HASH
|
254
|
+
) do
|
255
|
+
build_case_when(node.children[1..-1])
|
256
|
+
end
|
257
|
+
|
258
|
+
case_clause = predicates.process(s(:case, *patterns))
|
259
|
+
|
260
|
+
rewrite_case_in! node, matchee_ast, case_clause
|
261
|
+
|
262
|
+
node.updated(
|
263
|
+
:kwbegin,
|
264
|
+
[
|
265
|
+
matchee_ast, case_clause
|
266
|
+
]
|
267
|
+
)
|
268
|
+
end
|
269
|
+
|
270
|
+
def on_match_pattern(node)
|
271
|
+
context.track! self
|
272
|
+
|
273
|
+
@deconstructed_keys = {}
|
274
|
+
@predicates = Predicates::Noop.new
|
275
|
+
@lvars = []
|
276
|
+
|
277
|
+
matchee =
|
278
|
+
s(:begin, s(:lvasgn, MATCHEE, node.children[0]))
|
279
|
+
|
280
|
+
pattern =
|
281
|
+
locals.with(
|
282
|
+
matchee: MATCHEE,
|
283
|
+
arr: MATCHEE_ARR,
|
284
|
+
hash: MATCHEE_HASH
|
285
|
+
) do
|
286
|
+
with_declared_locals do
|
287
|
+
send(
|
288
|
+
:"#{node.children[1].type}_clause",
|
289
|
+
node.children[1]
|
290
|
+
)
|
291
|
+
end.then do |node|
|
292
|
+
s(:begin,
|
293
|
+
s(:or,
|
294
|
+
node,
|
295
|
+
no_matching_pattern))
|
296
|
+
end
|
297
|
+
end
|
298
|
+
|
299
|
+
node.updated(
|
300
|
+
:and,
|
301
|
+
[
|
302
|
+
matchee,
|
303
|
+
pattern
|
304
|
+
]
|
305
|
+
).tap do |new_node|
|
306
|
+
replace(node.loc.expression, inline_blocks(unparse(new_node)))
|
307
|
+
end
|
308
|
+
end
|
309
|
+
|
310
|
+
alias on_in_match on_match_pattern
|
311
|
+
|
312
|
+
def on_match_pattern_p(node)
|
313
|
+
context.track! self
|
314
|
+
|
315
|
+
@deconstructed_keys = {}
|
316
|
+
@predicates = Predicates::Noop.new
|
317
|
+
@lvars = []
|
318
|
+
|
319
|
+
matchee =
|
320
|
+
s(:begin, s(:lvasgn, MATCHEE, node.children[0]))
|
321
|
+
|
322
|
+
pattern =
|
323
|
+
locals.with(
|
324
|
+
matchee: MATCHEE,
|
325
|
+
arr: MATCHEE_ARR,
|
326
|
+
hash: MATCHEE_HASH
|
327
|
+
) do
|
328
|
+
with_declared_locals do
|
329
|
+
send(
|
330
|
+
:"#{node.children[1].type}_clause",
|
331
|
+
node.children[1]
|
332
|
+
)
|
333
|
+
end
|
334
|
+
end
|
335
|
+
|
336
|
+
node.updated(
|
337
|
+
:and,
|
338
|
+
[
|
339
|
+
matchee,
|
340
|
+
pattern
|
341
|
+
]
|
342
|
+
).tap do |new_node|
|
343
|
+
replace(node.loc.expression, inline_blocks(unparse(new_node)))
|
344
|
+
end
|
345
|
+
end
|
346
|
+
|
347
|
+
private
|
348
|
+
|
349
|
+
def rewrite_case_in!(node, matchee, new_node)
|
350
|
+
replace(node.loc.keyword, "case; when (#{unparse(matchee)}) && false")
|
351
|
+
remove(node.children[0].loc.expression)
|
352
|
+
|
353
|
+
node.children[1..-1].each.with_index do |clause, i|
|
354
|
+
if clause&.type == :in_pattern
|
355
|
+
# handle multiline clauses differently
|
356
|
+
if clause.loc.last_line > clause.children[0].loc.last_line + 1
|
357
|
+
height = clause.loc.last_line - clause.children[0].loc.last_line
|
358
|
+
padding = "\n" * height
|
359
|
+
body_indent = " " * clause.children[2].loc.column
|
360
|
+
replace(
|
361
|
+
clause.loc.expression,
|
362
|
+
"when #{inline_blocks(unparse(new_node.children[i].children[0]))}" \
|
363
|
+
"#{padding}" \
|
364
|
+
"#{body_indent}#{clause.children[2].loc.expression.source}"
|
365
|
+
)
|
366
|
+
else
|
367
|
+
replace(
|
368
|
+
clause.loc.keyword.end.join(clause.children[0].loc.expression.end),
|
369
|
+
inline_blocks(unparse(new_node.children[i].children[0]))
|
370
|
+
)
|
371
|
+
remove(clause.children[1].loc.expression) if clause.children[1]
|
372
|
+
replace(clause.loc.keyword, "when ")
|
373
|
+
end
|
374
|
+
elsif clause.nil?
|
375
|
+
insert_after(node.children[-2].loc.expression, "; else; #{unparse(new_node.children.last)}")
|
376
|
+
end
|
377
|
+
end
|
378
|
+
end
|
379
|
+
|
380
|
+
def build_case_when(nodes)
|
381
|
+
else_clause = nil
|
382
|
+
clauses = []
|
383
|
+
|
384
|
+
nodes.each do |clause|
|
385
|
+
if clause&.type == :in_pattern
|
386
|
+
clauses << build_when_clause(clause)
|
387
|
+
else
|
388
|
+
else_clause = process(clause)
|
389
|
+
end
|
390
|
+
end
|
391
|
+
|
392
|
+
else_clause = (else_clause || no_matching_pattern).then do |node|
|
393
|
+
next node unless node.type == :empty_else
|
394
|
+
nil
|
395
|
+
end
|
396
|
+
|
397
|
+
clauses << else_clause
|
398
|
+
clauses
|
399
|
+
end
|
400
|
+
|
401
|
+
def build_when_clause(clause)
|
402
|
+
predicates.reset!
|
403
|
+
[
|
404
|
+
with_declared_locals do
|
405
|
+
with_guard(
|
406
|
+
send(
|
407
|
+
:"#{clause.children[0].type}_clause",
|
408
|
+
clause.children[0]
|
409
|
+
),
|
410
|
+
clause.children[1] # guard
|
411
|
+
)
|
412
|
+
end,
|
413
|
+
process(clause.children[2] || s(:nil)) # expression
|
414
|
+
].then do |children|
|
415
|
+
s(:when, *children)
|
416
|
+
end
|
417
|
+
end
|
418
|
+
|
419
|
+
def const_pattern_clause(node, right = s(:lvar, locals[:matchee]))
|
420
|
+
const, pattern = *node.children
|
421
|
+
|
422
|
+
predicates.const(case_eq_clause(const, right), const).then do |node|
|
423
|
+
next node if pattern.nil?
|
424
|
+
|
425
|
+
s(:begin,
|
426
|
+
s(:and,
|
427
|
+
node,
|
428
|
+
send(:"#{pattern.type}_clause", pattern, right)))
|
429
|
+
end
|
430
|
+
end
|
431
|
+
|
432
|
+
def match_alt_clause(node, matchee = s(:lvar, locals[:matchee]))
|
433
|
+
children = locals.with(ALTERNATION_MARKER => true) do
|
434
|
+
node.children.map.with_index do |child, i|
|
435
|
+
predicates.terminate! if i == 1
|
436
|
+
send :"#{child.type}_clause", child, matchee
|
437
|
+
end
|
438
|
+
end
|
439
|
+
s(:begin, s(:or, *children))
|
440
|
+
end
|
441
|
+
|
442
|
+
def match_as_clause(node, right = s(:lvar, locals[:matchee]))
|
443
|
+
s(:begin,
|
444
|
+
s(:and,
|
445
|
+
send(:"#{node.children[0].type}_clause", node.children[0], right),
|
446
|
+
match_var_clause(node.children[1], right)))
|
447
|
+
end
|
448
|
+
|
449
|
+
def match_var_clause(node, left = s(:lvar, locals[:matchee]))
|
450
|
+
var = node.children[0]
|
451
|
+
return s(:true) if var == :_
|
452
|
+
|
453
|
+
check_match_var_alternation!(var)
|
454
|
+
|
455
|
+
s(:begin,
|
456
|
+
s(:or,
|
457
|
+
s(:begin, build_var_assignment(var, left)),
|
458
|
+
s(:true)))
|
459
|
+
end
|
460
|
+
|
461
|
+
def pin_clause(node, right = s(:lvar, locals[:matchee]))
|
462
|
+
predicates.terminate!
|
463
|
+
case_eq_clause node.children[0], right
|
464
|
+
end
|
465
|
+
|
466
|
+
def case_eq_clause(node, right = s(:lvar, locals[:matchee]))
|
467
|
+
predicates.terminate!
|
468
|
+
s(:begin, s(:send,
|
469
|
+
process(node), :===, right))
|
470
|
+
end
|
471
|
+
|
472
|
+
#=========== ARRAY PATTERN (START) ===============
|
473
|
+
|
474
|
+
def array_pattern_clause(node, matchee = s(:lvar, locals[:matchee]))
|
475
|
+
deconstruct_node(matchee).then do |dnode|
|
476
|
+
size_check = nil
|
477
|
+
# if there is no rest or tail, match the size first
|
478
|
+
unless node.type == :array_pattern_with_tail || node.children.any? { |n| n.type == :match_rest }
|
479
|
+
size_check = predicates.array_size(
|
480
|
+
s(:begin,
|
481
|
+
s(:send,
|
482
|
+
node.children.size.to_ast_node,
|
483
|
+
:==,
|
484
|
+
s(:send, s(:lvar, locals[:arr]), :size))),
|
485
|
+
node.children.size
|
486
|
+
)
|
487
|
+
end
|
488
|
+
|
489
|
+
right =
|
490
|
+
if node.children.empty?
|
491
|
+
case_eq_clause(s(:array), s(:lvar, locals[:arr]))
|
492
|
+
elsif node.children.size > 1 && node.children.first.type == :match_rest && node.children.last.type == :match_rest
|
493
|
+
array_find(*node.children)
|
494
|
+
else
|
495
|
+
array_element(0, *node.children)
|
496
|
+
end
|
497
|
+
|
498
|
+
right = s(:and, size_check, right) if size_check
|
499
|
+
|
500
|
+
s(:begin,
|
501
|
+
s(:and,
|
502
|
+
dnode,
|
503
|
+
right))
|
504
|
+
end
|
505
|
+
end
|
506
|
+
|
507
|
+
alias array_pattern_with_tail_clause array_pattern_clause
|
508
|
+
alias find_pattern_clause array_pattern_clause
|
509
|
+
|
510
|
+
def deconstruct_node(matchee)
|
511
|
+
context.use_ruby_next!
|
512
|
+
|
513
|
+
# we do not memoize respond_to_check for arrays, 'cause
|
514
|
+
# we can memoize is together with #deconstruct result
|
515
|
+
respond_check = respond_to_check(matchee, :deconstruct)
|
516
|
+
right = s(:send, matchee, :deconstruct)
|
517
|
+
|
518
|
+
predicates.array_deconstructed(
|
519
|
+
s(:and,
|
520
|
+
respond_check,
|
521
|
+
s(:begin,
|
522
|
+
s(:and,
|
523
|
+
s(:begin,
|
524
|
+
s(:or,
|
525
|
+
s(:begin, s(:lvasgn, locals[:arr], right)),
|
526
|
+
s(:true))),
|
527
|
+
s(:begin,
|
528
|
+
s(:or,
|
529
|
+
s(:send,
|
530
|
+
s(:const, nil, :Array), :===, s(:lvar, locals[:arr])),
|
531
|
+
raise_error(:TypeError, "#deconstruct must return Array"))))))
|
532
|
+
)
|
533
|
+
end
|
534
|
+
|
535
|
+
def array_element(index, head, *tail)
|
536
|
+
return array_match_rest(index, head, *tail) if head.type == :match_rest
|
537
|
+
|
538
|
+
send("#{head.type}_array_element", head, index).then do |node|
|
539
|
+
next node if tail.empty?
|
540
|
+
|
541
|
+
s(:begin,
|
542
|
+
s(:and,
|
543
|
+
node,
|
544
|
+
array_element(index + 1, *tail)))
|
545
|
+
end
|
546
|
+
end
|
547
|
+
|
548
|
+
# [*a, 1, 2, *] -> arr.find.with_index { |_, i| (a = arr.take(i)) && arr[i] == 1 && arr[i + 1] == 2 }
|
549
|
+
def array_find(head, *nodes, tail)
|
550
|
+
index = s(:lvar, :__i__)
|
551
|
+
|
552
|
+
head_match =
|
553
|
+
unless head.children.empty?
|
554
|
+
# we only need to call this to track the lvar usage
|
555
|
+
build_var_assignment(head.children[0].children[0])
|
556
|
+
|
557
|
+
arr_take = s(:send,
|
558
|
+
s(:lvar, locals[:arr]),
|
559
|
+
:take,
|
560
|
+
index)
|
561
|
+
|
562
|
+
match_var_clause(head.children[0], arr_take)
|
563
|
+
end
|
564
|
+
|
565
|
+
tail_match =
|
566
|
+
unless tail.children.empty?
|
567
|
+
# we only need to call this to track the lvar usage
|
568
|
+
build_var_assignment(tail.children[0].children[0])
|
569
|
+
|
570
|
+
match_var_clause(tail.children[0], arr_slice(index + nodes.size, -1))
|
571
|
+
end
|
572
|
+
|
573
|
+
nodes.each do |node|
|
574
|
+
if node.type == :match_var
|
575
|
+
# we only need to call this to track the lvar usage
|
576
|
+
build_var_assignment(node.children[0])
|
577
|
+
elsif node.type == :match_as
|
578
|
+
# we only need to call this to track the lvar usage
|
579
|
+
build_var_assignment(node.children[1].children[0])
|
580
|
+
end
|
581
|
+
end
|
582
|
+
|
583
|
+
pattern = array_rest_element(*nodes, index).then do |needle|
|
584
|
+
next needle unless head_match
|
585
|
+
s(:begin,
|
586
|
+
s(:and,
|
587
|
+
needle,
|
588
|
+
head_match))
|
589
|
+
end.then do |headed_needle|
|
590
|
+
next headed_needle unless tail_match
|
591
|
+
|
592
|
+
s(:begin,
|
593
|
+
s(:and,
|
594
|
+
headed_needle,
|
595
|
+
tail_match))
|
596
|
+
end
|
597
|
+
|
598
|
+
s(:block,
|
599
|
+
s(:send,
|
600
|
+
s(:send,
|
601
|
+
s(:lvar, locals[:arr]),
|
602
|
+
:find),
|
603
|
+
:with_index),
|
604
|
+
s(:args,
|
605
|
+
s(:arg, :_),
|
606
|
+
s(:arg, :__i__)),
|
607
|
+
pattern)
|
608
|
+
end
|
609
|
+
|
610
|
+
def array_match_rest(index, node, *tail)
|
611
|
+
size = tail.size + 1
|
612
|
+
child = node.children[0]
|
613
|
+
|
614
|
+
rest = arr_slice(index, -size).then do |r|
|
615
|
+
next r unless child
|
616
|
+
|
617
|
+
match_var_clause(
|
618
|
+
child,
|
619
|
+
r
|
620
|
+
)
|
621
|
+
end
|
622
|
+
|
623
|
+
return rest if tail.empty?
|
624
|
+
|
625
|
+
s(:begin,
|
626
|
+
s(:and,
|
627
|
+
rest,
|
628
|
+
array_rest_element(*tail, -(size - 1))))
|
629
|
+
end
|
630
|
+
|
631
|
+
def array_rest_element(head, *tail, index)
|
632
|
+
send("#{head.type}_array_element", head, index).then do |node|
|
633
|
+
next node if tail.empty?
|
634
|
+
|
635
|
+
s(:begin,
|
636
|
+
s(:and,
|
637
|
+
node,
|
638
|
+
array_rest_element(*tail, index + 1)))
|
639
|
+
end
|
640
|
+
end
|
641
|
+
|
642
|
+
def array_pattern_array_element(node, index)
|
643
|
+
element = arr_item_at(index)
|
644
|
+
locals.with(arr: locals[:arr, index]) do
|
645
|
+
predicates.push :"i#{index}"
|
646
|
+
array_pattern_clause(node, element).tap { predicates.pop }
|
647
|
+
end
|
648
|
+
end
|
649
|
+
|
650
|
+
def find_pattern_array_element(node, index)
|
651
|
+
element = arr_item_at(index)
|
652
|
+
locals.with(arr: locals[:arr, index]) do
|
653
|
+
predicates.push :"i#{index}"
|
654
|
+
find_pattern_clause(node, element).tap { predicates.pop }
|
655
|
+
end
|
656
|
+
end
|
657
|
+
|
658
|
+
def hash_pattern_array_element(node, index)
|
659
|
+
element = arr_item_at(index)
|
660
|
+
locals.with(hash: locals[:arr, index]) do
|
661
|
+
predicates.push :"i#{index}"
|
662
|
+
hash_pattern_clause(node, element).tap { predicates.pop }
|
663
|
+
end
|
664
|
+
end
|
665
|
+
|
666
|
+
def const_pattern_array_element(node, index)
|
667
|
+
element = arr_item_at(index)
|
668
|
+
locals.with(arr: locals[:arr, index]) do
|
669
|
+
predicates.push :"i#{index}"
|
670
|
+
const_pattern_clause(node, element).tap { predicates.pop }
|
671
|
+
end
|
672
|
+
end
|
673
|
+
|
674
|
+
def match_alt_array_element(node, index)
|
675
|
+
children = node.children.map do |child, i|
|
676
|
+
send :"#{child.type}_array_element", child, index
|
677
|
+
end
|
678
|
+
s(:begin, s(:or, *children))
|
679
|
+
end
|
680
|
+
|
681
|
+
def match_var_array_element(node, index)
|
682
|
+
element = arr_item_at(index)
|
683
|
+
locals.with(arr: locals[:arr, index]) do
|
684
|
+
predicates.push :"i#{index}"
|
685
|
+
match_var_clause(node, element).tap { predicates.pop }
|
686
|
+
end
|
687
|
+
end
|
688
|
+
|
689
|
+
def match_as_array_element(node, index)
|
690
|
+
element = arr_item_at(index)
|
691
|
+
locals.with(arr: locals[:arr, index]) do
|
692
|
+
predicates.push :"i#{index}"
|
693
|
+
match_as_clause(node, element).tap { predicates.pop }
|
694
|
+
end
|
695
|
+
end
|
696
|
+
|
697
|
+
def pin_array_element(node, index)
|
698
|
+
case_eq_array_element node.children[0], index
|
699
|
+
end
|
700
|
+
|
701
|
+
def case_eq_array_element(node, index)
|
702
|
+
case_eq_clause(node, arr_item_at(index))
|
703
|
+
end
|
704
|
+
|
705
|
+
def arr_item_at(index, arr = s(:lvar, locals[:arr]))
|
706
|
+
s(:index, arr, index.to_ast_node)
|
707
|
+
end
|
708
|
+
|
709
|
+
def arr_slice(lindex, rindex, arr = s(:lvar, locals[:arr]))
|
710
|
+
s(:index,
|
711
|
+
arr,
|
712
|
+
s(:irange,
|
713
|
+
lindex.to_ast_node,
|
714
|
+
rindex.to_ast_node))
|
715
|
+
end
|
716
|
+
|
717
|
+
#=========== ARRAY PATTERN (END) ===============
|
718
|
+
|
719
|
+
#=========== HASH PATTERN (START) ===============
|
720
|
+
|
721
|
+
def hash_pattern_clause(node, matchee = s(:lvar, locals[:matchee]))
|
722
|
+
# Optimization: avoid hash modifications when not needed
|
723
|
+
# (we use #dup and #delete when "reading" values when **rest is present
|
724
|
+
# to assign the rest of the hash copy to it)
|
725
|
+
@hash_match_rest = node.children.any? { |child| child.type == :match_rest || child.type == :match_nil_pattern }
|
726
|
+
keys = hash_pattern_destruction_keys(node.children)
|
727
|
+
|
728
|
+
specified_key_names = hash_pattern_keys(node.children)
|
729
|
+
|
730
|
+
deconstruct_keys_node(keys, matchee).then do |dnode|
|
731
|
+
right =
|
732
|
+
if node.children.empty?
|
733
|
+
case_eq_clause(s(:hash), s(:lvar, locals[:hash]))
|
734
|
+
elsif specified_key_names.empty?
|
735
|
+
hash_element(*node.children)
|
736
|
+
else
|
737
|
+
s(:begin,
|
738
|
+
s(:and,
|
739
|
+
having_hash_keys(specified_key_names),
|
740
|
+
hash_element(*node.children)))
|
741
|
+
end
|
742
|
+
|
743
|
+
predicates.pop
|
744
|
+
|
745
|
+
next dnode if right.nil?
|
746
|
+
|
747
|
+
s(:begin,
|
748
|
+
s(:and,
|
749
|
+
dnode,
|
750
|
+
right))
|
751
|
+
end
|
752
|
+
end
|
753
|
+
|
754
|
+
def hash_pattern_keys(children)
|
755
|
+
children.filter_map do |child|
|
756
|
+
# Skip ** without var
|
757
|
+
next if child.type == :match_rest || child.type == :match_nil_pattern
|
758
|
+
|
759
|
+
send("#{child.type}_hash_key", child)
|
760
|
+
end
|
761
|
+
end
|
762
|
+
|
763
|
+
def hash_pattern_destruction_keys(children)
|
764
|
+
return s(:nil) if children.empty?
|
765
|
+
|
766
|
+
children.filter_map do |child|
|
767
|
+
# Skip ** without var
|
768
|
+
next if child.type == :match_rest && child.children.empty?
|
769
|
+
return s(:nil) if child.type == :match_rest || child.type == :match_nil_pattern
|
770
|
+
|
771
|
+
send("#{child.type}_hash_key", child)
|
772
|
+
end.then { |keys| s(:array, *keys) }
|
773
|
+
end
|
774
|
+
|
775
|
+
def pair_hash_key(node)
|
776
|
+
node.children[0]
|
777
|
+
end
|
778
|
+
|
779
|
+
def match_var_hash_key(node)
|
780
|
+
check_match_var_alternation! node.children[0]
|
781
|
+
|
782
|
+
s(:sym, node.children[0])
|
783
|
+
end
|
784
|
+
|
785
|
+
def deconstruct_keys_node(keys, matchee = s(:lvar, locals[:matchee]))
|
786
|
+
# Use original hash returned by #deconstruct_keys if not **rest matching,
|
787
|
+
# 'cause it remains immutable
|
788
|
+
deconstruct_name = @hash_match_rest ? locals[:hash, :src] : locals[:hash]
|
789
|
+
|
790
|
+
# Duplicate the source hash when matching **rest, 'cause we mutate it
|
791
|
+
hash_dup =
|
792
|
+
if @hash_match_rest
|
793
|
+
s(:begin, s(:lvasgn, locals[:hash], s(:send, s(:lvar, locals[:hash, :src]), :dup)))
|
794
|
+
else
|
795
|
+
s(:true)
|
796
|
+
end
|
797
|
+
|
798
|
+
context.use_ruby_next!
|
799
|
+
|
800
|
+
respond_to_checked = predicates.pred?(:respond_to_deconstruct_keys)
|
801
|
+
respond_check = predicates.respond_to_deconstruct_keys(respond_to_check(matchee, :deconstruct_keys))
|
802
|
+
|
803
|
+
key_names = keys.children.map { |node| node.children.last }
|
804
|
+
predicates.push locals[:hash]
|
805
|
+
|
806
|
+
s(:begin, s(:lvasgn, deconstruct_name,
|
807
|
+
s(:send,
|
808
|
+
matchee, :deconstruct_keys, keys))).then do |dnode|
|
809
|
+
next dnode if respond_to_checked
|
810
|
+
|
811
|
+
s(:and,
|
812
|
+
respond_check,
|
813
|
+
s(:begin,
|
814
|
+
s(:and,
|
815
|
+
s(:begin,
|
816
|
+
s(:or,
|
817
|
+
dnode,
|
818
|
+
s(:true))),
|
819
|
+
s(:begin,
|
820
|
+
s(:or,
|
821
|
+
s(:send,
|
822
|
+
s(:const, nil, :Hash), :===, s(:lvar, deconstruct_name)),
|
823
|
+
raise_error(:TypeError, "#deconstruct_keys must return Hash"))))))
|
824
|
+
end.then do |dnode|
|
825
|
+
predicates.hash_deconstructed(dnode, key_names)
|
826
|
+
end.then do |dnode|
|
827
|
+
next dnode unless @hash_match_rest
|
828
|
+
|
829
|
+
s(:begin,
|
830
|
+
s(:and,
|
831
|
+
dnode,
|
832
|
+
hash_dup))
|
833
|
+
end
|
834
|
+
end
|
835
|
+
|
836
|
+
def hash_pattern_hash_element(node, key)
|
837
|
+
element = hash_value_at(key)
|
838
|
+
key_index = deconstructed_key(key)
|
839
|
+
locals.with(hash: locals[:hash, key_index]) do
|
840
|
+
predicates.push :"k#{key_index}"
|
841
|
+
hash_pattern_clause(node, element).tap { predicates.pop }
|
842
|
+
end
|
843
|
+
end
|
844
|
+
|
845
|
+
def array_pattern_hash_element(node, key)
|
846
|
+
element = hash_value_at(key)
|
847
|
+
key_index = deconstructed_key(key)
|
848
|
+
locals.with(arr: locals[:hash, key_index]) do
|
849
|
+
predicates.push :"k#{key_index}"
|
850
|
+
array_pattern_clause(node, element).tap { predicates.pop }
|
851
|
+
end
|
852
|
+
end
|
853
|
+
|
854
|
+
def find_pattern_hash_element(node, key)
|
855
|
+
element = hash_value_at(key)
|
856
|
+
key_index = deconstructed_key(key)
|
857
|
+
locals.with(arr: locals[:hash, key_index]) do
|
858
|
+
predicates.push :"k#{key_index}"
|
859
|
+
find_pattern_clause(node, element).tap { predicates.pop }
|
860
|
+
end
|
861
|
+
end
|
862
|
+
|
863
|
+
def const_pattern_hash_element(node, key)
|
864
|
+
element = hash_value_at(key)
|
865
|
+
key_index = deconstructed_key(key)
|
866
|
+
locals.with(hash: locals[:hash, key_index]) do
|
867
|
+
predicates.push :"k#{key_index}"
|
868
|
+
const_pattern_clause(node, element).tap { predicates.pop }
|
869
|
+
end
|
870
|
+
end
|
871
|
+
|
872
|
+
def hash_element(head, *tail)
|
873
|
+
send("#{head.type}_hash_element", head).then do |node|
|
874
|
+
next node if tail.empty?
|
875
|
+
|
876
|
+
right = hash_element(*tail)
|
877
|
+
|
878
|
+
next node if right.nil?
|
879
|
+
|
880
|
+
s(:begin,
|
881
|
+
s(:and,
|
882
|
+
node,
|
883
|
+
right))
|
884
|
+
end
|
885
|
+
end
|
886
|
+
|
887
|
+
def pair_hash_element(node, _key = nil)
|
888
|
+
key, val = *node.children
|
889
|
+
send("#{val.type}_hash_element", val, key)
|
890
|
+
end
|
891
|
+
|
892
|
+
def match_alt_hash_element(node, key)
|
893
|
+
element_node = s(:begin, s(:lvasgn, locals[:hash, :el], hash_value_at(key)))
|
894
|
+
|
895
|
+
children = locals.with(hash_element: locals[:hash, :el]) do
|
896
|
+
node.children.map do |child, i|
|
897
|
+
send :"#{child.type}_hash_element", child, key
|
898
|
+
end
|
899
|
+
end
|
900
|
+
|
901
|
+
s(:begin,
|
902
|
+
s(:and,
|
903
|
+
s(:begin,
|
904
|
+
s(:or,
|
905
|
+
element_node,
|
906
|
+
s(:true))),
|
907
|
+
s(:begin,
|
908
|
+
s(:or, *children))))
|
909
|
+
end
|
910
|
+
|
911
|
+
def match_as_hash_element(node, key)
|
912
|
+
element = hash_value_at(key)
|
913
|
+
key_index = deconstructed_key(key)
|
914
|
+
locals.with(hash: locals[:hash, key_index]) do
|
915
|
+
predicates.push :"k#{key_index}"
|
916
|
+
match_as_clause(node, element).tap { predicates.pop }
|
917
|
+
end
|
918
|
+
end
|
919
|
+
|
920
|
+
def match_var_hash_element(node, key = nil)
|
921
|
+
key ||= node.children[0]
|
922
|
+
element = hash_value_at(key)
|
923
|
+
key_index = deconstructed_key(key)
|
924
|
+
locals.with(hash: locals[:hash, key_index]) do
|
925
|
+
predicates.push :"k#{key_index}"
|
926
|
+
match_var_clause(node, element).tap { predicates.pop }
|
927
|
+
end
|
928
|
+
end
|
929
|
+
|
930
|
+
def match_nil_pattern_hash_element(node, _key = nil)
|
931
|
+
s(:send,
|
932
|
+
s(:lvar, locals[:hash]),
|
933
|
+
:empty?)
|
934
|
+
end
|
935
|
+
|
936
|
+
def match_rest_hash_element(node, _key = nil)
|
937
|
+
# case {}; in **; end
|
938
|
+
return if node.children.empty?
|
939
|
+
|
940
|
+
child = node.children[0]
|
941
|
+
|
942
|
+
raise ArgumentError, "Unknown hash match_rest child: #{child.type}" unless child.type == :match_var
|
943
|
+
|
944
|
+
match_var_clause(child, s(:lvar, locals[:hash]))
|
945
|
+
end
|
946
|
+
|
947
|
+
def pin_hash_element(node, index)
|
948
|
+
case_eq_hash_element node.children[0], index
|
949
|
+
end
|
950
|
+
|
951
|
+
def case_eq_hash_element(node, key)
|
952
|
+
case_eq_clause node, hash_value_at(key)
|
953
|
+
end
|
954
|
+
|
955
|
+
def hash_value_at(key, hash = s(:lvar, locals[:hash]))
|
956
|
+
return s(:lvar, locals.fetch(:hash_element)) if locals.key?(:hash_element)
|
957
|
+
|
958
|
+
if @hash_match_rest
|
959
|
+
s(:send,
|
960
|
+
hash, :delete,
|
961
|
+
key.to_ast_node)
|
962
|
+
else
|
963
|
+
s(:index,
|
964
|
+
hash,
|
965
|
+
key.to_ast_node)
|
966
|
+
end
|
967
|
+
end
|
968
|
+
|
969
|
+
def hash_has_key(key, hash = s(:lvar, locals[:hash]))
|
970
|
+
s(:send,
|
971
|
+
hash, :key?,
|
972
|
+
key.to_ast_node)
|
973
|
+
end
|
974
|
+
|
975
|
+
def having_hash_keys(keys, hash = s(:lvar, locals[:hash]))
|
976
|
+
keys.reduce(nil) do |acc, key|
|
977
|
+
pnode = hash_has_key(key, hash)
|
978
|
+
next pnode unless acc
|
979
|
+
|
980
|
+
s(:begin,
|
981
|
+
s(:and, acc, pnode))
|
982
|
+
end.then do |node|
|
983
|
+
predicates.hash_keys(node, keys)
|
984
|
+
end
|
985
|
+
end
|
986
|
+
|
987
|
+
#=========== HASH PATTERN (END) ===============
|
988
|
+
|
989
|
+
def with_guard(node, guard)
|
990
|
+
return node unless guard
|
991
|
+
|
992
|
+
s(:begin,
|
993
|
+
s(:and,
|
994
|
+
node,
|
995
|
+
guard.children[0])).then do |expr|
|
996
|
+
next expr unless guard.type == :unless_guard
|
997
|
+
s(:send, expr, :!)
|
998
|
+
end
|
999
|
+
end
|
1000
|
+
|
1001
|
+
def with_declared_locals
|
1002
|
+
lvars.clear
|
1003
|
+
node = yield
|
1004
|
+
|
1005
|
+
return node if lvars.empty?
|
1006
|
+
|
1007
|
+
# We need to declare match lvars outside of the outer `find` block,
|
1008
|
+
# so we do that for that whole pattern
|
1009
|
+
locals_declare = s(:begin, s(:masgn,
|
1010
|
+
s(:mlhs, *lvars.uniq.map { s(:lvasgn, _1) }),
|
1011
|
+
s(:nil)))
|
1012
|
+
|
1013
|
+
s(:begin,
|
1014
|
+
s(:or,
|
1015
|
+
locals_declare,
|
1016
|
+
node))
|
1017
|
+
end
|
1018
|
+
|
1019
|
+
def no_matching_pattern
|
1020
|
+
raise_error(
|
1021
|
+
:NoMatchingPatternError,
|
1022
|
+
s(:send,
|
1023
|
+
s(:lvar, locals[:matchee]), :inspect)
|
1024
|
+
)
|
1025
|
+
end
|
1026
|
+
|
1027
|
+
def raise_error(type, msg = "")
|
1028
|
+
s(:send, s(:const, nil, :Kernel), :raise,
|
1029
|
+
s(:const, nil, type),
|
1030
|
+
msg.to_ast_node)
|
1031
|
+
end
|
1032
|
+
|
1033
|
+
# Add respond_to? check
|
1034
|
+
def respond_to_check(node, mid)
|
1035
|
+
s(:send, node, :respond_to?, mid.to_ast_node)
|
1036
|
+
end
|
1037
|
+
|
1038
|
+
def respond_to_missing?(mid, *__rest__)
|
1039
|
+
return true if mid.to_s.match?(/_(clause|array_element)/)
|
1040
|
+
super
|
1041
|
+
end
|
1042
|
+
|
1043
|
+
def method_missing(mid, *args, &block)
|
1044
|
+
mid = mid.to_s
|
1045
|
+
return case_eq_clause(*args) if mid.match?(/_clause$/)
|
1046
|
+
return case_eq_array_element(*args) if mid.match?(/_array_element$/)
|
1047
|
+
return case_eq_hash_element(*args) if mid.match?(/_hash_element$/)
|
1048
|
+
super
|
1049
|
+
end
|
1050
|
+
|
1051
|
+
private
|
1052
|
+
|
1053
|
+
attr_reader :deconstructed_keys, :predicates, :lvars
|
1054
|
+
|
1055
|
+
# Raise SyntaxError if match-var is used within alternation
|
1056
|
+
# https://github.com/ruby/ruby/blob/672213ef1ca2b71312084057e27580b340438796/compile.c#L5900
|
1057
|
+
def check_match_var_alternation!(name)
|
1058
|
+
return unless locals.key?(ALTERNATION_MARKER)
|
1059
|
+
|
1060
|
+
if name.is_a?(::Parser::AST::Node)
|
1061
|
+
raise ::SyntaxError, "illegal variable in alternative pattern (#{name.children.first})"
|
1062
|
+
end
|
1063
|
+
|
1064
|
+
return if name.start_with?("_")
|
1065
|
+
|
1066
|
+
raise ::SyntaxError, "illegal variable in alternative pattern (#{name})"
|
1067
|
+
end
|
1068
|
+
|
1069
|
+
def deconstructed_key(key)
|
1070
|
+
return deconstructed_keys[key] if deconstructed_keys.key?(key)
|
1071
|
+
|
1072
|
+
deconstructed_keys[key] = :"k#{deconstructed_keys.size}"
|
1073
|
+
end
|
1074
|
+
|
1075
|
+
# Unparser generates `do .. end` or `{ ... }` multiline blocks, we want to
|
1076
|
+
# have single-line blocks with `{ ... }`.
|
1077
|
+
def inline_blocks(source)
|
1078
|
+
source.gsub(/(?:do|{) \|_, __i__\|\n\s*([^\n]+)\n\s*(?:end|})/, '{ |_, __i__| \1 }')
|
1079
|
+
end
|
1080
|
+
|
1081
|
+
# Value could be omitted for mass assignment
|
1082
|
+
def build_var_assignment(var, value = nil)
|
1083
|
+
unless var.is_a?(::Parser::AST::Node)
|
1084
|
+
lvars << var
|
1085
|
+
return s(:lvasgn, *[var, value].compact)
|
1086
|
+
end
|
1087
|
+
|
1088
|
+
asign_type = :"#{var.type.to_s[0]}vasgn"
|
1089
|
+
|
1090
|
+
s(asign_type, *[var.children[0], value].compact)
|
1091
|
+
end
|
1092
|
+
end
|
1093
|
+
end
|
1094
|
+
end
|
1095
|
+
end
|