美文网首页
LLVM官方教程Kaleidoscope 1-2

LLVM官方教程Kaleidoscope 1-2

作者: GOGOYAO | 来源:发表于2019-10-11 18:36 被阅读0次

    参考

    Kaleidoscope: Kaleidoscope Introduction and the Lexer
    Kaleidoscope: Implementing a Parser and AST

    1. 前言

    Kaleidoscope语言是LLVM官方实现的一个用于教学的玩具语言,很多实现上都不是遵从软件工程的良好规划,力求实现的简单,以求更好的说明 LLVM 的特性和用法。比如说,Kaleidoscope语言中的数据类型都是64位浮点型(所以试用Kaleidoscope语言不需要申明数据类型),仅支持四种算子(加、减、乘、小于)。

    2. 词法标签(Token)

    Kaleidoscope中仅支持四种词法:

    enum Token {
      tok_eof = -1,
    
      // commands
      tok_def = -2,
      tok_extern = -3,
    
      // primary
      tok_identifier = -4,
      tok_number = -5,
    };
    static std::string IdentifierStr;   // 全局变量,Filled in if tok_identifier
    static double NumVal;             // 全局变量,Filled in if tok_number
    

    定义好 Token,接下来是 Token 解析函数:

    // 如果 Token 无法识别,那么返回的是0-255中的一个值,否则返回 enum Token 中的一个值
    static int gettok() {
      static int LastChar = ' ';
    
      // 跳过空格.
      while (isspace(LastChar))
        LastChar = getchar();
    
      if (isalpha(LastChar)) { // identifier: [a-zA-Z][a-zA-Z0-9]*
        IdentifierStr = LastChar;
        while (isalnum((LastChar = getchar())))  
          IdentifierStr += LastChar;
    
        if (IdentifierStr == "def")
          return tok_def;
        if (IdentifierStr == "extern")
          return tok_extern;
        return tok_identifier;
      }
    
      // 对于1.23.45.67这种情况,直接处理为1.23
      if (isdigit(LastChar) || LastChar == '.') { // Number: [0-9.]+
        std::string NumStr;
        do {
          NumStr += LastChar;
          LastChar = getchar();
        } while (isdigit(LastChar) || LastChar == '.');
    
        NumVal = strtod(NumStr.c_str(), nullptr);
        return tok_number;
      }
    
      if (LastChar == '#') {
        // Comment until end of line.
        do
          LastChar = getchar();
        while (LastChar != EOF && LastChar != '\n' && LastChar != '\r');
    
        if (LastChar != EOF)
          return gettok();
      }
    
      // Check for end of file.  Don't eat the EOF.
      if (LastChar == EOF)
        return tok_eof;
    
      // Otherwise, just return the character as its ascii value.
      int ThisChar = LastChar;
      LastChar = getchar();
      return ThisChar;
    }
    

    3. 抽象语法树(AST)

    我们希望把语言中的每一个结构用一个对象(object)来表示,AST 可以比较契合地去对语言建模。Kaleidoscope 语言中,有多个表达式(expressions)类、一个prototype类、一个function类。

    3.1. 表达式 AST

    /// ExprAST - Base class for all expression nodes.
    class ExprAST {
    public:
      virtual ~ExprAST() {}
    };
    
    /// NumberExprAST - Expression class for numeric literals like "1.0".
    class NumberExprAST : public ExprAST {
      double Val;
    
    public:
      NumberExprAST(double Val) : Val(Val) {}
    };
    
    /// VariableExprAST - Expression class for referencing a variable, like "a".
    class VariableExprAST : public ExprAST {
      std::string Name;
    
    public:
      VariableExprAST(const std::string &Name) : Name(Name) {}
    };
    
    /// BinaryExprAST - Expression class for a binary operator.
    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)) {}
    };
    
    /// CallExprAST - Expression class for function calls.
    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)) {}
    };
    

    由于我们都是用双精度浮点型数据类型,所以 Expr 中没有用于标记字段类型的字段。

    3.2. 函数 AST

    下一步,我们需要的是描述函数原型(prototype)和描述函数自身(function)。前者存储了函数名和参数列表,后者存储的是函数自身的定义(即前面的表达式)。

    /// PrototypeAST - This class represents the "prototype" for a function,
    /// which captures its name, and its argument names (thus implicitly the number
    /// of arguments the function takes).
    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; }
    };
    
    /// FunctionAST - This class represents a function definition itself.
    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)) {}
    };
    

    4. 词法解析(Parser)

    Kaleidoscope 语言中,Parser 采用的是递归降序解析(Recursive Descent Parsing)和算子优先解析(Operator-Precedence Parsing)的结合:后者用于解析二元表达式,前者用于其他的情况。Parser 的输出就是一个抽象语法树(AST)。

    此处我们增加两个 helper 函数

    /// CurTok/getNextToken - Provide a simple token buffer.  CurTok is the current
    /// token the parser is looking at.  getNextToken reads another token from the
    /// lexer and updates CurTok with its results.
    static int CurTok;
    static int getNextToken() {
      return CurTok = gettok();
    }
    

    4.1. 基础等式的解析

    4.1.1. 数字字面值解析

    /// numberexpr ::= number
    static std::unique_ptr<ExprAST> ParseNumberExpr() {
      auto Result = std::make_unique<NumberExprAST>(NumVal);
      getNextToken(); // consume the number
      return std::move(Result);
    }
    

    4.1.2. 括号的解析如下

    /// parenexpr ::= '(' expression ')'
    static std::unique_ptr<ExprAST> ParseParenExpr() {
      getNextToken(); // eat (.
      auto V = ParseExpression();
      if (!V)
        return nullptr;
    
      if (CurTok != ')')
        return LogError("expected ')'");
      getNextToken(); // eat ).
      return V;
    }
    

    两个注意点:

    • 如果是语法有错误,则返回 nullptr,上层函数判断出 nullptr 则报错。
    • 调用了递归函数ParseExpression,递归可以让每一个产出更简单

    4.1.3. 变量和函数解析

    /// identifierexpr
    ///   ::= identifier
    ///   ::= identifier '(' expression* ')'
    static std::unique_ptr<ExprAST> ParseIdentifierExpr() {
      std::string IdName = IdentifierStr;
    
      getNextToken();  // eat identifier.
    
      if (CurTok != '(') // Simple variable ref.
        return std::make_unique<VariableExprAST>(IdName);
    
      // Call.
      getNextToken();  // eat (
      std::vector<std::unique_ptr<ExprAST>> Args;
      if (CurTok != ')') {
        while (1) {
          if (auto Arg = ParseExpression())
            Args.push_back(std::move(Arg));
          else
            return nullptr;
    
          if (CurTok == ')')
            break;
    
          if (CurTok != ',')
            return LogError("Expected ')' or ',' in argument list");
          getNextToken();
        }
      }
    
      // Eat the ')'.
      getNextToken();
    
      return std::make_unique<CallExprAST>(IdName, std::move(Args));
    }
    

    4.1.4. Wrapper

    有了四个基本表达式解析逻辑,提供一个 helper 函数进行封装。

    /// primary
    ///   ::= identifierexpr
    ///   ::= numberexpr
    ///   ::= parenexpr
    static std::unique_ptr<ExprAST> ParsePrimary() {
      switch (CurTok) {
      default:
        return LogError("unknown token when expecting an expression");
      case tok_identifier:
        return ParseIdentifierExpr();
      case tok_number:
        return ParseNumberExpr();
      case '(':
        return ParseParenExpr();
      }
    }
    

    \color{red}{注:括号表达式也是一种基本表达式}

    4.2. 二元表达式解析

    二元表达式解析比较复杂,特别是涉及到运算符优先级问题。这里采用算子优先级解析法进行优先级判断。

    /// BinopPrecedence - This holds the precedence for each binary operator that is
    /// defined.
    static std::map<char, int> BinopPrecedence;
    
    /// GetTokPrecedence - Get the precedence of the pending binary operator token.
    static int GetTokPrecedence() {
      if (!isascii(CurTok))
        return -1;
    
      // Make sure it's a declared binop.
      int TokPrec = BinopPrecedence[CurTok];
      if (TokPrec <= 0) return -1;
      return TokPrec;
    }
    
    int main() {
      // Install standard binary operators.
      // 1 is lowest precedence.
      BinopPrecedence['<'] = 10;
      BinopPrecedence['+'] = 20;
      BinopPrecedence['-'] = 20;
      BinopPrecedence['*'] = 40;  // highest.
      ...
    }
    

    如上代码,定义好了算子的优先级和方法。

    现在开始看看表达式“a+b+(c+d)ef+g”的解析方法。算子优先级解析将表达式当作被二元算子分开的基本表达式流,从a 开始,可以看到这样的成对序列:[+, b] [+, (c+d)] [*, e] [*, f] and [+, g]。此处,括号也是当作基本表达式的。

    /// expression
    ///   ::= primary binoprhs
    ///
    static std::unique_ptr<ExprAST> ParseExpression() {
      auto LHS = ParsePrimary();
      if (!LHS)
        return nullptr;
    
      return ParseBinOpRHS(0, std::move(LHS));
    }
    

    上面代码中,ParseBinOpRHS就是用于解析成对序列。

    /// binoprhs
    ///   ::= ('+' primary)*
    // ExprPrec:表示可消化算子的最小优先级
    static std::unique_ptr<ExprAST> ParseBinOpRHS(int ExprPrec,
                                                  std::unique_ptr<ExprAST> LHS) {
      // If this is a binop, find its precedence.
      while (true) {
        int TokPrec = GetTokPrecedence();
    
        // If this is a binop that binds at least as tightly as the current binop,
        // consume it, otherwise we are done.
        if (TokPrec < ExprPrec)
          return LHS;
    
        // Okay, we know this is a binop.
        int BinOp = CurTok;
        getNextToken(); // eat binop
    
        // Parse the primary expression after the binary operator.
        auto RHS = ParsePrimary();
        if (!RHS)
          return nullptr;
    
        // If BinOp binds less tightly with RHS than the operator after RHS, let
        // the pending operator take RHS as its LHS.
        // 简单说就是下一个操作符优先级更高,则先处理下一个,
        // 并将处理的结果作为一个整体,当作当前二元表达式的右操作数。
        // 例如 a+b*c;
        // 注意,a+(b+c)*c这种是把 b+c 作为一个基本表达式的,所以这里的 RHS 已经是 (b+c) 了
        int NextPrec = GetTokPrecedence();
        if (TokPrec < NextPrec) {
          RHS = ParseBinOpRHS(TokPrec + 1, std::move(RHS));
          if (!RHS)
            return nullptr;
        }
    
        // Merge LHS/RHS.
        LHS = std::make_unique<BinaryExprAST>(BinOp, std::move(LHS),
                                               std::move(RHS));
      }
    }
    

    4.3. 函数的解析

    在Kaleidoscope中,函数的解析包括定义和声明(extern)。
    prototype的解析如下,看起来就比较简单了:

    /// prototype
    ///   ::= id '(' id* ')'
    static std::unique_ptr<PrototypeAST> ParsePrototype() {
      if (CurTok != tok_identifier)
        return LogErrorP("Expected function name in prototype");
    
      std::string FnName = IdentifierStr;
      getNextToken();
    
      if (CurTok != '(')
        return LogErrorP("Expected '(' in prototype");
    
      // Read the list of argument names.
      std::vector<std::string> ArgNames;
      while (getNextToken() == tok_identifier)
        ArgNames.push_back(IdentifierStr);
      if (CurTok != ')')
        return LogErrorP("Expected ')' in prototype");
    
      // success.
      getNextToken();  // eat ')'.
    
      return std::make_unique<PrototypeAST>(FnName, std::move(ArgNames));
    }
    

    函数定义的解析如下:

    /// definition ::= 'def' prototype expression
    static std::unique_ptr<FunctionAST> ParseDefinition() {
      getNextToken();  // eat def.
      auto Proto = ParsePrototype();
      if (!Proto) return nullptr;
    
      if (auto E = ParseExpression())
        return std::make_unique<FunctionAST>(std::move(Proto), std::move(E));
      return nullptr;
    }
    

    另外支持下 extern 这种仅声明或者前向声明的情况:

    /// external ::= 'extern' prototype
    static std::unique_ptr<PrototypeAST> ParseExtern() {
      getNextToken();  // eat extern.
      return ParsePrototype();
    }
    

    最后是支持任意的顶层表达式,采用的是无参的匿名函数。

    /// toplevelexpr ::= expression
    static std::unique_ptr<FunctionAST> ParseTopLevelExpr() {
      if (auto E = ParseExpression()) {
        // Make an anonymous proto.
        auto Proto = std::make_unique<PrototypeAST>("", std::vector<std::string>());
        return std::make_unique<FunctionAST>(std::move(Proto), std::move(E));
      }
      return nullptr;
    }
    

    5. 驱动器

    其实就是main 函数的Loop

    /// top ::= definition | external | expression | ';'
    static void MainLoop() {
      while (1) {
        fprintf(stderr, "ready> ");
        switch (CurTok) {
        case tok_eof:
          return;
        case ';': // ignore top-level semicolons.
          getNextToken();
          break;
        case tok_def:
          HandleDefinition();
          break;
        case tok_extern:
          HandleExtern();
          break;
        default:
          HandleTopLevelExpression();
          break;
        }
      }
    }
    

    6. 完整代码和测试

    完成代码见代码清单

    编译运行如下:

    # Compile
    $ clang++ -g -O3 toy.cpp `llvm-config --cxxflags`
    # Run
    $ ./a.out
    ready> def foo(x y) x+foo(y, 4.0);
    Parsed a function definition.
    ready> def foo(x y) x+y y;
    Parsed a function definition.
    Parsed a top-level expr
    ready> def foo(x y) x+y );
    Parsed a function definition.
    Error: unknown token when expecting an expression
    ready> extern sin(a);
    ready> Parsed an extern
    ready> ^D
    

    相关文章

      网友评论

          本文标题:LLVM官方教程Kaleidoscope 1-2

          本文链接:https://www.haomeiwen.com/subject/hqvpuctx.html