Files
agent-orchestrator-benchmark/calculators/builder-adversary-lean/run-05/calc/parser.py

128 lines
3.4 KiB
Python

"""Recursive-descent parser for calc expressions.
Grammar:
expr := term (('+' | '-') term)*
term := factor (('*' | '/') factor)*
factor := '-' factor | primary
primary := NUMBER | '(' expr ')'
Node shapes:
Num(value) — a numeric literal; value is int or float
BinOp(op, left, right) — op is one of '+' '-' '*' '/'
Unary(op, operand) — op is '-'
"""
class ParseError(Exception):
pass
class Num:
__slots__ = ('value',)
def __init__(self, value):
self.value = value
def __repr__(self):
return f'Num({self.value!r})'
def __eq__(self, other):
return isinstance(other, Num) and self.value == other.value
class BinOp:
__slots__ = ('op', 'left', 'right')
def __init__(self, op, left, right):
self.op = op
self.left = left
self.right = right
def __repr__(self):
return f'BinOp({self.op!r}, {self.left!r}, {self.right!r})'
def __eq__(self, other):
return (isinstance(other, BinOp) and self.op == other.op
and self.left == other.left and self.right == other.right)
class Unary:
__slots__ = ('op', 'operand')
def __init__(self, op, operand):
self.op = op
self.operand = operand
def __repr__(self):
return f'Unary({self.op!r}, {self.operand!r})'
def __eq__(self, other):
return (isinstance(other, Unary) and self.op == other.op
and self.operand == other.operand)
class _Parser:
def __init__(self, tokens):
self._tokens = tokens
self._pos = 0
def _peek(self):
return self._tokens[self._pos]
def _consume(self, kind=None):
tok = self._tokens[self._pos]
if kind and tok.kind != kind:
raise ParseError(f'expected {kind}, got {tok.kind!r}')
self._pos += 1
return tok
def parse(self):
if self._peek().kind == 'EOF':
raise ParseError('empty input')
node = self._expr()
if self._peek().kind != 'EOF':
raise ParseError(f'unexpected token {self._peek().kind!r}')
return node
def _expr(self):
node = self._term()
while self._peek().kind in ('PLUS', 'MINUS'):
op = self._consume().value
right = self._term()
node = BinOp(op, node, right)
return node
def _term(self):
node = self._factor()
while self._peek().kind in ('STAR', 'SLASH'):
op = self._consume().value
right = self._factor()
node = BinOp(op, node, right)
return node
def _factor(self):
if self._peek().kind == 'MINUS':
self._consume()
operand = self._factor()
return Unary('-', operand)
return self._primary()
def _primary(self):
tok = self._peek()
if tok.kind == 'NUMBER':
self._consume()
return Num(tok.value)
if tok.kind == 'LPAREN':
self._consume()
node = self._expr()
if self._peek().kind != 'RPAREN':
raise ParseError(f'unclosed parenthesis, got {self._peek().kind!r}')
self._consume()
return node
raise ParseError(f'unexpected token {tok.kind!r}')
def parse(tokens):
"""Parse a token list from tokenize() and return an AST node."""
return _Parser(tokens).parse()