class Prism::Pattern
A pattern is an object that wraps a Ruby pattern matching expression. The expression would normally be passed to an ‘in` clause within a `case` expression or a rightward assignment expression. For example, in the following snippet:
case node in ConstantPathNode[ConstantReadNode[name: :Prism], ConstantReadNode[name: :Pattern]] end
the pattern is the ConstantPathNode[...]
expression.
The pattern gets compiled into an object that responds to call by running the compile
method. This method itself will run back through Prism
to parse the expression into a tree, then walk the tree to generate the necessary callable objects. For example, if you wanted to compile the expression above into a callable, you would:
callable = Prism::Pattern.new("ConstantPathNode[ConstantReadNode[name: :Prism], ConstantReadNode[name: :Pattern]]").compile callable.call(node)
The callable object returned by compile
is guaranteed to respond to call with a single argument, which is the node to match against. It also is guaranteed to respond to ===, which means it itself can be used in a ‘case` expression, as in:
case node when callable end
If the query given to the initializer cannot be compiled into a valid matcher (either because of a syntax error or because it is using syntax we do not yet support) then a Prism::Pattern::CompilationError
will be raised.
Attributes
The query that this pattern was initialized with.
Public Class Methods
Create a new pattern with the given query. The query should be a string containing a Ruby pattern matching expression.
# File prism/pattern.rb, line 63 def initialize(query) @query = query @compiled = nil end
Public Instance Methods
Compile the query into a callable object that can be used to match against nodes.
# File prism/pattern.rb, line 70 def compile result = Prism.parse("case nil\nin #{query}\nend") compile_node(result.value.statements.body.last.conditions.last.pattern) end
Scan the given node and all of its children for nodes that match the pattern. If a block is given, it will be called with each node that matches the pattern. If no block is given, an enumerator will be returned that will yield each node that matches the pattern.
# File prism/pattern.rb, line 79 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
Private Instance Methods
Shortcut for combining two procs into one that returns true if both return true.
# File prism/pattern.rb, line 95 def combine_and(left, right) ->(other) { left.call(other) && right.call(other) } end
Shortcut for combining two procs into one that returns true if either returns true.
# File prism/pattern.rb, line 101 def combine_or(left, right) ->(other) { left.call(other) || right.call(other) } end
in foo | bar
# File prism/pattern.rb, line 136 def compile_alternation_pattern_node(node) combine_or(compile_node(node.left), compile_node(node.right)) end
in [foo, bar, baz]
# File prism/pattern.rb, line 111 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
# File prism/pattern.rb, line 141 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
in ConstantReadNode
in String
# File prism/pattern.rb, line 153 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
Raise an error because the given node is not supported.
# File prism/pattern.rb, line 106 def compile_error(node) raise CompilationError, node.inspect end
in InstanceVariableReadNode[name: Symbol] in { name: Symbol }
# File prism/pattern.rb, line 171 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
in nil
# File prism/pattern.rb, line 196 def compile_nil_node(node) ->(attribute) { attribute.nil? } end
Compile any kind of node. Dispatch out to the individual compilation methods based on the type of node.
# File prism/pattern.rb, line 225 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
in /foo/
# File prism/pattern.rb, line 201 def compile_regular_expression_node(node) regexp = Regexp.new(node.unescaped, node.closing[1..]) ->(attribute) { regexp === attribute } end
in “” in “foo”
# File prism/pattern.rb, line 209 def compile_string_node(node) string = node.unescaped ->(attribute) { string === attribute } end
in :+ in :foo
# File prism/pattern.rb, line 217 def compile_symbol_node(node) symbol = node.unescaped.to_sym ->(attribute) { symbol === attribute } end