Flex/Bison: yytext 跳过一个值
Flex/Bison: yytext skips over a value
两天来我一直在绞尽脑汁想弄清楚程序为什么会这样。对于 class 项目,我正在尝试编写一个程序来解析地址并以某种方式输出它。在我真正进入程序的输出部分之前,我只是想确保我的 Bison-fu 实际上是正确的并且正确地输出了一些调试信息。
看起来 Flex 和 Bison 合作得很好,正如预期的那样,但出于某种原因,当我解析地址的第三行时,yytext 只是跳过邮政编码并转到直接换行。
下面是我测试过的 Flex 和 Bison 文件的精简版本,它仍然输出与完整版本相同的内容:
[19:45]<Program4> $ cat scan.l
%option noyywrap
%option nounput
%option noinput
%{
#include <stdlib.h>
#include "y.tab.h"
#include "program4.h"
%}
%%
[\ \t]+ { /* Eat whitespace */}
[\n] { return EOLTOKEN; }
"," { return COMMATOKEN; }
[0-9]+ { return INTTOKEN; }
[A-Za-z]+ { return NAMETOKEN; }
[A-Za-z0-9]+ { return IDENTIFIERTOKEN; }
%%
/*This area just occupies space*/
[19:45]<Program4> $ cat parse.y
%{
#include <stdlib.h>
#include <stdio.h>
#include "program4.h"
%}
%union {int num; char id[20]; }
%start locationPart
%expect 0
%token <num> NAMETOKEN
%token <num> EOLTOKEN
%token <num> INTTOKEN
%token <num> COMMATOKEN
%type <id> townName zipCode stateCode
%%
/* Entire block */
locationPart: townName COMMATOKEN stateCode zipCode EOLTOKEN
{ printf("Rule 12: LP: TN COMMA SC ZC EOL: %s\n", yytext); }
| /* bad location part */
{ printf("Rule 13: LP: Bad location part: %s\n", yytext); }
;
/* Lil tokens */
townName: NAMETOKEN
{ printf("Rule 23: TN: NAMETOKEN: %s\n", yytext); }
;
stateCode: NAMETOKEN
{ printf("Rule 24: SC: NAMETOKEN: %s\n", yytext); }
;
zipCode: INTTOKEN DASHTOKEN INTTOKEN
{ printf("Rule 25: ZC: INT DASH INT: %s\n", yytext); }
| INTTOKEN
{ printf("Rule 26: ZC: INT: %s\n", yytext); }
;
%%
int yyerror (char const *s){
extern int yylineno; //Defined in lex
fprintf(stderr, "ERROR: %s at symbol \"%s\"\n at line %d.\n", s, yytext,
yylineno);
exit(1);
}
[19:45]<Program4> $ cat addresses/zip.txt
Rockford, HI 12345
[19:45]<Program4> $ parser < addresses/zip.txt
Operating in parse mode.
Rule 23: TN: NAMETOKEN: Rockford
Rule 24: SC: NAMETOKEN: HI
Rule 26: ZC: INT:
Rule 12: LP: TN COMMA SC ZC EOL:
Parse successful!
[19:46]<Program4> $
正如您在底部附近看到的那样,它打印 Rule 26: ZC: INT:
但无法打印 5 位邮政编码。这就像程序只是跳过数字并存储换行符一样。知道为什么它不存储和打印邮政编码吗?
备注:
- yytext 在我的 .h 文件中定义为 extern(此处未发布);
- 我正在使用
-vdy
标志来编译 parse.c 文件
如果您想跟踪解析器的工作情况,最好启用 bison 的跟踪功能。这真的很容易。只需将 -t
或 --debug
标志添加到 bison
命令以生成代码,然后添加一行以实际生成跟踪:
/* This assumes you have #included the parse.tab.h header */
int main(void) {
#if YYDEBUG
yydebug = 1;
#endif
这在the Bison manual中有解释;如果您离开 -t
标志,#if
允许您的程序编译。关于标志,我强烈建议您不要使用 -y
标志;它用于编译依赖于某些过时功能的旧 Yacc 程序。如果您不使用 -y
,那么 bison 将使用您的 .y
文件的基本名称,生成的文件扩展名为 .tab.c
和 .tab.h
。
现在,您的 bison 文件说您的一些标记具有语义类型,但是您的 flex 操作没有为这些标记设置语义值,并且您的 bison 操作不使用语义值。相反,您只需打印 yytext
的值。如果您稍微考虑一下,您应该能够明白为什么它不起作用。 Bison 是一个 lookahead 解析器;它根据当前解析状态和下一个标记(如有必要)的窥视做出解析决策。它通过调用词法分析器来查看下一个标记。当您调用词法分析器时,它会更改 yytext
.
的值
Bison(与其他 yacc 实现不同)并不总是查看下一个标记。但是在您的邮政编码规则中,它别无选择,因为如果不查看它就无法判断下一个标记是否为 -
。在这种情况下,它不是破折号;这是一个换行符。所以当你在邮政编码操作中打印出来时,猜猜 yytext
包含什么。
如果您的分词器将文本保存在 id
语义值成员中(这就是它的用途),那么您的解析器将能够访问语义值作为 </code>,<code>
, ...
因为 yytext
是一个全局变量,它被覆盖了,你必须将它复制到你的 lex 脚本中。在纯解析器中,即使它不再是全局的,它仍然会被重用并作为参数传递,因此像您尝试的那样使用它的值是不正确的。
此外,不要在 bison 中使用它,而是使用 $n
,其中 n
是令牌在规则中的位置。您可能需要将 %union
指令更改为
%union {
int number;
char *name;
};
所以在 flex 文件中,如果你想捕获文本,请执行类似
的操作
[A-Za-z]+ { yylval.name = strdup(yytext); return NAMETOKEN; }
记住,不要在 bison 中使用 yytext
,它是词法分析器使用的内部东西。
然后,由于您已经为邮政编码定义了类型
/* Entire block */
locationPart: townName COMMATOKEN stateCode zipCode EOLTOKEN {
printf("Rule 12: LP: TN COMMA SC ZC EOL: town:%s, stateCode:%d zip-code:%s\n", , , );
}
问题出在这里:
zipCode: INTTOKEN DASHTOKEN INTTOKEN { // case 25 }
| INTTOKEN { // case 26 }
;
解析器不知道要采用哪个规则--25 或 26--直到它解析下一个标记以查看它是否是 DASHTOKEN。到代码执行的时候,yytext已经被覆盖了。
处理此问题的最简单方法是生成一个采用 INTTOKEN 和 returns malloc() 内存中 yytext[] 中的内容的产生式。类似于:
zipCode: inttoken DASHTOKEN inttoken
{
printf("Rule 25: zip is %s-%s\n", , );
free();
free();
}
| inttoken
{
printf("Rule 26: zip is %s\n", );
free();
}
;
inttoken: INTTOKEN { $$ = strdup(yytext); }
;
两天来我一直在绞尽脑汁想弄清楚程序为什么会这样。对于 class 项目,我正在尝试编写一个程序来解析地址并以某种方式输出它。在我真正进入程序的输出部分之前,我只是想确保我的 Bison-fu 实际上是正确的并且正确地输出了一些调试信息。
看起来 Flex 和 Bison 合作得很好,正如预期的那样,但出于某种原因,当我解析地址的第三行时,yytext 只是跳过邮政编码并转到直接换行。
下面是我测试过的 Flex 和 Bison 文件的精简版本,它仍然输出与完整版本相同的内容:
[19:45]<Program4> $ cat scan.l
%option noyywrap
%option nounput
%option noinput
%{
#include <stdlib.h>
#include "y.tab.h"
#include "program4.h"
%}
%%
[\ \t]+ { /* Eat whitespace */}
[\n] { return EOLTOKEN; }
"," { return COMMATOKEN; }
[0-9]+ { return INTTOKEN; }
[A-Za-z]+ { return NAMETOKEN; }
[A-Za-z0-9]+ { return IDENTIFIERTOKEN; }
%%
/*This area just occupies space*/
[19:45]<Program4> $ cat parse.y
%{
#include <stdlib.h>
#include <stdio.h>
#include "program4.h"
%}
%union {int num; char id[20]; }
%start locationPart
%expect 0
%token <num> NAMETOKEN
%token <num> EOLTOKEN
%token <num> INTTOKEN
%token <num> COMMATOKEN
%type <id> townName zipCode stateCode
%%
/* Entire block */
locationPart: townName COMMATOKEN stateCode zipCode EOLTOKEN
{ printf("Rule 12: LP: TN COMMA SC ZC EOL: %s\n", yytext); }
| /* bad location part */
{ printf("Rule 13: LP: Bad location part: %s\n", yytext); }
;
/* Lil tokens */
townName: NAMETOKEN
{ printf("Rule 23: TN: NAMETOKEN: %s\n", yytext); }
;
stateCode: NAMETOKEN
{ printf("Rule 24: SC: NAMETOKEN: %s\n", yytext); }
;
zipCode: INTTOKEN DASHTOKEN INTTOKEN
{ printf("Rule 25: ZC: INT DASH INT: %s\n", yytext); }
| INTTOKEN
{ printf("Rule 26: ZC: INT: %s\n", yytext); }
;
%%
int yyerror (char const *s){
extern int yylineno; //Defined in lex
fprintf(stderr, "ERROR: %s at symbol \"%s\"\n at line %d.\n", s, yytext,
yylineno);
exit(1);
}
[19:45]<Program4> $ cat addresses/zip.txt
Rockford, HI 12345
[19:45]<Program4> $ parser < addresses/zip.txt
Operating in parse mode.
Rule 23: TN: NAMETOKEN: Rockford
Rule 24: SC: NAMETOKEN: HI
Rule 26: ZC: INT:
Rule 12: LP: TN COMMA SC ZC EOL:
Parse successful!
[19:46]<Program4> $
正如您在底部附近看到的那样,它打印 Rule 26: ZC: INT:
但无法打印 5 位邮政编码。这就像程序只是跳过数字并存储换行符一样。知道为什么它不存储和打印邮政编码吗?
备注:
- yytext 在我的 .h 文件中定义为 extern(此处未发布);
- 我正在使用
-vdy
标志来编译 parse.c 文件
如果您想跟踪解析器的工作情况,最好启用 bison 的跟踪功能。这真的很容易。只需将 -t
或 --debug
标志添加到 bison
命令以生成代码,然后添加一行以实际生成跟踪:
/* This assumes you have #included the parse.tab.h header */
int main(void) {
#if YYDEBUG
yydebug = 1;
#endif
这在the Bison manual中有解释;如果您离开 -t
标志,#if
允许您的程序编译。关于标志,我强烈建议您不要使用 -y
标志;它用于编译依赖于某些过时功能的旧 Yacc 程序。如果您不使用 -y
,那么 bison 将使用您的 .y
文件的基本名称,生成的文件扩展名为 .tab.c
和 .tab.h
。
现在,您的 bison 文件说您的一些标记具有语义类型,但是您的 flex 操作没有为这些标记设置语义值,并且您的 bison 操作不使用语义值。相反,您只需打印 yytext
的值。如果您稍微考虑一下,您应该能够明白为什么它不起作用。 Bison 是一个 lookahead 解析器;它根据当前解析状态和下一个标记(如有必要)的窥视做出解析决策。它通过调用词法分析器来查看下一个标记。当您调用词法分析器时,它会更改 yytext
.
Bison(与其他 yacc 实现不同)并不总是查看下一个标记。但是在您的邮政编码规则中,它别无选择,因为如果不查看它就无法判断下一个标记是否为 -
。在这种情况下,它不是破折号;这是一个换行符。所以当你在邮政编码操作中打印出来时,猜猜 yytext
包含什么。
如果您的分词器将文本保存在 id
语义值成员中(这就是它的用途),那么您的解析器将能够访问语义值作为 </code>,<code>
, ...
因为 yytext
是一个全局变量,它被覆盖了,你必须将它复制到你的 lex 脚本中。在纯解析器中,即使它不再是全局的,它仍然会被重用并作为参数传递,因此像您尝试的那样使用它的值是不正确的。
此外,不要在 bison 中使用它,而是使用 $n
,其中 n
是令牌在规则中的位置。您可能需要将 %union
指令更改为
%union {
int number;
char *name;
};
所以在 flex 文件中,如果你想捕获文本,请执行类似
的操作[A-Za-z]+ { yylval.name = strdup(yytext); return NAMETOKEN; }
记住,不要在 bison 中使用 yytext
,它是词法分析器使用的内部东西。
然后,由于您已经为邮政编码定义了类型
/* Entire block */
locationPart: townName COMMATOKEN stateCode zipCode EOLTOKEN {
printf("Rule 12: LP: TN COMMA SC ZC EOL: town:%s, stateCode:%d zip-code:%s\n", , , );
}
问题出在这里:
zipCode: INTTOKEN DASHTOKEN INTTOKEN { // case 25 }
| INTTOKEN { // case 26 }
;
解析器不知道要采用哪个规则--25 或 26--直到它解析下一个标记以查看它是否是 DASHTOKEN。到代码执行的时候,yytext已经被覆盖了。
处理此问题的最简单方法是生成一个采用 INTTOKEN 和 returns malloc() 内存中 yytext[] 中的内容的产生式。类似于:
zipCode: inttoken DASHTOKEN inttoken
{
printf("Rule 25: zip is %s-%s\n", , );
free();
free();
}
| inttoken
{
printf("Rule 26: zip is %s\n", );
free();
}
;
inttoken: INTTOKEN { $$ = strdup(yytext); }
;