Haskell 编译器如何在实践中实现 parse-error(t) 规则?
How do Haskell compilers implement the parse-error(t) rule in practice?
Haskell 报告在布局规则中包含一个臭名昭著的条款,称为“parse-error(t)”。该规则的目的是避免强迫程序员在单行 let
表达式和类似情况下编写大括号。相关句子是:
The side condition parse-error(t) is to be interpreted as follows: if the tokens generated so far by L together with the next token t represent an invalid prefix of the Haskell grammar, and the tokens generated so far by L followed by the token “}” represent a valid prefix of the Haskell grammar, then parse-error(t) is true.
这会产生一种不寻常的依赖关系,其中词法分析器必然会为解析器生成标记,并通过插入额外的标记供解析器使用来响应解析器中产生的错误。这与您在任何其他语言定义中找到的几乎任何东西都不一样,如果按字面意思 100% 解释它,会使实现变得非常复杂。
不出所料,据我所知,没有 Haskell 编译器实现了所写的整个规则。例如GHC fails to parse the following expression,根据报告是合法的:
let x = 42 in x == 42 == True
类似的奇葩案例还有很多。 This post 列出了一些特别困难的例子。其中一些 GHC 可以正常工作,但它也(从 7.10.1 开始)在这个上失败:
e = case 1 of 1 -> 1 :: Int + 1
此外,GHC 似乎有一个未记录的语言扩展,称为 AlternativeLayoutRule
,它用词法分析器中的一堆标记上下文替换了 parse-error(t) 子句,在大多数情况下给出了类似的结果;但是,这不是默认行为。
现实世界的 Haskell 编译器(尤其包括 GHC)在词法分析期间做了什么来近似解析错误(t)规则? 我很好奇因为我正在尝试实现一个简单的 Haskell 编译器,而这条规则真的让我很困惑。 (另请参阅 this related question。)
我不认为 parse-error(t)
规则 意味着 难以实施。是的,它确实需要解析器与词法分析器进行通信,但除此之外,它可能被设计为相对 容易 使用当时占主导地位的解析技术来实现:LALR( 1) 基于生成的解析器,对纠错有一些小的支持,比如 GNU Bison,或者确实像 GHC 使用的 Happy。
具有讽刺意味的是,至少部分由于 Haskell 在启用解析器组合器库方面的成功,旧技术并不像以前那样占主导地位,至少在 Haskell社区。
LALR(1)(或 LR(1))生成的解析器具有以下特性,这些特性与 parse-error(t)
规则的预期 非常吻合解读:
- 永不回头。
- 其 table 驱动的决策意味着它始终“知道”给定令牌在当前位置是否合法,如果是,如何处理它。
Happy 有一个特殊的 error
标记,可用于在当前词法标记 不 合法时实现操作。鉴于此,GHC 的 Happy 语法中的 most relevant definition 是
close :: { () }
: vccurly { () } -- context popped in lexer.
| error {% popContext }
vccurly
(“virtual close curly”)是词法分析器在它自己选择关闭布局级别时发送的标记。 popContext
是 an action defined in the lexer source 从布局堆栈中删除布局级别。 (注意顺便说一下,在这个实现中,error
案例 不需要 需要词法分析器发回 vccurly
令牌)。
使用这个,所有 GHC 解析器规则必须使用 close
作为它们的非终结符标记来结束用 vocurly
打开的缩进块。假设其余的语法是正确的,这也正确地实现了规则。
或者至少,理论上是这样。事实证明,由于 Haskell/GHC 的其他特征 不 也适合 LALR(1) 文法,这有时会中断。
在您上面的两个示例中,第一个在 Haskell 2010 年进行了更改(因为人们意识到解析起来太笨拙),因此 GHC 在那里是正确的。但是第二个 (e = case 1 of 1 -> 1 :: Int + 1
) 的发生是因为 different design decision GHC 使得:
Making a parser parse precisely the right language is hard. So GHC's parser follows the following principle:
- We often parse "over-generously", and filter out the bad cases later.
GHC 有足够的扩展,Int + 1
可以 解析为启用了足够多扩展的类型。此外,必须编写一个 LALR(1)-解析器来直接处理 enabled/disabled 扩展的每个组合将是 真的 尴尬(不确定它是否可能)。所以它只是首先解析最通用的语言,然后在检查结果所需的扩展是否已启用时失败。但是到那时解析已经完成并且触发 parse-error
规则为时已晚。 (或者我假设是这样。)
最后,我要说的是,即使您不使用 (LA)LR,我也不认为处理 parse-error(t)
规则有什么不可能 (1)解析器。我怀疑 GHC 的 close
令牌之类的东西在组合器中也能很好地工作。但是您仍然需要与词法分析器进行某种通信。
Haskell 报告在布局规则中包含一个臭名昭著的条款,称为“parse-error(t)”。该规则的目的是避免强迫程序员在单行 let
表达式和类似情况下编写大括号。相关句子是:
The side condition parse-error(t) is to be interpreted as follows: if the tokens generated so far by L together with the next token t represent an invalid prefix of the Haskell grammar, and the tokens generated so far by L followed by the token “}” represent a valid prefix of the Haskell grammar, then parse-error(t) is true.
这会产生一种不寻常的依赖关系,其中词法分析器必然会为解析器生成标记,并通过插入额外的标记供解析器使用来响应解析器中产生的错误。这与您在任何其他语言定义中找到的几乎任何东西都不一样,如果按字面意思 100% 解释它,会使实现变得非常复杂。
不出所料,据我所知,没有 Haskell 编译器实现了所写的整个规则。例如GHC fails to parse the following expression,根据报告是合法的:
let x = 42 in x == 42 == True
类似的奇葩案例还有很多。 This post 列出了一些特别困难的例子。其中一些 GHC 可以正常工作,但它也(从 7.10.1 开始)在这个上失败:
e = case 1 of 1 -> 1 :: Int + 1
此外,GHC 似乎有一个未记录的语言扩展,称为 AlternativeLayoutRule
,它用词法分析器中的一堆标记上下文替换了 parse-error(t) 子句,在大多数情况下给出了类似的结果;但是,这不是默认行为。
现实世界的 Haskell 编译器(尤其包括 GHC)在词法分析期间做了什么来近似解析错误(t)规则? 我很好奇因为我正在尝试实现一个简单的 Haskell 编译器,而这条规则真的让我很困惑。 (另请参阅 this related question。)
我不认为 parse-error(t)
规则 意味着 难以实施。是的,它确实需要解析器与词法分析器进行通信,但除此之外,它可能被设计为相对 容易 使用当时占主导地位的解析技术来实现:LALR( 1) 基于生成的解析器,对纠错有一些小的支持,比如 GNU Bison,或者确实像 GHC 使用的 Happy。
具有讽刺意味的是,至少部分由于 Haskell 在启用解析器组合器库方面的成功,旧技术并不像以前那样占主导地位,至少在 Haskell社区。
LALR(1)(或 LR(1))生成的解析器具有以下特性,这些特性与 parse-error(t)
规则的预期 非常吻合解读:
- 永不回头。
- 其 table 驱动的决策意味着它始终“知道”给定令牌在当前位置是否合法,如果是,如何处理它。
Happy 有一个特殊的 error
标记,可用于在当前词法标记 不 合法时实现操作。鉴于此,GHC 的 Happy 语法中的 most relevant definition 是
close :: { () }
: vccurly { () } -- context popped in lexer.
| error {% popContext }
vccurly
(“virtual close curly”)是词法分析器在它自己选择关闭布局级别时发送的标记。 popContext
是 an action defined in the lexer source 从布局堆栈中删除布局级别。 (注意顺便说一下,在这个实现中,error
案例 不需要 需要词法分析器发回 vccurly
令牌)。
使用这个,所有 GHC 解析器规则必须使用 close
作为它们的非终结符标记来结束用 vocurly
打开的缩进块。假设其余的语法是正确的,这也正确地实现了规则。
或者至少,理论上是这样。事实证明,由于 Haskell/GHC 的其他特征 不 也适合 LALR(1) 文法,这有时会中断。
在您上面的两个示例中,第一个在 Haskell 2010 年进行了更改(因为人们意识到解析起来太笨拙),因此 GHC 在那里是正确的。但是第二个 (e = case 1 of 1 -> 1 :: Int + 1
) 的发生是因为 different design decision GHC 使得:
Making a parser parse precisely the right language is hard. So GHC's parser follows the following principle:
- We often parse "over-generously", and filter out the bad cases later.
GHC 有足够的扩展,Int + 1
可以 解析为启用了足够多扩展的类型。此外,必须编写一个 LALR(1)-解析器来直接处理 enabled/disabled 扩展的每个组合将是 真的 尴尬(不确定它是否可能)。所以它只是首先解析最通用的语言,然后在检查结果所需的扩展是否已启用时失败。但是到那时解析已经完成并且触发 parse-error
规则为时已晚。 (或者我假设是这样。)
最后,我要说的是,即使您不使用 (LA)LR,我也不认为处理 parse-error(t)
规则有什么不可能 (1)解析器。我怀疑 GHC 的 close
令牌之类的东西在组合器中也能很好地工作。但是您仍然需要与词法分析器进行某种通信。