Compare commits

...

10 Commits

Author SHA1 Message Date
5b95c4dd64 v0.25 2025-09-22 13:02:35 +02:00
c32b20e5b3 c 2025-09-17 22:57:21 +02:00
alex_s168
b83109c751 finally TLS on maven server 2025-05-23 16:45:45 +02:00
alex_s168
f10ebb7427 Update README.md 2025-03-15 23:00:03 +01:00
alex-s168
6c44aae9cf maven publish 2025-02-03 12:55:02 +01:00
alex-s168
4ed0225b4f c 2024-10-30 18:00:49 +01:00
alex-s168
f0b2736af5 c 2024-10-28 18:53:53 +01:00
alex_s168
234a682f7e Update README.md 2024-09-22 13:26:10 +00:00
alex_s168
f18798bb5c increase parser perf by a lot 2024-09-21 10:27:34 +00:00
alex_s168
8c2325bdd3 improver parser perf 2024-09-19 21:55:17 +00:00
62 changed files with 2524 additions and 691 deletions

6
.gitignore vendored
View File

@@ -1,3 +1,7 @@
.env
.idea
gradle/
.gradle
build/
!gradle/wrapper/gradle-wrapper.jar
@@ -41,4 +45,4 @@ bin/
### Mac OS ###
.DS_Store
kls_database.db
kls_database.db

View File

