我有一个reEntrant解析器,它从字符串中获取输入,并具有维护上下文的结构。调用一个函数时,要解析不同的输入字符串。该职能的相关代码是:
void parseMyString(inputToBeParsed) {
//LEXICAL COMPONENT - INITIATE LEX PROCESSING
yyscan_t scanner;
YY_BUFFER_STATE buffer;
yylex_init_extra(&parseSupportStruct, &scanner );
//yylex_init(&scanner);
buffer = yy_scan_buffer(inputToBeParsed, i+2, scanner);
if (buffer == NULL) {
strcpy(errorStrings,"YY_BUFFER_STATE returned NULL pointer\n");
return (-1);
}
//BISON PART - THE ACTUAL PARSER
yyparse(scanner, &parseSupportStruct);
...
yylex_destroy(scanner);
...
}
我的.l选项是:
%option noinput nounput noyywrap 8bit nodefault
%option yylineno
%option reentrant bison-bridge bison-locations
%option extra-type="parseSupportStructType *"
来自.y的相关行是:
%define api.pure full
%locations
%param { yyscan_t scanner }
%parse-param { parseSupportStructType* parseSupportStruct}
%code {
int yylex(YYSTYPE* yylvalp, YYLTYPE* yyllocp, yyscan_t scanner);
void yyerror(YYLTYPE* yyllocp, yyscan_t unused, parseSupportStructType* parseSupportStruct, const char* msg);
char *yyget_text (yyscan_t);
char *strcpy(char *, const char *);
}
%union {
int numval;
char *strval;
double floatval;
}
在我的解析器中,在某些规则中,我尝试访问yyllocp->_line。在第一次调用parseMyString(.)时,我得到了正确的值。第二次,我得到一些未初始化的值。是否需要在每次调用parseMyString时初始化yyllocp->第一行?我知道我已经给出了部分的、经过修改的代码来解释这种情况。很乐意提供更多的细节。
使用val差制,我已经尽可能地删除了内存泄漏,但是一些第三方库问题超出了我的控制范围。
发布于 2020-01-15 12:12:31
flex或bison中没有任何东西能够维护yylloc
的价值。
Bison解析器(推送分析器除外)将初始化该变量。(如果您接受默认的位置类型--也就是说,您不接受#define YYLTYPE
-- yylloc
将初始化为{1, 1, 1, 1}
。否则,无论它是什么类型,它都将是零初始化的。)Bison还生成代码,根据非终端的第一个和最后一个子节点的位置计算非终端的位置。Flex生成的代码根本不涉及location对象。
如果要求启用此功能,则flex扫描器会自动维护yylineno
。
%option yylineno
Flex通常可以比您更有效地完成这个任务,并且它处理所有的拐角处情况(yyless
、yymore
、input()
、REJECT
)。因此,如果您想跟踪线路号码,我强烈建议让flex做它。
但是,flex的yylineno
支持有一个重要的问题。在可重入扫描器中,行号存储在每个flex缓冲区中,而不是存储在扫描器状态对象中。这几乎肯定是存储它的正确位置,IMHO,因为如果您使用多个缓冲区,它们可能代表多个输入流,通常您会想要引用它的文件中的一行数。但是yy_scan_buffer
并没有初始化这个字段。(因此,yy_scan_string
和yy_scan_bytes
也是如此,它们只是yy_scan_buffer
的包装器。)
因此,如果您使用的是yy_scan_*
接口之一,则应该在yy_scan_*
之后立即调用yyset_lineno
来重置yy_scan_*
。就你而言,这将是:
buffer = yy_scan_buffer(inputToBeParsed, i+2, scanner);
yyset_lineno(1, scanner);
一旦获得了yylineno
,就很容易维护yylloc
对象。Flex有一个钩子,它允许您在执行模式的任何操作之前注入代码(即使操作是空的),并且可以使用这个钩子自动维护yylloc
。在this answer中,我提供了这个技术的一个简单示例(它依赖于由flex生成的扫描器维护的yylineno
):
#define YY_USER_ACTION \
yylloc->first_line = yylloc->last_line; \
yylloc->first_column = yylloc->last_column; \
if (yylloc->last_line == yylineno) \
yylloc->last_column += yyleng; \
else { \
yylloc->last_line = yylineno; \
yylloc->last_column = yytext + yyleng - strrchr(yytext, '\n'); \
}
正如该答复中的说明所指出的,上述内容并不是完全笼统的,但在许多情况下是可行的:
这个
YY_USER_ACTION
宏应该适用于任何不使用yyless()
、yymore()
、input()
或REJECT
的扫描器。正确地处理这些特性并不太困难,但它似乎超出了这里的范围。
您无法在操作之前处理yyless()
、yymore()
或REJECT
(因为在操作之前不可能知道它们是否会被执行),因此,在使用这些功能的应用程序中,更健壮的位置跟踪器必须包括修复yylloc()
的代码。
yylineno
.
yyless()
,上述设置last_line
和last_column
的代码可以在yyless()
调用后重新执行,因为flex扫描器将修复yyleng
和yyleng
REJECT
,因此无法在REJECT
之后插入代码。处理它的唯一方法是保留yylloc
的备份,并在REJECT
宏之前立即恢复它。(我强烈建议不要使用REJECT
。它效率极低,几乎总是可以用调用yyless()
和start yyless()
yymore()
的组合来代替,yylloc
仍然是正确的,但是下一个操作不能覆盖令牌启动位置。正确的操作可能需要维护一个标志来指示是否调用了yymore()
。input()
,如果您希望将读取的字符视为当前令牌的一部分,则可以在调用input()
(这需要区分返回换行符、文件结束指示符或常规字符)之后,在yylloc
中推进结束位置。或者,如果您希望使用input()
读取的字符不被视为任何令牌的一部分,则需要放弃使用前一个令牌的结束位置作为当前令牌的起始位置的想法,这将需要保留一个分隔位置值作为下一个令牌的开始位置。https://stackoverflow.com/questions/59754253
复制