#!/usr/bin/env ruby # (c) 12/2006 Yoann Guillot # this code is licenced under the terms of the WTFPL # full text available at http://sam.zoy.org/wtfpl/ # full numeric calculator, with operator precedence require 'strscan' class MathExpr # key = operator, value = hash regrouping operators of lower precedence OP_PRIO = [[:|], [:^], [:&], [:<<, :>>], [:+, :-], [:*, :/, :%]].inject({}) { |h, oplist| lessprio = h.keys.inject({}) { |hh, op| hh.update op => true } oplist.each { |op| h[op] = lessprio } h } def initialize(op, rexpr, lexpr) @op, @lexpr, @rexpr = op, lexpr, rexpr end def eval l = @lexpr.kind_of?(MathExpr) ? @lexpr.eval : @lexpr r = @rexpr.kind_of?(MathExpr) ? @rexpr.eval : @rexpr if l l.send(@op, r) else case @op when :+ r when :- -r when :~ ~r end end end class << self # returns a Node or nil if unparsed def parse(ss) opstack = [] stack = [] return if not e = parse_expr(ss) stack << e loop do ss.scan(/\s*/) break unless tok = ss.scan(/[-+*\/%|&]|<<|>>/) tok = tok.to_sym lessprio = OP_PRIO[tok] until opstack.empty? or lessprio[opstack.last] stack << new(opstack.pop, stack.pop, stack.pop) end opstack << tok raise "Invalid expression" unless e = parse_expr(ss) stack << e end until opstack.empty? stack << new(opstack.pop, stack.pop, stack.pop) end e = stack.first e = new(:+, e, nil) unless e.kind_of? self e end def parse_expr(ss) ss.scan(/\s*/) return if ss.eos? case when tok = ss.scan(/[-+~]/) # unary operator return unless e = parse_expr(ss) new(tok.to_sym, e, nil) when tok = ss.scan(/\(/) # parenthesis return unless e = parse(ss) raise "')' expected" unless ss.scan(/\s*\)/) e when tok = ss.scan(/\d+/) tok.to_i end end end end if __FILE__ == $0 ss = StringScanner.new ARGV.join expr = MathExpr.parse ss p expr.eval end