|
8 | 8 | """ |
9 | 9 |
|
10 | 10 | import itertools |
11 | | -import re |
| 11 | +import ast |
| 12 | +import operator |
12 | 13 |
|
| 14 | +OPS = { |
| 15 | + ast.And: operator.and_, |
| 16 | + ast.Or: operator.or_, |
| 17 | + ast.Not: operator.not_ |
| 18 | +} |
13 | 19 |
|
14 | 20 | def safe_eval(expr: str, model: dict[str, bool]) -> bool: |
15 | | - """Safely evaluate propositional logic expression with given model.""" |
16 | | - # Replace symbols (like P, Q) with their boolean values |
17 | | - for sym, val in model.items(): |
18 | | - expr = re.sub(rf'\b{sym}\b', str(val), expr) |
19 | | - # Allow only True/False, and/or/not operators |
20 | | - allowed = {"True", "False", "and", "or", "not", "(", ")", " "} |
21 | | - if not all(token in allowed or token.isidentifier() or token in "()" for token in re.split(r'(\W+)', expr)): |
22 | | - raise ValueError("Unsafe expression detected") |
23 | | - return eval(expr, {"__builtins__": {}}, {}) |
| 21 | + """Safely evaluate propositional logic expression using ast.""" |
| 22 | + tree = ast.parse(expr, mode="eval") |
24 | 23 |
|
| 24 | + def _eval(node): |
| 25 | + if isinstance(node, ast.Expression): |
| 26 | + return _eval(node.body) |
| 27 | + if isinstance(node, ast.Name): |
| 28 | + return model[node.id] |
| 29 | + if isinstance(node, ast.Constant): # True / False |
| 30 | + return node.value |
| 31 | + if isinstance(node, ast.UnaryOp) and isinstance(node.op, ast.Not): |
| 32 | + return OPS[ast.Not](_eval(node.operand)) |
| 33 | + if isinstance(node, ast.BoolOp): |
| 34 | + if isinstance(node.op, ast.And): |
| 35 | + return all(_eval(v) for v in node.values) |
| 36 | + if isinstance(node.op, ast.Or): |
| 37 | + return any(_eval(v) for v in node.values) |
| 38 | + raise ValueError(f"Unsupported expression: {expr}") |
| 39 | + |
| 40 | + return _eval(tree) |
25 | 41 |
|
26 | 42 | def tt_entails(kb: list[str], query: str, symbols: list[str]) -> bool: |
27 | 43 | """ |
|
0 commit comments