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

运行与验证

  1. 保存为 parser.py,执行 python parser.py。
  2. 预期输出三行:True、False、True(由代码逻辑唯一确定)。
  3. 通过条件:三行输出与上述一致,且无异常抛出。
  4. 可自行追加用例: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 或默认值。