$TESTING ||= false # unless defined $TESTING

##
# Sexps are the basic storage mechanism of SexpProcessor.  Sexps have
# a +type+ (to be renamed +node_type+) which is the first element of
# the Sexp. The type is used by SexpProcessor to determine whom to
# dispatch the Sexp to for processing.

class Sexp < Array # ZenTest FULL
  ##
  # A setter for the line this sexp was found on. Usually set by ruby_parser.

  attr_writer :line

  ##
  # Accessors for the file. Usually set by ruby_parser.

  attr_accessor :file

  ##
  # Optional comments above/aside this sexp. Usually set by ruby_parser.

  attr_accessor :comments

  @@array_types = [ :array, :args ] # TODO: remove

  ##
  # Create a new Sexp containing +args+.

  def initialize *args
    super(args)
  end

  ##
  # Creates a new Sexp from Array +a+.

  def self.from_array a
    ary = Array === a ? a : [a]

    self.new(*ary.map { |x|
               case x
               when Sexp
                 x
               when Array
                 self.from_array(x)
               else
                 x
               end
             })
  end

  ##
  # Creates a new sexp with the new contents of +body+, but with the
  # same +file+, +line+, and +comment+ as self.

  def new(*body)
    r = self.class.new(*body) # ensures a sexp from map
    r.file     = self.file     if self.file
    r.line     = self.line     if self.line
    r.comments = self.comments if self.comments
    r
  end

  def map &blk # :nodoc:
    self.new(*super(&blk)) # ensures a sexp from map
  end

  def == obj # :nodoc:
    obj.class == self.class and super # only because of a bug in ruby
  end

  ##
  # Returns true if the node_type is +array+ or +args+.
  #
  # REFACTOR: to TypedSexp - we only care when we have units.

  def array_type?
    warn "DEPRECATED: please file an issue if you actually use this. from #{caller.first}"
    type = self.sexp_type
    @@array_types.include? type
  end

  def compact # :nodoc:
    self.delete_if(&:nil?)
  end

  ##
  # Recursively enumerates the sexp yielding to +block+ for every element.
  #
  # Returning :skip will stop traversing that subtree:
  #
  #   sexp.deep_each do |s|
  #     next :skip if s.sexp_type == :if
  #     # ...
  #   end

  def deep_each &block
    return enum_for(:deep_each) unless block_given?

    self.each_sexp do |sexp|
      next if block[sexp] == :skip
      sexp.deep_each(&block)
    end
  end

  ##
  # Return the maximum depth of the sexp. One-based.

  def depth
    1 + (each_sexp.map(&:depth).max || 0)
  end

  ##
  # Enumeratates the sexp yielding to +b+ when the node_type == +t+.

  def each_of_type t, &b
    return enum_for(:each_of_type) unless block_given?

    each_sexp do | sexp |
      sexp.each_of_type(t, &b)
      yield sexp if sexp.sexp_type == t
    end
  end

  ##
  # Recursively enumerates all sub-sexps skipping non-Sexp elements.

  def each_sexp
    return enum_for(:each_sexp) unless block_given?

    self.each do |sexp|
      next unless Sexp === sexp

      yield sexp
    end
  end

  ##
  # Replaces all elements whose node_type is +from+ with +to+. Used
  # only for the most trivial of rewrites.

  def find_and_replace_all from, to
    each_with_index do | elem, index |
      if Sexp === elem then
        elem.find_and_replace_all(from, to)
      elsif elem == from
        self[index] = to
      end
    end
  end

  ##
  # Replaces all Sexps matching +pattern+ with Sexp +repl+.

  def gsub pattern, repl
    return repl if pattern == self

    new = self.map { |subset|
      case subset
      when Sexp then
        if Matcher === pattern && pattern.satisfy?(subset) then # TODO: make === be satisfy? maybe?
          repl.dup rescue repl
        else
          subset.gsub pattern, repl
        end
      else
        subset
      end
    }

    Sexp.from_array new
  end

  def inspect # :nodoc:
    sexp_str = self.map(&:inspect).join ", "
    if ENV["VERBOSE"] && line then
      "s(#{sexp_str}).line(#{line})"
    else
      "s(#{sexp_str})"
    end
  end

  def find_node name, delete = false # :nodoc:
    matches = find_nodes name

    case matches.size
    when 0 then
      nil
    when 1 then
      match = matches.first
      delete match if delete
      match
    else
      raise NoMethodError, "multiple nodes for #{name} were found in #{inspect}"
    end
  end

  ##
  # Find every node with type +name+.

  def find_nodes name
    each_sexp.find_all { |sexp| sexp.sexp_type == name }
  end

  ##
  # If passed a line number, sets the line and returns self. Otherwise
  # returns the line number. This allows you to do message cascades
  # and still get the sexp back.

  def line n = nil
    if n then
      @line = n
      self
    else
      @line ||= nil
    end
  end

  ##
  # Returns the maximum line number of the children of self.

  def line_max
    @line_max ||= self.deep_each.map(&:line).max
  end

  ##
  # Returns the size of the sexp, flattened.

  def mass
    @mass ||= inject(1) { |t, s| Sexp === s ? t + s.mass : t }
  end

  ##
  # Returns the node named +node+, deleting it if +delete+ is true.

  def method_missing meth, delete = false
    r = find_node meth, delete
    if ENV["DEBUG"] then
      if r.nil? then
        warn "%p.method_missing(%p) => nil from %s" % [self, meth, caller.first]
      elsif ENV["VERBOSE"]
        warn "%p.method_missing(%p) from %s" % [self, meth, caller.first]
      end
    end
    r
  end

  def respond_to? msg, private = false # :nodoc:
    # why do I need this? Because ruby 2.0 is broken. That's why.
    super
  end

  def pretty_print q # :nodoc:
    nnd = ")"
    nnd << ".line(#{line})" if line && ENV["VERBOSE"]

    q.group(1, "s(", nnd) do
      q.seplist(self) {|v| q.pp v }
    end
  end

  ##
  # Returns the node type of the Sexp.

  def sexp_type
    first
  end

  ##
  # Sets the node type of the Sexp.

  def sexp_type= v
    self[0] = v
  end

  ##
  # Returns the Sexp body (starting at +from+, defaulting to 1), ie
  # the values without the node type.

  def sexp_body from = 1
    self.new(*self[from..-1])
  end

  ##
  # Returns the Sexp body, ie the values without the node type.

  def sexp_body= v
    self[1..-1] = v
  end

  alias :head :sexp_type
  alias :rest :sexp_body

  ##
  # If run with debug, Sexp will raise if you shift on an empty
  # Sexp. Helps with debugging.

  def shift
    raise "I'm empty" if self.empty?
    super
  end if ($DEBUG or $TESTING)

  ##
  # Returns the bare bones structure of the sexp.
  # s(:a, :b, s(:c, :d), :e) => s(:a, s(:c))

  def structure
    if Array === self.sexp_type then
      warn "NOTE: form s(s(:subsexp)).structure is deprecated. Removing in 5.0"
      s(:bogus, *self).structure # TODO: remove >= 4.2.0
    else
      s(self.sexp_type, *each_sexp.map(&:structure))
    end
  end

  ##
  # Replaces the Sexp matching +pattern+ with +repl+.

  def sub pattern, repl
    return repl.dup if pattern == self
    return repl.dup if Matcher === pattern && pattern.satisfy?(self)

    done = false

    new = self.map do |subset|
      if done then
        subset
      else
        case subset
        when Sexp then
          if pattern == subset then
            done = true
            repl.dup rescue repl
          elsif Matcher === pattern && pattern.satisfy?(subset) then
            done = true
            repl.dup rescue repl
          else
            subset.sub pattern, repl
          end
        else
          subset
        end
      end
    end

    Sexp.from_array new
  end

  def to_a # :nodoc:
    self.map { |o| Sexp === o ? o.to_a : o }
  end

  alias to_s inspect # :nodoc:
