我正在使用野牛解析一个我无法控制它的规范。在其定义中存在递归规则,并且由于语言使用缩进,因此导致减少/减少错误。所以为了摆脱减少/减少我添加了一个半冒号。我有一个布局跟踪器,可以自动插入半冒号和大括号。我正在考虑扩展插入半冒号的规则以支持我添加的那个不在规范中的规则,但我想不出一种知道我们何时处于递归规则末尾的方法。
有没有一种可靠的方法可以知道我何时处于递归规则的末尾或者对不同方法的任何建议?或者像解决方案和词法分析器之间的某种方式获得双向通信一样麻烦?
目前使用拉解析器。我认为使用推送解析器可以让我更好地跟踪我在词法分析器中的位置,但是当我尝试使用define指令生成推送解析器时,无法识别该选项。 我正在使用bison 3.0.4和一个自定义词法分析器,用C ++ api生成一个纯粹的解析器。
编辑:
exp : infixexp TYPE_SEP type {}
| infixexp TYPE_SEP context RIGHT_ARROW type {}
| infixexp {}
infixexp : lexp qop infixexp {}
| MINUS infixexp {}
| lexp {}
lexp : BACKSLASH apat_list FUNC_TYPE_CONS exp {}
| KW_LET decls KW_IN exp {}
| KW_IF exp SEMI_COLON KW_THEN exp SEMI_COLON KW_ELSE exp {} //conditional
| KW_CASE exp KW_OF L_BRACE alts R_BRACE {}
| KW_DO L_BRACE stmts R_BRACE {}
| fexp SEMI_COLON {}
fexp : aexp {}
| fexp aexp {}
literal : INTEGER {}
| FLOAT {}
| CHAR {}
| STRING {}
aexp : qvar {}
| gcon {}
| literal
| L_PAREN exp R_PAREN {}
| L_PAREN exp_list R_PAREN {}
| L_BRACKET exp R_BRACKET {}
| L_BRACKET exp_list R_BRACKET {}
| L_BRACKET exp DOTDOT R_BRACKET {}
| L_BRACKET exp DOTDOT exp R_BRACKET {}
| L_BRACKET exp COMMA exp DOTDOT exp R_BRACKET {}
| L_BRACKET exp PIPE qual_list R_BRACKET {}
| L_PAREN infixexp qop R_PAREN {}
| L_PAREN qop infixexp R_PAREN {}
| qcon L_BRACE fbind_list R_BRACE {}
| aexp L_BRACE fbind_list R_BRACE {}
apat : qvar {}
| qvar AT_SYM apat {}
| gcon {}
| qcon L_BRACE fpat_list R_BRACE {}
| literal {}
| WILDCARD {}
| L_PAREN pat R_PAREN {}
| L_PAREN pat COMMA pat_list R_PAREN {}
| L_BRACKET pat_list R_BRACKET {}
| TILDE apat {}
从语法中添加了一个部分。它基本上是Haskell 2010规范的修改版本。通过在fexp
的定义中添加lexp
之后的半冒号来解决减少/减少冲突。
我正在模拟缩进/ dedent并插入开括号和花括号。而我基本上是在考虑the lexer hack,但无法弄清楚如何用Bison做到这一点。并且有多个递归规则,但只有一个导致减少/减少错误。
编辑2:
答案 0 :(得分:2)
处理缩进感知语言的常用方法是在词法扫描程序中制作INDENT和DEDENT标记。使用推送界面会更容易,因此很遗憾您使用的是未实现该功能的野牛C ++ API。
但是在词法扫描程序和解析器之间使用填充程序也可以毫不费力地完成。您可以在this answer中看到Python填充程序的示例; ply
也没有提供推送解析器接口,因此垫片会保留一个小的持久性令牌队列,这些令牌将被发送到解析器并在向真正的词法扫描器询问下一个令牌之前检查该队列。 / p>
正如答案所示,在大多数布局感知语言中,并非所有换行实际上都具有语义意义。例如,在Python本身中,括号,大括号或括号内的换行符只是普通的空格。该规则也可以通过垫片轻松实现(虽然我没有通过在链接的答案中这样做来使代码复杂化),只需跟踪括号的级别即可。
并非所有语言都能让生活如此轻松;例如,由于存在函数文字,您可能会使用一种语言,其中缩进可以在括号列表中重新插入。或者你可能有像ecmascript这样的语言,如果替代解析是不可能的,分号插入规则允许在括号之外的连续行。 Haskell有一个类似的规则,如果无法进行替代解析,可以插入大括号。
起草了ecmascript规则,旨在使编写解析器成为可能。 (或者,更准确地说,我认为,该规则是通过对现有解析器进行逆向工程来制定的,但我无法证明这一点。)事实证明,通过构建一个可以实现ecmascript自动分号插入是可能的。成对令牌的字典,可以用换行符分隔而不插入分号。 (或者,如果可能的话,必须在它们之间插入分号的令牌对,这是另一组的反转。)这些集合可以通过语法分析自动构建,使用每个产品的FIRST和FOLLOW集合。 (ecmascript规则的细节需要稍微调整,因为有一些令牌对可能出现在有效的程序中,但不允许用换行符分隔。例如,return 3
是一个有效的语句,但如果return
位于一行的末尾并且3位于下一行,则必须自动插入分号')。 Bison不会自动进行此分析,因此它取决于自定义工具,但并不是特别困难。
Haskell似乎并不那么宽容。我在Haskell report, section 9.3中看到,在该部分的末尾:
解析错误规则难以完全实现,因为这样做涉及固定性。例如,表达式
do a == b == c
有一个明确的(尽管可能是类型不正确的)解析,即
(do { a == b }) == c
因为(==)是非关联的。因此,建议程序员避免编写需要解析器在这种情况下插入右括号的代码。
这不是很有希望,但它也表明实现不会是完美的,并且请求程序员不要期望完美的解析器实现:)
我认为将链接答案中的IndentWrapper
垫片翻译成C ++并不困难,即使对于不太熟悉Python的人来说也是如此,所以我并不打算在这里做到这一点。如果这个假设不正确,请告诉我。