This is a complete rewrite of recipe 576790. While aiming to maintain similar functionality and continuing its implementation for self-academic purposes, a much cleaner parser / tokenizer and operator execution engine were developed. A slightly different math syntax is supported in this version, but it is arguably better and more capable than it previously was. Base prefixes are a feature now supported, and the single downgrade is calculating with integers instead of floats.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 | #! /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()
|
This is a small sample exchange with the program:
This is really cool! Thanks!