1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146
|
# frozen_string_literal: true
module RuboCop
module AST
class NodePattern
class Compiler
# Compiles code that evalues to true or false
# for a given value `var` (typically a RuboCop::AST::Node)
# or it's `node.type` if `seq_head` is true
#
# Doc on how this fits in the compiling process:
# /docs/modules/ROOT/pages/node_pattern.adoc
class NodePatternSubcompiler < Subcompiler
attr_reader :access, :seq_head
def initialize(compiler, var: nil, access: var, seq_head: false)
super(compiler)
@var = var
@access = access
@seq_head = seq_head
end
private
def visit_negation
expr = compile(node.child)
"!(#{expr})"
end
def visit_ascend
compiler.with_temp_variables do |ascend|
expr = compiler.compile_as_node_pattern(node.child, var: ascend)
"(#{ascend} = #{access_node}) && (#{ascend} = #{ascend}.parent) && #{expr}"
end
end
def visit_descend
compiler.with_temp_variables { |descendant| <<~RUBY.chomp }
::RuboCop::AST::NodePattern.descend(#{access}).any? do |#{descendant}|
#{compiler.compile_as_node_pattern(node.child, var: descendant)}
end
RUBY
end
def visit_wildcard
'true'
end
def visit_unify
name = compiler.bind(node.child) do |unify_name|
# double assign to avoid "assigned but unused variable"
return "(#{unify_name} = #{access_element}; #{unify_name} = #{unify_name}; true)"
end
compile_value_match(name)
end
def visit_capture
"(#{compiler.next_capture} = #{access_element}; #{compile(node.child)})"
end
### Lists
def visit_union
multiple_access(:union) do
terms = compiler.each_union(node.children)
.map { |child| compile(child) }
"(#{terms.join(' || ')})"
end
end
def visit_intersection
multiple_access(:intersection) do
node.children.map { |child| compile(child) }
.join(' && ')
end
end
def visit_predicate
"#{access_element}.#{node.method_name}#{compile_args(node.arg_list)}"
end
def visit_function_call
"#{node.method_name}#{compile_args(node.arg_list, first: access_element)}"
end
def visit_node_type
"#{access_node}.#{node.child.to_s.tr('-', '_')}_type?"
end
def visit_sequence
multiple_access(:sequence) do |var|
term = compiler.compile_sequence(node, var: var)
"#{compile_guard_clause} && #{term}"
end
end
# Assumes other types are atoms.
def visit_other_type
value = compiler.compile_as_atom(node)
compile_value_match(value)
end
# Compiling helpers
def compile_value_match(value)
"#{value} === #{access_element}"
end
# @param [Array<Node>, nil]
# @return [String, nil]
def compile_args(arg_list, first: nil)
args = arg_list&.map { |arg| compiler.compile_as_atom(arg) }
args = [first, *args] if first
"(#{args.join(', ')})" if args
end
def access_element
seq_head ? "#{access}.type" : access
end
def access_node
return access if seq_head
"#{compile_guard_clause} && #{access}"
end
def compile_guard_clause
"#{access}.is_a?(::RuboCop::AST::Node)"
end
def multiple_access(kind)
return yield @var if @var
compiler.with_temp_variables(kind) do |var|
memo = "#{var} = #{access}"
@var = @access = var
"(#{memo}; #{yield @var})"
end
end
end
end
end
end
end
|