From 64e0c2a0e538fdf2f3a1d0f7bb992473b6b72fb8 Mon Sep 17 00:00:00 2001 From: "Ross A. Baker" Date: Sun, 6 Dec 2020 12:06:59 -0500 Subject: [PATCH] filter, based on selective functors --- .../src/main/scala/cats/parse/Parser.scala | 25 +++++++++++++++++++ .../test/scala/cats/parse/ParserTest.scala | 14 +++++++++++ 2 files changed, 39 insertions(+) diff --git a/core/shared/src/main/scala/cats/parse/Parser.scala b/core/shared/src/main/scala/cats/parse/Parser.scala index e9ca160a..ca98b0f9 100644 --- a/core/shared/src/main/scala/cats/parse/Parser.scala +++ b/core/shared/src/main/scala/cats/parse/Parser.scala @@ -189,6 +189,9 @@ sealed abstract class Parser[+A] { def map[B](fn: A => B): Parser[B] = Parser.map(this)(fn) + def filter(fn: A => Boolean): Parser[A] = + Parser.select(this.map(a => Either.cond(fn(a), a, ())))(Parser.Fail) + /** Dynamically construct the next parser based on the previously * parsed value. * @@ -911,6 +914,9 @@ object Parser extends ParserInstances { case _ => Impl.Map1(p, fn) } + def select[A, B](p: Parser[Either[A, B]])(fn: Parser[A => B]): Parser[B] = + Impl.Select(p, fn) + /** Standard monadic flatMap * Avoid this function if possible. If you can * instead use product, ~, *>, or <* use that. @@ -1331,6 +1337,9 @@ object Parser extends ParserInstances { case Map(p, _) => // we discard any allocations done by fn unmap(p) + case Select(p, _) => + // we discard any allocations done by fn + unmap(p) case StringP(s) => // StringP is added privately, and only after unmap s @@ -1722,6 +1731,22 @@ object Parser extends ParserInstances { override def parseMut(state: State): B = Impl.map(parser, fn, state) } + final def select[A, B]( + parser: Parser[Either[A, B]], + fn: Parser[A => B], + state: State + ): B = { + val either = parser.parseMut(state) + if ((state.error eq null) && state.capture) + either.valueOr(a => fn.map(_(a)).parseMut(state)) + else null.asInstanceOf[B] + } + + case class Select[A, B](parser: Parser[Either[A, B]], fn: Parser[A => B]) + extends Parser[B] { + override def parseMut(state: State): B = Impl.select[A, B](parser, fn, state) + } + final def flatMap[A, B](parser: Parser[A], fn: A => Parser[B], state: State): B = { // we can't void before flatMap unfortunately, because // we need to be able to produce the next parser diff --git a/core/shared/src/test/scala/cats/parse/ParserTest.scala b/core/shared/src/test/scala/cats/parse/ParserTest.scala index c2faba39..225adb1e 100644 --- a/core/shared/src/test/scala/cats/parse/ParserTest.scala +++ b/core/shared/src/test/scala/cats/parse/ParserTest.scala @@ -1561,4 +1561,18 @@ class ParserTest extends munit.ScalaCheckSuite { } } + property("p.filter(_ => true) == p") { + forAll(ParserGen.gen, Arbitrary.arbitrary[String]) { (genP, str) => + val res0 = genP.fa.filter(_ => true).parse(str) + val res1 = genP.fa.parse(str) + assertEquals(res0, res1) + } + } + + property("p.filter(_ => false) fails") { + forAll(ParserGen.gen, Arbitrary.arbitrary[String]) { (genP, str) => + val res = genP.fa.filter(_ => false).parse(str) + assert(res.isLeft) + } + } }