Welcome, guest | Sign In | My Account | Store | Cart
#! /usr/bin/env python
import operator

################################################################################

def evaluate(source, local):
    "Execute all math operations found in the source."
    for expression in expressions(source):
        local['_'] = tokens(expression).evaluate(local)

def expressions(source):
    "Separate expressions and yield each individually."
    lines = source.replace('\r\n', '\n').replace('\r', '\n').split('\n')
    uncommented = map(lambda line: line.split('#', 1)[0], lines)
    for line in uncommented:
        if line and not line.isspace():
            for expression in line.split(';'):
                yield expression

def tokens(string):
    "Build an expression tree by tokenizing expression."
    evaluator = _tokens(string)
    if isinstance(evaluator, Operation) and \
       evaluator._Operation__symbol == Operation.ASSIGNMENT:
        return evaluator
    return Print(evaluator)

def _tokens(string):
    "Private module function: recursively builds a tree."
    expression = string.strip()
    if not expression:
        raise SyntaxError('empty expression')
    divisions = Operation.split(expression)
    if divisions:
        left, symbol, right = divisions
        return Operation(_tokens(left), symbol, _tokens(right))
    if len(expression.split()) > 1:
        raise SyntaxError(expression)
    if expression.startswith('0x'):
        return Constant(int(expression[2:], 16))
    if expression.startswith('0d'):
        return Constant(int(expression[2:], 10))
    if expression.startswith('0o'):
        return Constant(int(expression[2:], 8))
    if expression.startswith('0q'):
        return Constant(int(expression[2:], 4))
    if expression.startswith('0b'):
        return Constant(int(expression[2:], 2))
    if expression.isdigit():
        return Constant(int(expression))
    if expression.isidentifier():
        return Variable(expression)
    raise SyntaxError(expression)

################################################################################

class Expression:

    "Abstract class for Expression objects."

    def __init__(self):
        "Initialize the Expression object."
        raise NotImplementedError()

    def evaluate(self, bindings):
        "Calculate the value of this object."
        raise NotImplementedError()

    def __repr__(self):
        "Return a representation of this object."
        klass = self.__class__.__name__
        private = '_{}__'.format(klass)
        args = []
        for name in vars(self):
            if name.startswith(private):
                key = name[len(private):]
                value = getattr(self, name)
                args.append('{}={!r}'.format(key, value))
        return '{}({})'.format(klass, ', '.join(args))

################################################################################

class Constant(Expression):

    "Class for storing all math constants."

    def __init__(self, value):
        "Initialize the Constant object."
        self.__value = value

    def evaluate(self, bindings):
        "Calculate the value of this object."
        return self.__value

################################################################################

class Variable(Expression):

    "Class for storing all math variables."

    def __init__(self, name):
        "Initialize the Variable object."
        self.__name = name

    def evaluate(self, bindings):
        "Calculate the value of this object."
        if self.__name not in bindings:
            raise NameError(self.__name)
        return bindings[self.__name]

################################################################################

class Operation(Expression):

    "Class for executing math operations."

    ASSIGNMENT = '->'
    OPERATORS = {ASSIGNMENT: lambda a, b: None,
                 'and': lambda a, b: a and b,
                 'or': lambda a, b: a or b,
                 '+': operator.add,
                 '-': operator.sub,
                 '*': operator.mul,
                 '/': operator.floordiv,
                 '%': operator.mod,
                 '**': operator.pow,
                 '&': operator.and_,
                 '|': operator.or_,
                 '^': operator.xor,
                 '>>': operator.rshift,
                 '<<': operator.lshift,
                 '==': operator.eq,
                 '!=': operator.ne,
                 '>': operator.gt,
                 '>=': operator.ge,
                 '<': operator.lt,
                 '<=': operator.le}

    def __init__(self, left, symbol, right):
        "Initialize the Operation object."
        self.__left = left
        self.__symbol = symbol
        self.__right = right

    def evaluate(self, bindings):
        "Calculate the value of this object."
        if self.__symbol == self.ASSIGNMENT:
            if not isinstance(self.__right, Variable):
                raise TypeError(self.__right)
            key = self.__right._Variable__name
            value = self.__left.evaluate(bindings)
            bindings[key] = value
            return value
        return self.__operate(bindings)

    def __operate(self, bindings):
        "Execute operation defined by symbol."
        if self.__symbol not in self.OPERATORS:
            raise SyntaxError(self.__symbol)
        a = self.__left.evaluate(bindings)
        b = self.__right.evaluate(bindings)
        return self.OPERATORS[self.__symbol](a, b)

    __operators = sorted(OPERATORS, key=len, reverse=True)

    @classmethod
    def split(cls, expression):
        "Split expression on rightmost symbol."
        tail = cls.__split(expression)
        if tail:
            symbol, right = tail
            return expression[:-sum(map(len, tail))], symbol, right

    @classmethod
    def __split(cls, expression):
        "Private class method: help with split."
        for symbol in cls.__operators:
            if symbol in expression:
                right = expression.rsplit(symbol, 1)[1]
                tail = cls.__split(right)
                if tail is None:
                    return symbol, right
                return tail

################################################################################

class Print(Expression):

    "Class for printing all math results."

    def __init__(self, expression):
        "Initialize the Print object."
        self.__expression = expression

    def evaluate(self, bindings):
        "Calculate the value of this object."
        value = self.__expression.evaluate(bindings)
        print(value)
        return value

################################################################################

def test():
    "Run a simple demo that shows evaluator's capability."
    from sys import exc_info, stderr
    from traceback import format_exception_only
    local = {}
    while True:
        try:
            evaluate(input('>>> '), local)
        except EOFError:
            break
        except:
            stderr.write(format_exception_only(*exc_info()[:2])[-1])

if __name__ == '__main__':
    test()

History