rubocop-ast 0.5.1 → 1.0.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/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
|