Я пытаюсь разобрать логические выражения в дерево Expr
, используя парсеры Packrat Scala из scala-parser-combinators
.
sealed trait Expr
case class Term(term: String) extends Expr
case class And(x: Expr, y: Expr) extends Expr
case class Or(x: Expr, y: Expr) extends Expr
aaa and bbb --> And(Term(aaa),Term(bbb))
aaa and bbb or ccc --> Or(And(Term(aaa),Term(bbb)),Term(ccc))
aaa and (bbb or ccc) --> And(Term(aaa),Or(Term(bbb),Term(ccc)))
Эта грамматика, кажется, работает отлично:
object Parsing extends RegexParsers with PackratParsers {
override val skipWhitespace = false
val validIdentifiers = List("aaa", "bbb", "ccc", "ddd")
lazy val term: PackratParser[Term] = """\s*""".r ~> """\w+""".r flatMap { identifier =>
if (validIdentifiers.contains(identifier))
success(Term(identifier))
else
err(s"expected one of: $validIdentifiers")
}
lazy val and: PackratParser[And] =
expr ~ """\s+and\s+""".r ~ (term | parensExpr) ^^ { case e1 ~ _ ~ e2 => And(e1, e2) }
lazy val or: PackratParser[Or] =
expr ~ """\s+or\s+""".r ~ (term | parensExpr) ^^ { case e1 ~ _ ~ e2 => Or(e1, e2) }
lazy val parensExpr: PackratParser[Expr] = """\s*\(""".r ~> expr <~ """\s*\)""".r
lazy val expr: PackratParser[Expr] =
term ||| and ||| or ||| parensExpr
lazy val root: PackratParser[Expr] =
phrase(expr)
def parseExpr(input: String): ParseResult[Expr] =
parse(root, new PackratReader(new CharSequenceReader(input)))
}
Но сообщения об ошибках иногда .... плохие. Если синтаксический анализатор найдет неверный идентификатор в левой части and
, он правильно скажет нам об этом.
println(parseExpr("invalidIdentifier and aaa"))
[1.18] error: expected one of: List(aaa, bbb, ccc, ddd)
invalidIdentifier and aaa
^
Но если он найдет неверный идентификатор в справа со стороны and
, это даст нам это вводящее в заблуждение сообщение об ошибке.
println(parseExpr("aaa and invalidIdentifier"))
[1.4] failure: end of input expected
aaa and invalidIdentifier
^
Я почти уверен, что это произойдет, потому что expr
попробует все 4 варианта: and
/ or
/ parensExpr
потерпит неудачу, но term
удастся, просто набрав Term("aaa")
.
Затем root
phrase
включится и проверит, есть ли еще вход для потреблять и терпеть неудачу, потому что есть: «и invalidIdentifier».
Так что я подумал, что я буду pu sh phrase
на один уровень ниже. Другими словами, я изменил это:
lazy val expr: PackratParser[Expr] =
term ||| and ||| or ||| parensExpr
lazy val root: PackratParser[Expr] =
phrase(expr)
На это:
lazy val expr: PackratParser[Expr] =
term ||| and ||| or ||| parensExpr
lazy val root: PackratParser[Expr] =
phrase(term) ||| phrase(and) ||| phrase(or) ||| phrase(parensExpr)
Теперь все 4 параметра должны потерпеть неудачу, но мы должны увидеть сообщение об ошибке and
, потому что and
потреблял больше входных данных, чем другие 3 опции
Теперь я получаю более качественные сообщения об ошибках, НО, к моему удивлению, некоторые ранее действительные входные данные теперь недействительны!
println(parseExpr("aaa or bbb"))
[1.4] failure: string matching regex '\s+and\s+' expected but ' ' found
aaa or bbb
^
println(parseExpr("aaa and bbb or ccc"))
[1.12] failure: end of input expected
aaa and bbb or ccc
^
Я не понимаю, почему.
На самом деле, даже простое, тривиальное изменение, такое как это:
// before
lazy val root: PackratParser[Expr] =
phrase(expr)
// after
lazy val root: PackratParser[Expr] =
phrase(term ||| and ||| or ||| parensExpr)
... ломает ранее действительные входные данные.
Как получилось? Разве эти два определения root
не должны быть эквивалентными? Разве эти парсеры не являются прозрачными по ссылкам?
Что более важно, как мне go исправить это?