@@ -6,13 +6,12 @@ Big Kotlin library adding features that the Kotlin standard library just does no
repositories {
maven {
name = "alex's repo"
url = uri("http://207.180.202.42:8080/libs")
isAllowInsecureProtocol = true
url = uri("https://maven.vxcc.dev/libs")
}
}
dependencies {
implementation("me.alex_s168:blitz:0.20")
implementation("me.alex_s168:blitz:0.25")
}
```
@@ -242,4 +241,4 @@ println(JSON.parse(json)!!.obj["b"]!!.obj["1"]!!.num)
- `BlitzHashMap`
- `Dense16x16BoolMap`
- `DenseIx16x16BoolMap`
- `SlicedIntKeyMap`
- `SlicedIntKeyMap`

View File

@@ -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.20"
version = "0.25"
repositories {
mavenCentral()
@@ -25,11 +26,27 @@ kotlin {
jvmToolchain(11)
}
java {
withSourcesJar()
withJavadocJar()
}
application {
mainClass.set("blitz.FnpKt")
}
publishing {
repositories {
maven {
name = "vxccLibs"
url = uri("https://maven.vxcc.dev/libs")
credentials(PasswordCredentials::class)
authentication {
create<BasicAuthentication>("basic")
}
}
}
publications {
create<MavenPublication>("maven") {
groupId = group.toString()
@@ -37,6 +54,13 @@ publishing {
version = version.toString()
from(components["kotlin"])
artifact(tasks["sourcesJar"])
artifact(tasks["javadocJar"])
pom {
name.set("blitz")
url.set("https://github.com/alex_s168/blitz")
}
}
}
}
}

12
gradlew vendored
View File

@@ -15,6 +15,8 @@
# See the License for the specific language governing permissions and
# limitations under the License.
#
# SPDX-License-Identifier: Apache-2.0
#
##############################################################################
#
@@ -55,7 +57,7 @@
# Darwin, MinGW, and NonStop.
#
# (3) This script is generated from the Groovy template
# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# within the Gradle project.
#
# You can find Gradle at https://github.com/gradle/gradle/.
@@ -84,7 +86,7 @@ done
# shellcheck disable=SC2034
APP_BASE_NAME=${0##*/}
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit
APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD=maximum
@@ -112,7 +114,7 @@ case "$( uname )" in #(
NONSTOP* ) nonstop=true ;;
esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
CLASSPATH="\\\"\\\""
# Determine the Java command to use to start the JVM.
@@ -203,7 +205,7 @@ fi
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Collect all arguments for the java command:
# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
# and any embedded shellness will be escaped.
# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
# treated as '${Hostname}' itself on the command line.
@@ -211,7 +213,7 @@ DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
set -- \
"-Dorg.gradle.appname=$APP_BASE_NAME" \
-classpath "$CLASSPATH" \
org.gradle.wrapper.GradleWrapperMain \
-jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \
"$@"
# Stop when "xargs" is not available.

26
gradlew.bat vendored
View File

@@ -13,6 +13,8 @@
@rem See the License for the specific language governing permissions and
@rem limitations under the License.
@rem
@rem SPDX-License-Identifier: Apache-2.0
@rem
@if "%DEBUG%"=="" @echo off
@rem ##########################################################################
@@ -43,11 +45,11 @@ set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if %ERRORLEVEL% equ 0 goto execute
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
echo. 1>&2
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
echo. 1>&2
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
echo location of your Java installation. 1>&2
goto fail
@@ -57,22 +59,22 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto execute
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
echo. 1>&2
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
echo. 1>&2
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
echo location of your Java installation. 1>&2
goto fail
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
set CLASSPATH=
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %*
:end
@rem End local scope for the variables with windows NT shell

View File

@@ -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 <R> 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

View File

@@ -1,48 +1,33 @@
package blitz
class Either<A, B> private constructor(
private val a: Obj<A>?,
private val b: Obj<B>?
import blitz.annotations.DontAccessManually
@Suppress("UNCHECKED_CAST", "NOTHING_TO_INLINE")
class Either<A: Any, B: Any> 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 =
other is Either<*, *> && other.a == a && other.b == b
fun getAOrNull(): A? =
a?.v
fun assertA(): A =
(a ?: throw Exception("Value of Either is not of type A!"))
fun getA(): A =
(a ?: throw Exception("Value of Either is not of type A!")).v
fun getAOr(prov: Provider<A>): A =
getAOrNull() ?: prov()
fun getBOrNull(): B? =
b?.v
fun getB(): B =
(b ?: throw Exception("Value of Either is not of type B!")).v
fun getBOr(prov: Provider<B>): B =
getBOrNull() ?: prov()
val isA: Boolean =
a != null
val isB: Boolean =
b != null
fun <R> then(af: (A) -> R, bf: (B) -> R): R =
if (isA) af(a!!.v) else bf(b!!.v)
fun <RA> mapA(transform: (A) -> RA): Either<RA, B> =
Either(a.mapNotNull(transform), b)
fun <RB> mapB(transform: (B) -> RB): Either<A, RB> =
Either(a, b.mapNotNull(transform))
fun assertB(): B =
(b ?: throw Exception("Value of Either is not of type B!"))
override fun toString(): String =
if (isA) "Either<A>(${a!!.v})"
else "Either<B>(${b!!.v})"
if (isA()) "Either<A>(${a!!})"
else "Either<B>(${b!!})"
override fun hashCode(): Int {
var result = a?.hashCode() ?: 0
@@ -51,43 +36,97 @@ class Either<A, B> private constructor(
}
companion object {
fun <A, B> ofA(a: A): Either<A, B> =
Either(Obj.of(a), null)
fun <A: Any, B: Any> unsafeCreate(a: A?, b: B?, pool: StupidObjPool<Either<*,*>>? = null): Either<A, B> =
Either(a, b)
fun <A, B> ofB(b: B): Either<A, B> =
Either(null, Obj.of(b))
inline fun <A: Any, B: Any> ofA(a: A, pool: StupidObjPool<Either<*,*>>? = null): Either<A, B> =
unsafeCreate(a, null, pool)
inline fun <A: Any, B: Any> ofB(b: B, pool: StupidObjPool<Either<*,*>>? = null): Either<A, B> =
unsafeCreate(null, b, pool)
}
}
fun <A, B, R> Either<A, B>.flatten(): R where A: R, B: R =
getAOrNull() ?: getB()
@Suppress("NOTHING_TO_INLINE")
inline fun <A: Any, B: Any> Either<A, B>.isA() = a != null
fun <A, A2, B> Either<A, Either<A2, B>>.partiallyFlattenB(): Either<A2, B> where A: A2 =
mapA<Either<A2, B>> { Either.ofA(it) }.flatten()
@Suppress("NOTHING_TO_INLINE")
inline fun <A: Any, B: Any> Either<A, B>.isB() = b != null
fun <A, B, B2> Either<Either<A, B2>, B>.partiallyFlattenA(): Either<A, B2> where B: B2 =
mapB<Either<A, B2>> { Either.ofB(it) }.flatten()
inline fun <A: Any, B: Any> Either<A, B>.getAOr(prov: Provider<A>): A =
a ?: prov()
fun <A, BA, BB, BAN> Either<A, Either<BA, BB>>.mapBA(fn: (BA) -> BAN): Either<A, Either<BAN, BB>> =
inline fun <A: Any, B: Any> Either<A, B>.getBOr(prov: Provider<B>): B =
b ?: prov()
inline fun <A: Any, B: Any, R> Either<A, B>.then(af: (A) -> R, bf: (B) -> R): R =
if (isA()) af(a!!) else bf(b!!)
inline fun <A: Any, B: Any, RA: Any> Either<A, B>.mapA(transform: (A) -> RA): Either<RA, B> =
Either.unsafeCreate(a?.let(transform), b)
inline fun <A: Any, B: Any> Either<A, B>.flatMapA(transform: (A) -> Either<A, B>): Either<A, B> =
if (a != null) {
transform(a!!)
} else this
inline fun <A: Any, B: Any> Either<A, B>.flatMapB(transform: (B) -> Either<A, B>): Either<A, B> =
if (b != null) {
transform(b!!)
} else this
@JvmName("flatMapA_changeType")
inline fun <A: Any, B: Any, RA: Any> Either<A, B>.flatMapA(transform: (A) -> Either<RA, B>): Either<RA, B> =
if (a != null) {
transform(a!!)
} else Either.ofB(b!!)
@JvmName("flatMapB_changeType")
inline fun <A: Any, B: Any, RB: Any> Either<A, B>.flatMapB(transform: (B) -> Either<A, RB>): Either<A, RB> =
if (b != null) {
transform(b!!)
} else Either.ofA(a!!)
inline fun <A: Any, B: Any, RB: Any> Either<A, B>.mapB(transform: (B) -> RB): Either<A, RB> =
Either.unsafeCreate(a, b?.let(transform))
fun <A, B, R: Any> Either<A, B>.flatten(): R where A: R, B: R =
a ?: assertB()
fun <A, A2: Any, B: Any> Either<A, Either<A2, B>>.partiallyFlattenB(): Either<A2, B> where A: A2 =
mapA { Either.ofA<A2, B>(it) }.flatten()
fun <A: Any, B, B2: Any> Either<Either<A, B2>, B>.partiallyFlattenA(): Either<A, B2> where B: B2 =
mapB { Either.ofB<A, B2>(it) }.flatten()
inline fun <A: Any, BA: Any, BB: Any, BAN: Any> Either<A, Either<BA, BB>>.mapBA(fn: (BA) -> BAN): Either<A, Either<BAN, BB>> =
mapB { it.mapA(fn) }
fun <A, BA, BB, BBN> Either<A, Either<BA, BB>>.mapBB(fn: (BB) -> BBN): Either<A, Either<BA, BBN>> =
inline fun <A: Any, BA: Any, BB: Any, BBN: Any> Either<A, Either<BA, BB>>.mapBB(fn: (BB) -> BBN): Either<A, Either<BA, BBN>> =
mapB { it.mapB(fn) }
fun <AA, AB, B, AAN> Either<Either<AA, AB>, B>.mapAA(fn: (AA) -> AAN): Either<Either<AAN, AB>, B> =
inline fun <AA: Any, AB: Any, B: Any, AAN: Any> Either<Either<AA, AB>, B>.mapAA(fn: (AA) -> AAN): Either<Either<AAN, AB>, B> =
mapA { it.mapA(fn) }
fun <AA, AB, B, ABN> Either<Either<AA, AB>, B>.mapAB(fn: (AB) -> ABN): Either<Either<AA, ABN>, B> =
inline fun <AA: Any, AB: Any, B: Any, ABN: Any> Either<Either<AA, AB>, B>.mapAB(fn: (AB) -> ABN): Either<Either<AA, ABN>, B> =
mapA { it.mapB(fn) }
fun <AA, AB, B> Either<Either<AA, AB>, B>.getAAOrNull(): AA? =
getAOrNull()?.getAOrNull()
fun <AA: Any, AB: Any, B: Any> Either<Either<AA, AB>, B>.getAAOrNull(): AA? =
a?.a
fun <AA, AB, B> Either<Either<AA, AB>, B>.getABOrNull(): AB? =
getAOrNull()?.getBOrNull()
fun <AA: Any, AB: Any, B: Any> Either<Either<AA, AB>, B>.getABOrNull(): AB? =
a?.b
fun <A, BA, BB> Either<A, Either<BA, BB>>.getBAOrNull(): BA? =
getBOrNull()?.getAOrNull()
fun <A: Any, BA: Any, BB: Any> Either<A, Either<BA, BB>>.getBAOrNull(): BA? =
b?.a
fun <A, BA, BB> Either<A, Either<BA, BB>>.getBBOrNull(): BB? =
getBOrNull()?.getBOrNull()
fun <A: Any, BA: Any, BB: Any> Either<A, Either<BA, BB>>.getBBOrNull(): BB? =
b?.b
inline fun <A: Any, B: Any, RA: Any, RB: Any> Either<A, B>.map(fa: (A) -> RA, fb: (B) -> RB): Either<RA, RB> =
if (a != null) Either.ofA(fa(a!!))
else Either.ofB(fb(b!!))
inline fun <A: Any, B: Any, R> Either<A, B>.flatMap(fa: (A) -> R, fb: (B) -> R): R =
if (a != null) fa(a!!)
else fb(b!!)

View File

@@ -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()

View File

@@ -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)
}

View File

@@ -21,10 +21,10 @@ interface Obj<T> {
}
}
fun <I, O> Obj<I>?.mapNotNull(transform: (I) -> O): Obj<O>? =
inline fun <I, O> Obj<I>?.mapNotNull(transform: (I) -> O): Obj<O>? =
this?.v?.let { Obj.of(transform(it)) }
fun <I, O> Obj<I>.map(transform: (I) -> O): Obj<O> =
inline fun <I, O> Obj<I>.map(transform: (I) -> O): Obj<O> =
Obj.of(transform(v))
interface MutObj<T> {

View File

@@ -1,87 +0,0 @@
package blitz
import blitz.collections.selfInitializingSequence
class OperationChain<I, O> private constructor(
private val impl: Impl = Impl()
) {
private var until = 0
private class Impl {
val seqe = mutableListOf<(Sequence<Any?>) -> Sequence<Any?>>()
var finalized = false
fun add(op: Operator<*, *>) {
seqe += { seq: Sequence<Any?> ->
seq.map(op as Operator<Any?, Any?>)
}
}
fun addFlat(op: Operator<*, Sequence<*>>) {
seqe += { seq: Sequence<Any?> ->
seq.flatMap(op as Operator<Any?, Sequence<Any?>>)
}
}
}
fun <NO> map(op: Operator<O, NO>): OperationChain<I, NO> =
OperationChain<I, NO>(impl.also { it.add(op) })
.also { it.until = this.until + 1 }
fun <NO> flatMap(op: Operator<O, Sequence<NO>>): OperationChain<I, NO> =
OperationChain<I, NO>(impl.also { it.addFlat(op) })
.also { it.until = this.until + 1 }
fun <NO> map(op: OperationChain<O, NO>): OperationChain<I, NO> {
if (!op.impl.finalized)
throw Exception("Can not map un-finalized operation chain onto operation chain!")
return flatMap(op::process)
}
fun <NO> modifier(op: Operator<Sequence<O>, Sequence<NO>>): OperationChain<I, NO> =
OperationChain<I, NO>(impl.also { it.seqe.add(op as (Sequence<Any?>) -> Sequence<Any?>) })
.also { it.until = this.until + 1 }
fun finalize(): OperationChain<I, O> {
if (impl.finalized)
throw Exception("Can't finalize a finalized OperationChain!")
impl.finalized = true
return this
}
fun process(v: I): Sequence<O> =
selfInitializingSequence {
var seq = sequenceOf<Any?>(v)
impl.seqe
.asSequence()
.take(until)
.forEach { op ->
seq = op(seq)
}
seq as Sequence<O>
}
fun processAll(v: Sequence<I>): Sequence<O> =
v.flatMap { process(it) }
companion object {
internal fun <I> create(): OperationChain<I, I> =
OperationChain()
}
}
fun <T, O> Sequence<T>.map(chain: OperationChain<T, O>): Sequence<O> =
chain.processAll(this)
fun <I> chain(): OperationChain<I, I> =
OperationChain.create()
fun <I, O> OperationChain<I, O>.chunked(size: Int): OperationChain<I, List<O>> =
modifier { it.chunked(size) }
fun <I, O, R> OperationChain<I, O>.chunked(size: Int, transform: (List<O>) -> R): OperationChain<I, R> =
modifier { it.chunked(size, transform) }
fun <I, O> OperationChain<I, O>.filter(predicate: (O) -> Boolean): OperationChain<I, O> =
modifier { it.filter(predicate) }

View File

@@ -0,0 +1,45 @@
package blitz
import blitz.collections.RefVec
import kotlin.contracts.ExperimentalContracts
import kotlin.contracts.InvocationKind
import kotlin.contracts.contract
/**
* all created objects are stored in this pool, and when you call [StupidObjPool.markClear], all objects are marked as free and can be re-used
* this is useful for when you need a ton of objects during a process and the process runs multiple times (caches the objects between executions)
*/
class StupidObjPool<T>(initialCap: Int) {
@JvmField val _objs = RefVec<T>(initialCap)
@JvmField var _nextFreeId = 0
/** only one of constructor or initializer is called */
@OptIn(ExperimentalContracts::class)
inline fun get(constructor: () -> T, initializer: (T) -> Unit): T {
contract {
callsInPlace(constructor, InvocationKind.AT_MOST_ONCE)
callsInPlace(initializer, InvocationKind.AT_MOST_ONCE)
}
if (_nextFreeId < _objs.size) {
val o = _objs[_nextFreeId++]
initializer(o)
return o
}
val o = constructor()
_objs.pushBack(o)
_nextFreeId ++
return o
}
fun clearPool() {
_objs.clear()
_nextFreeId = 0
}
fun markClear() {
_nextFreeId = 0
}
}

View File

@@ -0,0 +1,27 @@
package blitz
data class SwitchCase<C, T: Any, R>(
val cond: (C) -> Pair<Boolean, T?>,
val then: (T) -> R,
)
@Suppress("NOTHING_TO_INLINE")
inline infix fun <C, T: Any, R> ((C)->Pair<Boolean, T?>).case(noinline then: (T) -> R) =
SwitchCase(this, then)
infix fun <R> Regex.startsWithCase(then: (MatchResult) -> R): SwitchCase<String, MatchResult, R> =
{ it: String ->
this.matchAt(it, 0)?.let {
true to it
} ?: (false to null)
} case then
inline fun <T, R> T.switch(vararg cases: SwitchCase<T, *, R>, default: (T) -> R): R {
cases.forEach { (cond, then) ->
val (b, v) = cond(this)
if (b) {
return (then as (Any) -> R)(v!!)
}
}
return default(this)
}

View File

@@ -1,5 +1,3 @@
package blitz
typealias Provider<T> = () -> T
typealias Operator<I, O> = (I) -> O
typealias Provider<T> = () -> T

View File

@@ -0,0 +1,7 @@
package blitz
fun unreachable(): Nothing =
throw UnsupportedOperationException("this should be unreachable")
inline fun <reified R> Any?.cast(): R? =
this?.let { it as? R }

View File

@@ -0,0 +1,4 @@
package blitz.annotations
@Retention(AnnotationRetention.SOURCE)
annotation class DontAccessManually

View File

@@ -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

View File

@@ -8,7 +8,7 @@ interface Buckets<K, V, B> {
fun contents(bucket: B): Contents<Pair<K,V>>
}
class DynBucketsT<K, V, B>(
class DynBucketsT<K, V, B: Any?>(
private val parent: Buckets<K, V, B>
): Buckets<K, V, Any?> {
override fun new(): Any? =

View File

@@ -18,6 +18,9 @@ class ByteVec(private val initCap: Int = 0): Vec<Byte>, ByteBatchSequence {
}
}
fun unsafeBackingArr(): ByteArray =
array
fun copyAsArray(): ByteArray =
array.copyOfRange(0, size)
@@ -36,6 +39,13 @@ class ByteVec(private val initCap: Int = 0): Vec<Byte>, ByteBatchSequence {
cap = size + amount
}
override fun reserve(need: Int, wantIfRealloc: Int) {
if (need > 0 && cap - size >= need)
return
cap = size + wantIfRealloc
array = array.copyOf(cap)
}
override fun popBack(): Byte =
array[size - 1].also {
reserve(-1)
@@ -110,7 +120,7 @@ class ByteVec(private val initCap: Int = 0): Vec<Byte>, ByteBatchSequence {
}
override fun pushBack(elem: Byte) {
reserve(8)
reserve(1, 8)
array[size] = elem
size ++
}
@@ -125,6 +135,9 @@ class ByteVec(private val initCap: Int = 0): Vec<Byte>, ByteBatchSequence {
array[index] = value
}
override fun indexOf(value: Byte): Int =
array.indexOf(value)
companion object {
fun from(bytes: ByteArray) =
ByteVec(bytes.size).also {

View File

@@ -0,0 +1,159 @@
package blitz.collections
import kotlin.contracts.ExperimentalContracts
import kotlin.contracts.contract
class CharVec(private val initCap: Int = 0): Vec<Char>, BatchSequence<Char> {
override var size = 0
private var cap = initCap
private var array = CharArray(initCap)
override fun clear() {
size = 0
if (array.size <= initCap) {
cap = array.size
} else {
cap = initCap
array = CharArray(initCap)
}
}
fun copyAsArray(): CharArray =
array.copyOfRange(0, size)
fun copyIntoArray(arr: CharArray, destOff: Int = 0, startOff: Int = 0) =
array.copyInto(arr, destOff, startOff, size)
override fun copy(): CharVec =
CharVec(size).also {
copyIntoArray(it.array)
}
override fun reserve(amount: Int) {
if (amount > 0 && cap - size >= amount)
return
array = array.copyOf(size + amount)
cap = size + amount
}
override fun reserve(need: Int, wantIfRealloc: Int) {
if (need > 0 && cap - size >= need)
return
cap = size + wantIfRealloc
array = array.copyOf(cap)
}
override fun popBack(): Char =
array[size - 1].also {
reserve(-1)
size --
}
fun tryPopPack(dest: CharArray, destOff: Int = 0): Int {
val can = kotlin.math.min(size, dest.size - destOff)
copyIntoArray(dest, destOff, size - can)
reserve(-can)
size -= can
return can
}
fun popBack(dest: CharArray, destOff: Int = 0) {
val destCopySize = dest.size - destOff
require(size >= destCopySize)
copyIntoArray(dest, destOff, size - destCopySize)
reserve(-destCopySize)
size -= destCopySize
}
@OptIn(ExperimentalContracts::class)
inline fun consumePopBack(batching: CharArray, fn: (CharArray, Int) -> Unit) {
contract {
callsInPlace(fn)
}
while (true) {
val rem = tryPopPack(batching)
if (rem == 0) break
fn(batching, rem)
}
}
inline fun consumePopBack(batching: CharArray, fn: (Char) -> Unit) =
consumePopBack(batching) { batch, count ->
repeat(count) {
fn(batch[it])
}
}
@OptIn(ExperimentalContracts::class)
inline fun consumePopBackSlicedBatches(batching: CharArray, fn: (CharArray) -> Unit) {
contract {
callsInPlace(fn)
}
while (true) {
val rem = tryPopPack(batching)
if (rem == 0) break
if (rem == batching.size)
fn(batching)
else
fn(batching.copyOf(rem))
}
}
override fun get(index: Int): Char =
array[index]
override fun flip() {
array = array.reversedArray()
}
fun pushBack(arr: CharArray) {
reserve(arr.size)
arr.copyInto(array, size)
size += arr.size
}
override fun pushBack(elem: Char) {
reserve(1, 8)
array[size] = elem
size ++
}
override fun iterator(): BatchIterator<Char> =
array.asSequence().asBatch().iterator()
override fun toString(): String =
String(array, 0, size)
fun subViewToString(from: Int, num: Int = size - from): String =
String(array, from, num)
override fun set(index: Int, value: Char) {
array[index] = value
}
override fun indexOf(value: Char): Int =
array.indexOf(value)
companion object {
fun from(data: String) =
CharVec(data.length).also {
data.toCharArray().copyInto(it.array)
it.size = data.length
}
fun from(bytes: CharArray) =
CharVec(bytes.size).also {
bytes.copyInto(it.array)
it.size += bytes.size
}
fun from(bytes: Sequence<Char>) =
CharVec().also { bv ->
bytes.forEach(bv::pushBack)
}
}
}

View File

@@ -0,0 +1,23 @@
package blitz.collections
fun <T> List<T>.containsAt(at: Int, other: List<T>): Boolean {
if (at + other.size > size)
return false
for (i in at..<at+other.size)
if (this[i] != other[i-at])
return false
return true
}
fun <T> List<T>.startsWith(other: List<T>): Boolean =
containsAt(0, other)
fun String.startsWith(re: Regex): Boolean =
re.matchesAt(this, 0)
fun String.substringAfter(m: MatchResult): String =
this.drop(m.value.length)
fun String.substringAfter(re: Regex): String? =
re.matchAt(this, 0)
?.let(this::substringAfter)

View File

@@ -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(

View File

@@ -23,6 +23,6 @@ fun <V> I2HashMap(
): BlitzHashMap<I2HashMapKey, V> =
BlitzHashMap(
bucketCount,
DynBuckets(SortedListBuckets(underlying)),
DynBucketsT<I2HashMapKey, V, _>(SortedListBuckets(underlying)),
I2HashMapKey::cantorHash
)

View File

@@ -24,6 +24,6 @@ fun <V> I3HashMap(
): BlitzHashMap<I3HashMapKey, V> =
BlitzHashMap(
bucketCount,
DynBuckets(SortedListBuckets(underlying)),
DynBucketsT<_, _, _>(SortedListBuckets(underlying)),
I3HashMapKey::cantorHash
)

View File

@@ -0,0 +1,150 @@
package blitz.collections
import kotlin.contracts.ExperimentalContracts
import kotlin.contracts.contract
class IntVec(private val initCap: Int = 0): Vec<Int>, BatchSequence<Int> {
override var size = 0
private var cap = initCap
private var array = IntArray(initCap)
override fun clear() {
size = 0
if (array.size <= initCap) {
cap = array.size
} else {
cap = initCap
array = IntArray(initCap)
}
}
fun copyAsArray(): IntArray =
array.copyOfRange(0, size)
fun copyIntoArray(arr: IntArray, destOff: Int = 0, startOff: Int = 0) =
array.copyInto(arr, destOff, startOff, size)
override fun copy(): IntVec =
IntVec(size).also {
copyIntoArray(it.array)
}
override fun reserve(amount: Int) {
if (amount > 0 && cap - size >= amount)
return
array = array.copyOf(size + amount)
cap = size + amount
}
override fun reserve(need: Int, wantIfRealloc: Int) {
if (need > 0 && cap - size >= need)
return
cap = size + wantIfRealloc
array = array.copyOf(cap)
}
override fun popBack(): Int =
array[size - 1].also {
reserve(-1)
size --
}
fun tryPopPack(dest: IntArray, destOff: Int = 0): Int {
val can = kotlin.math.min(size, dest.size - destOff)
copyIntoArray(dest, destOff, size - can)
reserve(-can)
size -= can
return can
}
fun popBack(dest: IntArray, destOff: Int = 0) {
val destCopySize = dest.size - destOff
require(size >= destCopySize)
copyIntoArray(dest, destOff, size - destCopySize)
reserve(-destCopySize)
size -= destCopySize
}
@OptIn(ExperimentalContracts::class)
inline fun consumePopBack(batching: IntArray, fn: (IntArray, Int) -> Unit) {
contract {
callsInPlace(fn)
}
while (true) {
val rem = tryPopPack(batching)
if (rem == 0) break
fn(batching, rem)
}
}
inline fun consumePopBack(batching: IntArray, fn: (Int) -> Unit) =
consumePopBack(batching) { batch, count ->
repeat(count) {
fn(batch[it])
}
}
@OptIn(ExperimentalContracts::class)
inline fun consumePopBackSlicedBatches(batching: IntArray, fn: (IntArray) -> Unit) {
contract {
callsInPlace(fn)
}
while (true) {
val rem = tryPopPack(batching)
if (rem == 0) break
if (rem == batching.size)
fn(batching)
else
fn(batching.copyOf(rem))
}
}
override fun get(index: Int): Int =
array[index]
override fun flip() {
array = array.reversedArray()
}
fun pushBack(arr: IntArray) {
reserve(arr.size)
arr.copyInto(array, size)
size += arr.size
}
override fun pushBack(elem: Int) {
reserve(1, 8)
array[size] = elem
size ++
}
override fun iterator(): BatchIterator<Int> =
array.asSequence().asBatch().iterator()
override fun toString(): String =
contents.toString()
override fun set(index: Int, value: Int) {
array[index] = value
}
override fun indexOf(value: Int): Int =
array.indexOf(value)
companion object {
fun from(bytes: IntArray) =
IntVec(bytes.size).also {
bytes.copyInto(it.array)
it.size += bytes.size
}
fun from(bytes: Sequence<Int>) =
IntVec().also { bv ->
bytes.forEach(bv::pushBack)
}
}
}

View File

@@ -0,0 +1,3 @@
package blitz.collections
typealias Iter<T> = ((T) -> Unit) -> Unit

View File

@@ -33,6 +33,7 @@ fun <T> generateIterator(fn: () -> T?): Iterator<T> =
/**
* Can't explain this function. Look at the source of [blitz.parse.tokenize] as an example
*/
@Deprecated("tf is this")
fun <T, R> Iterator<T>.funnyMap(fn: (UnGettableIterator<T>) -> R?): Iterator<R> {
val iter = asUnGettable()
return generateIterator { fn(iter) }
@@ -50,4 +51,17 @@ fun <T> Iterator<T>.collect(to: Vec<T>): Vec<T> {
to.pushBack(it)
}
return to
}
inline fun <T> Iterator<T>.collect(crossinline consumer: (T) -> Unit) {
forEachRemaining {
consumer(it)
}
}
fun <T> Iter<T>.collect(to: Vec<T>): Vec<T> {
this {
to.pushBack(it)
}
return to
}

View File

@@ -0,0 +1,51 @@
package blitz.collections
class LightCache<K: Any, V>(
private val keys: Vec<K>,
private val vals: Vec<V>,
) {
private var lastKey: K? = null
private var lastVal: V? = null
@Suppress("UNCHECKED_CAST")
fun getOrPut(key: K, compute: (K) -> V): V {
if (key == lastKey)
return (lastVal as V)
val idx = keys.indexOf(key)
val v = if (idx >= 0) {
vals[idx]
} else {
val x = compute(key)
keys.pushBack(key)
vals.pushBack(x)
x
}
lastKey = key
lastVal = v
return v
}
internal fun getOrNullInternal(key: K): V? {
if (key == lastKey)
return lastVal
val idx = keys.indexOf(key)
return if (idx >= 0) {
lastKey = key
lastVal = vals[idx]
lastVal
} else {
null
}
}
companion object {
inline fun <reified K: Any, reified V> new(initCap: Int = 0): LightCache<K, V> =
LightCache(
SmartVec<K>(initCap),
SmartVec<V>(initCap),
)
}
}
fun <K: Any, V: Any> LightCache<K, V>.getOrNull(key: K): V? =
getOrNullInternal(key)

View File

@@ -30,4 +30,40 @@ fun <T> MutableList<T>.addFront(value: T) =
add(0, value)
fun <T: Any> Iterable<T?>.countNotNull() =
count { it != null }
count { it != null }
fun <T> Iterable<Iterable<T>>.intersections(dest: MutableList<T> = mutableListOf()): MutableList<T> =
reduce { acc, li -> acc.intersect(li) }
.forEach { dest += it }
.let { dest }
fun <T> Iterable<T>.removeAtIndexes(idc: Iterable<Int>, dest: MutableList<T> = mutableListOf()): MutableList<T> =
filterIndexedTo(dest) { index, _ -> index !in idc }
fun <T> List<T>.gather(idc: Iterable<Int>): MutableList<T> {
val dest = mutableListOf<T>()
idc.forEach {
dest += get(it)
}
return dest
}
fun <T> List<T>.before(idx: Int): List<T> =
take(idx)
fun <T> List<T>.after(idx: Int): List<T> =
drop(idx + 1)
inline fun <I, reified O> Collection<I>.mapToArray(fn: (I) -> O): Array<O> {
val iter = this.iterator()
return Array(this.size) {
fn(iter.next())
}
}
inline fun <I, reified O> Collection<I>.mapIndexedToArray(fn: (Int, I) -> O): Array<O> {
val iter = this.iterator()
return Array(this.size) {
fn(it, iter.next())
}
}

View File

@@ -0,0 +1,150 @@
package blitz.collections
import kotlin.contracts.ExperimentalContracts
import kotlin.contracts.contract
class LongVec(private val initCap: Int = 0): Vec<Long>, BatchSequence<Long> {
override var size = 0
private var cap = initCap
private var array = LongArray(initCap)
override fun clear() {
size = 0
if (array.size <= initCap) {
cap = array.size
} else {
cap = initCap
array = LongArray(initCap)
}
}
fun copyAsArray(): LongArray =
array.copyOfRange(0, size)
fun copyIntoArray(arr: LongArray, destOff: Int = 0, startOff: Int = 0) =
array.copyInto(arr, destOff, startOff, size)
override fun copy(): LongVec =
LongVec(size).also {
copyIntoArray(it.array)
}
override fun reserve(amount: Int) {
if (amount > 0 && cap - size >= amount)
return
array = array.copyOf(size + amount)
cap = size + amount
}
override fun reserve(need: Int, wantIfRealloc: Int) {
if (need > 0 && cap - size >= need)
return
cap = size + wantIfRealloc
array = array.copyOf(cap)
}
override fun popBack(): Long =
array[size - 1].also {
reserve(-1)
size --
}
fun tryPopPack(dest: LongArray, destOff: Int = 0): Int {
val can = kotlin.math.min(size, dest.size - destOff)
copyIntoArray(dest, destOff, size - can)
reserve(-can)
size -= can
return can
}
fun popBack(dest: LongArray, destOff: Int = 0) {
val destCopySize = dest.size - destOff
require(size >= destCopySize)
copyIntoArray(dest, destOff, size - destCopySize)
reserve(-destCopySize)
size -= destCopySize
}
@OptIn(ExperimentalContracts::class)
inline fun consumePopBack(batching: LongArray, fn: (LongArray, Int) -> Unit) {
contract {
callsInPlace(fn)
}
while (true) {
val rem = tryPopPack(batching)
if (rem == 0) break
fn(batching, rem)
}
}
inline fun consumePopBack(batching: LongArray, fn: (Long) -> Unit) =
consumePopBack(batching) { batch, count ->
repeat(count) {
fn(batch[it])
}
}
@OptIn(ExperimentalContracts::class)
inline fun consumePopBackSlicedBatches(batching: LongArray, fn: (LongArray) -> Unit) {
contract {
callsInPlace(fn)
}
while (true) {
val rem = tryPopPack(batching)
if (rem == 0) break
if (rem == batching.size)
fn(batching)
else
fn(batching.copyOf(rem))
}
}
override fun get(index: Int): Long =
array[index]
override fun flip() {
array = array.reversedArray()
}
fun pushBack(arr: LongArray) {
reserve(arr.size)
arr.copyInto(array, size)
size += arr.size
}
override fun pushBack(elem: Long) {
reserve(1, 8)
array[size] = elem
size ++
}
override fun iterator(): BatchIterator<Long> =
array.asSequence().asBatch().iterator()
override fun toString(): String =
contents.toString()
override fun set(index: Int, value: Long) {
array[index] = value
}
override fun indexOf(value: Long): Int =
array.indexOf(value)
companion object {
fun from(bytes: LongArray) =
LongVec(bytes.size).also {
bytes.copyInto(it.array)
it.size += bytes.size
}
fun from(bytes: Sequence<Long>) =
LongVec().also { bv ->
bytes.forEach(bv::pushBack)
}
}
}

View File

@@ -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<T>(
val width: Int,
val height: Int,

View File

@@ -0,0 +1,125 @@
package blitz.collections
@Suppress("UNCHECKED_CAST", "NOTHING_TO_INLINE")
class RefVec<T>(private val initCap: Int = 0): Vec<T> {
override var size = 0
@JvmField var _cap = initCap
@JvmField var _array: Array<Any?>? = if (initCap > 0) arrayOfNulls(initCap) else null
override fun clear() {
size = 0
if (_array == null) return
if (_array!!.size <= initCap) {
_cap = _array!!.size
} else {
_cap = 0
_array = null
}
}
inline fun copyAsArray(): Array<Any?> =
_array?.copyOfRange(0, size) ?: emptyArray()
inline fun copyIntoArray(arr: Array<Any?>, destOff: Int = 0, startOff: Int = 0) =
_array?.copyInto(arr, destOff, startOff, size)
override fun copy(): RefVec<T> =
RefVec<T>(size).also {
it._array?.let { copyIntoArray(it) }
}
override fun reserve(amount: Int) {
if (amount > 0 && _cap - size >= amount)
return
if (_array == null) {
_cap = size + amount
_array = arrayOfNulls(_cap)
} else {
_array = _array!!.copyOf(size + amount)
_cap = size + amount
}
}
override fun reserve(need: Int, totalIfRealloc: Int) {
if (need > 0 && _cap - size >= need)
return
if (_array == null) {
_cap = size + totalIfRealloc
_array = arrayOfNulls(_cap)
} else {
_array = _array!!.copyOf(size + totalIfRealloc)
_cap = size + totalIfRealloc
}
}
override fun popBack(): T =
_array!![size - 1].also {
reserve(-1)
size --
} as T
override inline fun get(index: Int): T =
(_array as Array<Any?>)[index] as T
override fun flip() {
_array = _array?.reversedArray()
}
override fun pushBack(elem: T) {
reserve(1, 8)
this[size] = elem
size ++
}
override fun iterator(): Iterator<T> =
object : Iterator<T> {
var index = 0
override fun hasNext(): Boolean = index < size
override fun next(): T {
if (!hasNext())
throw NoSuchElementException()
return _array!![index++] as T
}
}
override fun toString(): String =
joinToString(prefix = "[", postfix = "]") { it.toString() }
override fun set(index: Int, value: T) {
(_array as Array<Any?>)[index] = value
}
inline fun <R, C: MutableCollection<R>> mapTo(dest: C, fn: (T) -> R): C {
_array?.let {
for (i in 0 until size) {
dest.add(fn(it[i] as T))
}
}
return dest
}
inline fun <R> map(fn: (T) -> R): MutableList<R> =
MutableList(size) { fn(this[it]) }
override fun indexOf(value: T): Int =
_array?.indexOf(value) ?: -1
companion object {
fun <T> from(data: Array<T>) =
RefVec<T>(data.size).also {
it._array?.let { data.copyInto(it) }
it.size += data.size
}
fun <T> from(data: Iterable<T>) =
RefVec<T>().also { bv ->
data.forEach(bv::pushBack)
}
inline fun <T> of(vararg elements: T): RefVec<T> =
RefVec<T>(elements.size shl 1).also {
it._array?.let { elements.copyInto(it) }
it.size += elements.size
}
}
}

View File

@@ -41,4 +41,39 @@ fun <T> Sequence<T>.limit(len: Int): Sequence<T> =
}
fun <A, B> IndexableSequence<A>.limitBy(other: Sequence<B>): IndexableSequence<A> =
modifier { it.limitBy(other) }
modifier { it.limitBy(other) }
fun <T> Sequence<T>.hasLeast(n: Int): Boolean {
val i = iterator()
repeat(n) {
if (!i.hasNext())
return false
i.next()
}
return true
}
/** cache already computed values across iterations */
fun <T> Sequence<T>.caching(): Sequence<T> =
object : Sequence<T> {
val cache = RefVec<T>()
val iter = this@caching.iterator()
override fun iterator() = object : Iterator<T> {
var idx = 0
override fun hasNext(): Boolean =
idx < cache.size || iter.hasNext()
override fun next(): T {
val v = if (idx < cache.size) {
cache[idx]
} else {
iter.next()
.also(cache::pushBack)
}
idx ++
return v
}
}
}

View File

@@ -0,0 +1,150 @@
package blitz.collections
import kotlin.contracts.ExperimentalContracts
import kotlin.contracts.contract
class ShortVec(private val initCap: Int = 0): Vec<Short>, BatchSequence<Short> {
override var size = 0
private var cap = initCap
private var array = ShortArray(initCap)
override fun clear() {
size = 0
if (array.size <= initCap) {
cap = array.size
} else {
cap = initCap
array = ShortArray(initCap)
}
}
fun copyAsArray(): ShortArray =
array.copyOfRange(0, size)
fun copyIntoArray(arr: ShortArray, destOff: Int = 0, startOff: Int = 0) =
array.copyInto(arr, destOff, startOff, size)
override fun copy(): ShortVec =
ShortVec(size).also {
copyIntoArray(it.array)
}
override fun reserve(amount: Int) {
if (amount > 0 && cap - size >= amount)
return
array = array.copyOf(size + amount)
cap = size + amount
}
override fun reserve(need: Int, wantIfRealloc: Int) {
if (need > 0 && cap - size >= need)
return
cap = size + wantIfRealloc
array = array.copyOf(cap)
}
override fun popBack(): Short =
array[size - 1].also {
reserve(-1)
size --
}
fun tryPopPack(dest: ShortArray, destOff: Int = 0): Int {
val can = kotlin.math.min(size, dest.size - destOff)
copyIntoArray(dest, destOff, size - can)
reserve(-can)
size -= can
return can
}
fun popBack(dest: ShortArray, destOff: Int = 0) {
val destCopySize = dest.size - destOff
require(size >= destCopySize)
copyIntoArray(dest, destOff, size - destCopySize)
reserve(-destCopySize)
size -= destCopySize
}
@OptIn(ExperimentalContracts::class)
inline fun consumePopBack(batching: ShortArray, fn: (ShortArray, Int) -> Unit) {
contract {
callsInPlace(fn)
}
while (true) {
val rem = tryPopPack(batching)
if (rem == 0) break
fn(batching, rem)
}
}
inline fun consumePopBack(batching: ShortArray, fn: (Short) -> Unit) =
consumePopBack(batching) { batch, count ->
repeat(count) {
fn(batch[it])
}
}
@OptIn(ExperimentalContracts::class)
inline fun consumePopBackSlicedBatches(batching: ShortArray, fn: (ShortArray) -> Unit) {
contract {
callsInPlace(fn)
}
while (true) {
val rem = tryPopPack(batching)
if (rem == 0) break
if (rem == batching.size)
fn(batching)
else
fn(batching.copyOf(rem))
}
}
override fun get(index: Int): Short =
array[index]
override fun flip() {
array = array.reversedArray()
}
fun pushBack(arr: ShortArray) {
reserve(arr.size)
arr.copyInto(array, size)
size += arr.size
}
override fun pushBack(elem: Short) {
reserve(1, 8)
array[size] = elem
size ++
}
override fun iterator(): BatchIterator<Short> =
array.asSequence().asBatch().iterator()
override fun toString(): String =
contents.toString()
override fun set(index: Int, value: Short) {
array[index] = value
}
override fun indexOf(value: Short): Int =
array.indexOf(value)
companion object {
fun from(bytes: ShortArray) =
ShortVec(bytes.size).also {
bytes.copyInto(it.array)
it.size += bytes.size
}
fun from(bytes: Sequence<Short>) =
ShortVec().also { bv ->
bytes.forEach(bv::pushBack)
}
}
}

View File

@@ -0,0 +1,11 @@
package blitz.collections
inline fun <reified T> SmartVec(initCap: Int = 0): Vec<T> =
when (T::class.java) {
Char::class.java -> CharVec(initCap) as Vec<T>
Byte::class.java -> ByteVec(initCap) as Vec<T>
Short::class.java -> ShortVec(initCap) as Vec<T>
Int::class.java -> IntVec(initCap) as Vec<T>
Long::class.java -> LongVec(initCap) as Vec<T>
else -> RefVec(initCap)
}

View File

@@ -1,5 +1,6 @@
package blitz.collections
@Deprecated("Results in unreadable code. Use custom tree instead")
class Tree<T> {
var root: Node<T>? = null
@@ -27,6 +28,7 @@ class Tree<T> {
}
}
@Deprecated("Results in unreadable code. Use custom tree instead")
class Node<T>(
var value: T? = null,
var children: MutableList<Node<T>> = mutableListOf()
@@ -74,6 +76,7 @@ class Node<T>(
}
}
@Deprecated("Results in unreadable code. Use custom tree instead")
interface TreeTraverser<T> {
fun process(node: Node<T>): Boolean /* true if continue, false if stop */

View File

@@ -8,6 +8,9 @@ interface Vec<T>: IndexableSequence<T> {
fun copy(): Vec<T>
fun reserve(amount: Int)
fun reserve(need: Int, totalIfRealloc: Int) {
reserve(need)
}
fun pushBack(elem: T)
fun pushBack(elems: Array<T>) {
@@ -18,6 +21,10 @@ interface Vec<T>: IndexableSequence<T> {
reserve(elems.size)
elems.forEach(::pushBack)
}
fun pushBack(elems: Vec<T>) {
reserve(elems.size)
elems.forEach(::pushBack)
}
fun popBack(): T
fun popBack(dest: Array<T>) {
@@ -30,4 +37,12 @@ interface Vec<T>: IndexableSequence<T> {
operator fun set(index: Int, value: T)
fun clear()
fun indexOf(value: T): Int {
for (i in 0 until size) {
if (this[i] == value)
return i
}
return -1
}
}

View File

@@ -1,20 +0,0 @@
package blitz.ice
class Cooled<T>(private val of: T): Freezable {
private var frozen = false
override fun freeze() {
frozen = true
}
override fun isFrozen(): Boolean {
return frozen
}
fun getOrNull(): T? =
if (isFrozen()) null else of
fun <R> use(block: (T) -> R): R? =
if (isFrozen()) null
else block(of)
}

View File

@@ -1,10 +0,0 @@
package blitz.ice
interface Freezable {
fun freeze()
fun isFrozen(): Boolean
}
inline fun <R> Freezable.map(block: (Freezable) -> R): R? =
if (isFrozen()) null
else block(this)

View File

@@ -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)
)
)
}

View File

@@ -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)
}
}
}

View File

@@ -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)
}
}

View File

@@ -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
}

View File

@@ -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()
}
}

View File

@@ -3,8 +3,14 @@ package blitz.math
infix fun Pair<Int, Int>.lerpi(t: Double): Double =
(1.0f - t) * first + second * t
infix fun Pair<Int, Int>.lerpi(t: Float): Float =
(1.0f - t) * first + second * t
infix fun Pair<Double, Double>.lerpd(t: Double): Double =
(1.0f - t) * first + second * t
infix fun Pair<Float, Float>.lerpf(t: Double): Double =
(1.0f - t) * first + second * t
infix fun Pair<Float, Float>.lerpf(t: Float): Float =
(1.0f - t) * first + second * t

View File

@@ -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..<sizes4x4x4.x) {
for (cz in 0..<sizes4x4x4.z) {
val chunk = VoxelShape4x4x4i(packed[index4x4x4(cx, cy, cz)])
if (chunk.empty) continue
val layer = chunk[y % 4]
if (!layer.empty) return false
}
}
return true
}
inline fun iterNonEmptyLayers(consumer: (Short) -> Unit) {
contract {
callsInPlace(consumer)
}
for (y in 0..<sizes4x4x4.y * 4) {
if (!layerEmpty(y)) {
consumer(y.toShort())
}
}
}
inline fun copy() = PackedVoxelShape3i(packed.copyOf())
override fun toString(): String {
val out = StringBuilder()
iterNonEmptyLayers { y ->
out.append("layer $y:\n")
for (x in 0..<sizes.x) {
out.append(" ")
for (z in 0..<sizes.z) {
out.append(if (this[x, y.toInt(), z]) '#' else '.')
}
out.append('\n')
}
}
return out.toString()
}
fun aabb(): Aabb3s? {
var out: Aabb3s? = null
iterChunks { cp, chunk ->
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)))
}
}
}

View File

@@ -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)
}

View File

@@ -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..<z) {
for (x in 0..<x) {
for (y in 0..<y) {
consumer(Vec3s(x, y, z))
}
}
}
}
fun rotate(direction: Direction): Vec3s =
when (direction) {
Direction.DOWN -> 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)
}

View File

@@ -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
}

View File

@@ -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)
}
}

View File

@@ -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)))
}
}
}

View File

@@ -1,102 +1,189 @@
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<Char, Element> = choose {
it(mapValue(seq("true".toList())) { Element.newBool(true) })
it(mapValue(seq("false".toList())) { Element.newBool(false) })
}
val jsonNull: Parser<Char, Element> =
mapValue(seq("null".toList())) { Element.newNull() }
val jsonNum: Parser<Char, Element> =
mapValue(floatLit, Element::newNum)
val jsonString: Parser<Char, Element> =
mapValue(stringLit, Element::newStr)
val jsonElement = futureRec { jsonElement: Parser<Char, Element> ->
val jsonNum: Parser<Char, Element> = floatLit()
.mapValue(::Number)
val jsonArray: Parser<Char, Element> =
thenIgnore(
thenIgnore(
thenOverwrite(
thenIgnore(just('['), whitespaces),
mapValue(delimitedBy(jsonElement,
chain(whitespaces, ignoreSeq(","), whitespaces)), Element::newArr)),
whitespaces),
just(']')
)
val jsonString: Parser<Char, Element> = stringLit()
.mapValue(::Str)
val jsonObj: Parser<Char, Element> =
mapValue(thenIgnore(thenIgnore(thenOverwrite(
just('{'),
delimitedBy(
then(
thenIgnore(
thenIgnore(
thenOverwrite(
whitespaces,
stringLit),
whitespaces),
just(':')),
jsonElement),
just(','))),
whitespaces),
just('}'))) { Element.newObj(it.toMap()) }
val jsonArray: Parser<Char, Element> = just('[')
.then(jsonElement
.delimitedBy(just(','))
.mapValue(::Array))
.thenIgnore(whitespaces())
.thenIgnore(just(']'))
.mapValue { it.second }
val jsonBool: Parser<Char, Element> = choose(
seq("true".toList()).mapValue { Bool(true) },
seq("false".toList()).mapValue { Bool(false) },
)
val jsonNull: Parser<Char, Element> = seq("null".toList())
.mapValue { Nul() }
val jsonObj: Parser<Char, Element> = just('{')
.then(
whitespaces()
.then(stringLit())
.mapValue { it.second }
.thenIgnore(whitespaces())
.thenIgnore(just(':'))
.then(jsonElement)
.delimitedBy(just(',')))
.thenIgnore(whitespaces())
.thenIgnore(just('}'))
.mapValue { Obj(it.second.toMap()) }
whitespaces()
.then(choose(
jsonArray,
jsonNum,
jsonString,
jsonObj,
jsonBool,
jsonNull
))
.thenIgnore(whitespaces())
.mapValue { it.second }
thenIgnore(thenOverwrite(
whitespaces,
choose {
it(jsonArray)
it(jsonNum)
it(jsonString)
it(jsonObj)
it(jsonBool)
it(jsonNull)
}),
whitespaces)
}
interface Element {
val arr get() = (this as Array).value
val num get() = (this as Number).value
val str get() = (this as Str).value
val obj get() = (this as Obj).value
val bool get() = (this as Bool).value
class Element(
@JvmField val kind: Int,
@JvmField val _boxed: Any? = null,
@JvmField val _num: Double = 0.0,
@JvmField val _bool: Boolean = false,
) {
companion object {
const val NUM = 0
const val BOOL = 1
const val NULL = 2
const val ARR = 3
const val STR = 4
const val OBJ = 5
fun isArr() = this is Array
fun isNum() = this is Number
fun isStr() = this is Str
fun isObj() = this is Obj
fun isBool() = this is Bool
fun isNul() = this is Nul
}
inline fun newNum(v: Double): Element =
Element(NUM, _num = v)
inline fun newBool(v: Boolean): Element =
Element(BOOL, _bool = v)
inline fun newNull(): Element =
Element(NULL)
inline fun newArr(v: RefVec<Element>): Element =
Element(ARR, _boxed = v)
inline fun newStr(v: String): Element =
Element(STR, _boxed = v)
inline fun newObj(v: Map<String, Element>): Element =
Element(OBJ, _boxed = v)
}
data class Array(val value: List<Element>): Element {
override fun toString(): String =
value.joinToString(separator = ", ", prefix = "[", postfix = "]")
when (kind) {
NUM -> uncheckedAsNum().toString()
BOOL -> uncheckedAsBool().toString()
NULL -> "null"
ARR -> uncheckedAsArr().contents.toString()
STR -> "\"${uncheckedAsStr()}\""
OBJ -> uncheckedAsObj().map { "${it.key}: ${it.value}" }.joinToString(prefix = "{", postfix = "}")
else -> unreachable()
}
}
inline fun Element.uncheckedAsNum(): Double =
_num
inline fun Element.uncheckedAsBool(): Boolean =
_bool
inline fun Element.uncheckedAsArr(): RefVec<Element> =
_boxed as RefVec<Element>
inline fun Element.uncheckedAsStr(): String =
_boxed as String
inline fun Element.uncheckedAsObj(): Map<String, Element> =
_boxed as Map<String, Element>
fun Element.asNum(): Double {
require(kind == Element.NUM) { "Element is not a Number" }
return _num
}
data class Number(val value: Double): Element {
override fun toString(): String =
value.toString()
fun Element.asBool(): Boolean {
require(kind == Element.BOOL) { "Element is not a Boolean" }
return _bool
}
data class Str(val value: String): Element {
override fun toString(): String =
"\"$value\""
fun Element.asArr(): RefVec<Element> {
require(kind == Element.ARR) { "Element is not an Array" }
return _boxed as RefVec<Element>
}
data class Obj(val value: Map<String, Element>): Element {
override fun toString(): String =
value.map { (k, v) -> "\"$k\": $v" }.joinToString(separator = ", ", prefix = "{", postfix = "}")
fun Element.asStr(): String {
require(kind == Element.STR) { "Element is not a String" }
return _boxed as String
}
data class Bool(val value: Boolean): Element {
override fun toString(): String =
value.toString()
fun Element.asObj(): Map<String, Element> {
require(kind == Element.OBJ) { "Element is not an Object" }
return _boxed as Map<String, Element>
}
class Nul: Element
fun parse(string: String): ParseResult<Element> =
jsonElement(ParseCtx(string.toList(), 0))
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)
}
}
}

View File

@@ -1,43 +0,0 @@
package blitz.parse
import blitz.parse.comb.*
object NumParse {
private val intBase = parser { it.require("0b")?.to(2) } or
parser { it.require("0x")?.to(16) } or
parser { it.require("0o")?.to(8) } or
constantParser(10)
private val sign = parser { it.require("+")?.to(1) } or
parser { it.require("-")?.to(-1) } or
constantParser(1)
val int = parser { s ->
s.map(sign)?.map(intBase)?.map { str, (sign, base) ->
val chars = when (base) {
2 -> "01"
8 -> "01234567"
10 -> "0123456789"
16 -> "0123456789abcdefABCDEF"
else -> error("wtf")
}
str.asLongAs(*chars.toCharArray()) {
it.toLongOrNull(base)?.times(sign)
}
}
}
val float = parser { s ->
s.map(sign)?.map { str, sign ->
str.asLongAs('0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '.') {
it.toDoubleOrNull()?.times(sign)
}
}
}
}
fun parseInt(str: String): Long? =
NumParse.int(Parsable(str))?.second
fun parseDouble(str: String): Double? =
NumParse.float(Parsable(str))?.second

View File

@@ -1,109 +0,0 @@
package blitz.parse.comb
import blitz.str.collectToString
data class Parsable(
val str: String,
val loc: Int? = null
)
typealias Parser<T> = (Parsable) -> Pair<Parsable, T>?
fun <T> parser(fn: (Parsable) -> Pair<Parsable, T>?): Parser<T> =
fn
fun <T> Parser<T>.trim(): Parser<T> = parser {
it.whitespaces()
.map(this@trim)
?.whitespaces()
}
fun <T> constantParser(const: T): Parser<T> = { it to const }
infix fun <T> Parser<T>.or(other: Parser<T>): Parser<T> = {
this@or(it) ?: other(it)
}
fun Parsable.spaces(): Parsable {
val new = str.trimStart(' ')
return Parsable(new, loc?.let { it + str.length - new.length })
}
fun Parsable.whitespaces(): Parsable {
val new = str.trimStart()
return Parsable(new, loc?.let { it + str.length - new.length })
}
fun Parsable.require(what: String): Parsable? {
if (str.startsWith(what))
return Parsable(str.substring(what.length), loc?.let { it + what.length })
return null
}
fun <T> Parsable.untilRequire(c: String, map: (String) -> T?): Pair<Parsable, T>? {
val before = str.substringBefore(c)
return map(before)?.let { Parsable(str.substringAfter(c), loc?.let { it + before.length }) to it }
}
fun <T> Parsable.asLongAs(vararg li: Char, map: (String) -> T?): Pair<Parsable, T>? {
val o = mutableListOf<Char>()
for (c in str) {
if (c in li)
o.add(c)
else
break
}
val out = str.substring(o.size)
return map(o.iterator().collectToString())?.let { Parsable(out, loc?.plus(o.size)) to it }
}
fun <T> Parsable.map(parser: Parser<T>): Pair<Parsable, T>? =
parser(this)
fun <T, R> Pair<Parsable, T>.map(fn: (Parsable, T) -> Pair<Parsable, R>?): Pair<Parsable, R>? =
fn(first, second)
fun <A, B> Pair<Parsable, A>.map(parser: Parser<B>): Pair<Parsable, Pair<A, B>>? =
map { parsable, a ->
parser(parsable)?.let { r ->
r.first to (a to r.second)
}
}
fun <T> Pair<Parsable, T>.mapFirst(fn: (Parsable) -> Parsable): Pair<Parsable, T> =
fn(first) to second
fun <T> Pair<Parsable, T>.mapFirstNullable(fn: (Parsable) -> Parsable?): Pair<Parsable, T>? =
fn(first)?.let { it to second }
fun <T, R> Pair<Parsable, T>.mapSecond(fn: (T) -> R): Pair<Parsable, R> =
first to fn(second)
fun <T> Pair<Parsable, T>.spaces(): Pair<Parsable, T> =
mapFirst { it.spaces() }
fun <T> Pair<Parsable, T>.whitespaces(): Pair<Parsable, T> =
mapFirst { it.whitespaces() }
fun <T> Pair<Parsable, T>.require(what: String): Pair<Parsable, T>? =
mapFirstNullable { it.require(what) }
fun <T> Parsable.array(sep: String, map: (Parsable) -> Pair<Parsable, T>?): Pair<Parsable, List<T>> {
val out = mutableListOf<T>()
var loc = loc
var curr = str
fun step() =
map(Parsable(curr, loc))?.also {
curr = it.first.str
loc = it.first.loc
}
while (true) {
val r = step() ?: break
out.add(r.second)
curr = (Parsable(curr, loc).require(sep) ?: break).str
}
return Parsable(curr, loc) to out
}

View File

@@ -1,36 +0,0 @@
package blitz.parse.comb
fun Parsable.stringWithEscape(): Pair<Parsable, String>? {
var escaped = false
var index = 0
val out = StringBuilder()
for (c in str) {
if (index == 0) {
if (c != '"')
return null
} else {
if (escaped) {
escaped = false
when (c) {
'"' -> out.append('"')
'\\' -> out.append('\\')
'n' -> out.append('\n')
'r' -> out.append('\r')
'b' -> out.append('\b')
't' -> out.append('\t')
else -> return null
}
} else if (c == '"')
break
else if (c == '\\')
escaped = true
else {
out.append(c)
}
}
index ++
}
if (escaped)
return null
return Parsable(str.substring(index + 1), loc?.plus(index + 1)) to out.toString()
}

View File

@@ -1,15 +1,14 @@
package blitz.parse.comb2
import blitz.Either
import blitz.Provider
import blitz.collections.contents
import blitz.partiallyFlattenA
import blitz.partiallyFlattenB
import blitz.*
import blitz.collections.RefVec
import blitz.collections.containsAt
import blitz.parse.JSON.jsonElement
import blitz.str.charsToString
data class ParseCtx<I>(
val input: List<I>,
var idx: Int
@JvmField val input: List<I>,
@JvmField var idx: Int
) {
fun loadFrom(old: ParseCtx<I>) {
idx = old.idx
@@ -17,187 +16,272 @@ data class ParseCtx<I>(
}
data class ParseError(
val loc: Int,
val message: String?,
@JvmField val loc: Int, /** can be -1 */
@JvmField val message: String?,
)
typealias ParseResult<O> = Either<O, List<ParseError>>
typealias ParseResult<O> = Either<O, ParseError>
typealias Parser<I, O> = (ParseCtx<I>) -> ParseResult<O>
inline fun <I, M, O> Parser<I, M>.mapValue(crossinline fn: (M) -> O): Parser<I, O> =
{ invoke(it).mapA { fn(it) } }
inline fun <I, O> Parser<I, O>.mapErrors(crossinline fn: (List<ParseError>) -> List<ParseError>): Parser<I, O> =
{ invoke(it).mapB { fn(it) } }
fun <I, M, O> Parser<I, M>.then(other: Parser<I, O>): Parser<I, Pair<M, O>> =
{ ctx ->
invoke(ctx).mapA { first ->
other.invoke(ctx)
.mapA { first to it }
}.partiallyFlattenA()
inline fun <I, M: Any, O: Any> mapValue(crossinline self: Parser<I, M>, crossinline fn: (M) -> O): Parser<I, O> =
{
val r = self(it) as Either<Any, ParseError>
r.a?.let {
r.a = fn(it as M)
}
r as Either<O, ParseError>
}
fun <I, O, T> Parser<I, O>.thenIgnore(other: Parser<I, T>): Parser<I, O> =
inline fun <I, O: Any> mapErrors(crossinline self: Parser<I, O>, crossinline fn: (ParseError) -> ParseError): Parser<I, O> =
{
val r = self(it)
r.b?.let { r.b = fn(it) }
r
}
inline fun <I, M: Any, O: Any> then(crossinline self: Parser<I, M>, crossinline other: Parser<I, O>): Parser<I, Pair<M, O>> =
{ ctx ->
invoke(ctx).mapA { first ->
val r0 = self(ctx) as ParseResult<Any>
r0.a?.let { first ->
val r1 = other(ctx)
r1.a?.let { second ->
(r1 as ParseResult<Any>).a = Pair(first, second)
(r1 as ParseResult<Pair<M, O>>)
} ?: (r1 as ParseResult<Pair<M, O>>)
} ?: (r0 as ParseResult<Pair<M, O>>)
}
inline fun <I, M: Any, O: Any> thenOverwrite(crossinline self: Parser<I, M>, crossinline other: Parser<I, O>): Parser<I, O> =
{ ctx ->
self(ctx).flatMapA<_,_,O> {
other.invoke(ctx)
}
}
inline fun <I, O: Any, T: Any> thenIgnore(crossinline self: Parser<I, O>, crossinline other: Parser<I, T>): Parser<I, O> =
{ ctx ->
self(ctx).flatMapA { first ->
other.invoke(ctx)
.mapA { first }
}.partiallyFlattenA()
}
}
fun <I, O> Parser<I, O>.orElseVal(value: O): Parser<I, O> =
orElse { Either.ofA(value) }
inline fun <I, O: Any> orElseVal(crossinline self: Parser<I, O>, value: O): Parser<I, O> =
orElse(self) { Either.ofA(value) }
fun <I, O: Any> Parser<I, O>.orNot(): Parser<I, O?> =
orElse { Either.ofA(null) }
fun <I, O, R> Parser<I, O>.orElse(other: Parser<I, R>): Parser<I, R> where O: R =
inline fun <I, O, R: Any> orElse(crossinline self: Parser<I, O>, crossinline other: Parser<I, R>): Parser<I, R> where O: R =
{
val old = it.copy()
this(it).mapB { err ->
it.loadFrom(old)
val old = it.idx
self(it).mapB { _ ->
it.idx = old
other.invoke(it)
.mapB { err + it }
}.partiallyFlattenB()
}
fun <I, O> choose(possible: Iterable<Parser<I, O>>): Parser<I, O> =
/** Use the other choose that takes a function whenever possible because of perf */
fun <I, O: Any> choose(possible: Iterable<Parser<I, O>>): Parser<I, O> =
{ ctx ->
val errors = mutableListOf<ParseError>()
var res: O? = null
for (p in possible) {
val old = ctx.copy()
val old = ctx.idx
val t = p.invoke(ctx)
if (t.isA) {
res = t.getA()
if (t.isA()) {
res = t.a!!
break
} else {
ctx.loadFrom(old)
errors += t.getB()
ctx.idx = old
}
}
res?.let { Either.ofA(it) }
?: Either.ofB(errors)
?: Either.ofB(ParseError(ctx.idx, "none of the possible parsers match"))
}
fun <I, O> choose(vararg possible: Parser<I, O>): Parser<I, O> =
inline fun <I, O: Any> choose(crossinline fn: (run: (Parser<I, O>) -> Unit) -> Unit): Parser<I, O> =
{ ctx ->
var res: O? = null
fn { p ->
if (res == null) {
val old = ctx.idx
val t = p.invoke(ctx)
if (t.isA()) {
res = t.a!!
} else {
ctx.idx = old
}
}
}
res?.let { Either.ofA(it) }
?: Either.ofB(ParseError(ctx.idx, "none of the possible parsers match"))
}
/** Use the other choose that takes a function whenever possible because of perf */
inline fun <I, O: Any> choose(vararg possible: Parser<I, O>): Parser<I, O> =
choose(possible.toList())
fun <I, O> Parser<I, O>.repeated(): Parser<I, List<O>> =
inline fun <I, O: Any> repeated(crossinline what: Parser<I, O>): Parser<I, RefVec<O>> =
{ ctx ->
val out = mutableListOf<O>()
val out = RefVec<O>(16)
while (true) {
val old = ctx.copy()
val t = invoke(ctx)
if (t.isA) {
out += t.getA()
val old = ctx.idx
val t = what(ctx)
if (t.isA()) {
out.pushBack(t.a!!)
} else {
ctx.loadFrom(old)
ctx.idx = old
break
}
}
Either.ofA(out)
}
inline fun <I, O> Parser<I, O>.verifyValue(crossinline verif: (O) -> String?): Parser<I, O> =
inline fun <I, O: Any> repeatedNoSave(crossinline what: Parser<I, O>): Parser<I, Unit> =
{ ctx ->
invoke(ctx).mapA<ParseResult<O>> {
verif(it)?.let { Either.ofB(listOf(ParseError(ctx.idx, it))) }
?: Either.ofA(it)
}.partiallyFlattenA()
}
inline fun <I, O> Parser<I, Pair<IntRange, O>>.verifyValueWithSpan(crossinline fn: (O) -> String?): Parser<I, O> =
{ ctx ->
invoke(ctx).mapA<ParseResult<O>> { (span, v) ->
fn(v)?.let { Either.ofB(listOf(ParseError(span.first, it))) }
?: Either.ofA(v)
}.partiallyFlattenA()
}
fun <I, O: Any?> Parser<I, O?>.errIfNull(msg: String = "parser value was null internally"): Parser<I, O> =
verifyValue { if (it == null) msg else null }
.mapValue { it!! }
inline fun <I, O> location(crossinline fn: (Int) -> O): Parser<I, O> =
{ Either.ofA(fn(it.idx)) }
fun <I> location(): Parser<I, Int> =
location { it }
fun <I, O> withSpan(p: Parser<I, O>): Parser<I, Pair<IntRange, O>> =
location<I>()
.then(p)
.then(location())
.mapValue { (beginAndV, end) ->
(beginAndV.first..end) to beginAndV.second
}
fun <I, O> value(value: O): Parser<I, O> =
{ Either.ofA(value) }
fun <I, O> chain(parsers: List<Parser<I, O>>): Parser<I, List<O>> =
{ ctx ->
val results = mutableListOf<O>()
val errs = mutableListOf<ParseError>()
for (p in parsers) {
val r = p.invoke(ctx)
if (r.isA) {
results += r.getA()
} else {
errs += r.getB()
while (true) {
val old = ctx.idx
val t = what(ctx)
if (t.isB()) {
ctx.idx = old
break
}
}
if (errs.isNotEmpty()) Either.ofB(errs)
else Either.ofA(results)
Either.ofA(Unit)
}
fun <I> seq(want: List<I>): Parser<I, List<I>> =
chain(want.map(::just))
inline fun <I> filter(msg: String, crossinline filter: (I) -> Boolean): Parser<I, I> =
inline fun <I, O: Any> verifyValue(crossinline self: Parser<I, O>, crossinline verif: (O) -> String?): Parser<I, O> =
{ ctx ->
if (ctx.idx >= ctx.input.size) {
Either.ofB(listOf(ParseError(ctx.idx, "unexpected end of file")))
} else {
val i = ctx.input[ctx.idx++]
if (filter(i)) Either.ofA(i)
else Either.ofB(listOf(ParseError(ctx.idx - 1, msg)))
self(ctx).flatMapA<_,_,_> {
verif(it)?.let { Either.ofB(ParseError(ctx.idx, it)) }
?: Either.ofA(it)
}
}
fun <I> just(want: I): Parser<I, I> =
filter("expected $want") { it == want }
inline fun <I, O: Any> verifyValueWithSpan(crossinline self: Parser<I, Pair<IntRange, O>>, crossinline fn: (O) -> String?): Parser<I, O> =
{ ctx ->
self(ctx).flatMapA<_,_,_> { (span, v) ->
fn(v)?.let { Either.ofB(ParseError(span.first, it)) }
?: Either.ofA(v)
}
}
fun <I> oneOf(possible: Iterable<I>): Parser<I, I> =
filter("expected one of ${possible.contents}") { it in possible }
inline fun <I, O: Any> location(crossinline fn: (Int) -> O): Parser<I, O> =
{ Either.ofA(fn(it.idx)) }
fun <I, O> future(prov: Provider<Parser<I, O>>): Parser<I, O> =
inline fun <I> location(): Parser<I, Int> =
location { it }
fun <I, O: Any> withSpan(p: Parser<I, O>): Parser<I, Pair<IntRange, O>> =
mapValue(then(then(location(), p), location())) { (beginAndV, end) ->
(beginAndV.first..end) to beginAndV.second
}
inline fun <I, O: Any> value(value: O): Parser<I, O> =
{ Either.ofA(value) }
fun <I, O: Any> chain(parsers: List<Parser<I, O>>): Parser<I, RefVec<O>> =
{ ctx ->
val results = RefVec<O>(parsers.size)
var errs: ParseError? = null
for (p in parsers) {
val r = p.invoke(ctx)
if (r.isA()) {
results.pushBack(r.a!!)
} else {
errs = r.b!!
break
}
}
if (errs != null) Either.ofB(errs)
else Either.ofA(results)
}
inline fun <I, O: Any> chain(vararg parsers: Parser<I, O>): Parser<I, RefVec<O>> =
chain(parsers.toList())
inline fun <I: Any> seq(want: List<I>): Parser<I, RefVec<I>> =
chain(want.map(::just))
inline fun seq(want: String): Parser<Char, RefVec<Char>> =
chain(want.map(::just))
inline fun ignoreSeq(want: String): Parser<Char, Unit> =
{ ctx ->
if (ctx.idx >= ctx.input.size) {
Either.ofB(ParseError(ctx.idx, "unexpected end of file"))
} else {
if (ctx.input.containsAt(ctx.idx, want.toList())) {
ctx.idx += want.length
Either.ofA(Unit)
} else {
Either.ofB(ParseError(ctx.idx, "expected $want"))
}
}
}
inline fun <I: Any> filter(msg: String, crossinline filter: (I) -> Boolean): Parser<I, I> =
{ ctx ->
if (ctx.idx >= ctx.input.size) {
Either.ofB(ParseError(ctx.idx, "unexpected end of file"))
} else {
val i = ctx.input[ctx.idx++]
if (filter(i)) Either.ofA(i)
else Either.ofB(ParseError(ctx.idx - 1, msg))
}
}
private class JustParse<I: Any>(wantIn: I): Parser<I, I> {
@JvmField val want = wantIn
@JvmField val uef: ParseResult<I> = Either.ofB(ParseError(-1, "unexpected end of file"))
@JvmField val exdiff: ParseResult<I> = Either.ofB(ParseError(-1, "expected $wantIn"))
@JvmField val eitherOfWant: ParseResult<I> = Either.ofA(want)
override fun invoke(ctx: ParseCtx<I>): ParseResult<I> {
return if (ctx.idx >= ctx.input.size) uef
else {
val i = ctx.input[ctx.idx++]
if (i == want) eitherOfWant
else exdiff
}
}
}
fun <I: Any> just(wantIn: I): Parser<I, I> =
JustParse(wantIn)
inline fun <I: Any> oneOf(possible: Iterable<I>): Parser<I, I> =
filter("expected different") { it in possible }
inline fun <I, O: Any> future(crossinline prov: Provider<Parser<I, O>>): Parser<I, O> =
{ prov()(it) }
inline fun <I, O> futureRec(crossinline fn: (future: Parser<I, O>) -> Parser<I, O>): Parser<I, O> {
inline fun <I, O: Any> futureRec(fn: (future: Parser<I, O>) -> Parser<I, O>): Parser<I, O> {
lateinit var f: Parser<I, O>
f = fn(future { f })
f = fn({ f(it) })
return f
}
/** group values 0 is the entire match */
fun <O> regex(pattern: Regex, fn: (groups: MatchGroupCollection) -> O): Parser<Char, O> =
fun <O: Any> regex(pattern: Regex, fn: (groups: MatchGroupCollection) -> O): Parser<Char, O> =
{ ctx ->
pattern.matchAt(ctx.input.charsToString(), ctx.idx)?.let {
ctx.idx = it.range.last + 1
Either.ofA(fn(it.groups))
} ?: Either.ofB(listOf(
} ?: Either.ofB(
ParseError(ctx.idx, "regular expression \"$pattern\" does not apply")
))
)
}
fun regex(pattern: Regex) = regex(pattern) { it[0]!!.value }
/** group values 0 is the entire match */
fun <O> regex(pattern: String, fn: (groups: MatchGroupCollection) -> O): Parser<Char, O> =
fun <O: Any> regex(pattern: String, fn: (groups: MatchGroupCollection) -> O): Parser<Char, O> =
regex(Regex(pattern), fn)
fun regex(pattern: String) = regex(pattern) { it[0]!!.value }
fun regex(pattern: String) = regex(pattern) { it[0]!!.value }
fun <O: Any> ParseResult<O>.unwrap(): O =
flatMap(
{ it },
{ throw Exception("at ${it.loc}: ${it.message}") }
)
fun <I, O: Any> Parser<I, O>.run(input: List<I>): ParseResult<O> =
this(ParseCtx(input, 0))

View File

@@ -1,67 +1,70 @@
package blitz.parse.comb2
import blitz.collections.RefVec
import blitz.str.charsToString
import kotlin.math.absoluteValue
import kotlin.math.sign
fun whitespaces(): Parser<Char, String> =
oneOf("\n\t\r\b ".toList())
.repeated()
.mapValue { it.charsToString() }
private fun isWhitespace(it: Char) =
it == ' ' || it == '\n' || it == '\t' || it == '\r' || it == '\b'
val whitespaces: Parser<Char, Unit> =
repeatedNoSave(filter("expected whitespace", ::isWhitespace))
fun digit(): Parser<Char, Char> =
oneOf("0123456789".toList())
val digit: Parser<Char, Char> =
filter("expected digit") { it >= '0' && it <= '9' }
fun uintLit(): Parser<Char, UInt> =
withSpan(digit().repeated())
.verifyValueWithSpan { if (it.isEmpty()) "need digits after sign in num lit" else null }
.mapValue { it.charsToString().toUInt() }
val uintLit: Parser<Char, RefVec<Char>> =
verifyValue(repeated(digit))
{ if (it.size == 0) "need digits after sign in num lit" else null }
fun intLit(): Parser<Char, Int> =
choose(just('+').mapValue { +1 },
just('-').mapValue { -1 },
value(+1))
.then(uintLit())
.mapValue { (sign, v) -> sign * v.toInt() }
val intLit: Parser<Char, Long> =
mapValue(then(choose<Char, Int> {
it(mapValue(just('+')) { +1 })
it(mapValue(just('-')) { -1 })
it(value(+1))
}, uintLit))
{ (sign, v) -> sign * (v.charsToString().toLongOrNull() ?: Long.MAX_VALUE) }
fun floatLit(): Parser<Char, Double> =
intLit()
.then(just('.')
.then(uintLit())
.mapValue { it.second }
.orElseVal(0u))
.mapValue { (pre, post) ->
var p = post.toDouble()
while (p.absoluteValue >= 1) {
p *= 0.1
}
(pre.toDouble().absoluteValue + p) * pre.toDouble().sign
val floatLit: Parser<Char, Double> =
mapValue(
then(
intLit,
orElseVal(
thenOverwrite(just('.'), uintLit),
RefVec.of('0'))))
{ (pre, post) ->
var p = post.charsToString().toDouble()
while (p.absoluteValue >= 1) {
p *= 0.1
}
fun escapeChar(): Parser<Char, Char> =
just('\\').then(
choose(just('"'),
just('\''),
just('\\'),
just('n').mapValue { '\n' },
just('r').mapValue { '\r' },
just('b').mapValue { '\b' },
just('t').mapValue { '\t' })
.mapErrors { listOf(ParseError(it.first().loc, "invalid escape sequence")) }
).mapValue { it.second }
(pre.toDouble().absoluteValue + p) * pre.toDouble().sign
}
fun stringLit(): Parser<Char, String> =
just('"')
.then(choose(escapeChar(),
filter("a") { it != '"' })
.repeated())
.thenIgnore(just('"'))
.mapValue { (_, str) -> str.charsToString() }
val escapeChar: Parser<Char, Char> =
thenOverwrite(just('\\'),
mapErrors(choose {
it(just('"'))
it(just('\''))
it(just('\\'))
it(mapValue(just('n')) { '\n' })
it(mapValue(just('r')) { '\r' })
it(mapValue(just('b')) { '\b' })
it(mapValue(just('t')) { '\t' })
})
{ ParseError(it.loc, "invalid escape sequence") }
)
fun <I, O, T> Parser<I, O>.delimitedBy(delim: Parser<I, T>): Parser<I, List<O>> =
thenIgnore(delim)
.repeated()
.then(this)
.mapValue { (a, b) -> a + b }
.orElse(value(listOf()))
val stringLit: Parser<Char, String> =
mapValue(thenIgnore(then(just('"'),
repeated(choose<Char,Char>{
it(escapeChar)
it(filter("a") { it != '"' })
})),
just('"')))
{ (_, str) -> str.charsToString() }
inline fun <I, O: Any, T: Any> delimitedBy(crossinline self: Parser<I, O>, crossinline delim: Parser<I, T>): Parser<I, RefVec<O>> =
orElse(mapValue(then(repeated(thenIgnore(self, delim)), self))
{ (a, b) -> a.pushBack(b); a },
value(RefVec.of()))

View File

@@ -1,4 +1,21 @@
package blitz.str
import blitz.collections.ByteVec
import blitz.collections.CharVec
import blitz.collections.Vec
fun Collection<Char>.charsToString(): String =
String(this.toCharArray())
String(this.toCharArray())
fun Vec<Char>.charsToString(): String =
when (this) {
is CharVec -> subViewToString(0)
else -> String(CharArray(size) { this[it] })
}
@JvmName("charsToString_VecByte")
fun Vec<Byte>.charsToString(): String =
when (this) {
is ByteVec -> String(unsafeBackingArr())
else -> String(CharArray(size) { this[it].toInt().toChar() })
}

View File

@@ -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")

View File

@@ -0,0 +1,5 @@
package blitz.test.annotations
@Target(AnnotationTarget.FUNCTION, AnnotationTarget.CLASS)
@Retention(AnnotationRetention.SOURCE)
annotation class Test

View File

@@ -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> T.requireEqual(other: T) =
requireEqual(this, other)

View File

@@ -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() {