我正在使用GNU bison处理解析器,而我正面临一个有趣的问题。我的实际问题总体上有点不同而且不那么有趣,所以我会稍微说一点,所以答案一般会更有用。
我需要根据类型来区分表达式,比如字符串表达式中的算术表达式。他们的顶级非终结者有一些共同的祖先,比如
statement
: TOK_PRINT expression TOK_SEMICOLON {...}
;
expression
: arithmeticexpression {...}
| stringexpression {...}
;
现在我需要能够在两种表达式中都有变量
arithmeticexpression
: TOK_IDENTIFIER {...}
;
stringexpression
: TOK_IDENTIFIER {...}
;
(在stringexpression情况下只允许使用string-type的变量,并且在arithmeticexpression情况下只允许使用int-或float-type的变量) 但这显然导致R / R冲突,这是语言中固有的 - 它是不可能解决的,因为语言含糊不清。
当然,我可以淡化语言,所以只有一般"表达"对象在parse-stack上传递,但是我必须在动作中做很多手动类型检查,我想避免。 而且,在我的实际用例中,通过语法到变量规则的路径是如此不同,以至于我不得不将语言降低到太多以至于我会失去很多语法规则(即失去很多结构信息)并且需要手动将解析引擎写入某些动作。
我读过有关GLR解析的内容,听起来它可以解决我的问题。我考虑使用此功能:在相应变量类型错误的路径中使用上述语法和YYERROR
。
arithmeticexpression
: TOK_IDENTIFIER {
if(!dynamic_cast<IntVariable*>(
symbol_table[*$<stringvalue>1]))
YYERROR;
}
;
stringexpression
: TOK_IDENTIFIER {
if(!dynamic_cast<StringVariable*>(
symbol_table[*$<stringvalue>1]))
YYERROR;
}
;
但是Bison Manual说
- 在确定性GLR操作期间,YYERROR的效果是相同的 因为它在确定性解析器中的作用。
- 延迟动作中的效果类似,但精确点 错误未定义;相反,解析器恢复到确定性 操作,选择一个未指定的堆栈,继续使用a 语法错误。
- 在语义谓词中(参见Semantic Predicates) 非确定性解析,YYERROR默默地修剪调用的解析 测试。
醇>
我不确定我是否理解这一点 - 我这样理解:
有没有人有这种情况的经验?我对手册的理解是错误的吗?你会如何解决这个问题?对我来说,这个问题似乎是如此自然,以至于我认为人们会经常遇到这样的问题,因为野牛会内置一个解决方案......
答案 0 :(得分:1)
注意:至少从bison 3.0.1和bison 3.0.5开始,处理语义谓词操作时会出现一个错误,导致bison输出#line
指令在一行中间,导致编译失败。 this answer中描述了一个简单的修复程序,并已提交到bison存储库的maint
branch。 (从存储库中构建bison不适合胆小的人。但如果你不想自己编辑文件就可以从该存储库下载data/c.m4
。)
让我们从GLR解析器中关于YYERROR
的具体问题开始。
实际上,您可以在语义谓词中使用YYERROR
,以便通过拒绝它所属的生产来消除解析的歧义。您不能在语义操作中执行此操作,因为在解析器决定使用唯一解析之前不会执行语义操作,此时不再存在歧义。
语义操作可以在规则中的任何位置发生,并且在缩减时立即执行,即使缩减不明确。因为它们不按顺序执行,所以它们不能引用任何前面的语义动作产生的值(并且语义谓词本身没有语义值,即使它们占用了堆栈槽)。此外,因为它们作为生产的一部分被推测性地有效地执行,这可能不是最终解析的一部分,所以它们不应该改变解析器状态。
语义谓词通过变量yychar
可以访问前瞻标记(如果有)。如果yychar
既不是YYEMPTY
也不是YYEOF
,则相关的语义值和位置信息分别位于yylval
和yylloc
中。 (见Lookahead Tokens)。但是,在使用此功能之前必须小心;如果语法中的那一点没有歧义,那么野牛解析器很可能已经执行了语义谓词而没有读取先行标记。
所以你的理解很接近,但不太正确:
为了提高效率,GLR解析器会识别解析中何时没有歧义,并在这些点使用正常的直接移位减少算法。在大多数语法中,模糊性很少并且可以快速解决,因此这种优化意味着对于大多数解析而言,可以避免与维护多个备选方案相关的开销。在这种模式下(当然,如果可能存在歧义,它不会适用),YYERROR
会导致标准错误恢复,就像在减少操作时一样,在shift-reduce解析器中。
由于在解决歧义之前不会运行延迟操作,因此延迟操作中的YYERROR
将有效地执行,就像它处于确定状态一样,如上一段所述。即使存在自定义合并过程也是如此;如果正在进行自定义合并, all 将丢弃参与自定义合并的备选方案。之后,正常的错误恢复算法将继续。我不认为错误恢复点是&#34;随机&#34;,但预测它将发生在哪里并不容易。
如上所述,语义谓词可以出现在规则中的任何位置,并且如果有,则可以访问先行令牌。 (Bison手册在这里有点误导:语义谓词不能包含YYERROR
的调用,因为YYERROR
是一个语句,而语义谓词块的内容是一个表达式。如果是语义谓词是假的,然后解析器执行YYERROR
动作。)
实际上,这意味着你可以使用语义谓词而不是(或同样)动作,但基本上就像你提出的那样:
arithmeticexpression
: TOK_IDENTIFIER %?{
dynamic_cast<IntVariable*>(symbol_table[*$1])
}
stringexpression
: TOK_IDENTIFIER %?{
dynamic_cast<StringVariable*>(symbol_table[*$1])
}
注意:我从$<stringvalue>1
删除了类型声明,因为:
基本上不可读,至少对我而言,
更重要的是,就像显式演员一样,它不是类型安全的。
您应该声明令牌的语义类型:
%type <stringvalue> ID
这将允许野牛进行打字检查。
是否为此目的使用语义谓词是一个好主意是另一个问题。
答案 1 :(得分:0)
这里的示例有效,但是当我将其与Bison: GLR-parsing of valid expression fails without error message的解决方案放在一起时,遇到了以下问题:
该变量可以由多个标识符确定。您可以有一个数组索引,也可以是另一个对象的成员。我通过使用另一个非终结符来对这种情况进行建模
lvalue
: TOK_IDENTIFIER
| lvalue '[' arithmeticexpression ']'
| lvalue '.' TOK_IDENTIFIER
但是当我现在拥有
arithmeticexpression : lvalue
stringexpression : lvalue
并(尝试)从非终端“ lvalue”访问对象,如上所述,我遇到了分段错误。因此,看来这里的方法不适用于更复杂的情况。
我现在要做的是,如果变量类型错误,我将以语义ACTION访问对象并将$$
设置为nullptr
。
arithmeticexpression
: lvalue
{
$$ = nullptr;
if(dynamic_cast<IntVariable*>($lvalue->getVariable()))
$$ = new ArithmeticExpressionIntVariable($lvalue);
}
然后我必须像这样传播nullptr
(这是很多附加代码)
arithmeticexpression
...
| arithmeticexpression[left] '+' arithmeticexpressionM[right]
{
$$ = nullptr;
if($left && $right)
$$ = new ArithmeticExpressionPlus($left, $right);
}
现在,在
expression
: arithmeticexpression
| stringexpression
(note: I have "Expression* expr;" in the "%union{" declaration
and "%type <expression> expr" in the prologue)
我有一个歧义:可以用两种不同的方式来解析相同的输入文本,但是只有其中一种将具有值!= nullptr
。此时,我需要一个自定义合并过程,该过程基本上只选择非null值。
为此,我在这样的野牛文件的序言中向前声明了它
static Expression* exprMerge (yy::semantic_type x0, yy::semantic_type x1);
并像这样在结尾中定义它
static Expression* exprMerge (YYSTYPE x0, YYSTYPE x1) \
{
/* otherwise we'd have a legitimate ambiguity */
assert(!x0.expr || !x1.expr);
return x0.expr ? x0.expr : x1.expr;
}
最后,我不得不告诉野牛使用此程序来解决歧义,
expression
: arithmeticexpression %merge <exprMerge>
| stringexpression %merge <exprMerge>
老实说,我认为这是相当大的努力,如果bison在尝试合并路径而不是排除路径时测试了语义谓词(至少是规则背后的谓词),则这是不必要的。在它们合并之前。
但是至少这是有效的,并且比收集令牌并手动对其进行排序要省得多。