语法树解析器

📅 2026 年 10 月 10 日 👤 jyjswk 📁 编程 💬 暂无评论

标准的正则表达式只能做到词法分析,想要实现语法树需要借助递归来实现“先局部,后整体”的逻辑结构,具体例子可以参考算数运算、编程语法。
EBNF语法的形式定义如下:

expr ::= term { (+|-) term }*
term ::= factor { (|/) factor }
factor ::= ( expr ) | NUM

第一步、词法分析

def tokenize(text):
    tokens = []
    i = 0
    while i < len(text):
        char = text[i]
        
        # 1. 跳过空格
        if char.isspace():
            i += 1
            
        # 2. 如果遇到数字,就向后贪婪读取,直到拼接出完整的整数
        elif char.isdigit():
            num_str = ""
            while i < len(text) and text[i].isdigit():
                num_str += text[i]
                i += 1
            tokens.append(('NUM', int(num_str)))  # 存入完整的数字
            
        # 3. 如果遇到运算符号或括号,直接作为一个零件存入
        elif char in '+-*/()':
            tokens.append(('SYM', char))
            i += 1
            
        else:
            raise ValueError(f"不认识的字符: {char}")
            
    return tokens

# 演示:切分一个复杂的算术表达式
text = "3 + 4 * (5 - 2)"
tokens = tokenize(text)
print(f"切分结果: {tokens}")
# 输出: [('NUM', 3), ('SYM', '+'), ('NUM', 4), ('SYM', '*'), ('SYM', '('), ('NUM', 5), ('SYM', '-'), ('NUM', 2), ('SYM', ')')]

第二步、语法分析(这里用EBNF语法规则)

class Parser:
    def __init__(self, tokens):
        self.tokens = tokens
        self.pos = 0  # 游标卡尺,记录当前读到了哪个零件

    def current_token(self):
        """查看当前零件,但不吃掉它"""
        if self.pos < len(self.tokens):
            return self.tokens[self.pos]
        return ('EOF', None)  # 解析完毕

    def consume(self):
        """吃掉当前零件,游标推进一步"""
        token = self.current_token()
        self.pos += 1
        return token

    # 规则 3: factor ::= ( expr ) | NUM  (最底层,处理数字和括号)
    def parse_factor(self):
        token = self.consume()
        
        if token[0] == 'NUM':
            # 如果是纯数字,直接返回它的值
            return token[1]
            
        elif token[0] == 'SYM' and token[1] == '(':
            # 【递归的核心在这里!】遇到左括号,立刻回头调用最高层的 parse_expr
            result = self.parse_expr()
            
            # 表达式计算完后,必须紧跟着一个右括号,否则报错
            if self.consume()[1] != ')':
                raise SyntaxError("缺少右括号 ')'")
            return result

    # 规则 2: term ::= factor { (*|/) factor } (中层,处理高优先级的乘除法)
    def parse_term(self):
        # 先获取左边的一个因子
        left = self.parse_factor()
        
        # 只要后面紧跟着乘号或除号,就一直进行乘除运算(把它们焊死成一个整体)
        while self.current_token()[0] == 'SYM' and self.current_token()[1] in ('*', '/'):
            op = self.consume()[1]         # 拿出运算符
            right = self.parse_factor()    # 获取右边的因子
            
            if op == '*':
                left = left * right
            elif op == '/':
                left = left / right
                
        return left

    # 规则 1: expr ::= term { (+|-) term } (最高层,处理低优先级的加减法)
    def parse_expr(self):
        # 先获取左边的一个大块(项)
        left = self.parse_term()
        
        # 只要后面紧跟着加号或减号,就进行加减运算
        while self.current_token()[0] == 'SYM' and self.current_token()[1] in ('+', '-'):
            op = self.consume()[1]         # 拿出运算符
            right = self.parse_term()      # 获取右边的大块(项)
            
            if op == '+':
                left = left + right
            elif op == '-':
                left = left - right
                
        return left

# 终极测试
parser = Parser(tokens)
result = parser.parse_expr()
print(f"\n算术表达式 {text} 的最终计算结果是: {result}")
# 输出: 算术表达式 3 + 4 * (5 - 2) 的最终计算结果是: 15

💬 评论区

✍️ 发表你的看法

↑ -->