require 'strscan'
module Web
  module CSS
    class Scanner
      H          = "[0-9a-f]"
      NONASCII   = "[\302\240-\364\217\277\277]"
      UNICODE    = "\\\\#{H}{1,6}[ \\t\\r\\n\\f]?"
      ESCAPE     = "(?:#{UNICODE}|\\\\[ -~\302\240-\364\217\277\277])"
      NMSTART    = "(?:[a-z]|#{NONASCII}|#{ESCAPE})"
      NMCHAR     = "(?:[a-z0-9\-]|#{NONASCII}|#{ESCAPE})"
      NL         = "(?:\\n|\\r\\n|\\r|\\f)"
      STRING1    = "(?:\"([\\t !\#$%&(-~]|#{NL}|\'|#{NONASCII}|#{ESCAPE})*\")"
      STRING2    = "(?:\'([\\t !\#$%&(-~]|#{NL}|\"|#{NONASCII}|#{ESCAPE})*\')"
      IDENT      = "#{NMSTART}#{NMCHAR}*"
      NAME       = "#{NMCHAR}+"
      NUM        = "(?:[0-9]+|[0-9]*\.[0-9]+)"
      STRING     = "(?:#{STRING1}|#{STRING2})"
      URL        = "(?:[!\#$%&*-~]|#{NONASCII}|#{ESCAPE})*"
      W          = "[ \\t\\r\\n\\f]*"
      RANGE      = "\\?{1,6}|#{H}(\\?{0,5}|#{H}(\\?{0,4}|#{H}(\\?{0,3}|#{H}(\\?{0,2}|#{H}(\\?\\?|#{H})))))"
      def initialize(io,rs=$/)
        @__io          =  io              # IO
        @__rs          = rs               # Record Separator
        @__scan   = StringScanner.new('') # StringScanner(strscan)
        @__line_no     = 0                # Line Number
        @__queue       = []               # Token Queue
        @__state       = :default        # Scanner State
        @__state_stack = []               # State Stack(CALL/RETURN)
        init if respond_to? :init
      end
      
      def rest?
        if @__io.eof?
          @__scan.rest?
        else
          true
        end
      end
      
      #
      def shift
        until token = @__queue.shift
          return nil unless rest?
          if @__scan.empty?
            @__line_no += 1
            @__scan.string = @__io.gets(@__rs)
          end
          @__pos = @__scan.pointer
          send @__state
        end
        return token
      end
      
      def unshift(token)
        @__queue.unshift token
      end
      
      def push(token)
        @__queue.push token
      end
      
      def pop
        @__queue.pop
      end
      
      def token
        shift
      end
      
      def call_state(state)
        @__state_stack.push @__state
        @__state = state
      end
      
      def return_state
        @__state = @__state_stack.pop
      end
      
      def transit_state(state)
        @__state = state
      end
      
      def scan(regex)
        @__scan.scan(regex)
      end
      
      def scan_until(regex)
        @__scan.scan_until(regex)
      end
      
      def matched
        @__scan.matched
      end
      
      def unscan
        @__scan.unscan
      end
      
      def ungets(str)
        @__scan.string = str + @__scan.rest
      end
      
      def [](n)
        @__scan[n]
      end
      
      def aref(n)
        @__scan[n]
      end
      
      def pos
        @__pos
      end
      
      def line_no
        @__line_no
      end
      
      #<Rules>
def default
  if scan(/[ \t\r\n\f]+/ui)
          push [:S,matched]

  elsif scan(/\/\*/ui)
          transit_state :comment
  elsif scan(/<!--/ui)
          push [:CDO,matched]
  elsif scan(/-->/ui)
          push [:CDC,matched]
  elsif scan(/~=/ui)
          push [:INCLUDES,matched]
  elsif scan(/\|=/ui)
          push [:DASHMATCH,matched]
  elsif scan(/#{STRING}/ui)
          push [:STRING,matched]
  elsif scan(/url\(#{W}(#{STRING})#{W}\)/ui)
          push [:URI,matched]
  elsif scan(/url\(#{W}(#{URL})#{W}\)/ui)
          push [:URI,matched]
  elsif scan(/#{IDENT}/ui)
          push [:IDENT,matched]
  elsif scan(/\##{NAME}/ui)
          push [:HASH,matched]
  elsif scan(/@#{IDENT}/ui)
          case matched.downcase
          when "@import"
            push [:IMPORT_SYM,matched]
          when  "@page"
            push [:PAGE_SYM,matched]
          when  "@media"
            push [:MEDIA_SYM,matched]
          when "@font-face"
            push [:FONT_FACE_SYM,matched]
          when "@charset"
            push [:CHARSET_SYM,matched]
          else
            push [:ATKEYWORD,matched]
          end
  elsif scan(/!#{W}important/ui)
          push [:IMPORTANT_SYM,matched]
  elsif scan(/(#{NUM})em/ui)
          push [:EMS,matched]
  elsif scan(/(#{NUM})ex/ui)
          push [:EXS,matched]
  elsif scan(/(#{NUM})px/ui)
          push [:LENGTH,matched]
  elsif scan(/(#{NUM})cm/ui)
          push [:LENGTH,matched]
  elsif scan(/(#{NUM})mm/ui)
          push [:LENGTH,matched]
  elsif scan(/(#{NUM})in/ui)
          push [:LENGTH,matched]
  elsif scan(/(#{NUM})pt/ui)
          push [:LENGTH,matched]
  elsif scan(/(#{NUM})pc/ui)
          push [:LENGTH,matched]
  elsif scan(/(#{NUM})deg/ui)
          push [:ANGLE,matched]
  elsif scan(/(#{NUM})rad/ui)
          push [:ANGLE,matched]
  elsif scan(/(#{NUM})grad/ui)
          push [:ANGLE,matched]
  elsif scan(/(#{NUM})ms/ui)
          push [:TIME,matched]
  elsif scan(/(#{NUM})s/ui)
          push [:TIME,matched]
  elsif scan(/(#{NUM})Hz/ui)
          push [:FREQ,matched]
  elsif scan(/(#{NUM})kHz/ui)
          push [:FREQ,matched]
  elsif scan(/(#{NUM})(#{IDENT})/ui)
          push [:DIMEN,matched]
  elsif scan(/(#{NUM})%/ui)
          push [:PERCENTAGE,matched]
  elsif scan(/(#{NUM})/ui)
          push [:NUMBER,matched]
  elsif scan(/(#{IDENT})\(/ui)
          push [:FUNCTION,matched]
  elsif scan(/U\+#{RANGE}/ui)
          push [:UNICODERANGE,matched]
  elsif scan(/U\+#{H}{1,6}-#{H}{1,6}/ui)
          push [:UNICODERANGE,matched]
  elsif scan(/#{IDENT}/ui)
          push [:IDENT,matched]
  elsif scan(/./ui)
          push [matched.intern,matched]

  else
    raise
  end
end
def comment
  if scan(/[^*]/ui)
          #
  elsif scan(/\*\//ui)
          transit_state :default
  elsif scan(/./ui)
          #
  else
    raise
  end
end
      #</Rules>
     end # Scanner
  end # CSS
end # Web
if $0==__FILE__
  scan = Web::CSS::Scanner.new(ARGF)
  while token=scan.token
    p token
  end
end
