続き

昨日のは「まだマッチしていない」時と「マッチしなかった」時を区別できていなかった。
修正。

ParseResult = Struct.new(:value,:deriv)

class Deriv
  def Deriv.eval(str)
    d = Deriv.new(str)
    if pr = d.parse_add then
      pr.value
    else
      nil
    end
  end

  def initialize(rest_str)
    @add_value  = nil
    @mult_value = nil
    @prim_value = nil
    @dec_value  = nil
    if rest_str.empty? then
      @char_value = nil
    else
      @char_value = ParseResult.new(rest_str[0],Deriv.new(rest_str[1..-1]))
    end
  end

  def match_sequence(*items)
    if items.empty? then
      res = nil
      if block_given? then
        res = yield
      end
      return ParseResult.new(res,deriv)
    end

    results = []
    cur_deriv = self

    items.each{| item |
      pr = nil
      if item.kind_of?(Symbol) then
        pr = cur_deriv.send("parse_#{item}")
        return false unless pr
      else
        pr = cur_deriv.parse_char
        return false unless pr and item === pr.value 
      end
      results.push(pr.value)
      cur_deriv = pr.deriv
    }

    res = nil
    if block_given? then
      res = yield(*results)
    else
      res = results.first
    end

    ParseResult.new(res, cur_deriv)
  end
  private :match_sequence


  def parse_add
    return @add_value unless @add_value.nil?
    @add_value = 
      match_sequence(:mult,?+,:add){|x,_,y| x + y } ||
        match_sequence(:mult)
  end

  def parse_mult
    return @mult_value unless @mult_value.nil?
    @mult_value =
      match_sequence(:prim,?*,:mult){|x,_,y| x * y} ||
        match_sequence(:prim)
  end

  def parse_prim
    return @prim_value unless @prim_value.nil?
    @prim_value =
      match_sequence(?(,:add,?)){|_,x,_| x } ||
        match_sequence(:dec)
  end

  def parse_dec
    return @dec_value unless @dec_value.nil?
    @dec_value =
      match_sequence(?0..?9){|x| x - ?0 }
  end

  def parse_char
    @char_value
  end
end

def test_expr(expr)
  if Deriv.eval(expr) == eval(expr) then
    puts "ok"
  else
    puts "failed : #{expr}"
  end
end

test_expr('2*3+(4+5)*2')
test_expr('2+3+4*5')
test_expr('3*7+5+3')