end

##
# This is a very important shortcut to make using Sexps much more awesome.
#
# In its normal form +s(...)+, creates a Sexp instance. If passed a
# block, it creates a Sexp::Matcher using the factory methods on Sexp.
#
# See Matcher and other factory class methods on Sexp.

def s *args, &blk
  return Sexp.class_eval(&blk) if blk
  Sexp.new(*args)
end

class Sexp #:nodoc:
  ##
  # Verifies that +pattern+ is a Matcher and then dispatches to its
  # #=~ method.
  #
  # See Matcher.=~

  def =~ pattern
    raise ArgumentError, "Not a pattern: %p" % [pattern] unless Matcher === pattern
    pattern =~ self
  end

  ##
  # Verifies that +pattern+ is a Matcher and then dispatches to its
  # #satisfy? method.
  #
  # TODO: rename match?

  def satisfy? pattern
    raise ArgumentError, "Not a pattern: %p" % [pattern] unless Matcher === pattern
    pattern.satisfy? self
  end

  ##
  # Verifies that +pattern+ is a Matcher and then dispatches to its #/
  # method.
  #
  # TODO: rename grep? match_all ? find_all ?

  def / pattern
    raise ArgumentError, "Not a pattern: %p" % [pattern] unless Matcher === pattern
    pattern / self
  end

  ##
  # Recursively searches for the +pattern+ yielding the matches.

  def search_each pattern, &block # TODO: rename to grep?
    raise ArgumentError, "Needs a pattern" unless pattern.kind_of? Matcher

    return enum_for(:search_each, pattern) unless block_given?

    if pattern.satisfy? self then
      yield self
    end

    self.each_sexp do |subset|
      subset.search_each pattern, &block
    end
  end

  ##
  # Recursively searches for the +pattern+ yielding each match, and
  # replacing it with the result of the block.
  #

  def replace_sexp pattern, &block # TODO: rename to gsub?
    raise ArgumentError, "Needs a pattern" unless pattern.kind_of? Matcher

    return yield self if pattern.satisfy? self

    # TODO: Needs #new_from(*new_body) to copy file/line/comment
    self.class.new(*self.map { |subset|
                     case subset
                     when Sexp then
                       subset.replace_sexp pattern, &block
                     else
                       subset
                     end
                   })
  end

  ##
  # Matches an S-Expression.
  #
  # See Matcher for examples.

  def self.s *args
    Matcher.new(*args)
  end

  ##
  # Matches any single item.
  #
  # See Wild for examples.

  def self._
    Wild.new
  end

  # TODO: reorder factory methods and classes to match

  ##
  # Matches all remaining input.
  #
  # See Remaining for examples.

  def self.___
    Remaining.new
  end

  ##
  # Matches an expression or any expression that includes the child.
  #
  # See Include for examples.

  def self.include child # TODO: rename, name is generic ruby
    Include.new(child)
  end

  ##
  # Matches any atom.
  #
  # See Atom for examples.

  def self.atom
    Atom.new
  end

  ##
  # Matches when any of the sub-expressions match.
  #
  # This is also available via Matcher#|.
  #
  # See Any for examples.

  def self.any *args
    Any.new(*args)
  end

  ##
  # Matches only when all sub-expressions match.
  #
  # This is also available via Matcher#&.
  #
  # See All for examples.

  def self.all *args
    All.new(*args)
  end

  ##
  # Matches when sub-expression does not match.
  #
  # This is also available via Matcher#-@.
  #
  # See Not for examples.

  def self.not? arg
    Not.new arg
  end

  # TODO: add Sibling factory method?

  ##
  # Matches anything that has a child matching the sub-expression.
  #
  # See Child for examples.

  def self.child child
    Child.new child
  end

  ##
  # Matches anything having the same sexp_type, which is the first
  # value in a Sexp.
  #
  # See Type for examples.

  def self.t name
    Type.new name
  end

  ##
  # Matches any atom who's string representation matches the patterns
  # passed in.
  #
  # See Pattern for examples.

  def self.m *values
    res = values.map { |value|
      case value
      when Regexp then
        value
      else
        re = Regexp.escape value.to_s
        Regexp.new "\\A%s\\Z" % re
      end
    }
    Pattern.new Regexp.union(*res)
  end

  ##
  # Defines a family of objects that can be used to match sexps to
  # certain types of patterns, much like regexps can be used on
  # strings. Generally you won't use this class directly.
  #
  # You would normally create a matcher using the top-level #s method,
  # but with a block, calling into the Sexp factory methods. For example:
  #
  #   s{ s(:class, m(/^Test/), _, ___) }
  #
  # This creates a matcher for classes whose names start with "Test".
  # It uses Sexp.m to create a Sexp::Matcher::Pattern matcher, Sexp._
  # to create a Sexp::Matcher::Wild matcher, and Sexp.___ to create a
  # Sexp::Matcher::Remaining matcher. It works like this:
  #
  #   s{              # start to create a pattern
  #     s(            # create a sexp matcher
  #       :class.     # for class nodes
  #       m(/^Test/), # matching name slots that start with "Test"
  #       _,          # any superclass value
  #       ___         # and whatever is in the class
  #      )
  #    }
  #
  # Then you can use that with #=~, #/, Sexp#replace_sexp, and others.
  #
  # For more examples, see the various Sexp class methods, the examples,
  # and the tests supplied with Sexp.
  #
  # * For pattern creation, see factory methods: Sexp::_, Sexp::___, etc.
  # * For matching returning truthy/falsey results, see Sexp#=~.
  # * For case expressions, see Matcher#===.
  # * For getting all subtree matches, see Sexp#/.
  #
  # If rdoc didn't suck, these would all be links.

  class Matcher < Sexp
    ##
    # Should #=~ match sub-trees?

    def self.match_subs?
      @@match_subs
    end

    ##
    # Setter for +match_subs?+.

    def self.match_subs= o
      @@match_subs = o
    end

    self.match_subs = true

    ##
    # Does this matcher actually match +o+? Returns falsey if +o+ is
    # not a Sexp or if any sub-tree of +o+ is not satisfied by or
    # equal to its corresponding sub-matcher.
    #
    #--
    # TODO: push this up to Sexp and make this the workhorse
    # TODO: do the same with ===/satisfy?

    def satisfy? o
      return unless o.kind_of?(Sexp) &&
        (length == o.length || Matcher === last && last.greedy?)

      each_with_index.all? { |child, i|
        sexp = o.at i
        if Sexp === child then # TODO: when will this NOT be a matcher?
          sexp = o.sexp_body i if child.respond_to?(:greedy?) && child.greedy?
          child.satisfy? sexp
        else
          child == sexp
        end
      }
    end

    ##
    # Tree equivalent to String#=~, returns true if +self+ matches
    # +sexp+ as a whole or in a sub-tree (if +match_subs?+).
    #
    # TODO: maybe this should NOT be aliased to === ?
    #
    # TODO: example

    def =~ sexp
      raise ArgumentError, "Can't both be matchers: %p" % [sexp] if Matcher === sexp

      self.satisfy?(sexp) ||
        (self.class.match_subs? && sexp.each_sexp.any? { |sub| self =~ sub })
    end

    alias === =~ # TODO?: alias === satisfy?

    ##
    # Searches through +sexp+ for all sub-trees that match this
    # matcher and returns a MatchCollection for each match.
    #
    # TODO: redirect?
    # Example:
    #   Q{ s(:b) } / s(:a, s(:b)) => [s(:b)]

    def / sexp
      raise ArgumentError, "can't both be matchers" if Matcher === sexp

      # TODO: move search_each into matcher?
      MatchCollection.new sexp.search_each(self).to_a
    end

    ##
    # Combines the Matcher with another Matcher, the resulting one will
    # be satisfied if either Matcher would be satisfied.
    #
    # TODO: redirect
    # Example:
    #   s(:a) | s(:b)

    def | other
      Any.new self, other
    end

    ##
    # Combines the Matcher with another Matcher, the resulting one will
    # be satisfied only if both Matchers would be satisfied.
    #
    # TODO: redirect
    # Example:
    #   t(:a) & include(:b)

    def & other
      All.new self, other
    end

    ##
    # Returns a Matcher that matches whenever this Matcher would not have matched
    #
    # Example:
    #   -s(:a)

    def -@
      Not.new self
    end

    ##
    # Returns a Matcher that matches if this has a sibling +o+
    #
    # Example:
    #   s(:a) >> s(:b)

    def >> other
      Sibling.new self, other
    end

    ##
    # Is this matcher greedy? Defaults to false.

    def greedy?
      false
    end

    def inspect # :nodoc:
      s = super
      s[0] = "q"
      s
    end

    def pretty_print q # :nodoc:
      q.group 1, "q(", ")" do
        q.seplist self do |v|
          q.pp v
        end
      end
    end

    ##
    # Parse a lispy string representation of a matcher into a Matcher.
    # See +Parser+.

    def self.parse s
      Parser.new(s).parse
    end

    ##
    # Converts from a lispy string to Sexp matchers in a safe manner.
    #
    #   "(a 42 _ (c) [t x] ___)" => s{ s(:a, 42, _, s(:c), t(:x), ___) }

    class Parser

      ##
      # The stream of tokens to parse. See #lex.

      attr_accessor :tokens

      ##
      # Create a new Parser instance on +s+

      def initialize s
        self.tokens = []
        lex s
      end

      ##
      # Converts +s+ into a stream of tokens and adds them to +tokens+.

      def lex s
        tokens.concat s.scan(%r%[()\[\]]|\"[^"]*\"|/[^/]*/|[\w-]+%) # "
      end

      ##
      # Returns the next token and removes it from the stream or raises if empty.

      def next_token
        raise SyntaxError, "unbalanced input" if tokens.empty?
        tokens.shift
      end

      ##
      # Returns the next token without removing it from the stream.

      def peek_token
        tokens.first
      end

      ##
      # Parses tokens and returns a +Matcher+ instance.

      def parse
        result = parse_sexp until tokens.empty?
        result
      end

      ##
      # Parses a string into a sexp matcher:
      #
      #   SEXP : "(" SEXP:args* ")"          => Sexp.q(*args)
      #        | "[" CMD:cmd sexp:args* "]"  => Sexp.cmd(*args)
      #        | "nil"                       => nil
      #        | /\d+/:n                     => n.to_i
      #        | "___"                       => Sexp.___
      #        | "_"                         => Sexp._
      #        | /^\/(.*)\/$/:re             => Regexp.new re[0]
      #        | /^"(.*)"$/:s                => String.new s[0]
      #        | NAME:name                   => name.to_sym
      #   NAME : /\w+/
      #    CMD : "t" | "m" | "atom"

      def parse_sexp
        token = next_token

        case token
        when "(" then
          parse_list
        when "[" then
          parse_cmd
        when "nil" then
          nil
        when /^\d+$/ then
          token.to_i
        when "___" then
          Sexp.___
        when "_" then
          Sexp._
        when %r%^/(.*)/$% then
          re = $1
          raise SyntaxError, "Not allowed: /%p/" % [re] unless
            re =~ /\A([\w()|.*+^$]+)\z/
          Regexp.new re
        when /^"(.*)"$/ then
          $1
        when /^\w+$/ then
          token.to_sym
        else
          raise SyntaxError, "unhandled token: %p" % [token]
        end
      end

      ##
      # Parses a balanced list of expressions and returns the
      # equivalent matcher.

      def parse_list
        result = []

        result << parse_sexp while peek_token && peek_token != ")"
        next_token # pop off ")"

        Sexp.s(*result)
      end

      ##
      # A collection of allowed commands to convert into matchers.

      ALLOWED = [:t, :m, :atom].freeze

      ##
      # Parses a balanced command. A command is denoted by square
      # brackets and must conform to a whitelisted set of allowed
      # commands (see +ALLOWED+).

      def parse_cmd
        args = []
        args << parse_sexp while peek_token && peek_token != "]"
        next_token # pop off "]"

        cmd = args.shift
        args = Sexp.s(*args)

        raise SyntaxError, "bad cmd: %p" % [cmd] unless ALLOWED.include? cmd

        result = Sexp.send cmd, *args

        result
      end
    end # class Parser
  end # class Matcher

  ##
  # Matches any single item.
  #
  # examples:
  #
  #   s(:a)           / s{ _ }    #=> [s(:a)]
  #   s(:a, s(s(:b))) / s{ s(_) } #=> [s(s(:b))]

  class Wild < Matcher
    ##
    # Matches any single element.

    def satisfy? o
      true
    end

    def inspect # :nodoc:
      "_"
    end

    def pretty_print q # :nodoc:
      q.text "_"
    end
  end

  ##
  # Matches all remaining input. If remaining comes before any other
  # matchers, they will be ignored.
  #
  # examples:
  #
  #   s(:a)         / s{ s(:a, ___ ) } #=> [s(:a)]
  #   s(:a, :b, :c) / s{ s(:a, ___ ) } #=> [s(:a, :b, :c)]

  class Remaining < Matcher
    ##
    # Always satisfied once this is reached. Think of it as a var arg.

    def satisfy? o
      true
    end

    def greedy?
      true
    end

    def inspect # :nodoc:
      "___"
    end

    def pretty_print q # :nodoc:
      q.text "___"
    end
  end

  ##
  # Matches when any of the sub-expressions match.
  #
  # This is also available via Matcher#|.
  #
  # examples:
  #
  #   s(:a) / s{ any(s(:a), s(:b)) } #=> [s(:a)]
  #   s(:a) / s{     s(:a) | s(:b) } #=> [s(:a)] # same thing via |
  #   s(:a) / s{ any(s(:b), s(:c)) } #=> []

  class Any < Matcher
    ##
    # The collection of sub-matchers to match against.

    attr_reader :options

    ##
    # Create an Any matcher which will match any of the +options+.

    def initialize *options
      @options = options
    end

    ##
    # Satisfied when any sub expressions match +o+

    def satisfy? o
      options.any? { |exp|
        Sexp === exp && exp.satisfy?(o) || exp == o
      }
    end

    def == o # :nodoc:
      super && self.options == o.options
    end

    def inspect # :nodoc:
      options.map(&:inspect).join(" | ")
    end

    def pretty_print q # :nodoc:
      q.group 1, "any(", ")" do
        q.seplist options do |v|
          q.pp v
        end
      end
    end
  end

  ##
  # Matches only when all sub-expressions match.
  #
  # This is also available via Matcher#&.
  #
  # examples:
  #
  #   s(:a)     / s{ all(s(:a), s(:b)) }    #=> []
  #   s(:a, :b) / s{ t(:a) & include(:b)) } #=> [s(:a, :b)]

  class All < Matcher
    ##
    # The collection of sub-matchers to match against.

    attr_reader :options

    ##
    # Create an All matcher which will match all of the +options+.

    def initialize *options
      @options = options
    end

    ##
    # Satisfied when all sub expressions match +o+

    def satisfy? o
      options.all? { |exp|
        exp.kind_of?(Sexp) ? exp.satisfy?(o) : exp == o
      }
    end

    def == o # :nodoc:
      super && self.options == o.options
    end

    def inspect # :nodoc:
      options.map(&:inspect).join(" & ")
    end

    def pretty_print q # :nodoc:
      q.group 1, "all(", ")" do
        q.seplist options do |v|
          q.pp v
        end
      end
    end
  end

  ##
  # Matches when sub-expression does not match.
  #
  # This is also available via Matcher#-@.
  #
  # examples:
  #
  #   s(:a) / s{ not?(s(:b)) } #=> [s(:a)]
  #   s(:a) / s{ -s(:b) }      #=> [s(:a)]
  #   s(:a) / s{ s(not? :a) } #=> []

  class Not < Matcher

    ##
    # The value to negate in the match.

    attr_reader :value

    ##
    # Creates a Matcher which will match any Sexp that does not match the +value+

    def initialize value
      @value = value
    end

    def == o # :nodoc:
      super && self.value == o.value
    end

    ##
    # Satisfied if a +o+ does not match the +value+

    def satisfy? o
      !(value.kind_of?(Sexp) ? value.satisfy?(o) : value == o)
    end

    def inspect # :nodoc:
      "not?(%p)" % [value]
    end

    def pretty_print q # :nodoc:
      q.group 1, "not?(", ")" do
        q.pp value
      end
    end
  end

  ##
  # Matches anything that has a child matching the sub-expression
  #
  # example:
  #
  #   s(s(s(s(s(:a))))) / s{ child(s(:a)) } #=> [s(s(s(s(s(:a))))),
  #                                              s(s(s(s(:a)))),
  #                                              s(s(s(:a))),
  #                                              s(s(:a)),
  #                                              s(:a)]

  class Child < Matcher
    ##
    # The child to match.

    attr_reader :child

    ##
    # Create a Child matcher which will match anything having a
    # descendant matching +child+.

    def initialize child
      @child = child
    end

    ##
    # Satisfied if matches +child+ or +o+ has a descendant matching
    # +child+.

    def satisfy? o
      if child.satisfy? o
        true
      elsif o.kind_of? Sexp
        o.search_each(child).any?
      end
    end

    def == o # :nodoc:
      super && self.child == o.child
    end

    def inspect # :nodoc:
      "child(%p)" % [child]
    end

    def pretty_print q # :nodoc:
      q.group 1, "child(", ")" do
        q.pp child
      end
    end
  end

  ##
  # Matches any atom (non-Sexp).
  #
  # examples:
  #
  #   s(:a)        / s{ s(atom) } #=> [s(:a)]
  #   s(:a, s(:b)) / s{ s(atom) } #=> [s(:b)]

  class Atom < Matcher
    ##
    # Satisfied when +o+ is an atom.

    def satisfy? o
      !(o.kind_of? Sexp)
    end

    def inspect #:nodoc:
      "atom"
    end

    def pretty_print q # :nodoc:
      q.text "atom"
    end
  end

  ##
  # Matches any atom who's string representation matches the patterns
  # passed in.
  #
  # examples:
  #
  #   s(:a) / s{ m('a') }                                      #=> [s(:a)]
  #   s(:a) / s{ m(/\w/,/\d/) }                                #=> [s(:a)]
  #   s(:tests, s(s(:test_a), s(:test_b))) / s{ m(/test_\w/) } #=> [s(:test_a),
  #
  # TODO: maybe don't require non-sexps? This does respond to =~ now.

  class Pattern < Matcher

    ##
    # The regexp to match for the pattern.

    attr_reader :pattern

    def == o # :nodoc:
      super && self.pattern == o.pattern
    end

    ##
    # Create a Patten matcher which will match any atom that either
    # matches the input +pattern+.

    def initialize pattern
      @pattern = pattern
    end

    ##
    # Satisfied if +o+ is an atom, and +o+ matches +pattern+

    def satisfy? o
      !o.kind_of?(Sexp) && o.to_s =~ pattern # TODO: question to_s
    end

    def inspect # :nodoc:
      "m(%p)" % pattern
    end

    def pretty_print q # :nodoc:
      q.group 1, "m(", ")" do
        q.pp pattern
      end
    end
  end

  ##
  # Matches anything having the same sexp_type, which is the first
  # value in a Sexp.
  #
  # examples:
  #
  #   s(:a, :b) / s{ t(:a) }        #=> [s(:a, :b)]
  #   s(:a, :b) / s{ t(:b) }        #=> []
  #   s(:a, s(:b, :c)) / s{ t(:b) } #=> [s(:b, :c)]

  class Type < Matcher
    attr_reader :sexp_type

    ##
    # Creates a Matcher which will match any Sexp who's type is +type+, where a type is
    # the first element in the Sexp.

    def initialize type
      @sexp_type = type
    end

    def == o # :nodoc:
      super && self.sexp_type == o.sexp_type
    end

    ##
    # Satisfied if the sexp_type of +o+ is +type+.

    def satisfy? o
      o.kind_of?(Sexp) && o.sexp_type == sexp_type
    end

    def inspect # :nodoc:
      "t(%p)" % sexp_type
    end

    def pretty_print q # :nodoc:
      q.group 1, "t(", ")" do
        q.pp sexp_type
      end
    end
  end

  ##
  # Matches an expression or any expression that includes the child.
  #
  # examples:
  #
  #   s(:a, :b)   / s{ include(:b) } #=> [s(:a, :b)]
  #   s(s(s(:a))) / s{ include(:a) } #=> [s(:a)]

  class Include < Matcher
    ##
    # The value that should be included in the match.

    attr_reader :value

    ##
    # Creates a Matcher which will match any Sexp that contains the
    # +value+.

    def initialize value
      @value = value
    end

    ##
    # Satisfied if a +o+ is a Sexp and one of +o+'s elements matches
    # value

    def satisfy? o
      Sexp === o && o.any? { |c|
        # TODO: switch to respond_to??
        Sexp === value ? value.satisfy?(c) : value == c
      }
    end

    def == o # :nodoc:
      super && self.value == o.value
    end

    def inspect # :nodoc:
      "include(%p)" % [value]
    end

    def pretty_print q # :nodoc:
      q.group 1, "include(", ")" do
        q.pp value
      end
    end
  end

  ##
  # See Matcher for sibling relations: <,<<,>>,>

  class Sibling < Matcher

    ##
    # The LHS of the matcher.

    attr_reader :subject

    ##
    # The RHS of the matcher.

    attr_reader :sibling

    ##
    # An optional distance requirement for the matcher.

    attr_reader :distance

    ##
    # Creates a Matcher which will match any pair of Sexps that are siblings.
    # Defaults to matching the immediate following sibling.

    def initialize subject, sibling, distance = nil
      @subject = subject
      @sibling = sibling
      @distance = distance
    end

    ##
    # Satisfied if o contains +subject+ followed by +sibling+

    def satisfy? o
      # Future optimizations:
      # * Shortcut matching sibling
      subject_matches = index_matches(subject, o)
      return nil if subject_matches.empty?

      sibling_matches = index_matches(sibling, o)
      return nil if sibling_matches.empty?

      subject_matches.any? { |i1, _data_1|
        sibling_matches.any? { |i2, _data_2|
          distance ? (i2-i1 == distance) : i2 > i1
        }
      }
    end

    def == o # :nodoc:
      super &&
        self.subject  == o.subject &&
        self.sibling  == o.sibling &&
        self.distance == o.distance
    end

    def inspect # :nodoc:
      "%p >> %p" % [subject, sibling]
    end

    def pretty_print q # :nodoc:
      if distance then
        q.group 1, "sibling(", ")" do
          q.seplist [subject, sibling, distance] do |v|
            q.pp v
          end
        end
      else
        q.group 1 do
          q.pp subject
          q.text " >> "
          q.pp sibling
        end
      end
    end

    private

    def index_matches pattern, o
      indexes = []
      return indexes unless o.kind_of? Sexp

      o.each_with_index do |e, i|
        data = {}
        if pattern.kind_of?(Sexp) ? pattern.satisfy?(e) : pattern == o[i]
          indexes << [i, data]
        end
      end

      indexes
    end
  end # class Sibling

  ##
  # Wraps the results of a Sexp query. MatchCollection defines
  # MatchCollection#/ so that you can chain queries.
  #
  # For instance:
  #   res = s(:a, s(:b)) / s{ s(:a,_) } / s{ s(:b) }

  class MatchCollection < Array
    ##
    # See Traverse#search

    def / pattern
      inject(self.class.new) { |result, match|
        result.concat match / pattern
      }
    end

    def inspect # :nodoc:
      "MatchCollection.new(%s)" % self.to_a.inspect[1..-2]
    end

    alias :to_s :inspect # :nodoc:

    def pretty_print q # :nodoc:
      q.group 1, "MatchCollection.new(", ")" do
        q.seplist(self) {|v| q.pp v }
      end
    end
  end # class MatchCollection
end

require "strict_sexp" if ENV["STRICT_SEXP"].to_i > 0
