将扩展 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 表示法)。
背景
我正在为类似乳胶的语言开发编译器。到目前为止,我已经编写了 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 表示法)。