【问题标题】:Yacc parse won't finish reduction of a productionYacc 解析不会完成生产的减少
【发布时间】:2021-05-14 08:17:19
【问题描述】:

我正在通过 lax 和 yacc 构建一个小型解释器,该解释器适用于执行加法和乘法以及打印整数列表的基本编程语言。

例如指令:

Print(2,3,4);

应该输出:2 3 4

和指令:

Print(+(2,3));

应该输出:5

第一条打印指令运行良好。然而,任何加法指令(+ 后跟一个列表)都会自行运行,并且该操作会返回正确的答案(通过 printf 找到)但似乎 yacc 在执行加法指令之后发生的外部打印指令之前停止。

这是我的 .l 文件:

%{
#include "y.tab.h"
%}

digit [0-9]

%%
{digit}{digit}*     {yylval.str = strdup(yytext); return IntLit;}
Print               {return Print;}
\+                  {yylval.str = strdup(yytext); return '+';}
\*                  {yylval.str = strdup(yytext); return '*';}
\(                  {return '(';}
\)                  {return ')';}
\,                  {return ',';}
\;                  {return ';';}
\t                  {}
\r                  {}
\n                  {}

%%

    
int yywrap () {
    return 1;
}

这是我的 .y 文件:

%{
#include <stdio.h>
#include <stdlib.h>
#include <string.h>


extern int yylex();
extern int yyparse();
extern int yyerror(char *s);
extern char *yytext;

void doPrint(char *s);
int evaluate(char *c, char *s);
char* append(char *s, char *s2);
char* makeSingle(char *s);

%}

%union {
  char character;
  char *str;
}

%type <str> Item
%type <str> IntLit
%type <str> List
%type <str> Func

%token Print
%token IntLit

%%

Prog        :   StmtSeq                 { };
StmtSeq     :   Stmt StmtSeq            { };
StmtSeq     :                           { };
Stmt        : Print '(' List ')' ';'    { doPrint($3); };   
List        : List ',' Item             { $$ = append($1, $3); };
List        : Item                      { $$ = makeSingle($1); };
Item        : Func '(' List ')'         { $$ = evaluate($1, $3); };
Item        : IntLit                    { $$ = $1;};
Func        : '+'                       {$$ = yylval.str; };
Func        : '*'                       {$$ = yylval.str; };

%%

int main(int argc, char *argv[]){
    //yydebug = 1;
    return yyparse();
}

void doPrint(char *s){
    char * token = strtok(s, ",");
    while(token != NULL){
        printf("%s", token);
        printf(" ");
        token = strtok(NULL, ",");
    }
    printf("\n");
}

int evaluate(char *c, char *s){
    char * result;
    int res;
    int x;
    char * token;
    int cmp = strcmp(c, "+");
    if(cmp == 0){
        token = strtok(s, ",");
        res = 0;
        while(token != NULL){
            x = atoi(token);
            res = res + x;
            token = strtok(NULL, ",");
        }
        sprintf(result,"%d",res);
    } else {
        token = strtok(s, ",");
        res = 1;
        while(token != NULL){
            x = atoi(token);
            res = res * x;
            token = strtok(NULL, ",");
        }
        sprintf(result,"%d",res);
    }
    printf("resultstring is: '%s'\n", result);
    return result;
}

char* append(char *s, char *s2){
    char * result = s;
    strcat(result, ",");
    strcat(result, s2);
    return result;
}

char* makeSingle(char *s){
    //char * result;
    //sprintf(result, "%d", c);
    //return result;
    return s;
}

extern int yyerror(char *s)  {
  printf(s);
  return 1;
}

如果我输入指令:

Print(2,3,4);
Print(+(2,3));

第一条打印指令按预期工作,但第二条指令在打印加法结果之前停止,但在评估完成之后。

我是 yacc/lex 的新手,我不确定为什么 yacc 在不打印加法结果的情况下停止。添加的结果不应该是可以重写为“列表”然后正确打印的“项目”吗?任何帮助或建议将不胜感激,谢谢!

编辑 在查看yydebug = 1; 时的输出后,我发现解析在完全减少之前突然结束(我相信)。调试过程的最后一部分内容如下:

Reducing stack by rule 7 (line 40):
    $1 = nterm Func ()
    $2 = token '(' ()
    $3 = nterm List ()
    $4 = token ')' ()

在此缩减中没有$$,因为在之前完成的所有其他缩减中都存在

