先搞懂:解释器到底做了什么?
你写的每一行代码,比如print(1+2)
,计算机其实根本“看不懂”——它只认识0和1。解释器的作用,就是当“翻译官”:把人类能读的代码,一步步转换成计算机能执行的指令。

举个简单的类比:你跟老外说“我想吃苹果”,翻译官要做三件事:
1. 把这句话拆成“我”“想”“吃”“苹果”(拆词);
2. 搞清楚语法结构:“我(主语)+想(谓语)+吃苹果(宾语)”(懂逻辑);
3. 用老外的语言说出来:“I want to eat an apple”(执行)。
解释器的工作,本质上就是这三步的“代码版”——只不过更严谨、更机械。
第一步:词法分析——把代码拆成“可识别的积木块”
词法分析(Lexical Analysis)是解释器的第一步,核心任务是将原始代码字符串拆成一个个“有意义的最小单元”,这些单元叫Token
(令牌)。
比如代码1+2*3
,词法分析后会变成这样的Token列表:[Number(1), Operator('+'), Number(2), Operator('*'), Number(3)]
词法分析的核心逻辑:状态机
词法分析器(也叫Tokenizer
)其实是一个有限状态机(FSM)——它会根据当前字符的类型,切换“状态”,直到收集完一个完整的Token。
比如处理数字的逻辑:
– 初始状态:遇到数字字符→进入“收集数字”状态;
– 收集数字状态:继续读后续字符,直到遇到非数字(比如+
或空格)→输出一个Number
Token;
– 回到初始状态,处理下一个字符。
动手写个简单的Tokenizer(Python版)
我们用Python实现一个能处理数字、运算符、变量名的Tokenizer,直接跑代码更直观:
from enum import Enum
# 定义Token类型,方便后续识别
class TokenType(Enum):
NUMBER = "NUMBER"
OP = "OP" # 运算符(+、-、*、/)
IDENTIFIER = "IDENTIFIER" # 变量名(比如x、abc)
EQUAL = "EQUAL" # 等于号=
class Token:
def __init__(self, type: TokenType, value: str):
self.type = type
self.value = value
def __repr__(self):
return f"Token({self.type.name}, {self.value})"
def tokenize(code: str) -> list[Token]:
tokens = []
i = 0
n = len(code)
while i < n:
# 跳过空格
if code[i].isspace():
i += 1
continue
# 处理运算符(+、-、*、/)
if code[i] in "+-*/":
tokens.append(Token(TokenType.OP, code[i]))
i += 1
continue
# 处理等于号
if code[i] == "=":
tokens.append(Token(TokenType.EQUAL, code[i]))
i += 1
continue
# 处理数字(整数)
if code[i].isdigit():
num = ""
while i < n and code[i].isdigit():
num += code[i]
i += 1
tokens.append(Token(TokenType.NUMBER, num))
continue
# 处理变量名(字母或下划线开头,后跟字母、数字、下划线)
if code[i].isalpha() or code[i] == "_":
ident = ""
while i < n and (code[i].isalnum() or code[i] == "_"):
ident += code[i]
i += 1
tokens.append(Token(TokenType.IDENTIFIER, ident))
continue
# 遇到无法识别的字符,抛出错误
raise ValueError(f"无法识别的字符: {code[i]} at position {i}")
return tokens
# 测试一下
if __name__ == "__main__":
code = "x = 10 + 2 * 3"
tokens = tokenize(code)
print(tokens)
# 输出:[Token(IDENTIFIER, x), Token(EQUAL, =), Token(NUMBER, 10), Token(OP, +), Token(NUMBER, 2), Token(OP, *), Token(NUMBER, 3)]
运行这段代码,你会看到x = 10 + 2 * 3
被拆成了清晰的Token列表——这就是词法分析的成果。
第二步:语法分析——把Token拼成“可执行的框架”(AST)
词法分析只解决了“拆积木”的问题,但这些积木怎么组合才符合语法?比如1+2*3
是对的,但+12
(缺左操作数)或1*+2
(运算符连用)是错的——这就需要语法分析(Syntax Analysis)。
语法分析的核心输出是抽象语法树(Abstract Syntax Tree,AST)——它用树结构表示代码的语法结构,比如1+2*3
的AST长这样:
+
/
1 *
/
2 3
语法分析的核心逻辑:上下文无关文法(CFG)
语法分析器会根据上下文无关文法(比如编程语言的语法规则),检查Token序列是否符合规则,并生成AST。
比如简单表达式的文法规则(优先级从高到低):
1. 乘法/除法:term → factor ( ('*' | '/') factor )*
2. 加法/减法:expression → term ( ('+' | '-') term )*
3. 因子(基础单元):factor → NUMBER | IDENTIFIER | '(' expression ')'
用递归下降法写个语法分析器
递归下降法(Recursive Descent)是最直观的语法分析方法——它用递归函数对应文法的每一条规则。比如:
– parse_expression()
处理加法/减法;
– parse_term()
处理乘法/除法;
– parse_factor()
处理基础单元(数字、变量、括号)。
我们继续用Python实现,基于上面的Tokenizer输出:
from typing import Optional, List
from tokenizer import Token, TokenType # 假设上面的Tokenizer存为tokenizer.py
# 定义AST节点类型
class ASTNode:
pass
class Number(ASTNode):
def __init__(self, value: int):
self.value = value
class BinOp(ASTNode):
def __init__(self, left: ASTNode, op: str, right: ASTNode):
self.left = left
self.op = op
self.right = right
class Parser:
def __init__(self, tokens: List[Token]):
self.tokens = tokens
self.pos = 0 # 当前处理到的Token位置
def peek(self) -> Optional[Token]:
"""看一眼当前Token,不移动指针"""
return self.tokens[self.pos] if self.pos < len(self.tokens) else None
def consume(self, expected_type: TokenType) -> Token:
"""消耗当前Token,如果类型不符则报错"""
token = self.peek()
if not token or token.type != expected_type:
raise SyntaxError(f"预期{expected_type.name},但得到{token.type.name if token else 'None'}")
self.pos += 1
return token
# 对应文法:factor → NUMBER | IDENTIFIER | '(' expression ')'
def parse_factor(self) -> ASTNode:
token = self.peek()
if token.type == TokenType.NUMBER:
self.consume(TokenType.NUMBER)
return Number(int(token.value))
elif token.type == TokenType.IDENTIFIER:
self.consume(TokenType.IDENTIFIER)
return Variable(token.value) # 这里简化,暂时不处理变量赋值
elif token.value == "(":
self.consume(TokenType.OP) # 假设括号是OP类型(实际应单独定义TokenType)
expr = self.parse_expression()
self.consume(TokenType.OP) # 处理右括号
return expr
else:
raise SyntaxError(f"无法解析的因子: {token}")
# 对应文法:term → factor ( ('*' | '/') factor )*
def parse_term(self) -> ASTNode:
node = self.parse_factor()
while self.peek() and self.peek().value in "*/":
op_token = self.consume(TokenType.OP)
right = self.parse_factor()
node = BinOp(left=node, op=op_token.value, right=right)
return node
# 对应文法:expression → term ( ('+' | '-') term )*
def parse_expression(self) -> ASTNode:
node = self.parse_term()
while self.peek() and self.peek().value in "+-":
op_token = self.consume(TokenType.OP)
right = self.parse_term()
node = BinOp(left=node, op=op_token.value, right=right)
return node
# 测试一下
if __name__ == "__main__":
from tokenizer import tokenize
code = "1 + 2 * 3"
tokens = tokenize(code)
parser = Parser(tokens)
ast = parser.parse_expression()
print(ast)
# 输出:BinOp(left=Number(1), op='+', right=BinOp(left=Number(2), op='*', right=Number(3)))
运行这段代码,你会得到一个BinOp
节点——这就是1+2*3
的AST结构。
第三步:执行AST——让“框架”动起来
有了AST,解释器的最后一步就是遍历并执行这个树(也叫“解释执行”)。执行的核心逻辑是递归遍历AST节点,根据节点类型做相应的操作。
执行AST的核心逻辑:递归求值
比如:
– 遇到Number
节点:直接返回它的value
;
– 遇到BinOp
节点:先递归计算左子树和右子树的值,再根据运算符计算结果。
动手写个AST执行器
我们继续用Python实现:
from ast_nodes import ASTNode, Number, BinOp # 假设AST节点存为ast_nodes.py
def eval_ast(node: ASTNode) -> int:
if isinstance(node, Number):
return node.value
elif isinstance(node, BinOp):
left_val = eval_ast(node.left)
right_val = eval_ast(node.right)
if node.op == "+":
return left_val + right_val
elif node.op == "-":
return left_val - right_val
elif node.op == "*":
return left_val * right_val
elif node.op == "/":
return left_val // right_val # 简化为整数除法
else:
raise ValueError(f"未知运算符: {node.op}")
else:
raise TypeError(f"无法执行的节点类型: {type(node)}")
# 测试一下
if __name__ == "__main__":
from tokenizer import tokenize
from parser import Parser
code = "1 + 2 * 3"
tokens = tokenize(code)
parser = Parser(tokens)
ast = parser.parse_expression()
result = eval_ast(ast)
print(result) # 输出:7
运行这段代码,你会看到1+2*3
的结果是7——这就是解释器的完整流程:词法分析→语法分析→AST执行。
最后:把三部分连起来,写个迷你解释器
现在,我们把Tokenize、Parse、Eval三个部分连起来,写一个能处理简单表达式的迷你解释器:
from tokenizer import tokenize
from parser import Parser
from evaluator import eval_ast
class MiniInterpreter:
def __init__(self):
pass
def run(self, code: str) -> int:
# 1. 词法分析
tokens = tokenize(code)
# 2. 语法分析
parser = Parser(tokens)
ast = parser.parse_expression()
# 3. 执行AST
return eval_ast(ast)
# 测试
if __name__ == "__main__":
interpreter = MiniInterpreter()
code = input("输入简单表达式: ")
# 比如输入"10 + 2 * 3 - 4",输出12
print("结果:", interpreter.run(code))
运行这个脚本,输入10 + 2 * 3 - 4
,你会得到结果12——这就是你自己写的解释器在工作!
那些你可能想问的问题
Q:解释器和编译器有什么区别?
A:解释器是“边翻译边执行”(比如Python),编译器是“先翻译成机器码再执行”(比如C)。但核心步骤(词法、语法、AST)是一样的。
Q:复杂的解释器(比如Python)还有哪些步骤?
A:比如语义分析(检查变量是否定义、类型是否匹配)、字节码生成(Python会把AST编译成字节码,再用虚拟机执行)、垃圾回收等,但核心原理还是“拆代码→拼结构→执行”。
Q:想深入学习,该看什么资料?
A:推荐《编译原理》(龙书)的前几章(词法、语法分析),以及《Crafting Interpreters》(手把手写解释器和编译器)。
到这里,你已经理解了解释器实现的核心原理——从“拆代码”到“跑代码”的全流程。下次再用Python或JavaScript时,你可以试着想:“这段代码的Token是什么?AST长什么样?解释器是怎么执行它的?”——这就是“知其然更知其所以然”的乐趣。
原创文章,作者:,如若转载,请注明出处:https://zube.cn/archives/347