将扩展BNF转换为Bison语法,但有移位/减少错误



背景

我正在为一种类似乳胶的语言编写编译器。我已经编写了lex文件,到目前为止,它的工作方式应该是这样的。然而,由于我正在处理y.y文件中的语法,我遇到了一些问题。

问题

我复制了语法中我认为阻碍我的部分:

%start document
%%
document: BEGINDOCUMENT documentbody ENDDOCUMENT;
documentbody: contentseq | ws MAKETITLE contentseq | MAKETITLE contentseq;
contentseq: | contentseq content;
content: STRING | ws;
ws: WHITESPACE;

在这种情况下,空白基本上是空格、制表符和换行符的任意组合。

正如我从y.output文件中了解到的那样,移位/减少错误是由于规则引起的

documentbody: ... | ws MAKETITLE contentseq | ...

给定WHITESPACE令牌,Bison不知道这是否是终端"内容"的一部分,也不知道它后面是否会跟着一个MAKETTLE令牌。两者都是完全有效的输入,我不确定如何解决这个问题。

为了清晰起见,对最初的EBNF规范进行了改写:

document: BEGINDOCUMENT [ws] [MAKETITLE] contentseq ENDDOCUMENT

换句话说,ws终端和MAKETTLE都是可选的。

示例输入

BEGINDOCUMENT WHITESPACE MAKETITLE STRING ENDDOCUMENT
BEGINDOCUMENT WHITESPACE STRING ENDDOCUMENT
BEGINDOCUMENT MAKETITLE STRING ENDDOCUMENT
BEGINDOCUMENT STRING ENDDOCUMENT

语法应该接受以上所有内容。

我尝试过的

我知道很多冲突可以通过使用优先级来解决,但我在这方面尝试过的都没有奏效。我尝试过为MAKETTLE和WHITESPACE令牌分配各种优先级,但这并没有解决问题。

我看到了关于其他shift/reduce相关问题的建议,以重写语法,使其不那么模糊,但我不确定如何做到这一点——至少在不改变语法接受和不接受的输入的情况下。

我曾想过但没有尝试过的一个解决方案是破坏lex文件,但这似乎是一个相当讨厌的解决方案,我宁愿在yacc中找到一些方法。

冲突基本上是由contentseq的可空性引起的。这迫使解析器在识别较长的contentseq之前识别一个空的contentseq。当输入启动BEGINDOCUMENT WHITESPACE时,这会产生冲突,因为在WHITESPACE之前的点,不知道是否应该减少空的contentseq

通过使contentseq不可为空(contentseq: content | contentseq content(,您可以很容易地解决这个问题,代价是必须显式处理省略的序列:

documentbody: %empty | contentseq | maketitle optionalcs
contentseq: content | contentseq content
optionalcs: %empty | contentseq
maketitle: WHITESPACE MAKETITLE | MAKETITLE

这是转换EBNF的可选语法[ x ]时的一个常见问题,尤其是在重复x时。您不能总是依赖于能够定义optional-x;通常需要创建两个右侧,一个带有x,另一个没有。


我看不出ws: WHITESPACE的意义;您可以只使用WHITESPACE令牌而不是ws非终端。如果你的语法比你展示的更复杂,那么非终端可能会引发冲突,但我看不出你粘贴的内容有任何歧义。尽管如此,在上面的示例解决方案中,我删除了冗余的非终端。

我个人的偏好是避免特定于工具的技巧,并定义语法以更准确地描述我们想要识别的内容。我相信这个订单上的语法可以识别你想要的文档:

%start document
%token BEGINDOCUMENT ENDDOCUMENT MAKETITLE STRING WS
%%
document: BEGINDOCUMENT documentbody ENDDOCUMENT
;
documentbody: prefix title contents
;
prefix: 
| WS
;
title: 
| MAKETITLE
;
contents: 
| STRING contentseq
;
contentseq: 
| contentseq content
;
content: STRING 
| WS
;

因此,我们从一些空白的可选前缀开始。后面跟着一个可选标题。后面跟着内容,内容(因为我们已经识别出前导空格(要么是空的,要么是后面跟着字符串或空格的字符串。

简单、直接,几乎任何人都很容易理解(当然,假设他们完全认识yacc表示法(。

最新更新