使用 LEX 和 YACC 的解析器

Parser with LEX and YACC

我正在尝试使用 LEX 和 YACC 实现时间解析器。 我是那些工具和 C 编程的新手。

当输入以下格式之一时,程序必须打印一条消息(有效时间格式 1:输入):4pm,7:38pm , 23:42, 3:16, 3:16am, 否则打印 "Invalid character" 消息。

lex 文件 time.l :

%{
#include <stdio.h>
#include "y.tab.h"
%}

%%

[0-9]+                {yylval=atoi(yytext); return digit;}
"am"                   { return am;}
"pm"                   { return pm;}
[ \t\n]               ;
[:]                    { return colon;}
.                     { printf ("Invalid character\n");}

%%

yacc 文件time.y:

%{
void yyerror (char *s);
int yylex();
#include <stdio.h>
#include <string.h>

%}

%start time
%token digit
%token am
%token pm
%token colon

%%

time        :  hour ampm           {printf ("Valid time format 1 : %s%s\n ", , );}
            |  hour colon minute   {printf ("Valid time format 2 : %s:%s\n",, );}
            |  hour colon minute ampm {printf ("Valid time format 3 : %s:%s%s\n",, , ); }
            ;

ampm        :   am               {$$ = "am";}
            |   pm               {$$ = "pm";}
            ;

hour        :   digit digit             {$$ =  * 10 + ;}
            |   digit             { $$ = ;}
            ;

minute      :   digit digit         {$$ =   * 10 + ;} 
            ;

%%
int yywrap()
{
        return 1;
} 

int main (void) {

  return yyparse();
}

void yyerror (char *s) {fprintf (stderr, "%s\n", s);}

使用此命令编译:

yacc -d time.y && lex time.l && cc lex.yy.c y.tab.c -o time

我收到一些警告:

