【问题标题】:Shift Reduce Conflict in Bison GrammarShift 减少 Bison 语法中的冲突
【发布时间】:2016-01-01 16:25:28
【问题描述】:

我刚刚开始与 Bison 合作,但遇到了几个问题。我的语法目标是识别一种“命令管道”语言,其中一个命令的输出可以通过管道传输为另一个命令的输入。每个命令还可以有一个带有可选值的参数列表。这是一个例子:

command1 --param1 test --param2 test2 | command2 | command3 --param3

在前面的示例中,command1 采用两个参数,param1 的值为 testparam2 的值为 test2。然后command1 的输出被“传送”到command2,它不带任何参数。最后command2 的结果通过管道传递给command3,它接受一个参数(没有值的参数被认为是一个开关)。

对应的bison语法如下:

%{

#include <string>
#include <iostream>
#include "AST.h"
#include "Evaluator.h"

void yyerror (const char *error);
int  yylex();

%}

%code{
    Evaluator eval;
}

%union {
  char* s;
  double d;
  AbstractNode* abstractnode;
  CmdletCall* cmdletdef;
  ParameterDef* parameterdef;
  ParameterListDef* paramlistdef;
}

/* declare tokens */

%token EOL
%token<s> PARAMETERNAME
%token<s> BUILTIN
%token<s> IDENTIFIER


%type <parameterdef> parameterDef
%type <cmdletdef> cmdletCall
%type <abstractnode> pipeLineDef    
%type <paramlistdef> paramList
%%

input: /* nothing */
    | input pipeLineDef EOL {
                                $2->accept(eval);
                                delete $2;
                                std::cout << eval.result() << std::endl;
                            }                        
    | input EOL             {}
    ;



pipeLineDef: cmdletCall                  {$$ = $1;}      
    | pipeLineDef '|' cmdletCall         {$$ = new PipeLineDef($1, $3);}   
    ;


cmdletCall: IDENTIFIER                   {$$ = new CmdletCall($1);}                 
          | cmdletCall paramList         {$1->setParameterList($2);}   
          ;

paramList: parameterDef                 {  
                                            $$ = new ParameterListDef;
                                            $$->addChildNode($1);
                                        }  
         | paramList parameterDef       {
                                            $1->addChildNode($2);
                                            $$ = $1;
                                        }
         ;

parameterDef: PARAMETERNAME             {$$ = new ParameterDef($1);}
            | parameterDef IDENTIFIER   {
                                            $1->setValue($2);
                                        }         

            ;




%%


void yyerror (const char *error)
{
  std::cout << error << std::endl;
}

前面的语法有一个shift-reduce冲突,转载如下:

Terminals unused in grammar

BUILTIN

State 10 conflicts: 1 shift/reduce
State 10

7 cmdletCall: cmdletCall paramList .
9 paramList: paramList . parameterDef

PARAMETERNAME  shift, and go to state 9

PARAMETERNAME  [reduce using rule 7 (cmdletCall)]
$default       reduce using rule 7 (cmdletCall)

parameterDef  go to state 13

我想消除冲突,但我不确定我应该如何进行(我的理解是移位/减少冲突表明语法不明确)。但是,从我最初的测试来看,一切似乎都按预期工作。 令我困惑的另一点是 Bison 评估终端的方式。例如,如果我将cmdletCallparamList 规则改写如下,语法就会中断:

cmdletCall: IDENTIFIER paramList         {$$ = new CmdletCall($1);    $$->setParameterList($2);}   
          ;

paramList: /*nothing*/ 
                                        {  
                                            $$ = new ParameterListDef;

                                        }  
         | paramList parameterDef       {
                                            $1->addChildNode($2);
                                            $$ = $1;
                                        }
         ;

如果我如上所示重写语法,那么对于这样的输入:

command1 --param1

cmdletCall 规则中,$1 对应于IDENTIFIER 令牌的值将是command1 --param1,而不仅仅是command1

【问题讨论】:

  • 虽然 C(或可选的 C++)是 bison 生成代码的语言,但这个问题实际上与这两者无关。标签已编辑。
  • 那是两个独立的问题,应该是两个独立的问题。第二个问题几乎可以肯定涉及您的词法分析器(在某种意义上,其中的 C/C++ 代码),尽管我建议您只搜索在您的词法分析器中复制 yytext 的需要;它在所有常见问题解答中。
  • 保留第二个问题作为同一问题的一部分,因为它说明了在实践中使用 bison 和 flex 可能遇到的问题。既然你已经给出了这两个问题的答案,把它们放在一起是有意义的:)

标签: parsing bison


【解决方案1】:

您对cmdletCall 的定义实际上是IDENTIFIER paramList*(使用标准的常规语言Kleene 星号运算符)。但是paramListparamDef+。所以这显然是模棱两可的;没有办法知道有多少paramLists 跟随IDENTIFIER,因为没有迹象表明一个结束和下一个开始。

事实上,您只想(或最多)一个paramList。有多种选择,但最简单的是:

cmdletCall: IDENTIFIER paramList
paramList : /* empty */
          | paramList parameterDef

另一个选项是保持paramList 不可为空,并添加一个仅包含IDENTIFIERcmdletCall 选项。确实,这仅在您需要为语义规则分离案例时才有用,但肯定是可能的。

或者对于更简单的语法,只需去掉paramList

cmdletCall: IDENTIFIER
          | cmdletCall parameterDef

由于野牛生成的解析器更喜欢移位而不是归约,因此从您的模棱两可的语法生成的解析器将按照您的预期:只为每个命令生成零或一个 paramList。但无论如何,最好还是消除歧义。


相关语义值的问题是没有从扫描仪复制yytext 的内容; yytext 是 flex 构建的扫描器中的内部数据结构,其内容不属于您,因为扫描器可以并且会根据需要修改它们。

【讨论】:

  • 所以你的意思是在flex扫描仪中,我需要复制yytext的内容?目前我一直在做类似 yylval.s = yytext.
  • @BigLudinski:是的,这个问题一遍又一遍地出现。这是最近的答案:stackoverflow.com/a/33878874/1566221,这是几年前的答案:stackoverflow.com/a/13166741/1566221 正如我所说,它在所有常见问题解答中,包括野牛常见问题解答
  • 非常感谢所有的启发!我在新年前夜的大部分时间都在为此发疯。我按照您的建议修改了语法,这解决了移位/减少冲突。我还使用 yylval.s = strdup(yytext) 制作了 yytext 内容的私人副本,现在一切正常!我仍然需要正确地进行内存管理,但多亏了你,我走在了正确的轨道上 :)
  • @BigLudinski:酷。我的猜测是,在您的简单情况下,您可以只在AbstractNode 的析构函数中复制free 字符串,但是为了完全抵御解析错误,您还需要定义%destructor 函数以便bison 可以知道如何在错误处理期间从堆栈中弹出的语义值中释放内存。这是另一个 SO 答案:stackoverflow.com/a/31105408/1566221。我一直在想我应该把所有这些东西都收集到一个地方。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多