#
#   irb/slex.rb - symple lex analizer
#   	$Release Version: 0.9$
#   	$Revision: 1.1.1.2 $
#   	$Date: 2003/10/15 10:11:49 $
#   	by Keiju ISHITSUKA(keiju@ishituska.com)
#
# --
#
#   
#

require "e2mmap"

class SLex
  @RCS_ID='-$Id: slex.rb,v 1.1.1.2 2003/10/15 10:11:49 melville Exp $-'

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

  class << self
    attr_accessor :debug_level
    def debug?
      debug_level > 0
    end
  end
  @debug_level = 0

  def initialize
    @head = Node.new("")
  end
  
  def def_rule(token, preproc = nil, postproc = nil, &block)
    #      print node.inspect, "\n" if SLex.debug?
    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)
    printf "match end: %s:%s", ret, token.inspect if SLex.debug?
    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
	  p node
	  SLex.fail ErrNodeAlreadyExists
	else
	  print "Warn: change abstruct node to real node\n" if SLex.debug?
	  @preproc = preproc
	  @postproc = postproc
	end
	return self
      end
      
      ch = chrs.shift
      if node = @Tree[ch]
	if chrs.empty?
	  if node.postproc
	    p node
	    p self
	    p ch
	    p chrs
	    SLex.fail ErrNodeAlreadyExists
	  else
	    print "Warn: change abstruct node to real node\n" if SLex.debug?
	    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 = "")
      print "match>: ", chrs, "op:", op, "\n" if SLex.debug?
      if chrs.empty?
	if @preproc.nil? || @preproc.call(op, chrs)
	  printf "op1: %s\n", op if SLex.debug?
	  @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)
	      printf "op2: %s\n", op.inspect if SLex.debug?
	      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)
	    printf "op3: %s\n", op if SLex.debug?
	    @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)
	  printf "op1: %s\n", op if SLex.debug?
	  @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)
	      printf "op2: %s\n", op.inspect if SLex.debug?
	      @postproc.call(op, io)
	    else
	      nil
	    end
	  end
	else
	  io.ungetc ch
	  if @postproc and @preproc.nil? || @preproc.call(op, io)
	    printf "op3: %s\n", op if SLex.debug?
	    @postproc.call(op, io)
	  else
	    nil
	  end
	end
      end
    end
  end
end

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
