【问题标题】:Add operators during parsing在解析期间添加运算符
【发布时间】:2023-03-23 17:15:01
【问题描述】:

我尝试了一下使用 Haskell 的解析器生成器,这里使用的是 Happy。我以前使用解析器组合器,例如 Parsec,而我现在无法实现的一件事是动态添加(在执行期间)新的外部定义的运算符。例如,Haskell 有一些基本的运算符,但我们可以添加更多,赋予它们优先级和固定性。所以我想知道如何按照 Haskell 设计(查看下面要解析的示例代码)用 Happy 重现它,如果它不是很可行,或者是否应该通过解析器组合器来完成。

-- Adding the new operator
infixl 5 ++

(++) :: [a] -> [a] -> [a]
[]     ++ ys = ys
(x:xs) ++ ys = x : xs ++ ys

-- Using the new operator taking into consideration fixity and precedence during parsing
example = "Hello, " ++ "world!"

【问题讨论】:

  • 提示:你可以检查 GHC 的语法:github.com/ghc/ghc/blob/master/compiler/parser/Parser.y
  • IIRC,GHC 解析所有忽略固定点的中缀运算符,然后根据固定点转换 AST。本质上,优先级和关联性在解析后是固定的。我不知道这是否真的更容易——也许是这样。
  • @chi – 所以它会是一个“解析后”元素?
  • 嗯,它发生在类型检查期间,在“正确”的时间。一般的想法是:GHC 已经推断出10 :: Num a => a 所以,如果我们以这种方式注释10,我们不会告诉 GHC 任何它不知道的东西——这是一个空操作。相反,f 的类型被推断(因为 MR)到其他东西,所以注释很重要。完整的解释有点棘手,需要深入挖掘类型系统、GHC Core、MR 和其他一些血腥细节。

标签: parsing haskell parser-generator happy


【解决方案1】:

Haskell 只允许几个优先级。所以你并不严格需要动态语法;您可以只使用优先级标记类而不是单个运算符来编写语法,从而使词法分析器面临将给定符号与给定优先级相关联的问题。

实际上,这会将运算符的动态添加移动到词法分析器中。这是一个有点不舒服的设计决定,尽管在某些情况下实施起来可能并不太难。这是一个不舒服的设计,因为它需要给词法分析器提供语义反馈;至少,词法分析器需要查阅符号表以确定它正在查看的令牌类型。至少在 Haskell 的情况下,固定性声明是有范围的,这让这更让人不舒服,因此为了跟踪固定性信息,词法分析器还需要了解范围规则。

在实践中,大多数允许程序文本定义运算符和运算符优先级的语言的工作方式与 Haskell 编译器的工作方式完全相同:表达式由语法解析为简单的项目列表(其中带括号的子表达式算作单个项目),并且在稍后的语义分析中,列表被重新排列成一个实际的树,考虑到优先级和关联性规则,使用一个简单版本的调车场算法。 (这是一个简单的版本,因为它不需要处理带括号的子结构。)

这个设计决定有几个原因:

    1234563违反了关注点分离。更糟糕的是,如果没有小的固定前瞻,例如 GLR 解析器,就很难或不可能使用解析技术。
  1. 许多语言的优先级高于 Haskell。在某些情况下,甚至优先级的数量也不是由语法定义的。例如,在 Swift 中,您可以声明自己的 precedence levels,并且您定义的级别不是使用数字而是与另一个先前定义的级别进行比较,从而导致优先级别之间的偏序。

    恕我直言,这实际上是比 Haskell 更好的设计决策,部分原因是它避免了具有左右关联运算符的优先级的歧义,但更重要的是因为相对优先级声明既避免了幻数,又允许解析器来标记来自不同模块的运算符的不明确使用。换句话说,它不会强制将优先声明机械地应用于任何一对完全不相关的运算符;从这个意义上说,它使运算符声明更易于编写。

  2. 语法要简单得多,并且可以说更容易理解,因为大多数人无论如何都依赖优先表而不是分析语法产生式来弄清楚运算符如何相互交互。从这个意义上说,由语法设置优先级比文档更让人分心。将 C++ 语法视为优先表比语法更易于阅读的一个很好的例子。

    另一方面,正如 C++ 文法也说明的那样,文法比简单的优先级声明更通用,因为它可以表达不对称的优先级。 (语法并不总是优雅地表达这些,但它们可以表达。)不对称优先级的一个典型例子是 lambda 构造 (λ ID expr),它非常松散地绑定到右侧,非常紧密地绑定到左侧:预期a ∘ λ b b ∘ a 的解析从不咨询 ∘ 的关联性。因为 λ 在它们之间。

在实践中,稍后构建树的成本非常低。构建树的算法众所周知,简单且便宜。

【讨论】:

  • 我非常感谢 Swift 解决这个问题的方式,谢谢!如果我想尝试实现它,那么您是否认为我还应该传达解析器和词法分析器,正如您在答案的第一部分中描述的那样?
  • @foxy:不,正如我在答案中建议的那样,最好的方法是在解析后制作表达式树,在括号中的项目列表的语义操作中生成 pethsps。
猜你喜欢
  • 1970-01-01
  • 2011-07-08
  • 2010-11-30
  • 1970-01-01
  • 2012-04-20
  • 2020-04-15
  • 2014-12-11
  • 2021-11-11
  • 2015-07-09
相关资源
最近更新 更多