【问题标题】:Why lex invokes yyerror while parsing comma separated values?为什么 lex 在解析逗号分隔值时调用 yyerror?
【发布时间】:2020-07-14 14:37:50
【问题描述】:

我正在准备一个 yacc/lex 测试程序。词法分析器旨在以特定格式 (YYYYMMDD HHMM) 读取整数 (long)、浮点数 (double) 和日期时间。

lexer.l

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

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

%}

%%

                                                                        /* SKIP BLANKS AND TABS */
[\t ]                                                                   { ; }

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

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

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

%%

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

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

/* READ YYYYMMDD HHMM DATE */
void read_date_YYYYMMDD_HHMM(void) {

        /*  DATETIME STRUCT TM */
        struct tm dt;
        char buffer[80];

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

        /* 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 DATETIME */
        strftime(buffer, 80, "%c %z %Z\n", &dt);
        printf("%s\n", buffer);

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

}

/* YYERROR */
void yyerror(const char* msg) {
        fprintf(stderr, "yyerror %s\n", msg);
        exit(1);
}

grammar.y

语法是为了解析这类行(DATETIME,FLOAT,FLOAT,INTEGER):

20191201 17000,1.102290,1.102470,0
%{

#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

%%

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

%%

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

        yyparse();

        return 0;

}

构建一切的ma​​kefile如下:

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

我运行 readfile 但在解析 DATETIME lex 后似乎调用 yyerror:

% ./readfile 
20191201 170003296,1.102290,1.102470,0
Mon Feb 17 22:20:00 2020 +0100 CET

yyerror syntax error

数字也一样:

% ./readfile
45.45
yyerror syntax error
% ./readfile
45
yyerror syntax error

但不适用于任意文本:

% ./readfile
abc
abc

为什么 lex 会调用 yyerror? lex 解析代码中缺少什么?

【问题讨论】:

  • Lex 永远不会调用 yyerror(除非您在操作中明确调用)。只有解析器调用yyerror(因此将定义放在.y 文件中更有意义)。
  • 我想当你编译grammar.c 时缺少原型警告被-std=c89 抑制了。那不应该是必要的;除了上古版本,flex 和 bison 还可以生成现代 C 代码,甚至在 FreeBSD 上也是如此。
  • 我不确定 FreeBSD yacc 是否真的是野牛。阅读手册页我认为不是,它似乎是 Berkeley Yacc,因为它描述了与 Bison 的一些潜在差异。根据手册,Lex 在 FreeBSD 中似乎是灵活的。
  • 事实上,FreeBSD 使用了 Berkeley YACC 的衍生版本,最初由 Robert Corbett 编写,目前由 Thomas Dickey 维护。但是它生成的代码应该使用 C99 编译器(甚至 C11 编译器)编译而不会出现警告,尽管它被设计为与 C89 兼容。
  • 使用 -std=c89 的唯一原因是我目前正在重读《UNIX 编程环境》和《C 编程语言》和《Yacc 和 Lex -O'Reilly-》

标签: c yacc lex c89


【解决方案1】:

据我所知,您的词法分析器永远不会返回 ',' 令牌。默认情况下,(f)lex 扫描仪将无法识别的字符打印到标准输出,例如,在您的测试中输入abc。但是,无法识别的逗号不会显示在您的输出中,因为在 yyerror() 中调用 exit() 之前未刷新 stdout 缓冲区。

无论如何,我们通常会在扫描仪规范中将后备规则作为最后一条规则:

.    { return yytext[0]; }

这保证了任何无法识别的字符都将作为带引号的单字符标记传递给解析器。如果解析器不期望该标记,它将立即引发语法错误。

【讨论】:

  • 我在阅读两行而不是一行时仍然遇到问题。由于这是一个不同的问题,我将在另一个问题中提出,以避免与原始问题混淆。
猜你喜欢
  • 2017-08-29
  • 1970-01-01
  • 2020-09-22
  • 2010-11-29
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-06-22
  • 1970-01-01
相关资源
最近更新 更多