【问题标题】:Why this yacc+lex basic parser does not handle CONTROL+D / EOF?为什么这个 yacc+lex 基本解析器不处理 CONTROL+D / EOF?
【发布时间】:2020-07-14 21:01:42
【问题描述】:

我有一个 yacc/lex 程序来处理这种类型的行(在这个例子中它只处理一种格式,但想法是它显然会处理更多格式):

% cat test.csv 
20191201 170003296,1.102290,1.102470,0
20191201 170004413,1.102320,1.102470,0
20191201 170005270,1.102290,1.102470,0
20191201 170006063,1.102280,1.102460,0
20191201 170006629,1.102260,1.102440,0
20191201 170007523,1.102410,1.102470,0
20191201 170007573,1.102410,1.102530,0
20191201 170035268,1.102490,1.102530,0
20191201 170036505,1.102490,1.102540,0
20191201 170043219,1.102490,1.102530,0

词法分析器 (lexer.l):

%{

#include <time.h>
#include "grammar.h"

void read_float_number(void);
void read_integer_number(void);
void read_date_YYYYMMDD_HHMMSSmmm(void);
void yyerror(const char* msg);

%}

%%    
                                                                                                /* YYYYMMDD HHMMSSmmm DATE */
[12][09][0-9][0-9][0-1][0-9][0-3][0-9][ ][0-2][0-9][0-5][0-9][0-5][0-9][0-9][0-9][0-9]          { read_date_YYYYMMDD_HHMMSSmmm(); return DATETIME; }

                                                                                                /* FLOAT NUMBER */
[0-9]+\.[0-9]+                                                                                  { read_float_number(); return FLOAT_NUMBER; }

                                                                                                /* INTEGER NUMBER */
[0-9]+                                                                                          { read_integer_number(); return INTEGER_NUMBER; }

                                                                                                /* PASS ',' CHARACTER */
,                                                                                               { return ','; } 

                                                                                                /* PASS '\n' CHARACTER */
\n                                                                                              { return '\n'; } 

                                                                                                /* PASS UNEXPECTED CHARACTER */
.                                                                                               { return yytext[0]; }


%%

/* READ FLOAT NUMBER */
void read_float_number(void) {
        printf("void read_float_number(void)\n");
        printf("#%s#\n", yytext);
        sscanf(yytext, "%lf", &yylval.float_number);
        printf("%lf\n", yylval.float_number);
}

/* READ INTEGER NUMBER */
void read_integer_number(void) {
        printf("void read_integer_number(void)\n");
        printf("#%s#\n", yytext);
        sscanf(yytext, "%ld", &yylval.integer_number);
        printf("%ld\n", yylval.integer_number);
}

/* READ YYYYMMDD HHMMSSmmm DATE */
void read_date_YYYYMMDD_HHMMSSmmm(void) {

        printf("void read_date_YYYYMMDD_HHMMSSmmm(void)\n");
        printf("#%s#\n", yytext);

        /*  DATETIME STRUCT TM */
        struct tm dt;

        /* READ VALUES */
        sscanf(yytext, "%4d%2d%2d %2d%2d%2d", &dt.tm_year, &dt.tm_mon, &dt.tm_mday, &dt.tm_hour, &dt.tm_min, &dt.tm_sec);

        /* NORMALIZE VALUES */
        dt.tm_year = dt.tm_year - 1900;         /* NORMALIZE YEAR */
        dt.tm_mon = dt.tm_mon - 1;              /* NORMALIZE MONTH */
        dt.tm_isdst = -1;                       /* NO INFORMATION ABOUT DST */
        mktime(&dt);                            /* NORMALIZE STRUCT TM */

        /* PRINT DATE TIME */
        char buffer[80];
        strftime(buffer, 80, "%c %Z", &dt);
        printf("%s\n", buffer);

        /* COPY STRUCT TM TO YACC RETURN VALUE */
        memcpy(&yylval.datetime, &dt, sizeof(dt));


}

yacc 语法(grammar.y):

%{

#include <time.h>
#include <stdio.h>

%}

%union {

        struct tm       datetime;               /* DATE TIME VALUES */
        double          float_number;           /* 8 BYTES DOUBLE VALUE */
        long            integer_number;         /* 8 BYTES INTEGER VALUE */

}

%token  <datetime>              DATETIME
%token  <float_number>          FLOAT_NUMBER
%token  <integer_number>        INTEGER_NUMBER

%%

input:                          /* empty */
                        | input lastbid_lastask

lastbid_lastask:        DATETIME ',' FLOAT_NUMBER ',' FLOAT_NUMBER ',' INTEGER_NUMBER '\n'      { printf("MATCH %lf %lf %ld\n", $3, $5, $7); }
                        ;

%%

extern FILE *yyin;

