【问题标题】:Yacc Grammar not reducing the last literal of the input correctlyYacc 语法没有正确减少输入的最后一个文字
【发布时间】:2016-05-14 21:07:22
【问题描述】:

这是为算术表达式生成 3 地址码的代码。

我面临的问题是我的语法能够正确读取输入,直到最后一个文字。但无法减少 '\n' 之前的最后一个文字

示例:x = 1 + 2 * 3 - 4
1,2,3 被正确读取。即使是减号。当它读取 4 时,它能够将语法减少 2 个步骤。然后报错。

我已经把打印语句寻求帮助。这是整个代码。阅读最后一个文字时出现问题:

x -> IDEN | NUM | ....  
f -> x  
t -> f (This doesn't happen)  

icg.y

%{
    #include<stdio.h>
    #include<stdlib.h>
    int temp_reg_num = 0;
    void print_three_address_code(char* text1,char oper,char* text2)
    {
        printf("t%d = %s %c %s\n",temp_reg_num,text1,oper,text2);
    }   
%}

%token IDEN, NUM, FLOAT 
%union {
    char* text;
}

%left '+' '-'
%left '*' '/'

%%
s : IDEN '=' e '\n' {
    printf("start\n");
    if($3.text == NULL)
        printf("%s = t%d\n",$1.text,temp_reg_num-1);
    else
        printf("%s = %s\n",$1.text,$3.text);
    return 0;
}
;
e : e '+' t {
    printf("e -> e + t\n");
    char temp[5];
    print_three_address_code($1.text,'+',$3.text);
    sprintf(temp,"t%d",temp_reg_num++);
    $$.text = copy_string(temp);
}
  | e '-' t {
    printf("e -> e - t\n");
    char temp[5];
    print_three_address_code($1.text,'-',$3.text);
    sprintf(temp,"t%d",temp_reg_num++);
    $$.text = copy_string(temp);
}
  | t {
    printf("e -> t\n");
    $$.text = copy_string($1.text);
}
;
t : t '*' f {
    printf("t -> t * f\n");
    char temp[5];
    print_three_address_code($1.text,'*',$3.text);
    sprintf(temp,"t%d",temp_reg_num++);
    $$.text = copy_string(temp);
}
  | t '/' f {
    printf("t -> t / f\n");
    char temp[5];
    print_three_address_code($1.text,'/',$3.text);
    sprintf(temp,"t%d",temp_reg_num++);
    $$.text = copy_string(temp);
}
  | f {
    printf("t -> f\n");
    $$.text = copy_string($1.text);
}
;
f : f '^' x {
    printf("f -> f ^ x\n");
    char temp[5];
    print_three_address_code($1.text,'^',$3.text);
    sprintf(temp,"t%d",temp_reg_num++);
    $$.text = copy_string(temp);
}
  | x {
    printf("f -> x\n");
    $$.text = copy_string($1.text);
    printf("Why syntax error??");
}
;
x : '(' s ')' {}
      | NUM {
    printf("x -> NUM\n");
    $$.text = copy_string($1.text);
}
      | IDEN {
    printf("x -> IDEN\n");
    $$.text = copy_string($$.text,$1.text);
}
      | FLOAT {
    printf("x -> FLOAT\n");
    $$.text = copy_string($1.text);
}
      | '-' x {
    printf("x -> - x\n");
    $$.text = (char*)malloc(sizeof(char)*(strlen($2.text)+2));
    $$.text[0] = '-';
    strcat($$.text,$2.text);
}
;
%%

main()
{
    printf("Enter your expression : ");
    yyparse();
}
yyerror()
{
    printf("Syntax Error\n");
}
yywrap()
{
    return 1;
}

icg.l

%{
    #include<stdio.h>
    #include<stdlib.h>
    #include"y.tab.h"
    char* copy_string(char* fromstring)  
    {
        char* tostring = (char*)malloc(sizeof(char)*(strlen(fromstring)+1));  
        strcpy(tostring,fromstring);  
        return tostring;  
    }
%}

iden [a-zA-Z_][a-zA-Z_]*  
%%  
[0-9]+ {  
    yylval.text = copy_string(yytext);  
    return NUM;  
}
[0-9]+[.][0-9]+ {  
    yylval.text = copy_string(yytext);  
    return FLOAT;  
}
{iden} {  
    yylval.text = copy_string(yytext);  
    return IDEN;  
}  
[+] {  
    return '+';  
}  
[-] {  
    return '-';  
}  
[*] {  
    return '*';  
}  
[/] {  
    return '/';  
}  
['^'] {  
    return '^';  
}  
[=] {  
    return '=';  
}  
'\n' {  
    return '\n';  
}  
. {}  
%%

【问题讨论】:

    标签: grammar yacc lex


    【解决方案1】:

    由于这条规则,您会遇到语法错误:

    '\n' { return '\n'; }
    

    Flex 不认为 ' 有任何特殊性,因此该规则将(仅)匹配三个字符序列 'ENTER'。这不会出现在您的输入中,因此需要 \n 的规则将不会成功,并且当您提供文件结尾(或文件结束,如果您正在从文件中读取)时,那么您会出现语法错误。

    所以你应该写的是:

    \n { return '\n'; }
    

    还有,

    ['^'] 
    

    不仅匹配^。它还匹配',因为如上所述,' 没有任何特殊之处,因此字符类由三个字符组成,其中一个字符重复。要完全匹配字符 ^,请使用双引号(这是特殊的):

    "^"
    

    (但是,"\n" 不起作用,除非您想要匹配的正是 \ 后跟 n。您想要的只是 \n。)

    您实际上可以将所有这些单字符规则简化为一条规则:

    [-+*/^()=\n]   { return yytext[0]; }
    

    我包含了(),尽管它们不在您的 icg.l 文件中,因为您在语法中使用了它们。

    但是,您使用它们的规则不正确:

    x : '(' s ')' {}
    

    因为s 是一个赋值。这将允许

    x = 1 + (y = 2 * 3) - 4
    

    但不允许越正常

    x = 1 + (2 * 3) - 4
    

    你想要的是:

    x : '(' e ')' { $$ = $2; }
    

    您的语法中还有其他各种错误,包括:

    • 缺少#include &lt;string.h&gt; 标头(用于strlen
    • 缺少yylexyyerrorcopy_string 的声明
    • mainyyerror 的原型不正确(或更准确地说,是过时的)。 “现代 C”——就像过去 30 年左右一样——更喜欢显式返回类型,并且因为 C99 (1999) 需要它们。
    • copy_string 的实例有两个参数而不是一个,这是未定义的行为。 (如果您提供了copy_string 的声明,它将被标记为错误。)

    Berkeley yacc (byacc) 毫无投诉地处理了您的 icg.y 文件,但即使在 yacc 兼容模式下,bison 也不会处理它。问题是您使用$1.text 以及您未能声明终端和非终端的标签。如果您声明 union 类型,您应该为您的终端提供标签声明:

    %token <text> NUM IDEN FLOAT
    

    和你的非终结符(或至少是具有语义值的非终结符):

    %type <text> s e t f x
    

    然后您可以从您的操作中删除所有.text 选择器,因为bison(甚至byacc)会知道要使用哪个选择器。声明标记标记使您的解析器类型安全,或者至少减少类型不安全,并且通常也更具可读性。

    最后,您需要进行内存管理。你从来没有在free() 分配过任何在string_copy 中分配的字符串,所以你逐渐积累了相当多的泄漏内存。此外,您在许多情况下复制不必要的;例如,在单位规则中:

    x : NUM { $$.text = copy_string($1.text); }
    

    复制是完全没有必要的,因为$1 即将从解析器堆栈中弹出,它引用的字符串将永远不会被再次使用。所以你只是不必要地泄露了一份副本。会干净得多

    x : NUM { $$ = $1; }
    

    但这实际上是不必要的,因为这是默认操作。

    更改单位产品不会阻止其他内存泄漏;在实际使用语义值而不是通过它们的作品中,如果您不再需要它们,您仍然需要手动 free 复制的字符串(这似乎是所有语义的情况行动)。或者,如果您以后需要它们,您需要弄清楚如何在以后释放它们。

    您可以使用 yacc 或 bison 的内置跟踪功能,而不是在您的程序中塞满 printfs,您以后必须将其删除。运行yacc 时只需提供-t 选项,或添加

    #define YYDEBUG 1
    

    到 icg.y 文件中的 `%{...%}' 块。然后修改你的main函数:

    int main() {
      yydebug = 1;
      return yyparse();
    }
    

    这将准确地告诉您在解析您的输入时发生了什么,包括从您的扫描仪接收到哪些终端。


    这是一个完整的示例,展示了一种内存管理方法。它基于您的代码,但进行了一定程度的重构。请注意,解析器 从不 复制字符串,但 emit_tac 确实分配了一个新字符串,并释放了它作为参数传递的字符串。 (一些观察者可能认为这很粗鲁。)现代的flex 生成的扫描器需要在main 中调用yylex_destroy(),以释放词法分析器分配的资源。 bison 特有的%destructor 可防止出现语法错误时的内存泄漏:如果您不使用 bison,则必须为该问题找到不同的解决方案。

    icg.y

    %{
        #include <stdio.h>
        #include <stdlib.h>
        #include <string.h>
        int yylex();
        int yylex_destroy();
        void yyerror(const char* msg);
    
        int temp_reg_num = 0;
        char* emit_tac(char* text1, char oper, char* text2) {
            char* rv = malloc(8);
            snprintf(rv, 7, "t%d", temp_reg_num++);
            printf("%s = %s %c %s\n", rv, text1 ? text1 : "", oper, text2);
            free(text1); free(text2);
            return rv;
        }
    %}
    
    %union {
        char* text;
    }
    %token <text> IDEN NUMBER
    %type <text> e t f x
    %destructor { free($$); } <text>
    
    %%
    s : IDEN '=' e '\n' { printf("%s = %s\n", $1, $3);
                          free($1); free($3);
                        }
    e : e '+' t         { $$ = emit_tac($1,'+',$3); }
      | e '-' t         { $$ = emit_tac($1,'-',$3); }
      | t
    t : t '*' f         { $$ = emit_tac($1,'*',$3); }
      | t '/' f         { $$ = emit_tac($1,'/',$3); }
      | f
    f : f '^' x         { $$ = emit_tac($1,'^',$3); }
      | x 
    x : IDEN 
      | NUMBER
      | '-' x           { $$ = emit_tac(NULL, '-', $2); }
      | '(' e ')'       { $$ = $2; }
    %%
    
    int main() {
        int rc = yyparse();
        yylex_destroy();
        return rc;
    }
    
    void yyerror(const char* msg) {
        printf("%s\n", msg);
    }
    

    icg.l(需要弹性)

    %{
        /* Change to y.tab.h if you use yacc */
        #include "icg.tab.h"
        char* copy_yytext() {
            char* tostring = malloc(yyleng + 1);
            memcpy(tostring, yytext, yyleng);
            tostring[yyleng] = 0;
            return tostring;  
        }
    %}
    %option noinput nounput noyywrap nodefault
    
    %%
    
    \n                           { return '\n'; }
    [[:space:]]                  /* Ignore */
    [[:digit:]]+(\.[[:digit]]*)? { yylval.text = copy_yytext(); return NUMBER;  }
    [[:alpha:]_][[:alnum:]_]*    { yylval.text = copy_yytext(); return IDEN; } 
    .                            { return yytext[0]; }  
    

    编译

    $ bison -d icg.y
    $ flex icg.l
    $ gcc -Wall -o icg lex.yy.c icg.tab.c
    

    使用 valgrind 进行测试以证明不存在内存泄漏

    $ valgrind --leak-check=full ./icg <<< ' x = 1 + 2 - 3 / 4 ^ 5 * ( 6 - 9 )'
    ==26225== Memcheck, a memory error detector
    ==26225== Copyright (C) 2002-2013, and GNU GPL'd, by Julian Seward et al.
    ==26225== Using Valgrind-3.10.1 and LibVEX; rerun with -h for copyright info
    ==26225== Command: ./icg
    ==26225== 
    t0 = 1 + 2
    t1 = 4 ^ 5
    t2 = 3 / t1
    t3 = 6 - 9
    t4 = t2 * t3
    t5 = t0 - t4
    x = t5
    ==26225== 
    ==26225== HEAP SUMMARY:
    ==26225==     in use at exit: 0 bytes in 0 blocks
    ==26225==   total heap usage: 17 allocs, 17 frees, 16,530 bytes allocated
    ==26225== 
    ==26225== All heap blocks were freed -- no leaks are possible
    ==26225== 
    ==26225== For counts of detected and suppressed errors, rerun with: -v
    ==26225== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
    

    【讨论】:

    • 很好的答案!即使在原始问题的上下文之外,阅读也很有趣!
    • 非常感谢。我还没有测试过我放括号的情况。感谢我的单引号错误。关于您泄漏字符串的副本。我首先按照您建议的方式进行操作。只是将指针分配给字符串,但随后会引发很多分段错误。当我现在分配内存时,它可以安全运行。但是 语法的thanx。我不知道这个声明。
    • 也感谢 yydebug 评论。再次这将非常有用。我也不知道这一点。关于 strlen 包含在 中,而我的 icg.l 文件中提供了 copy_string 方法。所以我不需要在我的 icg.y 文件中明确声明。即使这样做,它也会引发错误。
    • 是的。非常感谢。你关于换行的建议奏效了。我很感激你花时间指出我所有的错误并告诉我更好的方法。脱帽先生。
    • @ishaan:例如,有时您必须复制您需要复制 yytext,而您显然不能只使用本地临时地址。 (尽管您使用本地临时文件毫无意义;您可以 malloc 空间然后填充它,而不需要副本。)您需要仔细考虑每个上下文。顺便说一句,strdup 存在于大多数系统上。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-04-04
    • 2021-06-07
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多