#
#   irb/slex.rb - simple lex analyzer
#   	$Release Version: 0.9.5$
#   	$Revision: 16857 $
#   	$Date: 2008-06-06 17:05:24 +0900 (Fri, 06 Jun 2008) $
#   	by Keiju ISHITSUKA(keiju@ruby-lang.org)
#
# --
#
#   
#

require "e2mmap"
require "irb/notifier"

module IRB
  class SLex
    @RCS_ID='-$Id: slex.rb 16857 2008-06-06 08:05:24Z knu $-'

    extend Exception2MessageMapper
    def_exception :ErrNodeNothing, "node nothing"
    def_exception :ErrNodeAlreadyExists, "node already exists"

    DOUT = Notifier::def_notifier("SLex::")
    D_WARN = DOUT::def_notifier(1, "Warn: ")
    D_DEBUG = DOUT::def_notifier(2, "Debug: ")
    D_DETAIL = DOUT::def_notifier(4, "Detail: ")
    
    DOUT.level = Notifier::D_NOMSG

    def initialize
      @head = Node.new("")
    end
    
    def def_rule(token, preproc = nil, postproc = nil, &block)
      D_DETAIL.pp token

      postproc = block if block_given?
      node = create(token, preproc, postproc)
    end
    
    def def_rules(*tokens, &block)
      if block_given?
	p = block
      end
      for token in tokens
	def_rule(token, nil, p)
      end
    end
    
    def preproc(token, proc)
      node = search(token)
      node.preproc=proc
    end
    
    #要チェック? 
    def postproc(token)
      node = search(token, proc)
      node.postproc=proc
    end
    
    def search(token)
      @head.search(token.split(//))
    end

    def create(token, preproc = nil, postproc = nil)
      @head.create_subnode(token.split(//), preproc, postproc)
    end
    
    def match(token)
      case token
      when Array
      when String
	return match(token.split(//))
      else
	return @head.match_io(token)
      end
      ret = @head.match(token)
      D_DETAIL.exec_if{D_DEATIL.printf "match end: %s:%s\n", ret, token.inspect}
      ret
    end
    
    def inspect
      format("<SLex: @head = %s>", @head.inspect)
    end

    #----------------------------------------------------------------------
    #
    #   class Node - 
    #
    #----------------------------------------------------------------------
    class Node
      # if postproc is nil, this node is an abstract node.
      # if postproc is non-nil, this node is a real node.
      def initialize(preproc = nil, postproc = nil)
	@Tree = {}
	@preproc = preproc
	@postproc = postproc
      end

      attr_accessor :preproc
      attr_accessor :postproc
      
      def search(chrs, opt = nil)
	return self if chrs.empty?
	ch = chrs.shift
	if node = @Tree[ch]
	  node.search(chrs, opt)
	else
	  if opt
	    chrs.unshift ch
	    self.create_subnode(chrs)
	  else
	    SLex.fail ErrNodeNothing
	  end
	end
      end
      
      def create_subnode(chrs, preproc = nil, postproc = nil)
	if chrs.empty?
	  if @postproc
	    D_DETAIL.pp node
	    SLex.fail ErrNodeAlreadyExists
	  else
	    D_DEBUG.puts "change abstract node to real node."
	    @preproc = preproc
	    @postproc = postproc
	  end
	  return self
	end
	
	ch = chrs.shift
	if node = @Tree[ch]
	  if chrs.empty?
	    if node.postproc
	      DebugLogger.pp node
	      DebugLogger.pp self
	      DebugLogger.pp ch
	      DebugLogger.pp chrs
	      SLex.fail ErrNodeAlreadyExists
	    else
	      D_WARN.puts "change abstract node to real node"
	      node.preproc = preproc
	      node.postproc = postproc
	    end
	  else
	    node.create_subnode(chrs, preproc, postproc)
	  end
	else
	  if chrs.empty?
	    node = Node.new(preproc, postproc)
	  else
	    node = Node.new
	    node.create_subnode(chrs, preproc, postproc)
	  end
	  @Tree[ch] = node
	end
	node
      end

      #
      # chrs: String
      #       character array
      #       io must have getc()/ungetc(); and ungetc() must be
      #       able to be called arbitrary number of times. 
      #
      def match(chrs, op = "")
	D_DETAIL.print "match>: ", chrs, "op:", op, "\n"
	if chrs.empty?
	  if @preproc.nil? || @preproc.call(op, chrs)
	    DOUT.printf(D_DETAIL, "op1: %s\n", op)
	    @postproc.call(op, chrs)
	  else
	    nil
	  end
	else
	  ch = chrs.shift
	  if node = @Tree[ch]
	    if ret = node.match(chrs, op+ch)
	      return ret
	    else
	      chrs.unshift ch
	      if @postproc and @preproc.nil? || @preproc.call(op, chrs)
		DOUT.printf(D_DETAIL, "op2: %s\n", op.inspect)
		ret = @postproc.call(op, chrs)
		return ret
	      else
		return nil
	      end
	    end
	  else
	    chrs.unshift ch
	    if @postproc and @preproc.nil? || @preproc.call(op, chrs)
	      DOUT.printf(D_DETAIL, "op3: %s\n", op)
	      @postproc.call(op, chrs)
	      return ""
	    else
	      return nil
	    end
	  end
	end
      end

      def match_io(io, op = "")
	if op == ""
	  ch = io.getc
	  if ch == nil
	    return nil
	  end
	else
	  ch = io.getc_of_rests
	end
	if ch.nil?
	  if @preproc.nil? || @preproc.call(op, io)
	    D_DETAIL.printf("op1: %s\n", op)
	    @postproc.call(op, io)
	  else
	    nil
	  end
	else
	  if node = @Tree[ch]
	    if ret = node.match_io(io, op+ch)
	      ret
	    else
	      io.ungetc ch
	      if @postproc and @preproc.nil? || @preproc.call(op, io)
		DOUT.exec_if{D_DETAIL.printf "op2: %s\n", op.inspect}
		@postproc.call(op, io)
	      else
		nil
	      end
	    end
	  else
	    io.ungetc ch
	    if @postproc and @preproc.nil? || @preproc.call(op, io)
	      D_DETAIL.printf("op3: %s\n", op)
	      @postproc.call(op, io)
	    else
	      nil
	    end
	  end
	end
      end
    end
  end
end

SLex=IRB::SLex

if $0 == __FILE__
  #    Tracer.on
  case $1
  when "1"
    tr = SLex.new
    print "0: ", tr.inspect, "\n"
    tr.def_rule("=") {print "=\n"}
    print "1: ", tr.inspect, "\n"
    tr.def_rule("==") {print "==\n"}
    print "2: ", tr.inspect, "\n"
    
    print "case 1:\n"
    print tr.match("="), "\n"
    print "case 2:\n"
    print tr.match("=="), "\n"
    print "case 3:\n"
    print tr.match("=>"), "\n"
    
  when "2"
    tr = SLex.new
    print "0: ", tr.inspect, "\n"
    tr.def_rule("=") {print "=\n"}
    print "1: ", tr.inspect, "\n"
    tr.def_rule("==", proc{false}) {print "==\n"}
    print "2: ", tr.inspect, "\n"
    
    print "case 1:\n"
    print tr.match("="), "\n"
    print "case 2:\n"
    print tr.match("=="), "\n"
    print "case 3:\n"
    print tr.match("=>"), "\n"
  end
  exit
end