int main(int argc, char *argv[]) {

        while(!feof(yyin)) {
                yyparse();
        }
        return 0;

}

ma​​kefile:

% cat makefile 
CCFLAGS = -std=c89 -c
YFLAGS = -d     # Forces generation of y.tab.h
OBJS = lexer.o grammar.o
TARGET = readfile

readfile:               $(OBJS)
                        cc $(OBJS) -std=c89 -ll -o $(TARGET)

grammar.h grammar.o:    grammar.y
                        yacc $(YFLAGS) -ogrammar.c grammar.y
                        cc $(CCFLAGS) grammar.c

lexer.o:                lexer.l grammar.h
                        lex -olexer.c lexer.l
                        cc $(CCFLAGS) lexer.c

clean:
                        rm -f $(OBJS) grammar.[ch] lexer.c

现在我编译程序并且没有错误,但是当我尝试执行它时,我得到了这个:

% cat test.csv | ./readfile
Segmentation fault (core dumped)

现在如果我替换:

while(!feof(yyin)) 

与:

while(1) 

然后我明白了:

% cat test.csv | ./readfile
void read_date_YYYYMMDD_HHMMSSmmm(void)
#20191201 170003296#
Sun Dec  1 17:00:03 2019 CET
void read_float_number(void)
#1.102290#
1.102290
void read_float_number(void)
#1.102470#
1.102470
void read_integer_number(void)
#0#
0
MATCH 1.102290 1.102470 0
void read_date_YYYYMMDD_HHMMSSmmm(void)
#20191201 170004413#
Sun Dec  1 17:00:04 2019 CET
void read_float_number(void)
#1.102320#
1.102320
void read_float_number(void)
#1.102470#
1.102470
void read_integer_number(void)
#0#
0
...

所以它可以工作,但程序不会以 EOF 结束。 虽然我知道核心转储可能意味着很多事情,但我可以做些什么来进一步定位问题并获得正常行为?

【问题讨论】:

  • 你为什么要循环调用yyparse()?我的理解是你只需调用一次,它会解析整个输入并在到达 EOF 时返回。
  • 删除循环句柄 CONTROL+D 很好。不是作为借口,只是作为一个信息丰富的旁注,循环和 yyin FILE 的用法包含在 O'Reilly 的“Lex and Yacc”一书中的第一个示例中

标签: c yacc lex c89


【解决方案1】:

不要循环调用yyparse()。它将解析整个输入并返回;当它返回时,您知道整个输入已被解析(或遇到语法错误)。应该不需要任何 EOF 测试。

(在个别情况下您需要打破此规则,其中大部分与扫描器返回输入结束指示符而不是在输入结束时有关,或解析器按顺序使用YYACCEPT/YYABORT过早终止解析。换句话说,如果你有需要打破这个规则的情况,你已经知道你必须这样做。)

while (!feof(file)) {…} 有一个完整的FAQ entry 解释为什么它几乎总是一个错误。 (总结:在读取检测到 EOF 之后 设置了 EOF 标志,因此在您进行读取之前没有设置 EOF 的事实证明什么。while(!feof(file)) 成语很好地保证了在结束时你会得到一个意外的 EOF 文件——在“但我刚刚检查了 EOF...”的意义上是意外的。)

不过,我认为常见问题解答并未涵盖这个特定问题,该问题特定于使用 (f)lex 的程序。当 (f)lex 扫描器到达文件末尾时,它会将 yyin 设置为 NULL。然后,如果yywrap 告诉它没有更多输入,yylex 返回 0,它告诉它的调用者 (yyparse) 已经到达文件末尾。然后yyparse 完成解析并返回。如果然后循环,yyin 为 NULL,feof(NULL) 为未定义行为。这就是您的程序出现段错误的原因。

当您删除feof 测试(但仍循环)时,您重新输入yyparse,但这次将yyin 设置为NULL。 flex 扫描器将其理解为“使用默认输入”,即stdin。如果yyin 以前是某个输入文件,这意味着yyparse 的新调用将尝试从终端获取其输入,这可能不是您所期望的。另一方面,如果到达 EOF 的是 stdin,那么您将处于一个硬循环中,不断接收来自 stdin 的新 EOF 信号。

【讨论】:

  • 出于某种原因(可能是特定情况),O'Reilly 的 Lex 和 Yacc 书中包含使用 while(!feof(yyin))。删除它并添加您在上一篇文章中指出的语法规则使该程序按预期运行(处理多行并处理 CONTROL+D/EOF)
  • @M.E.:那本书也有其他错误。但它仍然是一个非常有用的资源。
猜你喜欢
  • 2013-04-30
  • 1970-01-01
  • 2016-02-24
  • 2015-12-17
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-11-09
  • 1970-01-01
相关资源
最近更新 更多