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