【问题标题】:Parsing tokens issue during interpreter development在解释器开发期间解析令牌问题
【发布时间】:2018-08-29 14:38:50
【问题描述】:

我正在用 C++ 构建一个代码解释器,虽然我的整个令牌逻辑都在工作,但我遇到了一个意想不到的问题。

用户在控制台输入一个字符串,程序将所说的字符串解析为不同的对象类型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!

主要目标是让代码将双引号识别为字符串 Token 的开头,并将其整个(从 "")存储为单个 Token。 因此,如果我输入x = "hello world",它将存储x 作为令牌,然后接下来运行= 作为令牌,然后运行hello world 作为令牌而不是拆分它

【问题讨论】:

  • 您到底想完成什么?我猜你想要“世界”而不是“世界!”?
  • @Thebluefish 我希望它能够识别“Hello World!”作为单个标记,而不是用空格分隔符分割它
  • 标题似乎与给出的示例不匹配,“Hello world!”如何?与解析双打有关?
  • 我认为你应该先用字符'"'分割你的行以提取'真正的字符串',然后用字符''分割其余的。
  • 检查是否找到引用,然后忽略所有空格,直到找到另一个引用。

标签: c++ parsing token


【解决方案1】:

您可以使用 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" 相同的方式标记化(您几乎肯定会这样做),它不会帮助您。


    PS:您也可以先将整个源代码读入内存,然后让您的标记包含指向源代码而不是字符串的索引或指针(因此,您只需在循环然后返回Token(TokenKind::StringToken, start_index, current_index))。哪个更好部分取决于您在解析器中所做的事情。如果您的解析器直接产生结果并且您不需要在处理后保留标记,那么第一个更节省内存,因为您不需要将整个源保存在内存中。如果您创建一个 AST,那么两种方式的内存消耗都差不多,但第二个版本将允许您拥有一个大字符串而不是许多小字符串。

    【讨论】:

    • 我的程序背后的逻辑是:将输入的字符串拆分成Token,放入正确的栈中。然后按照您所说的将它们逐个传递到左侧堆栈,先阅读然后决定下一个是否有效。这仅在我可以将输入字符串拆分为我需要的标记时才有效
    • @John 生成令牌后如何处理令牌完全是另一回事,并且不会真正影响生成它们的方式。我的意思是,您在分隔符处拆分的方法根本不适用于任何具有稍微不重要的词汇规则的语言(如我的x="hello world" 示例中所示)。因此,您需要通过逐个字符处理输入来生成标记,而不是尝试通过operator&gt;&gt; 的内置拆分一次获取一个标记。之后,您可以逐个令牌处理生成的令牌流。
    • 那么我最好开始阅读,因为我对如何执行您刚才所说的操作一无所知,而且您是对的,它应该允许在不使用空格分隔符的情况下进行写入.什么是实现这一点的方法?在输入字符串上使用 substr() 并查看它是否等于定义的标记?如果确实如此,则删除 substr() 并继续?
    • @John 我在答案中添加了一个代码示例。也许这会让事情变得更清楚。
    • 嘿只是想让你知道我想出了一个方法来使用 getline()。但是无论如何感谢您的所有帮助,我知道我的方法在实践中可能很糟糕,但是我有点匆忙,并且当它应该是一个 5 人的努力时我正在做这个独奏。再次感谢您的宝贵时间和帮助!
    【解决方案3】:

    所以我终于想通了,我可以使用 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);
    
        }
    
    
    }
    

    感谢所有回答和评论的人,你们都帮了大忙!

    【讨论】:

      猜你喜欢
      • 2011-05-13
      • 1970-01-01
      • 2013-10-17
      • 2012-02-10
      • 2018-11-29
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-04-22
      相关资源
      最近更新 更多