# The <tt>SQLTree::Token</tt> class is the base class for every token
# in the SQL language. Actual tokens are represented by a subclass.
#
# Tokens are produced by the <tt>SQLTree::Tokenizer</tt> from a string
# and are consumed by the <tt>SQLTree::Parser</tt> to construct an
# abstract syntax tree for the query that is being parsed.
class SQLTree::Token

  # For some tokens, the encountered literal value is important
  # during the parsing phase (e.g. strings and variable names).
  # Therefore, the literal value encountered that represented the
  # token in the original SQL query string is stored.
  attr_accessor :literal

  # Creates a token instance with a given literal representation.
  #
  # <tt>literal<tt>:: The literal string value that was encountered
  #                   while tokenizing.
  def initialize(literal)
    @literal = literal
  end

  # Compares two tokens. Tokens are considered equal when they are
  # instances of the same class, i.e. do literal is not used.
  def ==(other)
    other.class == self.class
  end

  def inspect # :nodoc:
    literal
  end

  def join?
    [SQLTree::Token::JOIN, SQLTree::Token::LEFT, SQLTree::Token::RIGHT,
      SQLTree::Token::INNER, SQLTree::Token::OUTER, SQLTree::Token::NATURAL,
      SQLTree::Token::FULL].include?(self)
  end

  def direction?
    [SQLTree::Token::ASC, SQLTree::Token::DESC].include?(self)
  end

  ###################################################################
  # DYNAMIC TOKEN TYPES
  ###################################################################

  # The <tt>SQLTree::Token::Value</tt> class is the base class for
  # every dynamic token. A dynamic token is a token for which the
  # literal value used remains impoirtant during parsing.
  class Value < SQLTree::Token

    def inspect # :nodoc:
      "#<#{self.class.name.split('::').last}:#{literal.inspect}>"
    end

    # Compares two tokens. For values, the literal encountered value
    # of the token is also taken into account besides the class.
    def ==(other)
      other.class == self.class && @literal == other.literal
    end
  end

  # The <tt>SQLTree::Token::Variable</tt> class represents SQL
  # variables. The variable name is stored in the literal as string,
  # without quotes if they were present.
  class Variable < SQLTree::Token::Value
  end

  # The <tt>SQLTree::Token::String</tt> class represents strings.
  # The actual string is stored in the literal as string without quotes.
  class String < SQLTree::Token::Value
  end

  # The <tt>SQLTree::Token::Keyword</tt> class represents numbers.
  # The actual number is stored as an integer or float in the token's
  # literal.
  class Number < SQLTree::Token::Value
  end

  ###################################################################
  # STATIC TOKEN TYPES
  ###################################################################

  # The <tt>SQLTree::Token::Keyword</tt> class represents reserved SQL
  # keywords. These keywords are used to structure the query. Keywords
  # are static, i.e. the literal value is not important during the
  # parsing process.
  class Keyword < SQLTree::Token
    def inspect # :nodoc:
      ":#{literal.gsub(/ /, '_').downcase}"
    end
  end

  # The <tt>SQLTree::Token::Operator</tt> class represents logical and
  # arithmetic operators in SQL. These tokens are static, i.e. the literal
  # value is not important during the parsing process.
  class Operator < SQLTree::Token
    def inspect # :nodoc:
      OPERATORS_HASH[literal].inspect
    end
  end

  ###################################################################
  # STATIC TOKEN CONSTANTS
  ###################################################################

  # Create some static token classes and a single instance of them
  LPAREN = Class.new(SQLTree::Token).new('(')
  RPAREN = Class.new(SQLTree::Token).new(')')
  DOT    = Class.new(SQLTree::Token).new('.')
  COMMA  = Class.new(SQLTree::Token).new(',')

  # A list of all the SQL reserverd keywords.
  KEYWORDS = %w{SELECT FROM WHERE GROUP HAVING ORDER DISTINCT LEFT RIGHT INNER FULL OUTER NATURAL JOIN USING
                AND OR NOT AS ON IS NULL BY LIKE ILIKE BETWEEN IN ASC DESC}

  # Create a token for all the reserved keywords in SQL
  KEYWORDS.each { |kw| const_set(kw, Class.new(SQLTree::Token::Keyword).new(kw)) }

  # A list of keywords that aways occur in fixed combinations. Register these as separate keywords.
  KEYWORD_COMBINATIONS = [%w{IS NOT}, %w{NOT LIKE}, %w{NOT BETWEEN}, %w{NOT ILIKE}]
  KEYWORD_COMBINATIONS.each { |kw| const_set(kw.join('_'), Class.new(SQLTree::Token::Keyword).new(kw.join(' '))) }

  ARITHMETHIC_OPERATORS_HASH = { '+' => :plus, '-' => :minus, '*' => :multiply, '/' => :divide, '%' => :modulo }
  COMPARISON_OPERATORS_HASH  = { '=' => :eq, '!=' => :ne, '<>' => :ne, '>' => :gt, '<' => :lt, '>=' => :gte, '<=' => :lte }

  # Register a token class and single instance for every token.
  OPERATORS_HASH = ARITHMETHIC_OPERATORS_HASH.merge(COMPARISON_OPERATORS_HASH)
  OPERATORS_HASH.each_pair do |literal, symbol|
    self.const_set(symbol.to_s.upcase, Class.new(SQLTree::Token::Operator).new(literal)) unless self.const_defined?(symbol.to_s.upcase)
  end

  COMPARISON_OPERATORS = COMPARISON_OPERATORS_HASH.map { |(literal, symbol)| const_get(symbol.to_s.upcase) } +
    [SQLTree::Token::IN, SQLTree::Token::IS, SQLTree::Token::BETWEEN, SQLTree::Token::LIKE, SQLTree::Token::ILIKE, SQLTree::Token::NOT]
end