diff --git a/core/src/fr/hammons/slinc/Alias.scala b/core/src/fr/hammons/slinc/Alias.scala index 4a6826be..cfaa395d 100644 --- a/core/src/fr/hammons/slinc/Alias.scala +++ b/core/src/fr/hammons/slinc/Alias.scala @@ -6,7 +6,7 @@ trait Alias[T] extends DescriptorOf[T]: val name: String val aliases: PartialFunction[(OS, Arch), RealTypeDescriptor] lazy val descriptor: TypeDescriptor { type Inner >: T <: T } = - new AliasDescriptor[T]( + AliasDescriptor[T]( aliases.applyOrElse( os -> arch, _ => @@ -14,8 +14,4 @@ trait Alias[T] extends DescriptorOf[T]: s"Alias for $name is not defined on platform $os - $arch" ) ) - ): - - val reader = (rwm, _) ?=> (mem, bytes) => rwm.readAlias(mem, bytes, real) - val writer = (rwm, _) ?=> - (mem, bytes, a) => rwm.writeAlias(mem, bytes, real, a) + ) diff --git a/core/src/fr/hammons/slinc/Bytes.scala b/core/src/fr/hammons/slinc/Bytes.scala index a9076973..d75a6a66 100644 --- a/core/src/fr/hammons/slinc/Bytes.scala +++ b/core/src/fr/hammons/slinc/Bytes.scala @@ -1,6 +1,7 @@ package fr.hammons.slinc import scala.quoted.{ToExpr, Quotes} +import types.SizeT opaque type Bytes = Long @@ -15,6 +16,7 @@ object Bytes: inline def -(b: Bytes): Bytes = a - b inline def toLong: Long = a inline def toBits: Long = a * 8 + def sizeT = SizeT.maybe(a).get given Numeric[Bytes] = Numeric.LongIsIntegral given ToExpr[Bytes] with diff --git a/core/src/fr/hammons/slinc/CFunctionBindingGenerator.scala b/core/src/fr/hammons/slinc/CFunctionBindingGenerator.scala index 0b85f0a1..7f4aeb2a 100644 --- a/core/src/fr/hammons/slinc/CFunctionBindingGenerator.scala +++ b/core/src/fr/hammons/slinc/CFunctionBindingGenerator.scala @@ -88,7 +88,9 @@ object CFunctionBindingGenerator: (sym, inputs) => def inputExprs(alloc: Expr[Allocator])(using q: Quotes) = val prefix = if allocatingReturn then List(alloc.asTerm) else Nil - val toTransform = if varArg then inputs.init else inputs + val toTransform = + if varArg && inputs.nonEmpty then inputs.init + else inputs LambdaInputs.choose( prefix .concat(toTransform) @@ -96,7 +98,7 @@ object CFunctionBindingGenerator: .zipWithIndex .map: (exp, i) => '{ $inputTransitions(${ Expr(i) })($alloc, $exp) }, - varArg + varArg && inputs.nonEmpty )(inputs.last.asExprOf[Seq[Variadic]]) '{ diff --git a/core/src/fr/hammons/slinc/CFunctionDescriptor.scala b/core/src/fr/hammons/slinc/CFunctionDescriptor.scala index d32854f0..8a25801b 100644 --- a/core/src/fr/hammons/slinc/CFunctionDescriptor.scala +++ b/core/src/fr/hammons/slinc/CFunctionDescriptor.scala @@ -33,8 +33,8 @@ object CFunctionDescriptor: s"C Function analog ${methodSymbol.fullName} has unsupported type ${t.show}" ) - val isVariadic = argumentTypes.last match - case typ if typ =:= TypeRepr.of[Seq[Variadic]] => + val isVariadic = argumentTypes.lastOption match + case Some(typ) if typ =:= TypeRepr.of[Seq[Variadic]] => true case _ => false diff --git a/core/src/fr/hammons/slinc/TypeDescriptor.scala b/core/src/fr/hammons/slinc/TypeDescriptor.scala index 8d7d7511..3c1c8df4 100644 --- a/core/src/fr/hammons/slinc/TypeDescriptor.scala +++ b/core/src/fr/hammons/slinc/TypeDescriptor.scala @@ -146,15 +146,23 @@ trait StructDescriptor( val transform: Tuple => Product ) extends RealTypeDescriptor -trait AliasDescriptor[A](val real: RealTypeDescriptor) extends TypeDescriptor: +case class AliasDescriptor[A](val real: RealTypeDescriptor) + extends TypeDescriptor: type Inner = A + type RealInner = real.Inner - given bkwd: Conversion[Inner, real.Inner] with + given bkwd: Conversion[Inner, RealInner] with def apply(x: Inner): real.Inner = x.asInstanceOf[real.Inner] - given fwd: Conversion[real.Inner, Inner] with + given fwd: Conversion[RealInner, Inner] with def apply(x: real.Inner): Inner = x.asInstanceOf[Inner] + val reader: (ReadWriteModule, DescriptorModule) ?=> Reader[Inner] = + (rwm, _) ?=> (mem, bytes) => rwm.readAlias(mem, bytes, real) + + val writer: (ReadWriteModule, DescriptorModule) ?=> Writer[Inner] = + (rwm, _) ?=> (mem, bytes, a) => rwm.writeAlias(mem, bytes, real, a) + override def size(using dm: DescriptorModule): Bytes = dm.sizeOf(real) override def alignment(using dm: DescriptorModule): Bytes = dm.alignmentOf(real) diff --git a/core/src/fr/hammons/slinc/types/CLong.scala b/core/src/fr/hammons/slinc/types/CLong.scala index cc479693..eb223009 100644 --- a/core/src/fr/hammons/slinc/types/CLong.scala +++ b/core/src/fr/hammons/slinc/types/CLong.scala @@ -14,9 +14,8 @@ object CLong: case i: Int => IntegralAlias.transform[CLong](i) def maybe(maybeFits: Long): Option[CLong] = - if (maybeFits <= Int.MaxValue && maybeFits >= Int.MinValue) || IntegralAlias - .range[CLong] - .contains(maybeFits) + if (maybeFits <= Int.MaxValue && maybeFits >= Int.MinValue) || (IntegralAlias + .min[CLong] <= maybeFits && maybeFits <= IntegralAlias.max[CLong]) then Some(IntegralAlias.transform[CLong](maybeFits)) else None diff --git a/core/src/fr/hammons/slinc/types/IntegralAlias.scala b/core/src/fr/hammons/slinc/types/IntegralAlias.scala index cdc22f7d..29fe7ec3 100644 --- a/core/src/fr/hammons/slinc/types/IntegralAlias.scala +++ b/core/src/fr/hammons/slinc/types/IntegralAlias.scala @@ -7,19 +7,59 @@ import fr.hammons.slinc.ShortDescriptor import fr.hammons.slinc.IntDescriptor object IntegralAlias: + def min[T](using a: Alias[T]): Long = a.aliases.applyOrElse( + (os, arch), + _ => throw new Error(s"Alias for ${a.name} not defined for this platform") + ) match + case ByteDescriptor => Byte.MinValue.toLong + case ShortDescriptor => Short.MinValue.toLong + case IntDescriptor => Int.MinValue.toLong + case LongDescriptor => Long.MinValue + case _ => + throw new Error( + s"${a.name} is not an alias for an integral type on $os $arch" + ) + + def max[T](using a: Alias[T]): Long = a.aliases.applyOrElse( + (os, arch), + _ => throw new Error(s"Alias for ${a.name} not defined for this platform") + ) match + case ByteDescriptor => Byte.MaxValue.toLong + case ShortDescriptor => Short.MaxValue.toLong + case IntDescriptor => Int.MaxValue.toLong + case LongDescriptor => Long.MaxValue + case _ => + throw new Error( + s"${a.name} is not an alias for an integral type on $os $arch" + ) + def range[T](using a: Alias[T]) = a.aliases.applyOrElse( (os, arch), _ => throw new Error(s"Alias for ${a.name} not defined for this platform") ) match - case ByteDescriptor => Byte.MinValue to Byte.MaxValue - case ShortDescriptor => Short.MinValue to Short.MaxValue - case IntDescriptor => Int.MinValue to Int.MaxValue - case LongDescriptor => Long.MinValue to Long.MaxValue + case ByteDescriptor => Range.Long.inclusive(Byte.MinValue, Byte.MaxValue, 1) + case ShortDescriptor => + Range.Long.inclusive(Short.MinValue, Short.MaxValue, 1) + case IntDescriptor => Range.Long.inclusive(Int.MinValue, Int.MaxValue, 1) + case LongDescriptor => Long.MinValue to Long.MaxValue case _ => throw new Error( s"${a.name} is not an alias for an integral type on $os $arch" ) + def toLong[T](value: T)(using a: Alias[T]) = a.aliases.applyOrElse( + (os, arch), + _ => throw new Error(s"Alias for ${a.name} not defined for this platform") + ) match + case ByteDescriptor => value.asInstanceOf[Byte].toLong + case ShortDescriptor => value.asInstanceOf[Short].toLong + case IntDescriptor => value.asInstanceOf[Int].toLong + case LongDescriptor => value.asInstanceOf[Long].toLong + case _ => + throw new Error( + s"${a.name} is not an alias for an integral type on $os - $arch" + ) + def transform[T]: Transform[T] = Transform[T] class Transform[T]: def apply[U](value: U)(using a: Alias[T], n: Numeric[U]) = diff --git a/core/src/fr/hammons/slinc/types/SizeT.scala b/core/src/fr/hammons/slinc/types/SizeT.scala index 2fe1cc16..e470d1a1 100644 --- a/core/src/fr/hammons/slinc/types/SizeT.scala +++ b/core/src/fr/hammons/slinc/types/SizeT.scala @@ -5,12 +5,6 @@ import fr.hammons.slinc.LongDescriptor opaque type SizeT = AnyVal -given Alias[SizeT] with - val name = "SizeT" - val aliases = { case (OS.Linux | OS.Darwin | OS.Windows, Arch.X64) => - LongDescriptor - } - object SizeT: def apply(value: Short | Byte): SizeT = value match case s: Short => IntegralAlias.transform[SizeT](s) @@ -24,3 +18,9 @@ object SizeT: if value < 65536 || IntegralAlias.range[SizeT].contains(value) then Some(IntegralAlias.transform[SizeT](value)) else None + + given Alias[SizeT] with + val name = "SizeT" + val aliases = { case (OS.Linux | OS.Darwin | OS.Windows, Arch.X64) => + LongDescriptor + } diff --git a/core/src/fr/hammons/slinc/types/TimeT.scala b/core/src/fr/hammons/slinc/types/TimeT.scala index 79746865..94045ea4 100644 --- a/core/src/fr/hammons/slinc/types/TimeT.scala +++ b/core/src/fr/hammons/slinc/types/TimeT.scala @@ -5,12 +5,6 @@ import fr.hammons.slinc.LongDescriptor opaque type TimeT = Any -given Alias[TimeT] with - val name: String = "TimeT" - val aliases = { case (OS.Windows | OS.Linux | OS.Darwin, Arch.X64) => - LongDescriptor - } - object TimeT: def maybe(value: Byte | Short | Int | Long): Option[TimeT] = val upcast = value match @@ -22,3 +16,9 @@ object TimeT: if IntegralAlias.range[TimeT].contains(upcast) then Some(IntegralAlias.transform[TimeT](upcast)) else None + + given Alias[TimeT] with + val name: String = "TimeT" + val aliases = { case (OS.Windows | OS.Linux | OS.Darwin, Arch.X64) => + LongDescriptor + } diff --git a/core/test/src/fr/hammons/slinc/StdlibSpec.scala b/core/test/src/fr/hammons/slinc/StdlibSpec.scala index 0884a292..3a0d940b 100644 --- a/core/test/src/fr/hammons/slinc/StdlibSpec.scala +++ b/core/test/src/fr/hammons/slinc/StdlibSpec.scala @@ -1,75 +1,74 @@ package fr.hammons.slinc -import fr.hammons.slinc.StdlibSpec.div_t import scala.util.Random import munit.ScalaCheckSuite import org.scalacheck.Prop.* import org.scalacheck.Gen import org.scalacheck.Arbitrary -import fr.hammons.slinc.types.OS -import scala.annotation.nowarn +import fr.hammons.slinc.types.CInt +import fr.hammons.slinc.types.{CLong, SizeT, TimeT} +import fr.hammons.slinc.types.CChar +import fr.hammons.slinc.types.CDouble +import fr.hammons.slinc.types.IntegralAlias +import types.{OS, os} //todo: remove when https://github.com/lampepfl/dotty/issues/16876 is fixed trait StdlibSpec(val slinc: Slinc) extends ScalaCheckSuite: - import slinc.{given, *} - - object Cstd derives Library: - def abs(a: Int): Int = Library.binding - def labs(l: CLong): CLong = Library.binding - def div(a: Int, b: Int): div_t = Library.binding - // def ldiv(a: Long, b: Long): ldiv_t = Library.binding - // def lldiv(a: Long, b: Long): lldiv_t = Library.binding - def rand(): Int = Library.binding - def qsort[A]( - array: Ptr[A], + import slinc.{Null, given} + + case class div_t(quot: CInt, rem: CInt) derives Struct + case class ldiv_t(quot: CLong, rem: CLong) derives Struct + trait Cstd derives Lib: + def abs(a: CInt): CInt + def labs(l: CLong): CLong + def div(a: CInt, b: CInt): div_t + def ldiv(a: CLong, b: CLong): ldiv_t + def rand(): CInt + def qsort( + array: Ptr[Nothing], num: SizeT, size: SizeT, - fn: Ptr[(Ptr[A], Ptr[A]) => Int] - ): Unit = Library.binding - def sprintf(ret: Ptr[Byte], string: Ptr[Byte], args: Variadic*): Unit = - Library.binding - def atof(str: Ptr[Byte]): CDouble = Library.binding - def strtod(str: Ptr[Byte], endptr: Ptr[Ptr[Byte]]): CDouble = - Library.binding + fn: Ptr[(Ptr[Nothing], Ptr[Nothing]) => CInt] + ): Unit + def sprintf(ret: Ptr[CChar], string: Ptr[CChar], args: Seq[Variadic]): Unit + def atof(str: Ptr[CChar]): CDouble + def strtod(str: Ptr[CChar], endptr: Ptr[Ptr[CChar]]): CDouble given Struct[div_t] = Struct.derived + val cstd = Lib.instance[Cstd] + property("abs gives back absolute integers") { forAll { (i: Int) => - assertEquals(Cstd.abs(i), i.abs) + assertEquals(cstd.abs(i), i.abs) } } test("abs") { - assertEquals(Cstd.abs(-4), 4) + assertEquals(cstd.abs(-4), 4) } + val clongChoose = + Gen + .choose(IntegralAlias.min[CLong], IntegralAlias.max[CLong]) + .map(CLong.maybe) + .map(_.get) + property("labs gives back absolute CLongs") { - platformFocus(x64.Linux) { - forAll(Gen.choose(Long.MinValue + 1, Long.MaxValue)) { (l: Long) => - assertEquals(Cstd.labs(l): Long, l.abs) + forAll(clongChoose) { (c: CLong) => + assertEquals( + IntegralAlias.toLong(cstd.labs(c)), + IntegralAlias.toLong(c).abs + ) + } - } - }.orElse( - platformFocus(x64.Mac) { - forAll(Gen.choose(Long.MinValue + 1, Long.MaxValue)) { (l: Long) => - assertEquals(Cstd.labs(l): Long, l.abs) - } - } - ).orElse( - platformFocus(x64.Windows) { - forAll(Gen.choose(Int.MinValue + 1, Int.MaxValue)) { (i: Int) => - assertEquals(Cstd.labs(i): Int, i.abs) - } - } - ).getOrElse(assume(false, os)) } property("div calculates quotient and remainder") { val validIntRange = Gen.oneOf(Gen.choose(Int.MinValue + 1, -1), Gen.choose(1, Int.MaxValue)) forAll(validIntRange, validIntRange) { (a: Int, b: Int) => - val result = Cstd.div(a, b) + val result = cstd.div(a, b) assertEquals(result.quot, a / b) assertEquals(result.rem, a % b) @@ -77,66 +76,75 @@ trait StdlibSpec(val slinc: Slinc) extends ScalaCheckSuite: } test("div") { - assertEquals(Cstd.div(5, 2), div_t(2, 1)) + assertEquals(cstd.div(5, 2), div_t(2, 1)) } - /* test("ldiv") { - assertEquals(Cstd.ldiv(5L, 2L), ldiv_t(2L, 1L)) + assertEquals(cstd.ldiv(CLong(5), CLong(2)), ldiv_t(CLong(2), CLong(1))) } + /* test("lldiv") { assertEquals(Cstd.lldiv(5L, 2L), lldiv_t(2L, 1L)) } */ test("rand") { - assertNotEquals(Cstd.rand(), Cstd.rand()) - } - - property("qsort should sort") { - forAll { (arr: Array[Int]) => - Scope.confined { - val cArr = Ptr.copy(arr) - - Cstd.qsort( - cArr, - arr.size.as[SizeT], - 4.as[SizeT], - Ptr.upcall((a, b) => - val aVal = !a - val bVal = !b + assertNotEquals(cstd.rand(), cstd.rand()) + } + + // todo: this should generate arrays of max length of SizeT + property("qsort should sort"): + forAll: (arr: Array[Int]) => + Scope + .confined: + val cArr = Ptr.copy(arr).castTo[Nothing] + + SizeT + .maybe(arr.length) + .map: len => + cstd.qsort( + cArr, + len, + DescriptorOf[Int].size.sizeT, + Ptr.upcall((a: Ptr[Nothing], b: Ptr[Nothing]) => + val aVal = !a.castTo[CInt] + val bVal = !b.castTo[CInt] + if aVal < bVal then -1 + else if aVal == bVal then 0 + else 1 + ) + ) + assertEquals( + cArr.castTo[Int].asArray(arr.length).toSeq, + arr.sorted.toSeq + ) + .getOrElse(fail("Array too long for platform")) + + test("qsort"): + val testArray = Random.shuffle(Array.fill(1024)(Random.nextInt())).toArray + + Scope.confined: + val arr = Ptr.copy(testArray).castTo[Nothing] + + val size = SizeT.apply(1024: Short) + + cstd.qsort( + arr, + size, + DescriptorOf[CInt].size.sizeT, + Ptr.upcall: (a, b) => + val aVal = !a.castTo[CInt] + val bVal = !b.castTo[CInt] if aVal < bVal then -1 else if aVal == bVal then 0 else 1 - ) ) - assertEquals(cArr.asArray(arr.length).toSeq, arr.sorted.toSeq) - } - } - - } - test("qsort") { - val testArray = Random.shuffle(Array.fill(1024)(Random.nextInt())).toArray - Scope.confined { - val arr = Ptr.copy(testArray) - - Cstd.qsort( - arr, - testArray.size.as[SizeT], - 4.as[SizeT], - Ptr.upcall((a, b) => - val aVal = !a - val bVal = !b - if aVal < bVal then -1 - else if aVal == bVal then 0 - else 1 + assertEquals( + arr.castTo[CInt].asArray(testArray.size).toSeq, + testArray.sorted.toSeq ) - ) - - assertEquals(arr.asArray(testArray.size).toSeq, testArray.sorted.toSeq) - } - } + // end qsort test property("sprintf should format") { forAll(Arbitrary.arbitrary[Int], Gen.asciiPrintableStr) { (i, s) => @@ -146,7 +154,7 @@ trait StdlibSpec(val slinc: Slinc) extends ScalaCheckSuite: // ascii chars only assertEquals(format.copyIntoString(200), "%i %s") - Cstd.sprintf(buffer, format, i, Ptr.copy(s)) + cstd.sprintf(buffer, format, Seq(i, Ptr.copy(s))) assertEquals(buffer.copyIntoString(256), s"$i $s") } } @@ -158,36 +166,36 @@ trait StdlibSpec(val slinc: Slinc) extends ScalaCheckSuite: val buffer = Ptr.blankArray[Byte](256) assertEquals(format.copyIntoString(200), "%i hello: %s %i") - Cstd.sprintf(buffer, format, 1, Ptr.copy("hello"), 2) + cstd.sprintf(buffer, format, Seq(1, Ptr.copy("hello"), 2)) assertEquals(buffer.copyIntoString(256), "1 hello: hello 2") } } - test("time") { + test("time"): + val current = System.currentTimeMillis() / 1000 + val time = if os == OS.Windows then + trait Time derives Lib: + def _time64(timer: Ptr[TimeT]): TimeT - val current = System.currentTimeMillis() / 1000 - val time = if os == OS.Windows then - object Time derives Library: - def _time64(timer: Ptr[TimeT]): TimeT = Library.binding + val time = Lib.instance[Time] + time._time64(Null[TimeT]) + else + trait Time derives Lib: + def time(timer: Ptr[TimeT]): TimeT - Time._time64(Null[TimeT]) - else - object Time derives Library: - def time(timer: Ptr[TimeT]): TimeT = Library.binding - - Time.time(Null[TimeT]) + val time = Lib.instance[Time] + time.time(Null[TimeT]) + assert( + (IntegralAlias.toLong(time) - current).abs < 5 + ) - assert( - time.maybeAs[Long].map(_ - current).map(_.abs).forall(_ < 5), - time.maybeAs[Long].map(_ - current).map(_.abs) - ) - } + // end test time property("atof convert strings to floats") { forAll { (d: Double) => Scope.confined { val pStr = Ptr.copy(f"$d%f") - assertEqualsDouble(Cstd.atof(pStr), d, 0.1) + assertEqualsDouble(cstd.atof(pStr), d, 0.1) } } } @@ -200,14 +208,14 @@ trait StdlibSpec(val slinc: Slinc) extends ScalaCheckSuite: val pStr0 = Ptr.copy(input) val ans1 = Ptr.blank[Ptr[Byte]] - val a1 = Cstd.strtod(pStr0, ans1) + val a1 = cstd.strtod(pStr0, ans1) assertEqualsDouble(a1, d, 0.1) val pStr1 = !ans1 val r1 = pStr1.copyIntoString(maxSize) assertEquals(r1, f" $d%f") val ans2 = Ptr.blank[Ptr[Byte]] - val a2 = Cstd.strtod(pStr1, ans2) + val a2 = cstd.strtod(pStr1, ans2) assertEqualsDouble(a2, d, 0.1) assertEquals(!(!ans2), 0.toByte) } @@ -221,7 +229,7 @@ trait StdlibSpec(val slinc: Slinc) extends ScalaCheckSuite: val pStr0 = Ptr.copy(input) val ans1 = Ptr.blank[Ptr[Byte]] - val a = Cstd.strtod(pStr0, ans1) + val a = cstd.strtod(pStr0, ans1) assertEqualsDouble(a, 0.0d, 0.1) val pStr1 = !ans1 val r1 = pStr1.copyIntoString(maxSize) @@ -230,6 +238,4 @@ trait StdlibSpec(val slinc: Slinc) extends ScalaCheckSuite: } object StdlibSpec: - case class div_t(quot: Int, rem: Int) - case class ldiv_t(quot: Long, rem: Long) case class lldiv_t(quot: Long, rem: Long)