【问题标题】:Parser with LEX and YACC带有 LEX 和 YACC 的解析器
【发布时间】:2016-02-24 12:51:54
【问题描述】:

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

当输入以下格式之一时,程序必须打印一条消息(有效时间格式 1:输入):下午 4 点、下午 7:38、23:42、3:16、凌晨 3:16, 否则会打印 “无效字符” 消息。

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 ", $1, $2);}
            |  hour colon minute   {printf ("Valid time format 2 : %s:%s\n",$1, $3);}
            |  hour colon minute ampm {printf ("Valid time format 3 : %s:%s%s\n",$1, $3, $4); }
            ;

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

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

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

%%
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

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

【问题讨论】:

    标签: c yacc lex


    【解决方案1】:

    错误信息

    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 ", $1, $2);
    

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

    【讨论】:

      【解决方案2】:

      正如@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);
      

      【讨论】:

        【解决方案3】:

        这个(f)flex 规则:

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

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

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

        "am"                 { return am;}
        

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

        现在,在你的野牛文件中,你有:

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

        由于digit 实际上代表一个整数,所以digit digit 的产生是不正确的。例如,它会识别输入 23 75(因为您的 flex 文件忽略空格),但它会将其转换为值 305 (10*23 + 75)。这似乎不太合适。同样,它假设语义值$$$1 的类型是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 ", $1, $2);}
        

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

        如何解决这个问题取决于您对作业的解释。当它说“打印一条消息(有效时间格式1:输入)”时,是否意味着应该打印文字输入字符串,还是可以打印字符串的解释?也就是说,给定实际输入

        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 的绝对先决条件。试图同时教授这三个方面让我感到教学上的怀疑。

        【讨论】:

        • 我将用 lex 文件的最终版本回答我的问题。是的,是的,尝试同时使用 lex 和 yacc 学习 C 并不是最好的学习过程。我正在阅读 Pragmatic Programmer,这是书中的一个练习。我只学到了帮助我实施解决需求的程序的东西。
        【解决方案4】:

        我已经解决了为 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 ($1 > 12 || $1 <= 0)  {printf ("Hour out of range\n");validFormat = 0;} else if(validFormat) {printf("Valid time format %d%s\n", $1, ampm_str); } validFormat = 1;}
                    |  hour colon minute        {if ($1 > 24 || $1 <= 0)  {printf ("Hour out of range\n");validFormat = 0;} else if(validFormat) {printf("Valid time format   %d:%d\n", $1, $3); } validFormat = 1;}
                    |  hour colon minute ampm   {if ($1 > 12 || $1 <= 0)  {printf ("Hour out of range\n");validFormat = 0;} else if(validFormat) {printf ("Valid time format   %d:%d%s\n", $1, $3, ampm_str); } validFormat = 1;}
                    ;
        
        
        hour        :   two_digits        { $$ = $1; }
                    |   digit             { $$ = $1; }
                    ;
        
        minute      :   two_digits          { $$ = $1; if ($$ > 59) {printf ( "minute out of range\n");validFormat = 0;}}
                    |   digit               { $$ = $1; if ($$ > 59) {printf ( "minute out of range\n");validFormat = 0;}}
                    ;
        
        two_digits  :  digit digit          {$$ = 0; $$ = $1 * 10 + $2; }
                    ;
        
        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;}
        

        【讨论】:

          【解决方案5】:

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

          【讨论】:

          • 下面的病房可以使用
          • Forward 解析 [a-zA-Z_][a-zA-Z0-9_\+]* { yylval.string = strdup(yytext);返回字; }
          • 您可以编辑您的帖子。无需在评论中添加内容。
          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2015-11-09
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多