time.y:17:47: warning: format specifies type 'char *' but the argument has type
      'YYSTYPE' (aka 'int') [-Wformat]
    {printf ("Valid time format 1 : %s%s\n ", (yyvsp[(1) - (2)]), (yyvsp.

printf 语句中的所有变量都会出现此警告。 值都是char,因为连时间字符串中的数字都是用atoi函数转换的

使用有效输入执行程序会引发此错误:

./time

1pm

[1]    2141 segmentation fault  ./time

有人可以帮助我吗? 提前致谢。

错误信息

time.y:17:47: warning: format specifies type 'char *' but the argument has type
  'YYSTYPE' (aka 'int') [-Wformat]

例如那一行

printf ("Valid time format 1 : %s%s\n ", , );

说你指定了一个 %s (这是一个 char * 类型的 C 风格字符串)但实际上参数是 YYSTYPE 类型(这似乎是一个整数类型)。

正如@Elyasin 所指出的,您收到的错误消息告诉您到底出了什么问题- YYSTYPE 默认为 int 但您正试图将其用作字符串(这在您得到的每一行上错误)。此外,您试图在某些地方将其用作 int 而在其他地方用作字符串,这显然是不正确的。

您可以做的是创建一个字符串来保存您输入的内容并连接到该字符串中。您可以使用初始 yacc 块中的变量来执行此操作,因此类似这样:

%{
void yyerror (char *s);
int yylex();
#include <stdio.h>
#include <string.h>

char time_str[15];
%}

time_str 现在在整个解析器步骤中都可用,因此您可以将其复制到其中,然后在最后一步中您可以打印出构建的字符串,例如

printf ("Valid time format 1 : %s", timestr);

这个 (f)lex 规则:

[0-9]+                {yylval=atoi(yytext); return digit;}

识别任何整数,而不仅仅是数字。 (它允许前导零,这可能适用于日期解析器。)它假定 yylval 是一个 int,如果您不做任何事情来声明 [= 的类型,就会出现这种情况20=].

同时,这个 (f)lex 规则:

"am"                 { return am;}

识别令牌am,但不设置yylval的值。

现在,在您的 bison 文件中,您有:

hour        :   digit digit       { $$ =  * 10 + ; }
            |   digit             { $$ = ;}
            ;

由于 digit 实际上表示一个完整的整数,因此 digit digit 产生式是不正确的。例如,它会识别输入 23 75(因为您的 flex 文件会忽略空格),但会将其转换为值 305 (10*23 + 75)。这似乎不太合适。同样,它假定语义值 $$</code> 的类型是 <code>int,这是默认情况。

然而,生产:

ampm        :   am               {$$ = "am";}
            |   pm               {$$ = "pm";}
            ;

要求结果语义值的类型为char *(甚至const char*)。由于您没有做任何事情来声明语义值的类型,因此它们的类型是 int 并且赋值与 C 语句一样无效:

int ampm = "am";

因此 C 编译器发出错误消息。

此外,在您的制作中:

time        :  hour ampm           {printf ("Valid time format 1 : %s%s\n ", , );}

您假设语义值 </code> 和 <code> 是字符串 (char*)。但是这些值实际上是整数,所以 printf 会做一些未定义的事情并且可能是灾难性的(在这种情况下,段错误)。 (由于 C 的性质,这不是编译时错误,但大多数 C 编译器会发出警告。显然,您的 C 编译器会发出警告。)

如何解决这个问题取决于您对作业的理解。当它说 "print a message (Valid time format 1: input )" 时,它是否意味着应该打印文字输入字符串,或者是否可以打印字符串的解释?也就是说,给定实际输入

8:23am
08:23am

您希望消息是

Valid time format 1: 8:23am
Valid time format 1: 08:23am

或者归一化是否合适:

Valid time format 1: 8:23am
Valid time format 1: 8:23am

您应该(重新)阅读 bison manual on semantic types 中的部分,然后决定您想要的类型是 intchar* 还是两者的联合.

您需要考虑的其他一些事情:

  1. 您的 flex 文件可以识别任何整数,但小时和分钟都不能是任意整数。两者都限于两位数;通常,分钟应该总是两位数(因此 9:3am 而不是 的一种写法 9:03am)。它们都具有有限的有效值范围;分钟必须在 0059 之间,而如果指定了上午或下午,则小时必须在 1 到 12 之间,否则在 0 到 23 之间。或者可能是 24。(实际上,有很多不同的可能小时的有效性约定;您可以选择灵活或严格。)

  2. 您的问题描述似乎不允许在时间规范中使用空格,但您的 flex 文件忽略了空格。因此,这可能会导致您识别出不正确的输入(再次取决于您希望的严格程度)。另请参阅本例中有关输出的说明:输出中是否出现空格(假设可接受)?

  3. 您的 flex 文件在看到无法识别的字符时会发出错误消息,但不会停止词法分析。实际上,这意味着非法字符将从输入流中删除,因此像这样的输入:

    1;:17rpm
    

    将导致两个非法字符消息,然后是一条消息,说明输入是有效的 1:17pm。这不太可能是你想要的。

最后一点,我不得不说,在我看来,理解 C 是使用 flex 和 bison 的绝对先决条件。试图同时教授这三者让我觉得在教学上很可疑。

我已经解决了为 am 和 pm 值定义 char 数组的警告,并将 YYSTYPE 变量视为 int(按照建议)。

我还添加了空行的大小写、每次输入后的逗号分隔、小时和分钟的验证、退出命令:

%{
void yyerror (char *s);
int yylex();
#include <stdio.h>
#include <string.h>
#include <stdlib.h>

char ampm_str[15] = "";

typedef int bool;
bool validFormat = 1;
%}

%start input
%token digit
%token am
%token pm
%token colon
%token sep
%token exit_command

%%
input       : /* empty */
            | input line 
            ;

line        : '\n'
            | list '\n' 
            ;

list        : time
            | time sep list 
            | exit_command  {exit(EXIT_SUCCESS);}
            ;


time        :  hour ampm                {if ( > 12 ||  <= 0)  {printf ("Hour out of range\n");validFormat = 0;} else if(validFormat) {printf("Valid time format %d%s\n", , ampm_str); } validFormat = 1;}
            |  hour colon minute        {if ( > 24 ||  <= 0)  {printf ("Hour out of range\n");validFormat = 0;} else if(validFormat) {printf("Valid time format   %d:%d\n", , ); } validFormat = 1;}
            |  hour colon minute ampm   {if ( > 12 ||  <= 0)  {printf ("Hour out of range\n");validFormat = 0;} else if(validFormat) {printf ("Valid time format   %d:%d%s\n", , , ampm_str); } validFormat = 1;}
            ;


hour        :   two_digits        { $$ = ; }
            |   digit             { $$ = ; }
            ;

minute      :   two_digits          { $$ = ; if ($$ > 59) {printf ( "minute out of range\n");validFormat = 0;}}
            |   digit               { $$ = ; if ($$ > 59) {printf ( "minute out of range\n");validFormat = 0;}}
            ;

two_digits  :  digit digit          {$$ = 0; $$ =  * 10 + ; }
            ;

ampm        :   am               {strcpy(ampm_str, "am");}
            |   pm               {strcpy(ampm_str, "pm");}
            ;


%%
int yywrap()
{
        return 1;
} 

int main (void) {
printf ("Insert time, and press enter\n");
printf ("Type , after each time\n");
printf ("Valid formats : 2am, 12:00, 13:30pm\n");
printf ("exit to quit\n");

  return yyparse();
}


void yyerror (char *s) {fprintf (stderr, "Invalid character: %s\n", s); validFormat = 0;}

用于字节解析 在 lex 文件中 0x[0-9a-f]{8} { yylval.number = strtoll(yytext+2, NULL, 16); return BYTE_4; } 在 yacc 文件中 作为联合的一部分,您需要清除此号码。