Skip to content

Commit

Permalink
spartanz#26 - Syntax and examples for sequential composition of codecs
Browse files Browse the repository at this point in the history
  • Loading branch information
sergei-shabanau committed Feb 12, 2019
1 parent 590e326 commit 48537f6
Show file tree
Hide file tree
Showing 3 changed files with 94 additions and 13 deletions.
2 changes: 1 addition & 1 deletion src/main/scala/scalaz/parsers/ParserSyntax.scala
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ trait ParserSyntax[P[_], F[_], G[_]] {

def delay[A](pa: => P[A]): P[A]

implicit class ParserOps[A](p: P[A]) {
implicit final class ParserOps[A](p: P[A]) {

def /\ [B](other: P[B])(implicit P: ProductFunctor[P]): P[A /\ B] =
and(p, other)
Expand Down
41 changes: 39 additions & 2 deletions src/main/scala/scalaz/parsers/Parsing.scala
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ sealed trait Parsing[F[_], G[_]] {
}

trait EquivSyntax {
implicit class ToEquivOps[A, B](self: Equiv[A, B]) {
implicit final class ToEquivOps[A, B](self: Equiv[A, B]) {

def >>> [C](that: Equiv[B, C]): Equiv[A, C] =
Equiv(
Expand Down Expand Up @@ -175,6 +175,15 @@ sealed trait Parsing[F[_], G[_]] {
case class Codec[I, A](eq: Equiv[I, (I, A)]) { self =>
import syntax._

def parse(i: I): F[(I, A)] =
eq.to(i)

def print(a: A, initial: I): G[I] =
eq.from(initial -> a)

def print0(a: A)(implicit M: Monoid[I]): G[I] =
eq.from(M.mempty -> a)

// ProductFunctor functionality
def ~ [B](that: Codec[I, B]): Codec[I, (A, B)] =
Codec(
Expand Down Expand Up @@ -207,7 +216,7 @@ sealed trait Parsing[F[_], G[_]] {
)
)

// IsoFunctor functionality
// IsoMonad functionality
def [B](equiv: Equiv[A, B]): Codec[I, B] =
Codec(
self.eq >>> Equiv[(I, A), (I, B)](
Expand All @@ -228,4 +237,32 @@ sealed trait Parsing[F[_], G[_]] {
def pure[I, A](a: A): Codec[I, A] =
Codec(Equiv.lift[I, (I, A)]((_, a), _._1))
}

implicit final class CodecChainSyntax[I, A](self: Codec[I, A]) {
// I => (I, A) then A => (A, B) is
// I => ((I, A), B)
// cannot be expressed with Codec (I position is defined by Equiv)

// A => (A, B)
// cannot be implemented as Codec (input must be I)

// I => (I, (A, B))
// possible but clashes with product in return type

def andThen[B](other: Codec[A, B]): Codec[I, (A, B)] =
Codec(
new Equiv[I, (I, (A, B))] {
override def to: I => F[(I, (A, B))] =
AB.compose[I, (I, A), (I, (A, B))](
AB.second[A, (A, B), I](other.eq.to),
self.eq.to
)
override def from: ((I, (A, B))) => G[I] =
BA.compose[(I, (A, B)), (I, A), I](
self.eq.from,
BA.second[(A, B), A, I](other.eq.from)
)
}
)
}
}
64 changes: 54 additions & 10 deletions src/test/scala/scalaz/parsers/SimplestCodecSpec.scala
Original file line number Diff line number Diff line change
Expand Up @@ -64,12 +64,12 @@ class SimplestCodecSpec extends Specification {
{ case Sum(e1, e2) => e1 -> ('+' -> e2) }
)

type C[A] = Codec[List[Char], A]
type C[A] = Codec[String, A]

val char: C[Char] = Codec(
Equiv.liftF(
{ case h :: t => Some(t -> h); case Nil => None },
{ case (l, c) => Some(c :: l) }
s => s.headOption.map(s.drop(1) -> _),
{ case (s, c) => Some(s + c) }
)
)

Expand All @@ -88,11 +88,11 @@ class SimplestCodecSpec extends Specification {
lazy val expression: C[Expression] = case1
}

def parse(s: String): Option[(List[Char], Syntax.Expression)] =
Example.expression.eq.to(s.toCharArray.toList)
def parse(s: String): Option[(String, Syntax.Expression)] =
Example.expression.parse(s)

def print(e: Syntax.Expression): Option[String] =
Example.expression.eq.from((Nil, e)).map(_.reverse.toArray).map(String.valueOf)
Example.expression.print0(e)

"Simplest parser" should {
import Syntax._
Expand All @@ -102,11 +102,11 @@ class SimplestCodecSpec extends Specification {
}

"parse a digit into a literal" in {
parse("5") must_=== Some(Nil -> Constant(5))
parse("5") must_=== Some("" -> Constant(5))
}

"not parse two digits (because it's indeed simplest)" in {
parse("55") must_=== Some(List('5') -> Constant(5))
parse("55") must_=== Some("5" -> Constant(5))
}

"not parse a letter and indicate failure" in {
Expand All @@ -118,11 +118,11 @@ class SimplestCodecSpec extends Specification {
}

"parse sum of 2 numbers" in {
parse("5+6") must_=== Some(Nil -> Sum(Constant(5), Constant(6)))
parse("5+6") must_=== Some("" -> Sum(Constant(5), Constant(6)))
}

"parse sum of 3 numbers" in {
parse("5+6+7") must_=== Some(Nil -> Sum(Sum(Constant(5), Constant(6)), Constant(7)))
parse("5+6+7") must_=== Some("" -> Sum(Sum(Constant(5), Constant(6)), Constant(7)))
}
}

Expand All @@ -147,4 +147,48 @@ class SimplestCodecSpec extends Specification {
print(Sum(Sum(Constant(1), Constant(2)), Sum(Constant(3), Constant(4)))) must_=== None
}
}

object ExampleCodecComposition {
import Syntax._
import Example._
import parsing.Codec
import parsing.Equiv

val expressionToInt: Codec[Expression, Int] = Codec[Expression, Int](
Equiv.liftF(
e => Some((e, 1)),
{ case (e, i) => Some(Sum(e, Constant(i))) }
)
)
}

"Running chain of codecs" >> {
import Syntax._
import Example._
import ExampleCodecComposition._

"codec composition" in {
import parsing.CodecChainSyntax // special import required
val chain: C[(Syntax.Expression, Int)] = expression.andThen(expressionToInt)

chain.parse("567") must_=== Some(("67", (Constant(5), 1)))
chain.print((Constant(765), 1), "") must_=== Some("7+1")
}

"parser composition with Monad[F]" in {
val res = for {
(r1, exp) <- expression.parse("567")
(r2, int) <- expressionToInt.parse(exp)
} yield ((r1, r2), int)
res must_=== Some((("67", Constant(5)), 1))
}

"printer composition with Monad[G]" in {
val res = for {
exp <- expressionToInt.print(1, Constant(0))
str <- expression.print(exp, "")
} yield str
res must_=== Some("0+1")
}
}
}

0 comments on commit 48537f6

Please sign in to comment.