Class: RuboCop::AST::NodePattern::Compiler::SequenceSubcompiler
Relationships & Source Files | |
Extension / Inclusion / Inheritance Descendants | |
Subclasses:
RuboCop::AST::NodePattern::Compiler::Debug::SequenceSubcompiler
|
|
Super Chains via Extension / Inclusion / Inheritance | |
Class Chain:
self,
Subcompiler
|
|
Instance Chain:
self,
Subcompiler
|
|
Inherits: |
RuboCop::AST::NodePattern::Compiler::Subcompiler
|
Defined in: | lib/rubocop/ast/node_pattern/compiler/sequence_subcompiler.rb |
Overview
Compiles terms within a sequence to code that evalues to true or false.
Compilation of the nodes that can match only a single term is deferred to
NodePatternSubcompiler
; only nodes that can match multiple terms are
compiled here.
Assumes the given var
is a ::RuboCop::AST::Node
Doc on how this fits in the compiling process: /docs/modules/ROOT/pages/node_pattern.adoc
Constant Summary
-
DELTA =
Shift of 1 from standard Ruby indices
1
-
POSITIVE =
private
# File 'lib/rubocop/ast/node_pattern/compiler/sequence_subcompiler.rb', line 19:positive?.to_proc
Class Attribute Summary
Subcompiler
- Inherited
Class Method Summary
-
.new(compiler, sequence:, var:) ⇒ SequenceSubcompiler
constructor
Calls
compile_sequence
; the actualcompile
method will be used for the different terms of the sequence.
Subcompiler
- Inherited
Instance Attribute Summary
Instance Method Summary
- #compile_sequence
-
#compile_and_advance(term)
private
Compilation helpers.
- #compile_any_order_branches(matched_var) private
- #compile_any_order_else ⇒ Array<String> private
- #compile_captured_repetition(child_code, child_captures) private
- #compile_case(when_branches, else_code) private
- #compile_child_nb_guard(arity_range) private
- #compile_cur_index private
- #compile_index(cur = @cur_index) private
- #compile_loop(term) private
- #compile_loop_advance(to = '+=1') private
-
#compile_matched(kind)
private
Assumes
@cur_index
is already updated. - #compile_max_matched private
- #compile_min_check ⇒ String private
- #compile_remaining private
- #compile_union_forks ⇒ Hash private
- #empty_loop private
- #handle_prev private
-
#merge_forks!(forks)
private
Modifies in place
forks
Syncs our state. -
#preserve_union_start(forks)
private
Modifies in place
forks
to insure thatcur_{child|index}_var
are ok. -
#remaining_arities(children, last_arity) ⇒ Array<Range>
private
E.g.
-
#use_index_from_end
private
returns truthy iff
@cur_index
switched to relative from end mode (i.e. - #visit_any_order private
- #visit_capture private
-
#visit_other_type
private
Single node patterns are all handled here.
- #visit_repetition private
- #visit_rest private
- #visit_union private
-
#within_loop
private
Note
assumes @cur_index != :seq_head
.
Subcompiler
- Inherited
Constructor Details
.new(compiler, sequence:, var:) ⇒ SequenceSubcompiler
Calls compile_sequence
; the actual compile
method
will be used for the different terms of the sequence.
The only case of re-entrant call to compile
is visit_capture
# File 'lib/rubocop/ast/node_pattern/compiler/sequence_subcompiler.rb', line 25
def initialize(compiler, sequence:, var:) @seq = sequence # The node to be compiled @seq_var = var # Holds the name of the variable holding the AST::Node we are matching super(compiler) end
Instance Attribute Details
#in_sync (readonly)
# File 'lib/rubocop/ast/node_pattern/compiler/sequence_subcompiler.rb', line 251
attr_reader :in_sync, :cur_index
Instance Method Details
#compile_and_advance(term) (private)
Compilation helpers
# File 'lib/rubocop/ast/node_pattern/compiler/sequence_subcompiler.rb', line 165
def compile_and_advance(term) case @cur_index when :variadic_mode "#{term} && #{compile_loop_advance}" when :seq_head # @in_sync = false # already the case @cur_index = 0 term else @in_sync = false @cur_index += 1 term end end
#compile_any_order_branches(matched_var) (private)
[ GitHub ]# File 'lib/rubocop/ast/node_pattern/compiler/sequence_subcompiler.rb', line 128
def compile_any_order_branches(matched_var) node.term_nodes.map.with_index do |node, i| code = compiler.compile_as_node_pattern(node, var: @cur_child_var, seq_head: false) var = "#{matched_var}[#{i}]" "when !#{var} && #{code} then #{var} = true" end end
#compile_any_order_else ⇒ Array
<String
> (private)
# File 'lib/rubocop/ast/node_pattern/compiler/sequence_subcompiler.rb', line 137
def compile_any_order_else rest = node.rest_node if !rest 'false' elsif rest.capture? capture_rest = compiler.next_capture init = "#{capture_rest} = [];" ["#{capture_rest} << #{@cur_child_var}", init] else 'true' end end
#compile_captured_repetition(child_code, child_captures) (private)
[ GitHub ]# File 'lib/rubocop/ast/node_pattern/compiler/sequence_subcompiler.rb', line 180
def compile_captured_repetition(child_code, child_captures) captured_range = "#{compiler.captures - child_captures}...#{compiler.captures}" captured = "captures[#{captured_range}]" compiler.with_temp_variables do |accumulate| code = "#{child_code} && #{accumulate}.push(#{captured})" <<~RUBY (#{accumulate} = Array.new) && #{compile_loop(code)} && (#{captured} = if #{accumulate}.empty? (#{captured_range}).map{[]} # Transpose hack won't work for empty case else #{accumulate}.transpose end) \\ RUBY end end
#compile_case(when_branches, else_code) (private)
[ GitHub ]# File 'lib/rubocop/ast/node_pattern/compiler/sequence_subcompiler.rb', line 119
def compile_case(when_branches, else_code) <<~RUBY case #{when_branches.join(' ')} else #{else_code} end \\ RUBY end
#compile_child_nb_guard(arity_range) (private)
[ GitHub ]# File 'lib/rubocop/ast/node_pattern/compiler/sequence_subcompiler.rb', line 361
def compile_child_nb_guard(arity_range) case arity_range.max when Float::INFINITY "#{compile_remaining} >= #{arity_range.begin}" when arity_range.begin "#{compile_remaining} == #{arity_range.begin}" else "(#{arity_range.begin}..#{arity_range.max}).cover?(#{compile_remaining})" end end
#compile_cur_index (private)
[ GitHub ]# File 'lib/rubocop/ast/node_pattern/compiler/sequence_subcompiler.rb', line 319
def compile_cur_index return @cur_index_var if @in_sync compile_index end
#compile_index(cur = @cur_index) (private)
[ GitHub ]# File 'lib/rubocop/ast/node_pattern/compiler/sequence_subcompiler.rb', line 325
def compile_index(cur = @cur_index) return cur if cur >= 0 "#{@seq_var}.children.size - #{-(cur + DELTA)}" end
#compile_loop(term) (private)
[ GitHub ]# File 'lib/rubocop/ast/node_pattern/compiler/sequence_subcompiler.rb', line 353
def compile_loop(term) <<~RUBY (#{compile_max_matched}).times do break #{compile_min_check} unless #{term} end \\ RUBY end
#compile_loop_advance(to = '+=1') (private)
[ GitHub ]# File 'lib/rubocop/ast/node_pattern/compiler/sequence_subcompiler.rb', line 347
def compile_loop_advance(to = '+=1') # The `#{@cur_child_var} ||` is just to avoid unused variable warning "(#{@cur_child_var} = #{@seq_var}.children[#{@cur_index_var} #{to}]; " \ "#{@cur_child_var} || true)" end
#compile_matched(kind) (private)
Assumes @cur_index
is already updated
# File 'lib/rubocop/ast/node_pattern/compiler/sequence_subcompiler.rb', line 198
def compile_matched(kind) to = compile_cur_index from = if @prev_index == :variadic_mode @prev_index_used = true @prev_index_var else compile_index(@prev_index) end case kind when :range "#{from}...#{to}" when :length "#{to} - #{from}" end end
#compile_max_matched (private)
[ GitHub ]# File 'lib/rubocop/ast/node_pattern/compiler/sequence_subcompiler.rb', line 304
def compile_max_matched return node.arity unless node.variadic? min_remaining_children = "#{compile_remaining} - #{@remaining_arity.begin}" return min_remaining_children if node.arity.end.infinite? "[#{min_remaining_children}, #{node.arity.max}].min" end
#compile_min_check ⇒ String
(private)
# File 'lib/rubocop/ast/node_pattern/compiler/sequence_subcompiler.rb', line 270
def compile_min_check return 'false' unless node.variadic? unless @remaining_arity.end.infinite? not_too_much_remaining = "#{compile_remaining} <= #{@remaining_arity.max}" end min_to_match = node.arity_range.begin if min_to_match.positive? enough_matched = "#{compile_matched(:length)} >= #{min_to_match}" end return 'true' unless not_too_much_remaining || enough_matched [not_too_much_remaining, enough_matched].compact.join(' && ') end
#compile_remaining (private)
[ GitHub ]# File 'lib/rubocop/ast/node_pattern/compiler/sequence_subcompiler.rb', line 285
def compile_remaining offset = case @cur_index when :seq_head ' + 1' when :variadic_mode " - #{@cur_index_var}" when 0 '' when POSITIVE " - #{@cur_index}" else # odd compiling condition, result may not be expected # E.g: `(... {a | b c})` => the b c branch can never match return - (@cur_index + DELTA) end "#{@seq_var}.children.size #{offset}" end
#compile_sequence
[ GitHub ]# File 'lib/rubocop/ast/node_pattern/compiler/sequence_subcompiler.rb', line 31
def compile_sequence # rubocop:disable Layout/CommentIndentation compiler.with_temp_variables do |cur_child, cur_index, previous_index| @cur_child_var = cur_child # To hold the current child node @cur_index_var = cur_index # To hold the current child index (always >= 0) @prev_index_var = previous_index # To hold the child index before we enter the # variadic nodes @cur_index = :seq_head # Can be any of: # :seq_head : when the current child is actually the # sequence head # :variadic_mode : child index held by @cur_index_var # >= 0 : when the current child index is known # (from the beginning) # < 0 : when the index is known from the end, # where -1 is *past the end*, # -2 is the last child, etc... # This shift of 1 from standard Ruby indices # is stored in DELTA @in_sync = false # `true` iff `@cur_child_var` and `@cur_index_var` # correspond to `@cur_index` # Must be true if `@cur_index` is `:variadic_mode` compile_terms end # rubocop:enable Layout/CommentIndentation end
#compile_union_forks ⇒ Hash
(private)
# File 'lib/rubocop/ast/node_pattern/compiler/sequence_subcompiler.rb', line 373
def compile_union_forks compiler.each_union(node.children).to_h do |child| subsequence_terms = child.is_a?(Node::Subsequence) ? child.children : [child] fork = dup code = fork.compile_terms(subsequence_terms, @remaining_arity) @in_sync = false if @cur_index != :variadic_mode [fork, code] end end
#empty_loop (private)
[ GitHub ]# File 'lib/rubocop/ast/node_pattern/compiler/sequence_subcompiler.rb', line 313
def empty_loop @cur_index = -@remaining_arity.begin - DELTA @in_sync = false 'true' end
#handle_prev (private)
[ GitHub ]# File 'lib/rubocop/ast/node_pattern/compiler/sequence_subcompiler.rb', line 214
def handle_prev @prev_index = @cur_index @prev_index_used = false code = yield if @prev_index_used @prev_index_used = false code = "(#{@prev_index_var} = #{@cur_index_var}; true) && #{code}" end code end
#merge_forks!(forks) (private)
Modifies in place forks
Syncs our state
# File 'lib/rubocop/ast/node_pattern/compiler/sequence_subcompiler.rb', line 400
def merge_forks!(forks) sub_compilers = forks.keys if !node.variadic? # e.g {a b | c d} @cur_index = sub_compilers.first.cur_index # all cur_index should be equivalent elsif use_index_from_end # nothing to do else # can't use index from end, so we must sync all forks @cur_index = :variadic_mode forks.each do |sub, code| sub.sync { |sync_code| forks[sub] = "#{code} && #{sync_code}" } end end @in_sync = sub_compilers.all?(&:in_sync) end
#preserve_union_start(forks) (private)
Modifies in place forks
to insure that cur_{child|index}_var
are ok
# File 'lib/rubocop/ast/node_pattern/compiler/sequence_subcompiler.rb', line 384
def preserve_union_start(forks) return if @cur_index != :variadic_mode || forks.size <= 1 compiler.with_temp_variables do |union_reset| cur = "(#{union_reset} = [#{@cur_child_var}, #{@cur_index_var}]) && " reset = "(#{@cur_child_var}, #{@cur_index_var} = #{union_reset}) && " forks.transform_values! do |code| code = "#{cur}#{code}" cur = reset code end end end
#remaining_arities(children, last_arity) ⇒ Array
<Range
> (private)
E.g. For sequence (_ ? < _>)
, arities are: 1, 0..1, 2
and remaining arities are: 3..4, 2..3, 2..2, 0..0
# File 'lib/rubocop/ast/node_pattern/compiler/sequence_subcompiler.rb', line 259
def remaining_arities(children, last_arity) last = last_arity arities = children .reverse .map(&:arity_range) .map { |r| last = (last.begin + r.begin)..(last.max + r.max) } .reverse! arities.push last_arity end
#use_index_from_end (private)
returns truthy iff @cur_index
switched to relative from end mode (i.e. < 0)
# File 'lib/rubocop/ast/node_pattern/compiler/sequence_subcompiler.rb', line 341
def use_index_from_end return if @cur_index == :seq_head || @remaining_arity.begin != @remaining_arity.max @cur_index = -@remaining_arity.begin - DELTA end
#visit_any_order (private)
[ GitHub ]# File 'lib/rubocop/ast/node_pattern/compiler/sequence_subcompiler.rb', line 88
def visit_any_order within_loop do compiler.with_temp_variables do |matched| case_terms = compile_any_order_branches(matched) else_code, init = compile_any_order_else term = "#{compile_case(case_terms, else_code)} && #{compile_loop_advance}" all_matched_check = "&&\n#{matched}.size == #{node.term_nodes.size}" if node.rest_node <<~RUBY (#{init}#{matched} = {}; true) && #{compile_loop(term)} #{all_matched_check} \\ RUBY end end end
#visit_capture (private)
[ GitHub ]# File 'lib/rubocop/ast/node_pattern/compiler/sequence_subcompiler.rb', line 150
def visit_capture return visit_other_type if node.child.arity == 1 storage = compiler.next_capture term = compile(node.child) capture = "#{@seq_var}.children[#{compile_matched(:range)}]" "#{term} && (#{storage} = #{capture})" end
#visit_other_type (private)
Single node patterns are all handled here
# File 'lib/rubocop/ast/node_pattern/compiler/sequence_subcompiler.rb', line 62
def visit_other_type access = case @cur_index when :seq_head { var: @seq_var, seq_head: true } when :variadic_mode { var: @cur_child_var } else idx = @cur_index + (@cur_index.negative? ? DELTA : 0) { access: "#{@seq_var}.children[#{idx}]" } end term = compiler.compile_as_node_pattern(node, **access) compile_and_advance(term) end
#visit_repetition (private)
[ GitHub ]# File 'lib/rubocop/ast/node_pattern/compiler/sequence_subcompiler.rb', line 78
def visit_repetition within_loop do child_captures = node.child.nb_captures child_code = compile(node.child) next compile_loop(child_code) if child_captures.zero? compile_captured_repetition(child_code, child_captures) end end
#visit_rest (private)
[ GitHub ]# File 'lib/rubocop/ast/node_pattern/compiler/sequence_subcompiler.rb', line 159
def visit_rest empty_loop end
#visit_union (private)
[ GitHub ]# File 'lib/rubocop/ast/node_pattern/compiler/sequence_subcompiler.rb', line 104
def visit_union return visit_other_type if node.arity == 1 # The way we implement complex unions is by "forking", i.e. # making a copy of the present subcompiler to compile each branch # of the union. # We then use the resulting state of the subcompilers to # reset ourselves. forks = compile_union_forks preserve_union_start(forks) merge_forks!(forks) expr = forks.values.join(" || \n") "(#{expr})" end
#within_loop (private)
Note
|
assumes @cur_index != :seq_head . Node types using within_loop must
have def in_sequence_head; :raise; end
|
# File 'lib/rubocop/ast/node_pattern/compiler/sequence_subcompiler.rb', line 333
def within_loop sync do |sync_code| @cur_index = :variadic_mode "#{sync_code} && #{yield}" end || yield end