-
-
[分享]简单编译器实现
-
发表于: 1天前 415
-
前言
前几小结实现了简单的代码解释器、词法解析器,由以上基础铺垫 本节笔者将实现一个简易的编译器,实现效果为:编写简单代码 "a = 1 + 2" 编译器需要使用词法解析器将其解析成 token 序列,再将 token 序列建立 AST 节点,例如:
=
/ \
a +
/ \
1 2
这个“树结构”,就是 AST(Abstract Syntax Tree,抽象语法树)
其中涉及数字节点、二元表达式以及赋值节点
为什么要建立 AST 节点呢? 因为当一个表达式 a = 1 + 2 程序并不知道先处理 = 还是处理 + ,以后还会实现更复杂的表达式 AST 节点可以很好解决优先级处理问题
有了 AST 节点后就能编写代码生成器,将 AST 转换为 虚拟机指令(MOV / ADD),最后输出机器码即可放入之前写的解释器中运行
本节将以最简编译器的方式走通从源码到结果的全流程,大致流程如下:
ource Code(源码)
↓
Lexer(词法分析 把字符串 → 切成“最小语法单元”)
↓
Token(中间数据)
↓
Parser(语法分析)
↓
AST(抽象语法树)
↓
CodeGen(代码生成)
↓
指令(IR / VM 指令)
↓
执行(解释器 / 虚拟机)
↓
结果
一、词法分析
将调用上节的代码进行复用
// 1. 词法分析
Lexer lexer(input);
auto tokens = lexer.tokenize();
二、语法分析
目前只处理了 + 和 - 操作 以及 数字 + 数字 的形式,其余可自行补充
//语法解析
class Syntax {
public:
Syntax(const std::vector<Token>& tokens)
: tokens(tokens), pos(0) {
}
//解析
AssignStmt parse() {
AssignStmt stmt;
// a
stmt.name = consume(TOK_VAR).text;
// = 这里没有保存值,只是“确认语法正确”
consume(TOK_ASSIGN);
// 1 + 2 解析表达式
stmt.expr = parseExpr();
return stmt;
}
private:
std::vector<Token> tokens;
size_t pos;
//当前pos指向的token
Token& peek() {
return tokens[pos];
}
//验证 + 前进
Token consume(TokenType type) {
if (tokens[pos].type != type) {
throw std::runtime_error("unexpected token");
}
return tokens[pos++];
}
// 只实现 1 + 2 这种
Expr* parseExpr() {
//解析左边
Expr* left = parsePrimary();
//判断是否有 +
if (peek().type == TOK_ADD) {
//处理 +
consume(TOK_ADD);
//解析右边
Expr* right = parsePrimary();
//构建 BinaryExpr: 1+2
return new BinaryExpr('+', left, right);
}
//判断是否有 -
if (peek().type == TOK_SUB) {
//处理 -
consume(TOK_SUB);
//解析右边
Expr* right = parsePrimary();
//构建 BinaryExpr: 1+2
return new BinaryExpr('-', left, right);
}
return left;
}
//解析“最小单位” 把简单元素组合成表达式
Expr* parsePrimary() {
if (peek().type == TOK_NUMBER) {
//stoi: 字符转换整数
int v = std::stoi(consume(TOK_NUMBER).text);
return new NumberExpr(v);
}
throw std::runtime_error("invalid expression");
}
};
三、代码生成
代码生成目前只支持 + 和 - 操作,剩余可自行添加
class CodeGen {
public:
CodeGen(Assembler& as) : as(as) {}
void gen(const AssignStmt& stmt) {
//把表达式计算结果放到 R0
genExpr(stmt.expr);
// 这里只是演示,没有变量表
// 先直接 print 结果
PRINT(as, R0);
HALT(as);
}
private:
Assembler& as;
//递归生成表达式的指令
void genExpr(Expr* expr) {
//数字
//dynamic_cast: 判断 expr 是不是 NumberExpr 类型
if (auto num = dynamic_cast<NumberExpr*>(expr)) {
//生成:MOV R0, 1
MOVI(as, R0, num->value);
}
//二元表达式 expr 是 1 + 2
else if (auto bin = dynamic_cast<BinaryExpr*>(expr)) {
// 左边 → R0
genExpr(bin->left);
// 右边 → R1
int value = getNumber(bin->right);//把右边表达式“强行当成数字”
MOVI(as, R1, value);
////处理+
//if (bin->op == '+') {
// ADDR(as, R0, R1);
//}
switch (bin->op)
{
case '+': {
ADDR(as, R0, R1);
break;
}
case '-': {
SUBR(as, R0, R1);
break;
}
/*case '*': {
ADDR(as, R0, R1);
break;
}
case '/': {
ADDR(as, R0, R1);
break;
}*/
}
}
}
//把表达式“强行当成数字”
int getNumber(Expr* expr) {
if (auto num = dynamic_cast<NumberExpr*>(expr)) {
return num->value;
}
throw std::runtime_error("only support number");
}
};
四、测试
当前测试为简单表达式 "a = 10 - 2" ,运行后可在结果寄存器 R0 得到正确结果 8
int main() {
std::string input = "a = 10 - 2";
// 1. 词法分析
Lexer lexer(input);
auto tokens = lexer.tokenize();
// 2. 语法分析
Syntax parser(tokens);
AssignStmt stmt = parser.parse();
// 3. 生成代码: AST → 虚拟机指令(MOV / ADD)
Assembler as;
CodeGen gen(as);
gen.gen(stmt);
as.patch();
// 4. 调用循环解释器执行代码
VM vm = {};
LoopInterpreter(vm, as.getData());
}
总结
本节做了简单编译器的实现,用最小化的方式走通解释器的基础流程,欢迎讨论 如有错漏 请留言!
[培训]《冰与火的战歌:Windows内核攻防实战》!从零到实战,融合AI与Windows内核攻防全技术栈,打造具备自动化能力的内核开发高手。
赞赏
他的文章
- [分享]简单编译器实现 416
- [分享]Lexer简单词法解析器入门 362
- [分享]vm解释器入门2 478
- [分享]vm解释器入门 709
- [分享]混淆、加密、反沙箱概念介绍 917
赞赏
雪币:
留言: