-
Notifications
You must be signed in to change notification settings - Fork 5
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: Add datatype for holding statically sized array data (#189)
Fixes #178
- Loading branch information
1 parent
b67b2e8
commit 2248343
Showing
3 changed files
with
244 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
package fr.hammons.slinc | ||
|
||
class TypeSpec19 extends TypesSpec(Slinc19.default) |