class Prism::Pattern

raised.
do not yet support) then a Prism::Pattern::CompilationError will be
matcher (either because of a syntax error or because it is using syntax we
If the query given to the initializer cannot be compiled into a valid
end
when callable
case node
expression, as in:
guaranteed to respond to #===, which means it itself can be used in a ‘case`
with a single argument, which is the node to match against. It also is
The callable object returned by #compile is guaranteed to respond to #call
callable.call(node)
callable = Prism::Pattern.new(“ConstantPathNode[ConstantReadNode[name: :Prism], ConstantReadNode[name: :Pattern]]”).compile
expression above into a callable, you would:
necessary callable objects. For example, if you wanted to compile the
parse the expression into a tree, then walk the tree to generate the
the #compile method. This method itself will run back through Prism to
The pattern gets compiled into an object that responds to #call by running
the pattern is the ConstantPathNode[...] expression.
end
in ConstantPathNode[ConstantReadNode[name: :Prism], ConstantReadNode[name: :Pattern]]
case node
following snippet:
expression or a rightward assignment expression. For example, in the
expression would normally be passed to an `in` clause within a `case`
A pattern is an object that wraps a Ruby pattern matching expression. The

def combine_and(left, right)

true.
Shortcut for combining two procs into one that returns true if both return
def combine_and(left, right)
  ->(other) { left.call(other) && right.call(other) }
end

def combine_or(left, right)

returns true.
Shortcut for combining two procs into one that returns true if either
def combine_or(left, right)
  ->(other) { left.call(other) || right.call(other) }
end

def compile

nodes.
Compile the query into a callable object that can be used to match against
def compile
  result = Prism.parse("case nil\nin #{query}\nend")
  compile_node(result.value.statements.body.last.conditions.last.pattern)
end

def compile_alternation_pattern_node(node)

in foo | bar
def compile_alternation_pattern_node(node)
  combine_or(compile_node(node.left), compile_node(node.right))
end

def compile_array_pattern_node(node)

in [foo, bar, baz]
def compile_array_pattern_node(node)
  compile_error(node) if !node.rest.nil? || node.posts.any?
  constant = node.constant
  compiled_constant = compile_node(constant) if constant
  preprocessed = node.requireds.map { |required| compile_node(required) }
  compiled_requireds = ->(other) do
    deconstructed = other.deconstruct
    deconstructed.length == preprocessed.length &&
      preprocessed
        .zip(deconstructed)
        .all? { |(matcher, value)| matcher.call(value) }
  end
  if compiled_constant
    combine_and(compiled_constant, compiled_requireds)
  else
    compiled_requireds
  end
end

def compile_constant_path_node(node)

in Prism::ConstantReadNode
def compile_constant_path_node(node)
  parent = node.parent
  if parent.is_a?(ConstantReadNode) && parent.slice == "Prism"
    compile_node(node.child)
  else
    compile_error(node)
  end
end

def compile_constant_read_node(node)

in String
in ConstantReadNode
def compile_constant_read_node(node)
  value = node.slice
  if Prism.const_defined?(value, false)
    clazz = Prism.const_get(value)
    ->(other) { clazz === other }
  elsif Object.const_defined?(value, false)
    clazz = Object.const_get(value)
    ->(other) { clazz === other }
  else
    compile_error(node)
  end
end

def compile_error(node)

Raise an error because the given node is not supported.
def compile_error(node)
  raise CompilationError, node.inspect
end

def compile_hash_pattern_node(node)

in { name: Symbol }
in InstanceVariableReadNode[name: Symbol]
def compile_hash_pattern_node(node)
  compile_error(node) if node.rest
  compiled_constant = compile_node(node.constant) if node.constant
  preprocessed =
    node.elements.to_h do |element|
      [element.key.unescaped.to_sym, compile_node(element.value)]
    end
  compiled_keywords = ->(other) do
    deconstructed = other.deconstruct_keys(preprocessed.keys)
    preprocessed.all? do |keyword, matcher|
      deconstructed.key?(keyword) && matcher.call(deconstructed[keyword])
    end
  end
  if compiled_constant
    combine_and(compiled_constant, compiled_keywords)
  else
    compiled_keywords
  end
end

def compile_nil_node(node)

in nil
def compile_nil_node(node)
  ->(attribute) { attribute.nil? }
end

def compile_node(node)

methods based on the type of node.
Compile any kind of node. Dispatch out to the individual compilation
def compile_node(node)
  case node
  when AlternationPatternNode
    compile_alternation_pattern_node(node)
  when ArrayPatternNode
    compile_array_pattern_node(node)
  when ConstantPathNode
    compile_constant_path_node(node)
  when ConstantReadNode
    compile_constant_read_node(node)
  when HashPatternNode
    compile_hash_pattern_node(node)
  when NilNode
    compile_nil_node(node)
  when RegularExpressionNode
    compile_regular_expression_node(node)
  when StringNode
    compile_string_node(node)
  when SymbolNode
    compile_symbol_node(node)
  else
    compile_error(node)
  end
end

def compile_regular_expression_node(node)

in /foo/
def compile_regular_expression_node(node)
  regexp = Regexp.new(node.unescaped, node.closing[1..])
  ->(attribute) { regexp === attribute }
end

def compile_string_node(node)

in "foo"
in ""
def compile_string_node(node)
  string = node.unescaped
  ->(attribute) { string === attribute }
end

def compile_symbol_node(node)

in :foo
in :+
def compile_symbol_node(node)
  symbol = node.unescaped.to_sym
  ->(attribute) { symbol === attribute }
end

def initialize(query)

containing a Ruby pattern matching expression.
Create a new pattern with the given query. The query should be a string
def initialize(query)
  @query = query
  @compiled = nil
end

def scan(root)

that will yield each node that matches the pattern.
matches the pattern. If no block is given, an enumerator will be returned
pattern. If a block is given, it will be called with each node that
Scan the given node and all of its children for nodes that match the
def scan(root)
  return to_enum(__method__, root) unless block_given?
  @compiled ||= compile
  queue = [root]
  while (node = queue.shift)
    yield node if @compiled.call(node)
    queue.concat(node.compact_child_nodes)
  end
end