要做到这一点非常困难,因此我尝试对这些步骤进行注释。有很多烦人的细节。
在其核心,这只是悬空 else 歧义的一种表现,其解析是众所周知的(强制解析器始终移动 else)。下面的解决方案解决了语法本身的歧义,是明确的。
我在这里使用的基本原则是几十年前 Alfred Aho 和 Jeffrey Ullman 在 Principles of Compiler Design 中概述的原则(所谓的“龙之书”,我之所以提到它,因为它的作者最近是 granted the Turing award正是因为那个和他们其他有影响力的作品)。特别是,我使用术语“匹配”和“不匹配”(而不是“开放”和“封闭”,它们也很流行),因为这就是我学习它的方式。
也可以使用优先声明来解决这个语法问题;事实上,事实证明这通常要简单得多。但是在这种特殊情况下,使用运算符优先级并不容易,因为相关标记(else)可以在任意数量的换行标记之前。我很确定你仍然可以构建一个基于优先级的解决方案,但是使用明确的语法有很多好处,包括易于移植到不使用相同优先级算法的解析器生成器,以及它是可以进行机械分析。
解决方案的基本大纲是将所有语句分为两类:
- “匹配的”(或“关闭的”)语句,这些语句是完整的,因为无法使用
else 子句扩展语句。 (换句话说,每个if…then 都与对应的else 匹配。)这些
- “unmatched”(或“open”)语句,可以使用
else 子句进行扩展。 (换句话说,至少有一个if…then 子句没有被else 匹配。)由于不匹配的语句是完整的语句,它不能紧跟else 标记;如果出现了 else 标记,它可以用来扩展语句。
一旦我们设法为这两类语句构造了语法,只需要弄清楚statement在歧义语法中的哪些用法可以跟随else。在所有这些上下文中,必须将非终结符 statement 替换为非终结符 matched-statement,因为只有匹配的语句才能跟在 else 后面而不与它交互。在其他情况下,else 不能是下一个标记,任一类别的语句都是有效的。
所以基本的语法风格是(取自龙书):
stmt → matched_stmt
| unmatched_stmt
matched_stmt → "if" expr "then" matched_stmt "else" matched_stmt
| other_stmt
unmatched_stmt → "if" expr "then" matched_stmt "else" unmatched_stmt
| "if" expr "then" stmt
other_stmt 不是条件语句。或者,更准确地说,除了以stmt 结尾的复合语句之外的任何内容。
据我所知,在 Hypertalk 中,if 语句是唯一可以以语句结尾的复合语句。其他复合语句以end X 精确终止,这有效地关闭了语句。但在其他语言中,例如 C 语言中,复合语句有很多种,其中大部分需要根据它们的终止子语句是(递归地)匹配还是不匹配来划分为“匹配”和“不匹配”。
我想在这里指出的一件事,如果你稍微看一下,从大纲语法中可以明显看出,if 语句的if…then…else 部分在语法上类似于括号前缀运算符。也就是说,matched_stmt 和 unmatched_stmt 都类似于一元减号的右递归规则:
unary → '-' unary
| atom
这又可以用扩展 BNF 方言写成,允许 Kleene 明星作为
unary → ('-')* atom
如果我们要对 Aho&Ullman 的语法进行这种转换,我们最终会得到:
if_then_else → "if" expr "then" matched_stmt "else"
matched_stmt → (if_then_else)* other_stmt
unmatched_stmt → (if_then_else)* "if" expr "then" stmt
这使得如何使用自上而下的递归下降解析器实现此语法变得相当清楚。 (需要一点左分解,但它最终仍然类似于一元减法语法。)我不打算在这个答案中进一步发展这个想法,但我认为 EBNF 转换有助于指导直觉这个语法实际上是如何解开 else 的。
这对于弄清楚如何处理换行符也很有帮助。关键的见解(对我来说)是语句必须以换行符结尾。一个例外是if 命令的压缩单行版本。但是该异常仅发生在 else 标记之前(并且仅当它在同一行匹配的 then 时)。在这个语法中,这种情况是用inner-matched 非终端实现的,这得益于单行语句(如do-statement)缺少终止换行符这一事实。终止单行语句的换行符添加到matched (single-statement NL) 的递归基本情况中;这是唯一需要处理的地方。多行复合语句均使用终止换行符定义(例如,参见repeat-statement)。
其余的大部分复杂性都涉及各种句法形式。唯一真正有趣的是在行尾处理then 标记之后的块。该块可以通过两种方式终止:
- 带有
end if 行,没有else 子句。这被视为“匹配”的情况,因为它显然无法使用else 子句进行扩展。
- 带有
else 子句(可以是单行else 或块else,其中else 标记位于行尾)。但是这里可能存在歧义。如果块中的最后一条语句是不匹配的if,则else 行应该扩展该语句,而不是终止块。这与其他匹配/不匹配的逻辑并没有什么不同;为了实现它,我创建了两个不同的block 非终端,一个以匹配语句结尾,另一个以不匹配语句结尾。然后,像往常一样,在 else 之前只能使用匹配的块。
(我发现 bison 3.7.6 中的新反例生成器在这里非常有用;我最初的尝试只是使用了block,因为我没有注意到歧义。但这是一个真正的歧义,它导致了转变-减少起源似乎很神秘的冲突。一旦我看到由反例生成器生成的反例——它显示了在block 内部发生的冲突,紧接着if-then——问题变得更加明显。)
matched-block 和unmatched-block 之间的交替是语法产生式和状态机之间对应的一个简单示例。两个非终结符代表一个非常简单的状态机中的两种状态,状态机记录一个位:最后一条语句是否匹配。非终结符必须是右递归才能工作,这与构建 LALR(1) 语法的通常“首选左递归”启发式方法不同。
好的,序言过长,语法如下。为了紧凑化,我将表达式简化为变量和布尔常量,仅包含一个简单语句 (do expr) 并仅包含另一个复合语句 (重复直到 expr / 块 / 结束重复)。 (最后一个是占位符。)
program : block
block : %empty
| matched-block
| unmatched-block
NL : '\n'
| NL '\n'
matched-block
: block matched
unmatched-block
: block unmatched
simple-statement
: "do" expression
repeat-statement
: "repeat" "until" expression NL block "end" "repeat" NL
matched : if-then matched else-matched
| if-then inner-matched else-matched
| if-then NL matched-block else-matched
| if-then NL else-matched
| if-then NL block "end" "if" NL
| repeat-statement
| simple-statement NL
inner-matched
: %empty
| simple-statement
| if-then inner-matched "else" inner-matched
unmatched
: if-then matched
| if-then unmatched
| if-then inner-matched "else" unmatched
| if-then matched "else" unmatched
if-then : "if" expression NL "then"
| "if" expression "then"
else-matched
: "else" NL block "end" "if" NL
| "else" matched
expression
: ID
| "true"
| "false"
两者之间有明显的歧义
ifCondition THEN statement EOL ELSE statement
和
ifCondition THEN EOL statementList ELSE statement
回想一下
statement: %empty
statementList: statement
结果statement 和statementList 都可以导出空序列。所以ifStatement 的上述两个产生式都可以推导出来:
ifCondition THEN EOL ELSE statement
解析器无法知道statement 之前是空的EOL 还是空的statementList 之后。 (您可能不在乎选择了哪一个,但解析器对这种决定很着迷。)
可空的产生式通常是有问题的。在可能的情况下,避免使用它们。不要让statement 派生为空,而是通过添加省略可选语句的规则来明确指示空语句可能去哪里。并考虑重写statementList,使其必须以EOL 结尾,我认为这是你的意图(但也许我错了)。