使用LEX和YACC进行解析

时间:2015-11-22 10:19:12

标签: c yacc lex

我试图用LEX& amp;来实现时间解析器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

有人能帮助我吗? 提前谢谢。

5 个答案:

答案 0 :(得分:1)

这(f)lex规则:

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

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

同时,这(f)lex规则:

yylval

识别令牌"am" { return am;} ,但未设置am的值。

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

yylval

由于hour : digit digit { $$ = $1 * 10 + $2; } | digit { $$ = $1;} ; 实际上代表整个整数,因此digit生成不正确。例如,它会识别输入digit digit(因为您的flex文件忽略空格),但它会将其转换为值305(10 * 23 + 75)。这似乎不合适。同样,它假设语义值23 75$$的类型是$1,这是默认情况。

但是,制作:

int

要求结果语义值的类型为ampm : am {$$ = "am";} | pm {$$ = "pm";} ; (或甚至char *)。由于您没有做任何声明语义值类型的操作,因此它们的类型为const char*,并且赋值与C语句一样无效:

int

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

此外,在您的制作中:

int ampm = "am";

您认为语义值time : hour ampm {printf ("Valid time format 1 : %s%s\n ", $1, $2);} $1是字符串($2)。 BUt值实际上是整数,因此char*将执行一些未定义的事情,可能是灾难性的(在本例中为segfault)。 (由于C的性质,这不是编译时错误,但大多数C编译器会发出警告。显然,你的C编译器会这样做。)

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

printf

您希望消息是

吗?
8:23am
08:23am

或者是否适合规范化:

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

您应该(重新)阅读bison manual on semantic types中的部分,然后决定您是希望类型为Valid time format 1: 8:23am Valid time format 1: 8:23am int还是两者的并集。< / p>

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

  1. 您的flex文件识别任何整数,但小时和分钟都不能是任意整数。两者都限于两位数;通常,会议记录应始终为两位数(因此char* 是一种写作方式9:3am)。它们都具有有限的有效值范围;分钟必须介于9:03am00之间,如果指定am或pm,则小时介于1到12之间,否则介于0和23之间。或者可能是24.(实际上,有很多不同的可能数小时的有效性约定;您可以选择灵活或严格。)

  2. 您的问题描述似乎不允许时间规范中的空格,但您的flex文件会忽略空格。这可能会导致您识别不正确的输入(同样取决于您希望的严格程度)。在这种情况下,请参阅有关输出的说明:输出中是否出现空白(假设可以接受)?

  3. 当您的flex文件看到无法识别的字符时会发出错误消息,但它不会停止。实际上,这意味着将从输入流中删除非法字符,以便输入如下:

    59

    将导致两条非法字符消息,后跟一条消息,指出输入是有效的1;:17rpm 。这不太可能是你想要的。

  4. 作为最后一点,我必须说,在我看来,理解C是使用flex和bison的绝对先决条件。试图同时教三个人都是教学上的嫌疑人。

答案 1 :(得分:0)

错误消息

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 :(得分:0)

正如@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 :(得分:0)

我已经解决了为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;}

答案 4 :(得分:0)

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