背景
我正在为一种类似乳胶的语言编写编译器。我已经编写了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表示法(。