在实现语言时,首先需要的是能够处理文本文件并识别它所说的内容。传统的方法是使用“词法分析器”(又名“扫描仪”)将输入分解为“令牌”。词法分析器返回的每个标记包括标记代码和可能的一些元数据(例如,数字的数值)。首先,我们定义了可能性:

// 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枚举值之一,或者它将是一个’未知’字符,如’+’,它将作为ASCII值返回。如果当前令牌是标识符,则IdentifierStr全局变量保存标识符的名称。如果当前标记是数字文字(如1.0),则NumVal保持其值。请注意,为简单起见,我们使用全局变量,这不是真正的语言实现的最佳选择:)。 词法分析器的实际实现是一个名为gettok的函数。调用gettok函数以从标准输入返回下一个标记。其定义开始于:

/// 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()函数从标准输入一次读取一个字符来工作。它在识别它们时吃它们并在LastChar中存储读取但未处理的最后一个字符。它必须做的第一件事就是忽略令牌之间的空格。这是通过上面的循环完成的。 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.45.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();
}

我们通过跳到行尾来处理注释,然后返回下一个令牌。最后,如果输入与上述情况之一不匹配,则它是“+”之类的运算符字符或文件的结尾。这些代码使用以下代码处理:

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

发表评论