From 6f5eb6732d2da0accb23dc36575fbb5ee7122c9f Mon Sep 17 00:00:00 2001 From: Mark Hammons Date: Fri, 19 May 2023 17:15:57 +0200 Subject: [PATCH] feat: Add datatype for holding statically sized array data Fixes #178 --- core/src/fr/hammons/slinc/SetSizeArray.scala | 93 +++++++++++ .../fr/hammons/slinc/SetSizeArraySpec.scala | 148 ++++++++++++++++++ .../src/fr/hammons/slinc/TypeSpec19.scala | 3 + 3 files changed, 244 insertions(+) create mode 100644 core/src/fr/hammons/slinc/SetSizeArray.scala create mode 100644 core/test/src/fr/hammons/slinc/SetSizeArraySpec.scala create mode 100644 j19/test/src/fr/hammons/slinc/TypeSpec19.scala diff --git a/core/src/fr/hammons/slinc/SetSizeArray.scala b/core/src/fr/hammons/slinc/SetSizeArray.scala new file mode 100644 index 0000000..b56cbb7 --- /dev/null +++ b/core/src/fr/hammons/slinc/SetSizeArray.scala @@ -0,0 +1,93 @@ +package fr.hammons.slinc + +import scala.reflect.ClassTag +import scala.compiletime.ops.int.{`*`, `-`, `<=`, `+`, `<`} +import scala.compiletime.constValue +import scala.quoted.* +import scala.language.experimental.erasedDefinitions +import scala.annotation.experimental + +class SetSizeArray[A, B <: Int] private[slinc] (private val array: Array[A]) + extends AnyVal: + def map[C: ClassTag](fn: A => C): SetSizeArray[C, B] = + new SetSizeArray[C, B](array.map(fn)) + def flatMap[C: ClassTag, D <: Int]( + fn: A => SetSizeArray[C, D] + ): SetSizeArray[C, B * D] = + new SetSizeArray[C, B * D](array.flatMap(fn.andThen(_.array))) + def toSeq: Seq[A] = array.toSeq + inline def take[C <: Int](using + C <= B =:= true, + 0 <= C =:= true + ): SetSizeArray[A, C] = + SetSizeArray.fromArrayUnsafe[C](array.take(constValue[C])) + inline def drop[C <: Int](using + 0 <= B - C =:= true, + 0 <= C =:= true + ): SetSizeArray[A, B - C] = + SetSizeArray.fromArrayUnsafe[B - C](array.drop(constValue[C])) + def forall(fn: A => Boolean): Boolean = array.forall(fn) + def exists(fn: A => Boolean): Boolean = array.exists(fn) + inline def concat[C >: A: ClassTag, D <: Int]( + o: SetSizeArray[C, D] + )(using 0 <= D + B =:= true): SetSizeArray[C, D + B] = + SetSizeArray.fromArrayUnsafe[D + B](array.concat(o.array)) + def isEqual(oArray: SetSizeArray[A, B]): Boolean = + array.zip(oArray.array).forall(_ == _) + + def unsafeApply(index: Int): A = array(index) + def unsafePut(index: Int, value: A): Unit = array(index) = value + inline def apply[C <: Int](using 0 <= C =:= true, C < B =:= true): A = array( + constValue[C] + ) + inline def put[C <: Int]( + value: A + )(using 0 <= C =:= true, C < B =:= true): Unit = array(constValue[C]) = value + +object SetSizeArray: + class SetSizeArrayBuilderUnsafe[B <: Int]: + def apply[A](array: Array[A]): SetSizeArray[A, B] = new SetSizeArray(array) + class SetSizeArrayBuilder[B <: Int](length: B): + def apply[A](array: Array[A]): Option[SetSizeArray[A, B]] = + if length == array.length then Some(new SetSizeArray(array)) else None + + inline def fromArray[B <: Int](using + (0 <= B) =:= true + ): SetSizeArrayBuilder[B] = SetSizeArrayBuilder[B](constValue[B]) + + def fromArrayUnsafe[B <: Int](using + (0 <= B) =:= true + ): SetSizeArrayBuilderUnsafe[B] = SetSizeArrayBuilderUnsafe[B] + + inline def ofDim[B <: Int, A: ClassTag](using + 0 <= B =:= true + ): SetSizeArray[A, B] = + SetSizeArray.fromArrayUnsafe[B](Array.ofDim[A](constValue[B])) + + transparent inline def apply[A: ClassTag](inline a: A*): Any = ${ + knownValues[A]('a, '{ summon[ClassTag[A]] }) + } + private def knownValues[A](values: Expr[Seq[A]], ct: Expr[ClassTag[A]])(using + Quotes, + Type[A] + ): Expr[Any] = + import quotes.reflect.* + values match + case Varargs(vs) => + val n = Apply( + TypeApply( + Select( + New( + Applied( + TypeTree.of[SetSizeArray], + List(TypeTree.of[A], Singleton(Literal(IntConstant(vs.size)))) + ) + ), + TypeRepr.of[SetSizeArray].classSymbol.get.primaryConstructor + ), + List(TypeTree.of[A], Singleton(Literal(IntConstant(vs.size)))) + ), + List('{ Array($values*)(using $ct) }.asTerm) + ) + + n.asExpr diff --git a/core/test/src/fr/hammons/slinc/SetSizeArraySpec.scala b/core/test/src/fr/hammons/slinc/SetSizeArraySpec.scala new file mode 100644 index 0000000..730927d --- /dev/null +++ b/core/test/src/fr/hammons/slinc/SetSizeArraySpec.scala @@ -0,0 +1,148 @@ +package fr.hammons.slinc + +class SetSizeArraySpec extends munit.FunSuite: + test("instantiate"): + val result = compileErrors("SetSizeArray(1,2,3)") + assertNoDiff(result, "") + + test("fromArray"): + { + val result = compileErrors("SetSizeArray.fromArray[-1](Array(1,2,3))") + val expected = + """|error: Cannot prove that (0 : Int) <= (-1 : Int) =:= (true : Boolean). + |SetSizeArray.fromArray[-1](Array(1,2,3)) + | ^ + |""".stripMargin + assertNoDiff(result, expected) + } + { + val result = compileErrors("SetSizeArray.fromArray[Int](Array(1,2,3))") + val expected = + """|error: Cannot prove that (0 : Int) <= Int =:= (true : Boolean). + |SetSizeArray.fromArray[Int](Array(1,2,3)) + | ^ + |""".stripMargin + assertNoDiff(result, expected) + } + + assert(SetSizeArray.fromArray[5](Array(1, 2)).isEmpty) + assert(SetSizeArray.fromArray[5](Array(1, 2, 3, 4, 5, 6)).isEmpty) + assert(SetSizeArray.fromArray[5](Array(1, 2, 3, 4, 5)).isDefined) + + test("fromArrayUnsafe"): + val result = + compileErrors("SetSizeArray.fromArrayUnsafe[Int](Array(1,2))") + val expected = + """|error: Cannot prove that (0 : Int) <= Int =:= (true : Boolean). + |SetSizeArray.fromArrayUnsafe[Int](Array(1,2)) + | ^ + |""".stripMargin + assertNoDiff(result, expected) + + test("apply"): + val result = compileErrors("SetSizeArray(1,2,3)[3]") + val expected = + """|error: Cannot prove that (3 : Int) < (3 : Int) =:= (true : Boolean). + |SetSizeArray(1,2,3)[3] + | ^""".stripMargin + assertNoDiff(result, expected) + + assertEquals(SetSizeArray(1, 2, 3)[1], 2) + + test("put"): + val result = compileErrors("SetSizeArray(1,2,3).put[4](4)") + val expected = + """|error: Cannot prove that (4 : Int) < (3 : Int) =:= (true : Boolean). + |SetSizeArray(1,2,3).put[4](4) + | ^""".stripMargin + + assertNoDiff(result, expected) + + val arr = SetSizeArray.ofDim[3, Int] + arr.put[1](4) + assertEquals(arr[1], 4) + + test("isEqual"): + assert(SetSizeArray(1, 2, 3).isEqual(SetSizeArray(1, 2, 3))) + assert(!SetSizeArray(1, 2, 3).isEqual(SetSizeArray(2, 4, 6))) + + test("map"): + assert(SetSizeArray(1, 2, 3).map(_ * 2).isEqual(SetSizeArray(2, 4, 6))) + + test("flatmap"): + assert( + SetSizeArray(1, 2, 3, 4, 5, 6, 7, 8, 9, 10) + .flatMap(_ => SetSizeArray.ofDim[0, Int]) + .isEqual(SetSizeArray.ofDim[0, Int]) + ) + + assert( + SetSizeArray(1, 2, 3) + .flatMap(v => SetSizeArray(v - 1, v - 2, v - 3)) + .isEqual( + SetSizeArray( + 0, -1, -2, 1, 0, -1, 2, 1, 0 + ) + ) + ) + + test("concat"): + val a = SetSizeArray(1, 2) + val b = SetSizeArray(3, 4) + assert( + a.concat(b).isEqual(SetSizeArray(1, 2, 3, 4)) + ) + + val result = + compileErrors("SetSizeArray(1,2,3).concat(SetSizeArray('4','5'))") + val expected = """|error: Cannot prove that (0 : Int) <= Int + (3 : Int) =:= (true : Boolean). + |SetSizeArray(1,2,3).concat(SetSizeArray('4','5')) + | ^ + |error: + |Found: fr.hammons.slinc.SetSizeArray[Char, (2 : Int)] + |Required: fr.hammons.slinc.SetSizeArray[Int, Int] + | + |One of the following imports might make progress towards fixing the problem: + | + | import fr.hammons.slinc.container.Container.getContainer + | import munit.Clue.generate + | + |SetSizeArray(1,2,3).concat(SetSizeArray('4','5')) + | ^""".stripMargin + assertNoDiff(result, expected) + + test("drop"): + { + val result = compileErrors("SetSizeArray(1,2,3).drop[4]") + val expected = + """|error: Cannot prove that (0 : Int) <= (3 : Int) - (4 : Int) =:= (true : Boolean). + |SetSizeArray(1,2,3).drop[4] + | ^""".stripMargin + + assertNoDiff(result, expected) + } + + { + val result = compileErrors("SetSizeArray(1,2,3).drop[-1]") + val expected = + """|error: Cannot prove that (0 : Int) <= (-1 : Int) =:= (true : Boolean). + |SetSizeArray(1,2,3).drop[-1] + | ^""".stripMargin + + assertNoDiff(result, expected) + } + + assert( + SetSizeArray(1, 2, 3).drop[1].isEqual(SetSizeArray(2, 3)) + ) + + test("take"): + assert( + SetSizeArray(1, 2, 3).take[2].isEqual(SetSizeArray(1, 2)) + ) + + test("forall"): + assertEquals(SetSizeArray(1, 2, 3).forall(_ > 0), true) + + test("exists"): + assertEquals(SetSizeArray(1, 2, 3).exists(_ > 2), true) diff --git a/j19/test/src/fr/hammons/slinc/TypeSpec19.scala b/j19/test/src/fr/hammons/slinc/TypeSpec19.scala new file mode 100644 index 0000000..80ead24 --- /dev/null +++ b/j19/test/src/fr/hammons/slinc/TypeSpec19.scala @@ -0,0 +1,3 @@ +package fr.hammons.slinc + +class TypeSpec19 extends TypesSpec(Slinc19.default)