我刚刚开始与Bison合作,我面临着一些问题。我的语法的目标是识别“命令管道”语言,其中一个命令的输出可以作为另一个命令的输入。每个命令还可以包含带有可选值的参数列表。这是一个例子:
command1 --param1 test --param2 test2 | command2 | command3 --param3
在上一个示例中,command1
采用两个参数param1
,其值为test
,param2
的值为test2
。 command1
的输出然后“管道”到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评估终端的方式。例如,如果我按如下方式重写cmdletCall
和paramList
规则,则语法会中断:
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
。
答案 0 :(得分:3)
您对cmdletCall
的定义实际上是IDENTIFIER paramList*
(使用标准的常规语言Kleene星运算符)。但paramList
是paramDef+
。所以这显然是模棱两可的;没有办法告诉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
是灵活构建扫描程序中的内部数据结构,其内容不属于您,因为扫描程序可以并且将在其认为合适时修改它们。