Bison / Flex打印终端的价值来自替代品

时间:2015-06-09 21:11:32

标签: c bison flex-lexer

我写了一个简单的语法:

operations :
    /* empty */
    | operations operation ';'
    | operations operation_id ';'
    ;

operation :
    NUM operator NUM
    {
        printf("%d\n%d\n",$1, $3);
    }
    ;

operation_id :
    WORD operator WORD
    {
        printf("%s\n%s\n%s\n",$1, $3, $<string>2);
    }
    ;

operator :
    '+' | '-' | '*' | '/'
    {
        $<string>$ = strdup(yytext);
    }
    ;

如您所见,我已经定义了一个operator来识别4个符号中的一个。现在,我想在operation_id中打印此符号。问题是,operator中的逻辑仅适用于替代中的最后一个符号。 因此,如果我写 a / b; ,则打印 ab / ,这很酷。但对于其他操作,例如。 a + b; 打印 aba 。我做错了什么?

*我在示例输出中省略了新的行符号。

1 个答案:

答案 0 :(得分:4)

你的语法中的这个非终端是完全错误的。

operator :
    '+' | '-' | '*' | '/' { $<string>$ = strdup(yytext); }
    ;

首先,在yacc / bison中,每个产品都有一个动作。该规则有四个制作,其中只有最后一个有相关的动作。写这样更清楚:

operator : '+' 
         | '-'
         | '*'
         | '/' { $<string>$ = strdup(yytext); }
         ;

这使得该操作仅适用于令牌'/'的减少更为明显。

行动本身也是不正确的。永远不应该在 lexer 操作之外使用yytext,因为它的值不可靠;它将是最近的词法分析器操作时的值,但由于解析器通常(但并非总是)提前读取一个标记,因此通常(但不总是)是与 next < / em>令牌。这就是为什么通常的建议是制作yytext的副本,但想法是将其复制到词法分析器中,将副本分配给{{{}的相应成员。 1}}以便解析器可以使用令牌的语义值。

您应该避免使用yylval。非终端只能有一种类型,应该在bison文件的序言中声明:

$<type>$ =

最后,您会发现拥有一个识别不同运算符的非终端非常有用,因为不同的运算符在语法上是不同的。在一个更完整的表达式语法中,您需要区分 %type <string> operator a + b * c的总和,a * b + c是a和b与c的乘积之和,operator是c和a和b的乘积。这可以通过对总和和产品语法使用不同的非终端,或者通过对非终端表达式使用不同的产生并使用优先级规则消除歧义来完成,但在这两种情况下,您将无法使用{{1} } non-terminal,不加区分地产生+*

对于它的价值,这里解释为什么a+b导致aba的输出:

  1. 制作operator : '+'没有明确的操作,因此最终使用默认操作$$ = $1

  2. 但是,返回'+'的词法分析器规则(大概是我在这里猜测)从不设置yylval。因此,yylval仍具有上次分配的值。

  3. 据推测(另一个猜测),生成WORD的词法分析器规则正确设置yylval.string = strdup(yytext);。因此'+'标记的语义值是前一个WORD标记的语义值,也就是指向字符串"a"的指针。

  4. 所以当规则

    operation_id :
        WORD operator WORD
        {
            printf("%s\n%s\n%s\n",$1, $3, $<string>2);
        }
        ;
    
  5. 执行,$1$2都具有值"a"(指向同一字符串的两个指针),$3具有值"b"。< / p>

    显然,$2在语义上不正确,值"a",但还有另一个错误等待发生。如上所述,您的解析器会泄漏内存,因为您永远不会free() strdup创建的任何字符串。这不太令人满意,并且在某些时候您将需要修复操作,以便在不再需要语义值时释放它们。此时,您将发现有两个语义值指向同一个已分配内存块,这使得free()很可能在同一个内存块上被调用两次,这是未定义的行为(并且可能产生非常大的行为)难以诊断的错误。)