将扩展 BNF 转换为 Bison 语法,但有 shift/reduce 个错误

Converting Extended BNF to Bison grammar, but having shift/reduce errors

背景

我正在为类似乳胶的语言开发编译器。到目前为止,我已经编写了 lex 文件,并且按照它应该的方式工作。但是,由于我正在研究 .y 文件中的语法,所以我 运行 遇到了问题。

问题

我已经复制了我认为对我造成阻碍的语法部分:

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

在此上下文中,空白基本上是空格、制表符和换行符的任意组合。

根据我查看 y.output 文件的理解,shift/reduce 错误是由于规则

而引发的
documentbody: ... | ws MAKETITLE contentseq | ...

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

为清楚起见,对原始 EBNF 规范进行解释:

document: BEGINDOCUMENT [ws] [MAKETITLE] contentseq ENDDOCUMENT

也就是说ws终端和MAKETITLE都是可选的。

示例输入

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

以上所有都应该被语法接受。

我试过的

我知道许多冲突可以通过使用优先级来平息,但我在这方面的尝试都没有奏效。我试过为 MAKETITLE 和 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
        ;

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

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