我正在尝试为一种功能语言创建一个词法分析器,其中一种方法应允许在每次调用时返回文件的下一个标记。 例如:
func main() {
var MyVar : integer = 3+2;
}
因此,我希望每次调用next方法时,都返回该序列中的下一个标记;在这种情况下,它看起来像这样:
func
main
(
)
{
var
MyVar
:
integer
=
3
+
2
;
}
除了我得到的结果不是我期望的那样:
func
main(
)
{
var
MyVar
:
integer
=
3+
2
}
这是我的方法:
token_t Lexer::next() {
token_t ret;
std::string token_tmp;
bool IsSimpleQuote = false; // check string --> "..."
bool IsDoubleQuote = false; // check char --> '...'
bool IsComment = false; // check comments --> `...`
bool IterWhile = true;
while (IterWhile) {
bool IsInStc = (IsDoubleQuote || IsSimpleQuote || IsComment);
std::ifstream file_tmp(this->CurrentFilename);
if (this->eof) break;
char chr = this->File.get();
char next = file_tmp.seekg(this->CurrentCharIndex + 1).get();
++this->CurrentCharInCurrentLineIndex;
++this->CurrentCharIndex;
{
if (!IsInStc && !IsComment && chr == '`') IsComment = true; else if (!IsInStc && IsComment && chr == '`') { IsComment = false; continue; }
if (IsComment) continue;
if (!IsInStc && chr == '"') IsDoubleQuote = true;
else if (!IsInStc && chr == '\'') IsSimpleQuote = true;
else if (IsDoubleQuote && chr == '"') IsDoubleQuote = false;
else if (IsSimpleQuote && chr == '\'') IsSimpleQuote = false;
}
if (chr == '\n') {
++this->CurrentLineIndex;
this->CurrentCharInCurrentLineIndex = -1;
}
token_tmp += chr;
if (!IsInStc && IsLangDelim(chr)) IterWhile = false;
}
if (token_tmp.size() > 1 && System::Text::EndsWith(token_tmp, ";") || System::Text::EndsWith(token_tmp, " ")) token_tmp.pop_back();
++this->NbrOfTokens;
location_t pos;
pos.char_pos = this->CurrentCharInCurrentLineIndex;
pos.filename = this->CurrentFilename;
pos.line = this->CurrentLineIndex;
SetToken_t(&ret, token_tmp, TokenList::ToToken(token_tmp), pos);
return ret;
}
这是IsLangDelim函数:
bool IsLangDelim(char chr) {
return (chr == ' ' || chr == '\t' || TokenList::IsSymbol(CharToString(chr)));
}
TokenList是一个命名空间,其中包含令牌列表以及一些函数(在本例中为IsSymbol)。
我已经尝试过该方法的其他版本,但是结果几乎总是相同的。
您是否知道如何改进此方法?
答案 0 :(得分:1)
您的问题的解决方案是使用std::regex
。刚开始时,了解语法会有些困难,但是了解语法之后,您将始终使用它。
而且,它旨在查找令牌。
特定的标准可以在正则表达式字符串中表示。
对于您的情况,我将使用:std::regex re(R"#((\w+|\d+|[;:\(\)\{\}\+\-\*\/\%\=]))#");
这意味着:
您可以将正则表达式扩展为要搜索的所有其他内容。您还可以对正则表达式结果进行正则表达式。
请参见下面的示例。这将从提供的输入中创建显示的输出。
而且,您所描述的任务只是main中的一条语句。
#include <iostream>
#include <string>
#include <algorithm>
#include <regex>
// Our test data (raw string) .
std::string testData(
R"#(func main() {
var MyVar : integer = 3+2;
}
)#");
std::regex re(R"#((\w+|\d+|[;:\(\)\{\}\+\-\*\/\%\=]))#");
int main(void)
{
std::copy(
std::sregex_token_iterator(testData.begin(), testData.end(), re, 1),
std::sregex_token_iterator(),
std::ostream_iterator<std::string>(std::cout, "\n")
);
return 0;
}
答案 1 :(得分:0)
您尝试使用单循环进行解析,这使代码非常复杂。相反,我建议是这样的:
struct token { ... };
struct lexer {
vector<token> tokens;
string source;
unsigned int pos;
bool parse_ident() {
if (!is_alpha(source[pos])) return false;
auto start = pos;
while(pos < source.size() && is_alnum(source[pos])) ++pos;
tokens.push_back({ token_type::ident, source.substr(start, pos - start) });
return true;
}
bool parse_num() { ... }
bool parse_comment() { ... }
...
bool parse_whitespace() { ... }
void parse() {
while(pos < source.size()) {
if (!parse_comment() && !parse_ident() && !parse_num() && ... && !parse_comment()) {
throw error{ "unexpected character at position " + std::to_string(pos) };
}
}
}
当以我编写的任何脚本语言对文件进行词法化时,这是我使用的标准结构。 Lexing通常是贪婪的,因此您不必理会regex(这是有效的,但速度较慢,除非基于某些疯狂的基于模板的实现)。只需定义您的parse_*
函数,并确保它们返回false,如果它们没有解析令牌,并确保它们以正确的顺序被调用。
订单本身通常并不重要,但是:
.123
中的数字可能被错误地识别为.
运算符(因此,您需要确保在.
之后没有数字。