通过C中的标记读取字符串标记

时间:2015-04-22 02:05:19

标签: c string parsing loops token

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

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

我的问题是我不知道如何在C ..

中做到这一点

2 个答案:

答案 0 :(得分:0)

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

一种不太可取的方法是对所有可能性进行分类并编写正则表达式以匹配某些有效前缀(这是LEX在引擎盖下所做的事情)。

答案 1 :(得分:0)

在C中,"字符串"只是一个包含字符的内存区域,它由第一个NUL(0)字符终止。在这种情况下,字符串所需的只是指向第一个字符的指针。 (这意味着字符串的长度需要计算,所以尽量避免这种情况比必要时更频繁。)

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

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

尽管如此,只要遵循规则,C中的字符串操作既有效又无故障。例如,如果要从第3个字符开始引用s的子字符串,则可以使用指针算法:s + 2。如果要(暂时)在字符串中的给定点创建子字符串,可以在子字符串末尾的字符串中删除0,然后再恢复那里的字符。 (事实上​​,标准库函数strtok的作用是什么,它是用(f)lex构建的词法扫描器的工作方式。)注意这个策略要求字符数组是 mutable ,因此您无法将其应用于字符串文字。 (但字符串数组很好,因为它们是可变的。)

建立词法扫描程序的最佳选择很可能是使用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;
  }
}