Skip to content

Commit

Permalink
filter, based on selective functors
Browse files Browse the repository at this point in the history
  • Loading branch information
rossabaker committed Dec 6, 2020
1 parent a144809 commit 64e0c2a
Show file tree
Hide file tree
Showing 2 changed files with 39 additions and 0 deletions.
25 changes: 25 additions & 0 deletions core/shared/src/main/scala/cats/parse/Parser.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
14 changes: 14 additions & 0 deletions core/shared/src/test/scala/cats/parse/ParserTest.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
}
}

0 comments on commit 64e0c2a

Please sign in to comment.