Python:rpn

From Jonathan D. Lettvin

Jump to: navigation, search

Reverse Polish Notation calculator

#!/usr/bin/env python

"""
rpn.py Copyright(c) 2011 Jonathan D. Lettvin, All Rights Reserved.
This script is licensed under Creative Commons Attribution 3.0.
http://creativecommons.org/licenses/by/3.0/
The author requests attribution of authorship under this license.
The original source is located at:
http://wiki.lettvin.com/Jonathan/wiki/index.php/Python:rpn
"""

import sys,string,operator,scipy,scipy.special,exceptions

class RPN(object):
    """
    An RPN (Reverse Polish Notation) interpreter class.
    Single line strings are interpreted.  String is echoed, followed by '=' then result.
    Example: input "1 1 +" generates output 2 = "1 1 +"
    Example: input "9 sqrt" generates output 3 = "9 sqrt"
    Example: input "3 2 ^ 4 2 ^ + sqrt" generates output 5 = "3 2 ^ 4 2 ^ + sqrt"
    """

    def __init__(self, verbose=False):
        """
        Initialize an interpreter.
        The interpreter keeps a current token, a register index, an operand pair, verbosity,
        an operand stack, 26 letter-named registers, and a keytable for launching functions.
        First letter of a token triggers a function from the keytable.
        Operands are to be separated by spaces.
        Top of stack can be stored and retrieved from letter-named registers.
        Numbers must begin with a digit.
        """
        self.token, self.index, self.a1, self.a2, self.verbose = '', 0, 0, 0, verbose
        self.stack, self.register, self.key = [], [0.0]*26, [self.nop]*256
        self.dict, self.list, self.number = dict(), list(), dict()
        self.internal = { 'set': self.set, 'get': self.get, 'clr': self.clr }
        for k, v in {
            '+': self.add, '-': self.sub, '*': self.mul, '/': self.div,
            '^': self.pow, '$': self.reg }.iteritems(): self.key[ord(k)] = v
        for c in string.ascii_lowercase: self.key[ord(c)] = self.opr
        for c in string.digits: self.key[ord(c)] = self.num

    def call(self):
        """
        Call a scipy function.  If no direct link is local, find it in scipy and link it local.
        Pop the top of stack.  Run the single-parameter scipy function.  Push result on stack.
        """
        if self.token in self.internal: self.internal[self.token]()
        elif self.token not in self.dict:
            for module in (scipy, scipy.special):
                try: self.dict[self.token] = module.__dict__[self.token]
                except: pass
                if self.token in self.dict:
                    self.number[self.token] = len(self.list) # Enables future compilation
                    self.list.append(self.dict[self.token])  # Enables future compilation
                    break
            assert self.token in self.dict, 'Unable to use function: '+self.token
            if self.token in self.dict:
                self.stack.append(self.dict[self.token](self.stack.pop()))

    def regs(self):
        """Generate a readable output string showing all the non-zero letter registers."""
        s = ''
        for c in range(26):
            s += str(chr(c+ord('a')))+str(self.register[c])+' ' if self.register[c] else ''
        return s

    """Service functions used by keytable functions."""
    def regnum(self, letter): return ord(self.token[1])-ord('a')
    def limit(self,lo=1,hi=1): assert lo<=len(self.token)<=hi
    def pop2(self): self.a2, self.a1 = self.stack.pop(), self.stack.pop()

    """Functions to be driven from the keytable."""
    def clr(self): self.register = [0.0]*26; self.index = 0
    def nop(self): print self; assert False, 'nop'
    def reg(self): self.limit(2,2); self.index=self.regnum(self.token[1])
    def get(self): self.stack.append(self.register[self.index])
    def set(self): self.register[self.index] = self.stack.pop()
    def num(self): self.stack.append(float(self.token))
    def opr(self): self.call()
    def add(self): self.pop2(); self.stack.append(self.a2  + self.a1)
    def sub(self): self.pop2(); self.stack.append(self.a2  - self.a1)
    def mul(self): self.pop2(); self.stack.append(self.a2  * self.a1)
    def div(self): self.pop2(); self.stack.append(self.a2  / self.a1)
    def pow(self): self.pop2(); self.stack.append(self.a1 ** self.a2)

    def __call__(self, expr):
        """
        Interpreter: When interpretation finishes, string is empty and stack has one item.
        State of interpreter has residue during next entry.
        A "clr" instruction in the expression restores registers to 0.
        Snapped links to scipy functions remain snapped during lifetime of interpreter object.
        """
        if self.verbose: print expr
        for self.token in string.split(expr):
            self.key[ord(self.token[0])]()
            if self.verbose: print self
        assert len(self.stack)==1
        value = self.stack.pop()
        print '%f = rpn("%s")' % (value, expr)
        return value

    def __repr__(self):
        """Generate viewable string showing interpreter state."""
        return 'token: "%s", stack: %s, register: %s, registers: %s' % (
            self.token, str(self.stack), chr(ord('a')+self.index), self.regs())

if __name__ == "__main__":
    from optparse import OptionParser
    usage = """Usage: %prog [options]
    RPN (Reverse Polish Notation) calculator with full access to single-arg scipy functions."""
    parser = OptionParser(usage=usage, version="%prog 0.01", description="")
    parser.add_option("-v", "--verbose", action="store_true", default=False)
    parser.add_option("-p", "--pipe", action="store_true", default=False)
    (opts,args) = parser.parse_args()
    try:
        interpret = RPN(opts.verbose)
        if opts.pipe:
            for line in sys.stdin: interpret(line.strip())
        elif len(args) > 0:
            for arg in args: interpret(arg)
        else:
            interpret('2 2 +')
            interpret('%f $x set %f $y set $x get 2 ^ $y get 2 ^ + sqrt' % (3, 4))
            interpret('clr &_intentional_bad_character_to_illustrate_error_handling.')
    except Exception as e: print sys.exc_info()[0], e
Personal tools