import re
from collections import namedtuple
from fractions import Fraction
Token = namedtuple('Token', ['type', 'value'])
NUM_RE = r'(?P<NUM>\d+(\.\d*)?)'
WS_RE = r'(?P<WS>\s+)'
MUL_RE = r'(?P<MUL>\*)'
DIV_RE = r'(?P<DIV>/)'
PLUS_RE = r'(?P<PLUS>\+)'
MINUS_RE = r'(?P<MINUS>-)'
LPAREN = r'(?P<LPAREN>\()'
RPAREN = r'(?P<RPAREN>\))'
REGEXP = re.compile('|'.join([NUM_RE, WS_RE, MUL_RE, DIV_RE, PLUS_RE, MINUS_RE,
LPAREN, RPAREN]))
class Evaluator(object):
def tokenize(self, s):
scanner = REGEXP.scanner(s)
for tok in iter(scanner.match, None):
if tok.lastgroup != 'WS': # skip whitespaces
yield Token(tok.lastgroup, tok.group())
def eval(self, s):
self.tokens = self.tokenize(s)
self.current_t = None
self.next_t = None
self.advance()
return self.expr()
def advance(self):
self.current_t, self.next_t = self.next_t, next(self.tokens, None)
def accept(self, tp):
if self.next_t and self.next_t.type == tp:
self.advance()
return True
else:
return False
def expect(self, tp):
if not self.accept(tp):
error_msg = "Expected {}, got {}".format(tp, self.next_t and self.next_t.value)
raise SyntaxError(error_msg)
def expr(self):
left = self.term()
while self.accept('PLUS') or self.accept('MINUS'):
if self.current_t.type == 'PLUS':
right = self.term()
left += right
else:
right = self.term()
left -= right
return left
def term(self):
left = self.factor()
while self.accept('MUL') or self.accept('DIV'):
if self.current_t.type == 'MUL':
left *= self.factor()
else:
left /= self.factor()
return left
def factor(self):
if self.accept('MINUS') and self.accept('NUM'):
return -1 * Fraction(self.current_t.value)
elif self.accept('NUM'):
return Fraction(self.current_t.value)
else:
self.expect('LPAREN')
exp = self.expr()
self.expect('RPAREN')
return exp
# tests
def test():
suit = {'2 + 2 * 2': 6,
'(5 * 2) / 4': Fraction("5/2"),
'1 + 1 + 1': 3,
'3 * 4 / 2': 6,
'-3 * (5 + 0)': -15,
'3 * (5 + )': 15,
'2.5 / -5': Fraction("-1/2")}
ev = Evaluator()
for expr, res in suit.iteritems():
try:
print "{} -> {}, expected {}".format(expr, ev.eval(expr), res)
except SyntaxError as e:
print "{} failed with: {}".format(expr, e)
if __name__ == '__main__':
test()
aW1wb3J0IHJlCmZyb20gY29sbGVjdGlvbnMgaW1wb3J0IG5hbWVkdHVwbGUKZnJvbSBmcmFjdGlvbnMgaW1wb3J0IEZyYWN0aW9uCiAKVG9rZW4gPSBuYW1lZHR1cGxlKCdUb2tlbicsIFsndHlwZScsICd2YWx1ZSddKQogCk5VTV9SRSA9IHInKD9QPE5VTT5cZCsoXC5cZCopPyknCldTX1JFID0gcicoP1A8V1M+XHMrKScKTVVMX1JFID0gcicoP1A8TVVMPlwqKScKRElWX1JFID0gcicoP1A8RElWPi8pJwpQTFVTX1JFID0gcicoP1A8UExVUz5cKyknCk1JTlVTX1JFID0gcicoP1A8TUlOVVM+LSknCkxQQVJFTiA9IHInKD9QPExQQVJFTj5cKCknClJQQVJFTiA9IHInKD9QPFJQQVJFTj5cKSknCiAKUkVHRVhQID0gcmUuY29tcGlsZSgnfCcuam9pbihbTlVNX1JFLCBXU19SRSwgTVVMX1JFLCBESVZfUkUsIFBMVVNfUkUsIE1JTlVTX1JFLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgIExQQVJFTiwgUlBBUkVOXSkpCiAKIApjbGFzcyBFdmFsdWF0b3Iob2JqZWN0KToKICAgIGRlZiB0b2tlbml6ZShzZWxmLCBzKToKICAgICAgICBzY2FubmVyID0gUkVHRVhQLnNjYW5uZXIocykKICAgICAgICBmb3IgdG9rIGluIGl0ZXIoc2Nhbm5lci5tYXRjaCwgTm9uZSk6CiAgICAgICAgICAgIGlmIHRvay5sYXN0Z3JvdXAgIT0gJ1dTJzogICMgc2tpcCB3aGl0ZXNwYWNlcwogICAgICAgICAgICAgICAgeWllbGQgVG9rZW4odG9rLmxhc3Rncm91cCwgdG9rLmdyb3VwKCkpCiAKICAgIGRlZiBldmFsKHNlbGYsIHMpOgogICAgICAgIHNlbGYudG9rZW5zID0gc2VsZi50b2tlbml6ZShzKQogICAgICAgIHNlbGYuY3VycmVudF90ID0gTm9uZQogICAgICAgIHNlbGYubmV4dF90ID0gTm9uZQogICAgICAgIHNlbGYuYWR2YW5jZSgpCiAgICAgICAgcmV0dXJuIHNlbGYuZXhwcigpCiAKICAgIGRlZiBhZHZhbmNlKHNlbGYpOgogICAgICAgIHNlbGYuY3VycmVudF90LCBzZWxmLm5leHRfdCA9IHNlbGYubmV4dF90LCBuZXh0KHNlbGYudG9rZW5zLCBOb25lKQogCiAgICBkZWYgYWNjZXB0KHNlbGYsIHRwKToKICAgICAgICBpZiBzZWxmLm5leHRfdCBhbmQgc2VsZi5uZXh0X3QudHlwZSA9PSB0cDoKICAgICAgICAgICAgc2VsZi5hZHZhbmNlKCkKICAgICAgICAgICAgcmV0dXJuIFRydWUKICAgICAgICBlbHNlOgogICAgICAgICAgICByZXR1cm4gRmFsc2UKIAogICAgZGVmIGV4cGVjdChzZWxmLCB0cCk6CiAgICAgICAgaWYgbm90IHNlbGYuYWNjZXB0KHRwKToKICAgICAgICAgICAgZXJyb3JfbXNnID0gIkV4cGVjdGVkIHt9LCBnb3Qge30iLmZvcm1hdCh0cCwgc2VsZi5uZXh0X3QgYW5kIHNlbGYubmV4dF90LnZhbHVlKQogICAgICAgICAgICByYWlzZSBTeW50YXhFcnJvcihlcnJvcl9tc2cpCiAKICAgIGRlZiBleHByKHNlbGYpOgogICAgICAgIGxlZnQgPSBzZWxmLnRlcm0oKQogICAgICAgIHdoaWxlIHNlbGYuYWNjZXB0KCdQTFVTJykgb3Igc2VsZi5hY2NlcHQoJ01JTlVTJyk6CiAgICAgICAgICAgIGlmIHNlbGYuY3VycmVudF90LnR5cGUgPT0gJ1BMVVMnOgogICAgICAgICAgICAgICAgcmlnaHQgPSBzZWxmLnRlcm0oKQogICAgICAgICAgICAgICAgbGVmdCArPSByaWdodAogICAgICAgICAgICBlbHNlOgogICAgICAgICAgICAgICAgcmlnaHQgPSBzZWxmLnRlcm0oKQogICAgICAgICAgICAgICAgbGVmdCAtPSByaWdodAogICAgICAgIHJldHVybiBsZWZ0CiAKICAgIGRlZiB0ZXJtKHNlbGYpOgogICAgICAgIGxlZnQgPSBzZWxmLmZhY3RvcigpCiAgICAgICAgd2hpbGUgc2VsZi5hY2NlcHQoJ01VTCcpIG9yIHNlbGYuYWNjZXB0KCdESVYnKToKICAgICAgICAgICAgaWYgc2VsZi5jdXJyZW50X3QudHlwZSA9PSAnTVVMJzoKICAgICAgICAgICAgICAgIGxlZnQgKj0gc2VsZi5mYWN0b3IoKQogICAgICAgICAgICBlbHNlOgogICAgICAgICAgICAgICAgbGVmdCAvPSBzZWxmLmZhY3RvcigpCiAgICAgICAgcmV0dXJuIGxlZnQKIAogICAgZGVmIGZhY3RvcihzZWxmKToKICAgICAgICBpZiBzZWxmLmFjY2VwdCgnTUlOVVMnKSBhbmQgc2VsZi5hY2NlcHQoJ05VTScpOgogICAgICAgICAgICByZXR1cm4gLTEgKiBGcmFjdGlvbihzZWxmLmN1cnJlbnRfdC52YWx1ZSkKICAgICAgICBlbGlmIHNlbGYuYWNjZXB0KCdOVU0nKToKICAgICAgICAgICAgcmV0dXJuIEZyYWN0aW9uKHNlbGYuY3VycmVudF90LnZhbHVlKQogICAgICAgIGVsc2U6CiAgICAgICAgICAgIHNlbGYuZXhwZWN0KCdMUEFSRU4nKQogICAgICAgICAgICBleHAgPSBzZWxmLmV4cHIoKQogICAgICAgICAgICBzZWxmLmV4cGVjdCgnUlBBUkVOJykKICAgICAgICAgICAgcmV0dXJuIGV4cAogCiAKIyB0ZXN0cwpkZWYgdGVzdCgpOgogICAgc3VpdCA9IHsnMiArIDIgKiAyJzogNiwKICAgICAgICAgICAgJyg1ICogMikgLyA0JzogRnJhY3Rpb24oIjUvMiIpLAogICAgICAgICAgICAnMSArIDEgKyAxJzogMywKICAgICAgICAgICAgJzMgKiA0IC8gMic6IDYsCiAgICAgICAgICAgICctMyAqICg1ICsgMCknOiAtMTUsCiAgICAgICAgICAgICczICogKDUgKyApJzogMTUsCiAgICAgICAgICAgICcyLjUgLyAtNSc6IEZyYWN0aW9uKCItMS8yIil9CiAgICBldiA9IEV2YWx1YXRvcigpCiAgICBmb3IgZXhwciwgcmVzIGluIHN1aXQuaXRlcml0ZW1zKCk6CiAgICAgICAgdHJ5OgogICAgICAgICAgICBwcmludCAie30gLT4ge30sIGV4cGVjdGVkIHt9Ii5mb3JtYXQoZXhwciwgZXYuZXZhbChleHByKSwgcmVzKQogICAgICAgIGV4Y2VwdCBTeW50YXhFcnJvciBhcyBlOgogICAgICAgICAgICBwcmludCAie30gZmFpbGVkIHdpdGg6IHt9Ii5mb3JtYXQoZXhwciwgZSkKIAppZiBfX25hbWVfXyA9PSAnX19tYWluX18nOgogICAgdGVzdCgp