ruby-next-core 0.2.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 +7 -0
- data/CHANGELOG.md +68 -0
- data/LICENSE.txt +21 -0
- data/README.md +279 -0
- data/bin/parse +19 -0
- data/bin/ruby-next +16 -0
- data/bin/transform +21 -0
- data/lib/ruby-next.rb +37 -0
- data/lib/ruby-next/cli.rb +55 -0
- data/lib/ruby-next/commands/base.rb +42 -0
- data/lib/ruby-next/commands/nextify.rb +118 -0
- data/lib/ruby-next/core.rb +34 -0
- data/lib/ruby-next/core/array/difference_union_intersection.rb +31 -0
- data/lib/ruby-next/core/enumerable/filter.rb +23 -0
- data/lib/ruby-next/core/enumerable/filter_map.rb +50 -0
- data/lib/ruby-next/core/enumerable/tally.rb +28 -0
- data/lib/ruby-next/core/enumerator/produce.rb +22 -0
- data/lib/ruby-next/core/hash/merge.rb +16 -0
- data/lib/ruby-next/core/kernel/then.rb +12 -0
- data/lib/ruby-next/core/pattern_matching.rb +37 -0
- data/lib/ruby-next/core/proc/compose.rb +21 -0
- data/lib/ruby-next/core/runtime.rb +10 -0
- data/lib/ruby-next/language.rb +117 -0
- data/lib/ruby-next/language/bootsnap.rb +26 -0
- data/lib/ruby-next/language/eval.rb +64 -0
- data/lib/ruby-next/language/parser.rb +24 -0
- data/lib/ruby-next/language/rewriters/args_forward.rb +57 -0
- data/lib/ruby-next/language/rewriters/base.rb +105 -0
- data/lib/ruby-next/language/rewriters/endless_range.rb +60 -0
- data/lib/ruby-next/language/rewriters/method_reference.rb +31 -0
- data/lib/ruby-next/language/rewriters/numbered_params.rb +41 -0
- data/lib/ruby-next/language/rewriters/pattern_matching.rb +522 -0
- data/lib/ruby-next/language/runtime.rb +96 -0
- data/lib/ruby-next/language/setup.rb +43 -0
- data/lib/ruby-next/language/unparser.rb +8 -0
- data/lib/ruby-next/utils.rb +36 -0
- data/lib/ruby-next/version.rb +5 -0
- data/lib/uby-next.rb +68 -0
- metadata +117 -0
@@ -0,0 +1,60 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RubyNext
|
4
|
+
module Language
|
5
|
+
module Rewriters
|
6
|
+
class EndlessRange < Base
|
7
|
+
SYNTAX_PROBE = "[0, 1][1..]"
|
8
|
+
MIN_SUPPORTED_VERSION = Gem::Version.new("2.6.0")
|
9
|
+
|
10
|
+
def on_index(node)
|
11
|
+
@current_index = node
|
12
|
+
new_index = process(node.children.last)
|
13
|
+
return unless new_index != node.children.last
|
14
|
+
|
15
|
+
node.updated(
|
16
|
+
nil,
|
17
|
+
[
|
18
|
+
node.children.first,
|
19
|
+
new_index
|
20
|
+
]
|
21
|
+
)
|
22
|
+
end
|
23
|
+
|
24
|
+
def on_erange(node)
|
25
|
+
return unless node.children.last.nil?
|
26
|
+
|
27
|
+
context.track! self
|
28
|
+
|
29
|
+
new_end =
|
30
|
+
if index_arg?(node)
|
31
|
+
s(:int, -1)
|
32
|
+
else
|
33
|
+
s(:const,
|
34
|
+
s(:const,
|
35
|
+
s(:cbase), :Float),
|
36
|
+
:INFINITY)
|
37
|
+
end
|
38
|
+
|
39
|
+
node.updated(
|
40
|
+
:irange,
|
41
|
+
[
|
42
|
+
node.children.first,
|
43
|
+
new_end
|
44
|
+
]
|
45
|
+
)
|
46
|
+
end
|
47
|
+
|
48
|
+
alias_method :on_irange, :on_erange
|
49
|
+
|
50
|
+
private
|
51
|
+
|
52
|
+
attr_reader :current_index
|
53
|
+
|
54
|
+
def index_arg?(node)
|
55
|
+
current_index&.children&.include?(node)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RubyNext
|
4
|
+
module Language
|
5
|
+
module Rewriters
|
6
|
+
class MethodReference < Base
|
7
|
+
SYNTAX_PROBE = "Language.:transform"
|
8
|
+
MIN_SUPPORTED_VERSION = Gem::Version.new("2.7.0")
|
9
|
+
|
10
|
+
def on_meth_ref(node)
|
11
|
+
context.track! self
|
12
|
+
|
13
|
+
receiver, mid = *node.children
|
14
|
+
|
15
|
+
node.updated(
|
16
|
+
:send,
|
17
|
+
[
|
18
|
+
receiver,
|
19
|
+
:method,
|
20
|
+
s(:sym, mid)
|
21
|
+
]
|
22
|
+
)
|
23
|
+
end
|
24
|
+
|
25
|
+
unless transform(SYNTAX_PROBE) == "Language.method(:transform)"
|
26
|
+
warn_custom_parser_required_for("method reference")
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
using RubyNext
|
4
|
+
|
5
|
+
module RubyNext
|
6
|
+
module Language
|
7
|
+
module Rewriters
|
8
|
+
class NumberedParams < Base
|
9
|
+
SYNTAX_PROBE = "proc { _1 }.call(1)"
|
10
|
+
MIN_SUPPORTED_VERSION = Gem::Version.new("2.7.0")
|
11
|
+
|
12
|
+
def on_numblock(node)
|
13
|
+
context.track! self
|
14
|
+
|
15
|
+
proc_or_lambda, num, *rest = *node.children
|
16
|
+
|
17
|
+
node.updated(
|
18
|
+
:block,
|
19
|
+
[
|
20
|
+
proc_or_lambda,
|
21
|
+
proc_args(num),
|
22
|
+
*rest
|
23
|
+
]
|
24
|
+
)
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
def proc_args(n)
|
30
|
+
return s(:args, s(:procarg0, s(:arg, :_1))) if n == 1
|
31
|
+
|
32
|
+
(1..n).map do |numero|
|
33
|
+
s(:arg, :"_#{numero}")
|
34
|
+
end.then do |args|
|
35
|
+
s(:args, *args)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,522 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
using RubyNext
|
4
|
+
|
5
|
+
module RubyNext
|
6
|
+
module Language
|
7
|
+
module Rewriters
|
8
|
+
using(Module.new do
|
9
|
+
refine ::Parser::AST::Node do
|
10
|
+
def to_ast_node
|
11
|
+
self
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
refine String do
|
16
|
+
def to_ast_node
|
17
|
+
::Parser::AST::Node.new(:str, [self])
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
refine Symbol do
|
22
|
+
def to_ast_node
|
23
|
+
::Parser::AST::Node.new(:sym, [self])
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
refine Integer do
|
28
|
+
def to_ast_node
|
29
|
+
::Parser::AST::Node.new(:int, [self])
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end)
|
33
|
+
|
34
|
+
class PatternMatching < Base
|
35
|
+
SYNTAX_PROBE = "case 0; in 0; true; else; 1; end"
|
36
|
+
MIN_SUPPORTED_VERSION = Gem::Version.new("2.7.0")
|
37
|
+
|
38
|
+
MATCHEE = :__m__
|
39
|
+
MATCHEE_ARR = :__m_arr__
|
40
|
+
MATCHEE_HASH = :__m_hash__
|
41
|
+
|
42
|
+
def on_case_match(node)
|
43
|
+
context.track! self
|
44
|
+
|
45
|
+
@deconstructed = []
|
46
|
+
|
47
|
+
matchee_ast =
|
48
|
+
s(:lvasgn, MATCHEE, node.children[0])
|
49
|
+
|
50
|
+
ifs_ast = locals.with(
|
51
|
+
matchee: MATCHEE,
|
52
|
+
arr: MATCHEE_ARR,
|
53
|
+
hash: MATCHEE_HASH
|
54
|
+
) do
|
55
|
+
build_if_clause(node.children[1], node.children[2..-1])
|
56
|
+
end
|
57
|
+
|
58
|
+
node.updated(
|
59
|
+
:begin,
|
60
|
+
[
|
61
|
+
matchee_ast, ifs_ast
|
62
|
+
]
|
63
|
+
)
|
64
|
+
end
|
65
|
+
|
66
|
+
def on_in_match(node)
|
67
|
+
context.track! self
|
68
|
+
|
69
|
+
@deconstructed = []
|
70
|
+
|
71
|
+
matchee =
|
72
|
+
s(:lvasgn, MATCHEE, node.children[0])
|
73
|
+
|
74
|
+
pattern =
|
75
|
+
locals.with(
|
76
|
+
matchee: MATCHEE,
|
77
|
+
arr: MATCHEE_ARR,
|
78
|
+
hash: MATCHEE_HASH
|
79
|
+
) do
|
80
|
+
send(
|
81
|
+
:"#{node.children[1].type}_clause",
|
82
|
+
node.children[1]
|
83
|
+
).then do |node|
|
84
|
+
s(:or,
|
85
|
+
node,
|
86
|
+
no_matching_pattern)
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
node.updated(
|
91
|
+
:and,
|
92
|
+
[
|
93
|
+
matchee,
|
94
|
+
pattern
|
95
|
+
]
|
96
|
+
)
|
97
|
+
end
|
98
|
+
|
99
|
+
private
|
100
|
+
|
101
|
+
def build_if_clause(node, rest)
|
102
|
+
if node&.type == :in_pattern
|
103
|
+
build_in_pattern(node, rest)
|
104
|
+
else
|
105
|
+
raise "Unexpected else in the middle of case ... in" if rest && rest.size > 0
|
106
|
+
# else clause must be present
|
107
|
+
node || no_matching_pattern
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
def build_in_pattern(clause, rest)
|
112
|
+
[
|
113
|
+
with_guard(
|
114
|
+
send(
|
115
|
+
:"#{clause.children[0].type}_clause",
|
116
|
+
clause.children[0]
|
117
|
+
),
|
118
|
+
clause.children[1] # guard
|
119
|
+
),
|
120
|
+
clause.children[2] || s(:nil) # expression
|
121
|
+
].then do |children|
|
122
|
+
if rest && rest.size > 0
|
123
|
+
children << build_if_clause(rest.first, rest[1..-1])
|
124
|
+
end
|
125
|
+
|
126
|
+
s(:if, *children)
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
def const_pattern_clause(node)
|
131
|
+
const, pattern = *node.children
|
132
|
+
|
133
|
+
case_eq_clause(const).then do |node|
|
134
|
+
next node if pattern.nil?
|
135
|
+
|
136
|
+
s(:and,
|
137
|
+
node,
|
138
|
+
send(:"#{pattern.type}_clause", pattern))
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
def match_alt_clause(node)
|
143
|
+
children = node.children.map do |child|
|
144
|
+
send :"#{child.type}_clause", child
|
145
|
+
end
|
146
|
+
s(:or, *children)
|
147
|
+
end
|
148
|
+
|
149
|
+
def match_as_clause(node)
|
150
|
+
s(:and,
|
151
|
+
case_eq_clause(node.children[0]),
|
152
|
+
match_var_clause(node.children[1], s(:lvar, locals[:matchee])))
|
153
|
+
end
|
154
|
+
|
155
|
+
def match_var_clause(node, left = s(:lvar, locals[:matchee]))
|
156
|
+
s(:or,
|
157
|
+
s(:lvasgn, node.children[0], left),
|
158
|
+
s(:true)) # rubocop:disable Lint/BooleanSymbol
|
159
|
+
end
|
160
|
+
|
161
|
+
def pin_clause(node)
|
162
|
+
case_eq_clause node.children[0]
|
163
|
+
end
|
164
|
+
|
165
|
+
def case_eq_clause(node, right = s(:lvar, locals[:matchee]))
|
166
|
+
s(:send,
|
167
|
+
node, :===, right)
|
168
|
+
end
|
169
|
+
|
170
|
+
#=========== ARRAY PATTERN (START) ===============
|
171
|
+
|
172
|
+
def array_pattern_clause(node, matchee = s(:lvar, locals[:matchee]))
|
173
|
+
deconstruct_node(matchee).then do |dnode|
|
174
|
+
right =
|
175
|
+
if node.children.empty?
|
176
|
+
case_eq_clause(s(:array), s(:lvar, locals[:arr]))
|
177
|
+
else
|
178
|
+
array_element(0, *node.children)
|
179
|
+
end
|
180
|
+
|
181
|
+
# already deconsrtructed
|
182
|
+
next right if dnode.nil?
|
183
|
+
|
184
|
+
# if there is no rest or tail, match the size first
|
185
|
+
unless node.type == :array_pattern_with_tail || node.children.any? { |n| n.type == :match_rest }
|
186
|
+
right =
|
187
|
+
s(:and,
|
188
|
+
s(:send,
|
189
|
+
node.children.size.to_ast_node,
|
190
|
+
:==,
|
191
|
+
s(:send, s(:lvar, locals[:arr]), :size)),
|
192
|
+
right)
|
193
|
+
end
|
194
|
+
|
195
|
+
s(:and,
|
196
|
+
dnode,
|
197
|
+
right)
|
198
|
+
end
|
199
|
+
end
|
200
|
+
|
201
|
+
alias array_pattern_with_tail_clause array_pattern_clause
|
202
|
+
|
203
|
+
def deconstruct_node(matchee)
|
204
|
+
# only deconstruct once per case
|
205
|
+
return if deconstructed.include?(locals[:arr])
|
206
|
+
|
207
|
+
context.use_ruby_next!
|
208
|
+
|
209
|
+
right = s(:send, matchee, :deconstruct)
|
210
|
+
|
211
|
+
deconstructed << locals[:arr]
|
212
|
+
s(:and,
|
213
|
+
s(:or,
|
214
|
+
s(:lvasgn, locals[:arr], right),
|
215
|
+
s(:true)), # rubocop:disable Lint/BooleanSymbol
|
216
|
+
s(:or,
|
217
|
+
case_eq_clause(s(:const, nil, :Array), s(:lvar, locals[:arr])),
|
218
|
+
raise_error(:TypeError)))
|
219
|
+
end
|
220
|
+
|
221
|
+
def array_element(index, head, *tail)
|
222
|
+
return array_match_rest(index, head, *tail) if head.type == :match_rest
|
223
|
+
|
224
|
+
send("#{head.type}_array_element", head, index).then do |node|
|
225
|
+
next node if tail.empty?
|
226
|
+
|
227
|
+
s(:and,
|
228
|
+
node,
|
229
|
+
array_element(index + 1, *tail))
|
230
|
+
end
|
231
|
+
end
|
232
|
+
|
233
|
+
def array_match_rest(index, node, *tail)
|
234
|
+
child = node.children[0]
|
235
|
+
rest = arr_rest_items(index, tail.size).then do |r|
|
236
|
+
next r unless child
|
237
|
+
match_var_clause(
|
238
|
+
child,
|
239
|
+
r
|
240
|
+
)
|
241
|
+
end
|
242
|
+
|
243
|
+
return rest if tail.empty?
|
244
|
+
|
245
|
+
s(:and,
|
246
|
+
rest,
|
247
|
+
array_rest_element(*tail))
|
248
|
+
end
|
249
|
+
|
250
|
+
def array_rest_element(head, *tail)
|
251
|
+
send("#{head.type}_array_element", head, -(tail.size + 1)).then do |node|
|
252
|
+
next node if tail.empty?
|
253
|
+
|
254
|
+
s(:and,
|
255
|
+
node,
|
256
|
+
array_rest_element(*tail))
|
257
|
+
end
|
258
|
+
end
|
259
|
+
|
260
|
+
def array_pattern_array_element(node, index)
|
261
|
+
element = arr_item_at(index)
|
262
|
+
locals.with(arr: locals[:arr, index]) do
|
263
|
+
array_pattern_clause(node, element)
|
264
|
+
end
|
265
|
+
end
|
266
|
+
|
267
|
+
def hash_pattern_array_element(node, index)
|
268
|
+
element = arr_item_at(index)
|
269
|
+
locals.with(hash: locals[:arr, index]) do
|
270
|
+
hash_pattern_clause(node, element)
|
271
|
+
end
|
272
|
+
end
|
273
|
+
|
274
|
+
def match_alt_array_element(node, index)
|
275
|
+
children = node.children.map do |child, i|
|
276
|
+
send :"#{child.type}_array_element", child, index
|
277
|
+
end
|
278
|
+
s(:or, *children)
|
279
|
+
end
|
280
|
+
|
281
|
+
def match_var_array_element(node, index)
|
282
|
+
match_var_clause(node, arr_item_at(index))
|
283
|
+
end
|
284
|
+
|
285
|
+
def pin_array_element(node, index)
|
286
|
+
case_eq_array_element node.children[0], index
|
287
|
+
end
|
288
|
+
|
289
|
+
def case_eq_array_element(node, index)
|
290
|
+
case_eq_clause(node, arr_item_at(index))
|
291
|
+
end
|
292
|
+
|
293
|
+
def arr_item_at(index, arr = s(:lvar, locals[:arr]))
|
294
|
+
s(:index, arr, index.to_ast_node)
|
295
|
+
end
|
296
|
+
|
297
|
+
def arr_rest_items(index, size, arr = s(:lvar, locals[:arr]))
|
298
|
+
s(:index,
|
299
|
+
arr,
|
300
|
+
s(:irange,
|
301
|
+
s(:int, index),
|
302
|
+
s(:int, -(size + 1))))
|
303
|
+
end
|
304
|
+
|
305
|
+
#=========== ARRAY PATTERN (END) ===============
|
306
|
+
|
307
|
+
#=========== HASH PATTERN (START) ===============
|
308
|
+
|
309
|
+
def hash_pattern_clause(node, matchee = s(:lvar, locals[:matchee]))
|
310
|
+
# Optimization: avoid hash modifications when not needed
|
311
|
+
# (we use #dup and #delete when "reading" values when **rest is present
|
312
|
+
# to assign the rest of the hash copy to it)
|
313
|
+
@hash_match_rest = node.children.any? { |child| child.type == :match_rest || child.type == :match_nil_pattern }
|
314
|
+
keys = hash_pattern_keys(node.children)
|
315
|
+
|
316
|
+
deconstruct_keys_node(keys, matchee).then do |dnode|
|
317
|
+
right =
|
318
|
+
if node.children.empty?
|
319
|
+
case_eq_clause(s(:hash), s(:lvar, locals[:hash]))
|
320
|
+
else
|
321
|
+
hash_element(*node.children)
|
322
|
+
end
|
323
|
+
|
324
|
+
return dnode if right.nil?
|
325
|
+
|
326
|
+
s(:and,
|
327
|
+
dnode,
|
328
|
+
right)
|
329
|
+
end
|
330
|
+
end
|
331
|
+
|
332
|
+
def hash_pattern_keys(children)
|
333
|
+
return s(:nil) if children.empty?
|
334
|
+
|
335
|
+
children.filter_map do |child|
|
336
|
+
# Skip ** without var
|
337
|
+
next if child.type == :match_rest && child.children.empty?
|
338
|
+
return s(:nil) if child.type == :match_rest || child.type == :match_nil_pattern
|
339
|
+
|
340
|
+
send("#{child.type}_hash_key", child)
|
341
|
+
end.then { |keys| s(:array, *keys) }
|
342
|
+
end
|
343
|
+
|
344
|
+
def pair_hash_key(node)
|
345
|
+
node.children[0]
|
346
|
+
end
|
347
|
+
|
348
|
+
def match_var_hash_key(node)
|
349
|
+
s(:sym, node.children[0])
|
350
|
+
end
|
351
|
+
|
352
|
+
def deconstruct_keys_node(keys, matchee = s(:lvar, locals[:matchee]))
|
353
|
+
# Deconstruct once and use a copy of the hash for each pattern if we need **rest.
|
354
|
+
hash_dup =
|
355
|
+
if @hash_match_rest
|
356
|
+
s(:lvasgn, locals[:hash], s(:send, s(:lvar, locals[:hash, :src]), :dup))
|
357
|
+
else
|
358
|
+
s(:lvasgn, locals[:hash], s(:lvar, locals[:hash, :src]))
|
359
|
+
end
|
360
|
+
|
361
|
+
# Create a copy of the original hash if already deconstructed
|
362
|
+
return hash_dup if deconstructed.include?(locals[:hash])
|
363
|
+
|
364
|
+
context.use_ruby_next!
|
365
|
+
|
366
|
+
deconstructed << locals[:hash]
|
367
|
+
|
368
|
+
right = s(:send,
|
369
|
+
matchee, :deconstruct_keys, keys)
|
370
|
+
|
371
|
+
s(:and,
|
372
|
+
s(:or,
|
373
|
+
s(:lvasgn, locals[:hash, :src], right),
|
374
|
+
s(:true)), # rubocop:disable Lint/BooleanSymbol
|
375
|
+
s(:and,
|
376
|
+
s(:or,
|
377
|
+
case_eq_clause(s(:const, nil, :Hash), s(:lvar, locals[:hash, :src])),
|
378
|
+
raise_error(:TypeError)),
|
379
|
+
hash_dup))
|
380
|
+
end
|
381
|
+
|
382
|
+
def hash_pattern_hash_element(node, key)
|
383
|
+
element = hash_value_at(key)
|
384
|
+
locals.with(hash: locals[:hash, deconstructed.size]) do
|
385
|
+
hash_pattern_clause(node, element)
|
386
|
+
end
|
387
|
+
end
|
388
|
+
|
389
|
+
def array_pattern_hash_element(node, key)
|
390
|
+
element = hash_value_at(key)
|
391
|
+
locals.with(arr: locals[:hash, deconstructed.size]) do
|
392
|
+
array_pattern_clause(node, element)
|
393
|
+
end
|
394
|
+
end
|
395
|
+
|
396
|
+
def hash_element(head, *tail)
|
397
|
+
send("#{head.type}_hash_element", head).then do |node|
|
398
|
+
next node if tail.empty?
|
399
|
+
|
400
|
+
right = hash_element(*tail)
|
401
|
+
|
402
|
+
next node if right.nil?
|
403
|
+
|
404
|
+
s(:and,
|
405
|
+
node,
|
406
|
+
right)
|
407
|
+
end
|
408
|
+
end
|
409
|
+
|
410
|
+
def pair_hash_element(node, _key = nil)
|
411
|
+
key, val = *node.children
|
412
|
+
send("#{val.type}_hash_element", val, key)
|
413
|
+
end
|
414
|
+
|
415
|
+
def match_alt_hash_element(node, key)
|
416
|
+
element_node = s(:lvasgn, locals[:hash, :el], hash_value_at(key))
|
417
|
+
|
418
|
+
children = locals.with(hash_element: locals[:hash, :el]) do
|
419
|
+
node.children.map do |child, i|
|
420
|
+
send :"#{child.type}_hash_element", child, key
|
421
|
+
end
|
422
|
+
end
|
423
|
+
|
424
|
+
s(:and,
|
425
|
+
s(:or,
|
426
|
+
element_node,
|
427
|
+
s(:true)), # rubocop:disable Lint/BooleanSymbol
|
428
|
+
s(:or, *children))
|
429
|
+
end
|
430
|
+
|
431
|
+
def match_var_hash_element(node, key = nil)
|
432
|
+
key ||= node.children[0]
|
433
|
+
# We need to check whether key is present first
|
434
|
+
s(:and,
|
435
|
+
hash_has_key(key),
|
436
|
+
match_var_clause(node, hash_value_at(key)))
|
437
|
+
end
|
438
|
+
|
439
|
+
def match_nil_pattern_hash_element(node, _key = nil)
|
440
|
+
s(:send,
|
441
|
+
s(:lvar, locals[:hash]),
|
442
|
+
:empty?)
|
443
|
+
end
|
444
|
+
|
445
|
+
def match_rest_hash_element(node, _key = nil)
|
446
|
+
# case {}; in **; end
|
447
|
+
return if node.children.empty?
|
448
|
+
|
449
|
+
child = node.children[0]
|
450
|
+
|
451
|
+
raise ArgumentError, "Unknown hash match_rest child: #{child.type}" unless child.type == :match_var
|
452
|
+
|
453
|
+
match_var_clause(child, s(:lvar, locals[:hash]))
|
454
|
+
end
|
455
|
+
|
456
|
+
def case_eq_hash_element(node, key)
|
457
|
+
case_eq_clause node, hash_value_at(key)
|
458
|
+
end
|
459
|
+
|
460
|
+
def hash_value_at(key, hash = s(:lvar, locals[:hash]))
|
461
|
+
return s(:lvar, locals.fetch(:hash_element)) if locals.key?(:hash_element)
|
462
|
+
|
463
|
+
if @hash_match_rest
|
464
|
+
s(:send,
|
465
|
+
hash, :delete,
|
466
|
+
key.to_ast_node)
|
467
|
+
else
|
468
|
+
s(:index,
|
469
|
+
hash,
|
470
|
+
key.to_ast_node)
|
471
|
+
end
|
472
|
+
end
|
473
|
+
|
474
|
+
def hash_has_key(key, hash = s(:lvar, locals[:hash]))
|
475
|
+
s(:send,
|
476
|
+
hash, :key?,
|
477
|
+
key.to_ast_node)
|
478
|
+
end
|
479
|
+
|
480
|
+
#=========== HASH PATTERN (END) ===============
|
481
|
+
|
482
|
+
def with_guard(node, guard)
|
483
|
+
return node unless guard
|
484
|
+
|
485
|
+
s(:and,
|
486
|
+
node,
|
487
|
+
guard.children[0]).then do |expr|
|
488
|
+
next expr unless guard.type == :unless_guard
|
489
|
+
s(:send, expr, :!)
|
490
|
+
end
|
491
|
+
end
|
492
|
+
|
493
|
+
def no_matching_pattern
|
494
|
+
raise_error :NoMatchingPatternError
|
495
|
+
end
|
496
|
+
|
497
|
+
def raise_error(type)
|
498
|
+
s(:send, s(:const, nil, :Kernel), :raise,
|
499
|
+
s(:const, nil, type),
|
500
|
+
s(:send,
|
501
|
+
s(:lvar, locals[:matchee]), :inspect))
|
502
|
+
end
|
503
|
+
|
504
|
+
def respond_to_missing?(mid, *)
|
505
|
+
return true if mid.match?(/_(clause|array_element)/)
|
506
|
+
super
|
507
|
+
end
|
508
|
+
|
509
|
+
def method_missing(mid, *args, &block)
|
510
|
+
return case_eq_clause(args.first) if mid.match?(/_clause$/)
|
511
|
+
return case_eq_array_element(*args) if mid.match?(/_array_element$/)
|
512
|
+
return case_eq_hash_element(*args) if mid.match?(/_hash_element$/)
|
513
|
+
super
|
514
|
+
end
|
515
|
+
|
516
|
+
private
|
517
|
+
|
518
|
+
attr_reader :deconstructed
|
519
|
+
end
|
520
|
+
end
|
521
|
+
end
|
522
|
+
end
|