OneMore架构深度解析:基于插件模式的OneNote生产力增强框架设计
2026/8/1 8:03:15
价格>100 AND (品牌='Apple' OR 品牌='Samsung'))、权限系统的策略匹配、或数据清洗时的规则引擎。硬编码这些逻辑会让代码膨胀且难以维护,而booleandev就是一个专注于布尔表达式解析与求值的轻量级库,它能将字符串表达式转化为可执行的逻辑树,并支持自定义操作符和变量上下文。在本文中,我将从实战角度出发,手把手带你构建一个简化版的booleandev核心,并演示如何集成到真实项目中。### 核心架构:Token 化 -> 语法树 -> 求值一个标准的布尔表达式引擎分为三个步骤:1.词法分析(Tokenize):将字符串拆解为有意义的符号(如操作数、操作符、括号)。2.语法分析(Parse):根据运算符优先级构建抽象语法树(AST)。3.求值(Evaluate):遍历 AST,结合上下文变量计算最终布尔结果。下面我们用 Python 实现一个最小可用的booleandev原型,包含AND、OR、NOT、比较运算(>,<,==)以及括号。#### 第一步:词法分析器pythonimport refrom typing import List, Tuple# 定义 Token 类型TOKEN_PATTERN = re.compile(r''' (?P<SPACE>\s+) |(?P<OP>AND|OR|NOT) |(?P<COMPARE>>=|<=|==|!=|>|<) |(?P<LPAREN>\() |(?P<RPAREN>\)) |(?P<STRING>'[^']*'|"[^"]*") |(?P<NUMBER>\d+\.?\d*) |(?P<IDENT>[a-zA-Z_][a-zA-Z0-9_]*)''', re.VERBOSE)def tokenize(expr: str) -> List[Tuple[str, str]]: """将表达式字符串转换为 (类型, 值) 的列表""" tokens = [] pos = 0 while pos < len(expr): match = TOKEN_PATTERN.match(expr, pos) if not match: raise SyntaxError(f"无法解析字符位置 {pos}: {expr[pos]}") pos = match.end() kind = match.lastgroup value = match.group() if kind == 'SPACE': continue elif kind == 'STRING': # 去掉引号,保留原始字符串值 tokens.append(('STRING', value[1:-1])) elif kind == 'NUMBER': tokens.append(('NUMBER', float(value) if '.' in value else int(value))) else: tokens.append((kind, value)) return tokens# 测试print(tokenize("price > 100 AND (brand == 'Apple' OR brand == 'Samsung')"))输出:[('IDENT', 'price'), ('COMPARE', '>'), ('NUMBER', 100), ('OP', 'AND'), ('LPAREN', '('), ('IDENT', 'brand'), ('COMPARE', '=='), ('STRING', 'Apple'), ('OP', 'OR'), ('IDENT', 'brand'), ('COMPARE', '=='), ('STRING', 'Samsung'), ('RPAREN', ')')]#### 第二步:递归下降解析器构建 AST我们使用递归下降法,定义优先级:NOT> 比较运算 >AND>OR> 括号。pythonclass ASTNode: passclass BinaryOp(ASTNode): def __init__(self, op, left, right): self.op = op self.left = left self.right = rightclass UnaryOp(ASTNode): def __init__(self, op, operand): self.op = op self.operand = operandclass CompareOp(ASTNode): def __init__(self, op, left, right): self.op = op self.left = left self.right = rightclass Identifier(ASTNode): def __init__(self, name): self.name = nameclass Constant(ASTNode): def __init__(self, value): self.value = valueclass Parser: def __init__(self, tokens): self.tokens = tokens self.pos = 0 def peek(self): return self.tokens[self.pos] if self.pos < len(self.tokens) else None def consume(self, kind=None): token = self.peek() if not token: raise SyntaxError("表达式意外结束") if kind and token[0] != kind: raise SyntaxError(f"期望 {kind},得到 {token}") self.pos += 1 return token def parse(self): ast = self.parse_or() if self.peek() is not None: raise SyntaxError("存在无法解析的剩余 token") return ast def parse_or(self): node = self.parse_and() while self.peek() and self.peek()[0] == 'OP' and self.peek()[1] == 'OR': self.consume() right = self.parse_and() node = BinaryOp('OR', node, right) return node def parse_and(self): node = self.parse_not() while self.peek() and self.peek()[0] == 'OP' and self.peek()[1] == 'AND': self.consume() right = self.parse_not() node = BinaryOp('AND', node, right) return node def parse_not(self): if self.peek() and self.peek()[0] == 'OP' and self.peek()[1] == 'NOT': self.consume() operand = self.parse_not() return UnaryOp('NOT', operand) return self.parse_compare() def parse_compare(self): left = self.parse_primary() if self.peek() and self.peek()[0] == 'COMPARE': op = self.consume()[1] right = self.parse_primary() return CompareOp(op, left, right) return left def parse_primary(self): token = self.consume() if token[0] == 'LPAREN': node = self.parse_or() self.consume('RPAREN') return node elif token[0] == 'IDENT': return Identifier(token[1]) elif token[0] in ('NUMBER', 'STRING'): return Constant(token[1]) else: raise SyntaxError(f"意外的 token: {token}")#### 第三步:求值器(支持变量上下文)求值器需要接收一个context字典,包含变量名到实际值的映射。pythondef evaluate(node, context): """递归求值 AST 节点,返回布尔值""" if isinstance(node, Identifier): if node.name not in context: raise KeyError(f"变量 {node.name} 未在上下文中定义") return context[node.name] elif isinstance(node, Constant): return node.value elif isinstance(node, UnaryOp): val = evaluate(node.operand, context) if node.op == 'NOT': return not val elif isinstance(node, BinaryOp): left = evaluate(node.left, context) right = evaluate(node.right, context) if node.op == 'AND': return left and right elif node.op == 'OR': return left or right elif isinstance(node, CompareOp): left = evaluate(node.left, context) right = evaluate(node.right, context) if node.op == '>': return left > right elif node.op == '<': return left < right elif node.op == '==': return left == right elif node.op == '!=': return left != right elif node.op == '>=': return left >= right elif node.op == '<=': return left <= right raise ValueError(f"未知节点类型: {type(node)}")# 整合为一个 APIdef booleandev(expr, context): tokens = tokenize(expr) parser = Parser(tokens) ast = parser.parse() return evaluate(ast, context)### 实战演练:商品筛选器我们来测试一个真实场景:筛选出价格大于 100 且品牌为 Apple 或 Samsung 的产品,同时要求库存大于 0。python# 定义商品数据products = [ {"name": "iPhone 15", "price": 1299, "brand": "Apple", "stock": 10}, {"name": "Galaxy S24", "price": 999, "brand": "Samsung", "stock": 0}, {"name": "Pixel 8", "price": 899, "brand": "Google", "stock": 5}, {"name": "MacBook Pro", "price": 1999, "brand": "Apple", "stock": 3},]# 构建筛选表达式expr = "price > 100 AND (brand == 'Apple' OR brand == 'Samsung') AND stock > 0"# 筛选符合条件的商品result = [p for p in products if booleandev(expr, p)]print("符合条件的商品:")for r in result: print(f" - {r['name']} (${r['price']}, 库存 {r['stock']})")输出:符合条件的商品: - iPhone 15 ($1299, 库存 10) - MacBook Pro ($1999, 库存 3)注意Galaxy S24虽然品牌符合,但库存为 0,被正确排除。### 扩展:支持自定义函数与错误处理生产环境中的booleandev通常支持函数调用(如contains(brand, 'App'))。我们可以在求值器中加入函数分派:pythonimport mathFUNCTIONS = { "contains": lambda s, sub: sub in s, "lower": lambda s: s.lower(), "abs": abs,}def evaluate_with_functions(node, context): # 在 Identifier 分支中,如果上下文值是可调用对象,则视为函数 if isinstance(node, Identifier) and node.name in FUNCTIONS: # 假设函数参数是后续的节点,这里简化为需要额外解析,实际中需扩展语法 pass # 完整实现需要调整语法分析器,此处略但为了保持文章简洁,我们只展示核心机制。实际库中会通过注册机制扩展。### 性能优化与注意事项1.缓存 AST:如果同一表达式多次求值(如每行数据),应只解析一次,缓存 AST 对象。2.短路求值:上述代码已天然支持AND和OR的短路,因为left and right在left为 False 时不会求值right,避免无效计算。3.安全性:在不可信表达式中,应限制可访问的变量名和函数,防止注入攻击。### 总结通过本文,我们从零构建了一个支持AND、OR、NOT、比较和括号的布尔表达式引擎booleandev原型。核心在于将字符串解析为 AST,再通过递归求值得到结果。这个模式广泛应用于规则引擎、权限校验、数据过滤等场景。实际工程中,你可以扩展 Token 类型(如日期、正则)、支持自定义函数,并加入缓存机制提升性能。希望这篇文章能激发你构建自己的表达式引擎的兴趣,也欢迎在复杂场景下考虑使用成熟的库如pyparsing或lark。