首页
社区
课程
招聘
[分享]简单编译器实现
发表于: 1天前 415

[分享]简单编译器实现

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内核攻防全技术栈,打造具备自动化能力的内核开发高手。

收藏
免费 2
支持
分享
最新回复 (0)
游客
登录 | 注册 方可回帖
返回