diff --git a/README.md b/README.md index 601c04c..e9ecda7 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ repositories { } dependencies { - implementation("me.alex_s168:blitz:0.24.1") + implementation("me.alex_s168:blitz:0.25") } ``` diff --git a/build.gradle.kts b/build.gradle.kts index 5a82abe..d519e2a 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -2,10 +2,11 @@ plugins { kotlin("jvm") version "1.9.21" application `maven-publish` + id("org.jetbrains.dokka") version "2.0.0" } group = "me.alex_s168" -version = "0.24.1" +version = "0.25" repositories { mavenCentral() diff --git a/src/main/kotlin/blitz/Defer.kt b/src/main/kotlin/blitz/Defer.kt index 2b24f81..44281e7 100644 --- a/src/main/kotlin/blitz/Defer.kt +++ b/src/main/kotlin/blitz/Defer.kt @@ -1,5 +1,7 @@ package blitz +import blitz.collections.RefVec + fun interface DeferScope { fun defer(block: () -> Unit) } @@ -28,9 +30,9 @@ interface ExecutionScope: DeferScope { } fun resourceScoped(block: ExecutionScope.() -> R): R { - val defer = mutableListOf<() -> Unit>() - val onError = mutableListOf<() -> Unit>() - val scope = ExecutionScope.create(defer::add, onError::add) { + val defer = RefVec<() -> Unit>() + val onError = RefVec<() -> Unit>() + val scope = ExecutionScope.create(defer::pushBack, onError::pushBack) { throw Exception("Manual error triggered") } val ex: Exception diff --git a/src/main/kotlin/blitz/Either.kt b/src/main/kotlin/blitz/Either.kt index 8fd6e09..602f5aa 100644 --- a/src/main/kotlin/blitz/Either.kt +++ b/src/main/kotlin/blitz/Either.kt @@ -1,14 +1,19 @@ package blitz +import blitz.annotations.DontAccessManually + +@Suppress("UNCHECKED_CAST", "NOTHING_TO_INLINE") class Either private constructor( aIn: A?, bIn: B? ) { /** DO NOT SET MANUALLY!!! */ @JvmField + @DontAccessManually var a: A? = aIn /** DO NOT SET MANUALLY!!! */ @JvmField + @DontAccessManually var b: B? = bIn override fun equals(other: Any?): Boolean = @@ -42,7 +47,10 @@ class Either private constructor( } } +@Suppress("NOTHING_TO_INLINE") inline fun Either.isA() = a != null + +@Suppress("NOTHING_TO_INLINE") inline fun Either.isB() = b != null inline fun Either.getAOr(prov: Provider): A = diff --git a/src/main/kotlin/blitz/Endian.kt b/src/main/kotlin/blitz/Endian.kt index edb2c6b..b61ad93 100644 --- a/src/main/kotlin/blitz/Endian.kt +++ b/src/main/kotlin/blitz/Endian.kt @@ -3,17 +3,30 @@ package blitz import java.nio.ByteBuffer import java.nio.ByteOrder -enum class Endian { - LITTLE, - BIG - ; +@JvmInline +value class Endian private constructor(val little: Boolean) { + companion object { + @Deprecated("Renamed", replaceWith = ReplaceWith("Little")) + val LITTLE = Endian(little = true) + @JvmStatic + val Little = Endian(little = true) - infix fun encodeLittle(little: ByteArray) = - if (this == BIG) little.reversedArray() + @Deprecated("Renamed", replaceWith = ReplaceWith("Big")) + val BIG = Endian(little = false) + @JvmStatic + val Big = Endian(little = false) + } + + inline val big get() = !little + + @Suppress("NOTHING_TO_INLINE") + inline infix fun encodeLittle(little: ByteArray) = + if (this.big) little.reversedArray() else little - fun toNIO(): ByteOrder = - if (this == LITTLE) ByteOrder.LITTLE_ENDIAN + @Suppress("NOTHING_TO_INLINE") + inline fun toNIO(): ByteOrder = + if (this.little) ByteOrder.LITTLE_ENDIAN else ByteOrder.BIG_ENDIAN } @@ -22,8 +35,12 @@ fun ByteBuffer.order(endian: Endian): ByteBuffer = fun Long.toBytes(endian: Endian) = endian encodeLittle - toInt().toBytes(Endian.LITTLE) + - toInt().shr(32).toBytes(Endian.LITTLE) + toInt().toBytes(Endian.Little) + + toInt().shr(32).toBytes(Endian.Little) + +@Suppress("NOTHING_TO_INLINE") +inline fun ULong.toBytes(endian: Endian) = + toLong().toBytes(endian) fun Int.toBytes(endian: Endian) = endian encodeLittle byteArrayOf( @@ -33,19 +50,22 @@ fun Int.toBytes(endian: Endian) = this.shr(24).and(0xFF).toByte() ) -fun UInt.toBytes(endian: Endian) = +@Suppress("NOTHING_TO_INLINE") +inline fun UInt.toBytes(endian: Endian) = toInt().toBytes(endian) fun Short.toBytes(endian: Endian) = - endian encodeLittle toInt().toBytes(Endian.LITTLE).copyOf(2) + endian encodeLittle toInt().toBytes(Endian.Little).copyOf(2) -fun UShort.toBytes(endian: Endian) = +@Suppress("NOTHING_TO_INLINE") +inline fun UShort.toBytes(endian: Endian) = toShort().toBytes(endian) fun Byte.toBytes() = byteArrayOf(this) -fun UByte.toBytes() = +@Suppress("NOTHING_TO_INLINE") +inline fun UByte.toBytes() = toByte().toBytes() // TODO: no cheat @@ -68,8 +88,10 @@ fun ByteArray.toLong(endian: Endian) = fun ByteArray.toULong(endian: Endian) = toLong(endian).toULong() -fun ByteArray.toByte() = +@Suppress("NOTHING_TO_INLINE") +inline fun ByteArray.toByte() = this[0] -fun ByteArray.toUByte() = +@Suppress("NOTHING_TO_INLINE") +inline fun ByteArray.toUByte() = this[0].toUByte() \ No newline at end of file diff --git a/src/main/kotlin/blitz/IO.kt b/src/main/kotlin/blitz/IO.kt deleted file mode 100644 index 1a50780..0000000 --- a/src/main/kotlin/blitz/IO.kt +++ /dev/null @@ -1,13 +0,0 @@ -package blitz - -@Deprecated( - level = DeprecationLevel.ERROR, - message = "Will be removed in the future!", - replaceWith = ReplaceWith( - "Terminal.warn", - "blitz.term.Terminal" - ) -) -fun warn(msg: String) { - System.err.println(msg) -} \ No newline at end of file diff --git a/src/main/kotlin/blitz/OperationChain.kt b/src/main/kotlin/blitz/OperationChain.kt deleted file mode 100644 index f92342d..0000000 --- a/src/main/kotlin/blitz/OperationChain.kt +++ /dev/null @@ -1,87 +0,0 @@ -package blitz - -import blitz.collections.selfInitializingSequence - -class OperationChain private constructor( - private val impl: Impl = Impl() -) { - private var until = 0 - - private class Impl { - val seqe = mutableListOf<(Sequence) -> Sequence>() - - var finalized = false - - fun add(op: Operator<*, *>) { - seqe += { seq: Sequence -> - seq.map(op as Operator) - } - } - - fun addFlat(op: Operator<*, Sequence<*>>) { - seqe += { seq: Sequence -> - seq.flatMap(op as Operator>) - } - } - } - - fun map(op: Operator): OperationChain = - OperationChain(impl.also { it.add(op) }) - .also { it.until = this.until + 1 } - - fun flatMap(op: Operator>): OperationChain = - OperationChain(impl.also { it.addFlat(op) }) - .also { it.until = this.until + 1 } - - fun map(op: OperationChain): OperationChain { - if (!op.impl.finalized) - throw Exception("Can not map un-finalized operation chain onto operation chain!") - return flatMap(op::process) - } - - fun modifier(op: Operator, Sequence>): OperationChain = - OperationChain(impl.also { it.seqe.add(op as (Sequence) -> Sequence) }) - .also { it.until = this.until + 1 } - - fun finalize(): OperationChain { - if (impl.finalized) - throw Exception("Can't finalize a finalized OperationChain!") - impl.finalized = true - return this - } - - fun process(v: I): Sequence = - selfInitializingSequence { - var seq = sequenceOf(v) - impl.seqe - .asSequence() - .take(until) - .forEach { op -> - seq = op(seq) - } - seq as Sequence - } - - fun processAll(v: Sequence): Sequence = - v.flatMap { process(it) } - - companion object { - internal fun create(): OperationChain = - OperationChain() - } -} - -fun Sequence.map(chain: OperationChain): Sequence = - chain.processAll(this) - -fun chain(): OperationChain = - OperationChain.create() - -fun OperationChain.chunked(size: Int): OperationChain> = - modifier { it.chunked(size) } - -fun OperationChain.chunked(size: Int, transform: (List) -> R): OperationChain = - modifier { it.chunked(size, transform) } - -fun OperationChain.filter(predicate: (O) -> Boolean): OperationChain = - modifier { it.filter(predicate) } \ No newline at end of file diff --git a/src/main/kotlin/blitz/Switch.kt b/src/main/kotlin/blitz/Switch.kt index ba936f7..906f7be 100644 --- a/src/main/kotlin/blitz/Switch.kt +++ b/src/main/kotlin/blitz/Switch.kt @@ -5,6 +5,7 @@ data class SwitchCase( val then: (T) -> R, ) +@Suppress("NOTHING_TO_INLINE") inline infix fun ((C)->Pair).case(noinline then: (T) -> R) = SwitchCase(this, then) diff --git a/src/main/kotlin/blitz/Types.kt b/src/main/kotlin/blitz/Types.kt index d3ae566..21430e2 100644 --- a/src/main/kotlin/blitz/Types.kt +++ b/src/main/kotlin/blitz/Types.kt @@ -1,5 +1,3 @@ package blitz -typealias Provider = () -> T - -typealias Operator = (I) -> O \ No newline at end of file +typealias Provider = () -> T \ No newline at end of file diff --git a/src/main/kotlin/blitz/Utils.kt b/src/main/kotlin/blitz/Utils.kt index 567b07a..863da5e 100644 --- a/src/main/kotlin/blitz/Utils.kt +++ b/src/main/kotlin/blitz/Utils.kt @@ -1,7 +1,7 @@ package blitz fun unreachable(): Nothing = - error("this should be unreachable") + throw UnsupportedOperationException("this should be unreachable") inline fun Any?.cast(): R? = - this?.let { if (it is R) it else null } \ No newline at end of file + this?.let { it as? R } \ No newline at end of file diff --git a/src/main/kotlin/blitz/annotations/Access.kt b/src/main/kotlin/blitz/annotations/Access.kt new file mode 100644 index 0000000..4be6038 --- /dev/null +++ b/src/main/kotlin/blitz/annotations/Access.kt @@ -0,0 +1,4 @@ +package blitz.annotations + +@Retention(AnnotationRetention.SOURCE) +annotation class DontAccessManually \ No newline at end of file diff --git a/src/main/kotlin/blitz/annotations/Mutability.kt b/src/main/kotlin/blitz/annotations/Mutability.kt new file mode 100644 index 0000000..c8223ec --- /dev/null +++ b/src/main/kotlin/blitz/annotations/Mutability.kt @@ -0,0 +1,25 @@ +package blitz.annotations + +/** + * The target class is mutable. + * Mutable methods should be annotated with [Mutate] + * + * @see Immutable + */ +@Target(AnnotationTarget.CLASS) +@Retention(AnnotationRetention.SOURCE) +annotation class Mutable + +@Target(AnnotationTarget.FUNCTION) +@Retention(AnnotationRetention.SOURCE) +annotation class Mutate + +/** + * The target class has no mutability that can be detected from outside. + * It can have for example a mutable cache, but it should not affect the values of future reads. + * + * @see Mutable + */ +@Target(AnnotationTarget.CLASS) +@Retention(AnnotationRetention.SOURCE) +annotation class Immutable \ No newline at end of file diff --git a/src/main/kotlin/blitz/collections/ByteVec.kt b/src/main/kotlin/blitz/collections/ByteVec.kt index ad6f169..0ef64f5 100644 --- a/src/main/kotlin/blitz/collections/ByteVec.kt +++ b/src/main/kotlin/blitz/collections/ByteVec.kt @@ -135,7 +135,7 @@ class ByteVec(private val initCap: Int = 0): Vec, ByteBatchSequence { array[index] = value } - override fun idx(value: Byte): Int = + override fun indexOf(value: Byte): Int = array.indexOf(value) companion object { diff --git a/src/main/kotlin/blitz/collections/CharVec.kt b/src/main/kotlin/blitz/collections/CharVec.kt index 2b98969..4ffd0bb 100644 --- a/src/main/kotlin/blitz/collections/CharVec.kt +++ b/src/main/kotlin/blitz/collections/CharVec.kt @@ -135,7 +135,7 @@ class CharVec(private val initCap: Int = 0): Vec, BatchSequence { array[index] = value } - override fun idx(value: Char): Int = + override fun indexOf(value: Char): Int = array.indexOf(value) companion object { diff --git a/src/main/kotlin/blitz/collections/Dense16x16BoolMap.kt b/src/main/kotlin/blitz/collections/Dense16x16BoolMap.kt index 12a4192..1ee3a1c 100644 --- a/src/main/kotlin/blitz/collections/Dense16x16BoolMap.kt +++ b/src/main/kotlin/blitz/collections/Dense16x16BoolMap.kt @@ -3,6 +3,8 @@ package blitz.collections import kotlin.contracts.ExperimentalContracts import kotlin.contracts.contract +// TODO: rewrite to math package + @JvmInline @OptIn(ExperimentalUnsignedTypes::class) value class Dense16x16BoolMap( diff --git a/src/main/kotlin/blitz/collections/IntVec.kt b/src/main/kotlin/blitz/collections/IntVec.kt index c59c029..eac7e81 100644 --- a/src/main/kotlin/blitz/collections/IntVec.kt +++ b/src/main/kotlin/blitz/collections/IntVec.kt @@ -132,7 +132,7 @@ class IntVec(private val initCap: Int = 0): Vec, BatchSequence { array[index] = value } - override fun idx(value: Int): Int = + override fun indexOf(value: Int): Int = array.indexOf(value) companion object { diff --git a/src/main/kotlin/blitz/collections/Iter.kt b/src/main/kotlin/blitz/collections/Iter.kt new file mode 100644 index 0000000..71a029a --- /dev/null +++ b/src/main/kotlin/blitz/collections/Iter.kt @@ -0,0 +1,3 @@ +package blitz.collections + +typealias Iter = ((T) -> Unit) -> Unit \ No newline at end of file diff --git a/src/main/kotlin/blitz/collections/IterUtils.kt b/src/main/kotlin/blitz/collections/IterUtils.kt index a51608d..882f4a8 100644 --- a/src/main/kotlin/blitz/collections/IterUtils.kt +++ b/src/main/kotlin/blitz/collections/IterUtils.kt @@ -33,6 +33,7 @@ fun generateIterator(fn: () -> T?): Iterator = /** * Can't explain this function. Look at the source of [blitz.parse.tokenize] as an example */ +@Deprecated("tf is this") fun Iterator.funnyMap(fn: (UnGettableIterator) -> R?): Iterator { val iter = asUnGettable() return generateIterator { fn(iter) } @@ -50,4 +51,17 @@ fun Iterator.collect(to: Vec): Vec { to.pushBack(it) } return to +} + +inline fun Iterator.collect(crossinline consumer: (T) -> Unit) { + forEachRemaining { + consumer(it) + } +} + +fun Iter.collect(to: Vec): Vec { + this { + to.pushBack(it) + } + return to } \ No newline at end of file diff --git a/src/main/kotlin/blitz/collections/LongVec.kt b/src/main/kotlin/blitz/collections/LongVec.kt index d8a51c4..dad66b5 100644 --- a/src/main/kotlin/blitz/collections/LongVec.kt +++ b/src/main/kotlin/blitz/collections/LongVec.kt @@ -132,7 +132,7 @@ class LongVec(private val initCap: Int = 0): Vec, BatchSequence { array[index] = value } - override fun idx(value: Long): Int = + override fun indexOf(value: Long): Int = array.indexOf(value) companion object { diff --git a/src/main/kotlin/blitz/collections/Matrix.kt b/src/main/kotlin/blitz/collections/Matrix.kt index 09bf86c..a81dee8 100644 --- a/src/main/kotlin/blitz/collections/Matrix.kt +++ b/src/main/kotlin/blitz/collections/Matrix.kt @@ -1,8 +1,12 @@ package blitz.collections +import blitz.annotations.Mutable import blitz.str.MutMultiLineString import kotlin.math.min +// TODO: rewrite to Array2o + +@Mutable class Matrix( val width: Int, val height: Int, diff --git a/src/main/kotlin/blitz/collections/RefVec.kt b/src/main/kotlin/blitz/collections/RefVec.kt index 00064ae..8a9f87c 100644 --- a/src/main/kotlin/blitz/collections/RefVec.kt +++ b/src/main/kotlin/blitz/collections/RefVec.kt @@ -1,6 +1,6 @@ package blitz.collections -@Suppress("UNCHECKED_CAST") +@Suppress("UNCHECKED_CAST", "NOTHING_TO_INLINE") class RefVec(private val initCap: Int = 0): Vec { override var size = 0 @JvmField var _cap = initCap @@ -23,7 +23,7 @@ class RefVec(private val initCap: Int = 0): Vec { inline fun copyIntoArray(arr: Array, destOff: Int = 0, startOff: Int = 0) = _array?.copyInto(arr, destOff, startOff, size) - override inline fun copy(): RefVec = + override fun copy(): RefVec = RefVec(size).also { it._array?.let { copyIntoArray(it) } } @@ -101,7 +101,7 @@ class RefVec(private val initCap: Int = 0): Vec { inline fun map(fn: (T) -> R): MutableList = MutableList(size) { fn(this[it]) } - override fun idx(value: T): Int = + override fun indexOf(value: T): Int = _array?.indexOf(value) ?: -1 companion object { diff --git a/src/main/kotlin/blitz/collections/ShortVec.kt b/src/main/kotlin/blitz/collections/ShortVec.kt index 23914dd..600aeff 100644 --- a/src/main/kotlin/blitz/collections/ShortVec.kt +++ b/src/main/kotlin/blitz/collections/ShortVec.kt @@ -132,7 +132,7 @@ class ShortVec(private val initCap: Int = 0): Vec, BatchSequence { array[index] = value } - override fun idx(value: Short): Int = + override fun indexOf(value: Short): Int = array.indexOf(value) companion object { diff --git a/src/main/kotlin/blitz/collections/Tree.kt b/src/main/kotlin/blitz/collections/Tree.kt index a70aa2a..b778c29 100644 --- a/src/main/kotlin/blitz/collections/Tree.kt +++ b/src/main/kotlin/blitz/collections/Tree.kt @@ -1,5 +1,6 @@ package blitz.collections +@Deprecated("Results in unreadable code. Use custom tree instead") class Tree { var root: Node? = null @@ -27,6 +28,7 @@ class Tree { } } +@Deprecated("Results in unreadable code. Use custom tree instead") class Node( var value: T? = null, var children: MutableList> = mutableListOf() @@ -74,6 +76,7 @@ class Node( } } +@Deprecated("Results in unreadable code. Use custom tree instead") interface TreeTraverser { fun process(node: Node): Boolean /* true if continue, false if stop */ diff --git a/src/main/kotlin/blitz/collections/Vec.kt b/src/main/kotlin/blitz/collections/Vec.kt index 99e8542..5940183 100644 --- a/src/main/kotlin/blitz/collections/Vec.kt +++ b/src/main/kotlin/blitz/collections/Vec.kt @@ -38,7 +38,7 @@ interface Vec: IndexableSequence { fun clear() - fun idx(value: T): Int { + fun indexOf(value: T): Int { for (i in 0 until size) { if (this[i] == value) return i diff --git a/src/main/kotlin/blitz/math/Aabb3f.kt b/src/main/kotlin/blitz/math/Aabb3f.kt new file mode 100644 index 0000000..1e76198 --- /dev/null +++ b/src/main/kotlin/blitz/math/Aabb3f.kt @@ -0,0 +1,57 @@ +package blitz.math + +data class Aabb3f( + @JvmField val min: Vec3f, + @JvmField val max: Vec3f, +) { + inline val height get() = max.y - min.y + inline val width get() = max.x - min.x + inline val depth get() = max.z - min.z + + companion object { + fun from(other: Aabb3s) = + Aabb3f( + Vec3f(other.min.x.toFloat(), other.min.y.toFloat(), other.min.z.toFloat()), + Vec3f(other.max.x.toFloat(), other.max.y.toFloat(), other.max.z.toFloat()) + ) + } + + /** + * A vector representing the size of the box. (width, height, depth) + */ + val size: Vec3f get() = + Vec3f(width, height, depth) + + operator fun contains(point: Vec3f) = + point.x >= min.x && point.x <= max.x && + point.y >= min.y && point.y <= max.y && + point.z >= min.z && point.z <= max.z + + operator fun rangeTo(other: Aabb3f): Aabb3f = + Aabb3f( + min = Vec3f( + minOf(min.x, other.min.x), + minOf(min.y, other.min.y), + minOf(min.z, other.min.z) + ), + max = Vec3f( + maxOf(max.x, other.max.x), + maxOf(max.y, other.max.y), + maxOf(max.z, other.max.z) + ) + ) + + operator fun rangeTo(other: Vec3f) = + Aabb3f( + min = Vec3f( + minOf(min.x, other.x), + minOf(min.y, other.y), + minOf(min.z, other.z) + ), + max = Vec3f( + maxOf(max.x, other.x), + maxOf(max.y, other.y), + maxOf(max.z, other.z) + ) + ) +} \ No newline at end of file diff --git a/src/main/kotlin/blitz/math/Aabb3s.kt b/src/main/kotlin/blitz/math/Aabb3s.kt new file mode 100644 index 0000000..7fc859b --- /dev/null +++ b/src/main/kotlin/blitz/math/Aabb3s.kt @@ -0,0 +1,90 @@ +package blitz.math + +import blitz.Endian +import blitz.annotations.Immutable +import blitz.toBytes +import blitz.toLong +import kotlin.contracts.ExperimentalContracts +import kotlin.contracts.InvocationKind +import kotlin.contracts.contract + +@Immutable +@Suppress("NOTHING_TO_INLINE") + +@OptIn(ExperimentalContracts::class) +class Aabb3s private constructor( + val min: Vec3s, + val max: Vec3s +) { + inline fun copy(min: Vec3s = this.min, max: Vec3s = this.max) = + of(min, max) + + override fun equals(other: Any?): Boolean = + other is Aabb3s && other.min == min && other.max == max + + override fun hashCode(): Int = + 31 * min.hashCode() + max.hashCode() + + override fun toString(): String = + "($min, $max)" + + operator fun contains(point: Vec3s): Boolean = + point.x in min.x..max.x && point.y in min.y..max.y && point.z in min.z..max.z + + operator fun contains(other: Aabb3s): Boolean = + contains(other.min) && contains(other.max) + + operator fun rangeTo(include: Vec3s): Aabb3s { + val actualMin = Vec3s(minOf(min.x, include.x), minOf(min.y, include.y), minOf(min.z, include.z)) + val actualMax = Vec3s(maxOf(max.x, include.x), maxOf(max.y, include.y), maxOf(max.z, include.z)) + return Aabb3s(actualMin, actualMax) + } + + operator fun rangeTo(include: Aabb3s): Aabb3s { + val actualMin = Vec3s(minOf(min.x, include.min.x), minOf(min.y, include.min.y), minOf(min.z, include.min.z)) + val actualMax = Vec3s(maxOf(max.x, include.max.x), maxOf(max.y, include.max.y), maxOf(max.z, include.max.z)) + return Aabb3s(actualMin, actualMax) + } + + operator fun plus(off: Vec3s) = + Aabb3s(min + off, max + off) + + operator fun minus(off: Vec3s) = + Aabb3s(min - off, max - off) + + inline fun iter(consumer: (Vec3s) -> Unit) { + for (y in min.y..max.y) { + for (x in min.x..max.x) { + for (z in min.z..max.z) { + consumer(Vec3s(x, y, z)) + } + } + } + } + + inline fun serialize(put: (ByteArray) -> Unit) { + contract { + callsInPlace(put, InvocationKind.AT_LEAST_ONCE) + } + + min.packed.toBytes(Endian.Little).let(put) + max.packed.toBytes(Endian.Little).let(put) + } + + companion object { + fun of(a: Vec3s, b: Vec3s): Aabb3s = + Aabb3s(a, a)..b + + + @OptIn(ExperimentalContracts::class) + inline fun deserialize(get: (Int) -> ByteArray): Aabb3s { + contract { + callsInPlace(get, InvocationKind.AT_LEAST_ONCE) + } + + val min = Vec3s.from(get(8).toLong(Endian.LITTLE).toULong()) + val max = Vec3s.from(get(8).toLong(Endian.LITTLE).toULong()) + return of(min, max) + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/blitz/math/Anglef.kt b/src/main/kotlin/blitz/math/Anglef.kt new file mode 100644 index 0000000..6f6c1c6 --- /dev/null +++ b/src/main/kotlin/blitz/math/Anglef.kt @@ -0,0 +1,51 @@ +package blitz.math + +@JvmInline +value class Anglef( + val radians: Float +) { + val degrees get() = + radians * 180f / Math.PI.toFloat() + + val gradians get() = + radians * 200f / Math.PI.toFloat() + + fun normalized() = + Anglef(radians % (2 * Math.PI.toFloat())) + + operator fun plus(other: Anglef) = + Anglef(radians + other.radians) + + operator fun minus(other: Anglef) = + Anglef(radians - other.radians) + + operator fun times(other: Anglef) = + Anglef(radians * other.radians) + + operator fun div(other: Anglef) = + Anglef(radians / other.radians) + + operator fun times(other: Float) = + Anglef(radians * other) + + operator fun div(other: Float) = + Anglef(radians / other) + + operator fun unaryMinus() = + Anglef(-radians) + + override fun toString(): String { + return "$degrees°" + } + + companion object { + fun fromRadians(radians: Float) = + Anglef(radians) + + fun fromDegrees(degrees: Float) = + Anglef(degrees * Math.PI.toFloat() / 180f) + + fun fromGradians(gradians: Float) = + Anglef(gradians * Math.PI.toFloat() / 200f) + } +} \ No newline at end of file diff --git a/src/main/kotlin/blitz/math/BitInts.kt b/src/main/kotlin/blitz/math/BitInts.kt new file mode 100644 index 0000000..a607cfe --- /dev/null +++ b/src/main/kotlin/blitz/math/BitInts.kt @@ -0,0 +1,41 @@ +package blitz.math + +import blitz.annotations.Immutable + +@Immutable +@JvmInline +@Suppress("NOTHING_TO_INLINE") +value class U3(val value: UByte) { + override fun toString() = + value.toString() + + inline fun toU4() = U4(value) +} + +@Immutable +@JvmInline +@Suppress("NOTHING_TO_INLINE") +value class U4(val value: UByte) { + override fun toString() = + value.toString() + + inline fun toU3() = U3(value and 0b111u) +} + + +/** two U4 values packed => UByte */ +@Immutable +@JvmInline +@Suppress("NOTHING_TO_INLINE") +value class Vec2u4(val packed: UByte) { + inline val x : U4 get() = U4((packed.toUInt() shr 4).toUByte()) + inline val y : U4 get() = U4(packed and 0b111u) + + constructor(x: U4, y: U4): this((x.value.toUInt() shl 4 or + y.value.toUInt()).toUByte()) + + override fun toString() = "($x, $y)" + + inline fun component1() = x + inline fun component2() = y +} \ No newline at end of file diff --git a/src/main/kotlin/blitz/math/Direction.kt b/src/main/kotlin/blitz/math/Direction.kt new file mode 100644 index 0000000..e881057 --- /dev/null +++ b/src/main/kotlin/blitz/math/Direction.kt @@ -0,0 +1,29 @@ +package blitz.math + +import blitz.unreachable + +/** only 6 directions -> only needs 3 bits */ +@JvmInline +value class Direction( + val idx: U3 +) { + companion object { + val UP = Direction(U3(0u)) + val DOWN = Direction(U3(1u)) + val NORTH = Direction(U3(2u)) + val EAST = Direction(U3(3u)) + val SOUTH = Direction(U3(4u)) + val WEST = Direction(U3(5u)) + } + + override fun toString() = + when (this) { + UP -> "Up" + DOWN -> "Down" + NORTH -> "North" + EAST -> "East" + SOUTH -> "South" + WEST -> "West" + else -> unreachable() + } +} \ No newline at end of file diff --git a/src/main/kotlin/blitz/math/Lerp.kt b/src/main/kotlin/blitz/math/Interpolate.kt similarity index 62% rename from src/main/kotlin/blitz/math/Lerp.kt rename to src/main/kotlin/blitz/math/Interpolate.kt index 2aa353c..18bd8a8 100644 --- a/src/main/kotlin/blitz/math/Lerp.kt +++ b/src/main/kotlin/blitz/math/Interpolate.kt @@ -3,8 +3,14 @@ package blitz.math infix fun Pair.lerpi(t: Double): Double = (1.0f - t) * first + second * t +infix fun Pair.lerpi(t: Float): Float = + (1.0f - t) * first + second * t + infix fun Pair.lerpd(t: Double): Double = (1.0f - t) * first + second * t infix fun Pair.lerpf(t: Double): Double = (1.0f - t) * first + second * t + +infix fun Pair.lerpf(t: Float): Float = + (1.0f - t) * first + second * t diff --git a/src/main/kotlin/blitz/math/PackedVoxelShape3i.kt b/src/main/kotlin/blitz/math/PackedVoxelShape3i.kt new file mode 100644 index 0000000..0c004a5 --- /dev/null +++ b/src/main/kotlin/blitz/math/PackedVoxelShape3i.kt @@ -0,0 +1,146 @@ +package blitz.math + +import blitz.annotations.Mutable +import blitz.annotations.Mutate +import blitz.test.annotations.Test +import blitz.test.util.requireEqual +import kotlin.contracts.ExperimentalContracts +import kotlin.contracts.contract + +/** + * use this only for small-ish voxel shapes: 16x16, 32x16, etc + * + * size needs to be known at construction + */ +@Mutable +@JvmInline +@OptIn(ExperimentalContracts::class) +@Suppress("NOTHING_TO_INLINE") +value class PackedVoxelShape3i( + val packed: LongArray, +) { + inline val sizes4x4x4 get() = + Vec3s.from(packed[0].toULong()) + + val sizes get() = + Vec3s(sizes4x4x4.x * 4.toShort(), sizes4x4x4.y * 4.toShort(), sizes4x4x4.z * 4.toShort()) + + inline fun index4x4x4(x: Int, y: Int, z: Int) = + 1 + x + sizes4x4x4.x.toInt() * (y + sizes4x4x4.y.toInt() * z) + + operator fun get(x: Int, y: Int, z: Int): Boolean { + require(Vec3s(x, y, z).allComponents(sizes) { a, sz -> a < sz }) + return VoxelShape4x4x4i(packed[index4x4x4(x / 4, y / 4, z / 4)])[x % 4, y % 4, z % 4] + } + + inline operator fun get(idx: Vec3s) = + get(idx.x.toInt(), idx.y.toInt(), idx.z.toInt()) + + @Mutate + operator fun set(x: Int, y: Int, z: Int, value: Boolean) { + require(Vec3s(x, y, z).allComponents(sizes) { a, sz -> a < sz }) { "index out of bounds: ($x, $y, $z) for $sizes" } + val idx = index4x4x4(x / 4, y / 4, z / 4) + packed[idx] = VoxelShape4x4x4i(packed[idx]).set(x % 4, y % 4, z % 4, value).packed + } + + @Mutate + inline operator fun set(idx: Vec3s, value: Boolean) = + set(idx.x.toInt(), idx.y.toInt(), idx.z.toInt(), value) + + inline fun iterChunks(consumer: (Vec3s, VoxelShape4x4x4i) -> Unit) { + contract { + callsInPlace(consumer) + } + + sizes4x4x4.asSizeIterate { + consumer(it, + VoxelShape4x4x4i( + packed[index4x4x4(it.x.toInt(), it.y.toInt(), it.z.toInt())])) + } + } + + inline fun iterSetVoxels(consumer: (Vec3s) -> Unit) { + contract { + callsInPlace(consumer) + } + + iterChunks { cp, chunk -> + chunk.iterSetVoxels { off -> + consumer(cp * 4 + off) + } + } + } + + fun layerEmpty(y: Int): Boolean { + val cy = y / 4 + for (cx in 0.. Unit) { + contract { + callsInPlace(consumer) + } + + for (y in 0.. + out.append("layer $y:\n") + for (x in 0.. + chunk.aabb()?.let { + val x = it + cp * 4 + out = out?.let { it..x } ?: x + } + } + return out + } + + companion object { + fun of(size: Vec3s): PackedVoxelShape3i { + val sizes4x4x4 = size.ceilDiv(4) + val obj = PackedVoxelShape3i(LongArray(sizes4x4x4.x.toInt() * sizes4x4x4.y.toInt() * sizes4x4x4.z.toInt() + 1)) + obj.packed[0] = sizes4x4x4.packed.toLong() + return obj + } + } + + object _tests { + @Test + fun aabbCorrect() { + val voxels = of(Vec3s(8, 6, 8)) + voxels[3,2,1] = true + voxels[6,2,2] = true + voxels[7,4,2] = true + requireEqual(voxels.aabb(), Aabb3s.of(Vec3s(3, 2, 1), Vec3s(7, 4, 2))) + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/blitz/math/Vec3f.kt b/src/main/kotlin/blitz/math/Vec3f.kt new file mode 100644 index 0000000..ea2731d --- /dev/null +++ b/src/main/kotlin/blitz/math/Vec3f.kt @@ -0,0 +1,98 @@ +package blitz.math + +import blitz.Endian +import blitz.annotations.Immutable +import blitz.toBytes +import blitz.toInt +import blitz.unreachable +import kotlin.contracts.ExperimentalContracts +import kotlin.contracts.InvocationKind +import kotlin.contracts.contract + +@Immutable +@OptIn(ExperimentalContracts::class) +@Suppress("NOTHING_TO_INLINE") +data class Vec3f( + @JvmField val x: Float, + @JvmField val y: Float, + @JvmField val z: Float +) { + + inline fun serialize(put: (ByteArray) -> Unit) { + contract { + callsInPlace(put, InvocationKind.AT_LEAST_ONCE) + } + + x.toRawBits().toBytes(Endian.Little).let(put) + y.toRawBits().toBytes(Endian.Little).let(put) + z.toRawBits().toBytes(Endian.Little).let(put) + } + + companion object { + inline fun from(s: Vec3s) = + Vec3f(s.x.toFloat(), s.y.toFloat(), s.z.toFloat()) + + val Zero = Vec3f(0f, 0f, 0f) + + + @OptIn(ExperimentalContracts::class) + inline fun deserialize(get: (Int) -> ByteArray): Vec3f { + contract { + callsInPlace(get, InvocationKind.AT_LEAST_ONCE) + } + + return Vec3f( + get(4).toInt(Endian.Little).let(Float::fromBits), + get(4).toInt(Endian.Little).let(Float::fromBits), + get(4).toInt(Endian.Little).let(Float::fromBits), + ) + } + } + + override fun toString() = + "($x, $y, $z)" + + + fun rotate(direction: Direction): Vec3f = + when (direction) { + Direction.DOWN -> Vec3f(x, -z, y) + Direction.UP -> Vec3f(x, z, -y) + Direction.NORTH -> Vec3f(x, y, z) + Direction.SOUTH -> Vec3f(-x, y, -z) + Direction.WEST -> Vec3f(z, y, -x) + Direction.EAST -> Vec3f(-z, y, x) + else -> unreachable() + } + + inline operator fun plus(r: Vec3f): Vec3f = + Vec3f(this.x + r.x, this.y + r.y, this.z + r.z) + + inline operator fun minus(r: Vec3f): Vec3f = + Vec3f(this.x - r.x, this.y - r.y, this.z - r.z) + + inline operator fun times(r: Vec3f): Vec3f = + Vec3f(this.x * r.x, this.y * r.y, this.z * r.z) + + inline operator fun div(r: Vec3f): Vec3f = + Vec3f(this.x / r.x, this.y / r.y, this.z / r.z) + + inline infix fun dot(r: Vec3f) = + this.x * r.x + this.y * r.y + this.z * r.z + + inline fun product() = x * y * z + + inline operator fun unaryMinus() = + Vec3f(-x, -y, -z) + + inline fun allComponents(fn: (Float) -> Boolean) = + fn(x) && fn(y) && fn(z) + + inline fun anyComponents(fn: (Float) -> Boolean) = + fn(x) || fn(y) || fn(z) + + inline fun allComponents(other: Vec3f, fn: (Float, Float) -> Boolean) = + fn(x, other.x) && fn(y, other.y) && fn(z, other.z) + + inline fun anyComponent(other: Vec3f, fn: (Float, Float) -> Boolean) = + fn(x, other.x) || fn(y, other.y) || fn(z, other.z) +} \ No newline at end of file diff --git a/src/main/kotlin/blitz/math/Vec3s.kt b/src/main/kotlin/blitz/math/Vec3s.kt new file mode 100644 index 0000000..ff93203 --- /dev/null +++ b/src/main/kotlin/blitz/math/Vec3s.kt @@ -0,0 +1,136 @@ +package blitz.math + +import blitz.annotations.Immutable +import blitz.unreachable +import blitz.test.annotations.Test +import kotlin.contracts.ExperimentalContracts +import kotlin.contracts.contract + +@Immutable +@JvmInline +@OptIn(ExperimentalContracts::class) +@Suppress("NOTHING_TO_INLINE") +value class Vec3s private constructor(val backing: Vec4s) { + inline val x: Short get() = backing.x + inline val y: Short get() = backing.y + inline val z: Short get() = backing.z + + inline val packed: ULong get() = backing.packed + + companion object { + fun from(xyz: Vec4s) = + Vec3s(Vec4s(xyz.packed and 0xFFFF_FFFF_FFFF_0000uL)) + + inline fun from(packed: ULong) = + from(Vec4s(packed)) + + inline fun from(v: Vec3f) = + Vec3s(v.x.toInt(), v.y.toInt(), v.z.toInt()) + + @JvmStatic + private fun Int.toShortThrow() = toShort().also { + if (it.toInt() != this) throw IllegalArgumentException("Value $this cannot be represented as Short") + } + + val Zero = Vec3s(0, 0, 0) + } + + @Suppress("UNUSED") + object _tests { + @Test + fun fromPackedCorrect() { + require(from(Vec4s(1,2,3,4).packed) == Vec3s(1,2,3)) + } + + @Test + fun serialFormatStable() { + require(Vec4s(1,2,3,4).packed == 0x0001_0002_0003_0004uL) + require(Vec4s(0x0001_0002_0003_0004uL) == Vec4s(1,2,3,4)) + } + } + + constructor(x: Short, y: Short, z: Short): this(Vec4s(x, y, z, 0)) + constructor(x: Int, y: Int, z: Int): this(Vec4s(x.toShortThrow(), y.toShortThrow(), z.toShortThrow(), 0)) + + override fun toString() = "($x, $y, $z)" + + inline operator fun component1() = x + inline operator fun component2() = y + inline operator fun component3() = z + + inline fun asSizeIterate(consumer: (Vec3s) -> Unit) { + contract { + callsInPlace(consumer) + } + + for (z in 0.. Vec3s(x, (-z).toShort(), y) + Direction.UP -> Vec3s(x, z, (-y).toShort()) + Direction.NORTH -> Vec3s(x, y, z) + Direction.SOUTH -> Vec3s((-x).toShort(), y, (-z).toShort()) + Direction.WEST -> Vec3s(z, y, (-x).toShort()) + Direction.EAST -> Vec3s((-z).toShort(), y, x) + else -> unreachable() + } + + inline operator fun plus(r: Vec3s): Vec3s = + Vec3s(this.x + r.x, this.y + r.y, this.z + r.z) + + inline operator fun minus(r: Vec3s): Vec3s = + Vec3s(this.x - r.x, this.y - r.y, this.z - r.z) + + inline operator fun times(r: Vec3s): Vec3s = + Vec3s(this.x * r.x, this.y * r.y, this.z * r.z) + + inline operator fun plus(off: Short): Vec3s = + Vec3s(this.x + off, this.y + off, this.z + off) + + inline operator fun minus(off: Short): Vec3s = + Vec3s(this.x - off, this.y - off, this.z - off) + + operator fun times(value: Float): Vec3s = + Vec3s((x * value).toInt().toShortThrow(), (y * value).toInt().toShortThrow(), (z * value).toInt().toShortThrow()) + + operator fun times(value: Short): Vec3s = + Vec3s(x * value, y * value, z * value) + + operator fun div(value: Float): Vec3s = + Vec3s((x / value).toInt().toShortThrow(), (y / value).toInt().toShortThrow(), (z / value).toInt().toShortThrow()) + + operator fun div(value: Short): Vec3s = + Vec3s(x / value, y / value, z / value) + + fun ceilDiv(value: Short): Vec3s = + Vec3s( + if (x >= 0) (x / value) + 1 else (x / value), + if (y >= 0) (y / value) + 1 else (y / value), + if (z >= 0) (z / value) + 1 else (z / value) + ) + + inline fun product() = x.toInt() * y.toInt() * z.toInt() + + inline operator fun unaryMinus() = + Vec3s(-x, -y, -z) + + inline fun allComponents(fn: (Short) -> Boolean) = + fn(x) && fn(y) && fn(z) + + inline fun anyComponents(fn: (Short) -> Boolean) = + fn(x) || fn(y) || fn(z) + + inline fun allComponents(other: Vec3s, fn: (Short, Short) -> Boolean) = + fn(x, other.x) && fn(y, other.y) && fn(z, other.z) + + inline fun anyComponent(other: Vec3s, fn: (Short, Short) -> Boolean) = + fn(x, other.x) || fn(y, other.y) || fn(z, other.z) +} diff --git a/src/main/kotlin/blitz/math/Vec4s.kt b/src/main/kotlin/blitz/math/Vec4s.kt new file mode 100644 index 0000000..a4119e6 --- /dev/null +++ b/src/main/kotlin/blitz/math/Vec4s.kt @@ -0,0 +1,26 @@ +package blitz.math + +import blitz.annotations.Immutable + +@Immutable +@JvmInline +@Suppress("NOTHING_TO_INLINE") +value class Vec4s(val packed: ULong) { + inline val x: Short get() = (packed shr 48).toUShort().toShort() + inline val y: Short get() = (packed shr 32).toUShort().toShort() + inline val z: Short get() = (packed shr 16).toUShort().toShort() + inline val w: Short get() = packed.toUShort().toShort() + + constructor(x: Short, y: Short, z: Short, w: Short): this( + (x.toUShort().toULong() shl 48) or + (y.toUShort().toULong() shl 32) or + (z.toUShort().toULong() shl 16) or + w.toUShort().toULong()) + + override fun toString() = "($x, $y, $z, $w)" + + inline operator fun component1() = x + inline operator fun component2() = y + inline operator fun component3() = z + inline operator fun component4() = w +} \ No newline at end of file diff --git a/src/main/kotlin/blitz/math/VoxelShape4x4i.kt b/src/main/kotlin/blitz/math/VoxelShape4x4i.kt new file mode 100644 index 0000000..64823c0 --- /dev/null +++ b/src/main/kotlin/blitz/math/VoxelShape4x4i.kt @@ -0,0 +1,40 @@ +package blitz.math + +import blitz.annotations.Immutable +import kotlin.experimental.and +import kotlin.experimental.or + +@Immutable +@JvmInline +@Suppress("NOTHING_TO_INLINE") +value class VoxelShape4x4i( + val packed: Short, +) { + fun bitIndexFor(x: Int, y: Int): Int { + require(x in 0..3 && y in 0..3) { "index out of bounds: ($x, $y) for 4x4" } + return 4 * y + x + } + + inline operator fun get(x: Int, y: Int): Boolean = + (packed.toUShort().toInt() and (1 shl bitIndexFor(x, y))) != 0 + + inline fun set(x: Int, y: Int, value: Boolean): VoxelShape4x4i { + val mask = 1 shl bitIndexFor(x, y) + return VoxelShape4x4i((if (value) + packed.toUShort().toInt() or mask + else + packed.toUShort().toInt() and mask.inv()).toShort()) + } + + inline operator fun plus(other: VoxelShape4x4i) = + VoxelShape4x4i(packed or other.packed) + + inline operator fun times(other: VoxelShape4x4i) = + VoxelShape4x4i(packed and other.packed) + + inline val empty get() = packed.toInt() == 0 + + companion object { + val Air = VoxelShape4x4i(0) + } +} diff --git a/src/main/kotlin/blitz/math/VoxelShape4x4x4i.kt b/src/main/kotlin/blitz/math/VoxelShape4x4x4i.kt new file mode 100644 index 0000000..537fa2b --- /dev/null +++ b/src/main/kotlin/blitz/math/VoxelShape4x4x4i.kt @@ -0,0 +1,93 @@ +package blitz.math + +import blitz.annotations.Immutable +import blitz.test.annotations.Test +import blitz.test.util.requireEqual +import kotlin.contracts.ExperimentalContracts +import kotlin.contracts.contract + +@Immutable +@JvmInline +@OptIn(ExperimentalContracts::class) +@Suppress("NOTHING_TO_INLINE") +value class VoxelShape4x4x4i( + val packed: Long +) { + operator fun get(y: Int): VoxelShape4x4i { + require(y in 0..3) { "layer index out of bounds: y=$y (expected 0..3)" } + val shift = 16 * y + return VoxelShape4x4i(((packed ushr shift) and 0xFFFFL).toShort()) + } + + fun set(y: Int, value: VoxelShape4x4i): VoxelShape4x4x4i { + require(y in 0..3) { "layer index out of bounds: y=$y (expected 0..3)" } + val shift = 16 * y + val clearMask = (0xFFFFL shl shift).inv() + val slice = ((value.packed.toUShort().toLong() and 0xFFFFL) shl shift) + return VoxelShape4x4x4i((packed and clearMask) or slice) + } + + inline operator fun plus(other: VoxelShape4x4x4i) = + VoxelShape4x4x4i(packed or other.packed) + + inline operator fun times(other: VoxelShape4x4x4i) = + VoxelShape4x4x4i(packed and other.packed) + + inline operator fun get(x: Int, y: Int, z: Int): Boolean = + this[y][x, z] + + inline fun set(x: Int, y: Int, z: Int, value: Boolean) = + set(y, this[y].set(x, z, value)) + + inline val empty get() = packed == 0L + + inline fun iterSetVoxels(consumer: (Vec3s) -> Unit) { + contract { + callsInPlace(consumer) + } + + if (empty) return + + for (y in 0..<4) { + for (x in 0..<4) { + for (z in 0..<4) { + if (this[x, y, z]) { + consumer(Vec3s(x, y, z)) + } + } + } + } + } + + fun aabb(): Aabb3s? { + if (empty) return null + val minBit = packed.countTrailingZeroBits() + val maxBit = 63 - packed.countLeadingZeroBits() + return Aabb3s.of( + bitToPos(minBit), + bitToPos(maxBit) + ) + } + + companion object { + val AIR = VoxelShape4x4x4i(0) + + private fun bitToPos(bit: Int): Vec3s { + // bit = x + 4 * z + 16 * y + val pos2 = bit % 16 + val x = pos2 % 4 + val z = pos2 / 4 + return Vec3s(x.toShort(), (bit / 16).toShort(), z.toShort()) + } + } + + object _test { + @Test + fun aabbCorrect() { + val x = AIR + .set(2, 3, 1, true) + .set(3, 3, 1, true) + requireEqual(x.aabb(), Aabb3s.of(Vec3s(2, 3, 1), Vec3s(3, 3, 1))) + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/blitz/parse/JSON.kt b/src/main/kotlin/blitz/parse/JSON.kt index dfe6163..c3ccf24 100644 --- a/src/main/kotlin/blitz/parse/JSON.kt +++ b/src/main/kotlin/blitz/parse/JSON.kt @@ -3,10 +3,12 @@ package blitz.parse import blitz.collections.RefVec import blitz.collections.contents import blitz.parse.comb2.* +import blitz.test.annotations.Test +import blitz.test.util.requireEqual import blitz.unreachable +@Suppress("NOTHING_TO_INLINE") object JSON { - val jsonBool: Parser = choose { it(mapValue(seq("true".toList())) { Element.newBool(true) }) it(mapValue(seq("false".toList())) { Element.newBool(false) }) @@ -116,31 +118,72 @@ object JSON { inline fun Element.uncheckedAsObj(): Map = _boxed as Map - inline fun Element.asNum(): Double { + fun Element.asNum(): Double { require(kind == Element.NUM) { "Element is not a Number" } return _num } - inline fun Element.asBool(): Boolean { + fun Element.asBool(): Boolean { require(kind == Element.BOOL) { "Element is not a Boolean" } return _bool } - inline fun Element.asArr(): RefVec { + fun Element.asArr(): RefVec { require(kind == Element.ARR) { "Element is not an Array" } return _boxed as RefVec } - inline fun Element.asStr(): String { + fun Element.asStr(): String { require(kind == Element.STR) { "Element is not a String" } return _boxed as String } - inline fun Element.asObj(): Map { + fun Element.asObj(): Map { require(kind == Element.OBJ) { "Element is not an Object" } return _boxed as Map } fun parse(string: String): ParseResult = jsonElement.run(string.toList()) + + object _tests { + @Test + fun parseJsonNumber() { + parse("-1.351").assertA().asNum() + .requireEqual(-1.351) + } + + @Test + fun parseJsonBool() { + parse("true").assertA().asBool() + .requireEqual(true) + parse("false").assertA().asBool() + .requireEqual(false) + } + + @Test + fun parseJsonNull() { + parse("null").assertA().kind + .requireEqual(Element.NULL) + } + + @Test + fun parseJsonStr() { + parse("\"Hello\\\n\\\"aworld\"").assertA().asStr() + .requireEqual("Hello\n\"aworld") + } + + @Test + fun parseJsonArr() { + parse("[1, 2, 3,\n 4]").assertA().asArr() + .map { it.asNum() }.contents.requireEqual(listOf(1.0,2.0,3.0,4.0).contents) + } + + @Test + fun parseJsonObj() { + val obj = parse("{\"a\": 1, \"b\": 2}").assertA().asObj() + obj.map { it.value.asNum() }.contents.requireEqual(listOf(1.0,2.0).contents) + obj.map { it.key }.contents.requireEqual(listOf("a","b").contents) + } + } } \ No newline at end of file diff --git a/src/main/kotlin/blitz/term/Terminal.kt b/src/main/kotlin/blitz/term/Terminal.kt index 08179b9..2e15583 100644 --- a/src/main/kotlin/blitz/term/Terminal.kt +++ b/src/main/kotlin/blitz/term/Terminal.kt @@ -77,17 +77,6 @@ object Terminal { System.err.println(encodeString(str, *modes)) } - @Deprecated( - "Use errln instead!", - ReplaceWith( - "errln(str, *modes)", - "blitz.term.Terminal.errln" - ) - ) - fun warn(str: String, vararg modes: AnsiiMode) { - errln(str, *modes) - } - object Cursor { fun savePos() { print(escape + "7") diff --git a/src/main/kotlin/blitz/test/annotations/Test.kt b/src/main/kotlin/blitz/test/annotations/Test.kt new file mode 100644 index 0000000..726d818 --- /dev/null +++ b/src/main/kotlin/blitz/test/annotations/Test.kt @@ -0,0 +1,5 @@ +package blitz.test.annotations + +@Target(AnnotationTarget.FUNCTION, AnnotationTarget.CLASS) +@Retention(AnnotationRetention.SOURCE) +annotation class Test diff --git a/src/main/kotlin/blitz/test/util/Asserts.kt b/src/main/kotlin/blitz/test/util/Asserts.kt new file mode 100644 index 0000000..dcab9bc --- /dev/null +++ b/src/main/kotlin/blitz/test/util/Asserts.kt @@ -0,0 +1,7 @@ +package blitz.test.util + +fun requireEqual(a: Any?, b: Any?) = + require(a == b) { "Expected $a to be equal to $b"} + +fun T.requireEqual(other: T) = + requireEqual(this, other) \ No newline at end of file diff --git a/src/test/kotlin/map.kt b/src/test/kotlin/map.kt index 65561c0..e0b39e4 100644 --- a/src/test/kotlin/map.kt +++ b/src/test/kotlin/map.kt @@ -4,6 +4,8 @@ import blitz.collections.I2HashMapKey import blitz.collections.contents import kotlin.test.Test +// TODO: port tests to sanetest + class Maps { @Test fun i2hashmap() {