Kaleidoscope:Extending the Language:User Defined Operators

translate from: http://llvm.org/docs/tutorial/LangImpl06.html

发一篇干货,接着之前的LLVM tutorial教程的翻译,本次翻译的为第六章。

扩展我们的Kaleidoscope语言,以支持用户定义操作符(一元操作符和二元操作符)。

  • 为了实现用户自定义运算符,我们采用了扩展函数申明和扩展函数定义的实现方式。

  • 为了实现对用户自定义运算符的表达式支持:

    1. 针对用户自定义的二元运算符。因为我们之前已经支持了部分二元运算符(如+、- 等),所以我们只需要扩展BinaryExprAST来支持用户自定义的二元运算符即可;
    2. 针对用户自定义的一元运算符。我们需要定义UnaryExprAST抽象语法树,并实现Codegen支持,且添加到解析表达式的逻辑中。

graph TD subgraph 二元运算符 binaryParser[1. 添加词法分析支持] subgraph 2. 实现用户自定义二元运算符函数 funcProtoExtend[扩展函数申明AST] --> funcProtoParser[扩展ParsePrototype函数] end binaryParser --> funcProtoExtend funcProtoParser --> binaryCodegen[3. 在BinaryExprAST的Codegen中支持自定义的二元运算符] end subgraph 一元运算符 unaryParser["1. 添加词法分析支持"] subgraph 2. 实现用户自定义的一元运算符函数 unaryFuncProtoExtend[扩展函数申明AST] --> unaryFuncProtoParser[扩展ParsePrototype函数] end unaryParser --> unaryFuncProtoExtend subgraph 3. 实现一元运算符表达式的解析 createUnaryAST[创建UnaryExprAST节点] --> parseUnary[添加解析一元运算符表达式的函数] parseUnary --> parsePrimary[在ParsePrimay中添加调用ParseUnary的逻辑] end unaryFuncProtoParser --> createUnaryAST end style funcProtoExtend fill:#f9f style funcProtoParser fill:#f9f style unaryFuncProtoExtend fill:#f9f style unaryFuncProtoParser fill:#f9f style binaryCodegen fill:#ccff66 style createUnaryAST fill:#ccff66 style parseUnary fill:#ccff66 style parsePrimary fill:#ccff66

6.1 第六章介绍 #

欢迎来到第六章。在我们的教程中,我们现在拥有一个功能完备的语言,它非常简单,但也非常有用。然而,它仍然存在一个大问题。我们的语言没有很多有用的运算符(例如除法,逻辑否定,甚至除了小于以外的任何比较运算符)。

本章对简单而漂亮的Kaleidoscope语言添加用户定义的运算符,但是我们会稍微偏离主题。这种偏题会导致我们的语言变得有些丑陋,但是会让我们的语言很强大。创建自己语言的好处是:你可以决定什么是好的或者坏的。在本教程中,我们假设可以使用它作为一种展示一些有趣的解析技术的方式。

在本教程的最后,我们将会运行一个Kaleidoscope 应用 that renders the Mandelbrot set(渲染Mandelbrot集合)。我们将会通过Kaleidoscope来构建该例子,并且来展示Kaleidoscope的特性,

6.2 用户定义运算符: 思路 #

我们将会添加“运算符重载”功能到Kaleidoscope语言中,它的设计将比C++中的设计更加通用。在C++中,你只能重新定义已经存在的运算符:你不能人工改变语法,并引入新的运算符,改变优先级等。在本章,我们将在Kaleidoscope语言中添加此功能,这可以用户就可以使用更多自定义的运算符。

我们现在介绍用于定义运算符的目的是展示使用手写解析器的强大功能和灵活性。到目前为止,我们实现的解析器在大部分对于表达式的语法和操作符优先级解析中都使用到了递归下降。可以阅读 第二章了解更多相关的内容。通过使用运算符优先级解析,允许程序员在语法中引入新的运算符是相当容易的:程序在JIT运行时,语法是动态可扩展的。

我们将会添加两个功能:可以编程的一元运算符(unary operators)(现在,Kaleidoscope还没有一元运算符)以及二元运算符(binary operators)。

