# Algebraic Parser
#
#   by Shin-ichiro Hara
#
# Version 1.1 (2001.04.08)

require 'numeric-supplement'

class AlgebraicParser
  def self.eval(str, klass)#, coeff = Numeric)
    new(str, klass).compile
  end

  Var = '[a-zA-Z]\d*'
  Num = '\d+(?:\.\d*)?'
  Op = '(?:\*\*|[+\-*\/^])'
  Gr = '[\(\)]'
  Dlm = ';'

  def initialize(str, klass)
    @klass = klass
    @coeff = klass.ground
    @s = str.gsub(/\s+/, '').scan(/#{Dlm}|#{Num}|#{Op}|#{Gr}|#{Var}|.+/o)
    @basis = [] # stack for the basis of the power
  end
  def compile; sentence; end

  private
  def head; @s[0]; end

  def succ(x = nil)
    raise "Error: receive #{x} expect #{head}" if x && x != head
    @s.shift
  end

  def sentence
    e = expr
    while /^#{Dlm}$/ =~ head
      succ(";")
      e = expr
    end
    e
  end

  def expr
    e = expr1
    while /^[+-]$/ =~ head
      case succ
      when "+"
	e += expr1
      when "-"
	e -= expr1
      end
    end
    e
  end

  def expr1
    case head
    when "+"; succ; term
    when "-"; succ; - term
    else term
    end
  end
  
  def term
    e = sequence
    while /^[*\/]$/ =~ head #/
      case succ
      when "*"
	e = e * sequence
      when "/"
	e /= sequence
      end
    end
    e
  end
  
  def sequence
    e = power
    while head && head !~ /^#{Dlm}$|^#{Op}$|^#{Gr}$/o
      e *= power
    end 
    e
  end

  def power
    @basis.push(k = factor)
    n = 0
    while /^(?:\*\*)$|^\^$/ =~ head
      succ
      @basis.push(factor)
      n += 1
    end
    f = @basis.pop
    while n > 0
      f = (@basis.pop) ** f
      n -= 1
    end
    f
  end

  def factor
    case head
    when "("; succ; e = sentence; succ(")"); e
    when /^#{Num}$/o
      @basis.empty? ? @coeff.indeterminate(succ) : eval(succ)
    when /^#{Var}$/o; @klass.indeterminate(succ)
    else
      raise "unknown token '#{head}'"
    end
  end
end

if $0 == __FILE__
  class A
    def self.indeterminate(str)
      case str
      when "x"; 7
      when "y"; 11
      when /\d+/; eval($&)
      else
	raise
      end
    end
    def self.ground
      Fixnum
    end
  end


  p  AlgebraicParser.eval("x * y - x^2 + x/8", A) #=> 7*11 - 7**2 + 7/8 = 28

  require "rational"

  class B < A
    def self.indeterminate(str)
      case str
      when /\d+/; Rational(eval($&))
      else
	super
      end
    end
  end
  puts  AlgebraicParser.eval("x * y - x^2 + x/8", B)
                                          #=> 7*11 - 7**2 + 7/8 = 231/8
  p  AlgebraicParser.eval("2^(-8+4-2+(3-1)^3)^2", Integer) #=> 16

  require "m-polynomial"
  F = MPolynomial(Rational)
  p  AlgebraicParser.eval("- (2*y)**3 + x", F) #=> -8y^3 + x
  F.variables.clear
  p  AlgebraicParser.eval("x; y; - (2*y)**3 + x", F) #=> x - 8y^3
end
