class YARP::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: :YARP], 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 YARP
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 = YARP::Pattern.new("ConstantPathNode[ConstantReadNode[name: :YARP], 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 YARP::Pattern::CompilationError
will be raised.
Attributes
Public Class Methods
# File yarp/pattern.rb, line 58 def initialize(query) @query = query @compiled = nil end
Public Instance Methods
# File yarp/pattern.rb, line 63 def compile result = YARP.parse("case nil\nin #{query}\nend") compile_node(result.value.statements.body.last.conditions.last.pattern) end
# File yarp/pattern.rb, line 68 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.child_nodes.compact) end end
Private Instance Methods
Shortcut for combining two procs into one that returns true if both return true.
# File yarp/pattern.rb, line 84 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 yarp/pattern.rb, line 90 def combine_or(left, right) ->(other) { left.call(other) || right.call(other) } end
in foo | bar
# File yarp/pattern.rb, line 125 def compile_alternation_pattern_node(node) combine_or(compile_node(node.left), compile_node(node.right)) end
in [foo, bar, baz]
# File yarp/pattern.rb, line 100 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 yarp/pattern.rb, line 130 def compile_constant_path_node(node) parent = node.parent if parent.is_a?(ConstantReadNode) && parent.slice == "YARP" compile_node(node.child) else compile_error(node) end end
in ConstantReadNode
in String
# File yarp/pattern.rb, line 142 def compile_constant_read_node(node) value = node.slice if YARP.const_defined?(value, false) clazz = YARP.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 yarp/pattern.rb, line 95 def compile_error(node) raise CompilationError, node.inspect end
in InstanceVariableReadNode[name: Symbol] in { name: Symbol }
# File yarp/pattern.rb, line 160 def compile_hash_pattern_node(node) compile_error(node) unless node.kwrest.nil? compiled_constant = compile_node(node.constant) if node.constant preprocessed = node.assocs.to_h do |assoc| [assoc.key.unescaped.to_sym, compile_node(assoc.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 yarp/pattern.rb, line 185 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 yarp/pattern.rb, line 214 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 yarp/pattern.rb, line 190 def compile_regular_expression_node(node) regexp = Regexp.new(node.unescaped, node.closing[1..]) ->(attribute) { regexp === attribute } end
in “” in “foo”
# File yarp/pattern.rb, line 198 def compile_string_node(node) string = node.unescaped ->(attribute) { string === attribute } end
in :+ in :foo
# File yarp/pattern.rb, line 206 def compile_symbol_node(node) symbol = node.unescaped.to_sym ->(attribute) { symbol === attribute } end