2. 规则条件表达式解析器
任务目标
实现一个轻量条件表达式解析器,把形如 age >= 18 and (score > 600 or vip == True) 的字符串解析为可求值的抽象语法树(AST),并支持用字典变量求值。覆盖比较运算、and/or/not、括号、布尔与数字字面量。
实现思路
采用经典三段式:词法分析把字符串切成 ;递归下降解析按优先级(or < and < not < 比较)构造 AST;求值器递归遍历 AST,用变量字典取值。AST 用元组表示,便于调试与序列化。
完整代码
import re
_RE = re.compile(r"""
\s*(?:
(?P<num>\d+(?:\.\d+)?)
| (?P<str>'[^']*'|"[^"]*")
| (?P<op>>=|<=|==|!=|>|<)
| (?P<logic>\band\b|\bor\b|\bnot\b)
| (?P<bool>\bTrue\b|\bFalse\b)
| (?P<lp>\()
| (?P<rp>\))
| (?P<id>[A-Za-z_]\w*)
)
""", re.VERBOSE)
def tokenize(s):
tokens, pos = [], 0
while pos < len(s):
m = _RE.match(s, pos)
if not m or m.end() == pos:
raise SyntaxError(f"无法识别的字符: {s[pos:]!r}")
pos = m.end()
kind = m.lastgroup
val = m.group(kind)
if kind == "num":
val = float(val) if "." in val else int(val)
elif kind == "str":
val = val[1:-1]
elif kind == "bool":
val = (val == "True")
tokens.append((kind, val))
return tokens
class Parser:
def __init__(self, tokens):
self.tokens, self.i = tokens, 0
def peek(self):
return self.tokens[self.i] if self.i < len(self.tokens) else (None, None)
def eat(self, kind=None):
k, v = self.peek()
if kind and k != kind:
raise SyntaxError(f"期望 {kind},实际 {k}")
self.i += 1
return v
def parse(self):
node = self.parse_or()
if self.i != len(self.tokens):
raise SyntaxError(f"多余 : {self.peek()}")
return node
def parse_or(self):
node = self.parse_and()
while self.peek() == ("logic", "or"):
self.eat(); node = ("or", node, self.parse_and())
return node
def parse_and(self):
node = self.parse_not()
while self.peek() == ("logic", "and"):
self.eat(); node = ("and", node, self.parse_not())
return node
def parse_not(self):
if self.peek() == ("logic", "not"):
self.eat(); return ("not", self.parse_not())
return self.parse_cmp()
def parse_cmp(self):
left = self.parse_atom()
k, v = self.peek()
if k == "op":
self.eat(); return ("cmp", v, left, self.parse_atom())
return left
def parse_atom(self):
k, v = self.peek()
if k == "lp":
self.eat(); node = self.parse_or(); self.eat("rp"); return node
if k in ("num", "str", "bool"):
self.eat(); return ("lit", v)
if k == "id":
self.eat(); return ("var", v)
raise SyntaxError(f"意外的 : {(k, v)}")
def evaluate(node, env):
t = node[0]
if t == "lit": return node[1]
if t == "var": return env[node[1]]
if t == "not": return not evaluate(node[1], env)
if t == "and": return evaluate(node[1], env) and evaluate(node[2], env)
if t == "or": return evaluate(node[1], env) or evaluate(node[2], env)
if t == "cmp":
op, l, r = node[1], evaluate(node[2], env), evaluate(node[3], env)
return {">": l > r, "<": l < r, ">=": l >= r,
"<=": l <= r, "==": l == r, "!=": l != r}[op]
raise ValueError(t)
def run(expr, env):
return evaluate(Parser(tokenize(expr)).parse(), env)
if __name__ == "__main__":
expr = "age >= 18 and (score > 600 or vip == True)"
print(run(expr, {"age": 25, "score": 580, "vip": True})) # True
print(run(expr, {"age": 16, "score": 700, "vip": False})) # False
print(run("not (a == 1 or b > 2)", {"a": 0, "b": 0})) # True
运行与验证
- 保存为
parser.py,执行python parser.py。 - 预期输出三行:
True、False、True(由代码逻辑唯一确定)。 - 通过条件:三行输出与上述一致,且无异常抛出。
- 可自行追加用例:
run("score > 600 and not vip", {"score": 700, "vip": False})应返回True;非法表达式如"age >= "应抛SyntaxError。
关键点说明
- 优先级由函数调用链保证:
or → and → not → cmp → atom,括号在atom中递归回or。 - AST 使用元组,
("cmp", ">=", ("var","age"), ("lit",18))可直接打印检查。 - 变量缺失会抛
KeyError,接入规则引擎时可在evaluate的var分支改为返回None或默认值。