From fb4390230743d9403711ac73696e7a52caad25f9 Mon Sep 17 00:00:00 2001 From: sergei-shabanau Date: Wed, 25 Mar 2020 15:39:26 -0400 Subject: [PATCH 1/2] #75 - Eliminate creation of nested tuples --- .../org/spartanz/parserz/ParsersModule.scala | 39 ++++++ .../org/spartanz/parserz/prototype/T2.scala | 131 ++++++++++++++++++ 2 files changed, 170 insertions(+) create mode 100644 src/test/scala/org/spartanz/parserz/prototype/T2.scala diff --git a/src/main/scala/org/spartanz/parserz/ParsersModule.scala b/src/main/scala/org/spartanz/parserz/ParsersModule.scala index 028568d..b6e21f0 100644 --- a/src/main/scala/org/spartanz/parserz/ParsersModule.scala +++ b/src/main/scala/org/spartanz/parserz/ParsersModule.scala @@ -111,6 +111,7 @@ trait ParsersModule extends ExprModule { private[parserz] case class MapES[SI, SO, E, A, B](value: Grammar[SI, SO, E, A], fe: SI => (SO, E), to: A => Option[B], from: B => Option[A]) extends Grammar[SI, SO, E, B] private[parserz] case class Filter[SI, SO, E, A](value: Grammar[SI, SO, E, A], e: E, filter: Expr[A]) extends Grammar[SI, SO, E, A] private[parserz] case class FilterES[SI, SO, E, A](value: Grammar[SI, SO, E, A], fe: SI => (SO, E), filter: Expr[A]) extends Grammar[SI, SO, E, A] + private[parserz] case class ZipUnsafe[SI, SO, E](gs: Array[Grammar[SI, SO, E, Any]]) extends Grammar[SI, SO, E, Array[Any]] private[parserz] case class Zip[SI, SO, E, A, B](left: Grammar[SI, SO, E, A], right: Grammar[SI, SO, E, B]) extends Grammar[SI, SO, E, A /\ B] private[parserz] case class ZipL[SI, SO, E, A, B](left: Grammar[SI, SO, E, A], right: Grammar[SI, SO, E, B], b: B) extends Grammar[SI, SO, E, A] private[parserz] case class ZipR[SI, SO, E, A, B](left: Grammar[SI, SO, E, A], right: Grammar[SI, SO, E, B], a: A) extends Grammar[SI, SO, E, B] @@ -283,6 +284,24 @@ trait ParsersModule extends ExprModule { } } + case Grammar.GADT.ZipUnsafe(gs) => + (s: S, in: Input) => { + val size = gs.length + val arr: Array[Any] = Array.ofDim(size) + var i = 0 + var state: S = s + var input: Input = in + var res: E \/ (Input, Array[Any]) = Right((input, arr)) + while (i < size && res.isRight) { + val (s1, res1): (S, E \/ (Input, Any)) = parser(gs(i))(state, input) + res1.foreach { case (i1, v1) => input = i1; arr(i) = v1 } + state = s1 + res = res1.map(_ => (input, arr)) + i += 1 + } + (state, res) + } + case zip: Grammar.GADT.Zip[S, S, E, ta, tb] => (s: S, i: Input) => { val (s1, res1): (S, E \/ (Input, ta)) = parser(zip.left)(s, i) @@ -439,6 +458,24 @@ trait ParsersModule extends ExprModule { } } + case Grammar.GADT.ZipUnsafe(gs) => + (s: S, in: (Input, Array[Any])) => { + val arr = in._2 + val size = arr.length + var i = 0 + var state: S = s + var input = in._1 + var res: E \/ Input = Right(input) + while (i < size && res.isRight) { + val (s1: S, res1: Either[E, Input]) = printer(gs(i))(state, (input, arr(i))) + state = s1 + res = res1 + res.foreach(input = _) + i += 1 + } + (state, res) + } + case zip: Grammar.GADT.Zip[S, S, E, ta, tb] => (s: S, in: (Input, ta /\ tb)) => { val (i, (a, b)) = in @@ -548,6 +585,7 @@ trait ParsersModule extends ExprModule { case Grammar.GADT.MapES(value, _, _, _) => tagOrExpand(value) case Grammar.GADT.Filter(v, _, expr) => Some(exprBNF(expr)).filter(_.nonEmpty).getOrElse(tagOrExpand(v)) case Grammar.GADT.FilterES(v, _, expr) => Some(exprBNF(expr)).filter(_.nonEmpty).getOrElse(tagOrExpand(v)) + case Grammar.GADT.ZipUnsafe(gs) => gs.map(tagOrExpand).mkString(" ") case Grammar.GADT.Zip(left, right) => tagOrExpand(left) + " " + tagOrExpand(right) case Grammar.GADT.ZipL(left, right, _) => tagOrExpand(left) + " " + tagOrExpand(right) case Grammar.GADT.ZipR(left, right, _) => tagOrExpand(left) + " " + tagOrExpand(right) @@ -576,6 +614,7 @@ trait ParsersModule extends ExprModule { case Grammar.GADT.MapES(value, _, _, _) => show(value) case Grammar.GADT.Filter(value, _, _) => show(value) case Grammar.GADT.FilterES(value, _, _) => show(value) + case Grammar.GADT.ZipUnsafe(gs) => gs.toList.flatMap(show) case Grammar.GADT.Zip(left, right) => show(left) ::: show(right) case Grammar.GADT.ZipL(left, right, _) => show(left) ::: show(right) case Grammar.GADT.ZipR(left, right, _) => show(left) ::: show(right) diff --git a/src/test/scala/org/spartanz/parserz/prototype/T2.scala b/src/test/scala/org/spartanz/parserz/prototype/T2.scala new file mode 100644 index 0000000..9e17cab --- /dev/null +++ b/src/test/scala/org/spartanz/parserz/prototype/T2.scala @@ -0,0 +1,131 @@ +package org.spartanz.parserz.prototype + +import org.spartanz.parserz.ParsersModule + +object T2 { + + object Module extends ParsersModule { + override type Input = List[Char] + } + + import Module.Grammar._ + import Module._ + import Tools._ + + type S = String + type E = String + type G[A] = Grammar[S, S, E, A] + + val one: G[Int] = "one" @@ consumePure(i => i.tail -> i.head.toString.toInt, { case (i, a) => i ::: a.toString.toList }) + val two: G[Boolean] = "two" @@ consumePure(i => i.tail -> (i.head == 'T'), { case (i, a) => i ::: (if (a) List('T') else List('F')) }) + val thr: G[String] = "thr" @@ succeed("blah") + + case class Thing(idx: Int, exists: Boolean, name: String) + + implicit val thingEquiv: Equiv[((Int, Boolean), String), Thing] = + Equiv.caseClass3((a, b, c) => Thing.apply(a, b, c), t => Thing.unapply(t)) + + val g1: G[((Int, Boolean), String)] = one ~ two ~ thr + + val g2: G[Thing] = toZDirect(g1) + val g3: G[Thing] = toZ(g1) + + + object Tools { + import Hacks._ + + sealed trait Equiv[A, Repr] + case class Eq3[Z, A, B, C](f: (A, B, C) => Z, g: Z => Option[(A, B, C)]) extends Equiv[((A, B), C), Z] + + object Equiv { +// def caseClass1[Z, A](f: A => Z, g: Z => Option[A]): Equiv[A, Z] = ??? +// def caseClass2[Z, A, B](f: (A, B) => Z, g: Z => Option[(A, B)]): Equiv[(A, B), Z] = ??? + def caseClass3[Z, A, B, C](f: (A, B, C) => Z, g: Z => Option[(A, B, C)]): Equiv[((A, B), C), Z] = Eq3(f, g) + } + + // using Equiv with prior optimization of Zip operations + def toZ[A, AA, Z](g: G[A])(implicit equiv: Equiv[AA, Z], ev: AA <:< A): G[Z] = { + val g1: Option[GADT.ZipUnsafe[S, S, E]] = equiv match { + case Eq3(_, _) => zippy(3)(g).map(l => GADT.ZipUnsafe(l.toArray)) + } +// g1.fold { +// toZDirect(g)(equiv, ev) +// ??? +// } { g2 => + equiv match { + case eq: Eq3[Z, ta, tb, tc] => + GADT.Map[S, S, E, Array[Any], Z]( + g1.get, + arr => { + val Array(a: ta, b: tb, c: tc) = arr + Right(eq.f(a, b, c)) + }, + z => { + eq.g(z).map { case (a, b, c) => Array(a, b, c) }.toRight("some error") + } + ) + } +// } + } + + // using Equiv without any optimization of execution plan + def toZDirect[A, AA, Z](g: G[A])(implicit equiv: Equiv[AA, Z], ev: AA <:< A): G[Z] = + equiv match { + case eq: Eq3[Z, ta, tb, tc] => GADT.Map[S, S, E, A, Z](g, + { case ((a: ta, b: tb), c: tc) => + Right(eq.f(a, b, c)) + }, + z => { + eq.g(z).map { case (a, b, c) => val a0: A = ((a, b), c); a0 }.toRight("some error") + } + ) + } + } + + // place where we loose types + object Hacks { + def zippy[A](size: Int)(g: G[A]): Option[List[G[Any]]] = { + + @scala.annotation.tailrec + def step[AA](i: Int)(acc: List[G[Any]])(g: G[AA]): List[G[Any]] = + g match { +// case GADT.ZipL(left, right, b) => +// case GADT.ZipR(left, right, a) => + case GADT.Zip(l, r) if i > 0 => + step(i - 1)(r.asInstanceOf[G[Any]] :: acc)(l) + case g if i > 0 => + g.asInstanceOf[G[Any]] :: acc + case _ => + acc + } + + val list = step(size)(Nil)(g) + if (list.length == size) Some(list) else None + } + } + + def main(args: Array[String]): Unit = { + println(parser(g1)("", List('1', 'T'))) + println() + println(parser(g2)("", List('1', 'T'))) + println() + println(parser(g3)("", List('1', 'T'))) + println() + + println() + println(printer(g1)("", (Nil, ((2, true), "printed")))) + println() + println(printer(g2)("", (Nil, Thing(2, true, "printed")))) + println() + println(printer(g3)("", (Nil, Thing(2, true, "printed")))) + println() + + println() + println(bnf("g1" @@ g1).mkString("\n")) + println() + println(bnf("g2" @@ g2).mkString("\n")) + println() + println(bnf("g3" @@ g3).mkString("\n")) + println() + } +} From 7139c688d36089206fe307e6f8104d3d0bb69913 Mon Sep 17 00:00:00 2001 From: sergei-shabanau Date: Sat, 28 Mar 2020 13:21:56 -0400 Subject: [PATCH 2/2] #75 - Refactored code and used in JSON example --- .../parserz/compare/ParserzJsonTest.scala | 23 ++-- .../spartanz/parserz/prototype/P2Module.scala | 103 ++++++++++++++++++ .../org/spartanz/parserz/prototype/T2.scala | 85 +-------------- 3 files changed, 120 insertions(+), 91 deletions(-) create mode 100644 src/test/scala/org/spartanz/parserz/prototype/P2Module.scala diff --git a/src/test/scala/org/spartanz/parserz/compare/ParserzJsonTest.scala b/src/test/scala/org/spartanz/parserz/compare/ParserzJsonTest.scala index 9ff0673..6dd61cf 100644 --- a/src/test/scala/org/spartanz/parserz/compare/ParserzJsonTest.scala +++ b/src/test/scala/org/spartanz/parserz/compare/ParserzJsonTest.scala @@ -1,6 +1,7 @@ package org.spartanz.parserz.compare -import org.spartanz.parserz.{ParsersModule, \/} +import org.spartanz.parserz.\/ +import org.spartanz.parserz.prototype.P2Module object ParserzJsonTest { @@ -18,19 +19,18 @@ object ParserzJsonTest { } - object Parser extends ParsersModule { + object Parser extends P2Module { override type Input = List[Char] + override type S = Unit + override type E = String } import Parser.Grammar._ + import Parser.Tools._ import Parser.Expr._ import Parser._ import Js._ - type S = Unit - type E = String - type G[A] = Grammar[Any, Nothing, E, A] - def char(c: Char): G[Char] = consume( cs => if (cs.nonEmpty && cs.head == c) Right((cs.tail, c)) else Left("expected: " + c), { case (cs, _) => Right(c :: cs) } @@ -73,10 +73,13 @@ object ParserzJsonTest { val fractional: G[List[Char]] = (dot, '.') ~> digits val integral: G[List[Char]] = digits - val num: G[Num] = (sign ~ integral ~ fractional.orEmpty ~ exponent.orEmpty).map( - { case (((s, l1), l2), l3) => Num((s.mkString + l1.mkString + l2.mkString + l3.mkString).toDouble) }, - { case Num(_) => ??? } - ) + implicit val numEquiv: Equiv[(((Option[Char], List[Char]), List[Char]), List[Char]), Num] = + Equiv.caseClass4( + { case (s, l1, l2, l3) => Num((s.mkString + l1.mkString + l2.mkString + l3.mkString).toDouble) }, + { case Num(_) => ??? } + ) + + val num: G[Num] = toZ(sign ~ integral ~ fractional.orEmpty ~ exponent.orEmpty) val `null`: G[Null.type] = token("null".toList).map( _ => Null, _ => "null".toList) val `false`: G[False.type] = token("false".toList).map(_ => False, _ => "false".toList) diff --git a/src/test/scala/org/spartanz/parserz/prototype/P2Module.scala b/src/test/scala/org/spartanz/parserz/prototype/P2Module.scala new file mode 100644 index 0000000..57b311e --- /dev/null +++ b/src/test/scala/org/spartanz/parserz/prototype/P2Module.scala @@ -0,0 +1,103 @@ +package org.spartanz.parserz.prototype + +import org.spartanz.parserz.ParsersModule + +trait P2Module extends ParsersModule { + import Grammar._ + + type S + type E + type G[A] = Grammar[S, S, E, A] + + object Tools { + import Hacks._ + + sealed trait Equiv[A, Repr] + case class Equiv3[Z, A, B, C](f: (A, B, C) => Z, g: Z => (A, B, C)) extends Equiv[((A, B), C), Z] + case class Equiv4[Z, A, B, C, D](f: (A, B, C, D) => Z, g: Z => (A, B, C, D)) extends Equiv[(((A, B), C), D), Z] + + object Equiv { +// def caseClass1[Z, A](f: A => Z, g: Z => Option[A]): Equiv[A, Z] = ??? +// def caseClass2[Z, A, B](f: (A, B) => Z, g: Z => Option[(A, B)]): Equiv[(A, B), Z] = ??? + def caseClass3[Z, A, B, C](f: (A, B, C) => Z, g: Z => (A, B, C)): Equiv[((A, B), C), Z] = Equiv3(f, g) + def caseClass4[Z, A, B, C, D](f: (A, B, C, D) => Z, g: Z => (A, B, C, D)): Equiv[(((A, B), C), D), Z] = Equiv4(f, g) + } + + // using Equiv with prior optimization of Zip operations + def toZ[A, AA, Z](g: G[A])(implicit equiv: Equiv[AA, Z], ev: AA <:< A): G[Z] = { + val g1: Option[GADT.ZipUnsafe[S, S, E]] = equiv match { + case Equiv3(_, _) => zippy(3)(g).map(l => GADT.ZipUnsafe(l.toArray)) + case Equiv4(_, _) => zippy(4)(g).map(l => GADT.ZipUnsafe(l.toArray)) + } +// g1.fold { +// toZDirect(g)(equiv, ev) +// ??? +// } { g2 => + // place where we cast types back at runtime + equiv match { + case equiv: Equiv3[Z, ta, tb, tc] => GADT.Map[S, S, E, Array[Any], Z](g1.get, + arr => { + val Array(a: ta, b: tb, c: tc) = arr + Right(equiv.f(a, b, c)) + }, + z => { + val (a: ta, b: tb, c: tc) = equiv.g(z) + Right(Array(a, b, c)) + } + ) + case equiv: Equiv4[Z, ta, tb, tc, td] => GADT.Map[S, S, E, Array[Any], Z](g1.get, + arr => { + val Array(a: ta, b: tb, c: tc, d: td) = arr + Right(equiv.f(a, b, c, d)) + }, + z => { + val (a: ta, b: tb, c: tc, d: td) = equiv.g(z) + Right(Array(a, b, c, d)) + } + ) + } +// } + } + + // using Equiv without any optimization of execution plan + def toZDirect[A, AA, Z](g: G[A])(implicit equiv: Equiv[AA, Z], ev: AA <:< A): G[Z] = + equiv match { + case equiv: Equiv3[Z, ta, tb, tc] => GADT.Map[S, S, E, A, Z](g, + { case ((a: ta, b: tb), c: tc) => Right(equiv.f(a, b, c)) }, + z => { + val (a: ta, b: tb, c: tc) = equiv.g(z) + Right(((a, b), c)) + } + ) + case equiv: Equiv4[Z, ta, tb, tc, td] => GADT.Map[S, S, E, A, Z](g, + { case (((a: ta, b: tb), c: tc), d: td) => Right(equiv.f(a, b, c, d)) }, + z => { + val (a: ta, b: tb, c: tc, d: td) = equiv.g(z) + Right((((a, b), c), d)) + } + ) + } + } + + // place where we loose types + object Hacks { + def zippy[A](size: Int)(g: G[A]): Option[List[G[Any]]] = { + + @scala.annotation.tailrec + def step[AA](i: Int)(acc: List[G[Any]])(g: G[AA]): List[G[Any]] = + g match { +// case GADT.ZipL(left, right, b) => +// case GADT.ZipR(left, right, a) => + case GADT.Zip(l, r) if i > 0 => + step(i - 1)(r.asInstanceOf[G[Any]] :: acc)(l) + case g if i > 0 => + g.asInstanceOf[G[Any]] :: acc + case _ => + acc + } + + val list = step(size)(Nil)(g) + if (list.length == size) Some(list) else None + } + } +} diff --git a/src/test/scala/org/spartanz/parserz/prototype/T2.scala b/src/test/scala/org/spartanz/parserz/prototype/T2.scala index 9e17cab..dde22da 100644 --- a/src/test/scala/org/spartanz/parserz/prototype/T2.scala +++ b/src/test/scala/org/spartanz/parserz/prototype/T2.scala @@ -1,21 +1,17 @@ package org.spartanz.parserz.prototype -import org.spartanz.parserz.ParsersModule - object T2 { - object Module extends ParsersModule { + object Module extends P2Module { override type Input = List[Char] + override type S = String + override type E = String } import Module.Grammar._ import Module._ import Tools._ - type S = String - type E = String - type G[A] = Grammar[S, S, E, A] - val one: G[Int] = "one" @@ consumePure(i => i.tail -> i.head.toString.toInt, { case (i, a) => i ::: a.toString.toList }) val two: G[Boolean] = "two" @@ consumePure(i => i.tail -> (i.head == 'T'), { case (i, a) => i ::: (if (a) List('T') else List('F')) }) val thr: G[String] = "thr" @@ succeed("blah") @@ -23,7 +19,7 @@ object T2 { case class Thing(idx: Int, exists: Boolean, name: String) implicit val thingEquiv: Equiv[((Int, Boolean), String), Thing] = - Equiv.caseClass3((a, b, c) => Thing.apply(a, b, c), t => Thing.unapply(t)) + Equiv.caseClass3(Thing.apply, Thing.unapply(_).get) val g1: G[((Int, Boolean), String)] = one ~ two ~ thr @@ -31,79 +27,6 @@ object T2 { val g3: G[Thing] = toZ(g1) - object Tools { - import Hacks._ - - sealed trait Equiv[A, Repr] - case class Eq3[Z, A, B, C](f: (A, B, C) => Z, g: Z => Option[(A, B, C)]) extends Equiv[((A, B), C), Z] - - object Equiv { -// def caseClass1[Z, A](f: A => Z, g: Z => Option[A]): Equiv[A, Z] = ??? -// def caseClass2[Z, A, B](f: (A, B) => Z, g: Z => Option[(A, B)]): Equiv[(A, B), Z] = ??? - def caseClass3[Z, A, B, C](f: (A, B, C) => Z, g: Z => Option[(A, B, C)]): Equiv[((A, B), C), Z] = Eq3(f, g) - } - - // using Equiv with prior optimization of Zip operations - def toZ[A, AA, Z](g: G[A])(implicit equiv: Equiv[AA, Z], ev: AA <:< A): G[Z] = { - val g1: Option[GADT.ZipUnsafe[S, S, E]] = equiv match { - case Eq3(_, _) => zippy(3)(g).map(l => GADT.ZipUnsafe(l.toArray)) - } -// g1.fold { -// toZDirect(g)(equiv, ev) -// ??? -// } { g2 => - equiv match { - case eq: Eq3[Z, ta, tb, tc] => - GADT.Map[S, S, E, Array[Any], Z]( - g1.get, - arr => { - val Array(a: ta, b: tb, c: tc) = arr - Right(eq.f(a, b, c)) - }, - z => { - eq.g(z).map { case (a, b, c) => Array(a, b, c) }.toRight("some error") - } - ) - } -// } - } - - // using Equiv without any optimization of execution plan - def toZDirect[A, AA, Z](g: G[A])(implicit equiv: Equiv[AA, Z], ev: AA <:< A): G[Z] = - equiv match { - case eq: Eq3[Z, ta, tb, tc] => GADT.Map[S, S, E, A, Z](g, - { case ((a: ta, b: tb), c: tc) => - Right(eq.f(a, b, c)) - }, - z => { - eq.g(z).map { case (a, b, c) => val a0: A = ((a, b), c); a0 }.toRight("some error") - } - ) - } - } - - // place where we loose types - object Hacks { - def zippy[A](size: Int)(g: G[A]): Option[List[G[Any]]] = { - - @scala.annotation.tailrec - def step[AA](i: Int)(acc: List[G[Any]])(g: G[AA]): List[G[Any]] = - g match { -// case GADT.ZipL(left, right, b) => -// case GADT.ZipR(left, right, a) => - case GADT.Zip(l, r) if i > 0 => - step(i - 1)(r.asInstanceOf[G[Any]] :: acc)(l) - case g if i > 0 => - g.asInstanceOf[G[Any]] :: acc - case _ => - acc - } - - val list = step(size)(Nil)(g) - if (list.length == size) Some(list) else None - } - } - def main(args: Array[String]): Unit = { println(parser(g1)("", List('1', 'T'))) println()