Last active
March 28, 2024 13:55
-
-
Save iafisher/6f53ce4df29c91ac9276c13a113ccf9f to your computer and use it in GitHub Desktop.
A simple implementation of a recursive-descent parser for a language of boolean expressions.
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
"""A simple implementation of a recursive-descent parser for a language of boolean expressions.""" | |
import readline | |
def eval_str(expr): | |
"""Evaluate a boolean expression with the symbols '0', '1', '|', '&', '(' and ')'. All binary | |
operators must be wrapped in parentheses, even when it would be unambiguous to omit them. | |
""" | |
tokens = tokenize(expr) | |
ast = match_expr(tokens) | |
return eval_ast(ast) | |
def eval_ast(ast): | |
"""Evaluate an AST as returned by match_expr.""" | |
if isinstance(ast, list): | |
if ast[0] == '&': | |
return eval_ast(ast[1]) and eval_ast(ast[2]) | |
elif ast[0] == '|': | |
return eval_ast(ast[1]) or eval_ast(ast[2]) | |
else: | |
raise ValueError('unknown AST node "{}"'.format(ast[0])) | |
else: | |
return bool(ast == '1') | |
def match_expr(tokens): | |
"""Given a list of tokens (as strings), return the abstract syntax tree as parsed by the grammar | |
START -> EXPR | |
EXPR -> VALUE | |
EXPR -> ( EXPR OP EXPR ) | |
OP -> & | |
OP -> | | |
VALUE -> 1 | |
VALUE -> 0 | |
The return value is either a tree (actually just a Python list whose first element is the node | |
value, either '|' or '&', and whose next two elements are the left and right children of the | |
tree) or the literal values '1' or '0'. | |
""" | |
tkn = next_token(tokens) | |
if tkn == '1' or tkn == '0': | |
return tkn | |
elif tkn == '(': | |
left = match_expr(tokens) | |
op = next_token(tokens) | |
if op not in ('&', '|'): | |
raise SyntaxError('expected "&" or "|", got "{}"'.format(op)) | |
right = match_expr(tokens) | |
tkn = next_token(tokens) | |
if tkn != ')': | |
raise SyntaxError('expected ")", got "{}"'.format(tkn)) | |
return [op, left, right] | |
else: | |
raise SyntaxError('expected "(", "0" or "1", got "{}"'.format(tkn)) | |
def next_token(tokens): | |
try: | |
return tokens.pop(0) | |
except IndexError: | |
raise SyntaxError('premature end of input') from None | |
TOKENS = ('1', '0', '&', '|', '(', ')') | |
def tokenize(expr): | |
"""Return a list of tokens from the expression.""" | |
# Quick and dirty tokenizing. Don't do this in real life! | |
for tkn in TOKENS: | |
expr = expr.replace(tkn, ' ' + tkn + ' ') | |
return expr.split() | |
while True: | |
try: | |
expr = input('>>> ') | |
except EOFError: | |
break | |
try: | |
print(eval_str(expr)) | |
except SyntaxError as e: | |
print('Error:', e) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment