花拾录
← 返回知识库

从零写一个简易解释器:词法、语法树与求值三段式入门

编程语言AI2026/09/240 阅读0 评论

解释器是编程语言的核心组件之一,它负责将源代码转化为可执行的结果。本文以最简单的算术表达式语言为例,带你走完词法分析、语法分析和求值三个阶段,理解解释器的基本骨架。代码示例使用 Python,因其简洁易读,但思路适用于任何语言。

目标语言定义

我们支持以下语法:

  • 整数(如 42)
  • 加法 +、减法 -、乘法 *、除法 /
  • 括号 () 用于改变优先级

例如:(1 + 2) * 3 应求值为 9。

第一阶段:词法分析(Lexer)

词法分析将字符流转换为有意义的记号(Token)。每个 Token 包含类型和值。

# 定义 Token 类型
INTEGER = 'INTEGER'
PLUS = 'PLUS'
MINUS = 'MINUS'
MUL = 'MUL'
DIV = 'DIV'
LPAREN = 'LPAREN'
RPAREN = 'RPAREN'
EOF = 'EOF'

class Token:
    def __init__(self, type, value):
        self.type = type
        self.value = value
    def __repr__(self):
        return f'Token({self.type}, {self.value})'

class Lexer:
    def __init__(self, text):
        self.text = text
        self.pos = 0
        self.current_char = self.text[self.pos] if self.text else None

    def advance(self):
        self.pos += 1
        if self.pos >= len(self.text):
            self.current_char = None
        else:
            self.current_char = self.text[self.pos]

    def skip_whitespace(self):
        while self.current_char is not None and self.current_char.isspace():
            self.advance()

    def integer(self):
        result = ''
        while self.current_char is not None and self.current_char.isdigit():
            result += self.current_char
            self.advance()
        return int(result)

    def get_next_token(self):
        while self.current_char is not None:
            if self.current_char.isspace():
                self.skip_whitespace()
                continue
            if self.current_char.isdigit():
                return Token(INTEGER, self.integer())
            if self.current_char == '+':
                self.advance()
                return Token(PLUS, '+')
            if self.current_char == '-':
                self.advance()
                return Token(MINUS, '-')
            if self.current_char == '*':
                self.advance()
                return Token(MUL, '*')
            if self.current_char == '/':
                self.advance()
                return Token(DIV, '/')
            if self.current_char == '(':
                self.advance()
                return Token(LPAREN, '(')
            if self.current_char == ')':
                self.advance()
                return Token(RPAREN, ')')
            raise Exception(f'非法字符: {self.current_char}')
        return Token(EOF, None)

第二阶段:语法分析(Parser)

语法分析将 Token 序列构建为抽象语法树(AST)。我们采用递归下降法,为运算符定义优先级:+ 和 - 优先级低于 * 和 /。

# AST 节点
class BinOp:
    def __init__(self, left, op, right):
        self.left = left
        self.op = op
        self.right = right

class Num:
    def __init__(self, value):
        self.value = value

class Parser:
    def __init__(self, lexer):
        self.lexer = lexer
        self.current_token = self.lexer.get_next_token()

    def eat(self, token_type):
        if self.current_token.type == token_type:
            self.current_token = self.lexer.get_next_token()
        else:
            raise Exception(f'期望 {token_type},实际 {self.current_token.type}')

    def factor(self):
        """factor : INTEGER | LPAREN expr RPAREN"""
        token = self.current_token
        if token.type == INTEGER:
            self.eat(INTEGER)
            return Num(token.value)
        elif token.type == LPAREN:
            self.eat(LPAREN)
            node = self.expr()
            self.eat(RPAREN)
            return node

    def term(self):
        """term : factor ((MUL | DIV) factor)*"""
        node = self.factor()
        while self.current_token.type in (MUL, DIV):
            token = self.current_token
            if token.type == MUL:
                self.eat(MUL)
            elif token.type == DIV:
                self.eat(DIV)
            node = BinOp(left=node, op=token, right=self.factor())
        return node

    def expr(self):
        """expr : term ((PLUS | MINUS) term)*"""
        node = self.term()
        while self.current_token.type in (PLUS, MINUS):
            token = self.current_token
            if token.type == PLUS:
                self.eat(PLUS)
            elif token.type == MINUS:
                self.eat(MINUS)
            node = BinOp(left=node, op=token, right=self.term())
        return node

第三阶段:求值(Evaluator)

求值器遍历 AST,计算最终结果。

class NodeVisitor:
    def visit(self, node):
        method_name = 'visit_' + type(node).__name__
        visitor = getattr(self, method_name, self.generic_visit)
        return visitor(node)

    def generic_visit(self, node):
        raise Exception(f'未处理的节点类型: {type(node).__name__}')

class Interpreter(NodeVisitor):
    def __init__(self, parser):
        self.parser = parser

    def visit_BinOp(self, node):
        left = self.visit(node.left)
        right = self.visit(node.right)
        if node.op.type == PLUS:
            return left + right
        elif node.op.type == MINUS:
            return left - right
        elif node.op.type == MUL:
            return left * right
        elif node.op.type == DIV:
            return left // right  # 整数除法

    def visit_Num(self, node):
        return node.value

    def interpret(self):
        tree = self.parser.expr()
        return self.visit(tree)

运行示例

def main():
    while True:
        try:
            text = input('calc> ')
        except EOFError:
            break
        if not text:
            continue
        lexer = Lexer(text)
        parser = Parser(lexer)
        interpreter = Interpreter(parser)
        result = interpreter.interpret()
        print(result)

if __name__ == '__main__':
    main()

运行后输入 (1 + 2) * 3,输出 9。

总结与扩展

本文实现了一个支持四则运算和括号的简易解释器,核心分为三步:词法分析生成 Token,语法分析构建 AST,求值遍历 AST 计算结果。你可以在此基础上扩展:

  • 支持浮点数、变量和赋值
  • 增加一元负号、比较运算符
  • 引入符号表实现变量存储
  • 添加错误恢复和更友好的报错信息

理解这个三段式结构,是学习更复杂解释器(如 Python、JavaScript 引擎)的坚实基础。

评论(0)

  • 还没有评论,来抢沙发~

相关文章