Skip to content

Commit

Permalink
Added extra collect combinators as well as ~> and <~ (#53)
Browse files Browse the repository at this point in the history
* Added extra collect combinators as well as ~> and <~

* Fixed collect

* Quick fix on CI

* gave collect new names to resolve stupid ambiguity
  • Loading branch information
j-mie6 authored Jan 28, 2021
1 parent 7891944 commit d29d318
Show file tree
Hide file tree
Showing 3 changed files with 55 additions and 9 deletions.
12 changes: 6 additions & 6 deletions .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -39,35 +39,35 @@ jobs:

steps:
- name: Checkout repository
if: needs.check-duplicate.outputs.should_skip != 'true'
#if: needs.check-duplicate.outputs.should_skip != 'true'
uses: actions/[email protected]

- name: Setup Scala
if: needs.check-duplicate.outputs.should_skip != 'true'
#if: needs.check-duplicate.outputs.should_skip != 'true'
uses: olafurpg/setup-scala@v10
with:
java-version: ${{ matrix.java }}

- name: Cache Coursier
if: needs.check-duplicate.outputs.should_skip != 'true'
#if: needs.check-duplicate.outputs.should_skip != 'true'
uses: actions/cache@v2
with:
path: ~/.cache/coursier
key: sbt-coursier-cache

- name: Cache SBT
if: needs.check-duplicate.outputs.should_skip != 'true'
#if: needs.check-duplicate.outputs.should_skip != 'true'
uses: actions/cache@v2
with:
path: ~/.sbt
key: sbt-${{ hashFiles('**/build.sbt') }}

- name: Test
if: needs.check-duplicate.outputs.should_skip != 'true'
#if: needs.check-duplicate.outputs.should_skip != 'true'
run: sbt ++$SCALA_VERSION test

- name: Scaladoc
if: needs.check-duplicate.outputs.should_skip != 'true'
#if: needs.check-duplicate.outputs.should_skip != 'true'
run: sbt ++$SCALA_VERSION doc

coverage:
Expand Down
30 changes: 30 additions & 0 deletions src/main/scala/parsley/Parsley.scala
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,20 @@ object Parsley
def #>[B](x: B): Parsley[B] = this *> pure(x)
/**This combinator is an alias for `*>`*/
def >>[B](q: Parsley[B]): Parsley[B] = this *> q
/**
* This is the parser that corresponds to a more optimal version of `(p <~> q).map(_._2)`. It performs
* the parse action of both parsers, in order, but discards the result of the invokee.
* @param q The parser whose result should be returned
* @return A new parser which first parses `p`, then `q` and returns the result of `q`
*/
def ~>[B](q: Parsley[B]): Parsley[B] = this *> q
/**
* This is the parser that corresponds to a more optimal version of `(p <~> q).map(_._1)`. It performs
* the parse action of both parsers, in order, but discards the result of the second parser.
* @param q The parser who should be executed but then discarded
* @return A new parser which first parses `p`, then `q` and returns the result of the `p`
*/
def <~[B](q: Parsley[B]): Parsley[A] = this <* q
/**This parser corresponds to `lift2(_+:_, p, ps)`.*/
def <+:>[B >: A](ps: =>Parsley[Seq[B]]): Parsley[Seq[B]] = lift.lift2[A, Seq[B], Seq[B]](_ +: _, p, ps)
/**This parser corresponds to `lift2(_::_, p, ps)`.*/
Expand Down Expand Up @@ -181,6 +195,22 @@ object Parsley
* @since 1.7
*/
def collect[B](pf: PartialFunction[A, B]): Parsley[B] = this.filter(pf.isDefinedAt).map(pf)
/** Attempts to first filter the parser to ensure that `pf` is defined over it. If it is, then the function `pf`
* is mapped over its result. Roughly the same as a `guard` then a `map`.
* @param pf The partial function
* @param msg The message used for the error if the input failed the check
* @return The result of applying `pf` to this parsers value (if possible), or fails
* @since 1.7
*/
def collectMsg[B](msg: String)(pf: PartialFunction[A, B]): Parsley[B] = this.guard(pf.isDefinedAt(_), msg).map(pf)
/** Attempts to first filter the parser to ensure that `pf` is defined over it. If it is, then the function `pf`
* is mapped over its result. Roughly the same as a `guard` then a `map`.
* @param pf The partial function
* @param msggen Generator function for error message, generating a message based on the result of the parser
* @return The result of applying `pf` to this parsers value (if possible), or fails
* @since 1.7
*/
def collectMsg[B](msggen: A => String)(pf: PartialFunction[A, B]): Parsley[B] = this.guard(pf.isDefinedAt(_), msggen).map(pf)
/** Similar to `filter`, except the error message desired is also provided. This allows you to name the message
* itself.
* @param pred The predicate that is tested against the parser result
Expand Down
22 changes: 19 additions & 3 deletions src/test/scala/parsley/CoreTests.scala
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ class CoreTests extends ParsleyTest {
case Failure(err) => err should startWith ("(line 1, column 2)")
case _ =>
}
res = ('a' *> 'b').runParser("bc")
res = ('a' ~> 'b').runParser("bc")
res shouldBe a [Failure]
res match {
case Failure(err) => err should startWith ("(line 1, column 1)")
Expand All @@ -54,7 +54,7 @@ class CoreTests extends ParsleyTest {
}

they must "not consume any input" in {
(pure('a') *> 'a').runParser("a") should be (Success('a'))
(pure('a') <~ 'a').runParser("a") should be (Success('a'))
}

// APPLICATIVE LAWS
Expand Down Expand Up @@ -256,13 +256,29 @@ class CoreTests extends ParsleyTest {
}

"the collect combinator" should "act like a filter then a map" in {
val p = anyChar.collect {
val p = anyChar.collect[Int] {
case '+' => 0
case c if c.isUpper => c - 'A' + 1
}
p.runParser("+") shouldBe Success(0)
p.runParser("C") shouldBe Success(3)
p.runParser("a") shouldBe a [Failure]

val q = anyChar.collectMsg("oops") {
case '+' => 0
case c if c.isUpper => c - 'A' + 1
}
q.runParser("+") shouldBe Success(0)
q.runParser("C") shouldBe Success(3)
q.runParser("a") shouldBe Failure("(line 1, column 2):\n oops")

val r = anyChar.collectMsg(c => s"$c is not appropriate") {
case '+' => 0
case c if c.isUpper => c - 'A' + 1
}
r.runParser("+") shouldBe Success(0)
r.runParser("C") shouldBe Success(3)
r.runParser("a") shouldBe Failure("(line 1, column 2):\n a is not appropriate")
}

"the cast combinator" should "allow for casts to valid types" in {
Expand Down

0 comments on commit d29d318

Please sign in to comment.