【问题标题】:Grammar for if statements in newline sensitive language换行敏感语言中 if 语句的语法
【发布时间】:2021-03-30 19:02:23
【问题描述】:

我正在研究一种读起来很像英语的语言,但 if 语句的语法存在问题。如果您好奇,该语言的灵感来自 HyperTalk,因此我试图确保匹配该语言中的所有有效结构。我使用的示例输入演示了所有可能的if 构造,可以查看here。有很多,所以我不想内联代码。

我已经从语法中删除了大多数其他结构,使其更易于阅读,但基本上语句如下所示:

start
    : statementList
;

statementList
    : '\n'
    | statement '\n'
    | statementList '\n'
    | statementList statement '\n'
;

statement
    : ID
    | ifStatement
;

我看到的移位/减少冲突在 ifStatement 规则中:

ifStatement
    : ifCondition THEN statement
    | ifCondition THEN statement ELSE statement
    | ifCondition THEN statement ELSE '\n' statementList END IF
    | ifCondition THEN '\n' statementList END IF
    | ifCondition THEN '\n' END IF
    | ifCondition THEN '\n' ELSE statement
    | ifCondition THEN '\n' ELSE '\n' statementList END IF
    | ifCondition THEN '\n' statementList ELSE statement
    | ifCondition THEN '\n' statementList ELSE '\n' statementList END IF
// The following rules cause issues, but should be legal:
    | ifCondition THEN statement newlines ELSE statement
    | ifCondition THEN statement newlines ELSE '\n' statementList END IF
;

ifCondition
    : IF expression
    | IF expression '\n'
;

expression
    : TRUE
    | FALSE
;

newlines
    : '\n'
    | newlines '\n'
;

问题是我需要支持这个结构:

if true then statement # <- Any number of newlines
else statement

问题(据我了解)是没有足够的上下文来正确确定是移动else,还是只减少if true then statement 部分而不知道后面会发生什么(语句列表的末尾,或另一种说法)。这甚至可以解析吗?

我有parserscannersample input 的要点可以试用。

【问题讨论】:

  • 所以以下内容不在您的列表中:if true then statement \n else \n statement \n statement \n end if?我非常期待看到它,以至于我想象了能够识别它的制作。我现在已经从我的回答中消除了这种幻想。
  • 其实你是对的!看起来我在测试输入中错过了那个案例。似乎没有大量关于语法的优秀文档,所以我不得不猜测并使用原始软件进行检查。
  • 更新了问题,修复了@rici 指出的问题,但重申了我认为的根本原因。
  • 好的,我想我知道了。只是想再做一些测试。
  • 我印象深刻!通过让语句消耗所有换行符(而不是让 statementList 对此负责),我非常接近,但这使得内联语句(即:if true then statement else statement 等结构)无法解析。

标签: parsing grammar bison


【解决方案1】:

要做到这一点非常困难,因此我尝试对这些步骤进行注释。有很多烦人的细节。

在其核心,这只是悬空 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_stmtunmatched_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-blockunmatched-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"

上一个答案(针对原始问题,仅在edit history 中可见)

两者之间有明显的歧义

ifCondition THEN statement EOL ELSE statement

ifCondition THEN EOL statementList ELSE statement

回想一下

statement: %empty
statementList: statement

结果statementstatementList 都可以导出空序列。所以ifStatement 的上述两个产生式都可以推导出来:

ifCondition THEN EOL ELSE statement

解析器无法知道statement 之前是空的EOL 还是空的statementList 之后。 (您可能不在乎选择了哪一个,但解析器对这种决定很着迷。)

可空的产生式通常是有问题的。在可能的情况下,避免使用它们。不要让statement 派生为空,而是通过添加省略可选语句的规则来明确指示空语句可能去哪里。并考虑重写statementList,使其必须以EOL 结尾,我认为这是你的意图(但也许我错了)。

【讨论】:

  • 我会看看我是否可以修改语法以删除可为空的产品。实际上,要使换行符分隔的语句/定义正常工作,我确实遇到了一些困难。 This post 有所帮助,但替代建议对我不起作用。
  • @james:我想在看到人们编写语法的问题的七年里,我对如何最好地做到这一点的看法已经改变了:-)
  • @james:顺便说一句,如果您使用的是jaedworks.com/hypercard/scripts/hypertalk-bnf.html,那么您会发现该语法存在一些歧义。我认为它包括我在这个答案中提到的那个。
  • 我开始使用该参考,但由于缺少规则和一些歧义,最终放弃了它。从那以后,我一直在研究原始手册(没有正式的语法规范,只是对语言如何工作的描述)。我开始怀疑编写自己的解析器是否比继续与 Bison 斗争更容易。
  • @james:我认为你可以用递归下降来解析超话。但如果你能做到这一点,野牛也不会成为问题。问题是正确指定语言而没有歧义。使用 R.D. 你可以忽略歧义,但你会留下“语言是我的解析器碰巧识别的任何东西”。我不认为这会打扰到最初的实现者,也可能不会打扰你,所以我的偏见不需要考虑在内。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-06-09
  • 1970-01-01
  • 1970-01-01
  • 2011-12-01
  • 1970-01-01
  • 2010-10-06
相关资源
最近更新 更多