Skip to content

Instantly share code, notes, and snippets.

@crowsonkb
Created March 5, 2016 03:00
Show Gist options
  • Save crowsonkb/25e7c3384ecc26f8de8c to your computer and use it in GitHub Desktop.
Save crowsonkb/25e7c3384ecc26f8de8c to your computer and use it in GitHub Desktop.
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