Compare commits
10 Commits
39f34ee77b
...
5b95c4dd64
Author | SHA1 | Date | |
---|---|---|---|
5b95c4dd64 | |||
c32b20e5b3 | |||
![]() |
b83109c751 | ||
![]() |
f10ebb7427 | ||
![]() |
6c44aae9cf | ||
![]() |
4ed0225b4f | ||
![]() |
f0b2736af5 | ||
![]() |
234a682f7e | ||
![]() |
f18798bb5c | ||
![]() |
8c2325bdd3 |
4
.gitignore
vendored
4
.gitignore
vendored
@@ -1,3 +1,7 @@
|
||||
.env
|
||||
.idea
|
||||
gradle/
|
||||
|
||||
.gradle
|
||||
build/
|
||||
!gradle/wrapper/gradle-wrapper.jar
|
||||
|
@@ -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")
|
||||
}
|
||||
```
|
||||
|
||||
|
@@ -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
12
gradlew
vendored
@@ -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
26
gradlew.bat
vendored
@@ -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
|
||||
|
@@ -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
|
||||
|
@@ -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!!)
|
@@ -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()
|
@@ -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)
|
||||
}
|
@@ -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> {
|
||||
|
@@ -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) }
|
45
src/main/kotlin/blitz/StupidObjPool.kt
Normal file
45
src/main/kotlin/blitz/StupidObjPool.kt
Normal 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
|
||||
}
|
||||
}
|
27
src/main/kotlin/blitz/Switch.kt
Normal file
27
src/main/kotlin/blitz/Switch.kt
Normal 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)
|
||||
}
|
@@ -1,5 +1,3 @@
|
||||
package blitz
|
||||
|
||||
typealias Provider<T> = () -> T
|
||||
|
||||
typealias Operator<I, O> = (I) -> O
|
7
src/main/kotlin/blitz/Utils.kt
Normal file
7
src/main/kotlin/blitz/Utils.kt
Normal 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 }
|
4
src/main/kotlin/blitz/annotations/Access.kt
Normal file
4
src/main/kotlin/blitz/annotations/Access.kt
Normal file
@@ -0,0 +1,4 @@
|
||||
package blitz.annotations
|
||||
|
||||
@Retention(AnnotationRetention.SOURCE)
|
||||
annotation class DontAccessManually
|
25
src/main/kotlin/blitz/annotations/Mutability.kt
Normal file
25
src/main/kotlin/blitz/annotations/Mutability.kt
Normal 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
|
@@ -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? =
|
||||
|
@@ -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 {
|
||||
|
159
src/main/kotlin/blitz/collections/CharVec.kt
Normal file
159
src/main/kotlin/blitz/collections/CharVec.kt
Normal 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)
|
||||
}
|
||||
}
|
||||
}
|
23
src/main/kotlin/blitz/collections/Compare.kt
Normal file
23
src/main/kotlin/blitz/collections/Compare.kt
Normal 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)
|
@@ -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(
|
||||
|
@@ -23,6 +23,6 @@ fun <V> I2HashMap(
|
||||
): BlitzHashMap<I2HashMapKey, V> =
|
||||
BlitzHashMap(
|
||||
bucketCount,
|
||||
DynBuckets(SortedListBuckets(underlying)),
|
||||
DynBucketsT<I2HashMapKey, V, _>(SortedListBuckets(underlying)),
|
||||
I2HashMapKey::cantorHash
|
||||
)
|
@@ -24,6 +24,6 @@ fun <V> I3HashMap(
|
||||
): BlitzHashMap<I3HashMapKey, V> =
|
||||
BlitzHashMap(
|
||||
bucketCount,
|
||||
DynBuckets(SortedListBuckets(underlying)),
|
||||
DynBucketsT<_, _, _>(SortedListBuckets(underlying)),
|
||||
I3HashMapKey::cantorHash
|
||||
)
|
150
src/main/kotlin/blitz/collections/IntVec.kt
Normal file
150
src/main/kotlin/blitz/collections/IntVec.kt
Normal 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)
|
||||
}
|
||||
}
|
||||
}
|
3
src/main/kotlin/blitz/collections/Iter.kt
Normal file
3
src/main/kotlin/blitz/collections/Iter.kt
Normal file
@@ -0,0 +1,3 @@
|
||||
package blitz.collections
|
||||
|
||||
typealias Iter<T> = ((T) -> Unit) -> Unit
|
@@ -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) }
|
||||
@@ -51,3 +52,16 @@ fun <T> Iterator<T>.collect(to: Vec<T>): Vec<T> {
|
||||
}
|
||||
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
|
||||
}
|
51
src/main/kotlin/blitz/collections/LightCache.kt
Normal file
51
src/main/kotlin/blitz/collections/LightCache.kt
Normal 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)
|
@@ -31,3 +31,39 @@ fun <T> MutableList<T>.addFront(value: T) =
|
||||
|
||||
fun <T: Any> Iterable<T?>.countNotNull() =
|
||||
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())
|
||||
}
|
||||
}
|
150
src/main/kotlin/blitz/collections/LongVec.kt
Normal file
150
src/main/kotlin/blitz/collections/LongVec.kt
Normal 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)
|
||||
}
|
||||
}
|
||||
}
|
@@ -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,
|
||||
|
125
src/main/kotlin/blitz/collections/RefVec.kt
Normal file
125
src/main/kotlin/blitz/collections/RefVec.kt
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
@@ -42,3 +42,38 @@ fun <T> Sequence<T>.limit(len: Int): Sequence<T> =
|
||||
|
||||
fun <A, B> IndexableSequence<A>.limitBy(other: Sequence<B>): IndexableSequence<A> =
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
150
src/main/kotlin/blitz/collections/ShortVec.kt
Normal file
150
src/main/kotlin/blitz/collections/ShortVec.kt
Normal 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)
|
||||
}
|
||||
}
|
||||
}
|
11
src/main/kotlin/blitz/collections/SmartVec.kt
Normal file
11
src/main/kotlin/blitz/collections/SmartVec.kt
Normal 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)
|
||||
}
|
@@ -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 */
|
||||
|
||||
|
@@ -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
|
||||
}
|
||||
}
|
@@ -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)
|
||||
}
|
@@ -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)
|
57
src/main/kotlin/blitz/math/Aabb3f.kt
Normal file
57
src/main/kotlin/blitz/math/Aabb3f.kt
Normal 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)
|
||||
)
|
||||
)
|
||||
}
|
90
src/main/kotlin/blitz/math/Aabb3s.kt
Normal file
90
src/main/kotlin/blitz/math/Aabb3s.kt
Normal 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)
|
||||
}
|
||||
}
|
||||
}
|
51
src/main/kotlin/blitz/math/Anglef.kt
Normal file
51
src/main/kotlin/blitz/math/Anglef.kt
Normal 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)
|
||||
}
|
||||
}
|
41
src/main/kotlin/blitz/math/BitInts.kt
Normal file
41
src/main/kotlin/blitz/math/BitInts.kt
Normal 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
|
||||
}
|
29
src/main/kotlin/blitz/math/Direction.kt
Normal file
29
src/main/kotlin/blitz/math/Direction.kt
Normal 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()
|
||||
}
|
||||
}
|
@@ -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
|
146
src/main/kotlin/blitz/math/PackedVoxelShape3i.kt
Normal file
146
src/main/kotlin/blitz/math/PackedVoxelShape3i.kt
Normal 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)))
|
||||
}
|
||||
}
|
||||
}
|
98
src/main/kotlin/blitz/math/Vec3f.kt
Normal file
98
src/main/kotlin/blitz/math/Vec3f.kt
Normal 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)
|
||||
}
|
136
src/main/kotlin/blitz/math/Vec3s.kt
Normal file
136
src/main/kotlin/blitz/math/Vec3s.kt
Normal 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)
|
||||
}
|
26
src/main/kotlin/blitz/math/Vec4s.kt
Normal file
26
src/main/kotlin/blitz/math/Vec4s.kt
Normal 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
|
||||
}
|
40
src/main/kotlin/blitz/math/VoxelShape4x4i.kt
Normal file
40
src/main/kotlin/blitz/math/VoxelShape4x4i.kt
Normal 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)
|
||||
}
|
||||
}
|
93
src/main/kotlin/blitz/math/VoxelShape4x4x4i.kt
Normal file
93
src/main/kotlin/blitz/math/VoxelShape4x4x4i.kt
Normal 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)))
|
||||
}
|
||||
}
|
||||
}
|
@@ -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 jsonString: Parser<Char, Element> = stringLit()
|
||||
.mapValue(::Str)
|
||||
|
||||
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 jsonArray: Parser<Char, Element> =
|
||||
thenIgnore(
|
||||
thenIgnore(
|
||||
thenOverwrite(
|
||||
thenIgnore(just('['), whitespaces),
|
||||
mapValue(delimitedBy(jsonElement,
|
||||
chain(whitespaces, ignoreSeq(","), whitespaces)), Element::newArr)),
|
||||
whitespaces),
|
||||
just(']')
|
||||
)
|
||||
|
||||
val jsonNull: Parser<Char, Element> = seq("null".toList())
|
||||
.mapValue { Nul() }
|
||||
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 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()
|
||||
}
|
||||
}
|
||||
|
||||
data class Number(val value: Double): Element {
|
||||
override fun toString(): String =
|
||||
value.toString()
|
||||
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 Str(val value: String): Element {
|
||||
override fun toString(): String =
|
||||
"\"$value\""
|
||||
fun Element.asBool(): Boolean {
|
||||
require(kind == Element.BOOL) { "Element is not a Boolean" }
|
||||
return _bool
|
||||
}
|
||||
|
||||
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.asArr(): RefVec<Element> {
|
||||
require(kind == Element.ARR) { "Element is not an Array" }
|
||||
return _boxed as RefVec<Element>
|
||||
}
|
||||
|
||||
|
||||
data class Bool(val value: Boolean): Element {
|
||||
override fun toString(): String =
|
||||
value.toString()
|
||||
fun Element.asStr(): String {
|
||||
require(kind == Element.STR) { "Element is not a String" }
|
||||
return _boxed as String
|
||||
}
|
||||
|
||||
class Nul: Element
|
||||
fun Element.asObj(): Map<String, Element> {
|
||||
require(kind == Element.OBJ) { "Element is not an Object" }
|
||||
return _boxed as Map<String, 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)
|
||||
}
|
||||
}
|
||||
}
|
@@ -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
|
@@ -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
|
||||
}
|
@@ -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()
|
||||
}
|
@@ -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)
|
||||
Either.ofA(Unit)
|
||||
}
|
||||
|
||||
inline fun <I, O: Any> verifyValue(crossinline self: Parser<I, O>, crossinline verif: (O) -> String?): Parser<I, O> =
|
||||
{ ctx ->
|
||||
self(ctx).flatMapA<_,_,_> {
|
||||
verif(it)?.let { Either.ofB(ParseError(ctx.idx, it)) }
|
||||
?: Either.ofA(it)
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
inline fun <I, O: Any> location(crossinline fn: (Int) -> O): Parser<I, O> =
|
||||
{ Either.ofA(fn(it.idx)) }
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
fun <I> seq(want: List<I>): Parser<I, List<I>> =
|
||||
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 <I> filter(msg: String, crossinline filter: (I) -> Boolean): Parser<I, I> =
|
||||
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(listOf(ParseError(ctx.idx, "unexpected end of file")))
|
||||
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(listOf(ParseError(ctx.idx - 1, msg)))
|
||||
else Either.ofB(ParseError(ctx.idx - 1, msg))
|
||||
}
|
||||
}
|
||||
|
||||
fun <I> just(want: I): Parser<I, I> =
|
||||
filter("expected $want") { it == want }
|
||||
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> oneOf(possible: Iterable<I>): Parser<I, I> =
|
||||
filter("expected one of ${possible.contents}") { it in possible }
|
||||
fun <I: Any> just(wantIn: I): Parser<I, I> =
|
||||
JustParse(wantIn)
|
||||
|
||||
fun <I, O> future(prov: Provider<Parser<I, O>>): Parser<I, O> =
|
||||
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 <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))
|
@@ -1,37 +1,39 @@
|
||||
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()
|
||||
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
|
||||
}
|
||||
@@ -39,29 +41,30 @@ fun floatLit(): Parser<Char, Double> =
|
||||
(pre.toDouble().absoluteValue + p) * pre.toDouble().sign
|
||||
}
|
||||
|
||||
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 }
|
||||
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 stringLit(): Parser<Char, String> =
|
||||
just('"')
|
||||
.then(choose(escapeChar(),
|
||||
filter("a") { it != '"' })
|
||||
.repeated())
|
||||
.thenIgnore(just('"'))
|
||||
.mapValue { (_, str) -> str.charsToString() }
|
||||
val stringLit: Parser<Char, String> =
|
||||
mapValue(thenIgnore(then(just('"'),
|
||||
repeated(choose<Char,Char>{
|
||||
it(escapeChar)
|
||||
it(filter("a") { it != '"' })
|
||||
})),
|
||||
just('"')))
|
||||
{ (_, str) -> str.charsToString() }
|
||||
|
||||
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()))
|
||||
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()))
|
@@ -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())
|
||||
|
||||
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() })
|
||||
}
|
@@ -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")
|
||||
|
5
src/main/kotlin/blitz/test/annotations/Test.kt
Normal file
5
src/main/kotlin/blitz/test/annotations/Test.kt
Normal file
@@ -0,0 +1,5 @@
|
||||
package blitz.test.annotations
|
||||
|
||||
@Target(AnnotationTarget.FUNCTION, AnnotationTarget.CLASS)
|
||||
@Retention(AnnotationRetention.SOURCE)
|
||||
annotation class Test
|
7
src/main/kotlin/blitz/test/util/Asserts.kt
Normal file
7
src/main/kotlin/blitz/test/util/Asserts.kt
Normal 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)
|
@@ -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() {
|
||||
|
Reference in New Issue
Block a user