fix #34 minimal time manipulation
This commit is contained in:
parent
23006b5caa
commit
732d8f3877
123
docs/time.md
Normal file
123
docs/time.md
Normal file
@ -0,0 +1,123 @@
|
|||||||
|
# Lyng time functions
|
||||||
|
|
||||||
|
Lyng date and time support requires importing `lyng.time` packages. Lyng uses simple yet modern time object models:
|
||||||
|
|
||||||
|
- `Instant` class for time stamps with platform-dependent resolution
|
||||||
|
- `Duration` to represent amount of time not depending on the calendar, e.g. in absolute units (milliseconds, seconds, hours, days)
|
||||||
|
|
||||||
|
## Time instant: `Instant`
|
||||||
|
|
||||||
|
Represent some moment of time not depending on the calendar (calendar for example may b e changed, daylight saving time can be for example introduced or dropped). It is similar to `TIMESTAMP` in SQL or `Instant` in Kotlin. Some moment of time; not the calendar date.
|
||||||
|
|
||||||
|
Instant is comparable to other Instant. Subtracting instants produce `Duration`, period in time that is not dependent on the calendar, e.g. absolute time period.
|
||||||
|
|
||||||
|
It is possible to add or subtract `Duration` to and from `Instant`, that gives another `Instant`.
|
||||||
|
|
||||||
|
Instants are converted to and from `Real` number of seconds before or after Unix Epoch, 01.01.1970. Constructor with single number parameter constructs from such number of seconds,
|
||||||
|
and any instance provide `.epochSeconds` member:
|
||||||
|
|
||||||
|
import lyng.time
|
||||||
|
|
||||||
|
// default constructor returns time now:
|
||||||
|
val t1 = Instant()
|
||||||
|
val t2 = Instant()
|
||||||
|
assert( t2 - t1 < 1.millisecond )
|
||||||
|
assert( t2.epochSeconds - t1.epochSeconds < 0.001 )
|
||||||
|
>>> void
|
||||||
|
|
||||||
|
## Constructing
|
||||||
|
|
||||||
|
import lyng.time
|
||||||
|
|
||||||
|
// empty constructor gives current time instant using system clock:
|
||||||
|
val now = Instant()
|
||||||
|
|
||||||
|
// constructor with Instant instance makes a copy:
|
||||||
|
assertEquals( now, Instant(now) )
|
||||||
|
|
||||||
|
// constructing from a number is trated as seconds since unix epoch:
|
||||||
|
val copyOfNow = Instant( now.epochSeconds )
|
||||||
|
|
||||||
|
// note that instant resolution is higher that Real can hold
|
||||||
|
// so reconstructed from real slightly differs:
|
||||||
|
assert( abs( (copyOfNow - now).milliseconds ) < 0.01 )
|
||||||
|
>>> void
|
||||||
|
|
||||||
|
The resolution of system clock could be more precise and double precision real number of `Real`, keep it in mind.
|
||||||
|
|
||||||
|
## Comparing and calculating periods
|
||||||
|
|
||||||
|
import lyng.time
|
||||||
|
|
||||||
|
val now = Instant()
|
||||||
|
|
||||||
|
// you cam add or subtract periods, and compare
|
||||||
|
assert( now - 5.minutes < now )
|
||||||
|
val oneHourAgo = now - 1.hour
|
||||||
|
assertEquals( now, oneHourAgo + 1.hour)
|
||||||
|
|
||||||
|
>>> void
|
||||||
|
|
||||||
|
## Instant members
|
||||||
|
|
||||||
|
| member | description |
|
||||||
|
|-----------------------|---------------------------------------------------------|
|
||||||
|
| epochSeconds: Real | positive or negative offset in seconds since Unix epoch |
|
||||||
|
| isDistantFuture: Bool | true if it `Instant.distantFuture` |
|
||||||
|
| isDistantPast: Bool | true if it `Instant.distantPast` |
|
||||||
|
|
||||||
|
## Class members
|
||||||
|
|
||||||
|
| member | description |
|
||||||
|
|--------------------------------|---------------------------------------------------------|
|
||||||
|
| Instant.distantPast: Instant | most distant instant in past |
|
||||||
|
| Instant.distantFuture: Instant | most distant instant in future |
|
||||||
|
|
||||||
|
# `Duraion` class
|
||||||
|
|
||||||
|
Represent absolute time distance between two `Instant`.
|
||||||
|
|
||||||
|
import lyng.time
|
||||||
|
val t1 = Instant()
|
||||||
|
|
||||||
|
// yes we can delay to period, and it is not blocking. is suspends!
|
||||||
|
delay(1.millisecond)
|
||||||
|
|
||||||
|
val t2 = Instant()
|
||||||
|
// be suspend, so actual time may vary:
|
||||||
|
assert( t2 - t1 >= 1.millisecond)
|
||||||
|
assert( t2 - t1 < 100.millisecond)
|
||||||
|
>>> void
|
||||||
|
|
||||||
|
Duration can be converted from numbers, like `5.minutes` and so on. Extensions are created for
|
||||||
|
`Int` and `Real`, so for n as Real or Int it is possible to create durations::
|
||||||
|
|
||||||
|
- `n.millisecond`, `n.milliseconds`
|
||||||
|
- `n.second`, `n.seconds`
|
||||||
|
- `n.minute`, `n.minutes`
|
||||||
|
- `n.hour`, `n.hours`
|
||||||
|
- `n.day`, `n.days`
|
||||||
|
|
||||||
|
The bigger time units like months or years are calendar-dependent and can't be used with `Duration`.
|
||||||
|
|
||||||
|
Each duration instance can be converted to number of any of these time units, as `Real` number, if `d` is a `Duration` instance:
|
||||||
|
|
||||||
|
- `d.milliseconds`
|
||||||
|
- `d.seconds`
|
||||||
|
- `d.minutes`
|
||||||
|
- `d.hours`
|
||||||
|
- `d.days`
|
||||||
|
|
||||||
|
for example
|
||||||
|
|
||||||
|
import lyng.time
|
||||||
|
assertEquals( 60, 1.minute.seconds )
|
||||||
|
>>> void
|
||||||
|
|
||||||
|
# Utility functions
|
||||||
|
|
||||||
|
## delay(duration: Duration)
|
||||||
|
|
||||||
|
Suspends current coroutine for at least the specified duration.
|
||||||
|
|
||||||
|
|
@ -14,7 +14,7 @@ __Other documents to read__ maybe after this one:
|
|||||||
- [Advanced topics](advanced_topics.md), [declaring arguments](declaring_arguments.md)
|
- [Advanced topics](advanced_topics.md), [declaring arguments](declaring_arguments.md)
|
||||||
- [OOP notes](OOP.md), [exception handling](exceptions_handling.md)
|
- [OOP notes](OOP.md), [exception handling](exceptions_handling.md)
|
||||||
- [math in Lyng](math.md)
|
- [math in Lyng](math.md)
|
||||||
- Some class references: [List], [Set], [Map], [Real], [Range], [Iterable], [Iterator]
|
- Some class references: [List], [Set], [Map], [Real], [Range], [Iterable], [Iterator], [time manipulation](time.md)
|
||||||
- Some samples: [combinatorics](samples/combinatorics.lyng.md), national vars and loops: [сумма ряда](samples/сумма_ряда.lyng.md). More at [samples folder](samples)
|
- Some samples: [combinatorics](samples/combinatorics.lyng.md), national vars and loops: [сумма ряда](samples/сумма_ряда.lyng.md). More at [samples folder](samples)
|
||||||
|
|
||||||
# Expressions
|
# Expressions
|
||||||
@ -668,7 +668,6 @@ are [Iterable]:
|
|||||||
|
|
||||||
Please see [Map] reference for detailed description on using Maps.
|
Please see [Map] reference for detailed description on using Maps.
|
||||||
|
|
||||||
|
|
||||||
# Flow control operators
|
# Flow control operators
|
||||||
|
|
||||||
## if-then-else
|
## if-then-else
|
||||||
@ -1101,6 +1100,17 @@ and you can use ranges in for-loops:
|
|||||||
|
|
||||||
See [Ranges](Range.md) for detailed documentation on it.
|
See [Ranges](Range.md) for detailed documentation on it.
|
||||||
|
|
||||||
|
# Time routines
|
||||||
|
|
||||||
|
These should be imported from [lyng.time](time.md). For example:
|
||||||
|
|
||||||
|
import lyng.time
|
||||||
|
|
||||||
|
val now = Instant()
|
||||||
|
val hourAgo = now - 1.hour
|
||||||
|
|
||||||
|
See [more docs on time manipulation](time.md)
|
||||||
|
|
||||||
# Comments
|
# Comments
|
||||||
|
|
||||||
// single line comment
|
// single line comment
|
||||||
|
@ -4,7 +4,7 @@ import org.jetbrains.kotlin.gradle.ExperimentalWasmDsl
|
|||||||
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
|
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
|
||||||
|
|
||||||
group = "net.sergeych"
|
group = "net.sergeych"
|
||||||
version = "0.7.3-SNAPSHOT"
|
version = "0.7.4-SNAPSHOT"
|
||||||
|
|
||||||
buildscript {
|
buildscript {
|
||||||
repositories {
|
repositories {
|
||||||
|
@ -42,6 +42,9 @@ data class Arguments(val list: List<Obj>,val tailBlockMode: Boolean = false) : L
|
|||||||
return list.map { it.toKotlin(scope) }
|
return list.map { it.toKotlin(scope) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun inspect(): String = list.joinToString(", ") { it.inspect() }
|
||||||
|
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
val EMPTY = Arguments(emptyList())
|
val EMPTY = Arguments(emptyList())
|
||||||
fun from(values: Collection<Obj>) = Arguments(values.toList())
|
fun from(values: Collection<Obj>) = Arguments(values.toList())
|
||||||
|
@ -751,7 +751,7 @@ class Compiler(
|
|||||||
val t = cc.next()
|
val t = cc.next()
|
||||||
return when (t.type) {
|
return when (t.type) {
|
||||||
Token.Type.INT, Token.Type.HEX -> {
|
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)
|
if (isPlus) ObjInt(n) else ObjInt(-n)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -320,6 +320,8 @@ open class Obj {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun Double.toObj(): Obj = ObjReal(this)
|
||||||
|
|
||||||
@Suppress("unused")
|
@Suppress("unused")
|
||||||
inline fun <reified T> T.toObj(): Obj = Obj.from(this)
|
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 start: Int = index.startInt(scope)
|
||||||
val end: Int = index.exclusiveIntEnd(scope) ?: size
|
val end: Int = index.exclusiveIntEnd(scope) ?: size
|
||||||
ObjBuffer(byteArray.sliceArray(start..<end))
|
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) {
|
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
|
val size by byteArray::size
|
||||||
|
|
||||||
override suspend fun compareTo(scope: Scope, other: Obj): Int {
|
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)
|
val limit = min(size, other.size)
|
||||||
for (i in 0..<limit) {
|
for (i in 0..<limit) {
|
||||||
val own = byteArray[i]
|
val own = byteArray[i]
|
||||||
@ -56,11 +55,12 @@ class ObjBuffer(val byteArray: UByteArray) : Obj() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun plus(scope: Scope, other: Obj): Obj {
|
override suspend fun plus(scope: Scope, other: Obj): Obj {
|
||||||
return if( other is ObjBuffer)
|
return if (other is ObjBuffer)
|
||||||
ObjBuffer(byteArray + other.byteArray)
|
ObjBuffer(byteArray + other.byteArray)
|
||||||
else if( other.isInstanceOf(ObjIterable)) {
|
else if (other.isInstanceOf(ObjIterable)) {
|
||||||
ObjBuffer(
|
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()}")
|
} else scope.raiseIllegalArgument("can't concatenate buffer with ${other.inspect()}")
|
||||||
}
|
}
|
||||||
@ -84,7 +84,8 @@ class ObjBuffer(val byteArray: UByteArray) : Obj() {
|
|||||||
else -> {
|
else -> {
|
||||||
if (obj.isInstanceOf(ObjIterable)) {
|
if (obj.isInstanceOf(ObjIterable)) {
|
||||||
ObjBuffer(
|
ObjBuffer(
|
||||||
obj.toFlow(scope).map { it.toLong().toUByte() }.toList().toTypedArray().toUByteArray()
|
obj.toFlow(scope).map { it.toLong().toUByte() }.toList().toTypedArray()
|
||||||
|
.toUByteArray()
|
||||||
)
|
)
|
||||||
} else
|
} else
|
||||||
scope.raiseIllegalArgument(
|
scope.raiseIllegalArgument(
|
||||||
|
@ -4,7 +4,7 @@ val ObjClassType by lazy { ObjClass("Class") }
|
|||||||
|
|
||||||
open class ObjClass(
|
open class ObjClass(
|
||||||
val className: String,
|
val className: String,
|
||||||
vararg val parents: ObjClass,
|
vararg parents: ObjClass,
|
||||||
) : Obj() {
|
) : Obj() {
|
||||||
|
|
||||||
var instanceConstructor: Statement? = null
|
var instanceConstructor: Statement? = null
|
||||||
@ -18,6 +18,7 @@ open class ObjClass(
|
|||||||
|
|
||||||
// members: fields most often
|
// members: fields most often
|
||||||
private val members = mutableMapOf<String, ObjRecord>()
|
private val members = mutableMapOf<String, ObjRecord>()
|
||||||
|
private val classMembers = mutableMapOf<String, ObjRecord>()
|
||||||
|
|
||||||
override fun toString(): String = className
|
override fun toString(): String = className
|
||||||
|
|
||||||
@ -32,9 +33,9 @@ open class ObjClass(
|
|||||||
return instance
|
return instance
|
||||||
}
|
}
|
||||||
|
|
||||||
fun defaultInstance(): Obj = object : Obj() {
|
// fun defaultInstance(): Obj = object : Obj() {
|
||||||
override val objClass: ObjClass = this@ObjClass
|
// override val objClass: ObjClass = this@ObjClass
|
||||||
}
|
// }
|
||||||
|
|
||||||
fun createField(
|
fun createField(
|
||||||
name: String,
|
name: String,
|
||||||
@ -49,11 +50,25 @@ open class ObjClass(
|
|||||||
members[name] = ObjRecord(initialValue, isMutable, visibility)
|
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) {
|
fun addFn(name: String, isOpen: Boolean = false, code: suspend Scope.() -> Obj) {
|
||||||
createField(name, statement { code() }, isOpen)
|
createField(name, statement { code() }, isOpen)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun addConst(name: String, value: Obj) = createField(name, value, isMutable = false)
|
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 =
|
fun getInstanceMember(atPos: Pos, name: String): ObjRecord =
|
||||||
getInstanceMemberOrNull(name)
|
getInstanceMemberOrNull(name)
|
||||||
?: throw ScriptError(atPos, "symbol doesn't exist: $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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
137
lynglib/src/commonMain/kotlin/net/sergeych/lyng/ObjDuration.kt
Normal file
137
lynglib/src/commonMain/kotlin/net/sergeych/lyng/ObjDuration.kt
Normal file
@ -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 -> {
|
in digitsSet -> {
|
||||||
pos.back()
|
pos.back()
|
||||||
decodeNumber(loadChars(digits), from)
|
decodeNumber(loadChars { it in digitsSet || it == '_'}, from)
|
||||||
}
|
}
|
||||||
|
|
||||||
'\'' -> {
|
'\'' -> {
|
||||||
|
@ -179,6 +179,20 @@ class Script(
|
|||||||
addPackage("lyng.buffer") {
|
addPackage("lyng.buffer") {
|
||||||
it.addConst("Buffer", ObjBuffer.type)
|
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.map
|
||||||
import kotlinx.coroutines.flow.toList
|
import kotlinx.coroutines.flow.toList
|
||||||
import kotlinx.coroutines.test.runTest
|
import kotlinx.coroutines.test.runTest
|
||||||
@ -2513,4 +2514,50 @@ class ScriptTest {
|
|||||||
""".trimIndent())
|
""".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()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
@ -2,6 +2,7 @@ import kotlinx.coroutines.Dispatchers
|
|||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.coroutines.flow.flow
|
import kotlinx.coroutines.flow.flow
|
||||||
import kotlinx.coroutines.flow.flowOn
|
import kotlinx.coroutines.flow.flowOn
|
||||||
|
import kotlinx.coroutines.runBlocking
|
||||||
import kotlinx.coroutines.test.runTest
|
import kotlinx.coroutines.test.runTest
|
||||||
import net.sergeych.lyng.ObjVoid
|
import net.sergeych.lyng.ObjVoid
|
||||||
import net.sergeych.lyng.Scope
|
import net.sergeych.lyng.Scope
|
||||||
@ -105,6 +106,10 @@ fun parseDocTests(fileName: String, bookMode: Boolean = false): Flow<DocTest> =
|
|||||||
} else {
|
} else {
|
||||||
var isValid = true
|
var isValid = true
|
||||||
val result = mutableListOf<String>()
|
val result = mutableListOf<String>()
|
||||||
|
|
||||||
|
// remove empty trails:
|
||||||
|
while( block.last().isEmpty() ) block.removeLast()
|
||||||
|
|
||||||
while (block.size > outStart) {
|
while (block.size > outStart) {
|
||||||
val line = block.removeAt(outStart)
|
val line = block.removeAt(outStart)
|
||||||
if (!line.startsWith(">>> ")) {
|
if (!line.startsWith(">>> ")) {
|
||||||
@ -288,4 +293,10 @@ class BookTest {
|
|||||||
fun testExceptionsBooks() = runTest {
|
fun testExceptionsBooks() = runTest {
|
||||||
runDocTests("../docs/exceptions_handling.md")
|
runDocTests("../docs/exceptions_handling.md")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testTimeBooks() = runBlocking {
|
||||||
|
runDocTests("../docs/time.md")
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
Loading…
x
Reference in New Issue
Block a user