# encoding: utf-8

require 'antelope/grammar/token/nonterminal'
require 'antelope/grammar/token/terminal'
require 'antelope/grammar/token/epsilon'
require 'antelope/grammar/token/error'

module Antelope
  class Grammar
    # Defines a token type for productions/rules.
    #
    # @abstract This class should be inherited to define a real token.
    #   A base class does not match any token; however, any token can
    #   match the base class.
    class Token
      # The name of the token.
      #
      # @return [Symbol]
      attr_reader :name

      # The from state that this token is transitioned from.  This is
      # the _source_.  This is used in the constructor in order to
      # handle lookahead sets.
      #
      # @return [Recognizer::State]
      attr_reader :from

      # The to state that this token is transitioned to.  This is the
      # _destination_.  This is used in the constructor in order to
      # handle lookahead sets.
      #
      # @return [Recognizer::State]
      attr_reader :to

      # The type of the token.  This is given by a caret argument to
      # the grammar.  This is primarily used for generators.
      #
      # @return [String]
      attr_reader :type

      attr_accessor :id

      # Initialize.
      #
      # @param name [Symbol] the name of the token.
      # @param type [String?] the type of the token.  For definitions,
      #   this is the given type of the token (for typed language
      #   output).
      # @param id [String?] the id of the token in the production.
      #   For some languages, this allows references to the token via
      #   the id.
      # @param value [String?] the value of the token.  This is only
      #   used in output representation to the developer.
      def initialize(name, type = nil, id = nil, value = nil)
        @name  = name
        @value = value
        @type  = type
        @id    = id
        @from  = nil
        @to    = nil
      end

      include Comparable

      # Whether or not the token is a terminal.
      #
      # @abstract
      # @return [Boolean]
      def terminal?
        false
      end

      # Whether or not the token is a nonterminal.
      #
      # @abstract
      # @return [Boolean]
      def nonterminal?
        false
      end

      # Whether or not the token is an epsilon token.
      #
      # @abstract
      # @return [Boolean]
      def epsilon?
        false
      end

      # Whether or not the token is an error token.
      #
      # @abstract
      # @return [Boolean]
      def error?
        false
      end

      # Sets the from state of the token and invalidates the cache.
      #
      # @param state [Recognizer::State]
      # @return [void]
      def from=(state)
        invalidate_cache!
        @from = state
      end

      # Sets the to state of the token and invalidates the cache.
      #
      # @param state [Recognizer::State]
      # @return [void]
      def to=(state)
        invalidate_cache!
        @to = state
      end

      # Sets the type of the token and invalidates the cache.
      #
      # @param type [String]
      # @return [void]
      def type=(type)
        invalidate_cache!
        @type = type
      end

      # Gives a string representation of the token.  The output is
      # formatted like so: `<data>["(" [<from_id>][:<to_id>] ")"]`,
      # where `<data>` is either the value (if it's non-nil) or the
      # name, `<from_id>` is the from state id, and `<to_id>` is the
      # to state id.  The last part of the format is optional; if
      # neither the from state or to state is non-nil, it's non-
      # existant.
      #
      # @return [String] the string representation.
      # @see #from
      # @see #to
      # @see #name
      def to_s
        buf = if @value
                @value.inspect
              else
                @name.to_s
              end

        if from || to
          buf << '('
          buf << "#{from.id}" if from
          buf << ":#{to.id}"  if to
          buf << ')'
        end

        buf
      end

      # Returns a nice inspect.
      #
      # @return [String]
      def inspect
        "#<#{self.class} from=#{from.id if from} to=#{to.id if to} " \
          "name=#{name.inspect} value=#{@value.inspect}>"
      end

      # Compares this class to any other object.  If the other object
      # is a token, it converts both this class and the other object
      # to an array and compares the array.  Otherwise, it delegates
      # the comparison.
      #
      # @param other [Object] the other object to compare.
      # @return [Numeric]
      def <=>(other)
        if other.is_a? Token
          to_a <=> other.to_a
        else
          super
        end
      end

      alias_method :eql?, :==

      # Compares this class and another object, fuzzily.  If the other
      # object is a token, it removes the transitions (to and from)
      # on both objects and compares them like that.  Otherwise, it
      # delegates the comparison.
      #
      # @param other [Object] the other object to compare.
      # @return [Boolean] if they are equal.
      def ===(other)
        if other.is_a? Token
          without_transitions == other.without_transitions
        else
          super
        end
      end

      # Creates a new token without to or from states.
      #
      # @return [Token]
      def without_transitions
        self.class.new(name, @type, @id, @value)
      end

      # Invalidates the cache.
      #
      # @return [void]
      def invalidate_cache!
        @_hash = nil
        @_array = nil
      end

      # Generates a hash for this class.
      #
      # @note This is not intended for use.  It is only defined to be
      #   compatible with Hashs (and by extension, Sets).
      # @private
      # @return [Object]
      def hash
        @_hash ||= to_a.hash
      end

      alias_method :eql?, :==

      # Creates an array representation of this class.
      #
      # @note This is not intended for use.  It is only defined to
      #   make equality checking easier, and to create a hash.
      # @private
      # @return [Array<(Recognizer::State, Recognizer::State, Class, Symbol, String?)>]
      def to_a
        @_array ||= [to, from, self.class, name, @value]
      end
    end
  end
end