基于虎书 Modern Compiler Implementation 第2版:用Java实现一个简易编译器(附完整代码)

📅 2026/7/13 8:57:54
基于虎书 Modern Compiler Implementation 第2版:用Java实现一个简易编译器(附完整代码)
基于虎书实现Java简易编译器的实战指南编译原理作为计算机科学的核心课程长期被视为高门槛的技术领域。许多开发者即便掌握了Java等编程语言面对编译器开发仍感到无从下手。Andrew W. Appel的《Modern Compiler Implementation in Java》俗称虎书正是打破这一困境的实践指南。本文将带您从零开始基于虎书第2版实现一个支持算术表达式和变量的简易编译器完整呈现从词法分析到代码生成的全过程。1. 编译器开发环境搭建在开始编译器实现前需要准备合适的开发环境。推荐使用IntelliJ IDEA或Eclipse等现代Java IDE它们对大型项目的支持更为完善。以下是环境配置的关键步骤Java开发工具包确保安装JDK 8或更高版本构建工具建议使用Maven或Gradle管理项目依赖测试框架JUnit 5用于编写单元测试辅助工具ANTLR 4.9用于语法分析ASM 9.2用于字节码生成创建项目时建议采用标准的Maven目录结构compiler-project/ ├── src/ │ ├── main/ │ │ ├── java/ # 主代码目录 │ │ └── resources/ # 资源文件 │ └── test/ # 测试代码 ├── pom.xml # Maven配置文件 └── README.md提示在pom.xml中添加ANTLR和ASM依赖时注意版本兼容性。虎书第2版示例代码基于较旧的Java版本可能需要适当调整。2. 词法分析器实现词法分析是编译器的第一道工序负责将源代码转换为标记(token)序列。我们采用虎书中推荐的递归下降分析法实现一个简易的词法分析器。首先定义语言的基本词法单元public enum TokenType { // 关键字 LET, IN, IF, THEN, ELSE, // 运算符 PLUS, MINUS, TIMES, DIVIDE, ASSIGN, EQUAL, LESS, GREATER, AND, OR, NOT, // 分隔符 LPAREN, RPAREN, SEMICOLON, COMMA, // 字面量 INTEGER, FLOAT, STRING, BOOLEAN, // 标识符 IDENTIFIER, // 特殊标记 EOF }接着实现词法分析器核心逻辑public class Lexer { private final String input; private int position 0; public Lexer(String input) { this.input input.trim(); } public Token nextToken() { if (position input.length()) { return new Token(TokenType.EOF, ); } char current input.charAt(position); // 跳过空白字符 if (Character.isWhitespace(current)) { position; return nextToken(); } // 识别数字字面量 if (Character.isDigit(current)) { return readNumber(); } // 识别标识符和关键字 if (Character.isLetter(current)) { return readIdentifier(); } // 识别运算符和分隔符 switch (current) { case : position; return new Token(TokenType.PLUS, ); case -: position; return new Token(TokenType.MINUS, -); // 其他运算符处理... default: throw new RuntimeException(Unexpected character: current); } } private Token readNumber() { // 实现数字读取逻辑 } private Token readIdentifier() { // 实现标识符和关键字识别 } }词法分析器的测试案例Test public void testLexer() { String input let x 5 3 in x * 2; Lexer lexer new Lexer(input); ListToken expected Arrays.asList( new Token(TokenType.LET, let), new Token(TokenType.IDENTIFIER, x), // 其他预期token... ); for (Token expectedToken : expected) { assertEquals(expectedToken, lexer.nextToken()); } }3. 语法分析与抽象语法树构建语法分析阶段将词法单元组织成语法结构通常表示为抽象语法树(AST)。我们采用递归下降分析法实现语法解析器。首先定义AST节点类型public interface ASTNode { T T accept(ASTVisitorT visitor); } public class VariableDecl implements ASTNode { public final String name; public final ASTNode initializer; public VariableDecl(String name, ASTNode initializer) { this.name name; this.initializer initializer; } Override public T T accept(ASTVisitorT visitor) { return visitor.visit(this); } } // 其他AST节点类型BinaryExpr, IfExpr, FunctionCall等语法解析器核心实现public class Parser { private final Lexer lexer; private Token currentToken; public Parser(Lexer lexer) { this.lexer lexer; this.currentToken lexer.nextToken(); } public ASTNode parse() { return parseExpression(); } private ASTNode parseExpression() { ASTNode left parseTerm(); while (currentToken.type TokenType.PLUS || currentToken.type TokenType.MINUS) { Token op currentToken; currentToken lexer.nextToken(); ASTNode right parseTerm(); left new BinaryExpr(op.type, left, right); } return left; } private ASTNode parseTerm() { // 类似parseExpression处理乘除运算 } private ASTNode parseFactor() { // 处理基本元素数字、标识符、括号表达式等 } }为支持不同的编译器阶段类型检查、代码生成等我们实现访问者模式public interface ASTVisitorT { T visit(VariableDecl node); T visit(BinaryExpr node); T visit(IfExpr node); // 其他节点类型的访问方法 }4. 语义分析与类型检查语义分析确保程序的逻辑正确性包括变量声明检查、类型检查等。我们扩展ASTVisitor实现类型检查器public class TypeChecker implements ASTVisitorType { private final SymbolTable symbolTable new SymbolTable(); Override public Type visit(VariableDecl node) { Type initType node.initializer.accept(this); symbolTable.put(node.name, initType); return initType; } Override public Type visit(BinaryExpr node) { Type leftType node.left.accept(this); Type rightType node.right.accept(this); if (!leftType.equals(rightType)) { throw new TypeException(Type mismatch in binary operation); } switch (node.operator) { case PLUS: case MINUS: case TIMES: case DIVIDE: if (!(leftType instanceof NumberType)) { throw new TypeException(Arithmetic operation on non-number); } return leftType; // 其他运算符检查... default: return BuiltInType.BOOLEAN; } } }符号表实现示例public class SymbolTable { private final MapString, Type symbols new HashMap(); private SymbolTable parent; public void put(String name, Type type) { if (symbols.containsKey(name)) { throw new SemanticException(Duplicate identifier: name); } symbols.put(name, type); } public Type lookup(String name) { Type type symbols.get(name); if (type null parent ! null) { return parent.lookup(name); } return type; } }5. 中间代码生成与优化现代编译器通常先生成与机器无关的中间表示(IR)再进行优化。我们采用虎书推荐的树型IR表示public abstract class IRExpr { public abstract IRType type(); } public class IRBinOp extends IRExpr { public enum Op { ADD, SUB, MUL, DIV, AND, OR } public final Op op; public final IRExpr left, right; public IRBinOp(Op op, IRExpr left, IRExpr right) { this.op op; this.left left; this.right right; } Override public IRType type() { return left.type(); // 二元操作结果类型与操作数相同 } }从AST到IR的转换public class IRGenerator implements ASTVisitorIRExpr { Override public IRExpr visit(BinaryExpr node) { IRExpr left node.left.accept(this); IRExpr right node.right.accept(this); IRBinOp.Op op; switch (node.operator) { case PLUS: op IRBinOp.Op.ADD; break; case MINUS: op IRBinOp.Op.SUB; break; // 其他运算符转换... default: throw new UnsupportedOperationException(); } return new IRBinOp(op, left, right); } }简单的IR优化——常量折叠public class ConstantFolder { public IRExpr fold(IRExpr expr) { if (expr instanceof IRBinOp) { IRBinOp binOp (IRBinOp)expr; IRExpr left fold(binOp.left); IRExpr right fold(binOp.right); if (left instanceof IRConstant right instanceof IRConstant) { return evalConstOp(binOp.op, (IRConstant)left, (IRConstant)right); } return new IRBinOp(binOp.op, left, right); } return expr; } private IRConstant evalConstOp(IRBinOp.Op op, IRConstant l, IRConstant r) { // 实现各种运算符的常量计算 } }6. 目标代码生成最后阶段将优化后的IR转换为JVM字节码。我们使用ASM库直接生成.class文件public class BytecodeGenerator { private final ClassWriter cw new ClassWriter(ClassWriter.COMPUTE_FRAMES); public byte[] generate(IRFunction function) { cw.visit(Opcodes.V1_8, Opcodes.ACC_PUBLIC, function.name, null, java/lang/Object, null); generateConstructor(); generateMethods(function); cw.visitEnd(); return cw.toByteArray(); } private void generateMethods(IRFunction function) { MethodVisitor mv cw.visitMethod(Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC, main, ([Ljava/lang/String;)V, null, null); for (IRStmt stmt : function.body) { generateStmt(mv, stmt); } mv.visitInsn(Opcodes.RETURN); mv.visitMaxs(0, 0); // ASM自动计算 mv.visitEnd(); } private void generateStmt(MethodVisitor mv, IRStmt stmt) { if (stmt instanceof IRExprStmt) { generateExpr(mv, ((IRExprStmt)stmt).expr); } // 其他语句类型... } private void generateExpr(MethodVisitor mv, IRExpr expr) { if (expr instanceof IRBinOp) { IRBinOp binOp (IRBinOp)expr; generateExpr(mv, binOp.left); generateExpr(mv, binOp.right); switch (binOp.op) { case ADD: mv.visitInsn(Opcodes.IADD); break; case SUB: mv.visitInsn(Opcodes.ISUB); break; // 其他运算符... } } // 其他表达式类型... } }7. 编译器集成与测试将各阶段组合成完整编译器public class Compiler { public byte[] compile(String source) throws CompileException { // 词法分析 Lexer lexer new Lexer(source); // 语法分析 Parser parser new Parser(lexer); ASTNode ast parser.parse(); // 语义分析 TypeChecker typeChecker new TypeChecker(); ast.accept(typeChecker); // 中间代码生成 IRGenerator irGen new IRGenerator(); IRExpr ir ast.accept(irGen); // 优化 IRExpr optimized new ConstantFolder().fold(ir); // 目标代码生成 BytecodeGenerator codeGen new BytecodeGenerator(); return codeGen.generate(new IRFunction(Main, optimized)); } }测试编译器功能public class CompilerTest { Test public void testArithmetic() throws Exception { String source let x (5 3) * 2 in x - 1; byte[] bytecode new Compiler().compile(source); // 使用反射执行生成的字节码 Class? cls new ByteArrayClassLoader() .defineClass(Main, bytecode); Method main cls.getMethod(main, String[].class); main.invoke(null, (Object)new String[0]); } } class ByteArrayClassLoader extends ClassLoader { public Class? defineClass(String name, byte[] b) { return defineClass(name, b, 0, b.length); } }8. 扩展语言功能基础编译器完成后可以逐步扩展语言功能控制结构添加if语句和while循环函数定义与调用实现函数作用域和调用栈复合数据类型增加数组和结构体支持标准库提供基本的I/O和数学函数以函数支持为例需要扩展的组件AST节点添加FunctionDecl和FunctionCall类型系统引入函数类型IR表示增加调用指令代码生成处理方法调用约定// 函数定义的AST表示 public class FunctionDecl implements ASTNode { public final String name; public final ListParameter params; public final ASTNode body; Override public T T accept(ASTVisitorT visitor) { return visitor.visit(this); } } // 函数调用的代码生成 private void generateCall(MethodVisitor mv, IRCall call) { generateExpr(mv, call.target); for (IRExpr arg : call.args) { generateExpr(mv, arg); } mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, call.target.type().internalName(), call.methodName, call.descriptor, false); }实现这些扩展后您的编译器将能够处理更复杂的程序结构逐步接近实用化的编程语言。