fork(1) download
  1. import re
  2. from collections import namedtuple
  3. from fractions import Fraction
  4.  
  5. Token = namedtuple('Token', ['type', 'value'])
  6.  
  7. NUM_RE = r'(?P<NUM>\d+(\.\d*)?)'
  8. WS_RE = r'(?P<WS>\s+)'
  9. MUL_RE = r'(?P<MUL>\*)'
  10. DIV_RE = r'(?P<DIV>/)'
  11. PLUS_RE = r'(?P<PLUS>\+)'
  12. MINUS_RE = r'(?P<MINUS>-)'
  13. LPAREN = r'(?P<LPAREN>\()'
  14. RPAREN = r'(?P<RPAREN>\))'
  15.  
  16. REGEXP = re.compile('|'.join([NUM_RE, WS_RE, MUL_RE, DIV_RE, PLUS_RE, MINUS_RE,
  17. LPAREN, RPAREN]))
  18.  
  19.  
  20. class Evaluator(object):
  21. def tokenize(self, s):
  22. scanner = REGEXP.scanner(s)
  23. for tok in iter(scanner.match, None):
  24. if tok.lastgroup != 'WS': # skip whitespaces
  25. yield Token(tok.lastgroup, tok.group())
  26.  
  27. def eval(self, s):
  28. self.tokens = self.tokenize(s)
  29. self.current_t = None
  30. self.next_t = None
  31. self.advance()
  32. return self.expr()
  33.  
  34. def advance(self):
  35. self.current_t, self.next_t = self.next_t, next(self.tokens, None)
  36.  
  37. def accept(self, tp):
  38. if self.next_t and self.next_t.type == tp:
  39. self.advance()
  40. return True
  41. else:
  42. return False
  43.  
  44. def expect(self, tp):
  45. if not self.accept(tp):
  46. error_msg = "Expected {}, got {}".format(tp, self.next_t and self.next_t.value)
  47. raise SyntaxError(error_msg)
  48.  
  49. def expr(self):
  50. left = self.term()
  51. while self.accept('PLUS') or self.accept('MINUS'):
  52. if self.current_t.type == 'PLUS':
  53. right = self.term()
  54. left += right
  55. else:
  56. right = self.term()
  57. left -= right
  58. return left
  59.  
  60. def term(self):
  61. left = self.factor()
  62. while self.accept('MUL') or self.accept('DIV'):
  63. if self.current_t.type == 'MUL':
  64. left *= self.factor()
  65. else:
  66. left /= self.factor()
  67. return left
  68.  
  69. def factor(self):
  70. if self.accept('MINUS') and self.accept('NUM'):
  71. return -1 * Fraction(self.current_t.value)
  72. elif self.accept('NUM'):
  73. return Fraction(self.current_t.value)
  74. else:
  75. self.expect('LPAREN')
  76. exp = self.expr()
  77. self.expect('RPAREN')
  78. return exp
  79.  
  80.  
  81. # tests
  82. def test():
  83. suit = {'2 + 2 * 2': 6,
  84. '(5 * 2) / 4': Fraction("5/2"),
  85. '1 + 1 + 1': 3,
  86. '3 * 4 / 2': 6,
  87. '-3 * (5 + 0)': -15,
  88. '3 * (5 + )': 15,
  89. '2.5 / -5': Fraction("-1/2")}
  90. ev = Evaluator()
  91. for expr, res in suit.iteritems():
  92. try:
  93. print "{} -> {}, expected {}".format(expr, ev.eval(expr), res)
  94. except SyntaxError as e:
  95. print "{} failed with: {}".format(expr, e)
  96.  
  97. if __name__ == '__main__':
  98. test()
Success #stdin #stdout 0.08s 12488KB
stdin
Standard input is empty
stdout
3 * 4 / 2 -> 6, expected 6
2 + 2 * 2 -> 6, expected 6
-3 * (5 + 0) -> -15, expected -15
2.5 / -5 -> -1/2, expected -1/2
3 * (5 + ) failed with: Expected LPAREN, got )
1 + 1 + 1 -> 3, expected 3
(5 * 2) / 4 -> 5/2, expected 5/2