rubocop-ast 0.5.1 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/rubocop/ast.rb +17 -0
- data/lib/rubocop/ast/builder.rb +1 -0
- data/lib/rubocop/ast/node.rb +44 -125
- data/lib/rubocop/ast/node/array_node.rb +1 -0
- data/lib/rubocop/ast/node/block_node.rb +1 -0
- data/lib/rubocop/ast/node/def_node.rb +5 -0
- data/lib/rubocop/ast/node/keyword_splat_node.rb +1 -0
- data/lib/rubocop/ast/node/mixin/collection_node.rb +1 -0
- data/lib/rubocop/ast/node/mixin/descendence.rb +116 -0
- data/lib/rubocop/ast/node/mixin/method_dispatch_node.rb +2 -0
- data/lib/rubocop/ast/node/mixin/method_identifier_predicates.rb +9 -0
- data/lib/rubocop/ast/node/mixin/numeric_node.rb +1 -0
- data/lib/rubocop/ast/node/mixin/predicate_operator_node.rb +7 -3
- data/lib/rubocop/ast/node/pair_node.rb +4 -0
- data/lib/rubocop/ast/node/regexp_node.rb +9 -4
- data/lib/rubocop/ast/node_pattern.rb +44 -870
- data/lib/rubocop/ast/node_pattern/builder.rb +72 -0
- data/lib/rubocop/ast/node_pattern/comment.rb +45 -0
- data/lib/rubocop/ast/node_pattern/compiler.rb +104 -0
- data/lib/rubocop/ast/node_pattern/compiler/atom_subcompiler.rb +56 -0
- data/lib/rubocop/ast/node_pattern/compiler/binding.rb +78 -0
- data/lib/rubocop/ast/node_pattern/compiler/debug.rb +168 -0
- data/lib/rubocop/ast/node_pattern/compiler/node_pattern_subcompiler.rb +146 -0
- data/lib/rubocop/ast/node_pattern/compiler/sequence_subcompiler.rb +420 -0
- data/lib/rubocop/ast/node_pattern/compiler/subcompiler.rb +57 -0
- data/lib/rubocop/ast/node_pattern/lexer.rb +70 -0
- data/lib/rubocop/ast/node_pattern/lexer.rex +39 -0
- data/lib/rubocop/ast/node_pattern/lexer.rex.rb +182 -0
- data/lib/rubocop/ast/node_pattern/method_definer.rb +143 -0
- data/lib/rubocop/ast/node_pattern/node.rb +275 -0
- data/lib/rubocop/ast/node_pattern/parser.racc.rb +470 -0
- data/lib/rubocop/ast/node_pattern/parser.rb +66 -0
- data/lib/rubocop/ast/node_pattern/parser.y +103 -0
- data/lib/rubocop/ast/node_pattern/sets.rb +37 -0
- data/lib/rubocop/ast/node_pattern/with_meta.rb +111 -0
- data/lib/rubocop/ast/processed_source.rb +5 -1
- data/lib/rubocop/ast/traversal.rb +149 -172
- data/lib/rubocop/ast/version.rb +1 -1
- metadata +37 -3
@@ -0,0 +1,146 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module AST
|
5
|
+
class NodePattern
|
6
|
+
class Compiler
|
7
|
+
# Compiles code that evalues to true or false
|
8
|
+
# for a given value `var` (typically a RuboCop::AST::Node)
|
9
|
+
# or it's `node.type` if `seq_head` is true
|
10
|
+
#
|
11
|
+
# Doc on how this fits in the compiling process:
|
12
|
+
# /doc/modules/ROOT/pages/node_pattern.md
|
13
|
+
class NodePatternSubcompiler < Subcompiler
|
14
|
+
attr_reader :access, :seq_head
|
15
|
+
|
16
|
+
def initialize(compiler, var: nil, access: var, seq_head: false)
|
17
|
+
super(compiler)
|
18
|
+
@var = var
|
19
|
+
@access = access
|
20
|
+
@seq_head = seq_head
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
def visit_negation
|
26
|
+
expr = compile(node.child)
|
27
|
+
"!(#{expr})"
|
28
|
+
end
|
29
|
+
|
30
|
+
def visit_ascend
|
31
|
+
compiler.with_temp_variables do |ascend|
|
32
|
+
expr = compiler.compile_as_node_pattern(node.child, var: ascend)
|
33
|
+
"(#{ascend} = #{access_node}) && (#{ascend} = #{ascend}.parent) && #{expr}"
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def visit_descend
|
38
|
+
compiler.with_temp_variables { |descendant| <<~RUBY.chomp }
|
39
|
+
::RuboCop::AST::NodePattern.descend(#{access}).any? do |#{descendant}|
|
40
|
+
#{compiler.compile_as_node_pattern(node.child, var: descendant)}
|
41
|
+
end
|
42
|
+
RUBY
|
43
|
+
end
|
44
|
+
|
45
|
+
def visit_wildcard
|
46
|
+
'true'
|
47
|
+
end
|
48
|
+
|
49
|
+
def visit_unify
|
50
|
+
name = compiler.bind(node.child) do |unify_name|
|
51
|
+
# double assign to avoid "assigned but unused variable"
|
52
|
+
return "(#{unify_name} = #{access_element}; #{unify_name} = #{unify_name}; true)"
|
53
|
+
end
|
54
|
+
|
55
|
+
compile_value_match(name)
|
56
|
+
end
|
57
|
+
|
58
|
+
def visit_capture
|
59
|
+
"(#{compiler.next_capture} = #{access_element}; #{compile(node.child)})"
|
60
|
+
end
|
61
|
+
|
62
|
+
### Lists
|
63
|
+
|
64
|
+
def visit_union
|
65
|
+
multiple_access(:union) do
|
66
|
+
terms = compiler.each_union(node.children)
|
67
|
+
.map { |child| compile(child) }
|
68
|
+
|
69
|
+
"(#{terms.join(' || ')})"
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def visit_intersection
|
74
|
+
multiple_access(:intersection) do
|
75
|
+
node.children.map { |child| compile(child) }
|
76
|
+
.join(' && ')
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
def visit_predicate
|
81
|
+
"#{access_element}.#{node.method_name}#{compile_args(node.arg_list)}"
|
82
|
+
end
|
83
|
+
|
84
|
+
def visit_function_call
|
85
|
+
"#{node.method_name}#{compile_args(node.arg_list, first: access_element)}"
|
86
|
+
end
|
87
|
+
|
88
|
+
def visit_node_type
|
89
|
+
"#{access_node}.#{node.child.to_s.tr('-', '_')}_type?"
|
90
|
+
end
|
91
|
+
|
92
|
+
def visit_sequence
|
93
|
+
multiple_access(:sequence) do |var|
|
94
|
+
term = compiler.compile_sequence(node, var: var)
|
95
|
+
"#{compile_guard_clause} && #{term}"
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
# Assumes other types are atoms.
|
100
|
+
def visit_other_type
|
101
|
+
value = compiler.compile_as_atom(node)
|
102
|
+
compile_value_match(value)
|
103
|
+
end
|
104
|
+
|
105
|
+
# Compiling helpers
|
106
|
+
|
107
|
+
def compile_value_match(value)
|
108
|
+
"#{value} === #{access_element}"
|
109
|
+
end
|
110
|
+
|
111
|
+
# @param [Array<Node>, nil]
|
112
|
+
# @return [String, nil]
|
113
|
+
def compile_args(arg_list, first: nil)
|
114
|
+
args = arg_list&.map { |arg| compiler.compile_as_atom(arg) }
|
115
|
+
args = [first, *args] if first
|
116
|
+
"(#{args.join(', ')})" if args
|
117
|
+
end
|
118
|
+
|
119
|
+
def access_element
|
120
|
+
seq_head ? "#{access}.type" : access
|
121
|
+
end
|
122
|
+
|
123
|
+
def access_node
|
124
|
+
return access if seq_head
|
125
|
+
|
126
|
+
"#{compile_guard_clause} && #{access}"
|
127
|
+
end
|
128
|
+
|
129
|
+
def compile_guard_clause
|
130
|
+
"#{access}.is_a?(::RuboCop::AST::Node)"
|
131
|
+
end
|
132
|
+
|
133
|
+
def multiple_access(kind)
|
134
|
+
return yield @var if @var
|
135
|
+
|
136
|
+
compiler.with_temp_variables(kind) do |var|
|
137
|
+
memo = "#{var} = #{access}"
|
138
|
+
@var = @access = var
|
139
|
+
"(#{memo}; #{yield @var})"
|
140
|
+
end
|
141
|
+
end
|
142
|
+
end
|
143
|
+
end
|
144
|
+
end
|
145
|
+
end
|
146
|
+
end
|
@@ -0,0 +1,420 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module AST
|
5
|
+
class NodePattern
|
6
|
+
class Compiler
|
7
|
+
# Compiles terms within a sequence to code that evalues to true or false.
|
8
|
+
# Compilation of the nodes that can match only a single term is deferred to
|
9
|
+
# `NodePatternSubcompiler`; only nodes that can match multiple terms are
|
10
|
+
# compiled here.
|
11
|
+
# Assumes the given `var` is a `::RuboCop::AST::Node`
|
12
|
+
#
|
13
|
+
# Doc on how this fits in the compiling process:
|
14
|
+
# /doc/modules/ROOT/pages/node_pattern.md
|
15
|
+
#
|
16
|
+
# rubocop:disable Metrics/ClassLength
|
17
|
+
class SequenceSubcompiler < Subcompiler
|
18
|
+
DELTA = 1
|
19
|
+
POSITIVE = :positive?.to_proc
|
20
|
+
private_constant :POSITIVE
|
21
|
+
|
22
|
+
# Calls `compile_sequence`; the actual `compile` method
|
23
|
+
# will be used for the different terms of the sequence.
|
24
|
+
# The only case of re-entrant call to `compile` is `visit_capture`
|
25
|
+
def initialize(compiler, sequence:, var:)
|
26
|
+
@seq = sequence # The node to be compiled
|
27
|
+
@seq_var = var # Holds the name of the variable holding the AST::Node we are matching
|
28
|
+
super(compiler)
|
29
|
+
end
|
30
|
+
|
31
|
+
def compile_sequence
|
32
|
+
# rubocop:disable Layout/CommentIndentation
|
33
|
+
compiler.with_temp_variables do |cur_child, cur_index, previous_index|
|
34
|
+
@cur_child_var = cur_child # To hold the current child node
|
35
|
+
@cur_index_var = cur_index # To hold the current child index (always >= 0)
|
36
|
+
@prev_index_var = previous_index # To hold the child index before we enter the
|
37
|
+
# variadic nodes
|
38
|
+
@cur_index = :seq_head # Can be any of:
|
39
|
+
# :seq_head : when the current child is actually the
|
40
|
+
# sequence head
|
41
|
+
# :variadic_mode : child index held by @cur_index_var
|
42
|
+
# >= 0 : when the current child index is known
|
43
|
+
# (from the begining)
|
44
|
+
# < 0 : when the index is known from the end,
|
45
|
+
# where -1 is *past the end*,
|
46
|
+
# -2 is the last child, etc...
|
47
|
+
# This shift of 1 from standard Ruby indices
|
48
|
+
# is stored in DELTA
|
49
|
+
@in_sync = false # `true` iff `@cur_child_var` and `@cur_index_var`
|
50
|
+
# correspond to `@cur_index`
|
51
|
+
# Must be true if `@cur_index` is `:variadic_mode`
|
52
|
+
compile_terms
|
53
|
+
end
|
54
|
+
# rubocop:enable Layout/CommentIndentation
|
55
|
+
end
|
56
|
+
|
57
|
+
private
|
58
|
+
|
59
|
+
private :compile # Not meant to be called from outside
|
60
|
+
|
61
|
+
# Single node patterns are all handled here
|
62
|
+
def visit_other_type
|
63
|
+
access = case @cur_index
|
64
|
+
when :seq_head
|
65
|
+
{ var: @seq_var,
|
66
|
+
seq_head: true }
|
67
|
+
when :variadic_mode
|
68
|
+
{ var: @cur_child_var }
|
69
|
+
else
|
70
|
+
idx = @cur_index + (@cur_index.negative? ? DELTA : 0)
|
71
|
+
{ access: "#{@seq_var}.children[#{idx}]" }
|
72
|
+
end
|
73
|
+
|
74
|
+
term = compiler.compile_as_node_pattern(node, **access)
|
75
|
+
compile_and_advance(term)
|
76
|
+
end
|
77
|
+
|
78
|
+
def visit_repetition
|
79
|
+
within_loop do
|
80
|
+
child_captures = node.child.nb_captures
|
81
|
+
child_code = compile(node.child)
|
82
|
+
next compile_loop(child_code) if child_captures.zero?
|
83
|
+
|
84
|
+
compile_captured_repetition(child_code, child_captures)
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
def visit_any_order
|
89
|
+
within_loop do
|
90
|
+
compiler.with_temp_variables do |matched|
|
91
|
+
case_terms = compile_any_order_branches(matched)
|
92
|
+
else_code, init = compile_any_order_else
|
93
|
+
term = "#{compile_case(case_terms, else_code)} && #{compile_loop_advance}"
|
94
|
+
|
95
|
+
all_matched_check = "&&\n#{matched}.size == #{node.term_nodes.size}" if node.rest_node
|
96
|
+
<<~RUBY
|
97
|
+
(#{init}#{matched} = {}; true) &&
|
98
|
+
#{compile_loop(term)} #{all_matched_check} \\
|
99
|
+
RUBY
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
def visit_union
|
105
|
+
return visit_other_type if node.arity == 1
|
106
|
+
|
107
|
+
# The way we implement complex unions is by "forking", i.e.
|
108
|
+
# making a copy of the present subcompiler to compile each branch
|
109
|
+
# of the union.
|
110
|
+
# We then use the resulting state of the subcompilers to
|
111
|
+
# reset ourselves.
|
112
|
+
forks = compile_union_forks
|
113
|
+
preserve_union_start(forks)
|
114
|
+
merge_forks!(forks)
|
115
|
+
expr = forks.values.join(" || \n")
|
116
|
+
"(#{expr})"
|
117
|
+
end
|
118
|
+
|
119
|
+
def compile_case(when_branches, else_code)
|
120
|
+
<<~RUBY
|
121
|
+
case
|
122
|
+
#{when_branches.join(' ')}
|
123
|
+
else #{else_code}
|
124
|
+
end \\
|
125
|
+
RUBY
|
126
|
+
end
|
127
|
+
|
128
|
+
def compile_any_order_branches(matched_var)
|
129
|
+
node.term_nodes.map.with_index do |node, i|
|
130
|
+
code = compiler.compile_as_node_pattern(node, var: @cur_child_var, seq_head: false)
|
131
|
+
var = "#{matched_var}[#{i}]"
|
132
|
+
"when !#{var} && #{code} then #{var} = true"
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
# @return [Array<String>] Else code, and init code (if any)
|
137
|
+
def compile_any_order_else
|
138
|
+
rest = node.rest_node
|
139
|
+
if !rest
|
140
|
+
'false'
|
141
|
+
elsif rest.capture?
|
142
|
+
capture_rest = compiler.next_capture
|
143
|
+
init = "#{capture_rest} = [];"
|
144
|
+
["#{capture_rest} << #{@cur_child_var}", init]
|
145
|
+
else
|
146
|
+
'true'
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
def visit_capture
|
151
|
+
return visit_other_type if node.child.arity == 1
|
152
|
+
|
153
|
+
storage = compiler.next_capture
|
154
|
+
term = compile(node.child)
|
155
|
+
capture = "#{@seq_var}.children[#{compile_matched(:range)}]"
|
156
|
+
"#{term} && (#{storage} = #{capture})"
|
157
|
+
end
|
158
|
+
|
159
|
+
def visit_rest
|
160
|
+
empty_loop
|
161
|
+
end
|
162
|
+
|
163
|
+
# Compilation helpers
|
164
|
+
|
165
|
+
def compile_and_advance(term)
|
166
|
+
case @cur_index
|
167
|
+
when :variadic_mode
|
168
|
+
"#{term} && #{compile_loop_advance}"
|
169
|
+
when :seq_head
|
170
|
+
# @in_sync = false # already the case
|
171
|
+
@cur_index = 0
|
172
|
+
term
|
173
|
+
else
|
174
|
+
@in_sync = false
|
175
|
+
@cur_index += 1
|
176
|
+
term
|
177
|
+
end
|
178
|
+
end
|
179
|
+
|
180
|
+
def compile_captured_repetition(child_code, child_captures)
|
181
|
+
captured_range = "#{compiler.captures - child_captures}...#{compiler.captures}"
|
182
|
+
captured = "captures[#{captured_range}]"
|
183
|
+
compiler.with_temp_variables do |accumulate|
|
184
|
+
code = "#{child_code} && #{accumulate}.push(#{captured})"
|
185
|
+
<<~RUBY
|
186
|
+
(#{accumulate} = Array.new) &&
|
187
|
+
#{compile_loop(code)} &&
|
188
|
+
(#{captured} = if #{accumulate}.empty?
|
189
|
+
(#{captured_range}).map{[]} # Transpose hack won't work for empty case
|
190
|
+
else
|
191
|
+
#{accumulate}.transpose
|
192
|
+
end) \\
|
193
|
+
RUBY
|
194
|
+
end
|
195
|
+
end
|
196
|
+
|
197
|
+
# Assumes `@cur_index` is already updated
|
198
|
+
def compile_matched(kind)
|
199
|
+
to = compile_cur_index
|
200
|
+
from = if @prev_index == :variadic_mode
|
201
|
+
@prev_index_used = true
|
202
|
+
@prev_index_var
|
203
|
+
else
|
204
|
+
compile_index(@prev_index)
|
205
|
+
end
|
206
|
+
case kind
|
207
|
+
when :range
|
208
|
+
"#{from}...#{to}"
|
209
|
+
when :length
|
210
|
+
"#{to} - #{from}"
|
211
|
+
end
|
212
|
+
end
|
213
|
+
|
214
|
+
def handle_prev
|
215
|
+
@prev_index = @cur_index
|
216
|
+
@prev_index_used = false
|
217
|
+
code = yield
|
218
|
+
if @prev_index_used
|
219
|
+
@prev_index_used = false
|
220
|
+
code = "(#{@prev_index_var} = #{@cur_index_var}; true) && #{code}"
|
221
|
+
end
|
222
|
+
|
223
|
+
code
|
224
|
+
end
|
225
|
+
|
226
|
+
def compile_terms(children = @seq.children, last_arity = 0..0)
|
227
|
+
arities = remaining_arities(children, last_arity)
|
228
|
+
total_arity = arities.shift
|
229
|
+
guard = compile_child_nb_guard(total_arity)
|
230
|
+
return guard if children.empty?
|
231
|
+
|
232
|
+
@remaining_arity = total_arity
|
233
|
+
terms = children.map do |child|
|
234
|
+
use_index_from_end
|
235
|
+
@remaining_arity = arities.shift
|
236
|
+
handle_prev { compile(child) }
|
237
|
+
end
|
238
|
+
[guard, terms].join(" &&\n")
|
239
|
+
end
|
240
|
+
|
241
|
+
# yield `sync_code` iff not already in sync
|
242
|
+
def sync
|
243
|
+
return if @in_sync
|
244
|
+
|
245
|
+
code = compile_loop_advance("= #{compile_cur_index}")
|
246
|
+
@in_sync = true
|
247
|
+
yield code
|
248
|
+
end
|
249
|
+
|
250
|
+
# @api private
|
251
|
+
attr_reader :in_sync, :cur_index
|
252
|
+
|
253
|
+
public :in_sync
|
254
|
+
protected :cur_index, :compile_terms, :sync
|
255
|
+
|
256
|
+
# @return [Array<Range>] total arities (as Ranges) of remaining children nodes
|
257
|
+
# E.g. For sequence `(_ _? <_ _>)`, arities are: 1, 0..1, 2
|
258
|
+
# and remaining arities are: 3..4, 2..3, 2..2, 0..0
|
259
|
+
def remaining_arities(children, last_arity)
|
260
|
+
last = last_arity
|
261
|
+
arities = children
|
262
|
+
.reverse
|
263
|
+
.map(&:arity_range)
|
264
|
+
.map { |r| last = last.begin + r.begin..last.max + r.max }
|
265
|
+
.reverse!
|
266
|
+
arities.push last_arity
|
267
|
+
end
|
268
|
+
|
269
|
+
# @return [String] code that evaluates to `false` if the matched arity is too small
|
270
|
+
def compile_min_check
|
271
|
+
return 'false' unless node.variadic?
|
272
|
+
|
273
|
+
unless @remaining_arity.end.infinite?
|
274
|
+
not_too_much_remaining = "#{compile_remaining} <= #{@remaining_arity.max}"
|
275
|
+
end
|
276
|
+
min_to_match = node.arity_range.begin
|
277
|
+
if min_to_match.positive?
|
278
|
+
enough_matched = "#{compile_matched(:length)} >= #{min_to_match}"
|
279
|
+
end
|
280
|
+
return 'true' unless not_too_much_remaining || enough_matched
|
281
|
+
|
282
|
+
[not_too_much_remaining, enough_matched].compact.join(' && ')
|
283
|
+
end
|
284
|
+
|
285
|
+
def compile_remaining
|
286
|
+
offset = case @cur_index
|
287
|
+
when :seq_head
|
288
|
+
' + 1'
|
289
|
+
when :variadic_mode
|
290
|
+
" - #{@cur_index_var}"
|
291
|
+
when 0
|
292
|
+
''
|
293
|
+
when POSITIVE
|
294
|
+
" - #{@cur_index}"
|
295
|
+
else
|
296
|
+
# odd compiling condition, result may not be expected
|
297
|
+
# E.g: `(... {a | b c})` => the b c branch can never match
|
298
|
+
return - (@cur_index + DELTA)
|
299
|
+
end
|
300
|
+
|
301
|
+
"#{@seq_var}.children.size #{offset}"
|
302
|
+
end
|
303
|
+
|
304
|
+
def compile_max_matched
|
305
|
+
return node.arity unless node.variadic?
|
306
|
+
|
307
|
+
min_remaining_children = "#{compile_remaining} - #{@remaining_arity.begin}"
|
308
|
+
return min_remaining_children if node.arity.end.infinite?
|
309
|
+
|
310
|
+
"[#{min_remaining_children}, #{node.arity.max}].min"
|
311
|
+
end
|
312
|
+
|
313
|
+
def empty_loop
|
314
|
+
@cur_index = -@remaining_arity.begin - DELTA
|
315
|
+
@in_sync = false
|
316
|
+
'true'
|
317
|
+
end
|
318
|
+
|
319
|
+
def compile_cur_index
|
320
|
+
return @cur_index_var if @in_sync
|
321
|
+
|
322
|
+
compile_index
|
323
|
+
end
|
324
|
+
|
325
|
+
def compile_index(cur = @cur_index)
|
326
|
+
return cur if cur >= 0
|
327
|
+
|
328
|
+
"#{@seq_var}.children.size - #{-(cur + DELTA)}"
|
329
|
+
end
|
330
|
+
|
331
|
+
# Note: assumes `@cur_index != :seq_head`. Node types using `within_loop` must
|
332
|
+
# have `def in_sequence_head; :raise; end`
|
333
|
+
def within_loop
|
334
|
+
sync do |sync_code|
|
335
|
+
@cur_index = :variadic_mode
|
336
|
+
"#{sync_code} && #{yield}"
|
337
|
+
end || yield
|
338
|
+
end
|
339
|
+
|
340
|
+
# returns truthy iff `@cur_index` switched to relative from end mode (i.e. < 0)
|
341
|
+
def use_index_from_end
|
342
|
+
return if @cur_index == :seq_head || @remaining_arity.begin != @remaining_arity.max
|
343
|
+
|
344
|
+
@cur_index = -@remaining_arity.begin - DELTA
|
345
|
+
end
|
346
|
+
|
347
|
+
def compile_loop_advance(to = '+=1')
|
348
|
+
# The `#{@cur_child_var} ||` is just to avoid unused variable warning
|
349
|
+
"(#{@cur_child_var} = #{@seq_var}.children[#{@cur_index_var} #{to}]; " \
|
350
|
+
"#{@cur_child_var} || true)"
|
351
|
+
end
|
352
|
+
|
353
|
+
def compile_loop(term)
|
354
|
+
<<~RUBY
|
355
|
+
(#{compile_max_matched}).times do
|
356
|
+
break #{compile_min_check} unless #{term}
|
357
|
+
end \\
|
358
|
+
RUBY
|
359
|
+
end
|
360
|
+
|
361
|
+
def compile_child_nb_guard(arity_range)
|
362
|
+
case arity_range.max
|
363
|
+
when Float::INFINITY
|
364
|
+
"#{compile_remaining} >= #{arity_range.begin}"
|
365
|
+
when arity_range.begin
|
366
|
+
"#{compile_remaining} == #{arity_range.begin}"
|
367
|
+
else
|
368
|
+
"(#{arity_range.begin}..#{arity_range.max}).cover?(#{compile_remaining})"
|
369
|
+
end
|
370
|
+
end
|
371
|
+
|
372
|
+
# @return [Hash] of {subcompiler => code}
|
373
|
+
def compile_union_forks
|
374
|
+
compiler.each_union(node.children).map do |child|
|
375
|
+
subsequence_terms = child.is_a?(Node::Subsequence) ? child.children : [child]
|
376
|
+
fork = dup
|
377
|
+
code = fork.compile_terms(subsequence_terms, @remaining_arity)
|
378
|
+
@in_sync = false if @cur_index != :variadic_mode
|
379
|
+
[fork, code]
|
380
|
+
end.to_h # we could avoid map if RUBY_VERSION >= 2.6...
|
381
|
+
end
|
382
|
+
|
383
|
+
# Modifies in place `forks` to insure that `cur_{child|index}_var` are ok
|
384
|
+
def preserve_union_start(forks)
|
385
|
+
return if @cur_index != :variadic_mode || forks.size <= 1
|
386
|
+
|
387
|
+
compiler.with_temp_variables do |union_reset|
|
388
|
+
cur = "(#{union_reset} = [#{@cur_child_var}, #{@cur_index_var}]) && "
|
389
|
+
reset = "(#{@cur_child_var}, #{@cur_index_var} = #{union_reset}) && "
|
390
|
+
forks.transform_values! do |code|
|
391
|
+
code = "#{cur}#{code}"
|
392
|
+
cur = reset
|
393
|
+
code
|
394
|
+
end
|
395
|
+
end
|
396
|
+
end
|
397
|
+
|
398
|
+
# Modifies in place `forks`
|
399
|
+
# Syncs our state
|
400
|
+
def merge_forks!(forks)
|
401
|
+
sub_compilers = forks.keys
|
402
|
+
if !node.variadic? # e.g {a b | c d}
|
403
|
+
@cur_index = sub_compilers.first.cur_index # all cur_index should be equivalent
|
404
|
+
elsif use_index_from_end
|
405
|
+
# nothing to do
|
406
|
+
else
|
407
|
+
# can't use index from end, so we must sync all forks
|
408
|
+
@cur_index = :variadic_mode
|
409
|
+
forks.each do |sub, code|
|
410
|
+
sub.sync { |sync_code| forks[sub] = "#{code} && #{sync_code}" }
|
411
|
+
end
|
412
|
+
end
|
413
|
+
@in_sync = sub_compilers.all?(&:in_sync)
|
414
|
+
end
|
415
|
+
end
|
416
|
+
# rubocop:enable Metrics/ClassLength
|
417
|
+
end
|
418
|
+
end
|
419
|
+
end
|
420
|
+
end
|