以下是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冲突。我可以看到为什么会发生这种冲突,我想要的是一种重新组织语法以便删除它的方法。
这可能吗?
提前致谢!
答案 0 :(得分:3)
基本上,使用LR(1)语法很难表达这种语言。
这并非不可能,但结果语法不会生成您想要的解析树,因此您需要对AST进行后处理。此外,编写所有作品真的很痛苦,因此改变语法将很困难。
不幸的是,据我所知,GOLD解析器不会为您提供其他选项。但是,bison
允许您生成GLR解析器,因为它是明确的,因此解析语言没有问题,作为奖励,您可以以方便和可读的格式编写语法。这是我推荐的解决方案。
这是一个替代方案,显然是由一些Java和/或C#解析器(但不是官方解析器)使用的,基于以下观察:
通过检查下一个标记,始终可以判断>
是通用括号还是关系运算符的结尾。在您的语法中,>
关闭括号后面只能跟.
,[
或输入结束;这些令牌都不能跟随关系运算符。
作为通用括号的<
必须遵循Id
,并且只能跟Id
秒和句号>
之后。< / p>
因此,当您看到可能符合条件的<
(在词法扫描程序中)时,您可以提前开始阅读。 (您可能希望缓存令牌以避免重新扫描它们。)如果您遇到任何不能在<...>
通用选择器中的令牌,那么您知道<
是关系运算符。否则,您最终会到达>
,然后您可以检查以下令牌;如果该令牌符合通用选择器的条件,那么<
和>
可以作为OPEN_GENERIC
和CLOSE_GENERIC
令牌而不是关系运算符发出。
一旦弄明白,就开始将预读令牌重新放回解析器中。
准确的细节可能或多或少复杂,取决于你的语法:
大多数使用泛型的语言都允许使用嵌套泛型:vector<list<int>>
。在这种情况下,您需要在预扫描中保留一个下推式堆栈,甚至使用替代解析器。事实上,类型名称看起来很复杂,但它总是遵循某种语法。
使用嵌套泛型和右移运算符,您可能还必须确定>>
是两个关闭选择器括号还是运算符。但是,您仍然可以根据以下令牌决定;如果外>
是一个通用的选择器括号,那么内部a > b
也必须是。
C ++(与Java或C#不同)允许非类型模板参数,因此>
实际上是(布尔)模板参数是合法的。 C ++坚持认为(
可以通过括号表达来保护,如果你要沿着那条路走下去,这可能是一个好主意。
通用函数会产生一个特定的问题,因为它们后面跟着一个<
(参数列表),它也可以跟在关系运算符之后。 C ++通过实际知道潜在的模板函数名称是否是模板名称来解决这个问题,这实际上通常解决了通用括号识别问题,因为C ++不允许您在没有完整模板参数列表的情况下获取模板化函数的地址。因此,在C ++中,模板化函数名后面的<
只能是通用选择器括号。在Java中,当您调用模板化函数时,需要先放置模板参数,在这种情况下>
变得明确无误。 (但这很难看,恕我直言)。在C#中,语法专家通过命令决定如果(
可能是一个选择器括号后跟<...>
,那么它将始终被解释为选择器括号。我没有使用C#编程,但在我看来,C#相当于C ++最令人烦恼的解析,除了它可能几乎总会产生一些错误信息。
总的来说,我一直偏向于不使用[...]
作为选择器括号的解决方案。 Scala使用!<...>
,D使用{{1}};这两者似乎都是非常合理的选择。