bison マニュアルの中間記法電卓(StringTokenizer 未使用)

「^」にも対応

#!/usr/bin/env ruby

require 'tdp'
require 'tdp/utils'

class Calc
  include TDParser
  include TDPUtils

  def expr1
    rule(:expr2) - ((token("+")|token("-")) - rule(:expr2))*0 >> proc{|x|
      x[1].inject(x[0]){|n, y|
        case y[0]
        when "+"
          n + y[1]
        when "-"
          n - y[1]
        end
      }
    }
  end

  def expr2
    rule(:expr3) - ((token("*")|token("/")) - rule(:expr3))*0 >> proc{|x|
      x[1].inject(x[0]){|n, y|
        case y[0]
        when "*"
          n * y[1]
        when "/"
          n / y[1]
        end
      }
    }
  end

  def expr3
    token("-") - rule(:expr3) >> proc {|x| -x[1] } |
    rule(:expr4)              >> proc {|x|  x[0] }
  end

  def expr4
    rule(:prim) - (token("^") - rule(:expr3))*(0..1) >> proc{|x|
      x[1].inject(x[0]){|n, y|
        n ** y[1]
      }
    }
  end

  def prim
    token(:NUM) >> proc {|x| x[0].value.to_f } |
    token("(") - rule(:expr1) - token(")") >> proc{|x| x[1] }
  end

  def input
    rule(:expr1) >> proc {|x| puts "\t#{x}"}
  end

  def parse(f)
    f.each do |line|
      input.parse {|x| tokens = lex(line); tokens.each {|t| x.yield(t)}}
    end
  end

  def lex(line)
    @q = []

    line.strip!
    until line.empty? do
      case line
      when /\A\s+/, /\A\#.*/
        ;
      when /\A\d+(\.\d+)*/
        @q.push Token.new(:NUM, $&.to_f)
      when /\A./
        @q.push $&
      else
        raise RuntimeError, 'must not happen'
      end
      line = $'
    end

    return @q
  end
end

parser = Calc.new
if ARGV[0] then
  File.open( ARGV[0] ) do |f|
    parser.parse f
  end
else
  parser.parse $stdin
end

で、

1 + 2
	3.0
1-2
	-1.0
2*3
	6.0
10/3
	3.33333333333333
-56+2
	-54.0
-2^2
	-4.0
2^2^3
	256.0
-(1+2*3)
	-7.0
1+-1
	0.0
2^-1
	0.5
4 + 4.5 - (34/(8*3+-3))
	6.88095238095238