Class: RuboCop::AST::NodePattern
- Inherits:
-
Object
- Object
- RuboCop::AST::NodePattern
- Defined in:
- lib/rubocop/ast/node_pattern.rb
Overview
This class performs a pattern-matching operation on an AST node.
Initialize a new ‘NodePattern` with `NodePattern.new(pattern_string)`, then pass an AST node to `NodePattern#match`. Alternatively, use one of the class macros in `NodePattern::Macros` to define your own pattern-matching method.
If the match fails, ‘nil` will be returned. If the match succeeds, the return value depends on whether a block was provided to `#match`, and whether the pattern contained any “captures” (values which are extracted from a matching AST.)
-
With block: #match yields the captures (if any) and passes the return
value of the block through.
-
With no block, but one capture: the capture is returned.
-
With no block, but multiple captures: captures are returned as an array.
-
With no block and no captures: #match returns ‘true`.
## Pattern string format examples
':sym' # matches a literal symbol
'1' # matches a literal integer
'nil' # matches a literal nil
'send' # matches (send ...)
'(send)' # matches (send)
'(send ...)' # matches (send ...)
'(op-asgn)' # node types with hyphenated names also work
'{send class}' # matches (send ...) or (class ...)
'({send class})' # matches (send) or (class)
'(send const)' # matches (send (const ...))
'(send _ :new)' # matches (send <anything> :new)
'(send $_ :new)' # as above, but whatever matches the $_ is captured
'(send $_ $_)' # you can use as many captures as you want
'(send !const ...)' # ! negates the next part of the pattern
'$(send const ...)' # arbitrary matching can be performed on a capture
'(send _recv _msg)' # wildcards can be named (for readability)
'(send ... :new)' # you can match against the last children
'(array <str sym>)' # you can match children in any order. This
# would match `['x', :y]` as well as `[:y, 'x']
'(_ <str sym ...>)' # will match if arguments have at least a `str` and
# a `sym` node, but can have more.
'(array <$str $_>)' # captures are in the order of the pattern,
# irrespective of the actual order of the children
'(array int*)' # will match an array of 0 or more integers
'(array int ?)' # will match 0 or 1 integer.
# Note: Space needed to distinguish from int?
'(array int+)' # will match an array of 1 or more integers
'(array (int $_)+)' # as above and will capture the numbers in an array
'(send $...)' # capture all the children as an array
'(send $... int)' # capture all children but the last as an array
'(send _x :+ _x)' # unification is performed on named wildcards
# (like Prolog variables...)
# (#== is used to see if values unify)
'(int odd?)' # words which end with a ? are predicate methods,
# are are called on the target to see if it matches
# any Ruby method which the matched object supports
# can be used
# if a truthy value is returned, the match succeeds
'(int [!1 !2])' # [] contains multiple patterns, ALL of which must
# match in that position
# in other words, while {} is pattern union (logical
# OR), [] is intersection (logical AND)
'(send %1 _)' # % stands for a parameter which must be supplied to
# #match at matching time
# it will be compared to the corresponding value in
# the AST using #==
# a bare '%' is the same as '%1'
# the number of extra parameters passed to #match
# must equal the highest % value in the pattern
# for consistency, %0 is the 'root node' which is
# passed as the 1st argument to #match, where the
# matching process starts
'^^send' # each ^ ascends one level in the AST
# so this matches against the grandparent node
'`send' # descends any number of level in the AST
# so this matches against any descendant node
'#method' # we call this a 'funcall'; it calls a method in the
# context where a pattern-matching method is defined
# if that returns a truthy value, the match succeeds
'equal?(%1)' # predicates can be given 1 or more extra args
'#method(%0, 1)' # funcalls can also be given 1 or more extra args
You can nest arbitrarily deep:
# matches node parsed from 'Const = Class.new' or 'Const = Module.new':
'(casgn nil? :Const (send (const nil? {:Class :Module}) :new))'
# matches a node parsed from an 'if', with a '==' comparison,
# and no 'else' branch:
'(if (send _ :== _) _ nil?)'
Note that patterns like ‘send’ are implemented by calling ‘#send_type?` on the node being matched, ’const’ by ‘#const_type?`, ’int’ by ‘#int_type?`, and so on. Therefore, if you add methods which are named like `#prefix_type?` to the AST node class, then ’prefix’ will become usable as a pattern.
Also note that if you need a “guard clause” to protect against possible nils in a certain place in the AST, you can do it like this: ‘[!nil <pattern>]`
The compiler code is very simple; don’t be afraid to read through it!
Defined Under Namespace
Modules: Macros
Constant Summary collapse
- Invalid =
Class.new(StandardError)
Instance Attribute Summary collapse
-
#pattern ⇒ Object
readonly
Returns the value of attribute pattern.
Class Method Summary collapse
-
.descend(element) {|element| ... } ⇒ Object
Yields its argument and any descendants, depth-first.
Instance Method Summary collapse
- #==(other) ⇒ Object (also: #eql?)
-
#initialize(str) ⇒ NodePattern
constructor
A new instance of NodePattern.
- #marshal_dump ⇒ Object
- #marshal_load(pattern) ⇒ Object
- #match(*args) ⇒ Object
- #to_s ⇒ Object
Constructor Details
#initialize(str) ⇒ NodePattern
Returns a new instance of NodePattern.
832 833 834 835 836 837 838 |
# File 'lib/rubocop/ast/node_pattern.rb', line 832 def initialize(str) @pattern = str compiler = Compiler.new(str) src = "def match(node0#{compiler.emit_trailing_params});" \ "#{compiler.emit_method_code}end" instance_eval(src, __FILE__, __LINE__ + 1) end |
Instance Attribute Details
#pattern ⇒ Object (readonly)
Returns the value of attribute pattern.
830 831 832 |
# File 'lib/rubocop/ast/node_pattern.rb', line 830 def pattern @pattern end |
Class Method Details
.descend(element) {|element| ... } ⇒ Object
Yields its argument and any descendants, depth-first.
867 868 869 870 871 872 873 874 875 876 877 878 879 |
# File 'lib/rubocop/ast/node_pattern.rb', line 867 def self.descend(element, &block) return to_enum(__method__, element) unless block_given? yield element if element.is_a?(::RuboCop::AST::Node) element.children.each do |child| descend(child, &block) end end nil end |
Instance Method Details
#==(other) ⇒ Object Also known as: eql?
855 856 857 858 |
# File 'lib/rubocop/ast/node_pattern.rb', line 855 def ==(other) other.is_a?(NodePattern) && Compiler.tokens(other.pattern) == Compiler.tokens(pattern) end |
#marshal_dump ⇒ Object
851 852 853 |
# File 'lib/rubocop/ast/node_pattern.rb', line 851 def marshal_dump pattern end |
#marshal_load(pattern) ⇒ Object
847 848 849 |
# File 'lib/rubocop/ast/node_pattern.rb', line 847 def marshal_load(pattern) initialize pattern end |
#match(*args) ⇒ Object
840 841 842 843 844 845 |
# File 'lib/rubocop/ast/node_pattern.rb', line 840 def match(*args) # If we're here, it's because the singleton method has not been defined, # either because we've been dup'ed or serialized through YAML initialize(pattern) match(*args) end |
#to_s ⇒ Object
861 862 863 |
# File 'lib/rubocop/ast/node_pattern.rb', line 861 def to_s "#<#{self.class} #{pattern}>" end |