diff --git a/core/src/main/scala/parseback/parsers.scala b/core/src/main/scala/parseback/parsers.scala index 27c86e9..21fed57 100644 --- a/core/src/main/scala/parseback/parsers.scala +++ b/core/src/main/scala/parseback/parsers.scala @@ -53,7 +53,10 @@ sealed trait Parser[+A] { final def map2[B, C](that: Parser[B])(f: (A, B) => C): Parser[C] = (this ~ that) map f.tupled - def filter(p: A => Boolean): Parser[A] = Parser.Filter(this, p) + def filter(p: A => Boolean): Parser[A] = Parser.Filter(this, false, p) + + // filter out but at least leave one element. + def filterLeaveOne(p: A => Boolean): Parser[A] = Parser.Filter(this, true, p) // TODO come up with a better name final def ~!~[B](that: Parser[B]): Parser[A ~ B] = @@ -131,7 +134,7 @@ sealed trait Parser[+A] { case Apply(target, _, _) => markRoots(target, tracked2) - case Filter(target, _) => + case Filter(target, _, _) => markRoots(target, tracked2) case _ => tracked @@ -195,7 +198,7 @@ sealed trait Parser[+A] { p.nullableMemo = inner(target, tracked) p.nullableMemo - case p @ Filter(target, _) => + case p @ Filter(target, _, _) => p.nullableMemo = inner(target, tracked) p.nullableMemo @@ -465,17 +468,18 @@ object Parser { } } - final case class Filter[A](target: Parser[A], p: A => Boolean) extends Parser[A] { + final case class Filter[A](target: Parser[A], leaveOne: Boolean, p: A => Boolean) extends Parser[A] { nullableMemo = target.nullableMemo override def filter(p2: A => Boolean): Parser[A] = - Filter(target, { a: A => p(a) && p2(a) }) + Filter(target, leaveOne, { a: A => p(a) && p2(a) }) protected def _derive(line: Line, table: MemoTable) = - Filter(target.derive(line, table), p) + Filter(target.derive(line, table), leaveOne, p) protected def _finish(seen: Set[ParserId[_]], table: MemoTable) = - target.finish(seen, table) pmap { _ filter p } + // if the Result is unique, no need to filter out. + target.finish(seen, table) pmap { c => if(c.length == 1 && leaveOne) c else c filter p } } final case class Literal(literal: String, offset: Int = 0) extends Parser[String] { diff --git a/core/src/main/scala/parseback/render/Renderer.scala b/core/src/main/scala/parseback/render/Renderer.scala index 01d0a69..eac42c9 100644 --- a/core/src/main/scala/parseback/render/Renderer.scala +++ b/core/src/main/scala/parseback/render/Renderer.scala @@ -127,7 +127,7 @@ object Renderer { case Apply(target, _, _) => State pure (Left(target) :: Right("↪") :: Right("λ") :: Nil) - case Filter(target, _) => + case Filter(target, _, _) => State pure (Left(target) :: Nil) case Literal(literal, offset) => diff --git a/core/src/test/scala/parseback/ast/FilterSpecs.scala b/core/src/test/scala/parseback/ast/FilterSpecs.scala index e214a3f..2ae1c00 100644 --- a/core/src/test/scala/parseback/ast/FilterSpecs.scala +++ b/core/src/test/scala/parseback/ast/FilterSpecs.scala @@ -148,6 +148,17 @@ object FilterSpecs extends ParsebackSpec { expr must parseOk("1 - 2 + 3")(Add(Sub(IntLit(1), IntLit(2)), IntLit(3))) expr must parseOk("1 + 2 * 3")(Add(IntLit(1), Mul(IntLit(2), IntLit(3)))) } + + "disambiguate left-associativity with explicit parenthesis" in { + lazy val expr: Parser[Expr] = ( + expr ~ "+" ~ expr ^^ { (_, e1, _, e2) => Add(e1, e2) } + | "(" ~> expr <~ ")" + | num ^^ { (_, n) => IntLit(n) } + ) filterLeaveOne prec(Add) + + expr must parseOk("1 + (2 + 3)")(Add(IntLit(1), Add(IntLit(2), IntLit(3)))) + } + } // %%