逆ポーランド記法配列出力計算機: 演算子を関数に変えてパース後に計算する

class Calcp
  prechigh
    nonassoc UMINUS
    left '*' '/'
    left '+' '-'
  preclow
  options no_result_var
rule
  target: exp
        | { result = [] }

  exp: exp '+' exp { val[0] + val[2] + [lambda {|a, b| a + b }] }
     | exp '-' exp { val[0] + val[2] + [lambda {|a, b| a - b }] }
     | exp '*' exp { val[0] + val[2] + [lambda {|a, b| a * b }] }
     | exp '/' exp { val[0] + val[2] + [lambda {|a, b| a / b }] }
     | '(' exp ')' { val[1] }
     | '-' NUMBER  =UMINUS { [val[1], lambda {|a| -a }] }
     | NUMBER { [val[0]] }
end

---- header

require 'strscan'
require 'readline'

---- inner

class Scanner
  def initialize(src)
    @ss = StringScanner.new(src)
  end

  def scan
    until @ss.eos?
      if tok = @ss.scan(/\A\s+/)
      elsif tok = @ss.scan(/\A\d+/)
        yield :NUMBER, tok.to_f
      elsif tok = @ss.scan(/\A.|\n/o)
        yield tok, tok
      end
    end

    yield false, '$'
  end
end # Scanner

def parse(str)
  scanner = Scanner.new(str)
  rpn = yyparse(scanner, :scan)
  stack = []

  while i = rpn.shift
    if i.kind_of? Proc
      args = (1..i.arity).map { stack.pop }
      stack.push(i.call(*args))
    else
      stack.push(i)
    end
  end

  stack[0]
end

---- footer

parser = Calcp.new

while buf = Readline.readline('> ', true)
  print('=> ', parser.parse(buf).inspect, "\n")
end


~/work$ racc calc.y
~/work$ ruby calc.tab.rb
> 1 + 2 * 3 / 4 - 5 + ( -7 * 8 )
=> -52.6666666666667