LLVM项目教程:实现Kaleidoscope语言的解析器与抽象语法树

LLVM项目教程:实现Kaleidoscope语言的解析器与抽象语法树

llvm Project moved to: https://github.com/llvm/llvm-project llvm 项目地址: https://gitcode.com/gh_mirrors/ll/llvm

前言

在编译器开发领域,解析器和抽象语法树(AST)是构建编程语言前端的关键组件。本文将基于LLVM项目中的Kaleidoscope教程,深入讲解如何为自定义语言构建解析器和AST。这是继词法分析器之后的第二步,我们将学习如何将词法单元转换为有意义的语法结构。

抽象语法树(AST)设计

AST是源代码的树状表示,它抽象掉了语法细节,只保留程序的结构和语义信息。在Kaleidoscope语言中,我们需要为各种语言结构设计AST节点。

表达式基类

class ExprAST {
public:
    virtual ~ExprAST() {}
};

这是所有表达式节点的基类,使用虚析构函数确保派生类能被正确销毁。

具体表达式节点

  1. 数值字面量节点
class NumberExprAST : public ExprAST {
    double Val;
public:
    NumberExprAST(double Val) : Val(Val) {}
};

存储浮点数值,如1.0

  1. 变量引用节点
class VariableExprAST : public ExprAST {
    std::string Name;
public:
    VariableExprAST(const std::string &Name) : Name(Name) {}
};

表示对变量的引用,如x

  1. 二元运算符节点
class BinaryExprAST : public ExprAST {
    char Op;
    std::unique_ptr<ExprAST> LHS, RHS;
public:
    BinaryExprAST(char op, std::unique_ptr<ExprAST> LHS,
                 std::unique_ptr<ExprAST> RHS)
        : Op(op), LHS(std::move(LHS)), RHS(std::move(RHS)) {}
};

表示二元运算,如x + y

  1. 函数调用节点
class CallExprAST : public ExprAST {
    std::string Callee;
    std::vector<std::unique_ptr<ExprAST>> Args;
public:
    CallExprAST(const std::string &Callee,
               std::vector<std::unique_ptr<ExprAST>> Args)
        : Callee(Callee), Args(std::move(Args)) {}
};

表示函数调用,如sin(x)

函数相关节点

  1. 函数原型节点
class PrototypeAST {
    std::string Name;
    std::vector<std::string> Args;
public:
    PrototypeAST(const std::string &name, std::vector<std::string> Args)
        : Name(name), Args(std::move(Args)) {}
    const std::string &getName() const { return Name; }
};

表示函数声明,包含函数名和参数列表。

  1. 函数定义节点
class FunctionAST {
    std::unique_ptr<PrototypeAST> Proto;
    std::unique_ptr<ExprAST> Body;
public:
    FunctionAST(std::unique_ptr<PrototypeAST> Proto,
               std::unique_ptr<ExprAST> Body)
        : Proto(std::move(Proto)), Body(std::move(Body)) {}
};

组合函数原型和函数体,表示完整的函数定义。

解析器实现

解析器负责将词法单元流转换为AST,我们采用递归下降解析方法。

基础工具函数

  1. 令牌缓冲
static int CurTok;
static int getNextToken() {
    return CurTok = gettok();
}

提供单令牌前瞻能力。

  1. 错误处理
std::unique_ptr<ExprAST> LogError(const char *Str) {
    fprintf(stderr, "Error: %s\n", Str);
    return nullptr;
}

统一的错误报告机制。

基本表达式解析

  1. 数值字面量解析
static std::unique_ptr<ExprAST> ParseNumberExpr() {
    auto Result = std::make_unique<NumberExprAST>(NumVal);
    getNextToken(); // 消耗数字令牌
    return std::move(Result);
}
  1. 括号表达式解析
static std::unique_ptr<ExprAST> ParseParenExpr() {
    getNextToken(); // 消耗'('
    auto V = ParseExpression();
    if (!V) return nullptr;
    
    if (CurTok != ')')
        return LogError("expected ')'");
    getNextToken(); // 消耗')'
    return V;
}

处理括号分组,如(1+2)*3

  1. 标识符解析
static std::unique_ptr<ExprAST> ParseIdentifierExpr() {
    std::string IdName = IdentifierStr;
    getNextToken(); // 消耗标识符
    
    if (CurTok != '(') // 简单变量引用
        return std::make_unique<VariableExprAST>(IdName);
        
    // 函数调用处理
    getNextToken(); // 消耗'('
    std::vector<std::unique_ptr<ExprAST>> Args;
    // ... 参数解析逻辑
    return std::make_unique<CallExprAST>(IdName, std::move(Args));
}

区分变量引用和函数调用。

二元表达式解析

处理运算符优先级是解析器的核心挑战。我们采用运算符优先级解析算法。

  1. 优先级表
static std::map<char, int> BinopPrecedence = {
    {'<', 10}, {'+', 20}, {'-', 20}, {'*', 40}
};

定义各运算符的优先级。

  1. 表达式解析入口
static std::unique_ptr<ExprAST> ParseExpression() {
    auto LHS = ParsePrimary();
    if (!LHS) return nullptr;
    return ParseBinOpRHS(0, std::move(LHS));
}
  1. 右结合处理
static std::unique_ptr<ExprAST> ParseBinOpRHS(int ExprPrec,
                                            std::unique_ptr<ExprAST> LHS) {
    while (true) {
        int TokPrec = GetTokPrecedence();
        if (TokPrec < ExprPrec) return LHS;
        
        int BinOp = CurTok;
        getNextToken(); // 消耗运算符
        
        auto RHS = ParsePrimary();
        if (!RHS) return nullptr;
        
        // 处理更高优先级的运算符
        int NextPrec = GetTokPrecedence();
        if (TokPrec < NextPrec) {
            RHS = ParseBinOpRHS(TokPrec+1, std::move(RHS));
            if (!RHS) return nullptr;
        }
        
        LHS = std::make_unique<BinaryExprAST>(BinOp, std::move(LHS),
                                             std::move(RHS));
    }
}

这段代码实现了运算符优先级的正确处理,确保1+2*3被正确解析为1+(2*3)

总结

本文详细介绍了如何为Kaleidoscope语言构建解析器和AST。关键点包括:

  1. 设计了完整的AST类层次结构,覆盖语言的各种语法结构
  2. 实现了递归下降解析器,处理基本表达式
  3. 使用运算符优先级解析算法正确处理二元表达式
  4. 建立了完善的错误处理机制

这些技术不仅适用于Kaleidoscope语言,也是构建任何编程语言前端的通用方法。通过本教程,读者可以掌握编译器前端开发的核心技术,为后续的语义分析和代码生成打下坚实基础。

llvm Project moved to: https://github.com/llvm/llvm-project llvm 项目地址: https://gitcode.com/gh_mirrors/ll/llvm

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

戚宾来

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值