Created
March 5, 2016 03:00
-
-
Save crowsonkb/25e7c3384ecc26f8de8c to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import logging | |
import pyparsing as pp | |
pp.ParserElement.enablePackrat() | |
logging.basicConfig(format='%(message)s', level=logging.DEBUG) | |
LOG = logging.getLogger() | |
def unwrap(toks): | |
if len(toks) == 1: | |
return toks[0] | |
raise ValueError('ParseResults had more than one element.') | |
class CalcParser: | |
"""A four-function (with parens) infix notation calculator expression parser.""" | |
def __init__(self): | |
integer = pp.Word(pp.nums) | |
integer.setParseAction(lambda toks: int(toks[0])) | |
expr = pp.ungroup(pp.infixNotation( | |
integer, [ | |
(pp.Literal('-')('op*'), 1, pp.opAssoc.RIGHT), | |
(pp.oneOf('* /')('op*'), 2, pp.opAssoc.LEFT), | |
(pp.oneOf('+ -')('op*'), 2, pp.opAssoc.LEFT), | |
])) | |
expr.setParseAction(unwrap) | |
expr.setDebug(True) | |
self.integer = integer | |
self.expr = expr | |
class CalcEvaluator: | |
@classmethod | |
def eval_op(cls, node_a, op, node_b): | |
"""Recursively evaluates a single four-function arithmetic operation. For instance, | |
this might evaluate the '+' operation in '1 + 2 * 3': or, parsed, [1, '+', [2, '*', 3] | |
eval_op() evaluates its left and right sides and then performs its operation.""" | |
a = cls.eval_node(node_a) | |
b = cls.eval_node(node_b) | |
if op == '+': | |
return a+b | |
elif op == '-': | |
return a-b | |
elif op == '*': | |
return a*b | |
elif op == '/': | |
return a//b | |
else: | |
raise ValueError('Ill-formed AST: %s.' % node) | |
@classmethod | |
def eval_node(cls, node): | |
"""Evaluates an abstract syntax tree.""" | |
# A terminal node: just return it. | |
if isinstance(node, int): | |
return node | |
if hasattr(node, '__len__'): | |
l = len(node) | |
# Unwrap parenthesis. | |
if l == 1: | |
return cls.eval_node(node[0]) | |
if l == 2: | |
if node[0] == '-': | |
return -node[1] | |
# Evaluate a normal arithmetic operation. | |
if l == 3: | |
a = cls.eval_node(node[0]) | |
op = node[1] | |
b = cls.eval_node(node[2]) | |
return cls.eval_op(a, op, b) | |
# A left-associative AST (e.g. [1, '+', 2, '+', 3]). | |
elif l > 3 and l % 2 == 1: | |
b = cls.eval_node(node[-1]) | |
op = node[-2] | |
a = cls.eval_node(node[:-2]) | |
return cls.eval_op(a, op, b) | |
else: | |
raise ValueError('Ill-formed AST: %s.' % node) | |
else: | |
raise ValueError('Ill-formed AST: %s.' % node) | |
def main(): | |
"""The main user interface loop.""" | |
import readline | |
try: | |
while True: | |
input_line = input('> ') | |
try: | |
result = CalcParser().expr.parseString(input_line, parseAll=True) | |
LOG.info('AST:\n%s\n', result.dump()) | |
if result.haskeys(): | |
for k, v in result.items(): | |
LOG.info('%s: %s', k, v) | |
LOG.info('Result: %s', CalcEvaluator.eval_node(result)) | |
except (pp.ParseException, ValueError) as err: | |
LOG.exception('%s: %s', err.__class__.__name__, err) | |
except (EOFError, KeyboardInterrupt): | |
pass | |
if __name__ == '__main__': | |
main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment