如何修复此语法中的shift-reduce冲突

时间:2014-11-12 18:43:56

标签: parsing computer-science grammar context-free-grammar

以下是GOLD解析器语法的子集

<Start> ::= <Expression>

<Expression> ::= <RelationalExpression>

<RelationalExpression> ::= <RelationalExpression> <RelationalOp> <Factor>
                         | <Factor>

<Factor> ::= Id <FactorExtension>
           | <Literal>

<FactorExtension> ::= <FactorMember> <FactorExtension>
                    | <FactorIndexer> <FactorExtension> 
                    | <GenericIndexer> <FactorExtension> // Here is the problem! 
                    |

<FactorMember> ::= '.' Id

<FactorIndexer> ::= '[' <Expression> ']'

<GenericIndexer> ::= '<' <TypeName> '>'

<TypeName> ::= <Id>

<Literal> ::= DecLiteral | HexLiteral | FloatLiteral | RealLiteral | StringLiteral

<RelationalOp> ::= '=' | '<>' | '>' | '>=' | '<' | '<='

<Id> ::= <Id> '.' Id
       | Id

GOLD生成LR(1)解析器,所以此刻我将确信我的问题没有解决办法,但我决定在这里问一下这个问题。

这种语法生成的语言只识别非常简单的表达式,比如

Instance.Member < 50

然而,这个想法是这种语言可以识别像这样的表达式

Intance.Member<Integer> < 50

问题是识别<Integer>(名为<GenericFactor>)的语法部分会产生与关系运算符<的shift-reduce冲突。我可以看到为什么会发生这种冲突,我想要的是一种重新组织语法以便删除它的方法。

这可能吗?

提前致谢!

1 个答案:

答案 0 :(得分:3)

基本上,使用LR(1)语法很难表达这种语言。

这并非不可能,但结果语法不会生成您想要的解析树,因此您需要对AST进行后处理。此外,编写所有作品真的很痛苦,因此改变语法将很困难。

不幸的是,据我所知,GOLD解析器不会为您提供其他选项。但是,bison允许您生成GLR解析器,因为它是明确的,因此解析语言没有问题,作为奖励,您可以以方便和可读的格式编写语法。这是我推荐的解决方案。

这是一个替代方案,显然是由一些Java和/或C#解析器(但不是官方解析器)使用的,基于以下观察:

  • 通过检查下一个标记,始终可以判断>是通用括号还是关系运算符的结尾。在您的语法中,>关闭括号后面只能跟.[或输入结束;这些令牌都不能跟随关系运算符。

  • 作为通用括号的<必须遵循Id,并且只能跟Id秒和句号>之后。< / p>

因此,当您看到可能符合条件的<(在词法扫描程序中)时,您可以提前开始阅读。 (您可能希望缓存令牌以避免重新扫描它们。)如果您遇到任何不能在<...>通用选择器中的令牌,那么您知道<是关系运算符。否则,您最终会到达>,然后您可以检查以下令牌;如果该令牌符合通用选择器的条件,那么<>可以作为OPEN_GENERICCLOSE_GENERIC令牌而不是关系运算符发出。

一旦弄明白,就开始将预读令牌重新放回解析器中。

准确的细节可能或多或少复杂,取决于你的语法:

  • 大多数使用泛型的语言都允许使用嵌套泛型:vector<list<int>>。在这种情况下,您需要在预扫描中保留一个下推式堆栈,甚至使用替代解析器。事实上,类型名称看起来很复杂,但它总是遵循某种语法。

  • 使用嵌套泛型和右移运算符,您可能还必须确定>>是两个关闭选择器括号还是运算符。但是,您仍然可以根据以下令牌决定;如果外>是一个通用的选择器括号,那么内部a > b也必须是。

  • C ++(与Java或C#不同)允许非类型模板参数,因此>实际上是(布尔)模板参数是合法的。 C ++坚持认为(可以通过括号表达来保护,如果你要沿着那条路走下去,这可能是一个好主意。

  • 通用函数会产生一个特定的问题,因为它们后面跟着一个<(参数列表),它也可以跟在关系运算符之后。 C ++通过实际知道潜在的模板函数名称是否是模板名称来解决这个问题,这实际上通常解决了通用括号识别问题,因为C ++不允许您在没有完整模板参数列表的情况下获取模板化函数的地址。因此,在C ++中,模板化函数名后面的<只能是通用选择器括号。在Java中,当您调用模板化函数时,需要先放置模板参数,在这种情况下>变得明确无误。 (但这很难看,恕我直言)。在C#中,语法专家通过命令决定如果(可能是一个选择器括号后跟<...>,那么它将始终被解释为选择器括号。我没有使用C#编程,但在我看来,C#相当于C ++最令人烦恼的解析,除了它可能几乎总会产生一些错误信息。

总的来说,我一直偏向于不使用[...]作为选择器括号的解决方案。 Scala使用!<...>,D使用{{1}};这两者似乎都是非常合理的选择。