解释器开发过程中的解析令牌问题

3

我正在用C++构建一个代码解释器,虽然我已经使所有的Token逻辑工作正常,但我遇到了一个意想不到的问题。

用户在控制台中输入一个字符串,程序将该字符串解析为不同类型的Token对象,但问题是我是通过以下方式实现的:

void splitLine(string aLine) {

    stringstream ss(aLine);
    string stringToken, outp;
    char delim = ' ';

    // Break input string aLine into tokens and store them in rTokenBag
    while (getline(ss, stringToken, delim)) { 

        // assing value of stringToken parsed to t, this labes invalid tokens
        Token t (readToken(stringToken)); 

        R_Tokens.push_back(t);
    }   
}

问题在于,如果解析器接收到一个字符串,比如说 Hello World!,它会将其拆分为两个标记,HelloWorld!
主要目标是让代码将双引号识别为字符串标记的起始,并将其作为一个整体存储(从 ""),作为一个单一的标记。因此,如果输入 x = "hello world",它将把 x 存储为一个标记,然后把下一个运行符 = 存储为一个标记,最后将 hello world 存储为一个标记,而不是拆分成多个标记。

5
你到底想要实现什么目标?我猜你想要的是“World”而不是“World!”吧? - user820304
@Thebluefish 我希望它能够将“Hello World!”识别为一个单独的标记,而不是通过空格分隔。 - John
我认为你应该先使用字符“”将行分割以提取“真实字符串”,然后再使用字符“”将其余部分分割。 - Benjamin Barrois
检查是否找到引号,然后忽略所有空格直到找到另一个引号。 - Arnav Borborah
C++14内置了它 http://en.cppreference.com/w/cpp/io/manip/quoted - 7hibault
显示剩余6条评论
3个回答

2

您可以使用C++14的quoted操作符。

#include <string>
#include <sstream>
#include <iomanip>

#include <iostream>

void splitLine(std::string aLine) {

    std::istringstream iss(aLine);
    std::string stringToken;

    // Break input string aLine into tokens and store them in rTokenBag
    while(iss >> std::quoted(stringToken)) {
        std::cout << stringToken << "\n";
    }
}

int main() {

    splitLine("Heloo world \"single token\" new tokens");
}

2
你真的不想通过在分隔符处拆分来对编程语言进行标记化。
一个合适的标记器将根据第一个字符切换以决定要读取哪种类型的标记,然后继续读取符合该标记类型的字符,当它找到第一个不匹配的字符时发出该标记(这将用作下一个标记的起点)。
这可能看起来像这样(假设 it 是一个 istreambuf_iterator 或其他迭代器,按字符迭代输入):
Token Tokenizer::next_token() {
    if (isalpha(*it)) {
        return read_identifier();
    } else if(isdigit(*it)) {
        return read_number();
    } else if(*it == '"') {
        return read_string();
    } /* ... */
}

Token Tokenizer::read_string() {
    // This should only be called when the current character is a "
    assert(*it == '"');
    it++;
    string contents;
    while(*it != '"') {
        contents.push_back(*it);
        it++;
    }
    return Token(TokenKind::StringToken, contents);
}

这样做不能处理转义序列或在没有看到第二个"的情况下到达文件末尾,但它应该能给您基本的想法。

std::quoted这样的东西可能会解决您与字符串文字的即时问题,但如果您希望x="hello world"x = "hello world"被标记化相同(您几乎肯定是这样的),则无法帮助您。


附注:您还可以先将整个源读入内存,然后让令牌包含源中的索引或指针,而不是字符串(因此,您只需要在循环之前保存开始索引,然后返回Token(TokenKind::StringToken, start_index, current_index))。哪种更好部分取决于解析器的操作。如果您的解析器直接产生结果,并且在处理完它们之后不需要保留令牌,则第一种方法更节省内存,因为您永远不需要将整个源保留在内存中。如果您创建AST,则无论如何内存消耗都将大致相同,但第二个版本将允许您拥有一个大字符串,而不是许多小字符串。


我的程序逻辑是:将输入字符串拆分为标记并将它们放入正确的堆栈中。然后,通过按照您所说的方式逐个将它们传递到左侧堆栈,首先读取,然后决定下一个是否有效。只有当我能够将输入字符串拆分为所需的标记时,这才有效。 - John
2
@John 你生成令牌后的处理方式是另一回事,不会影响如何生成它们。我的意思是,你用分隔符拆分的方法对于任何具有稍微复杂词法规则的语言都不起作用(就像我在 x="hello world" 的例子中所看到的)。因此,你需要通过逐个字符处理输入来生成令牌,而不是尝试通过 operator>> 的内置拆分来获取一个令牌。之后,你可以逐个处理生成的令牌流。 - sepp2k
那我最好开始阅读了,因为我完全不知道如何做你刚才说的事情,你说得对,它应该允许在没有使用空格分隔符的情况下进行编写。有什么方法可以实现这一点吗?在输入字符串上使用substr()并查看是否等于定义的标记?如果是,则删除substr()并继续进行? - John
@John,我在我的回答中添加了一个代码示例。也许这会使事情更清晰。 - sepp2k
嘿,只是想让你知道我找到了一种使用getline()的方法来解决它。但无论如何感谢你的所有帮助,我知道我的方法在实践中可能很糟糕,但我有点赶时间,而且这是一个应该由5个人完成的任务,我现在是独自完成。再次感谢你的时间和帮助! - John

0

所以我终于找到了解决方法,我可以使用getline()来实现我的目标。

这段新代码运行并解析的方式符合我的需求:

    void splitLine(string aLine) {

    stringstream ss(aLine);
    string stringToken, outp;
    char delim = ' ';

    while (getline(ss, stringToken, delim)) { // Break line into tokens and store them in rTokenBag

        //new code starts here
        // if the current parse sub string starts with double quotes
        if (stringToken[0] == '"' ) { 

            string torzen;
            // parse me the rest of ss until you find another double quotes
            getline(ss, torzen, '"' ); 

           // Give back the space cut form the initial getline(), add the parsed sub string from the second getline(), and add a double quote at the end that was cut by the second getline()
            stringToken += ' ' + torzen + '"'; 

        }
        // And we can all continue with our lives 
        Token t (readToken(stringToken)); // assing value of stringToken parsed to t, this labes invalid tokens

        R_Tokens.push_back(t);

    }


}

感谢所有回答和评论的人,你们都非常有帮助!


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接