在上一章的旅程中,咱们讨论了词法分析器的实现思路,咱们也为词法分析器的实现作了许多准备工做。如今,就让咱们来实现词法分析器吧。前端
词法分析器的类定义以下:git
class Lexer { public: // Constructor explicit Lexer(const string &inputFilePath); // NextToken Token NextToken(); // Destructor ~Lexer(); private: // Attribute FILE *__filePtr; int __lineNo; // NextToken START Stage Dispatch void __nextTokenStartStage(int nowChar, bool &saveBool, LEXER_STAGE &lexerStage, TOKEN_TYPE &tokenType, string &tokenStr); // NextToken IN_ID Stage Dispatch void __nextTokenInIDStage(int nowChar, bool &saveBool, LEXER_STAGE &lexerStage, TOKEN_TYPE &tokenType, string &tokenStr); // NextToken IN_NUMBER Stage Dispatch void __nextTokenInNumberStage(int nowChar, bool &saveBool, LEXER_STAGE &lexerStage, TOKEN_TYPE &tokenType, string &tokenStr); // NextToken IN_DIVIDE Stage Dispatch void __nextTokenInDivideStage(int nowChar, bool &saveBool, LEXER_STAGE &lexerStage, TOKEN_TYPE &tokenType, string &tokenStr); // NextToken IN_COMMENT Stage Dispatch void __nextTokenInCommentStage(int nowChar, bool &saveBool, LEXER_STAGE &lexerStage, TOKEN_TYPE &tokenType, string &tokenStr); // NextToken END_COMMENT Stage Dispatch void __nextTokenEndCommentStage(int nowChar, bool &saveBool, LEXER_STAGE &lexerStage, TOKEN_TYPE &tokenType, string &tokenStr); // NextToken IN_LESS Stage Dispatch void __nextTokenInLessStage(int nowChar, bool &saveBool, LEXER_STAGE &lexerStage, TOKEN_TYPE &tokenType, string &tokenStr); // NextToken IN_GREATER Stage Dispatch void __nextTokenInGreaterStage(int nowChar, bool &saveBool, LEXER_STAGE &lexerStage, TOKEN_TYPE &tokenType, string &tokenStr); // NextToken IN_ASSIGN Stage Dispatch void __nextTokenInAssignStage(int nowChar, bool &saveBool, LEXER_STAGE &lexerStage, TOKEN_TYPE &tokenType, string &tokenStr); // NextToken IN_NOT Stage Dispatch void __nextTokenInNotStage(int nowChar, bool &saveBool, LEXER_STAGE &lexerStage, TOKEN_TYPE &tokenType, string &tokenStr); };
可见,词法分析器的核心函数是NextToken,每次调用这个函数,词法分析器都会返回下一个解析到的Token;为了实现词法分析器的各个状态的不一样行为,咱们定义了多个分派函数;此外,咱们还定义了一个__lineNo变量,以记录当前的行数,用于报错信息中。ide
接下来,咱们来看看词法分析器的构造函数和析构函数的实现:函数
Lexer::Lexer(const string &inputFilePath): __filePtr(fopen(inputFilePath.c_str(), "r")) {} Lexer::~Lexer() { fclose(__filePtr); }
构造函数和析构函数的实现很简单,这里就不讨论了。spa
接下来,咱们来看看词法分析器最重要的NextToken函数的实现:code
Token Lexer::NextToken() { LEXER_STAGE lexerStage = LEXER_STAGE::START; TOKEN_TYPE tokenType; string tokenStr; while (lexerStage != LEXER_STAGE::DONE) { int nowChar = fgetc(__filePtr); bool saveBool = true; switch (lexerStage) { case LEXER_STAGE::START: __nextTokenStartStage(nowChar, saveBool, lexerStage, tokenType, tokenStr); break; case LEXER_STAGE::IN_ID: __nextTokenInIDStage(nowChar, saveBool, lexerStage, tokenType, tokenStr); break; case LEXER_STAGE::IN_NUMBER: __nextTokenInNumberStage(nowChar, saveBool, lexerStage, tokenType, tokenStr); break; case LEXER_STAGE::IN_DIVIDE: __nextTokenInDivideStage(nowChar, saveBool, lexerStage, tokenType, tokenStr); break; case LEXER_STAGE::IN_COMMENT: __nextTokenInCommentStage(nowChar, saveBool, lexerStage, tokenType, tokenStr); break; case LEXER_STAGE::END_COMMENT: __nextTokenEndCommentStage(nowChar, saveBool, lexerStage, tokenType, tokenStr); break; case LEXER_STAGE::IN_LESS: __nextTokenInLessStage(nowChar, saveBool, lexerStage, tokenType, tokenStr); break; case LEXER_STAGE::IN_GREATER: __nextTokenInGreaterStage(nowChar, saveBool, lexerStage, tokenType, tokenStr); break; case LEXER_STAGE::IN_ASSIGN: __nextTokenInAssignStage(nowChar, saveBool, lexerStage, tokenType, tokenStr); break; case LEXER_STAGE::IN_NOT: __nextTokenInNotStage(nowChar, saveBool, lexerStage, tokenType, tokenStr); break; } if (saveBool) { tokenStr += nowChar; } } return {tokenType, tokenStr, __lineNo}; }
在解析开始前,咱们首先将词法分析器的状态置为“开始”状态,而后不断循环,直至词法分析器的状态变为“完成”状态。在循环体中,词法分析器不断读入下一个字符,并利用一个saveBool布尔值保存当前读入的这个字符是否须要被保存。接下来,根据词法分析器的不一样状态,分别调用各个分派函数。这些分派函数均具备修改saveBool,lexerStage,tokenType,tokenStr这些变量的能力。当分派函数执行完毕后,若是saveBool仍是true,则将当前读取到的字符加入记号字符串中。最终,咱们构造并返回一个Token。token
接下来,咱们来看看各个分派函数的实现,首先从“开始”状态的分派函数开始。ci
__nextTokenStartStage函数用于在词法分析器处于“开始”状态时被调用,其实现以下:字符串
void Lexer::__nextTokenStartStage(int nowChar, bool &saveBool, LEXER_STAGE &lexerStage, TOKEN_TYPE &tokenType, string &tokenStr) { if (isalpha(nowChar)) { lexerStage = LEXER_STAGE::IN_ID; } else if (isdigit(nowChar)) { lexerStage = LEXER_STAGE::IN_NUMBER; } else if (isspace(nowChar)) { saveBool = false; if (nowChar == '\n') { __lineNo++; } } else { switch (nowChar) { case '+': lexerStage = LEXER_STAGE::DONE; tokenType = TOKEN_TYPE::PLUS; break; case '-': lexerStage = LEXER_STAGE::DONE; tokenType = TOKEN_TYPE::MINUS; break; case '*': lexerStage = LEXER_STAGE::DONE; tokenType = TOKEN_TYPE::MULTIPLY; break; case '/': saveBool = false; lexerStage = LEXER_STAGE::IN_DIVIDE; break; case '<': lexerStage = LEXER_STAGE::IN_LESS; break; case '>': lexerStage = LEXER_STAGE::IN_GREATER; break; case '=': lexerStage = LEXER_STAGE::IN_ASSIGN; break; case '!': lexerStage = LEXER_STAGE::IN_NOT; break; case ';': lexerStage = LEXER_STAGE::DONE; tokenType = TOKEN_TYPE::SEMICOLON; break; case ',': lexerStage = LEXER_STAGE::DONE; tokenType = TOKEN_TYPE::COMMA; break; case '(': lexerStage = LEXER_STAGE::DONE; tokenType = TOKEN_TYPE::LEFT_ROUND_BRACKET; break; case ')': lexerStage = LEXER_STAGE::DONE; tokenType = TOKEN_TYPE::RIGHT_ROUND_BRACKET; break; case '[': lexerStage = LEXER_STAGE::DONE; tokenType = TOKEN_TYPE::LEFT_SQUARE_BRACKET; break; case ']': lexerStage = LEXER_STAGE::DONE; tokenType = TOKEN_TYPE::RIGHT_SQUARE_BRACKET; break; case '{': lexerStage = LEXER_STAGE::DONE; tokenType = TOKEN_TYPE::LEFT_CURLY_BRACKET; break; case '}': lexerStage = LEXER_STAGE::DONE; tokenType = TOKEN_TYPE::RIGHT_CURLY_BRACKET; break; case EOF: lexerStage = LEXER_STAGE::DONE; tokenType = TOKEN_TYPE::END_OF_FILE; break; default: InvalidChar(nowChar, __lineNo); break; } } }
“开始”状态是整个词法分析器中最复杂的状态。在这个状态下,词法分析器可能会遇到并处理不少种状况,列举以下:get
这里须要额外说明的是,若是当前读取到的字符是一个“/”,则咱们此时并不知道这个“/”需不须要被保存下来。咱们必须等到肯定这个“/”是一个除号时,再保存这个“/”,故此时,咱们须要将saveBool置为false。
__nextTokenInIDStage与__nextTokenInNumberStage函数分别用于在词法分析器处于“正在读取单词”和“正在读取数字”状态时被调用,其实现以下:
void Lexer::__nextTokenInIDStage(int nowChar, bool &saveBool, LEXER_STAGE &lexerStage, TOKEN_TYPE &tokenType, string &tokenStr) { if (!isalpha(nowChar)) { saveBool = false; ungetc(nowChar, __filePtr); lexerStage = LEXER_STAGE::DONE; tokenType = KEYWORD_MAP.count(tokenStr) ? KEYWORD_MAP.at(tokenStr) : TOKEN_TYPE::ID; } } void Lexer::__nextTokenInNumberStage(int nowChar, bool &saveBool, LEXER_STAGE &lexerStage, TOKEN_TYPE &tokenType, string &tokenStr) { if (!isdigit(nowChar)) { saveBool = false; ungetc(nowChar, __filePtr); lexerStage = LEXER_STAGE::DONE; tokenType = TOKEN_TYPE::NUMBER; } }
当词法分析器处于“正在读取单词”状态时,若是当前读取到的字符仍是一个字母,那么此时什么都不须要作(词法分析器的状态不变,saveBool也不变);若是不是,则咱们知道:此时单词已经读完了,且很重要的一点是:当前读取到的字符并不算在这个单词内。因此,咱们须要将saveBool置为false,并退回当前读取到的字符至文件句柄;同时,咱们将词法分析器的状态置为“完成”状态;此外,咱们须要查阅关键词表,以肯定当前读取到的单词是不是一个关键词。
当词法分析器处于“正在读取数字”状态时,状况与“正在读取单词”状态是几乎一致的。惟独不一样的是,读取数字时不须要进行关键词断定。
__nextTokenInDivideStage函数用于在词法分析器处于“正在读取除号”状态时被调用,其实现以下:
void Lexer::__nextTokenInDivideStage(int nowChar, bool &saveBool, LEXER_STAGE &lexerStage, TOKEN_TYPE &tokenType, string &tokenStr) { if (nowChar == '*') { saveBool = false; lexerStage = LEXER_STAGE::IN_COMMENT; } else { saveBool = false; ungetc(nowChar, __filePtr); lexerStage = LEXER_STAGE::DONE; tokenType = TOKEN_TYPE::DIVIDE; tokenStr = "/"; } }
当词法分析器已经读取到了一个“/”后,其进入“正在读取除号”状态(请注意,此时的saveBool是false)。此时,若是又读取到了一个“”,则词法分析器应进入“正在读取注释”状态;反之,若是读取到的字符不是“”,咱们就能够肯定:以前读取到的“/”真的是一个除号。那么咱们就须要退回当前读取到的这个字符,而后将词法分析器的状态置为“完成”状态,并设定记号的类别和记号字符串。
__nextTokenInCommentStage和__nextTokenEndCommentStage函数分别用于在词法分析器处于“正在读取注释”和“正在逃离注释”状态时被调用,其实现以下:
void Lexer::__nextTokenInCommentStage(int nowChar, bool &saveBool, LEXER_STAGE &lexerStage, TOKEN_TYPE &tokenType, string &tokenStr) { saveBool = false; if (nowChar == '*') { lexerStage = LEXER_STAGE::END_COMMENT; } } void Lexer::__nextTokenEndCommentStage(int nowChar, bool &saveBool, LEXER_STAGE &lexerStage, TOKEN_TYPE &tokenType, string &tokenStr) { saveBool = false; if (nowChar == '/') { lexerStage = LEXER_STAGE::START; } else if (nowChar != '*') { lexerStage = LEXER_STAGE::IN_COMMENT; } }
首先,不论是哪一个函数,只要和注释搭边了,saveBool都应置false。
正如上一章所说,当词法分析器处于“正在读取注释”状态时,其只但愿看到“*”,若是看到了,则词法分析器的状态就应转入“正在逃离注释”状态,不然,什么都没有变,词法分析器将仍然处于“正在读取注释”状态。
同理,当词法分析器处于“正在逃离注释”状态时,若是其看到的是“/”,则词法分析器就成功逃离了注释,其状态就回到了“开始状态”;而若是其看到的仍是“*”,则词法分析器将继续停留在“正在逃离注释”状态;不然,很惋惜,词法分析器应退回到“正在读取注释”状态。
__nextTokenInLessStage、__nextTokenInGreaterStage、__nextTokenInAssignStage、__nextTokenInNotStage函数分别用于在词法分析器处于“正在读取小于号”、“正在读取大于号”、“正在读取等号”和“正在读取不等号”状态时被调用,其实现以下:
void Lexer::__nextTokenInLessStage(int nowChar, bool &saveBool, LEXER_STAGE &lexerStage, TOKEN_TYPE &tokenType, string &tokenStr) { lexerStage = LEXER_STAGE::DONE; if (nowChar == '=') { tokenType = TOKEN_TYPE::LESS_EQUAL; } else { saveBool = false; ungetc(nowChar, __filePtr); tokenType = TOKEN_TYPE::LESS; } } void Lexer::__nextTokenInGreaterStage(int nowChar, bool &saveBool, LEXER_STAGE &lexerStage, TOKEN_TYPE &tokenType, string &tokenStr) { lexerStage = LEXER_STAGE::DONE; if (nowChar == '=') { tokenType = TOKEN_TYPE::GREATER_EQUAL; } else { saveBool = false; ungetc(nowChar, __filePtr); tokenType = TOKEN_TYPE::GREATER; } } void Lexer::__nextTokenInAssignStage(int nowChar, bool &saveBool, LEXER_STAGE &lexerStage, TOKEN_TYPE &tokenType, string &tokenStr) { lexerStage = LEXER_STAGE::DONE; if (nowChar == '=') { tokenType = TOKEN_TYPE::EQUAL; } else { saveBool = false; ungetc(nowChar, __filePtr); tokenType = TOKEN_TYPE::ASSIGN; } } void Lexer::__nextTokenInNotStage(int nowChar, bool &saveBool, LEXER_STAGE &lexerStage, TOKEN_TYPE &tokenType, string &tokenStr) { if (nowChar == '=') { lexerStage = LEXER_STAGE::DONE; tokenType = TOKEN_TYPE::NOT_EQUAL; } else { InvalidChar(nowChar, __lineNo); } }
这几个函数的实现思路都是一致的,其均用于处理相似于:当前记号是一个“<”仍是一个“<=”的矛盾。咱们不妨以“正在读取小于号”为例,来看看这几个函数的实现。
当词法分析器已经读取到了一个“<”后,其进入“正在读取小于号”状态。在此状态下,不管当前读取到的字符是什么,词法分析器都必定会进入“完成”状态。咱们只须要看看当前读取到的字符是否是一个“=”,若是是,则咱们就读取到了一个“<=”;若是不是,则咱们就只是读取到了一个“<”,和前面同样,此时咱们须要置saveBool为false,并退回当前读取到的字符。
略有不一样的是,当词法分析器读取到一个“!”,而后进入“正在读取不等号”状态后,其必须继续读取到一个“=”,以构成“!=”;而若是此时读取到的字符并非“=”,则将被认为是一个语法错误。
至此,“前端中的前端”——词法分析器,就已经所有实现完成了。接下来,咱们须要为实现编译器前端的第二个组件——语法分析器作准备。请看下一章:《实现语法分析器前的准备》。