【问题标题】:Reading String token by token in C在C中逐个标记读取字符串标记
【发布时间】:2015-04-22 02:05:18
【问题描述】:

我正在尝试使用给我的特定语法在 C 中构建一个 LL(1) 递归下降解析器。我知道如何递归地执行此操作……但是,我的问题是阻止我真正开始实施。我对C不太熟悉,所以我确定这就是我遇到问题的原因。基本上,我需要能够逐个读取一个字符串,例如"(1+2)*3" 令牌。因此,例如,在我上面的字符串的情况下,我需要首先阅读"(",然后在递归过程的下方,我会调用类似nextToken() 这样的东西,这会给我"1"

话虽如此,最终我可能只需要读取我称之为"nextToken()的每个字符串的第一个标记,因为在我获取值后,我会将初始字符串更改为与以前相同,减去最近读取的令牌。例如,我从"(1+2)*3" 开始,然后在字符串上调用nextToken(),这意味着我得到"(",然后初始字符串现在是"1+2)*3"

我的问题是我不知道如何在 C 中做到这一点。

【问题讨论】:

  • 在这里查看一些代码:dailyfreecode.com/code/ll1-parsing-1665.aspx
  • 不是很高兴,但是如果您对 C 不够了解,则需要掌握一些 C 基础知识。否则你只会不断地与工具作斗争。
  • 我精通 Java,它是 C 的派生词,所以我知道一些基础知识。我只是被困在这个特定问题上

标签: c string parsing loops token


【解决方案1】:

这就是“词法分析器”所做的,通常在解析器之前。我想你能做的最好的就是尝试 LEX(​​可能是 Flex 和 Bison 中的 flex)。 (确实,词法分析器的工作也可以单独在解析器中完成,但可能会更混乱。)

一种不太可取的方法是对所有可能性进行分类并编写正则表达式来匹配一些有效的前缀(这是 LEX 在后台所做的)。

【讨论】:

    【解决方案2】:

    在 C 中,“字符串”只是一个包含字符的内存区域,它以第一个 NUL (0) 字符终止。在这种情况下,字符串所需要的只是指向第一个字符的指针。 (这意味着需要计算字符串的长度,因此请尽量避免不必要地这样做。)

    有一些标准库函数可以执行比较字符串和复制字符串等操作,但重要的是要记住字符串的内存管理是您的责任

    虽然对于那些习惯于将字符串作为实际数据类型的语言的人来说,这可能看起来很原始、容易出错并且很复杂,但事实就是如此。如果您打算在 C 中进行字符串操作,则需要习惯它。

    不过,只要您遵守规则,C 语言中的字符串操作既高效又轻松。例如,如果要引用从第 3 个字符开始的s 的子字符串,则可以使用指针算法:s + 2。如果您想(临时)在字符串的给定点创建子字符串,您可以在子字符串的末尾将 0 放入字符串中,然后再恢复那里的字符。 (事实上​​,这就是标准库函数 strtok 所做的,也是使用 (f)lex 构建的词法扫描器的工作方式。)请注意,此策略要求字符数组是可变,所以您将无法将其应用于字符串文字。 (不过,字符串数组很好,因为它们是可变的。)

    构建词法扫描器的最佳选择很可能是使用flex。 flex 构建的扫描器将为您做很多事情,包括输入缓冲,并且 flex 允许您指定正则表达式而不是手动编码它们。

    但是,如果您想手动完成,这并不难,特别是如果整个输入都在内存中,因此不需要缓冲。 (如果没有标记跨越一行,您也可以一次读取一行输入,但这不如读取固定长度的块高效,这是 flex 扫描器会做的。)

    例如,这里是一个简单的扫描器,它处理算术运算符、整数和标识符。它不使用“用 NUL 覆盖”策略,因此可以与字符串文字一起使用。对于标识符,它会创建一个新分配的字符串,因此调用者在不再需要该标识符时需要free。 (没有垃圾收集。C'est la vie。)令牌通过引用参数“返回”;函数的实际返回值是指向源字符串其余部分的指针。省略了很多错误检查。

    #include <ctype.h>
    #include <stdlib.h>
    #include <string.h>
    
    /* The type of a single-character operators is the character, so
     * other token types need to start at 256. We use 0 to indicate
     * the end of input token type.
     */
    enum TokenType { NUMBER = 256, ID };
    typedef struct Token {
      enum TokenType token_type;
      union { /* Anonymous unions are a C11 feature. */
        long      number;  /* Only valid if type is NUMBER */
        char*     id;      /* Only valid if type is ID */
      }; 
    } Token;
    
    /* You would normally call this like this:
     * do {
     *   s = next_token(s, &token);
     *   // Do something with token
     * } while (token.token_type);
     */
    const char* next_token(const char* input, Token* out) {
      /* Skip whitespace */
      while (isspace(*input)) ++input;
      if (isdigit(*input)) {
        char* lim;
        out->number = strtol(input, &lim, 10);
        out->token_type = NUMBER;
        return lim;
      } else if (isalpha(*input)) {
        const char* lim = input + 1;
        /* Find the end of the id */
        while (isalnum(*lim)) ++lim;
        /* Allocate enough memory to copy the id. We need one extra byte
         * for the NUL
         */
        size_t len = lim - input;
        out->id = malloc(len + 1);
        memcpy(out->id, input, len);
        out->id[len] = 0;  /* NUL-terminate the string */
        out->token_type = ID;
        return lim;
      } else {
        out->token_type = *input;
        /* If we hit the end of the input string, we don't advance the
         * input pointer, to avoid reading random memory.
         */
        return *input ? input + 1 : input;
      }
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2012-06-02
      • 1970-01-01
      • 2010-09-20
      • 2011-05-31
      • 1970-01-01
      相关资源
      最近更新 更多