转变减少野牛语法中的冲突

时间:2016-01-01 16:25:29

标签: parsing bison

我刚刚开始与Bison合作,我面临着一些问题。我的语法的目标是识别“命令管道”语言,其中一个命令的输出可以作为另一个命令的输入。每个命令还可以包含带有可选值的参数列表。这是一个例子:

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

在上一个示例中,command1采用两个参数param1,其值为testparam2的值为test2command1的输出然后“管道”到command2,不带任何参数。最后,command2的结果通过管道传递给command3,它接受​​一个参数(没有值的参数被视为切换)。

相应的野牛语法如下:

%{

#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

1 个答案:

答案 0 :(得分:3)

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

事实上,您希望只有(或最多)一个cmdletCall: IDENTIFIER paramList paramList : /* empty */ | paramList parameterDef 。有几种选择,但最简单的是:

paramList

另一种选择是保持cmdletCall不可为空,并添加IDENTIFIER选项,该选项仅包含paramList。实际上,这只有在你需要为语义规则分开案例时才有用,但肯定是可能的。

或者对于更简单的语法,只需摆脱cmdletCall: IDENTIFIER | cmdletCall parameterDef

paramList

由于bison生成的解析器更喜欢shift to reduce,因此根据您的模糊语法生成的解析器将按照您的预期执行:仅为每个命令生成零或一yytext。但无论如何,建议您尽量消除歧义。

您对相关语义值的问题是不从您的扫描仪复制yytext的内容; echo是灵活构建扫描程序中的内部数据结构,其内容不属于您,因为扫描程序可以并且将在其认为合适时修改它们。