如何使我的解析器支持逻辑操作和单词不区分大小写?

时间:2018-04-20 13:19:37

标签: scala parsing parser-combinators

最近,我正在学习Scala解析器组合器。我想在给定的字符串中解析key。例如,

  

val expr1 =“local_province!= $ province_name $或city = $ city_name $或people_number<> $ some_digit $”

     

// ==> List("local_province", "city", "people_number")

     

val expr2 =“(local_province = $ province_name $)”

     

// ==> List("local_province")

     

val expr3 =“(local_province = $ province_name $或city = $ city_name $)和(lib_name = $ akka $或lib_author = $ martin $)”

     

// ==> List("local_province", "city", "lib_name", "lib_author")

试验

import scala.util.parsing.combinator.JavaTokenParsers

class KeyParser extends JavaTokenParsers {
  lazy val key = """[a-zA-Z_]+""".r
  lazy val value = "$" ~ key ~ "$"
  lazy val logicOps = ">" | "<" | "=" | ">=" | "<=" | "!=" | "<>"
  lazy val elem: Parser[String] = key <~ (logicOps  ~ value)
  lazy val expr: Parser[List[String]] =
    "(" ~> repsep(elem, "and" | "or") <~ ")" | repsep(elem, "and" | "or")

  lazy val multiExpr: Parser[List[String]] =
    repsep(expr, "and" | "or") ^^ { _.foldLeft(List.empty[String])(_ ++ _) }
}

object KeyParser extends KeyParser {
  def parse(input: String) = parseAll(multiExpr, input)
}

这是我在Scala REPL中的测试

  

KeyParser.parse(表达式1)

     

[1.72]失败:$' expected but&gt;'结果

     

KeyParser.parse(表达式2)

     

[1.33]解析:List(local_province)

     

KeyParser.parse(表达式3)

     

[1.98]解析:列表(local_province,city,lib_name,lib_author)

我注意到KeyParser仅适用于"=",并且不支持"(local_province<>$province_name$ AND city!=$city_name$)"包含"<> | !="和“AND”的情况。

所以我想知道如何修改它。

1 个答案:

答案 0 :(得分:1)

  

我注意到KeyParser仅适用于"="

这不是真的。它也适用于!=<>。它不起作用的是>=<=<>

更一般地说,对于那些具有前缀的运算符出现在它们之前的备选列表中,它不起作用。这是>=不匹配,因为>出现在它之前,并且是它的前缀。

那么为什么会这样呢? |运算符创建一个解析器,如果成功则生成左解析器的结果,否则生成正确解析器的结果。因此,如果您有一个|链,您将获得该链中第一个可以匹配当前输入的解析器的结果。因此,如果当前输入为<>$some_digit$,解析器logicOps将与<匹配,并留下>$some_digit$作为剩余输入。所以现在它尝试将value与输入匹配并失败。

为什么回溯没有帮助?因为logicOps解析器已经成功,所以无处可回溯。如果解析器的结构如下:

lazy val logicOpAndValue = ">" ~ value | "<" ~ value | "=" ~ value |
                           ">=" ~ value | "<=" ~ value | "!=" ~ value |
                           "<>" ~ value
lazy val elem: Parser[String] = key <~ logicOpAndValue

然后会发生以下情况(当前输入为<>$some_digit$):

  • ">"与当前输入不匹配,因此请转到下一个备选
  • "<"与当前输入匹配,因此请使用当前输入~尝试value(即>$some_digit$)的右操作数。这失败了,所以继续下一个替代方案。
  • ......一堆不匹配的替代品......
  • "<>"与当前输入匹配,因此请尝试~的右操作数。这也匹配。成功了!

但是在您的代码中,~ value不在备选列表中,而是在每个备选内容之外。因此,当解析器失败时,我们不再在任何替代方案中,因此没有下一个替代尝试,它只是失败。

当然,将~ value移到替代方案中并不是一个令人满意的解决方案,因为它在一般情况下难以控制并且不易维护。

一种解决方案就是在备选方案的开头移动较长的运营商(即">=" | "<=" | "<>" | ">" | "<" | ...)。这种方式只有在">""<"">="失败时才会尝试"<=""<>"

更好的解决方案,不依赖于备选方案的顺序,因此不易出错,是使用|||代替。 |||的工作方式与|类似,只是它会尝试所有替代方案,然后返回最长的成功结果 - 而不是第一个。

PS:这与您的问题无关,但您目前正在限制括号的嵌套深度,因为您的语法不是递归的。要允许无限制嵌套,您希望exprmultiExpr规则如下所示:

lazy val expr: Parser[List[String]] =
  "(" ~> multiExpr <~ ")" | elem
lazy val multiExpr: Parser[List[String]] =
  repsep(expr, "and" | "or") ^^ { _.foldLeft(List.empty[String])(_ ++ _) }

但我建议将expr重命名为primaryExprmultiExprexpr

_.foldLeft(List.empty[String])(_ ++ _)也可以更简洁地表达为_.flatten