fix #34 minimal time manipulation

This commit is contained in:
sergeych committed 2025-07-10 00:00:02 +03:00
1 parent 23006b5caa
commit 732d8f3877
14 files changed
+478 -16

No files matched your search

@@ -42,6 +42,9 @@ data class Arguments(val list: List<Obj>,val tailBlockMode: Boolean = false) : L
return list.map { it.toKotlin(scope) }
}
fun inspect(): String = list.joinToString(", ") { it.inspect() }
companion object {
val EMPTY = Arguments(emptyList())
fun from(values: Collection<Obj>) = Arguments(values.toList())
@@ -751,7 +751,7 @@ class Compiler(
val t = cc.next()
return when (t.type) {
Token.Type.INT, Token.Type.HEX -> {
val n = t.value.toLong(if (t.type == Token.Type.HEX) 16 else 10)
val n = t.value.replace("_", "").toLong(if (t.type == Token.Type.HEX) 16 else 10)
if (isPlus) ObjInt(n) else ObjInt(-n)
}
@@ -320,6 +320,8 @@ open class Obj {
}
}
fun Double.toObj(): Obj = ObjReal(this)
@Suppress("unused")
inline fun <reified T> T.toObj(): Obj = Obj.from(this)
@@ -25,8 +25,7 @@ class ObjBuffer(val byteArray: UByteArray) : Obj() {
val start: Int = index.startInt(scope)
val end: Int = index.exclusiveIntEnd(scope) ?: size
ObjBuffer(byteArray.sliceArray(start..<end))
}
else ObjInt(byteArray[checkIndex(scope, index)].toLong(), true)
} else ObjInt(byteArray[checkIndex(scope, index)].toLong(), true)
}
override suspend fun putAt(scope: Scope, index: Obj, newValue: Obj) {
@@ -42,7 +41,7 @@ class ObjBuffer(val byteArray: UByteArray) : Obj() {
val size by byteArray::size
override suspend fun compareTo(scope: Scope, other: Obj): Int {
if (other !is ObjBuffer) return -1
if (other !is ObjBuffer) return super.compareTo(scope, other)
val limit = min(size, other.size)
for (i in 0..<limit) {
val own = byteArray[i]
@@ -56,11 +55,12 @@ class ObjBuffer(val byteArray: UByteArray) : Obj() {
}
override suspend fun plus(scope: Scope, other: Obj): Obj {
return if( other is ObjBuffer)
return if (other is ObjBuffer)
ObjBuffer(byteArray + other.byteArray)
else if( other.isInstanceOf(ObjIterable)) {
else if (other.isInstanceOf(ObjIterable)) {
ObjBuffer(
byteArray + other.toFlow(scope).map { it.toLong().toUByte() }.toList().toTypedArray().toUByteArray()
byteArray + other.toFlow(scope).map { it.toLong().toUByte() }.toList().toTypedArray()
.toUByteArray()
)
} else scope.raiseIllegalArgument("can't concatenate buffer with ${other.inspect()}")
}
@@ -84,7 +84,8 @@ class ObjBuffer(val byteArray: UByteArray) : Obj() {
else -> {
if (obj.isInstanceOf(ObjIterable)) {
ObjBuffer(
obj.toFlow(scope).map { it.toLong().toUByte() }.toList().toTypedArray().toUByteArray()
obj.toFlow(scope).map { it.toLong().toUByte() }.toList().toTypedArray()
.toUByteArray()
)
} else
scope.raiseIllegalArgument(
@@ -4,7 +4,7 @@ val ObjClassType by lazy { ObjClass("Class") }
open class ObjClass(
val className: String,
vararg val parents: ObjClass,
vararg parents: ObjClass,
) : Obj() {
var instanceConstructor: Statement? = null
@@ -18,6 +18,7 @@ open class ObjClass(
// members: fields most often
private val members = mutableMapOf<String, ObjRecord>()
private val classMembers = mutableMapOf<String, ObjRecord>()
override fun toString(): String = className
@@ -32,9 +33,9 @@ open class ObjClass(
return instance
}
fun defaultInstance(): Obj = object : Obj() {
override val objClass: ObjClass = this@ObjClass
}
// fun defaultInstance(): Obj = object : Obj() {
// override val objClass: ObjClass = this@ObjClass
// }
fun createField(
name: String,
@@ -49,11 +50,25 @@ open class ObjClass(
members[name] = ObjRecord(initialValue, isMutable, visibility)
}
fun createClassField(
name: String,
initialValue: Obj,
isMutable: Boolean = false,
visibility: Visibility = Visibility.Public,
pos: Pos = Pos.builtIn
) {
val existing = classMembers[name]
if( existing != null)
throw ScriptError(pos, "$name is already defined in $objClass or one of its supertypes")
classMembers[name] = ObjRecord(initialValue, isMutable, visibility)
}
fun addFn(name: String, isOpen: Boolean = false, code: suspend Scope.() -> Obj) {
createField(name, statement { code() }, isOpen)
}
fun addConst(name: String, value: Obj) = createField(name, value, isMutable = false)
fun addClassConst(name: String, value: Obj) = createClassField(name, value)
/**
@@ -68,6 +83,14 @@ open class ObjClass(
fun getInstanceMember(atPos: Pos, name: String): ObjRecord =
getInstanceMemberOrNull(name)
?: throw ScriptError(atPos, "symbol doesn't exist: $name")
override suspend fun readField(scope: Scope, name: String): ObjRecord {
classMembers[name]?.let {
println("class field $it")
return it
}
return super.readField(scope, name)
}
}
@@ -0,0 +1,137 @@
package net.sergeych.lyng
import kotlin.time.Duration
import kotlin.time.Duration.Companion.days
import kotlin.time.Duration.Companion.hours
import kotlin.time.Duration.Companion.milliseconds
import kotlin.time.Duration.Companion.minutes
import kotlin.time.Duration.Companion.seconds
import kotlin.time.DurationUnit
class ObjDuration(val duration: Duration) : Obj() {
override val objClass: ObjClass = type
override fun toString(): String {
return duration.toString()
}
override suspend fun compareTo(scope: Scope, other: Obj): Int {
return if( other is ObjDuration)
duration.compareTo(other.duration)
else -1
}
companion object {
val type = object : ObjClass("Duration") {
override suspend fun callOn(scope: Scope): Obj {
val args = scope.args
if( args.list.size > 1 )
scope.raiseIllegalArgument("can't construct Duration(${args.inspect()})")
val a0 = args.list.getOrNull(0)
return ObjDuration(
when (a0) {
null -> Duration.ZERO
is ObjInt -> a0.value.seconds
is ObjReal -> a0.value.seconds
else -> {
scope.raiseIllegalArgument("can't construct Instant(${args.inspect()})")
}
}
)
}
}.apply {
addFn("days") {
thisAs<ObjDuration>().duration.toDouble(DurationUnit.DAYS).toObj()
}
addFn("hours") {
thisAs<ObjDuration>().duration.toDouble(DurationUnit.HOURS).toObj()
}
addFn("minutes") {
thisAs<ObjDuration>().duration.toDouble(DurationUnit.MINUTES).toObj()
}
addFn("seconds") {
thisAs<ObjDuration>().duration.toDouble(DurationUnit.SECONDS).toObj()
}
addFn("milliseconds") {
thisAs<ObjDuration>().duration.toDouble(DurationUnit.MILLISECONDS).toObj()
}
ObjInt.type.addFn("seconds") {
ObjDuration(thisAs<ObjInt>().value.seconds)
}
ObjInt.type.addFn("second") {
ObjDuration(thisAs<ObjInt>().value.seconds)
}
ObjInt.type.addFn("milliseconds") {
ObjDuration(thisAs<ObjInt>().value.milliseconds)
}
ObjInt.type.addFn("millisecond") {
ObjDuration(thisAs<ObjInt>().value.milliseconds)
}
ObjReal.type.addFn("seconds") {
ObjDuration(thisAs<ObjReal>().value.seconds)
}
ObjReal.type.addFn("second") {
ObjDuration(thisAs<ObjReal>().value.seconds)
}
ObjReal.type.addFn("milliseconds") {
ObjDuration(thisAs<ObjReal>().value.milliseconds)
}
ObjReal.type.addFn("millisecond") {
ObjDuration(thisAs<ObjReal>().value.milliseconds)
}
ObjInt.type.addFn("minutes") {
ObjDuration(thisAs<ObjInt>().value.minutes)
}
ObjReal.type.addFn("minutes") {
ObjDuration(thisAs<ObjReal>().value.minutes)
}
ObjInt.type.addFn("minute") {
ObjDuration(thisAs<ObjInt>().value.minutes)
}
ObjReal.type.addFn("minute") {
ObjDuration(thisAs<ObjReal>().value.minutes)
}
ObjInt.type.addFn("hours") {
ObjDuration(thisAs<ObjInt>().value.hours)
}
ObjReal.type.addFn("hours") {
ObjDuration(thisAs<ObjReal>().value.hours)
}
ObjInt.type.addFn("hour") {
ObjDuration(thisAs<ObjInt>().value.hours)
}
ObjReal.type.addFn("hour") {
ObjDuration(thisAs<ObjReal>().value.hours)
}
ObjInt.type.addFn("days") {
ObjDuration(thisAs<ObjInt>().value.days)
}
ObjReal.type.addFn("days") {
ObjDuration(thisAs<ObjReal>().value.days)
}
ObjInt.type.addFn("day") {
ObjDuration(thisAs<ObjInt>().value.days)
}
ObjReal.type.addFn("day") {
ObjDuration(thisAs<ObjReal>().value.days)
}
// addFn("epochSeconds") {
// val instant = thisAs<ObjInstant>().instant
// ObjReal(instant.epochSeconds + instant.nanosecondsOfSecond * 1e-9)
// }
// addFn("epochMilliseconds") {
// ObjInt(instant.toEpochMilliseconds())
// }
}
}
}
@@ -0,0 +1,91 @@
package net.sergeych.lyng
import kotlinx.datetime.Clock
import kotlinx.datetime.Instant
import kotlinx.datetime.isDistantFuture
import kotlinx.datetime.isDistantPast
class ObjInstant(val instant: Instant) : Obj() {
override val objClass: ObjClass get() = type
override fun toString(): String {
return instant.toString()
}
override suspend fun plus(scope: Scope, other: Obj): Obj {
return when (other) {
is ObjDuration -> ObjInstant(instant + other.duration)
else -> super.plus(scope, other)
}
}
override suspend fun minus(scope: Scope, other: Obj): Obj {
return when (other) {
is ObjDuration -> ObjInstant(instant - other.duration)
is ObjInstant -> ObjDuration(instant - other.instant)
else -> super.plus(scope, other)
}
}
override suspend fun compareTo(scope: Scope, other: Obj): Int {
if( other is ObjInstant) {
return instant.compareTo(other.instant)
}
return super.compareTo(scope, other)
}
companion object {
val distantFuture by lazy {
ObjInstant(Instant.DISTANT_FUTURE)
}
val distantPast by lazy {
ObjInstant(Instant.DISTANT_PAST)
}
val type = object : ObjClass("Instant") {
override suspend fun callOn(scope: Scope): Obj {
val args = scope.args
val a0 = args.list.getOrNull(0)
return ObjInstant(
when (a0) {
null -> {
val t = Clock.System.now()
Instant.fromEpochSeconds(t.epochSeconds, (t.nanosecondsOfSecond / 1_000_000).toLong()*1_000_000)
}
is ObjInt -> Instant.fromEpochSeconds(a0.value)
is ObjReal -> {
val seconds = a0.value.toLong()
val nanos = (a0.value - seconds) * 1e9
Instant.fromEpochSeconds(seconds, nanos.toLong())
}
is ObjInstant -> a0.instant
else -> {
scope.raiseIllegalArgument("can't construct Instant(${args.inspect()})")
}
}
)
}
}.apply {
addFn("epochSeconds") {
val instant = thisAs<ObjInstant>().instant
ObjReal(instant.epochSeconds + instant.nanosecondsOfSecond * 1e-9)
}
addFn("isDistantFuture") {
thisAs<ObjInstant>().instant.isDistantFuture.toObj()
}
addFn("isDistantPast") {
thisAs<ObjInstant>().instant.isDistantPast.toObj()
}
addClassConst("distantFuture", distantFuture)
addClassConst("distantPast", distantPast)
// addFn("epochMilliseconds") {
// ObjInt(instant.toEpochMilliseconds())
// }
}
}
}
@@ -250,7 +250,7 @@ private class Parser(fromPos: Pos) {
in digitsSet -> {
pos.back()
decodeNumber(loadChars(digits), from)
decodeNumber(loadChars { it in digitsSet || it == '_'}, from)
}
'\'' -> {
@@ -179,6 +179,20 @@ class Script(
addPackage("lyng.buffer") {
it.addConst("Buffer", ObjBuffer.type)
}
addPackage("lyng.time") {
it.addConst("Instant", ObjInstant.type)
it.addConst("Duration", ObjDuration.type)
it.addFn("delay") {
val a = args.firstAndOnly()
when(a) {
is ObjInt -> delay(a.value * 1000)
is ObjReal -> delay((a.value * 1000).roundToLong())
is ObjDuration -> delay(a.duration)
else -> raiseIllegalArgument("Expected Duration, Int or Real, got ${a.inspect()}")
}
ObjVoid
}
}
}
}
@@ -1,3 +1,4 @@
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.toList
import kotlinx.coroutines.test.runTest
@@ -2513,4 +2514,50 @@ class ScriptTest {
""".trimIndent())
}
@Test
fun testInstant() = runTest {
eval(
"""
import lyng.time
val now = Instant()
// assertEquals( now.epochSeconds, Instant(now.epochSeconds).epochSeconds )
assert( 10.seconds is Duration )
assertEquals( 10.seconds, Duration(10) )
assertEquals( 10.milliseconds, Duration(0.01) )
assertEquals( 10.milliseconds, 0.01.seconds )
assertEquals( 1001.5.milliseconds, 1.0015.seconds )
val n1 = now + 7.seconds
assert( n1 is Instant )
assertEquals( n1 - now, 7.seconds )
assertEquals( now - n1, -7.seconds )
""".trimIndent()
)
delay(1000)
}
@Test
fun testTimeStatics() = runTest {
eval(
"""
import lyng.time
assert( 100.minutes is Duration )
assert( 100.days is Duration )
assert( 1.day == 24.hours )
assert( 1.day.hours == 24 )
assert( 1.hour.seconds == 3600 )
assert( 1.minute.milliseconds == 60_000 )
assert(Instant.distantFuture is Instant)
assert(Instant.distantPast is Instant)
assert( Instant.distantFuture - Instant.distantPast > 70_000_000.days)
val maxRange = Instant.distantFuture - Instant.distantPast
println("всего лет %g"(maxRange.days/365.2425))
""".trimIndent()
)
}
}
+11
View File
@@ -2,6 +2,7 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.test.runTest
import net.sergeych.lyng.ObjVoid
import net.sergeych.lyng.Scope
@@ -105,6 +106,10 @@ fun parseDocTests(fileName: String, bookMode: Boolean = false): Flow<DocTest> =
} else {
var isValid = true
val result = mutableListOf<String>()
// remove empty trails:
while( block.last().isEmpty() ) block.removeLast()
while (block.size > outStart) {
val line = block.removeAt(outStart)
if (!line.startsWith(">>> ")) {
@@ -288,4 +293,10 @@ class BookTest {
fun testExceptionsBooks() = runTest {
runDocTests("../docs/exceptions_handling.md")
}
@Test
fun testTimeBooks() = runBlocking {
runDocTests("../docs/time.md")
}
}