From 681e14b3fbb22c063eb42028b0181f7843d2f246 Mon Sep 17 00:00:00 2001 From: Gabor Bakos Date: Sun, 3 Jun 2018 19:14:14 +0200 Subject: [PATCH 1/4] :sparkles: :heavy_plus_sign: Initial version with FastParse Also removed requirement for `AnyRef` in type param. --- build.sbt | 2 + .../caseyclassy/FastParseParseCaseClass.scala | 75 +++++++++++++ .../aborg0/caseyclassy/ParseCaseClass.scala | 2 +- .../caseyclassy/RegexParseCaseClass.scala | 2 +- .../aborg0/caseyclassy/FPSimpleTests.scala | 88 +++++++++++++++ .../aborg0/caseyclassy/FPTwoArgsTests.scala | 70 ++++++++++++ .../aborg0/caseyclassy/FPVarArgsTests.scala | 104 ++++++++++++++++++ .../aborg0/caseyclassy/SimpleTests.scala | 1 + 8 files changed, 342 insertions(+), 2 deletions(-) create mode 100644 src/main/scala/com/github/aborg0/caseyclassy/FastParseParseCaseClass.scala create mode 100644 src/test/scala/com/github/aborg0/caseyclassy/FPSimpleTests.scala create mode 100644 src/test/scala/com/github/aborg0/caseyclassy/FPTwoArgsTests.scala create mode 100644 src/test/scala/com/github/aborg0/caseyclassy/FPVarArgsTests.scala diff --git a/build.sbt b/build.sbt index 686da0a..c3e3157 100644 --- a/build.sbt +++ b/build.sbt @@ -103,3 +103,5 @@ resolvers ++= Seq( libraryDependencies ++= Seq( "com.chuusai" %%% "shapeless" % "2.3.3" ) + +libraryDependencies += "com.lihaoyi" %%% "fastparse" % "1.0.0" diff --git a/src/main/scala/com/github/aborg0/caseyclassy/FastParseParseCaseClass.scala b/src/main/scala/com/github/aborg0/caseyclassy/FastParseParseCaseClass.scala new file mode 100644 index 0000000..fe3ddf7 --- /dev/null +++ b/src/main/scala/com/github/aborg0/caseyclassy/FastParseParseCaseClass.scala @@ -0,0 +1,75 @@ +package com.github.aborg0.caseyclassy + +import fastparse.all._ +import java.time.{LocalDate, LocalTime} + +import fastparse.all +import shapeless._ + +import scala.reflect.runtime.universe._ + +private[caseyclassy] trait FPGenericImplementations { + implicit def longParse: FastParseParse[Long] = () => P("-".? ~ CharIn('0' to '9').rep(1)).!.map(_.toLong) + + implicit def intParse: FastParseParse[Int] = () => P("-".? ~ CharIn('0' to '9').rep(1)).!.map(_.toInt) + + implicit def shortParse: FastParseParse[Short] = () => P("-".? ~ CharIn('0' to '9').rep(1)).!.map(_.toShort) + + implicit def byteParse: FastParseParse[Byte] = () => P("-".? ~ CharIn('0' to '9').rep(1)).!.map(_.toByte) + + implicit def booleanParse: FastParseParse[Boolean] = () => P("true" | "false").!.map(_.toBoolean) + + implicit def doubleParse: FastParseParse[Double] = () => numeric.!.map(_.toDouble) + + implicit def floatParse: FastParseParse[Float] = () => numeric.!.map(_.toFloat) + + private[this] def numeric: all.Parser[String] = P("NaN" | "-".? ~ "Infinity" | + ("-".? ~ CharIn('0' to '9').rep(1) ~ + ("." ~/ CharIn('0' to '9').rep(1)).? ~ + (CharIn("eE") ~/ CharIn("+-").? ~/ CharIn('0' to '9').rep(1)).?)).! + + implicit def parseHNil: FastParseParse[HNil] = () => Pass.map(_ => HNil) + + implicit def parseCNil: FastParseParse[CNil] = () => Fail.log("CNil") + + implicit def parseProduct[Head, Tail <: HList](implicit headParse: Lazy[FastParseParse[Head]], tailParse: Lazy[FastParseParse[Tail]]): FastParseParse[Head :: Tail] = () => (headParse.value.parser() ~ ",".? ~ tailParse.value.parser()).map { case (h, t) => h :: t } + + implicit def parseCoproduct[Head: TypeTag, Tail <: Coproduct](implicit headParse: Lazy[FastParseParse[Head]], tailParse: Lazy[FastParseParse[Tail]]): FastParseParse[Head :+: Tail] = () => headParse.value.parser().map(Inl(_)) | tailParse.value.parser().map(Inr(_)) + + implicit def generic[A: TypeTag, R](implicit gen: Generic.Aux[A, R], argParse: FastParseParse[R]): FastParseParse[A] = () => { + val typeKind = implicitly[TypeTag[A]] + if (typeKind.tpe.typeSymbol.isAbstract /* Approximation of sealed trait */ ) argParse.parser().map(gen.from) else { + val name = typeKind.tpe.typeSymbol.name.toString + if (name.startsWith("Tuple")) P("(" ~/ argParse.parser() ~ ")").map(gen.from) else P(name ~ (("(" ~/ argParse.parser() ~ ")") | argParse.parser())).map(gen.from) + } + } +} + +case object FastParseParseCaseClass extends ParseCaseClass with FPGenericImplementations { + + override def to[A](input: String)(implicit parse: Lazy[Parse[A]]): A = { + parse.value.parse(input) + } + + def apply[A](implicit p: Lazy[FastParseParse[A]]): FastParseParse[A] = p.value + + //region Custom overrides of special types + implicit def stringParse: FastParseParse[String] = () => P(CharsWhile(c => c != ',' && c != ')').!) | P("").! + + implicit def timeParse: FastParseParse[LocalTime] = () => P( + CharIn('0' to '9').rep(2, "", 2) ~ ":" ~ + CharIn('0' to '9').rep(2, "", 2) ~ + (":" ~/ CharIn('0' to '9').rep(2, "", 2) ~ + ("." ~/ CharIn('0' to '9').rep(1)).?).?).!.map(LocalTime.parse(_)) + + implicit def dateParse: FastParseParse[LocalDate] = () => P(CharIn('0' to '9').rep(4, "", 4) ~ "-" ~ CharIn('0' to '9').rep(1, "", 2) ~ "-" ~ CharIn('0' to '9').rep(1, "", 2)).!.map(LocalDate.parse(_)) + + implicit def seqConverter[A](implicit parseA: FastParseParse[A]): FastParseParse[Seq[A]] = () => P(("WrappedArray" | "List" | "Vector") ~ "(" ~/ parseA.parser().rep(sep = ", ") ~ ")") + //endregion +} + +trait FastParseParse[A] extends Parse[A] { + protected[caseyclassy] def parser(): Parser[A] + + override def parse(input: String): A = parser().parse(input).fold((p, i, e) => throw new IllegalArgumentException(s"Expected: $p at position: $i"), (a, i) => a) +} \ No newline at end of file diff --git a/src/main/scala/com/github/aborg0/caseyclassy/ParseCaseClass.scala b/src/main/scala/com/github/aborg0/caseyclassy/ParseCaseClass.scala index 316f18d..884f6dc 100644 --- a/src/main/scala/com/github/aborg0/caseyclassy/ParseCaseClass.scala +++ b/src/main/scala/com/github/aborg0/caseyclassy/ParseCaseClass.scala @@ -3,7 +3,7 @@ package com.github.aborg0.caseyclassy import shapeless.Lazy trait ParseCaseClass { - def to[A <: AnyRef](input: String)(implicit parse: Lazy[Parse[A]]): A + def to[A /*<: AnyRef*/](input: String)(implicit parse: Lazy[Parse[A]]): A } trait Parse[A] { diff --git a/src/main/scala/com/github/aborg0/caseyclassy/RegexParseCaseClass.scala b/src/main/scala/com/github/aborg0/caseyclassy/RegexParseCaseClass.scala index 9c8ae69..cff6386 100644 --- a/src/main/scala/com/github/aborg0/caseyclassy/RegexParseCaseClass.scala +++ b/src/main/scala/com/github/aborg0/caseyclassy/RegexParseCaseClass.scala @@ -126,7 +126,7 @@ private[caseyclassy] trait GenericImplementations { case object RegexParseCaseClass extends ParseCaseClass with GenericImplementations { - override def to[A <: AnyRef](input: String)(implicit parse: Lazy[Parse[A]]): A = { + override def to[A/* <: AnyRef*/](input: String)(implicit parse: Lazy[Parse[A]]): A = { parse.value.parse(input) } diff --git a/src/test/scala/com/github/aborg0/caseyclassy/FPSimpleTests.scala b/src/test/scala/com/github/aborg0/caseyclassy/FPSimpleTests.scala new file mode 100644 index 0000000..8962460 --- /dev/null +++ b/src/test/scala/com/github/aborg0/caseyclassy/FPSimpleTests.scala @@ -0,0 +1,88 @@ +package com.github.aborg0.caseyclassy + +import java.time.LocalDate + +import com.github.aborg0.caseyclassy.example.{SimpleBoolean, SimpleDouble, SimpleInt, SimpleObject} +import org.scalatest.FlatSpec +import org.scalatest.prop.{TableDrivenPropertyChecks, TableFor1, TableFor2} + +class FPSimpleTests extends FlatSpec with TableDrivenPropertyChecks { + val implementations: TableFor1[ParseCaseClass] = Table("implementation", FastParseParseCaseClass) + + behavior of "FastParseParseCaseClass for simple cases" + import FastParseParseCaseClass._ + + it should "parse SimpleDouble" in { + val simpleDoubleInputs: TableFor2[ParseCaseClass, SimpleDouble] = Table( + ("implementation", "SimpleDouble"), + Seq(1d, + 0d, + .25, + -2.5, + -0d, + 7e-17, + Double.MaxValue, + Double.NaN, + Double.NegativeInfinity, + Double.PositiveInfinity).flatMap(d => + implementations.map(impl => impl -> SimpleDouble(d))): _* + ) + forAll(simpleDoubleInputs) { (impl: ParseCaseClass, input: SimpleDouble) => + assert(impl.to[SimpleDouble](input.toString) === input) + } + } + it should "parse SimpleInt" in { + val simpleIntInputs: TableFor2[ParseCaseClass, SimpleInt] = Table( + ("implementation", "SimpleInt"), + Seq(1, 0, 2, -5, -10, Int.MaxValue, Int.MinValue).flatMap(i => + implementations.map(impl => impl -> SimpleInt(i))): _* + ) + forAll(simpleIntInputs) { (impl: ParseCaseClass, input: SimpleInt) => + assert(impl.to[SimpleInt](input.toString) === input) + } + } + it should "parse Int" in { + val simpleIntInputs: TableFor2[ParseCaseClass, Int] = Table( + ("implementation", "Int"), + Seq(1, 0, 2, -5, -10, Int.MaxValue, Int.MinValue).flatMap(i => + implementations.map(impl => impl -> i)): _* + ) + forAll(simpleIntInputs) { (impl: ParseCaseClass, input: Int) => + assert(impl.to[Int](input.toString) === input) + } + } + it should "parse SimpleBoolean" in { + val simpleIntInputs: TableFor2[ParseCaseClass, SimpleBoolean] = Table( + ("implementation", "SimpleBoolean"), + Seq(false, true).flatMap(b => + implementations.map(impl => impl -> SimpleBoolean(b))): _* + ) + forAll(simpleIntInputs) { (impl: ParseCaseClass, input: SimpleBoolean) => + assert(impl.to[SimpleBoolean](input.toString) === input) + } + } + it should "parse SimpleObject" in { + forAll(implementations) { impl => assert(impl.to[SimpleObject.type](SimpleObject.toString) === SimpleObject) } + } + it should "parse options" in { + val options = Table(("implementation", "option"), (for {opt <- Seq(None, Some(4)) + impl <- implementations} yield impl -> opt): _*) + forAll(options) { (impl, input) => assert(impl.to[Option[Int]](input.toString) === input) } + } + it should "parse eithers" in { + val options = Table(("implementation", "either"), (for {either <- Seq(Left(LocalDate.of(2018, 4, 2)), Right(3.14159f)) + impl <- implementations} yield impl -> either): _*) + forAll(options) { (impl, input) => assert(impl.to[Either[LocalDate, Float]](input.toString) === input) } + } + it should "parse Tuple1s" in { + val options = Table(("implementation", "tuple1"), (for {tup1 <- Seq(Tuple1(Some(2)), Tuple1(None)) + impl <- implementations} yield impl -> tup1): _*) + forAll(options) { (impl, input) => assert(impl.to[Tuple1[Option[Int]]](input.toString) === input) } + } + + "FastParseParseCaseClass" should "support reuse" in { + val simpleBooleanParser = FastParseParseCaseClass[SimpleBoolean] + assert(simpleBooleanParser.parse("SimpleBoolean(false)") === SimpleBoolean(false)) + assert(simpleBooleanParser.parse("SimpleBoolean(true)") === SimpleBoolean(true)) + } +} diff --git a/src/test/scala/com/github/aborg0/caseyclassy/FPTwoArgsTests.scala b/src/test/scala/com/github/aborg0/caseyclassy/FPTwoArgsTests.scala new file mode 100644 index 0000000..3307220 --- /dev/null +++ b/src/test/scala/com/github/aborg0/caseyclassy/FPTwoArgsTests.scala @@ -0,0 +1,70 @@ +package com.github.aborg0.caseyclassy + +import java.time.{LocalDate, LocalTime} + +import com.github.aborg0.caseyclassy.example.TwoArgsBoolInt +import org.scalatest.FlatSpec +import org.scalatest.prop.{TableDrivenPropertyChecks, TableFor1, TableFor2} + +class FPTwoArgsTests extends FlatSpec with TableDrivenPropertyChecks { + val implementations: TableFor1[ParseCaseClass] = Table("implementation", FastParseParseCaseClass) + + import FastParseParseCaseClass._ + behavior of "FastParseParseCaseClass for two args cases" + it should "parse Tuple2[Int, LocalDate]" in { + val intDateInputs: TableFor2[ParseCaseClass, (Int, LocalDate)] = Table( + ("implementation", "(Int, LocalDate)"), + Seq(1, + 0, + -11111111, + ).flatMap(i => Seq(LocalDate.of(1970, 1, 1), LocalDate.of(2000, 12, 31)).map((i, _))).flatMap(tup => + implementations.map(impl => impl -> tup)): _* + ) + forAll(intDateInputs) { (impl: ParseCaseClass, input: (Int, LocalDate)) => + assert(impl.to[(Int, LocalDate)](input.toString) === input) + } + } + it should "parse Tuple2[Byte, LocalTime]" in { + val byteTimeInputs: TableFor2[ParseCaseClass, (Byte, LocalTime)] = Table( + ("implementation", "(Byte, LocalTime)"), + Seq(1.toByte, + 0.toByte, + Byte.MinValue, + ).flatMap(i => Seq(LocalTime.of(0, 0, 0), LocalTime.of(23, 59, 59)).map((i, _))).flatMap(tup => + implementations.map(impl => impl -> tup)): _* + ) + forAll(byteTimeInputs) { (impl: ParseCaseClass, input: (Byte, LocalTime)) => + assert(impl.to[(Byte, LocalTime)](input.toString) === input) + } + } + it should "parse Tuple2[Float, Long]" in { + val floatLongInputs: TableFor2[ParseCaseClass, (Float, Long)] = Table( + ("implementation", "(Float, Long)"), + Seq(1l, + 0l, + Long.MinValue, + Long.MaxValue, + ).flatMap(i => Seq(Float.NegativeInfinity, -3.14159f).map((_, i))).flatMap(tup => + implementations.map(impl => impl -> tup)): _* + ) + forAll(floatLongInputs) { (impl: ParseCaseClass, input: (Float, Long)) => + assert(impl.to[(Float, Long)](input.toString) === input) + } + } + + it should "parse TwoArgsBoolInt" in { + val simpleDoubleInputs: TableFor2[ParseCaseClass, TwoArgsBoolInt] = Table( + ("implementation", "TwoArgsBoolInt"), + Seq(1, + 0, + -11111111, + ).flatMap(i => Seq(true, false).map((i, _))).flatMap(tup => + implementations.map(impl => impl -> TwoArgsBoolInt(tup._2, tup._1))): _* + ) + forAll(simpleDoubleInputs) { (impl: ParseCaseClass, input: TwoArgsBoolInt) => + assert(impl.to[TwoArgsBoolInt](input.toString) === input) + } + + } + +} diff --git a/src/test/scala/com/github/aborg0/caseyclassy/FPVarArgsTests.scala b/src/test/scala/com/github/aborg0/caseyclassy/FPVarArgsTests.scala new file mode 100644 index 0000000..3a535d8 --- /dev/null +++ b/src/test/scala/com/github/aborg0/caseyclassy/FPVarArgsTests.scala @@ -0,0 +1,104 @@ +package com.github.aborg0.caseyclassy + +import com.github.aborg0.caseyclassy.example.{OnlyVarArgs, StringPlusVarArgs} +import org.scalatest.WordSpec +import org.scalatest.prop.{TableDrivenPropertyChecks, TableFor1, TableFor2} + +class FPVarArgsTests extends WordSpec with TableDrivenPropertyChecks { + val implementations: TableFor1[ParseCaseClass] = Table("implementation", FastParseParseCaseClass) + + import FastParseParseCaseClass._ + + "ParseCaseClass for variable arity arguments successfully parse" when { + "OnlyVarArgs" should { + "has empty args" in { + forAll( + Table(("implementation", "OnlyVarArgs[Int]"), implementations.map(impl => impl -> OnlyVarArgs[Int]()): _*)) { + (impl: ParseCaseClass, input: OnlyVarArgs[Int]) => + assert(impl.to[OnlyVarArgs[Int]](input.toString) === input) + } + } + "has one argument" in { + val simpleVarArgsInputs: TableFor2[ParseCaseClass, OnlyVarArgs[Int]] = Table( + ("implementation", "OnlyVarArgs[Int]"), + Seq(1, + 0, + -1, + Int.MaxValue, + Int.MinValue).flatMap(i => + implementations.map(impl => impl -> OnlyVarArgs[Int](i))): _* + ) + forAll(simpleVarArgsInputs) { (impl: ParseCaseClass, input: OnlyVarArgs[Int]) => + assert(impl.to[OnlyVarArgs[Int]](input.toString) === input) + } + } + "has two arguments" in { + val varArgsInputs: TableFor2[ParseCaseClass, OnlyVarArgs[Int]] = Table( + ("implementation", "OnlyVarArgs[Int]"), + Seq(1, + 0, + -1, + Int.MaxValue, + Int.MinValue).flatMap(i => + implementations.map(impl => impl -> OnlyVarArgs[Int](i, -i))): _* + ) + forAll(varArgsInputs) { (impl: ParseCaseClass, input: OnlyVarArgs[Int]) => + assert(impl.to[OnlyVarArgs[Int]](input.toString) === input) + } + } + "has three arguments" in { + val varArgsInputs: TableFor2[ParseCaseClass, OnlyVarArgs[Int]] = Table( + ("implementation", "OnlyVarArgs[Int]"), + Seq(1, + 0, + -1, + Int.MaxValue, + Int.MinValue).flatMap(i => + implementations.map(impl => impl -> OnlyVarArgs[Int](3, i, -i))): _* + ) + forAll(varArgsInputs) { (impl: ParseCaseClass, input: OnlyVarArgs[Int]) => + assert(impl.to[OnlyVarArgs[Int]](input.toString) === input) + } + } + } + "StringPlusVarArgs" should { + "with String + no arguments" in { + val simpleStringPlusVarArgs: TableFor2[ParseCaseClass, StringPlusVarArgs[Short]] = Table( + ("implementation", "StringPlusVarArgs[Short]"), + Seq("", "Hello", "3").flatMap(s => implementations.map(impl => impl -> StringPlusVarArgs[Short](s))): _* + ) + forAll(simpleStringPlusVarArgs) { (impl: ParseCaseClass, input: StringPlusVarArgs[Short]) => + assert(impl.to[StringPlusVarArgs[Short]](input.toString) === input) + } + } + "with String + one argument" in { + val stringPlusVarArgs: TableFor2[ParseCaseClass, StringPlusVarArgs[Short]] = Table( + ("implementation", "StringPlusVarArgs[Short]"), + Seq("", "Hello", "3").flatMap(s => Seq(1.toShort, + 0.toShort, + (-1).toShort, + Short.MaxValue, + Short.MinValue).flatMap(i => + implementations.map(impl => impl -> StringPlusVarArgs(s, i)))): _* + ) + forAll(stringPlusVarArgs) { (impl: ParseCaseClass, input: StringPlusVarArgs[Short]) => + assert(impl.to[StringPlusVarArgs[Short]](input.toString) === input) + } + } + "with String + two arguments" in { + val stringPlusVarArgs: TableFor2[ParseCaseClass, StringPlusVarArgs[Short]] = Table( + ("implementation", "StringPlusVarArgs[Short]"), + Seq("", "Hello", "3").flatMap(s => Seq(1.toShort, + 0.toShort, + (-1).toShort, + Short.MaxValue, + Short.MinValue).flatMap(i => + implementations.map(impl => impl -> StringPlusVarArgs(s, i, 2.toShort)))): _* + ) + forAll(stringPlusVarArgs) { (impl: ParseCaseClass, input: StringPlusVarArgs[Short]) => + assert(impl.to[StringPlusVarArgs[Short]](input.toString) === input) + } + } + } + } +} diff --git a/src/test/scala/com/github/aborg0/caseyclassy/SimpleTests.scala b/src/test/scala/com/github/aborg0/caseyclassy/SimpleTests.scala index 7b66da5..08b6f8f 100644 --- a/src/test/scala/com/github/aborg0/caseyclassy/SimpleTests.scala +++ b/src/test/scala/com/github/aborg0/caseyclassy/SimpleTests.scala @@ -22,6 +22,7 @@ class SimpleTests extends FlatSpec with TableDrivenPropertyChecks { .25, -2.5, -0d, + 7e-17, Double.MaxValue, Double.NaN, Double.NegativeInfinity, From cf8d73ffe8ccaded2f451ac2420c2c198f418635 Mon Sep 17 00:00:00 2001 From: Gabor Bakos Date: Thu, 14 Jun 2018 08:00:36 +0200 Subject: [PATCH 2/4] :memo: :art: Updated tut examples with FastParse, code cleanup --- .../caseyclassy/FastParseParseCaseClass.scala | 50 ++++++---- src/main/tut/Examples.md | 95 ++++++++++++++++++- 2 files changed, 124 insertions(+), 21 deletions(-) diff --git a/src/main/scala/com/github/aborg0/caseyclassy/FastParseParseCaseClass.scala b/src/main/scala/com/github/aborg0/caseyclassy/FastParseParseCaseClass.scala index fe3ddf7..d7f86f3 100644 --- a/src/main/scala/com/github/aborg0/caseyclassy/FastParseParseCaseClass.scala +++ b/src/main/scala/com/github/aborg0/caseyclassy/FastParseParseCaseClass.scala @@ -3,44 +3,52 @@ package com.github.aborg0.caseyclassy import fastparse.all._ import java.time.{LocalDate, LocalTime} -import fastparse.all import shapeless._ import scala.reflect.runtime.universe._ private[caseyclassy] trait FPGenericImplementations { - implicit def longParse: FastParseParse[Long] = () => P("-".? ~ CharIn('0' to '9').rep(1)).!.map(_.toLong) + implicit def booleanParse: FastParseParse[Boolean] = () => P("true" | "false").!.map(_.toBoolean) + + private[this] def integral: Parser[String] = P("-".? ~ CharIn('0' to '9').rep(1)).! - implicit def intParse: FastParseParse[Int] = () => P("-".? ~ CharIn('0' to '9').rep(1)).!.map(_.toInt) + private[this] def numeric: Parser[String] = P("NaN" | "-".? ~ "Infinity" | + ("-".? ~ CharIn('0' to '9').rep(1) ~ + ("." ~/ CharIn('0' to '9').rep(1)).? ~ + (CharIn("eE") ~/ CharIn("+-").? ~/ CharIn('0' to '9').rep(1)).?)).! - implicit def shortParse: FastParseParse[Short] = () => P("-".? ~ CharIn('0' to '9').rep(1)).!.map(_.toShort) + implicit def longParse: FastParseParse[Long] = () => integral.map(_.toLong) - implicit def byteParse: FastParseParse[Byte] = () => P("-".? ~ CharIn('0' to '9').rep(1)).!.map(_.toByte) + implicit def intParse: FastParseParse[Int] = () => integral.map(_.toInt) - implicit def booleanParse: FastParseParse[Boolean] = () => P("true" | "false").!.map(_.toBoolean) + implicit def shortParse: FastParseParse[Short] = () => integral.map(_.toShort) + + implicit def byteParse: FastParseParse[Byte] = () => integral.map(_.toByte) implicit def doubleParse: FastParseParse[Double] = () => numeric.!.map(_.toDouble) implicit def floatParse: FastParseParse[Float] = () => numeric.!.map(_.toFloat) - private[this] def numeric: all.Parser[String] = P("NaN" | "-".? ~ "Infinity" | - ("-".? ~ CharIn('0' to '9').rep(1) ~ - ("." ~/ CharIn('0' to '9').rep(1)).? ~ - (CharIn("eE") ~/ CharIn("+-").? ~/ CharIn('0' to '9').rep(1)).?)).! - implicit def parseHNil: FastParseParse[HNil] = () => Pass.map(_ => HNil) implicit def parseCNil: FastParseParse[CNil] = () => Fail.log("CNil") - implicit def parseProduct[Head, Tail <: HList](implicit headParse: Lazy[FastParseParse[Head]], tailParse: Lazy[FastParseParse[Tail]]): FastParseParse[Head :: Tail] = () => (headParse.value.parser() ~ ",".? ~ tailParse.value.parser()).map { case (h, t) => h :: t } + implicit def parseProduct[Head, Tail <: HList](implicit headParse: Lazy[FastParseParse[Head]], + tailParse: Lazy[FastParseParse[Tail]]): FastParseParse[Head :: Tail] = + () => (headParse.value.parser() ~ ",".? ~ tailParse.value.parser()).map { case (h, t) => h :: t } - implicit def parseCoproduct[Head: TypeTag, Tail <: Coproduct](implicit headParse: Lazy[FastParseParse[Head]], tailParse: Lazy[FastParseParse[Tail]]): FastParseParse[Head :+: Tail] = () => headParse.value.parser().map(Inl(_)) | tailParse.value.parser().map(Inr(_)) + implicit def parseCoproduct[Head, Tail <: Coproduct](implicit headParse: Lazy[FastParseParse[Head]], + tailParse: Lazy[FastParseParse[Tail]]): FastParseParse[Head :+: Tail] = + () => headParse.value.parser().map(Inl(_)) | tailParse.value.parser().map(Inr(_)) implicit def generic[A: TypeTag, R](implicit gen: Generic.Aux[A, R], argParse: FastParseParse[R]): FastParseParse[A] = () => { val typeKind = implicitly[TypeTag[A]] if (typeKind.tpe.typeSymbol.isAbstract /* Approximation of sealed trait */ ) argParse.parser().map(gen.from) else { val name = typeKind.tpe.typeSymbol.name.toString - if (name.startsWith("Tuple")) P("(" ~/ argParse.parser() ~ ")").map(gen.from) else P(name ~ (("(" ~/ argParse.parser() ~ ")") | argParse.parser())).map(gen.from) + if (name.startsWith("Tuple")) + P("(" ~/ argParse.parser() ~ ")").map(gen.from) + else + P(name ~ (("(" ~/ argParse.parser() ~ ")") | argParse.parser())).map(gen.from) } } } @@ -54,7 +62,8 @@ case object FastParseParseCaseClass extends ParseCaseClass with FPGenericImpleme def apply[A](implicit p: Lazy[FastParseParse[A]]): FastParseParse[A] = p.value //region Custom overrides of special types - implicit def stringParse: FastParseParse[String] = () => P(CharsWhile(c => c != ',' && c != ')').!) | P("").! + implicit def stringParse: FastParseParse[String] = () => + P(CharsWhile(c => c != ',' && c != ')').!) | P("").! implicit def timeParse: FastParseParse[LocalTime] = () => P( CharIn('0' to '9').rep(2, "", 2) ~ ":" ~ @@ -62,14 +71,19 @@ case object FastParseParseCaseClass extends ParseCaseClass with FPGenericImpleme (":" ~/ CharIn('0' to '9').rep(2, "", 2) ~ ("." ~/ CharIn('0' to '9').rep(1)).?).?).!.map(LocalTime.parse(_)) - implicit def dateParse: FastParseParse[LocalDate] = () => P(CharIn('0' to '9').rep(4, "", 4) ~ "-" ~ CharIn('0' to '9').rep(1, "", 2) ~ "-" ~ CharIn('0' to '9').rep(1, "", 2)).!.map(LocalDate.parse(_)) + implicit def dateParse: FastParseParse[LocalDate] = () => + P(CharIn('0' to '9').rep(4, "", 4) ~ "-" ~ + CharIn('0' to '9').rep(1, "", 2) ~ "-" ~ + CharIn('0' to '9').rep(1, "", 2)).!.map(LocalDate.parse(_)) - implicit def seqConverter[A](implicit parseA: FastParseParse[A]): FastParseParse[Seq[A]] = () => P(("WrappedArray" | "List" | "Vector") ~ "(" ~/ parseA.parser().rep(sep = ", ") ~ ")") + implicit def seqConverter[A](implicit parseA: FastParseParse[A]): FastParseParse[Seq[A]] = () => + P(("WrappedArray" | "List" | "Vector") ~ "(" ~/ parseA.parser().rep(sep = ", ") ~ ")") //endregion } trait FastParseParse[A] extends Parse[A] { protected[caseyclassy] def parser(): Parser[A] - override def parse(input: String): A = parser().parse(input).fold((p, i, e) => throw new IllegalArgumentException(s"Expected: $p at position: $i"), (a, i) => a) + override def parse(input: String): A = parser().parse(input).fold((p, i, e) => + throw new IllegalArgumentException(s"Expected: $p at position: $i"), (a, i) => a) } \ No newline at end of file diff --git a/src/main/tut/Examples.md b/src/main/tut/Examples.md index a6c659f..d73ae12 100644 --- a/src/main/tut/Examples.md +++ b/src/main/tut/Examples.md @@ -1,14 +1,14 @@ # Examples ## Intro -There might be multiple implementations of ParseCaseClass, currently only RegexParseCaseClass is supported: +There might be multiple implementations of `ParseCaseClass`, currently only `RegexParseCaseClass` and `FastParseParseCaseClass` are supported, but only one should be used in a scope (in this case `RegexParseCaseClass`): ```tut:silent import com.github.aborg0.caseyclassy.RegexParseCaseClass import com.github.aborg0.caseyclassy.RegexParseCaseClass._ import java.time.{LocalDate, LocalTime} import shapeless._ ``` -It might possible to parse simple values, like `Boolean`, `Byte`, `Short`, `Int`, `Long`, `Float`, `Double`, `LocalTime`, `LocalDate` and `String`s without comma (`,`) or closing parenthesis (`)`), but this library was designed to parse toString of algebraic data types (products -case classes, tuples- and coproducts) of them. Also some `Seq`s (`List`, `Vector`, `WrappedArray` (for varargs)) are supported. +It is possible to parse simple values, like `Boolean`, `Byte`, `Short`, `Int`, `Long`, `Float`, `Double`, `LocalTime`, `LocalDate` and `String`s without comma (`,`) or closing parenthesis (`)`), but this library was designed to parse toString of algebraic data types (products -case classes, tuples- and coproducts) of them. Also some `Seq`s (`List`, `Vector`, `WrappedArray` (for varargs)) are supported. ### Simple primitives @@ -92,4 +92,93 @@ RegexParseCaseClass.to[Option[Either[String, Seq[Boolean]]]]("Some(Right(List(fa RegexParseCaseClass.to[Option[Either[String, Seq[Boolean]]]]("Some(Left(List(false, true)))") ``` -Please note that the `String` part contains only till the first `,` (`List(false`) within and no error is reported currently. \ No newline at end of file +Please note that the `String` part contains only till the first `,` (`List(false`) within and no error is reported currently. + +# Same content with FastParse (`FastParseParseCaseClass`) + +```tut:silent:reset +``` + +## Intro +There might be multiple implementations of `ParseCaseClass`, currently only `RegexParseCaseClass` and `FastParseParseCaseClass` are supported, but only one should be used in a scope (in this case `FastParseParseCaseClass`): +```tut:silent +import com.github.aborg0.caseyclassy.FastParseParseCaseClass +import com.github.aborg0.caseyclassy.FastParseParseCaseClass._ +import java.time.{LocalDate, LocalTime} +import shapeless._ +``` +It is possible to parse simple values, like `Boolean`, `Byte`, `Short`, `Int`, `Long`, `Float`, `Double`, `LocalTime`, `LocalDate` and `String`s without comma (`,`) or closing parenthesis (`)`), but this library was designed to parse toString of algebraic data types (products -case classes, tuples- and coproducts) of them. Also some `Seq`s (`List`, `Vector`, `WrappedArray` (for varargs)) are supported. + +### Simple primitives + +`LocalDate`: + +```tut +val date: LocalDate = FastParseParseCaseClass.to[LocalDate]("2018-04-01") +``` + +or it is also possible to create a parser and reuse it: + +```tut +val dateParser = FastParseParseCaseClass[LocalDate] +dateParser.parse("2018-04-01") +dateParser.parse("2018-04-22") +``` + +Tuple2 of `String` and `Int`: + +```tut +FastParseParseCaseClass.to[(String, Int)]("( hello,4)") +``` + +Or in the other order: + +```tut +val (i, s) = FastParseParseCaseClass.to[(Int, String)]("(4, hello)") +``` + +The error messages are not very good: + +```tut:fail +val dateTuple1 = FastParseParseCaseClass.to[Tuple1[LocalDate]]("2018-04-01") +``` + +## Algebraic data types + +With help of shapeless the following constructs are supported: +- case classes +- case objects +- sealed hierarchies +- tuples +- a few `Seq` types + +### Case classes + +```tut +case class Example(a: Int, s: String) +FastParseParseCaseClass.to[Example]("Example(-3, Hello)") +``` + +```tut +case object Dot + +FastParseParseCaseClass.to[Dot.type]("Dot") +``` + +### Sealed hierarchies + +#### Either + +```tut +FastParseParseCaseClass.to[Either[Short, Boolean]]("Left(-1111)") +FastParseParseCaseClass.to[Either[Short, Boolean]]("Right(false)") +``` + +#### Option + +```tut +FastParseParseCaseClass.to[Option[Option[Int]]]("Some(None)") +FastParseParseCaseClass.to[Option[Option[Int]]]("None") +FastParseParseCaseClass.to[Option[Either[String, Seq[Boolean]]]]("Some(Right(List()))") +FastParseParseCaseClass.to[Option[Either[String, Seq[Boolean]]]]("Some(Right(List(false, true)))") +``` From 6255ed90213b8ba507e8e546bcaa28147d242237 Mon Sep 17 00:00:00 2001 From: Gabor Bakos Date: Sun, 22 Jul 2018 19:02:48 +0200 Subject: [PATCH 3/4] :sparkles: Magnolia support with unreleased fastparse feature Using features from: https://github.com/aborg0/fastparse/tree/seqseqence --- build.sbt | 4 +- .../FastParseMagnoliaParseCaseClass.scala | 130 ++++++++++++++++++ .../caseyclassy/FPMagnoliaSimpleTests.scala | 88 ++++++++++++ .../caseyclassy/FPMagnoliaTwoArgsTests.scala | 69 ++++++++++ .../caseyclassy/FPMagnoliaVarArgsTests.scala | 104 ++++++++++++++ 5 files changed, 394 insertions(+), 1 deletion(-) create mode 100644 src/main/scala/com/github/aborg0/caseyclassy/FastParseMagnoliaParseCaseClass.scala create mode 100644 src/test/scala/com/github/aborg0/caseyclassy/FPMagnoliaSimpleTests.scala create mode 100644 src/test/scala/com/github/aborg0/caseyclassy/FPMagnoliaTwoArgsTests.scala create mode 100644 src/test/scala/com/github/aborg0/caseyclassy/FPMagnoliaVarArgsTests.scala diff --git a/build.sbt b/build.sbt index c3e3157..7200f1e 100644 --- a/build.sbt +++ b/build.sbt @@ -104,4 +104,6 @@ libraryDependencies ++= Seq( "com.chuusai" %%% "shapeless" % "2.3.3" ) -libraryDependencies += "com.lihaoyi" %%% "fastparse" % "1.0.0" +libraryDependencies += "com.lihaoyi" %%% "fastparse" % "1.0.1" + +libraryDependencies += "com.propensive" %%% "magnolia" % "0.7.1" \ No newline at end of file diff --git a/src/main/scala/com/github/aborg0/caseyclassy/FastParseMagnoliaParseCaseClass.scala b/src/main/scala/com/github/aborg0/caseyclassy/FastParseMagnoliaParseCaseClass.scala new file mode 100644 index 0000000..e015fac --- /dev/null +++ b/src/main/scala/com/github/aborg0/caseyclassy/FastParseMagnoliaParseCaseClass.scala @@ -0,0 +1,130 @@ +package com.github.aborg0.caseyclassy + +import fastparse.all._ +import java.time.{LocalDate, LocalTime} + +import fastparse.core + +import language.experimental.macros +import magnolia._ + +import scala.reflect.runtime.universe._ + +private[caseyclassy] trait FPMagnoliaImplementations { + implicit def booleanParse: FastParseParse[Boolean] = () => P("true" | "false").!.map(_.toBoolean) + + private[this] def integral: Parser[String] = P("-".? ~ CharIn('0' to '9').rep(1)).! + + private[this] def numeric: Parser[String] = P("NaN" | "-".? ~ "Infinity" | + ("-".? ~ CharIn('0' to '9').rep(1) ~ + ("." ~/ CharIn('0' to '9').rep(1)).? ~ + (CharIn("eE") ~/ CharIn("+-").? ~/ CharIn('0' to '9').rep(1)).?)).! + + implicit def longParse: FastParseParse[Long] = () => integral.map(_.toLong) + + implicit def intParse: FastParseParse[Int] = () => integral.map(_.toInt) + + implicit def shortParse: FastParseParse[Short] = () => integral.map(_.toShort) + + implicit def byteParse: FastParseParse[Byte] = () => integral.map(_.toByte) + + implicit def doubleParse: FastParseParse[Double] = () => numeric.!.map(_.toDouble) + + implicit def floatParse: FastParseParse[Float] = () => numeric.!.map(_.toFloat) + +// implicit def parseHNil: FastParseParse[HNil] = () => Pass.map(_ => HNil) +// +// implicit def parseCNil: FastParseParse[CNil] = () => Fail.log("CNil") +// +// implicit def parseProduct[Head, Tail <: HList](implicit headParse: Lazy[FastParseParse[Head]], +// tailParse: Lazy[FastParseParse[Tail]]): FastParseParse[Head :: Tail] = +// () => (headParse.value.parser() ~ ",".? ~ tailParse.value.parser()).map { case (h, t) => h :: t } +// +// implicit def parseCoproduct[Head, Tail <: Coproduct](implicit headParse: Lazy[FastParseParse[Head]], +// tailParse: Lazy[FastParseParse[Tail]]): FastParseParse[Head :+: Tail] = +// () => headParse.value.parser().map(Inl(_)) | tailParse.value.parser().map(Inr(_)) +// +// implicit def generic[A: TypeTag, R](implicit gen: Generic.Aux[A, R], argParse: FastParseParse[R]): FastParseParse[A] = () => { +// val typeKind = implicitly[TypeTag[A]] +// if (typeKind.tpe.typeSymbol.isAbstract /* Approximation of sealed trait */ ) argParse.parser().map(gen.from) else { +// val name = typeKind.tpe.typeSymbol.name.toString +// if (name.startsWith("Tuple")) +// P("(" ~/ argParse.parser() ~ ")").map(gen.from) +// else +// P(name ~ (("(" ~/ argParse.parser() ~ ")") | argParse.parser())).map(gen.from) +// } +// } +} + +case object FastParseMagnoliaParseCaseClass extends MagnoliaParseCaseClass with FPMagnoliaImplementations { + type Typeclass[T] = FastParseParse[T] + + def combine[T](ctx: CaseClass[FastParseParse, T]): FastParseParse[T] = () => { + val typeKind = ctx.typeName + val typeName = typeKind.short + if (ctx.isObject) { + P(typeName).map(_ => ctx.rawConstruct(Seq.empty)) + } else { + P((if (typeName.startsWith("Tuple")) Pass else P(typeName)) ~ "(" ~ +// val argsParser = ctx.parameters.map(param => param.typeclass.parser() ~ ",".?) + //ctx.rawConstruct(argsParser) +// ctx.parameters.map(p => p.typeclass.parser()).map(v => v: Parser[Any]) +// .reduce((p1, p2) => p1.! ~",".? ~ p2.!) + sequence(ctx.parameters.map(_.typeclass.parser().map(v=> v: Any)), ",").map(seq => ctx.rawConstruct(seq)) ~ ")" + ) +// ctx.parameters.foldLeft(Pass: Parser[Any])((parser, param) => parser ~ ",".? ~ param.typeclass.parser()) +// ~ ")").map(params => ctx.rawConstruct(params.asInstanceOf[Seq[Any]])) + } +// def show(value: T): String = ctx.parameters.map { p => +// s"${p.label}=${p.typeclass.show(p.dereference(value))}" +// }.mkString("{", ",", "}") + } + + def dispatch[T](ctx: SealedTrait[FastParseParse, T]): FastParseParse[T] = new FastParseParse[T] { + override def parser(): Parser[T] = { + ctx.subtypes.map(_.typeclass.parser()).reduce[Parser[T]]((p1, p2) => p1 | p2) + // def show(value: T): String = ctx.dispatch(value) { sub => + // sub.typeclass.show(sub.cast(value)) + // } + } + } + + implicit def gen[T]: FastParseParse[T] = macro Magnolia.gen[T] + + override def to[A](input: String)(implicit parse: Parse[A]): A = { + parse.parse(input) + } + + def apply[A](implicit p: FastParseParse[A]): FastParseParse[A] = p + + //region Custom overrides of special types + implicit def stringParse: FastParseParse[String] = () => + P(CharsWhile(c => c != ',' && c != ')').!) | P("").! + + implicit def timeParse: FastParseParse[LocalTime] = () => P( + CharIn('0' to '9').rep(2, "", 2) ~ ":" ~ + CharIn('0' to '9').rep(2, "", 2) ~ + (":" ~/ CharIn('0' to '9').rep(2, "", 2) ~ + ("." ~/ CharIn('0' to '9').rep(1)).?).?).!.map(LocalTime.parse(_)) + + implicit def dateParse: FastParseParse[LocalDate] = () => + P(CharIn('0' to '9').rep(4, "", 4) ~ "-" ~ + CharIn('0' to '9').rep(1, "", 2) ~ "-" ~ + CharIn('0' to '9').rep(1, "", 2)).!.map(LocalDate.parse(_)) + + implicit def seqConverter[A](implicit parseA: FastParseParse[A]): FastParseParse[Seq[A]] = () => + P(("WrappedArray" | "List" | "Vector") ~ "(" ~/ parseA.parser().rep(sep = ", ") ~ ")") + + //endregion +} + +trait MagnoliaParseCaseClass { + def to[A /*<: AnyRef*/](input: String)(implicit parse: Parse[A]): A +} + +//trait FastParseParse[A] extends Parse[A] { +// protected[caseyclassy] def parser(): Parser[A] +// +// override def parse(input: String): A = parser().parse(input).fold((p, i, e) => +// throw new IllegalArgumentException(s"Expected: $p at position: $i"), (a, i) => a) +//} \ No newline at end of file diff --git a/src/test/scala/com/github/aborg0/caseyclassy/FPMagnoliaSimpleTests.scala b/src/test/scala/com/github/aborg0/caseyclassy/FPMagnoliaSimpleTests.scala new file mode 100644 index 0000000..7955752 --- /dev/null +++ b/src/test/scala/com/github/aborg0/caseyclassy/FPMagnoliaSimpleTests.scala @@ -0,0 +1,88 @@ +package com.github.aborg0.caseyclassy + +import java.time.LocalDate + +import com.github.aborg0.caseyclassy.example.{SimpleBoolean, SimpleDouble, SimpleInt, SimpleObject} +import org.scalatest.FlatSpec +import org.scalatest.prop.{TableDrivenPropertyChecks, TableFor1, TableFor2} + +class FPMagnoliaSimpleTests extends FlatSpec with TableDrivenPropertyChecks { + val implementations: TableFor1[FastParseMagnoliaParseCaseClass.type] = Table("implementation", FastParseMagnoliaParseCaseClass) + + behavior of "FastParseMagnoliaParseCaseClass for simple cases" + import FastParseMagnoliaParseCaseClass._ + + it should "parse SimpleDouble" in { + val simpleDoubleInputs: TableFor2[FastParseMagnoliaParseCaseClass.type, SimpleDouble] = Table( + ("implementation", "SimpleDouble"), + Seq(1d, + 0d, + .25, + -2.5, + -0d, + 7e-17, + Double.MaxValue, + Double.NaN, + Double.NegativeInfinity, + Double.PositiveInfinity).flatMap(d => + implementations.map(impl => impl -> SimpleDouble(d))): _* + ) + forAll(simpleDoubleInputs) { (impl, input) => + assert(impl.to[SimpleDouble](input.toString) === input) + } + } + it should "parse SimpleInt" in { + val simpleIntInputs = Table( + ("implementation", "SimpleInt"), + Seq(1, 0, 2, -5, -10, Int.MaxValue, Int.MinValue).flatMap(i => + implementations.map(impl => impl -> SimpleInt(i))): _* + ) + forAll(simpleIntInputs) { (impl, input: SimpleInt) => + assert(impl.to[SimpleInt](input.toString) === input) + } + } + it should "parse Int" in { + val simpleIntInputs = Table( + ("implementation", "Int"), + Seq(1, 0, 2, -5, -10, Int.MaxValue, Int.MinValue).flatMap(i => + implementations.map(impl => impl -> i)): _* + ) + forAll(simpleIntInputs) { (impl, input: Int) => + assert(impl.to[Int](input.toString) === input) + } + } + it should "parse SimpleBoolean" in { + val simpleIntInputs = Table( + ("implementation", "SimpleBoolean"), + Seq(false, true).flatMap(b => + implementations.map(impl => impl -> SimpleBoolean(b))): _* + ) + forAll(simpleIntInputs) { (impl, input: SimpleBoolean) => + assert(impl.to[SimpleBoolean](input.toString) === input) + } + } + it should "parse SimpleObject" in { + forAll(implementations) { impl => assert(impl.to[SimpleObject.type](SimpleObject.toString) === SimpleObject) } + } + it should "parse options" in { + val options = Table(("implementation", "option"), (for {opt <- Seq(None, Some(4)) + impl <- implementations} yield impl -> opt): _*) + forAll(options) { (impl, input) => assert(impl.to[Option[Int]](input.toString) === input) } + } + it should "parse eithers" in { + val options = Table(("implementation", "either"), (for {either <- Seq(Left(LocalDate.of(2018, 4, 2)), Right(3.14159f)) + impl <- implementations} yield impl -> either): _*) + forAll(options) { (impl, input) => assert(impl.to[Either[LocalDate, Float]](input.toString) === input) } + } + it should "parse Tuple1s" in { + val options = Table(("implementation", "tuple1"), (for {tup1 <- Seq(Tuple1(Some(2)), Tuple1(None)) + impl <- implementations} yield impl -> tup1): _*) + forAll(options) { (impl, input) => assert(impl.to[Tuple1[Option[Int]]](input.toString) === input) } + } + +// "FastParseParseCaseClass" should "support reuse" in { +// val simpleBooleanParser = FastParseParseCaseClass[SimpleBoolean] +// assert(simpleBooleanParser.parse("SimpleBoolean(false)") === SimpleBoolean(false)) +// assert(simpleBooleanParser.parse("SimpleBoolean(true)") === SimpleBoolean(true)) +// } +} diff --git a/src/test/scala/com/github/aborg0/caseyclassy/FPMagnoliaTwoArgsTests.scala b/src/test/scala/com/github/aborg0/caseyclassy/FPMagnoliaTwoArgsTests.scala new file mode 100644 index 0000000..8eb191a --- /dev/null +++ b/src/test/scala/com/github/aborg0/caseyclassy/FPMagnoliaTwoArgsTests.scala @@ -0,0 +1,69 @@ +package com.github.aborg0.caseyclassy + +import java.time.{LocalDate, LocalTime} + +import com.github.aborg0.caseyclassy.example.TwoArgsBoolInt +import org.scalatest.FlatSpec +import org.scalatest.prop.{TableDrivenPropertyChecks, TableFor1, TableFor2} + +class FPMagnoliaTwoArgsTests extends FlatSpec with TableDrivenPropertyChecks { + val implementations: TableFor1[FastParseMagnoliaParseCaseClass.type] = Table("implementation", FastParseMagnoliaParseCaseClass) + behavior of "FastParseMagnoliaParseCaseClass for two args cases" + import FastParseMagnoliaParseCaseClass._ + it should "parse Tuple2[Int, LocalDate]" in { + val intDateInputs = Table( + ("implementation", "(Int, LocalDate)"), + Seq(1, + 0, + -11111111, + ).flatMap(i => Seq(LocalDate.of(1970, 1, 1), LocalDate.of(2000, 12, 31)).map((i, _))).flatMap(tup => + implementations.map(impl => impl -> tup)): _* + ) + forAll(intDateInputs) { (impl, input: (Int, LocalDate)) => + assert(impl.to[(Int, LocalDate)](input.toString) === input) + } + } + it should "parse Tuple2[Byte, LocalTime]" in { + val byteTimeInputs = Table( + ("implementation", "(Byte, LocalTime)"), + Seq(1.toByte, + 0.toByte, + Byte.MinValue, + ).flatMap(i => Seq(LocalTime.of(0, 0, 0), LocalTime.of(23, 59, 59)).map((i, _))).flatMap(tup => + implementations.map(impl => impl -> tup)): _* + ) + forAll(byteTimeInputs) { (impl, input: (Byte, LocalTime)) => + assert(impl.to[(Byte, LocalTime)](input.toString) === input) + } + } + it should "parse Tuple2[Float, Long]" in { + val floatLongInputs = Table( + ("implementation", "(Float, Long)"), + Seq(1l, + 0l, + Long.MinValue, + Long.MaxValue, + ).flatMap(i => Seq(Float.NegativeInfinity, -3.14159f).map((_, i))).flatMap(tup => + implementations.map(impl => impl -> tup)): _* + ) + forAll(floatLongInputs) { (impl, input: (Float, Long)) => + assert(impl.to[(Float, Long)](input.toString) === input) + } + } + + it should "parse TwoArgsBoolInt" in { + val simpleDoubleInputs = Table( + ("implementation", "TwoArgsBoolInt"), + Seq(1, + 0, + -11111111, + ).flatMap(i => Seq(true, false).map((i, _))).flatMap(tup => + implementations.map(impl => impl -> TwoArgsBoolInt(tup._2, tup._1))): _* + ) + forAll(simpleDoubleInputs) { (impl, input: TwoArgsBoolInt) => + assert(impl.to[TwoArgsBoolInt](input.toString) === input) + } + + } + +} diff --git a/src/test/scala/com/github/aborg0/caseyclassy/FPMagnoliaVarArgsTests.scala b/src/test/scala/com/github/aborg0/caseyclassy/FPMagnoliaVarArgsTests.scala new file mode 100644 index 0000000..6612ea4 --- /dev/null +++ b/src/test/scala/com/github/aborg0/caseyclassy/FPMagnoliaVarArgsTests.scala @@ -0,0 +1,104 @@ +package com.github.aborg0.caseyclassy + +import com.github.aborg0.caseyclassy.example.{OnlyVarArgs, StringPlusVarArgs} +import org.scalatest.WordSpec +import org.scalatest.prop.{TableDrivenPropertyChecks, TableFor1, TableFor2} + +class FPMagnoliaVarArgsTests extends WordSpec with TableDrivenPropertyChecks { + val implementations: TableFor1[FastParseMagnoliaParseCaseClass.type] = Table("implementation", FastParseMagnoliaParseCaseClass) + + import FastParseMagnoliaParseCaseClass._ + + "ParseCaseClass for variable arity arguments successfully parse" when { + "OnlyVarArgs" should { + "has empty args" in { + forAll( + Table(("implementation", "OnlyVarArgs[Int]"), implementations.map(impl => impl -> OnlyVarArgs[Int]()): _*)) { + (impl, input: OnlyVarArgs[Int]) => + assert(impl.to[OnlyVarArgs[Int]](input.toString) === input) + } + } + "has one argument" in { + val simpleVarArgsInputs = Table( + ("implementation", "OnlyVarArgs[Int]"), + Seq(1, + 0, + -1, + Int.MaxValue, + Int.MinValue).flatMap(i => + implementations.map(impl => impl -> OnlyVarArgs[Int](i))): _* + ) + forAll(simpleVarArgsInputs) { (impl, input: OnlyVarArgs[Int]) => + assert(impl.to[OnlyVarArgs[Int]](input.toString) === input) + } + } + "has two arguments" in { + val varArgsInputs = Table( + ("implementation", "OnlyVarArgs[Int]"), + Seq(1, + 0, + -1, + Int.MaxValue, + Int.MinValue).flatMap(i => + implementations.map(impl => impl -> OnlyVarArgs[Int](i, -i))): _* + ) + forAll(varArgsInputs) { (impl, input: OnlyVarArgs[Int]) => + assert(impl.to[OnlyVarArgs[Int]](input.toString) === input) + } + } + "has three arguments" in { + val varArgsInputs = Table( + ("implementation", "OnlyVarArgs[Int]"), + Seq(1, + 0, + -1, + Int.MaxValue, + Int.MinValue).flatMap(i => + implementations.map(impl => impl -> OnlyVarArgs[Int](3, i, -i))): _* + ) + forAll(varArgsInputs) { (impl, input: OnlyVarArgs[Int]) => + assert(impl.to[OnlyVarArgs[Int]](input.toString) === input) + } + } + } + "StringPlusVarArgs" should { + "with String + no arguments" in { + val simpleStringPlusVarArgs = Table( + ("implementation", "StringPlusVarArgs[Short]"), + Seq("", "Hello", "3").flatMap(s => implementations.map(impl => impl -> StringPlusVarArgs[Short](s))): _* + ) + forAll(simpleStringPlusVarArgs) { (impl, input: StringPlusVarArgs[Short]) => + assert(impl.to[StringPlusVarArgs[Short]](input.toString) === input) + } + } + "with String + one argument" in { + val stringPlusVarArgs = Table( + ("implementation", "StringPlusVarArgs[Short]"), + Seq("", "Hello", "3").flatMap(s => Seq(1.toShort, + 0.toShort, + (-1).toShort, + Short.MaxValue, + Short.MinValue).flatMap(i => + implementations.map(impl => impl -> StringPlusVarArgs(s, i)))): _* + ) + forAll(stringPlusVarArgs) { (impl, input: StringPlusVarArgs[Short]) => + assert(impl.to[StringPlusVarArgs[Short]](input.toString) === input) + } + } + "with String + two arguments" in { + val stringPlusVarArgs = Table( + ("implementation", "StringPlusVarArgs[Short]"), + Seq("", "Hello", "3").flatMap(s => Seq(1.toShort, + 0.toShort, + (-1).toShort, + Short.MaxValue, + Short.MinValue).flatMap(i => + implementations.map(impl => impl -> StringPlusVarArgs(s, i, 2.toShort)))): _* + ) + forAll(stringPlusVarArgs) { (impl, input: StringPlusVarArgs[Short]) => + assert(impl.to[StringPlusVarArgs[Short]](input.toString) === input) + } + } + } + } +} From cb8f5d7733234e72b807540ff2bc025caae49e30 Mon Sep 17 00:00:00 2001 From: Gabor Bakos Date: Tue, 11 Feb 2020 21:06:37 +0100 Subject: [PATCH 4/4] :arrow_up: Updating to use scala-js 1.0.0, scala 2.12.8 Removed other implementations ScalaTest 3.1.0 tut 0.6.12 Scala-js: 1.0.0 sbt-crossproject: 1.0.0 laika-sbt: 0.11.0 scoverage: 1.6.0 coveralls: 1.2.7 sbt-osgi: 0.9.5 --- build.sbt | 14 +- project/build.properties | 2 +- project/plugins.sbt | 19 +- .../FastParseMagnoliaParseCaseClass.scala | 12 +- .../caseyclassy/FastParseParseCaseClass.scala | 89 -------- .../aborg0/caseyclassy/ParseCaseClass.scala | 8 +- .../caseyclassy/RegexParseCaseClass.scala | 191 ------------------ src/main/tut/Examples.md | 127 ++---------- .../aborg0/caseyclassy/FPSimpleTests.scala | 26 +-- .../aborg0/caseyclassy/FPTwoArgsTests.scala | 24 +-- .../aborg0/caseyclassy/FPVarArgsTests.scala | 34 ++-- .../aborg0/caseyclassy/SimpleTests.scala | 22 +- .../aborg0/caseyclassy/TwoArgsTests.scala | 24 +-- .../aborg0/caseyclassy/VarArgsTests.scala | 34 ++-- 14 files changed, 126 insertions(+), 500 deletions(-) delete mode 100644 src/main/scala/com/github/aborg0/caseyclassy/FastParseParseCaseClass.scala delete mode 100644 src/main/scala/com/github/aborg0/caseyclassy/RegexParseCaseClass.scala diff --git a/build.sbt b/build.sbt index 7200f1e..44bfa9e 100644 --- a/build.sbt +++ b/build.sbt @@ -5,7 +5,7 @@ enablePlugins(TutPlugin) enablePlugins(LaikaPlugin) val sharedSettings = Seq( - scalaVersion := "2.12.6", + scalaVersion := "2.12.8", scalacOptions ++= Seq( // from: https://tpolecat.github.io/2017/04/25/scalac-flags.html "-deprecation", // Emit warning and location for usages of deprecated APIs. "-encoding", @@ -63,7 +63,7 @@ organization := "com.github.aborg0" version := "0.1" -scalaVersion := "2.12.6" +scalaVersion := "2.12.8" startYear := Some(2018) @@ -92,17 +92,17 @@ lazy val caseyClassyJVM = caseyClassy.jvm.enablePlugins(SbtOsgi) //lazy val caseyClassyNative = caseyClassy.native -libraryDependencies += "org.scalactic" %% "scalactic" % "3.0.5" -libraryDependencies += "org.scalatest" %% "scalatest" % "3.0.5" % "test" +libraryDependencies += "org.scalactic" %% "scalactic" % "3.1.0" +libraryDependencies += "org.scalatest" %% "scalatest" % "3.1.0" % "test" resolvers ++= Seq( Resolver.sonatypeRepo("releases"), Resolver.sonatypeRepo("snapshots") ) -libraryDependencies ++= Seq( - "com.chuusai" %%% "shapeless" % "2.3.3" -) +//libraryDependencies ++= Seq( +// "com.chuusai" %%% "shapeless" % "2.3.3" +//) libraryDependencies += "com.lihaoyi" %%% "fastparse" % "1.0.1" diff --git a/project/build.properties b/project/build.properties index d6e3507..a919a9b 100644 --- a/project/build.properties +++ b/project/build.properties @@ -1 +1 @@ -sbt.version=1.1.6 +sbt.version=1.3.8 diff --git a/project/plugins.sbt b/project/plugins.sbt index ed576a4..e93f8c5 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -1,11 +1,12 @@ -addSbtPlugin("org.scala-js" % "sbt-scalajs" % "0.6.23") -addSbtPlugin("org.portable-scala" % "sbt-crossproject" % "0.4.0") // (1) -addSbtPlugin("org.portable-scala" % "sbt-scalajs-crossproject" % "0.4.0") // (2) +//addSbtPlugin("org.scala-js" % "sbt-scalajs" % "0.6.32") +addSbtPlugin("org.scala-js" % "sbt-scalajs" % "1.0.0") +addSbtPlugin("org.portable-scala" % "sbt-crossproject" % "1.0.0") // (1) +addSbtPlugin("org.portable-scala" % "sbt-scalajs-crossproject" % "1.0.0") // (2) addSbtPlugin("org.scala-native" % "sbt-scala-native" % "0.3.7") // (3) -addSbtPlugin("org.tpolecat" % "tut-plugin" % "0.6.3") +addSbtPlugin("org.tpolecat" % "tut-plugin" % "0.6.12") //addSbtPlugin("com.artima.supersafe" % "sbtplugin" % "1.1.5") -addSbtPlugin("org.planet42" % "laika-sbt" % "0.7.5") -addSbtPlugin("org.scoverage" % "sbt-scoverage" % "1.5.1") -addSbtPlugin("org.scoverage" % "sbt-coveralls" % "1.2.4") -addSbtPlugin("com.geirsson" % "sbt-scalafmt" % "1.4.0") -addSbtPlugin("com.typesafe.sbt" % "sbt-osgi" % "0.9.3") \ No newline at end of file +addSbtPlugin("org.planet42" % "laika-sbt" % "0.11.0") +addSbtPlugin("org.scoverage" % "sbt-scoverage" % "1.6.0") +addSbtPlugin("org.scoverage" % "sbt-coveralls" % "1.2.7") +addSbtPlugin("org.scalameta" % "sbt-scalafmt" % "2.0.0") +addSbtPlugin("com.typesafe.sbt" % "sbt-osgi" % "0.9.5") \ No newline at end of file diff --git a/src/main/scala/com/github/aborg0/caseyclassy/FastParseMagnoliaParseCaseClass.scala b/src/main/scala/com/github/aborg0/caseyclassy/FastParseMagnoliaParseCaseClass.scala index e015fac..bcc4e9f 100644 --- a/src/main/scala/com/github/aborg0/caseyclassy/FastParseMagnoliaParseCaseClass.scala +++ b/src/main/scala/com/github/aborg0/caseyclassy/FastParseMagnoliaParseCaseClass.scala @@ -122,9 +122,9 @@ trait MagnoliaParseCaseClass { def to[A /*<: AnyRef*/](input: String)(implicit parse: Parse[A]): A } -//trait FastParseParse[A] extends Parse[A] { -// protected[caseyclassy] def parser(): Parser[A] -// -// override def parse(input: String): A = parser().parse(input).fold((p, i, e) => -// throw new IllegalArgumentException(s"Expected: $p at position: $i"), (a, i) => a) -//} \ No newline at end of file +trait FastParseParse[A] extends Parse[A] { + protected[caseyclassy] def parser(): Parser[A] + + override def parse(input: String): A = parser().parse(input).fold((p, i, e) => + throw new IllegalArgumentException(s"Expected: $p at position: $i"), (a, i) => a) +} \ No newline at end of file diff --git a/src/main/scala/com/github/aborg0/caseyclassy/FastParseParseCaseClass.scala b/src/main/scala/com/github/aborg0/caseyclassy/FastParseParseCaseClass.scala deleted file mode 100644 index d7f86f3..0000000 --- a/src/main/scala/com/github/aborg0/caseyclassy/FastParseParseCaseClass.scala +++ /dev/null @@ -1,89 +0,0 @@ -package com.github.aborg0.caseyclassy - -import fastparse.all._ -import java.time.{LocalDate, LocalTime} - -import shapeless._ - -import scala.reflect.runtime.universe._ - -private[caseyclassy] trait FPGenericImplementations { - implicit def booleanParse: FastParseParse[Boolean] = () => P("true" | "false").!.map(_.toBoolean) - - private[this] def integral: Parser[String] = P("-".? ~ CharIn('0' to '9').rep(1)).! - - private[this] def numeric: Parser[String] = P("NaN" | "-".? ~ "Infinity" | - ("-".? ~ CharIn('0' to '9').rep(1) ~ - ("." ~/ CharIn('0' to '9').rep(1)).? ~ - (CharIn("eE") ~/ CharIn("+-").? ~/ CharIn('0' to '9').rep(1)).?)).! - - implicit def longParse: FastParseParse[Long] = () => integral.map(_.toLong) - - implicit def intParse: FastParseParse[Int] = () => integral.map(_.toInt) - - implicit def shortParse: FastParseParse[Short] = () => integral.map(_.toShort) - - implicit def byteParse: FastParseParse[Byte] = () => integral.map(_.toByte) - - implicit def doubleParse: FastParseParse[Double] = () => numeric.!.map(_.toDouble) - - implicit def floatParse: FastParseParse[Float] = () => numeric.!.map(_.toFloat) - - implicit def parseHNil: FastParseParse[HNil] = () => Pass.map(_ => HNil) - - implicit def parseCNil: FastParseParse[CNil] = () => Fail.log("CNil") - - implicit def parseProduct[Head, Tail <: HList](implicit headParse: Lazy[FastParseParse[Head]], - tailParse: Lazy[FastParseParse[Tail]]): FastParseParse[Head :: Tail] = - () => (headParse.value.parser() ~ ",".? ~ tailParse.value.parser()).map { case (h, t) => h :: t } - - implicit def parseCoproduct[Head, Tail <: Coproduct](implicit headParse: Lazy[FastParseParse[Head]], - tailParse: Lazy[FastParseParse[Tail]]): FastParseParse[Head :+: Tail] = - () => headParse.value.parser().map(Inl(_)) | tailParse.value.parser().map(Inr(_)) - - implicit def generic[A: TypeTag, R](implicit gen: Generic.Aux[A, R], argParse: FastParseParse[R]): FastParseParse[A] = () => { - val typeKind = implicitly[TypeTag[A]] - if (typeKind.tpe.typeSymbol.isAbstract /* Approximation of sealed trait */ ) argParse.parser().map(gen.from) else { - val name = typeKind.tpe.typeSymbol.name.toString - if (name.startsWith("Tuple")) - P("(" ~/ argParse.parser() ~ ")").map(gen.from) - else - P(name ~ (("(" ~/ argParse.parser() ~ ")") | argParse.parser())).map(gen.from) - } - } -} - -case object FastParseParseCaseClass extends ParseCaseClass with FPGenericImplementations { - - override def to[A](input: String)(implicit parse: Lazy[Parse[A]]): A = { - parse.value.parse(input) - } - - def apply[A](implicit p: Lazy[FastParseParse[A]]): FastParseParse[A] = p.value - - //region Custom overrides of special types - implicit def stringParse: FastParseParse[String] = () => - P(CharsWhile(c => c != ',' && c != ')').!) | P("").! - - implicit def timeParse: FastParseParse[LocalTime] = () => P( - CharIn('0' to '9').rep(2, "", 2) ~ ":" ~ - CharIn('0' to '9').rep(2, "", 2) ~ - (":" ~/ CharIn('0' to '9').rep(2, "", 2) ~ - ("." ~/ CharIn('0' to '9').rep(1)).?).?).!.map(LocalTime.parse(_)) - - implicit def dateParse: FastParseParse[LocalDate] = () => - P(CharIn('0' to '9').rep(4, "", 4) ~ "-" ~ - CharIn('0' to '9').rep(1, "", 2) ~ "-" ~ - CharIn('0' to '9').rep(1, "", 2)).!.map(LocalDate.parse(_)) - - implicit def seqConverter[A](implicit parseA: FastParseParse[A]): FastParseParse[Seq[A]] = () => - P(("WrappedArray" | "List" | "Vector") ~ "(" ~/ parseA.parser().rep(sep = ", ") ~ ")") - //endregion -} - -trait FastParseParse[A] extends Parse[A] { - protected[caseyclassy] def parser(): Parser[A] - - override def parse(input: String): A = parser().parse(input).fold((p, i, e) => - throw new IllegalArgumentException(s"Expected: $p at position: $i"), (a, i) => a) -} \ No newline at end of file diff --git a/src/main/scala/com/github/aborg0/caseyclassy/ParseCaseClass.scala b/src/main/scala/com/github/aborg0/caseyclassy/ParseCaseClass.scala index 884f6dc..edffde9 100644 --- a/src/main/scala/com/github/aborg0/caseyclassy/ParseCaseClass.scala +++ b/src/main/scala/com/github/aborg0/caseyclassy/ParseCaseClass.scala @@ -1,10 +1,10 @@ package com.github.aborg0.caseyclassy -import shapeless.Lazy +//import shapeless.Lazy -trait ParseCaseClass { - def to[A /*<: AnyRef*/](input: String)(implicit parse: Lazy[Parse[A]]): A -} +//trait ParseCaseClass { +// def to[A /*<: AnyRef*/](input: String)(implicit parse: Lazy[Parse[A]]): A +//} trait Parse[A] { def parse(input: String): A diff --git a/src/main/scala/com/github/aborg0/caseyclassy/RegexParseCaseClass.scala b/src/main/scala/com/github/aborg0/caseyclassy/RegexParseCaseClass.scala deleted file mode 100644 index cff6386..0000000 --- a/src/main/scala/com/github/aborg0/caseyclassy/RegexParseCaseClass.scala +++ /dev/null @@ -1,191 +0,0 @@ -package com.github.aborg0.caseyclassy - -import java.time.{LocalDate, LocalTime} - -import shapeless._ - -import scala.collection.mutable.ArrayBuffer -import scala.reflect.runtime.universe._ -import scala.util.Try -import scala.util.matching.Regex - -private[caseyclassy] trait GenericImplementations { - implicit def longParse: RegexParse[Long] = _.toLong - - implicit def intParse: RegexParse[Int] = new CommonRegexParse[Int] { - override protected[caseyclassy] val pattern: Regex = "([\\-]?\\d+)".r - - override def toResult(found: String): Int = found.toInt - } - - implicit def shortParse: RegexParse[Short] = new CommonRegexParse[Short] { - override protected[caseyclassy] val pattern: Regex = "([\\-]?\\d+)".r - - override def toResult(found: String): Short = found.toShort - } - - implicit def byteParse: CommonRegexParse[Byte] = _.toByte - - implicit def doubleParse: RegexParse[Double] = _.toDouble - - implicit def floatParse: CommonRegexParse[Float] = _.toFloat - - implicit def booleanParse: CommonRegexParse[Boolean] = new CommonRegexParse[Boolean] { - protected[caseyclassy] override val pattern: Regex = "(true|false)".r - - override def toResult(found: String): Boolean = found.toBoolean - } - - implicit def parseHNil: RegexParse[HNil] = new CommonRegexParse[HNil] { - protected[caseyclassy] override val pattern: Regex = "()".r - - override def toResult(found: String): HNil = if (found.isEmpty) HNil else throw new IllegalArgumentException(s"Non-empty: |$found|") - } - - implicit def parseCNil: RegexParse[CNil] = new CommonRegexParseWithoutToResult[CNil] { - override def parse(input: String): CNil = throw new IllegalStateException(input) - - override protected[caseyclassy] val pattern: Regex = "".r - } - - implicit def parseProduct[Head, Tail <: HList](implicit headParse: Lazy[RegexParse[Head]], tailParse: Lazy[RegexParse[Tail]]): RegexParse[Head :: Tail] = new CommonRegexParseWithoutToResult[Head :: Tail] { - protected[caseyclassy] override val pattern: Regex = { - val tailPattern = tailParse.value.pattern.pattern.pattern - if (tailPattern.length <= 2 /*"((?=\\)))".length*/ - /*"()"*/ ) - toNonCapturing(headParse.value.pattern.pattern.pattern).r - else - s"${toNonCapturing(headParse.value.pattern.pattern.pattern)},${toNonCapturing(tailPattern)}".r - } - - override def parse(input: String): Head :: Tail = { - val head = headParse.value.parse(input) - val headLength = head.toString.length - head :: tailParse.value.parse(if (input.length > headLength) input.substring(headLength + 1) else "") - } - } - - implicit def parseCoproduct[Head, Tail <: Coproduct](implicit headParse: Lazy[RegexParse[Head]], tailParse: Lazy[RegexParse[Tail]]): RegexParse[Head :+: Tail] = new CommonRegexParseWithoutToResult[Head :+: Tail] { - protected[caseyclassy] override val pattern: Regex = { - val tailPattern = tailParse.value.pattern.pattern.pattern - if (tailPattern.isEmpty) - toNonCapturing(headParse.value.pattern.pattern.pattern).r - else - s"((?:${toNonCapturing(headParse.value.pattern.pattern.pattern)})|(?:${toNonCapturing(tailPattern)}))".r - } - - override def parse(input: String): Head :+: Tail = { - val leftValue = Try(Inl(headParse.value.parse(input))) - leftValue.getOrElse { - Inr(tailParse.value.parse(input)) - } - } - } - - implicit def generic[A: TypeTag, R](implicit gen: Generic.Aux[A, R], argParse: RegexParse[R]): RegexParse[A] = new CommonRegexParseWithoutToResult[A] { - protected[caseyclassy] override val pattern: Regex = { - val typeKind = implicitly[TypeTag[A]] - /*if (typeKind.tpe <:< typeTag[AnyVal].tpe || typeKind.tpe <:< typeTag[String].tpe || typeKind.tpe <:< typeTag[LocalDate].tpe || typeKind.tpe <:< typeTag[LocalTime].tpe) { - s"(${toNonCapturing(argParse.pattern.pattern.pattern)})".r - } else */ if (typeKind.tpe.typeSymbol.isAbstract /*approximation of sealed base*/ ) { - toNonCapturing(argParse.pattern.pattern.pattern).r - } else { - val name = typeKind.tpe.typeSymbol.name.toString - if (name.startsWith("Tuple")) { - s"(?:\\()?(${toNonCapturing(argParse.pattern.pattern.pattern)})(?:\\))?".r - } else - s"(?:$name)(?:\\()?(${toNonCapturing(argParse.pattern.pattern.pattern)})(?:\\))?".r - } - } - - override def parse(input: String): A = { - val typeKind = implicitly[TypeTag[A]] - /*if (typeKind.tpe <:< typeTag[AnyVal].tpe || typeKind.tpe <:< typeTag[String].tpe || typeKind.tpe <:< typeTag[LocalDate].tpe || typeKind.tpe <:< typeTag[LocalTime].tpe) { - gen.from(argParse.parse(input)) - } else*/ if (pattern.findPrefixOf(input).isDefined /*input.startsWith(typeKind.tpe.typeSymbol.name.toString)*/ ) { - val name = typeKind.tpe.typeSymbol.name.toString - if (input.startsWith(name)) { - val rest = if (input.length > name.length + 1) input.substring(name.length + 1, input.length - 1) else "" - // if (argParse.pattern.pattern.pattern.length < 3 && rest.isEmpty) { - // gen.from(HNil.asInstanceOf[R]) - // } else - gen.from(argParse.parse(rest)) - } else if (name.startsWith("Tuple") && input.length >= 2) { - gen.from(argParse.parse(input.substring(1, input.length - 1))) - } else { - gen.from(argParse.parse(input)) - } - } else if (typeKind.tpe.typeSymbol.isAbstract /*approximation of sealed base*/ ) { - // fall back for base sealed structure - gen.from(argParse.parse(input)) - } else - throw new IllegalArgumentException(s"|$input| does not start with ${typeKind.tpe.typeSymbol.name.toString}") - } - } -} - -case object RegexParseCaseClass extends ParseCaseClass with GenericImplementations { - - override def to[A/* <: AnyRef*/](input: String)(implicit parse: Lazy[Parse[A]]): A = { - parse.value.parse(input) - } - - def apply[A](implicit p: Lazy[RegexParse[A]]): RegexParse[A] = p.value - - //region Custom overrides of special types - implicit def stringParse: CommonRegexParse[String] = new CommonRegexParse[String] { - - override protected[caseyclassy] val pattern: Regex = "([^,)]*)".r - - override def toResult(found: String): String = found - } - - implicit def timeParse: CommonRegexParse[LocalTime] = LocalTime.parse(_) - - implicit def dateParse: CommonRegexParse[LocalDate] = new CommonRegexParse[LocalDate] { - override def toResult(found: String): LocalDate = LocalDate.parse(found) - - override protected[caseyclassy] val pattern: Regex = "(\\d{4}\\-\\d{2}\\-\\d{2})".r - } - - implicit def seqConverter[A](implicit parseA: RegexParse[A]): CommonRegexParse[Seq[A]] = new CommonRegexParseWithoutToResult[Seq[A]] { - override protected[caseyclassy] val pattern: Regex = s"(?:WrappedArray|List|Vector)\\(((?:(?:${toNonCapturing(parseA.pattern.pattern.pattern)})(?:, )?)*)\\)".r - - override def parse(input: String): Seq[A] = pattern.findPrefixMatchOf(input).fold(throw new IllegalArgumentException(s"$input does not start with a Seq"))(m => { - val all = m.group(1) - val buffer = new ArrayBuffer[A]() - var rest = all - while (rest != "") { - val newA = parseA.parse(rest) - rest = rest.substring(newA.toString.length) - if (rest.startsWith(", ")) { - rest = rest.substring(", ".length) - } - buffer.append(newA) - } - buffer.toVector - }) - } - - //endregion - -} - -trait RegexParse[A] extends Parse[A] { - protected[caseyclassy] def pattern: Regex = "([^,)]+)".r -} - -trait CommonRegexParse[A] extends RegexParse[A] { - - def toResult(found: String): A - - override def parse(input: String): A = pattern.findPrefixMatchOf(input).map(m => toResult(m.group(1))).getOrElse( - throw new IllegalArgumentException(s"|$input| does not start with proper value")) - - protected final def toNonCapturing(pattern: String): String = pattern.replaceAll("(? implementations.map(impl => impl -> SimpleDouble(d))): _* ) - forAll(simpleDoubleInputs) { (impl: ParseCaseClass, input: SimpleDouble) => + forAll(simpleDoubleInputs) { (impl: MagnoliaParseCaseClass, input: SimpleDouble) => assert(impl.to[SimpleDouble](input.toString) === input) } } it should "parse SimpleInt" in { - val simpleIntInputs: TableFor2[ParseCaseClass, SimpleInt] = Table( + val simpleIntInputs: TableFor2[MagnoliaParseCaseClass, SimpleInt] = Table( ("implementation", "SimpleInt"), Seq(1, 0, 2, -5, -10, Int.MaxValue, Int.MinValue).flatMap(i => implementations.map(impl => impl -> SimpleInt(i))): _* ) - forAll(simpleIntInputs) { (impl: ParseCaseClass, input: SimpleInt) => + forAll(simpleIntInputs) { (impl: MagnoliaParseCaseClass, input: SimpleInt) => assert(impl.to[SimpleInt](input.toString) === input) } } it should "parse Int" in { - val simpleIntInputs: TableFor2[ParseCaseClass, Int] = Table( + val simpleIntInputs: TableFor2[MagnoliaParseCaseClass, Int] = Table( ("implementation", "Int"), Seq(1, 0, 2, -5, -10, Int.MaxValue, Int.MinValue).flatMap(i => implementations.map(impl => impl -> i)): _* ) - forAll(simpleIntInputs) { (impl: ParseCaseClass, input: Int) => + forAll(simpleIntInputs) { (impl: MagnoliaParseCaseClass, input: Int) => assert(impl.to[Int](input.toString) === input) } } it should "parse SimpleBoolean" in { - val simpleIntInputs: TableFor2[ParseCaseClass, SimpleBoolean] = Table( + val simpleIntInputs: TableFor2[MagnoliaParseCaseClass, SimpleBoolean] = Table( ("implementation", "SimpleBoolean"), Seq(false, true).flatMap(b => implementations.map(impl => impl -> SimpleBoolean(b))): _* ) - forAll(simpleIntInputs) { (impl: ParseCaseClass, input: SimpleBoolean) => + forAll(simpleIntInputs) { (impl: MagnoliaParseCaseClass, input: SimpleBoolean) => assert(impl.to[SimpleBoolean](input.toString) === input) } } @@ -81,7 +81,7 @@ class FPSimpleTests extends FlatSpec with TableDrivenPropertyChecks { } "FastParseParseCaseClass" should "support reuse" in { - val simpleBooleanParser = FastParseParseCaseClass[SimpleBoolean] + val simpleBooleanParser = FastParseMagnoliaParseCaseClass[SimpleBoolean] assert(simpleBooleanParser.parse("SimpleBoolean(false)") === SimpleBoolean(false)) assert(simpleBooleanParser.parse("SimpleBoolean(true)") === SimpleBoolean(true)) } diff --git a/src/test/scala/com/github/aborg0/caseyclassy/FPTwoArgsTests.scala b/src/test/scala/com/github/aborg0/caseyclassy/FPTwoArgsTests.scala index 3307220..07285df 100644 --- a/src/test/scala/com/github/aborg0/caseyclassy/FPTwoArgsTests.scala +++ b/src/test/scala/com/github/aborg0/caseyclassy/FPTwoArgsTests.scala @@ -3,16 +3,16 @@ package com.github.aborg0.caseyclassy import java.time.{LocalDate, LocalTime} import com.github.aborg0.caseyclassy.example.TwoArgsBoolInt -import org.scalatest.FlatSpec +import org.scalatest.flatspec.AnyFlatSpec import org.scalatest.prop.{TableDrivenPropertyChecks, TableFor1, TableFor2} -class FPTwoArgsTests extends FlatSpec with TableDrivenPropertyChecks { - val implementations: TableFor1[ParseCaseClass] = Table("implementation", FastParseParseCaseClass) +class FPTwoArgsTests extends AnyFlatSpec with TableDrivenPropertyChecks { + val implementations: TableFor1[MagnoliaParseCaseClass] = Table("implementation", FastParseMagnoliaParseCaseClass) - import FastParseParseCaseClass._ + import FastParseMagnoliaParseCaseClass._ behavior of "FastParseParseCaseClass for two args cases" it should "parse Tuple2[Int, LocalDate]" in { - val intDateInputs: TableFor2[ParseCaseClass, (Int, LocalDate)] = Table( + val intDateInputs: TableFor2[MagnoliaParseCaseClass, (Int, LocalDate)] = Table( ("implementation", "(Int, LocalDate)"), Seq(1, 0, @@ -20,12 +20,12 @@ class FPTwoArgsTests extends FlatSpec with TableDrivenPropertyChecks { ).flatMap(i => Seq(LocalDate.of(1970, 1, 1), LocalDate.of(2000, 12, 31)).map((i, _))).flatMap(tup => implementations.map(impl => impl -> tup)): _* ) - forAll(intDateInputs) { (impl: ParseCaseClass, input: (Int, LocalDate)) => + forAll(intDateInputs) { (impl: MagnoliaParseCaseClass, input: (Int, LocalDate)) => assert(impl.to[(Int, LocalDate)](input.toString) === input) } } it should "parse Tuple2[Byte, LocalTime]" in { - val byteTimeInputs: TableFor2[ParseCaseClass, (Byte, LocalTime)] = Table( + val byteTimeInputs: TableFor2[MagnoliaParseCaseClass, (Byte, LocalTime)] = Table( ("implementation", "(Byte, LocalTime)"), Seq(1.toByte, 0.toByte, @@ -33,12 +33,12 @@ class FPTwoArgsTests extends FlatSpec with TableDrivenPropertyChecks { ).flatMap(i => Seq(LocalTime.of(0, 0, 0), LocalTime.of(23, 59, 59)).map((i, _))).flatMap(tup => implementations.map(impl => impl -> tup)): _* ) - forAll(byteTimeInputs) { (impl: ParseCaseClass, input: (Byte, LocalTime)) => + forAll(byteTimeInputs) { (impl: MagnoliaParseCaseClass, input: (Byte, LocalTime)) => assert(impl.to[(Byte, LocalTime)](input.toString) === input) } } it should "parse Tuple2[Float, Long]" in { - val floatLongInputs: TableFor2[ParseCaseClass, (Float, Long)] = Table( + val floatLongInputs: TableFor2[MagnoliaParseCaseClass, (Float, Long)] = Table( ("implementation", "(Float, Long)"), Seq(1l, 0l, @@ -47,13 +47,13 @@ class FPTwoArgsTests extends FlatSpec with TableDrivenPropertyChecks { ).flatMap(i => Seq(Float.NegativeInfinity, -3.14159f).map((_, i))).flatMap(tup => implementations.map(impl => impl -> tup)): _* ) - forAll(floatLongInputs) { (impl: ParseCaseClass, input: (Float, Long)) => + forAll(floatLongInputs) { (impl: MagnoliaParseCaseClass, input: (Float, Long)) => assert(impl.to[(Float, Long)](input.toString) === input) } } it should "parse TwoArgsBoolInt" in { - val simpleDoubleInputs: TableFor2[ParseCaseClass, TwoArgsBoolInt] = Table( + val simpleDoubleInputs: TableFor2[MagnoliaParseCaseClass, TwoArgsBoolInt] = Table( ("implementation", "TwoArgsBoolInt"), Seq(1, 0, @@ -61,7 +61,7 @@ class FPTwoArgsTests extends FlatSpec with TableDrivenPropertyChecks { ).flatMap(i => Seq(true, false).map((i, _))).flatMap(tup => implementations.map(impl => impl -> TwoArgsBoolInt(tup._2, tup._1))): _* ) - forAll(simpleDoubleInputs) { (impl: ParseCaseClass, input: TwoArgsBoolInt) => + forAll(simpleDoubleInputs) { (impl: MagnoliaParseCaseClass, input: TwoArgsBoolInt) => assert(impl.to[TwoArgsBoolInt](input.toString) === input) } diff --git a/src/test/scala/com/github/aborg0/caseyclassy/FPVarArgsTests.scala b/src/test/scala/com/github/aborg0/caseyclassy/FPVarArgsTests.scala index 3a535d8..8099505 100644 --- a/src/test/scala/com/github/aborg0/caseyclassy/FPVarArgsTests.scala +++ b/src/test/scala/com/github/aborg0/caseyclassy/FPVarArgsTests.scala @@ -1,25 +1,25 @@ package com.github.aborg0.caseyclassy import com.github.aborg0.caseyclassy.example.{OnlyVarArgs, StringPlusVarArgs} -import org.scalatest.WordSpec +import org.scalatest.wordspec.AnyWordSpec import org.scalatest.prop.{TableDrivenPropertyChecks, TableFor1, TableFor2} -class FPVarArgsTests extends WordSpec with TableDrivenPropertyChecks { - val implementations: TableFor1[ParseCaseClass] = Table("implementation", FastParseParseCaseClass) +class FPVarArgsTests extends AnyWordSpec with TableDrivenPropertyChecks { + val implementations: TableFor1[MagnoliaParseCaseClass] = Table("implementation", FastParseMagnoliaParseCaseClass) - import FastParseParseCaseClass._ + import FastParseMagnoliaParseCaseClass._ "ParseCaseClass for variable arity arguments successfully parse" when { "OnlyVarArgs" should { "has empty args" in { forAll( Table(("implementation", "OnlyVarArgs[Int]"), implementations.map(impl => impl -> OnlyVarArgs[Int]()): _*)) { - (impl: ParseCaseClass, input: OnlyVarArgs[Int]) => + (impl: MagnoliaParseCaseClass, input: OnlyVarArgs[Int]) => assert(impl.to[OnlyVarArgs[Int]](input.toString) === input) } } "has one argument" in { - val simpleVarArgsInputs: TableFor2[ParseCaseClass, OnlyVarArgs[Int]] = Table( + val simpleVarArgsInputs: TableFor2[MagnoliaParseCaseClass, OnlyVarArgs[Int]] = Table( ("implementation", "OnlyVarArgs[Int]"), Seq(1, 0, @@ -28,12 +28,12 @@ class FPVarArgsTests extends WordSpec with TableDrivenPropertyChecks { Int.MinValue).flatMap(i => implementations.map(impl => impl -> OnlyVarArgs[Int](i))): _* ) - forAll(simpleVarArgsInputs) { (impl: ParseCaseClass, input: OnlyVarArgs[Int]) => + forAll(simpleVarArgsInputs) { (impl: MagnoliaParseCaseClass, input: OnlyVarArgs[Int]) => assert(impl.to[OnlyVarArgs[Int]](input.toString) === input) } } "has two arguments" in { - val varArgsInputs: TableFor2[ParseCaseClass, OnlyVarArgs[Int]] = Table( + val varArgsInputs: TableFor2[MagnoliaParseCaseClass, OnlyVarArgs[Int]] = Table( ("implementation", "OnlyVarArgs[Int]"), Seq(1, 0, @@ -42,12 +42,12 @@ class FPVarArgsTests extends WordSpec with TableDrivenPropertyChecks { Int.MinValue).flatMap(i => implementations.map(impl => impl -> OnlyVarArgs[Int](i, -i))): _* ) - forAll(varArgsInputs) { (impl: ParseCaseClass, input: OnlyVarArgs[Int]) => + forAll(varArgsInputs) { (impl: MagnoliaParseCaseClass, input: OnlyVarArgs[Int]) => assert(impl.to[OnlyVarArgs[Int]](input.toString) === input) } } "has three arguments" in { - val varArgsInputs: TableFor2[ParseCaseClass, OnlyVarArgs[Int]] = Table( + val varArgsInputs: TableFor2[MagnoliaParseCaseClass, OnlyVarArgs[Int]] = Table( ("implementation", "OnlyVarArgs[Int]"), Seq(1, 0, @@ -56,23 +56,23 @@ class FPVarArgsTests extends WordSpec with TableDrivenPropertyChecks { Int.MinValue).flatMap(i => implementations.map(impl => impl -> OnlyVarArgs[Int](3, i, -i))): _* ) - forAll(varArgsInputs) { (impl: ParseCaseClass, input: OnlyVarArgs[Int]) => + forAll(varArgsInputs) { (impl: MagnoliaParseCaseClass, input: OnlyVarArgs[Int]) => assert(impl.to[OnlyVarArgs[Int]](input.toString) === input) } } } "StringPlusVarArgs" should { "with String + no arguments" in { - val simpleStringPlusVarArgs: TableFor2[ParseCaseClass, StringPlusVarArgs[Short]] = Table( + val simpleStringPlusVarArgs: TableFor2[MagnoliaParseCaseClass, StringPlusVarArgs[Short]] = Table( ("implementation", "StringPlusVarArgs[Short]"), Seq("", "Hello", "3").flatMap(s => implementations.map(impl => impl -> StringPlusVarArgs[Short](s))): _* ) - forAll(simpleStringPlusVarArgs) { (impl: ParseCaseClass, input: StringPlusVarArgs[Short]) => + forAll(simpleStringPlusVarArgs) { (impl: MagnoliaParseCaseClass, input: StringPlusVarArgs[Short]) => assert(impl.to[StringPlusVarArgs[Short]](input.toString) === input) } } "with String + one argument" in { - val stringPlusVarArgs: TableFor2[ParseCaseClass, StringPlusVarArgs[Short]] = Table( + val stringPlusVarArgs: TableFor2[MagnoliaParseCaseClass, StringPlusVarArgs[Short]] = Table( ("implementation", "StringPlusVarArgs[Short]"), Seq("", "Hello", "3").flatMap(s => Seq(1.toShort, 0.toShort, @@ -81,12 +81,12 @@ class FPVarArgsTests extends WordSpec with TableDrivenPropertyChecks { Short.MinValue).flatMap(i => implementations.map(impl => impl -> StringPlusVarArgs(s, i)))): _* ) - forAll(stringPlusVarArgs) { (impl: ParseCaseClass, input: StringPlusVarArgs[Short]) => + forAll(stringPlusVarArgs) { (impl: MagnoliaParseCaseClass, input: StringPlusVarArgs[Short]) => assert(impl.to[StringPlusVarArgs[Short]](input.toString) === input) } } "with String + two arguments" in { - val stringPlusVarArgs: TableFor2[ParseCaseClass, StringPlusVarArgs[Short]] = Table( + val stringPlusVarArgs: TableFor2[MagnoliaParseCaseClass, StringPlusVarArgs[Short]] = Table( ("implementation", "StringPlusVarArgs[Short]"), Seq("", "Hello", "3").flatMap(s => Seq(1.toShort, 0.toShort, @@ -95,7 +95,7 @@ class FPVarArgsTests extends WordSpec with TableDrivenPropertyChecks { Short.MinValue).flatMap(i => implementations.map(impl => impl -> StringPlusVarArgs(s, i, 2.toShort)))): _* ) - forAll(stringPlusVarArgs) { (impl: ParseCaseClass, input: StringPlusVarArgs[Short]) => + forAll(stringPlusVarArgs) { (impl: MagnoliaParseCaseClass, input: StringPlusVarArgs[Short]) => assert(impl.to[StringPlusVarArgs[Short]](input.toString) === input) } } diff --git a/src/test/scala/com/github/aborg0/caseyclassy/SimpleTests.scala b/src/test/scala/com/github/aborg0/caseyclassy/SimpleTests.scala index 08b6f8f..2d12a88 100644 --- a/src/test/scala/com/github/aborg0/caseyclassy/SimpleTests.scala +++ b/src/test/scala/com/github/aborg0/caseyclassy/SimpleTests.scala @@ -4,18 +4,18 @@ import java.time.LocalDate import com.github.aborg0.caseyclassy.example.{SimpleBoolean, SimpleDouble, SimpleInt, SimpleObject} import org.scalactic.TypeCheckedTripleEquals -import org.scalatest.FlatSpec +import org.scalatest.flatspec.AnyFlatSpec import org.scalatest.prop.{TableDrivenPropertyChecks, TableFor1, TableFor2} -class SimpleTests extends FlatSpec with TableDrivenPropertyChecks { - val implementations: TableFor1[ParseCaseClass] = Table("implementation", RegexParseCaseClass) +class SimpleTests extends AnyFlatSpec with TableDrivenPropertyChecks { + val implementations: TableFor1[MagnoliaParseCaseClass] = Table("implementation", FastParseMagnoliaParseCaseClass) behavior of "ParseCaseClass for simple cases" - import RegexParseCaseClass._ + import FastParseMagnoliaParseCaseClass._ it should "parse SimpleDouble" in { - val simpleDoubleInputs: TableFor2[ParseCaseClass, SimpleDouble] = Table( + val simpleDoubleInputs: TableFor2[MagnoliaParseCaseClass, SimpleDouble] = Table( ("implementation", "SimpleDouble"), Seq(1d, 0d, @@ -29,27 +29,27 @@ class SimpleTests extends FlatSpec with TableDrivenPropertyChecks { Double.PositiveInfinity).flatMap(d => implementations.map(impl => impl -> SimpleDouble(d))): _* ) - forAll(simpleDoubleInputs) { (impl: ParseCaseClass, input: SimpleDouble) => + forAll(simpleDoubleInputs) { (impl: MagnoliaParseCaseClass, input: SimpleDouble) => assert(impl.to[SimpleDouble](input.toString) === input) } } it should "parse SimpleInt" in { - val simpleIntInputs: TableFor2[ParseCaseClass, SimpleInt] = Table( + val simpleIntInputs: TableFor2[MagnoliaParseCaseClass, SimpleInt] = Table( ("implementation", "SimpleInt"), Seq(1, 0, 2, -5, -10, Int.MaxValue, Int.MinValue).flatMap(i => implementations.map(impl => impl -> SimpleInt(i))): _* ) - forAll(simpleIntInputs) { (impl: ParseCaseClass, input: SimpleInt) => + forAll(simpleIntInputs) { (impl: MagnoliaParseCaseClass, input: SimpleInt) => assert(impl.to[SimpleInt](input.toString) === input) } } it should "parse SimpleBoolean" in { - val simpleIntInputs: TableFor2[ParseCaseClass, SimpleBoolean] = Table( + val simpleIntInputs: TableFor2[MagnoliaParseCaseClass, SimpleBoolean] = Table( ("implementation", "SimpleBoolean"), Seq(false, true).flatMap(b => implementations.map(impl => impl -> SimpleBoolean(b))): _* ) - forAll(simpleIntInputs) { (impl: ParseCaseClass, input: SimpleBoolean) => + forAll(simpleIntInputs) { (impl: MagnoliaParseCaseClass, input: SimpleBoolean) => assert(impl.to[SimpleBoolean](input.toString) === input) } } @@ -73,7 +73,7 @@ class SimpleTests extends FlatSpec with TableDrivenPropertyChecks { } "RegexParseCaseClass" should "support reuse" in { - val simpleBooleanParser = RegexParseCaseClass[SimpleBoolean] + val simpleBooleanParser = FastParseMagnoliaParseCaseClass[SimpleBoolean] assert(simpleBooleanParser.parse("SimpleBoolean(false)") === SimpleBoolean(false)) assert(simpleBooleanParser.parse("SimpleBoolean(true)") === SimpleBoolean(true)) } diff --git a/src/test/scala/com/github/aborg0/caseyclassy/TwoArgsTests.scala b/src/test/scala/com/github/aborg0/caseyclassy/TwoArgsTests.scala index 96a4d45..70e1293 100644 --- a/src/test/scala/com/github/aborg0/caseyclassy/TwoArgsTests.scala +++ b/src/test/scala/com/github/aborg0/caseyclassy/TwoArgsTests.scala @@ -4,17 +4,17 @@ import java.time.{LocalDate, LocalTime} import com.github.aborg0.caseyclassy.example.TwoArgsBoolInt import org.scalactic.TypeCheckedTripleEquals -import org.scalatest.FlatSpec +import org.scalatest.flatspec.AnyFlatSpec import org.scalatest.prop.{TableDrivenPropertyChecks, TableFor1, TableFor2} -class TwoArgsTests extends FlatSpec with TableDrivenPropertyChecks { - val implementations: TableFor1[ParseCaseClass] = Table("implementation", RegexParseCaseClass) +class TwoArgsTests extends AnyFlatSpec with TableDrivenPropertyChecks { + val implementations: TableFor1[MagnoliaParseCaseClass] = Table("implementation", FastParseMagnoliaParseCaseClass) behavior of "ParseCaseClass for two args cases" - import RegexParseCaseClass._ + import FastParseMagnoliaParseCaseClass._ it should "parse Tuple2[Int, LocalDate]" in { - val intDateInputs: TableFor2[ParseCaseClass, (Int, LocalDate)] = Table( + val intDateInputs: TableFor2[MagnoliaParseCaseClass, (Int, LocalDate)] = Table( ("implementation", "(Int, LocalDate)"), Seq(1, 0, @@ -22,12 +22,12 @@ class TwoArgsTests extends FlatSpec with TableDrivenPropertyChecks { ).flatMap(i => Seq(LocalDate.of(1970, 1, 1), LocalDate.of(2000, 12, 31)).map((i, _))).flatMap(tup => implementations.map(impl => impl -> tup)): _* ) - forAll(intDateInputs) { (impl: ParseCaseClass, input: (Int, LocalDate)) => + forAll(intDateInputs) { (impl: MagnoliaParseCaseClass, input: (Int, LocalDate)) => assert(impl.to[(Int, LocalDate)](input.toString) === input) } } it should "parse Tuple2[Byte, LocalTime]" in { - val byteTimeInputs: TableFor2[ParseCaseClass, (Byte, LocalTime)] = Table( + val byteTimeInputs: TableFor2[MagnoliaParseCaseClass, (Byte, LocalTime)] = Table( ("implementation", "(Byte, LocalTime)"), Seq(1.toByte, 0.toByte, @@ -35,12 +35,12 @@ class TwoArgsTests extends FlatSpec with TableDrivenPropertyChecks { ).flatMap(i => Seq(LocalTime.of(0, 0, 0), LocalTime.of(23, 59, 59)).map((i, _))).flatMap(tup => implementations.map(impl => impl -> tup)): _* ) - forAll(byteTimeInputs) { (impl: ParseCaseClass, input: (Byte, LocalTime)) => + forAll(byteTimeInputs) { (impl: MagnoliaParseCaseClass, input: (Byte, LocalTime)) => assert(impl.to[(Byte, LocalTime)](input.toString) === input) } } it should "parse Tuple2[Float, Long]" in { - val floatLongInputs: TableFor2[ParseCaseClass, (Float, Long)] = Table( + val floatLongInputs: TableFor2[MagnoliaParseCaseClass, (Float, Long)] = Table( ("implementation", "(Float, Long)"), Seq(1l, 0l, @@ -49,13 +49,13 @@ class TwoArgsTests extends FlatSpec with TableDrivenPropertyChecks { ).flatMap(i => Seq(Float.NegativeInfinity, -3.14159f).map((_, i))).flatMap(tup => implementations.map(impl => impl -> tup)): _* ) - forAll(floatLongInputs) { (impl: ParseCaseClass, input: (Float, Long)) => + forAll(floatLongInputs) { (impl: MagnoliaParseCaseClass, input: (Float, Long)) => assert(impl.to[(Float, Long)](input.toString) === input) } } it should "parse TwoArgsBoolInt" in { - val simpleDoubleInputs: TableFor2[ParseCaseClass, TwoArgsBoolInt] = Table( + val simpleDoubleInputs: TableFor2[MagnoliaParseCaseClass, TwoArgsBoolInt] = Table( ("implementation", "TwoArgsBoolInt"), Seq(1, 0, @@ -63,7 +63,7 @@ class TwoArgsTests extends FlatSpec with TableDrivenPropertyChecks { ).flatMap(i => Seq(true, false).map((i, _))).flatMap(tup => implementations.map(impl => impl -> TwoArgsBoolInt(tup._2, tup._1))): _* ) - forAll(simpleDoubleInputs) { (impl: ParseCaseClass, input: TwoArgsBoolInt) => + forAll(simpleDoubleInputs) { (impl: MagnoliaParseCaseClass, input: TwoArgsBoolInt) => assert(impl.to[TwoArgsBoolInt](input.toString) === input) } diff --git a/src/test/scala/com/github/aborg0/caseyclassy/VarArgsTests.scala b/src/test/scala/com/github/aborg0/caseyclassy/VarArgsTests.scala index cbb245f..820d337 100644 --- a/src/test/scala/com/github/aborg0/caseyclassy/VarArgsTests.scala +++ b/src/test/scala/com/github/aborg0/caseyclassy/VarArgsTests.scala @@ -1,25 +1,25 @@ package com.github.aborg0.caseyclassy import com.github.aborg0.caseyclassy.example.{OnlyVarArgs, StringPlusVarArgs} -import org.scalatest.WordSpec +import org.scalatest.wordspec.AnyWordSpec import org.scalatest.prop.{TableDrivenPropertyChecks, TableFor1, TableFor2} -class VarArgsTests extends WordSpec with TableDrivenPropertyChecks { - val implementations: TableFor1[ParseCaseClass] = Table("implementation", RegexParseCaseClass) +class VarArgsTests extends AnyWordSpec with TableDrivenPropertyChecks { + val implementations: TableFor1[MagnoliaParseCaseClass] = Table("implementation", FastParseMagnoliaParseCaseClass) - import RegexParseCaseClass._ + import FastParseMagnoliaParseCaseClass._ "ParseCaseClass for variable arity arguments successfully parse" when { "OnlyVarArgs" should { "has empty args" in { forAll( Table(("implementation", "OnlyVarArgs[Int]"), implementations.map(impl => impl -> OnlyVarArgs[Int]()): _*)) { - (impl: ParseCaseClass, input: OnlyVarArgs[Int]) => + (impl: MagnoliaParseCaseClass, input: OnlyVarArgs[Int]) => assert(impl.to[OnlyVarArgs[Int]](input.toString) === input) } } "has one argument" in { - val simpleVarArgsInputs: TableFor2[ParseCaseClass, OnlyVarArgs[Int]] = Table( + val simpleVarArgsInputs: TableFor2[MagnoliaParseCaseClass, OnlyVarArgs[Int]] = Table( ("implementation", "OnlyVarArgs[Int]"), Seq(1, 0, @@ -28,12 +28,12 @@ class VarArgsTests extends WordSpec with TableDrivenPropertyChecks { Int.MinValue).flatMap(i => implementations.map(impl => impl -> OnlyVarArgs[Int](i))): _* ) - forAll(simpleVarArgsInputs) { (impl: ParseCaseClass, input: OnlyVarArgs[Int]) => + forAll(simpleVarArgsInputs) { (impl: MagnoliaParseCaseClass, input: OnlyVarArgs[Int]) => assert(impl.to[OnlyVarArgs[Int]](input.toString) === input) } } "has two arguments" in { - val varArgsInputs: TableFor2[ParseCaseClass, OnlyVarArgs[Int]] = Table( + val varArgsInputs: TableFor2[MagnoliaParseCaseClass, OnlyVarArgs[Int]] = Table( ("implementation", "OnlyVarArgs[Int]"), Seq(1, 0, @@ -42,12 +42,12 @@ class VarArgsTests extends WordSpec with TableDrivenPropertyChecks { Int.MinValue).flatMap(i => implementations.map(impl => impl -> OnlyVarArgs[Int](i, -i))): _* ) - forAll(varArgsInputs) { (impl: ParseCaseClass, input: OnlyVarArgs[Int]) => + forAll(varArgsInputs) { (impl: MagnoliaParseCaseClass, input: OnlyVarArgs[Int]) => assert(impl.to[OnlyVarArgs[Int]](input.toString) === input) } } "has three arguments" in { - val varArgsInputs: TableFor2[ParseCaseClass, OnlyVarArgs[Int]] = Table( + val varArgsInputs: TableFor2[MagnoliaParseCaseClass, OnlyVarArgs[Int]] = Table( ("implementation", "OnlyVarArgs[Int]"), Seq(1, 0, @@ -56,23 +56,23 @@ class VarArgsTests extends WordSpec with TableDrivenPropertyChecks { Int.MinValue).flatMap(i => implementations.map(impl => impl -> OnlyVarArgs[Int](3, i, -i))): _* ) - forAll(varArgsInputs) { (impl: ParseCaseClass, input: OnlyVarArgs[Int]) => + forAll(varArgsInputs) { (impl: MagnoliaParseCaseClass, input: OnlyVarArgs[Int]) => assert(impl.to[OnlyVarArgs[Int]](input.toString) === input) } } } "StringPlusVarArgs" should { "with String + no arguments" in { - val simpleStringPlusVarArgs: TableFor2[ParseCaseClass, StringPlusVarArgs[Short]] = Table( + val simpleStringPlusVarArgs: TableFor2[MagnoliaParseCaseClass, StringPlusVarArgs[Short]] = Table( ("implementation", "StringPlusVarArgs[Short]"), Seq("", "Hello", "3").flatMap(s => implementations.map(impl => impl -> StringPlusVarArgs[Short](s))): _* ) - forAll(simpleStringPlusVarArgs) { (impl: ParseCaseClass, input: StringPlusVarArgs[Short]) => + forAll(simpleStringPlusVarArgs) { (impl: MagnoliaParseCaseClass, input: StringPlusVarArgs[Short]) => assert(impl.to[StringPlusVarArgs[Short]](input.toString) === input) } } "with String + one argument" in { - val stringPlusVarArgs: TableFor2[ParseCaseClass, StringPlusVarArgs[Short]] = Table( + val stringPlusVarArgs: TableFor2[MagnoliaParseCaseClass, StringPlusVarArgs[Short]] = Table( ("implementation", "StringPlusVarArgs[Short]"), Seq("", "Hello", "3").flatMap(s => Seq(1.toShort, 0.toShort, @@ -81,12 +81,12 @@ class VarArgsTests extends WordSpec with TableDrivenPropertyChecks { Short.MinValue).flatMap(i => implementations.map(impl => impl -> StringPlusVarArgs(s, i)))): _* ) - forAll(stringPlusVarArgs) { (impl: ParseCaseClass, input: StringPlusVarArgs[Short]) => + forAll(stringPlusVarArgs) { (impl: MagnoliaParseCaseClass, input: StringPlusVarArgs[Short]) => assert(impl.to[StringPlusVarArgs[Short]](input.toString) === input) } } "with String + two arguments" in { - val stringPlusVarArgs: TableFor2[ParseCaseClass, StringPlusVarArgs[Short]] = Table( + val stringPlusVarArgs: TableFor2[MagnoliaParseCaseClass, StringPlusVarArgs[Short]] = Table( ("implementation", "StringPlusVarArgs[Short]"), Seq("", "Hello", "3").flatMap(s => Seq(1.toShort, 0.toShort, @@ -95,7 +95,7 @@ class VarArgsTests extends WordSpec with TableDrivenPropertyChecks { Short.MinValue).flatMap(i => implementations.map(impl => impl -> StringPlusVarArgs(s, i, 2.toShort)))): _* ) - forAll(stringPlusVarArgs) { (impl: ParseCaseClass, input: StringPlusVarArgs[Short]) => + forAll(stringPlusVarArgs) { (impl: MagnoliaParseCaseClass, input: StringPlusVarArgs[Short]) => assert(impl.to[StringPlusVarArgs[Short]](input.toString) === input) } }