require 'token'
require 'stringio'

module LLIP
  
  #It's the base class of all the scanners. It handles common issues like read a multibyte char or setting up correctly an IO to read from.
  #This class is +Abstract+ because it lacks the +next+ method, which a subclass must add.
  class AbstractScanner
    
    # It contains the data scanned by the scanner. It can be nil or an IO.
    # To set it, use scan.
    attr_reader :source
    
    # It contains the last token generated by the scanner
    attr_reader :current
    
    # It contains the current line of the input file.
    # It's default is -1, and it's incremented every $-0 the AbstractScanner reads.
    attr_reader :current_line
    
    # It contains the current char of the input file, it's automatically incremented for every read char.
    # It's default is -1.
    # For every $-0 read, it's re-initialized to -1.
    attr_reader :current_char
    
    def initialize(source=nil)
      @current_line = -1
      @current_char = -1
      scan(source) if source
      @current = Token.new
    end
    
    # :call-seq:
    # 	scan(IO)
    # 	scan(String)
    #
    # It initializes the scanner to scan an IO. If a String is given, it is automatically converted into a StringIO.
    # It memorize the IO in the source attribute.
    def scan(source)
      @source = source
      @source = StringIO.new(source) unless @source.respond_to? :readchar and @source.respond_to? :eof?
      @current_line += 1
      @current_char = -1
      read_next 
      self
    end
    
    #	This method is abstract and it must be implemented by a subclass. 
    # This method must read the chars to produce a Token, set it to current and return it. If a char doesn't match the first char of a token, this method should raise LLIPError. 
    # If a char matches the first char of a token, but the next ones don't, this method should raise an UnvalidTokenError.
    def next
      raise NotImplementedError.new
    end
    
    protected
    
    # It's used to recognize if a char is multibyte
    UTF8_MB_PATTERN = /[\xc0-\xdf]$|[\xe0-\xef]$|[\xe0-\xef][\x80-\xbf]$/ #it's a little hack
    
    # It reads one char from the source and memorize it in the internal variabile @next_char. It handles multibyte chars correctly.
    # If EOF is reached, it will return nil.
    def read_next
      unless @source.eof?
        @next_char = @source.readchar.chr
        while @next_char =~ UTF8_MB_PATTERN
          @next_char << @source.readchar.chr	
        end
        
        if @next_char == "\n"
          @current_line += 1
          @current_char = -1	
        else
          @current_char += 1
        end
        
        @next_char
      else
        @next_char = nil
      end
    end
    
  end
end