一个例子:

# Logical unary not.
def unary!(v)
  if v then
    0
  else
    1;

# Define > with the same precedence as <.
def binary> 10 (LHS RHS)
  RHS < LHS;

# Binary "logical or", (note that it does not "short circuit")
def binary| 5 (LHS RHS)
  if LHS then
    1
  else if RHS then
    1
  else
    0;

# Define = with slightly lower precedence than relationals.
def binary= 9 (LHS RHS)
  !(LHS < RHS | LHS > RHS);

许多语言都希望能够使用该语言本身来实现其标准库。在Kaleidoscope中,我们可以在库中实现该语言的重要部分!

我们将这些功能的实现分解为两个部分:实现对用户定义的二元运算符的支持添加一元运算符

6.3 用户定义的二元运算符 #

使用我们目前的框架来添加对用户定义的二元运算符的支持非常简单。我们将首先添加对一元/二元关键字:

enum Token {
  ...
  // operators
  tok_binary = -11,
  tok_unary = -12
};
...
static int gettok() {
...
    if (IdentifierStr == "for")
      return tok_for;
    if (IdentifierStr == "in")
      return tok_in;
    if (IdentifierStr == "binary")
      return tok_binary;
    if (IdentifierStr == "unary")
      return tok_unary;
    return tok_identifier;

这只是添加了对一元和二元关键字的词法支持。当前的AST的优点是:我们通过使用该二元运算符操作码的ASCI码来表示具有范型的二元运算符。对于我们扩展的操作符来说,我们将使用相同的表示,所以我们不需要任何新的AST或者解析器支持。

在另一方面,我们必须保证在函数定义时,可以正确解析和表示这些新的运算符的定义,例如"def binary | 5"。到目前为止,我们的语法中,函数定义时的名字被解析作为申明,并且会进入到PrototypeAST AST 节点。为了将我们新定义的运算符表示为申明,我们必须扩展Prototype AST 节点:

/// PrototypeAST - This class represents the "prototype" for a function,
/// which captures its argument names as well as if it is an operator.
class PrototypeAST {
  std::string Name;
  std::vector<std::string> Args;
  bool IsOperator;      // 是否是运算符
  unsigned Precedence;  // Precedence if a binary op. 运算符优先级

public:
  PrototypeAST(const std::string &name, std::vector<std::string> Args,
               bool IsOperator = false, unsigned Prec = 0)
  : Name(name), Args(std::move(Args)), IsOperator(IsOperator),
    Precedence(Prec) {}

  Function *codegen();
  const std::string &getName() const { return Name; }

  bool isUnaryOp() const { return IsOperator && Args.size() == 1; }
  bool isBinaryOp() const { return IsOperator && Args.size() == 2; }

  char getOperatorName() const {
    assert(isUnaryOp() || isBinaryOp());
    return Name[Name.size() - 1];
  }

  unsigned getBinaryPrecedence() const { return Precedence; }
};

基本上,除了要知道一个申明的名字之外,我们现在还要跟踪它是否为一个运算符,并且如果它是的话,运算符的优先级别是什么。优先级仅仅被用于二元运算符(正如你将在下面看到的,它对一元运算符不适用)。现在我们有了一种方法来表示对用户定义运算符的声明,我们现在来解析它:

/// prototype
///   ::= id '(' id* ')'
///   ::= binary LETTER number? (id, id)
static std::unique_ptr<PrototypeAST> ParsePrototype() {
  std::string FnName;

  unsigned Kind = 0;  // 0 = identifier, 1 = unary, 2 = binary.
  unsigned BinaryPrecedence = 30;

  switch (CurTok) {
  default:
    return LogErrorP("Expected function name in prototype");
  case tok_identifier:
    FnName = IdentifierStr;
    Kind = 0;
    getNextToken();
    break;
  case tok_binary:
    getNextToken();
    if (!isascii(CurTok))
      return LogErrorP("Expected binary operator");
    FnName = "binary";			// 为二元运算符设置FnName = "binary"+CurTok
    FnName += (char)CurTok;
    Kind = 2;
    getNextToken();

    // Read the precedence if present.
    if (CurTok == tok_number) {
      if (NumVal < 1 || NumVal > 100)
        return LogErrorP("Invalid precedence: must be 1..100");
      BinaryPrecedence = (unsigned)NumVal;
      getNextToken();
    }
    break;
  }

  if (CurTok != '(')
    return LogErrorP("Expected '(' in prototype");

  std::vector<std::string> ArgNames;
  while (getNextToken() == tok_identifier)
    ArgNames.push_back(IdentifierStr);
  if (CurTok != ')')
    return LogErrorP("Expected ')' in prototype");

  // success.
  getNextToken();  // eat ')'.

  // Verify right number of names for operator.
  if (Kind && ArgNames.size() != Kind)
    return LogErrorP("Invalid number of operands for operator");

  return llvm::make_unique<PrototypeAST>(FnName, std::move(ArgNames), Kind != 0,
                                         BinaryPrecedence);
}

上面的代码都非常简单,我们之前已经看到了很多类似的代码。关于上面的代码一个有趣的部分是为二元运算符设计FnName的那几行。这位新定义的“@”运算符构建了像“binary@”这样的名称。它利用了LLVM符号表中的符号表可以被允许包含任何字符的事实,甚至可以包括嵌入的nul字符。

下一个有趣的事情是为这些二元运算符添加Codegen支持(产生LLVM IR的代码)。通过使用我们目前的代码框架,在已经有了二元操作符节点后,实现Codegen只需要简单添加一个case。

Value *BinaryExprAST::codegen() {
  Value *L = LHS->codegen();
  Value *R = RHS->codegen();
  if (!L || !R)
    return nullptr;

  switch (Op) {
  case '+':
    return Builder.CreateFAdd(L, R, "addtmp");
  case '-':
    return Builder.CreateFSub(L, R, "subtmp");
  case '*':
    return Builder.CreateFMul(L, R, "multmp");
  case '<':
    L = Builder.CreateFCmpULT(L, R, "cmptmp");
    // Convert bool 0/1 to double 0.0 or 1.0
    return Builder.CreateUIToFP(L, Type::getDoubleTy(TheContext),
                                "booltmp");
  default:
    break;
  }

  // If it wasn't a builtin binary operator, it must be a user defined one. Emit
  // a call to it.
  // 如果它不是一个内建的二元操作符,它一定是用户定义的二元操作符。Emit a call to it.
  Function *F = getFunction(std::string("binary") + Op);
  assert(F && "binary operator not found!");

  Value *Ops[2] = { L, R };
  return Builder.CreateCall(F, Ops, "binop");
}

正如上面看到的,添加的代码实际上非常简单。它只是在符号表中查找合适的运算符并且生成对它的函数调用。由于用户定义的运算符只是被作为普通函数构建(因为“申明”就是有正确名称的函数)。

我们最好还需要添加的一段代码,是一段顶级膜法(hhhh):

Function *FunctionAST::codegen() {
  // Transfer ownership of the prototype to the FunctionProtos map, but keep a
  // reference to it for use below.
  // 将申明的所有权转移到了FunctionProtos映射中,但是会保留一个引用以便于之后调用。
  auto &P = *Proto;
  FunctionProtos[Proto->getName()] = std::move(Proto);
  Function *TheFunction = getFunction(P.getName());
  if (!TheFunction)
    return nullptr;

  // If this is an operator, install it.、
  // 如果当前正在解析的函数是一个运算符函数,install it.
  if (P.isBinaryOp())
    BinopPrecedence[P.getOperatorName()] = P.getBinaryPrecedence(); // 赋予它优先级

  // Create a new basic block to start insertion into.
  BasicBlock *BB = BasicBlock::Create(TheContext, "entry", TheFunction);
  ...

基本上,在一个函数codegen之前,如果它是一个用户定义的运算符,我们会在优先级表中注册它。这会允许我们使用已经有的二元运算符解析逻辑来处理它。由于我们正在研究一个完全通用的运算符优先级解析器,所以我们需要做的就是“扩展语法”。

现在我们有了有用的用户定义的二元运算符。我们在之前的框架上添加了一些逻辑来支持这个功能。

添加一元运算符是更具有挑战性的,因为我们还没有任何框架 – let’s see what it takes.

6.4 用户定义的一元运算符 #

由于目前我们的Kaleidoscope语言中还不支持一元运算符,所以为了支持该功能,我们必须要从头开始做。在之前的介绍中,我们已经在词法分析器中为一元运算符添加了"unary"关键字支持。除此之外,我们还需要创建AST节点

/// UnaryExprAST - Expression class for a unary operator.
class UnaryExprAST : public ExprAST {
  char Opcode;
  std::unique_ptr<ExprAST> Operand;

public:
  UnaryExprAST(char Opcode, std::unique_ptr<ExprAST> Operand)
    : Opcode(Opcode), Operand(std::move(Operand)) {}

  Value *codegen() override;
};

到现在为止,AST节点的意义是非常简单明了的。除了一元运算符只有一个子节点之外(二元运算符有两个子节点),它与二元运算符的AST节点基本相同。有了AST节点之后,我们需要添加解析逻辑。解析一元运算符实现起来是非常简单的:我们将添加一个新的函数来支持该功能

/// unary
///   ::= primary
///   ::= '!' unary
static std::unique_ptr<ExprAST> ParseUnary() {
  // If the current token is not an operator, it must be a primary expr.
  // 如果当前处理的token不是运算符,那它就一定是一个主表达式。
  if (!isascii(CurTok) || CurTok == '(' || CurTok == ',')
    return ParsePrimary();

  // If this is a unary operator, read it.
  // 如果它是一个一元运算符,read it。
  int Opc = CurTok;
  getNextToken();
  if (auto Operand = ParseUnary())
    return llvm::make_unique<UnaryExprAST>(Opc, std::move(Operand));
  return nullptr;
}

在这里我们添加的语法是非常简单的。当解析主运算符时,如果我们看到了一个一元运算符,我们将会将运算符作为前缀使用,并且将剩余的部分解析为另一个一元运算符。这允许我们处理多个一元运算符(例如“!!x”)。请注意,一元运算符不能像二元运算符那样具有模糊的解析,所以不需要优先级信息。

这个函数的问题是:我们需要在某个地方调用ParseUnary。为了做这个,我们改变以前的 ParsePrimay 调用者来调用 ParseUnary

/// binoprhs
///   ::= ('+' unary)*
static std::unique_ptr<ExprAST> ParseBinOpRHS(int ExprPrec,
                                              std::unique_ptr<ExprAST> LHS) {
  ...
    // Parse the unary expression after the binary operator.
    // 在解析二元操作符后解析一元表达式
    auto RHS = ParseUnary();
    if (!RHS)
      return nullptr;
  ...
}
/// expression
///   ::= unary binoprhs
///
static std::unique_ptr<ExprAST> ParseExpression() {
  auto LHS = ParseUnary();
  if (!LHS)
    return nullptr;

  return ParseBinOpRHS(0, std::move(LHS));
}

有了这两个改变之后,我们现在能够解析一元运算符并为他们构建AST。下一步,我们需要为申明添加解析器支持,来解析一元运算符的申明。我们对上面的二元运算符代码进行扩展:

/// prototype
///   ::= id '(' id* ')'
///   ::= binary LETTER number? (id, id)
///   ::= unary LETTER (id)
static std::unique_ptr<PrototypeAST> ParsePrototype() {
  std::string FnName;

  unsigned Kind = 0;  // 0 = identifier, 1 = unary, 2 = binary.
  unsigned BinaryPrecedence = 30;

  switch (CurTok) {
  default:
    return LogErrorP("Expected function name in prototype");
  case tok_identifier:
    FnName = IdentifierStr;
    Kind = 0;
    getNextToken();
    break;
  case tok_unary:
    getNextToken();
    if (!isascii(CurTok))
      return LogErrorP("Expected unary operator");
    FnName = "unary";
    FnName += (char)CurTok;
    Kind = 1;
    getNextToken();
    break;
  case tok_binary:
    ...

和二元运算符一样,我们将一元运算符命名为包含operator+字符的名称。这有助于我们进行代码产生。Speaking of,我们需要添加的最后一部分是对一元运算符添加codegen支持。它像下面这样:

Value *UnaryExprAST::codegen() {
  Value *OperandV = Operand->codegen();
  if (!OperandV)
    return nullptr;

  Function *F = getFunction(std::string("unary") + Opcode);
  if (!F)
    return LogErrorV("Unknown unary operator");

  return Builder.CreateCall(F, OperandV, "unop");
}

此代码与二进制运算符的代码类似,但是更简单。它更简单,主要是因为它不需要处理任何预定义的运算符。

6.5 Kicking the Tires #

听起来是难以置信的,但是在有了最后几章中介绍的简单的扩展之后,Kaleidoscope就会成长为一个真正的语言。之后,我们可以使用Kaleidoscope做大量有趣的事情,包括I/O,数学,和一堆其它的东西。例如,我们现在可以添加一个排序运算符(printd 被定义为打印出指定的值和换行符):

ready> extern printd(x);
Read extern:
declare double @printd(double)

ready> def binary : 1 (x y) 0;  # Low-precedence operator that ignores operands.
...
ready> printd(123) : printd(456) : printd(789);
123.000000
456.000000
789.000000
Evaluated to 0.000000

我们可以定义一堆其它的“primitive”的操作,例如:

# Logical unary not.
# 逻辑 not
def unary!(v)
  if v then
    0
  else
    1;

# Unary negate.
# 一元运算符 负
def unary-(v)
  0-v;

# Define > with the same precedence as <.
# 定义 > 与 < 的优先级相同
def binary> 10 (LHS RHS)
  RHS < LHS;

# Binary logical or, which does not short circuit.
# 逻辑或
def binary| 5 (LHS RHS)
  if LHS then
    1
  else if RHS then
    1
  else
    0;

# Binary logical and, which does not short circuit.
# 逻辑 and
def binary& 6 (LHS RHS)
  if !LHS then
    0
  else
    !!RHS;

# Define = with slightly lower precedence than relationals.
# = 
def binary = 9 (LHS RHS)
  !(LHS < RHS | LHS > RHS);

# Define ':' for sequencing: as a low-precedence operator that ignores operands
# and just returns the RHS.
def binary : 1 (x y) y;

有了之前的 if/then/else 支持,我们也可以为 I/O 定义一些有趣的函数。例如,下面打印出一个字符,其密度反映了传入的值: 值越低,字符越密集。

ready> extern putchard(char);
...
ready> def printdensity(d)
  if d > 8 then
    putchard(32)  # ' '
  else if d > 4 then
    putchard(46)  # '.'
  else if d > 2 then
    putchard(43)  # '+'
  else
    putchard(42); # '*'
...
ready> printdensity(1): printdensity(2): printdensity(3):
       printdensity(4): printdensity(5): printdensity(9):
       putchard(10);
**++.
Evaluated to 0.000000

基于这些简单的操作,我们可以开始定义一些有趣的东西。例如,这儿有一个函数,它确定复平面中某个函数发散所需的迭代次数:

# Determine whether the specific location diverges. 决定具体位置是否发散.
# Solve for z = z^2 + c in the complex plane. 求解复平面中的 z = z^2 + c.
def mandelconverger(real imag iters creal cimag)
  if iters > 255 | (real*real + imag*imag > 4) then
    iters
  else
    mandelconverger(real*real - imag*imag + creal,
                    2*real*imag + cimag,
                    iters+1, creal, cimag);

# Return the number of iterations required for the iteration to escape
# 返回iteration to escape所需的迭代次数.
def mandelconverge(real imag)
  mandelconverger(real, imag, 0, real, imag);

这个“z = z2 + c”函数是一个漂亮的小生物(hhh),是计算 Mandelbrot 集合的基础。我们的 mandelconverge 函数返回复杂轨道逃逸所需的迭代次数,饱和度是255。这本身不是一个有用的函数,但是如果你在二维平面上绘制它的值,你能够看到 Mandelbrot set。由于我们仅限于使用 putchard,我们的图形输出是有限的,但是我们可以使用上面的密度绘图仪将一些东西结合在一起。

# Compute and plot the mandelbrot set with the specified 2 dimensional range
# info.
def mandelhelp(xmin xmax xstep   ymin ymax ystep)
  for y = ymin, y < ymax, ystep in (
    (for x = xmin, x < xmax, xstep in
       printdensity(mandelconverge(x,y)))
    : putchard(10)
  )

# mandel - This is a convenient helper function for plotting the mandelbrot set
# from the specified position with the specified Magnification.
def mandel(realstart imagstart realmag imagmag)
  mandelhelp(realstart, realstart+realmag*78, realmag,
             imagstart, imagstart+imagmag*40, imagmag);

有了这个,我们可以尝试绘制出mandelbrot集合! Lets try it out:

ready> mandel(-2.3, -1.3, 0.05, 0.07);
*******************************+++++++++++*************************************
*************************+++++++++++++++++++++++*******************************
**********************+++++++++++++++++++++++++++++****************************
*******************+++++++++++++++++++++.. ...++++++++*************************
*****************++++++++++++++++++++++.... ...+++++++++***********************
***************+++++++++++++++++++++++.....   ...+++++++++*********************
**************+++++++++++++++++++++++....     ....+++++++++********************
*************++++++++++++++++++++++......      .....++++++++*******************
************+++++++++++++++++++++.......       .......+++++++******************
***********+++++++++++++++++++....                ... .+++++++*****************
**********+++++++++++++++++.......                     .+++++++****************
*********++++++++++++++...........                    ...+++++++***************
********++++++++++++............                      ...++++++++**************
********++++++++++... ..........                        .++++++++**************
*******+++++++++.....                                   .+++++++++*************
*******++++++++......                                  ..+++++++++*************
*******++++++.......                                   ..+++++++++*************
*******+++++......                                     ..+++++++++*************
*******.... ....                                      ...+++++++++*************
*******.... .                                         ...+++++++++*************
*******+++++......                                    ...+++++++++*************
*******++++++.......                                   ..+++++++++*************
*******++++++++......                                   .+++++++++*************
*******+++++++++.....                                  ..+++++++++*************
********++++++++++... ..........                        .++++++++**************
********++++++++++++............                      ...++++++++**************
*********++++++++++++++..........                     ...+++++++***************
**********++++++++++++++++........                     .+++++++****************
**********++++++++++++++++++++....                ... ..+++++++****************
***********++++++++++++++++++++++.......       .......++++++++*****************
************+++++++++++++++++++++++......      ......++++++++******************
**************+++++++++++++++++++++++....      ....++++++++********************
***************+++++++++++++++++++++++.....   ...+++++++++*********************
*****************++++++++++++++++++++++....  ...++++++++***********************
*******************+++++++++++++++++++++......++++++++*************************
*********************++++++++++++++++++++++.++++++++***************************
*************************+++++++++++++++++++++++*******************************
******************************+++++++++++++************************************
*******************************************************************************
*******************************************************************************
*******************************************************************************
Evaluated to 0.000000
ready> mandel(-2, -1, 0.02, 0.04);
**************************+++++++++++++++++++++++++++++++++++++++++++++++++++++
***********************++++++++++++++++++++++++++++++++++++++++++++++++++++++++
*********************+++++++++++++++++++++++++++++++++++++++++++++++++++++++++.
*******************+++++++++++++++++++++++++++++++++++++++++++++++++++++++++...
*****************+++++++++++++++++++++++++++++++++++++++++++++++++++++++++.....
***************++++++++++++++++++++++++++++++++++++++++++++++++++++++++........
**************++++++++++++++++++++++++++++++++++++++++++++++++++++++...........
************+++++++++++++++++++++++++++++++++++++++++++++++++++++..............
***********++++++++++++++++++++++++++++++++++++++++++++++++++........        .
**********++++++++++++++++++++++++++++++++++++++++++++++.............
********+++++++++++++++++++++++++++++++++++++++++++..................
*******+++++++++++++++++++++++++++++++++++++++.......................
******+++++++++++++++++++++++++++++++++++...........................
*****++++++++++++++++++++++++++++++++............................
*****++++++++++++++++++++++++++++...............................
****++++++++++++++++++++++++++......   .........................
***++++++++++++++++++++++++.........     ......    ...........
***++++++++++++++++++++++............
**+++++++++++++++++++++..............
**+++++++++++++++++++................
*++++++++++++++++++.................
*++++++++++++++++............ ...
*++++++++++++++..............
*+++....++++................
*..........  ...........
*
*..........  ...........
*+++....++++................
*++++++++++++++..............
*++++++++++++++++............ ...
*++++++++++++++++++.................
**+++++++++++++++++++................
**+++++++++++++++++++++..............
***++++++++++++++++++++++............
***++++++++++++++++++++++++.........     ......    ...........
****++++++++++++++++++++++++++......   .........................
*****++++++++++++++++++++++++++++...............................
*****++++++++++++++++++++++++++++++++............................
******+++++++++++++++++++++++++++++++++++...........................
*******+++++++++++++++++++++++++++++++++++++++.......................
********+++++++++++++++++++++++++++++++++++++++++++..................
Evaluated to 0.000000
ready> mandel(-0.9, -1.4, 0.02, 0.03);
*******************************************************************************
*******************************************************************************
*******************************************************************************
**********+++++++++++++++++++++************************************************
*+++++++++++++++++++++++++++++++++++++++***************************************
+++++++++++++++++++++++++++++++++++++++++++++**********************************
++++++++++++++++++++++++++++++++++++++++++++++++++*****************************
++++++++++++++++++++++++++++++++++++++++++++++++++++++*************************
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++**********************
+++++++++++++++++++++++++++++++++.........++++++++++++++++++*******************
+++++++++++++++++++++++++++++++....   ......+++++++++++++++++++****************
+++++++++++++++++++++++++++++.......  ........+++++++++++++++++++**************
++++++++++++++++++++++++++++........   ........++++++++++++++++++++************
+++++++++++++++++++++++++++.........     ..  ...+++++++++++++++++++++**********
++++++++++++++++++++++++++...........        ....++++++++++++++++++++++********
++++++++++++++++++++++++.............       .......++++++++++++++++++++++******
+++++++++++++++++++++++.............        ........+++++++++++++++++++++++****
++++++++++++++++++++++...........           ..........++++++++++++++++++++++***
++++++++++++++++++++...........                .........++++++++++++++++++++++*
++++++++++++++++++............                  ...........++++++++++++++++++++
++++++++++++++++...............                 .............++++++++++++++++++
++++++++++++++.................                 ...............++++++++++++++++
++++++++++++..................                  .................++++++++++++++
+++++++++..................                      .................+++++++++++++
++++++........        .                               .........  ..++++++++++++
++............                                         ......    ....++++++++++
..............                                                    ...++++++++++
..............                                                    ....+++++++++
..............                                                    .....++++++++
.............                                                    ......++++++++
...........                                                     .......++++++++
.........                                                       ........+++++++
.........                                                       ........+++++++
.........                                                           ....+++++++
........                                                             ...+++++++
.......                                                              ...+++++++
                                                                    ....+++++++
                                                                   .....+++++++
                                                                    ....+++++++
                                                                    ....+++++++
                                                                    ....+++++++
Evaluated to 0.000000
ready> ^D

在此刻,你可能开始意识到 Kaleidoscope 是一个真实的并且强大的语言。It may not be self-similar:),但是它可以被用来绘制那些东西!

我们现在总结本章。我们已经成功的扩展了我们的语言,在库中增加了扩展语言的能力,并且我们已经展示了如何在Kaleidoscope中构建一个简单有趣的应用。在此刻,Kaleidoscope 可以构建各种功能性的应用并且能够调用具有副作用的函数,但是它实际上无法定义并改变变量。

引人注目的是,可变变量在许多语言中是一个重要的特性,在你的前端中没有SSA构造语法时,想要实现对可变变量的支持是不容易的。在下一章节,我们将会描述如何在你的前端,在不构建SSA的情况下添加对可变变量的支持

6.6 Full Code Listing #

http://llvm.org/docs/tutorial/LangImpl06.html#full-code-listing