Skip to content

Commit

Permalink
feat: Add datatype for holding statically sized array data (#189)
Browse files Browse the repository at this point in the history
Fixes #178
  • Loading branch information
markehammons authored May 20, 2023
1 parent b67b2e8 commit 2248343
Show file tree
Hide file tree
Showing 3 changed files with 244 additions and 0 deletions.
93 changes: 93 additions & 0 deletions core/src/fr/hammons/slinc/SetSizeArray.scala
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
148 changes: 148 additions & 0 deletions core/test/src/fr/hammons/slinc/SetSizeArraySpec.scala
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)
3 changes: 3 additions & 0 deletions j19/test/src/fr/hammons/slinc/TypeSpec19.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
package fr.hammons.slinc

class TypeSpec19 extends TypesSpec(Slinc19.default)

0 comments on commit 2248343

Please sign in to comment.