LLVM教程 (零)

文章简单翻译自LLVM totorial
原始链接

该教程需要有C++语言的编程基础,不过不需要有编译器相关的经验(要是有更好)

这里完整的介绍了一个简单的语言实现,这个教程会展示的使用LLVM生成代码的一个具体例子。

教程介绍了一个简单的语言,叫做“Kaleidoscope”,以迭代的方式在后面的几个教程章节中不断完事这个语言的构建。
这样可以让我们涵盖大量的语言设计和LLVM的技术思想,随着搭建程序的过程不断展示和实验程序,并且减少大量的细节问题。

注意:为了专注于编译器技术和LLVM,该教程不会展示一些关于软件工程的最佳实现原理。如,代码使用满天飞的全局变量,不会使用visitor设计模式等等(这个还是得看看)。

第一章 Kaleidoscope 语言 and Lexer(词法分析器)

本章会介绍我们要做什么,以及要构建的基本功能。通常lexer是构建一个语言的parser(解析器)的第一步,这里使用一个好理解的简单的C++程序实现的lexer。

原文链接

该教程以一个叫做“Kaleidoscope ”的玩具语言来展示编译器的构建过程。
Kaleidoscope 是一个过程语言,用户可以使用这个语言定义函数、使用条件判断和数学表达式等等。
通过这个教程,我们将拓展Kaleidoscope,使其可以支持if/then/else ,for循环, 用户定义运算符,使用命令行的方式进行即时编译(JIT),debug等等功能。
为了使语言简单一些,在Kaleidoscope 语言中只支持一种数据类型,该数据类型为一个64-bit的浮点数类型。
这样所有的变量都会以双精度的形式出现,并且该语言不需要类型声明。这会使语言的语法非常简单。例如下面计算斐波那契的程序。

def fib(x)
  if x < 3 then
    1
  else
    fib(x-1)+fib(x-2)

# This expression will compute the 40th number.
fib(40)

Kaleidoscope 语言可以调用便准库函数,LLVM的JIT会使这个操作非常简单。可以使用extern关键字定义一个函数(可以互相调用和递归使用)如下面:

extern sin(arg);
extern cos(arg);
extern atan2(arg1, arg2);
atan2(sin(.4), cos(42))

更多的例子在第六章,可以写一个小型的Kaleidoscope 应用程序,如写一个Mandelbrot Set (曼德勃罗集合)。

下面进入Kaleidoscope 语言的设计阶段。

Laxer

实现一个编程语言,首先需要做的就是就是处理文本文件和识别它表达的是什么。传统办法是使用"lexer"(也叫"scanner")将输入数据拆分成"token"。 每个token由lexer返回的信息包括token code和一些隐藏元信息(metadata),(例如:数字的数值) 首先定义所有可能的出现。

// The lexer returns tokens [0-255] if it is an unknown character, otherwise one
// of these for known things.
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 由lexer 要么返回一个Token 的枚举值,要么返回一个未知字符的(如‘+’)的ascII码。如果当前的token是一个标识符,那么这个标识符字符串的全局变量会保存该标识符的名称。如果当前的token是一个数字(如1.0),NumVal会保存它的的值。为了简化实现,我们使用全局变量,但是在一个真正的语言实现程序中,这不是一个好的方法。

真正执行lexer是一个名为gettok的函数。这个函数被调用并从当前的输入信息返回下一个token。
定义如下:

/// gettok - Return the next token from standard input.
static int gettok() {
  static int LastChar = ' ';

  // Skip any whitespace.
  while (isspace(LastChar))
    LastChar = getchar();

gettok函数通过调用C语言的getchar()函数从标准的输入中读取一个字符。识别并存储这些字符直到读取到最后一个字符为止,该过程只进行读取,并不进行处理。
首先要做的事事去掉所有token之间的空格。这个过程可以通过一个循环搞定。

接下来gettok函数要做的事识别表示和特殊的关键字,比如"def"。Kaleidoscope 语言实现这些功能使用简单的循环遍历的方式实现。

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;
}

注意,代码中将“IdentifierStr”变量设置成了全局的变量,并使用它来识别一个标识符。同样,一个语言的关键字同样由循环来进行匹配,识别数字变量也是同样的方法。

if (isdigit(LastChar) || LastChar == '.') {   // Number: [0-9.]+
  std::string NumStr;
  do {
    NumStr += LastChar;
    LastChar = getchar();
  } while (isdigit(LastChar) || LastChar == '.');

  NumVal = strtod(NumStr.c_str(), 0);
  return tok_number;
}

上面代码非常直观的处理输入信息。当读入一个数值变量时,我们使用C语言中的strtod函数将这个数值变量的字符串形式转换成数值,并粗出到NumVal中。 注意,这里没有有做充分的错误检查,可能会有不正确的输入信息,比如“1.23.56.67",会被当成”1.23"来处理(可以自己加个逻辑改正掉)

接下来处理注释信息:

if (LastChar == '#') {
  // Comment until end of line.
  do
    LastChar = getchar();
  while (LastChar != EOF && LastChar != '\n' && LastChar != '\r');

  if (LastChar != EOF)
    return gettok();
}

遇到注释信息直接跳过该行并返回下一个token。最后,如果输入没有匹配上任何一个上面的标识符或者关键字,而且也不是一个操作符字符比如"+",又不是读到文件尾部,那么用下面的代码处理:

  // 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;
}

上面的代码就是完整的Kaleidoscope语言的一个lexer,完整代码如下:

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

/// gettok - Return the next token from standard input.
static int gettok() {
  static int LastChar = ' ';

  // Skip any whitespace.
  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;
  }

  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;
}

就是一个简单的字符串处理程序,自己配上所需要的头文件,可以输入一些测试用的文本试试这个lexer的效果,查看一下有什么bug(bug超多,主要是为了理解lexer的功能)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值