【问题讨论】:

    标签: c yacc lex


    【解决方案1】:

    当你这样做时你看到了什么:doPrint(1);
    您在 doPrint 中有一行:char * token = strtok(s, ",");
    当您通过评估函数时,您会使用所有逗号,只留下一个数字。调用 doPrint 时,字符串中没有逗号,因此返回 NULL。
    如果它返回 NULL(但字符串有内容),您可能会有一秒钟的时间,或者您可以重新处理解析字符串的方式。

    顺便说一句,strdup() 返回一个你没有释放的 malloced 字符串。
    您还使用从 strdup 接收的字符串调用 strcat(),您正在写入内存并且您不知道大小。 strcat() 会让你溢出缓冲区。
    更好的方法是,当您将一个字符串附加到另一个字符串并且您不知道它们的大小时,请执行以下操作:

    char *mergestrings(char *str1,char *str2) {
      char *newstr=(char*)malloc(sizeof(char)*(strlen(str1)+strlen(str2)+1));
      strcpy(newstr,str1);
      strcat(newstr,str2);
      free(str1);
      free(str2);
      return newstr;
    }
    

    或使用 realloc 或其他东西,但你应该注意你对内存的处理。

    最大的问题是评估函数的定义和指针的使用。
    扫描仪.l:

    %{
    #include "../obj/y.tab.h"
    %}
    
    digit 0|(([1-9])[0-9]*)
    operator [+*]
    
    %%
    {digit}             {yylval.str = strdup(yytext); return IntLit;}
    {operator}          {yylval.character=yytext[0]; return Operator;}
    "Print"             {return Print;}
    \(                  {return LParen;}
    \)                  {return RParen;}
    \,                  {return Comma;}
    \;                  {return SemiCln;}
    \t                  {}
    \r                  {}
    \n                  {}
    
    %%
    

    parser.y:

    %{
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    
    
    extern int yylex();
    extern int yyparse();
    extern int yyerror(char *s);
    extern char *yytext;
    
    void doPrint(char *s);
    char* evaluate(char c, char *s);
    char* append(char *s, char *s2);
    
    %}
    
    %union {
      char character;
      char *str;
    }
    
    %type <str> Item List
    %type <character> Func
    
    %token Print Operator IntLit LParen RParen SemiCln Comma
    
    %%
    
    Prog       : Stmt                                { }
               | Prog Stmt                           { }
               ;
    Stmt       : Print LParen List RParen SemiCln    { doPrint($3); }
               ;
    List       : List Comma Item                     { $$ = append($1, $3); }
               | Item                                { $$ = $1; }
               ;
    Item       : Func LParen List RParen             { $$ = evaluate($1, $3); }
               | IntLit                              { $$ = yylval.str;}
               ;
    Func       : Operator                            { $$ = yylval.character; }
               ;
    
    %%
    
    int main(int argc, char *argv[]){
        return yyparse();
    }
    
    void doPrint(char *s){
        char *stop = strchr(s, ','), *start=s;
        while(stop){
            *stop='\0';
            printf("%s ", start);
            start = stop+1;
            stop = strchr(start, ',');
        }
        printf("%s\n",start);
        free(s);
    }
    
    char *evaluate(char c, char *s){
        int res=0;
        if(c == '+'){
            char *stop = strchr(s, ','), *start=s;
            while(stop){
                *stop='\0';
                res += atoi(start);
                start = stop+1;
                stop = strchr(start, ',');
            }
            res += atoi(start);
        } else {
            char *stop = strchr(s, ','), *start=s;
            res = 1;
            while(stop){
                *stop = '\0';
                res *= atoi(start);
                start = stop+1;
                stop = strchr(start, ',');
            }
            res *= atoi(start);
        }
        free(s);
        char *result = (char*)malloc(12); // big enough for a 32 bit integer
        sprintf(result,"%d",res);
        return result;
    }
    
    char* append(char *s, char *s2){
        char *result = (char*)realloc(s,strlen(s)+strlen(s2)+2);
        if (result) {
          strcat(result, ",");
          strcat(result, s2);
        }
        else {
            result=(char*)malloc(strlen(s)+strlen(s2)+2);
            sprintf(result,"%s,%s",s,s2);
            free(s);
        }
        free(s2);
        return result;
    }
    
    extern int yyerror(char *s)  {
      printf(s);
      return 1;
    }
    

    【讨论】:

    • 当我输入Print(1); 之类的内容时,输出正确打印为1
    • 我注意到虽然在评估返回后Stmt : Print '(' List ')' ';' { doPrint($3); }; 操作甚至没有触发。例如,如果我在doPrint($3); 之前添加printf("Print production fired, $3 is: %s\n", $3);,则操作的printf 部分永远不会执行,doPrint 也不会执行。在我看来,yacc 并没有识别出生产的右侧。
    • Item : IntLit { $$ = $1;}; 在 lex 文件中,您复制数字的字符串,将其放入并集,然后返回 IntLit 定义。在 yacc 中,您应该输入 Item : IntLit { $$ = yylval.str } 以获取数值
    • 我可能对你所说的有点困惑。你是说Item : IntLit { $$ = $1;}; 应该改为Item : IntLit { $$ = yylval.str; }; 吗?这样做不会改变输出。
    • 我搞定了。是的,在 lex 中您复制了字符串,您应该在语法中进行更改(对于 IntLit $$=yylval.str)。 evaluate() 函数定义为返回一个int,但它返回一个char 指针,函数定义和前向定义需要更改。 evaluate() 函数有一个 char *result,它是用 sprintf 分配的,但从不分配内存。你不能这样做,它是一个什么都没有的指针。更正评估签名和指针用法将为您的测试用例以及单个 Print(1) 修复它。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-11-22
    • 2015-12-17
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多