01-手写一个简单的编译器¶
项目简介¶
本项目将带你从零开始实现一个简单的编译器,支持基本的算术运算、变量定义和赋值。通过这个项目,你将深入理解编译器的工作原理。
项目目标: - ✅ 实现词法分析器(Lexer) - ✅ 实现语法分析器(Parser) - ✅ 实现代码生成器(Code Generator) - ✅ 支持整数运算、变量、赋值语句 - ✅ 生成x86-64汇编代码
项目架构¶
Text Only
┌─────────────────────────────────────────────────────────────────────┐
│ 简单编译器架构 │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ 源代码 │
│ int x = 10; │
│ int y = 20; │
│ int z = x + y; │
│ ↓ │
│ ┌───────────────────────────────────────────────────────────────┐ │
│ │ 词法分析器 (Lexer) │ │
│ │ • 输入: 源代码字符串 │ │
│ │ • 输出: Token列表 │ │
│ │ • Token: INT, IDENTIFIER, NUMBER, ASSIGN, PLUS, SEMICOLON │ │
│ └───────────────────────────────────────────────────────────────┘ │
│ ↓ │
│ ┌───────────────────────────────────────────────────────────────┐ │
│ │ 语法分析器 (Parser) │ │
│ │ • 输入: Token列表 │ │
│ │ • 输出: 抽象语法树 (AST) │ │
│ │ • AST节点: Program, VarDecl, BinaryOp, Number, Identifier │ │
│ └───────────────────────────────────────────────────────────────┘ │
│ ↓ │
│ ┌───────────────────────────────────────────────────────────────┐ │
│ │ 代码生成器 (Code Generator) │ │
│ │ • 输入: AST │ │
│ │ • 输出: x86-64汇编代码 │ │
│ │ • 生成: .s文件,可汇编链接成可执行文件 │ │
│ └───────────────────────────────────────────────────────────────┘ │
│ ↓ │
│ 汇编代码 │
│ .globl main │
│ main: │
│ ... │
│ │
└─────────────────────────────────────────────────────────────────────┘
第一步:词法分析器 (Lexer)¶
目标¶
将源代码字符串分解成Token序列。
支持的Token类型¶
C++
enum class TokenType {
// 关键字
INT, // int
RETURN, // return
// 标识符和字面量
IDENTIFIER, // 变量名、函数名
NUMBER, // 数字
// 运算符
ASSIGN, // =
PLUS, // +
MINUS, // -
MULTIPLY, // *
DIVIDE, // /
// 分隔符
SEMICOLON, // ;
LPAREN, // (
RPAREN, // )
LBRACE, // {
RBRACE, // }
// 特殊
END, // 文件结束
INVALID // 非法字符
};
实现代码¶
C++
// lexer.h
#ifndef LEXER_H
#define LEXER_H
#include <string>
#include <vector>
#include <cctype>
enum class TokenType {
INT, RETURN, IDENTIFIER, NUMBER,
ASSIGN, PLUS, MINUS, MULTIPLY, DIVIDE,
SEMICOLON, LPAREN, RPAREN, LBRACE, RBRACE,
END, INVALID
};
struct Token {
TokenType type;
std::string value;
int line;
int column;
};
class Lexer {
public:
Lexer(const std::string& source);
std::vector<Token> tokenize();
private:
std::string source;
size_t pos;
int line;
int column;
char current() const;
char advance();
void skipWhitespace();
Token readNumber();
Token readIdentifier();
Token readOperator();
};
#endif
C++
// lexer.cpp
#include "lexer.h"
Lexer::Lexer(const std::string& source)
: source(source), pos(0), line(1), column(1) {}
char Lexer::current() const {
if (pos >= source.length()) return '\0';
return source[pos];
}
char Lexer::advance() {
char c = current();
pos++;
if (c == '\n') {
line++;
column = 1;
} else {
column++;
}
return c;
}
void Lexer::skipWhitespace() {
while (std::isspace(current())) {
advance();
}
}
Token Lexer::readNumber() {
std::string value;
int startLine = line;
int startCol = column;
while (std::isdigit(current())) {
value += advance();
}
return {TokenType::NUMBER, value, startLine, startCol};
}
Token Lexer::readIdentifier() {
std::string value;
int startLine = line;
int startCol = column;
while (std::isalnum(current()) || current() == '_') {
value += advance();
}
TokenType type = TokenType::IDENTIFIER;
if (value == "int") type = TokenType::INT;
else if (value == "return") type = TokenType::RETURN;
return {type, value, startLine, startCol};
}
Token Lexer::readOperator() {
char c = current();
int startLine = line;
int startCol = column;
advance();
switch (c) {
case '=': return {TokenType::ASSIGN, "=", startLine, startCol};
case '+': return {TokenType::PLUS, "+", startLine, startCol};
case '-': return {TokenType::MINUS, "-", startLine, startCol};
case '*': return {TokenType::MULTIPLY, "*", startLine, startCol};
case '/': return {TokenType::DIVIDE, "/", startLine, startCol};
case ';': return {TokenType::SEMICOLON, ";", startLine, startCol};
case '(': return {TokenType::LPAREN, "(", startLine, startCol};
case ')': return {TokenType::RPAREN, ")", startLine, startCol};
case '{': return {TokenType::LBRACE, "{", startLine, startCol};
case '}': return {TokenType::RBRACE, "}", startLine, startCol};
default: return {TokenType::INVALID, std::string(1, c), startLine, startCol};
}
}
std::vector<Token> Lexer::tokenize() {
std::vector<Token> tokens;
while (current() != '\0') {
skipWhitespace();
if (current() == '\0') break;
if (std::isdigit(current())) {
tokens.push_back(readNumber());
} else if (std::isalpha(current()) || current() == '_') {
tokens.push_back(readIdentifier());
} else {
tokens.push_back(readOperator());
}
}
tokens.push_back({TokenType::END, "", line, column});
return tokens;
}
第二步:语法分析器 (Parser)¶
目标¶
将Token序列解析成抽象语法树(AST)。
AST节点定义¶
C++
// ast.h
#ifndef AST_H
#define AST_H
#include <string>
#include <vector>
#include <memory>
// 前向声明
struct ASTNode; // struct结构体:自定义复合数据类型
struct Program;
struct VarDecl;
struct BinaryOp;
struct Number;
struct Identifier;
struct ReturnStmt;
// 访问者模式
class ASTVisitor {
public:
virtual ~ASTVisitor() = default;
virtual void visit(Program* node) = 0;
virtual void visit(VarDecl* node) = 0;
virtual void visit(BinaryOp* node) = 0;
virtual void visit(Number* node) = 0;
virtual void visit(Identifier* node) = 0;
virtual void visit(ReturnStmt* node) = 0;
};
// AST节点基类
struct ASTNode {
virtual ~ASTNode() = default;
virtual void accept(ASTVisitor* visitor) = 0;
};
// 程序节点
struct Program : ASTNode {
std::vector<std::unique_ptr<ASTNode>> statements;
void accept(ASTVisitor* visitor) override { visitor->visit(this); }
};
// 变量声明
struct VarDecl : ASTNode {
std::string type;
std::string name;
std::unique_ptr<ASTNode> initializer;
void accept(ASTVisitor* visitor) override { visitor->visit(this); }
};
// 二元运算
struct BinaryOp : ASTNode {
std::string op;
std::unique_ptr<ASTNode> left;
std::unique_ptr<ASTNode> right;
void accept(ASTVisitor* visitor) override { visitor->visit(this); }
};
// 数字字面量
struct Number : ASTNode {
int value;
explicit Number(int v) : value(v) {}
void accept(ASTVisitor* visitor) override { visitor->visit(this); }
};
// 标识符
struct Identifier : ASTNode {
std::string name;
explicit Identifier(std::string n) : name(std::move(n)) {}
void accept(ASTVisitor* visitor) override { visitor->visit(this); }
};
// 返回语句
struct ReturnStmt : ASTNode {
std::unique_ptr<ASTNode> expression;
void accept(ASTVisitor* visitor) override { visitor->visit(this); }
};
#endif
Parser实现¶
C++
// parser.h
#ifndef PARSER_H
#define PARSER_H
#include "lexer.h"
#include "ast.h"
#include <memory>
class Parser {
public:
explicit Parser(const std::vector<Token>& tokens);
std::unique_ptr<Program> parse();
private:
std::vector<Token> tokens;
size_t pos;
Token& current();
Token& peek(size_t offset);
Token& advance();
bool match(TokenType type);
Token& expect(TokenType type);
std::unique_ptr<Program> parseProgram();
std::unique_ptr<ASTNode> parseStatement();
std::unique_ptr<VarDecl> parseVarDecl();
std::unique_ptr<ReturnStmt> parseReturnStmt();
std::unique_ptr<ASTNode> parseExpression();
std::unique_ptr<ASTNode> parseTerm();
std::unique_ptr<ASTNode> parseFactor();
};
#endif
C++
// parser.cpp
#include "parser.h"
#include <stdexcept>
Parser::Parser(const std::vector<Token>& tokens) : tokens(tokens), pos(0) {}
Token& Parser::current() {
return tokens[pos];
}
Token& Parser::peek(size_t offset) {
if (pos + offset >= tokens.size()) {
return tokens.back();
}
return tokens[pos + offset];
}
Token& Parser::advance() {
return tokens[pos++];
}
bool Parser::match(TokenType type) {
if (current().type == type) {
advance();
return true;
}
return false;
}
Token& Parser::expect(TokenType type) {
if (current().type != type) {
throw std::runtime_error("Expected token type " + std::to_string(static_cast<int>(type)));
}
return advance();
}
std::unique_ptr<Program> Parser::parse() {
return parseProgram();
}
std::unique_ptr<Program> Parser::parseProgram() {
auto program = std::make_unique<Program>();
while (current().type != TokenType::END) {
program->statements.push_back(parseStatement());
}
return program;
}
std::unique_ptr<ASTNode> Parser::parseStatement() {
if (current().type == TokenType::INT) {
return parseVarDecl();
} else if (current().type == TokenType::RETURN) {
return parseReturnStmt();
}
throw std::runtime_error("Unexpected token in statement");
}
std::unique_ptr<VarDecl> Parser::parseVarDecl() {
auto decl = std::make_unique<VarDecl>();
// int
decl->type = advance().value;
// identifier
decl->name = expect(TokenType::IDENTIFIER).value;
// = expression
if (match(TokenType::ASSIGN)) {
decl->initializer = parseExpression();
}
// ;
expect(TokenType::SEMICOLON);
return decl;
}
std::unique_ptr<ReturnStmt> Parser::parseReturnStmt() {
auto stmt = std::make_unique<ReturnStmt>();
advance(); // return
if (current().type != TokenType::SEMICOLON) {
stmt->expression = parseExpression();
}
expect(TokenType::SEMICOLON);
return stmt;
}
std::unique_ptr<ASTNode> Parser::parseExpression() {
auto left = parseTerm();
while (current().type == TokenType::PLUS || current().type == TokenType::MINUS) {
auto op = std::make_unique<BinaryOp>();
op->op = advance().value;
op->left = std::move(left);
op->right = parseTerm();
left = std::move(op);
}
return left;
}
std::unique_ptr<ASTNode> Parser::parseTerm() {
auto left = parseFactor();
while (current().type == TokenType::MULTIPLY || current().type == TokenType::DIVIDE) {
auto op = std::make_unique<BinaryOp>();
op->op = advance().value;
op->left = std::move(left);
op->right = parseFactor();
left = std::move(op);
}
return left;
}
std::unique_ptr<ASTNode> Parser::parseFactor() {
if (current().type == TokenType::NUMBER) {
int value = std::stoi(advance().value);
return std::make_unique<Number>(value);
} else if (current().type == TokenType::IDENTIFIER) {
return std::make_unique<Identifier>(advance().value);
} else if (match(TokenType::LPAREN)) {
auto expr = parseExpression();
expect(TokenType::RPAREN);
return expr;
}
throw std::runtime_error("Unexpected token in factor");
}
第三步:代码生成器 (Code Generator)¶
目标¶
将AST转换为x86-64汇编代码。
C++
// codegen.h
#ifndef CODEGEN_H
#define CODEGEN_H
#include "ast.h"
#include <sstream>
#include <unordered_map>
class CodeGenerator : public ASTVisitor {
public:
std::string generate(ASTNode* root);
// Visitor方法
void visit(Program* node) override;
void visit(VarDecl* node) override;
void visit(BinaryOp* node) override;
void visit(Number* node) override;
void visit(Identifier* node) override;
void visit(ReturnStmt* node) override;
private:
std::stringstream output;
std::unordered_map<std::string, int> variables; // 变量名 -> 栈偏移
int stackOffset = 0;
int labelCounter = 0;
void emit(const std::string& line);
std::string newLabel();
};
#endif
C++
// codegen.cpp
#include "codegen.h"
std::string CodeGenerator::generate(ASTNode* root) {
output.str("");
output.clear();
variables.clear();
stackOffset = 0;
labelCounter = 0;
// 第一遍:生成函数体代码,确定栈空间需求
root->accept(this);
std::string bodyCode = output.str();
// 第二遍:生成完整程序(现在已知栈空间大小)
output.str("");
output.clear();
// 汇编文件头
emit(".globl main");
emit(".text");
emit("main:");
emit(" push %rbp");
emit(" mov %rsp, %rbp");
// 为局部变量分配栈空间(16字节对齐)
// 必须在使用push/pop前预留空间,否则push会覆盖局部变量
if (stackOffset != 0) {
int stackSpace = ((-stackOffset) + 15) & ~15;
emit(" sub $" + std::to_string(stackSpace) + ", %rsp");
}
// 插入函数体代码
output << bodyCode;
// 函数结束
emit(" mov %rbp, %rsp");
emit(" pop %rbp");
emit(" ret");
return output.str();
}
void CodeGenerator::emit(const std::string& line) {
output << line << "\n";
}
std::string CodeGenerator::newLabel() {
return ".L" + std::to_string(labelCounter++);
}
void CodeGenerator::visit(Program* node) {
for (auto& stmt : node->statements) {
stmt->accept(this);
}
}
void CodeGenerator::visit(VarDecl* node) {
// 为变量分配栈空间
stackOffset -= 4; // 假设int为4字节
variables[node->name] = stackOffset;
if (node->initializer) {
// 计算初始值
node->initializer->accept(this);
// 结果在eax中,存入变量位置
emit(" mov %eax, " + std::to_string(stackOffset) + "(%rbp)");
}
}
void CodeGenerator::visit(BinaryOp* node) {
// 生成右操作数
node->right->accept(this);
emit(" push %rax"); // 保存右操作数
// 生成左操作数
node->left->accept(this);
emit(" pop %rbx"); // 恢复右操作数到rbx
// 执行运算
if (node->op == "+") {
emit(" add %ebx, %eax");
} else if (node->op == "-") {
emit(" sub %ebx, %eax");
} else if (node->op == "*") {
emit(" imul %ebx, %eax");
} else if (node->op == "/") {
emit(" mov %eax, %edx");
emit(" sar $31, %edx"); // 符号扩展
emit(" idiv %ebx");
}
}
void CodeGenerator::visit(Number* node) {
emit(" mov $" + std::to_string(node->value) + ", %eax");
}
void CodeGenerator::visit(Identifier* node) {
auto it = variables.find(node->name);
if (it != variables.end()) {
emit(" mov " + std::to_string(it->second) + "(%rbp), %eax");
} else {
throw std::runtime_error("Undefined variable: " + node->name);
}
}
void CodeGenerator::visit(ReturnStmt* node) {
if (node->expression) {
node->expression->accept(this);
// 结果已经在eax中,作为返回值
} else {
emit(" mov $0, %eax");
}
}
第四步:主程序¶
C++
// main.cpp
#include <iostream> // 引入头文件
#include <fstream>
#include "lexer.h"
#include "parser.h"
#include "codegen.h"
int main(int argc, char* argv[]) { // 指针:存储变量的内存地址
if (argc < 2) {
std::cerr << "Usage: " << argv[0] << " <source file>" << std::endl;
return 1;
}
// 读取源文件
std::ifstream file(argv[1]);
if (!file) {
std::cerr << "Cannot open file: " << argv[1] << std::endl;
return 1;
}
std::string source((std::istreambuf_iterator<char>(file)),
std::istreambuf_iterator<char>());
try {
// 词法分析
std::cout << "=== Lexical Analysis ===" << std::endl;
Lexer lexer(source);
auto tokens = lexer.tokenize();
for (const auto& token : tokens) {
std::cout << "Token: " << static_cast<int>(token.type)
<< " Value: " << token.value << std::endl;
}
// 语法分析
std::cout << "\n=== Syntax Analysis ===" << std::endl;
Parser parser(tokens);
auto ast = parser.parse();
std::cout << "Parsing successful!" << std::endl;
// 代码生成
std::cout << "\n=== Code Generation ===" << std::endl;
CodeGenerator codegen;
std::string assembly = codegen.generate(ast.get());
std::cout << assembly << std::endl;
// 保存汇编文件
std::ofstream out("output.s");
out << assembly;
out.close();
std::cout << "\nAssembly code saved to output.s" << std::endl;
std::cout << "Compile with: gcc output.s -o output" << std::endl;
} catch (const std::exception& e) {
std::cerr << "Error: " << e.what() << std::endl;
return 1;
}
return 0;
}
测试示例¶
输入文件 (test.txt)¶
编译运行¶
Bash
# 编译编译器
g++ -std=c++14 -o mycompiler main.cpp lexer.cpp parser.cpp codegen.cpp
# 编译测试文件
./mycompiler test.txt
# 汇编链接
gcc output.s -o output
# 运行
./output
echo $? # 查看返回值,应该是30
生成的汇编代码¶
GAS
.globl main
.text
main:
push %rbp
mov %rsp, %rbp
sub $16, %rsp
mov $10, %eax
mov %eax, -4(%rbp)
mov $20, %eax
mov %eax, -8(%rbp)
mov -8(%rbp), %eax
push %rax
mov -4(%rbp), %eax
pop %rbx
add %ebx, %eax
mov %eax, -12(%rbp)
mov -12(%rbp), %eax
mov %rbp, %rsp
pop %rbp
ret
注意:
sub $16, %rsp为局部变量预留栈空间(3个int变量共12字节,16字节对齐)。 如果不预留空间,后续的push %rax指令会从%rbp向下写入8字节,覆盖已存储的局部变量。 另外注意代码生成器先处理右操作数(y在-8处),再处理左操作数(x在-4处)。
扩展功能¶
完成基础版本后,可以尝试添加以下功能:
- 支持更多数据类型: char, long, float, double
- 支持函数定义和调用
- 支持条件语句: if-else
- 支持循环语句: while, for
- 支持数组
- 支持指针
- 优化: 常量折叠,死代码删除
总结¶
通过这个项目,你已经: - ✅ 理解了编译器的三个主要阶段 - ✅ 实现了词法分析器 - ✅ 实现了递归下降的语法分析器 - ✅ 实现了简单的代码生成器 - ✅ 生成了可执行的x86-64汇编代码
这是理解编译原理的绝佳实践!