最近,我正在学习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”的情况。
所以我想知道如何修改它。
答案 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:这与您的问题无关,但您目前正在限制括号的嵌套深度,因为您的语法不是递归的。要允许无限制嵌套,您希望expr
和multiExpr
规则如下所示:
lazy val expr: Parser[List[String]] =
"(" ~> multiExpr <~ ")" | elem
lazy val multiExpr: Parser[List[String]] =
repsep(expr, "and" | "or") ^^ { _.foldLeft(List.empty[String])(_ ++ _) }
但我建议将expr
重命名为primaryExpr
和multiExpr
至expr
。
_.foldLeft(List.empty[String])(_ ++ _)
也可以更简洁地表达为_.flatten
。