【问题标题】:Building a boolean logic parser in Scala with PackratParser使用 PackratParser 在 Scala 中构建布尔逻辑解析器
【发布时间】:2022-08-19 01:38:16
【问题描述】:

我正在尝试构建一个布尔逻辑解析器,例如A == B AND C == D 输出类似And(Equals(A,B), Equals(C,D))

我的解析器有以下定义:

def program: Parser[Operator] = {
    phrase(operator)
}
def operator: PackratParser[Operator] = {
    leaf | node
}
def node: PackratParser[Operator] = {
    and | or 
}
def leaf: PackratParser[Operator] = {
    equal | greater | less
}
def and: PackratParser[Operator] = {
    (operator ~ ANDT() ~ operator) ^^ {
      case left ~ _ ~ right => And(left, right)}
}

我希望解析器映射到program -> operator -> node -> and -> operator (left) -> leaf -> equal -> operator (right) -> leaf -> equal。这不起作用。 但是,如果在上面的代码中我进行更改

def operatorWithParens: PackratParser[Operator] = {
    lparen ~> (operator | operatorWithParens) <~ rparen
}

并将and 更改为

def and: PackratParser[Operator] = {
    (operatorWithParens ~ ANDT() ~ operatorWithParens) ^^ {
      case left ~ _ ~ right => And(left, right)}
}

解析(A == B) AND (C == D) 成功。

我无法理解为什么前者不起作用而后者起作用。 我应该如何更改我的代码才能解析A == B AND C == D

编辑: 遵循@Andrey Tyukin 的建议,我修改了语法以说明优先级

def program: Parser[Operator] = positioned {
    phrase(expr)
}
def expr: PackratParser[Operator] = positioned {
    (expr ~ ORT() ~ expr1) ^^ {
      case left ~ _ ~ right => Or(left, right)} | expr1
}
def expr1: PackratParser[Operator] = positioned {
    (expr1 ~ ANDT() ~ expr2) ^^ {
      case left ~ _ ~ right => And(left, right)} | expr2
}
def expr2: PackratParser[Operator] = positioned {
    (NOTT() ~ expr2) ^^ {case _ ~ opr => Not(opr)} | expr3
}
def expr3: PackratParser[Operator] = {
    lparen ~> (expr) <~ rparen | leaf
}

尽管PackratParser 支持左递归语法,但我遇到了一个永远不会离开expr 的无限循环

  • phrase 是什么? or 是什么?它在某种程度上是不完整的。是否有可能提供包含所有导入的完整解析器,理想情况下作为具有所有依赖项的 ammonite 脚本?
  • 它是否有机会生成Equals(And(Equals(A, B), C),D)?换句话说,它被解析为((A == B) AND C) == D?因为没有运算符优先级,这就是您所期望的。我们解析A*B/C*DA*B + C*D 不同,因为+ 的优先级低于*,但/ 的优先级与* 相同。运算符优先级必须在语法中表示。
  • 在询问有关解析器组合器的问题时,您应该指定正在使用哪个库。基于^^ 的存在,我猜想scala-parser-combinators?这是非常缓慢和错误的,并且有更好的替代方案可用(例如cats-parse)。
  • @MatthiasBerndt 是的,它正在使用 scala-parser-combinators。线索是标题和问题本身中的packrat-parsing 标签和单词PackratParser

标签: scala parsing packrat-parsing


【解决方案1】:

看起来有一条从operator 到更短的operator 的路径:

operator -> node -> and -> (operator ~ somethingElse)

您似乎假设较短的operator (left) 会以某种方式减少到leaf,而最外面的operator 会跳过leaf 并选择node,无论出于何种原因。相反,它所做的只是扼杀它遇到的第一个leaf

您可以尝试将node 移动到leaf 之前,这样整个operator 在看到某事时就不会被第一个A 卡住。喜欢A == B AND ...

否则,我建议将其重构为

  • 析取
  • 的连词
  • 的原子公式

其中原子公式是

  • 比较或
  • 不可分割的带括号的顶级元素(即带括号的 析取,在这种情况下)。

期望使用不少repSeps。

【讨论】:

  • 我认为在leaf 之前移动node 只会使其右关联而不是左关联,因此它仍然无法正确解析。它需要实现运算符优先级以解析所需的方式。
  • just makes it right associative rather than left associative so it still wouldn't parse correctly - 我认为你是对的。它不会正确解析。但我莫名其妙地怀疑它实际上可能会解析为某物而不是遇到第一片叶子并放弃。也许它甚至会接受“正确的语言”,即使它会从中生成一个乱码的数据结构。
  • 无论哪种方式,解决方案肯定是在语法中对运算符优先级进行建模,但我不太了解 Packrat 来展示如何:)
  • > 它所做的只是塞住它遇到的第一片叶子。我的印象是它会尝试所有可能的选项,直到找到与输入字符串匹配的选项。因此,只要存在有效路径,在第一个错误的情况下窒息的想法似乎很奇怪。
  • @AlexandruBarbarosie“我的印象是它会尝试所有可能的选项,直到找到与输入字符串匹配的选项”- 你是对的,这正是问题所在:它找到一个匹配的,它“吃掉”输入的一部分,但随后它发现它不能再“吃掉”之后的任何东西,所以它停止了。与 yacc 生成的普通 LR 解析器不同,它不会尝试反刍已经消耗的 sub-operator 并反复咀嚼和食用它。它不是反刍动物,它没有这种回溯行为。
猜你喜欢
  • 1970-01-01
  • 2011-10-18
  • 1970-01-01
  • 2011-02-15
  • 2018-11-30
  • 1970-01-01
  • 2015-05-08
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多