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)
 | 
			
		||||
- [OOP notes](OOP.md), [exception handling](exceptions_handling.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)
 | 
			
		||||
 | 
			
		||||
# Expressions
 | 
			
		||||
@ -668,7 +668,6 @@ are [Iterable]:
 | 
			
		||||
 | 
			
		||||
Please see [Map] reference for detailed description on using Maps.
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# Flow control operators
 | 
			
		||||
 | 
			
		||||
## if-then-else
 | 
			
		||||
@ -1101,6 +1100,17 @@ and you can use ranges in for-loops:
 | 
			
		||||
 | 
			
		||||
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
 | 
			
		||||
 | 
			
		||||
    // single line comment
 | 
			
		||||
 | 
			
		||||
@ -4,7 +4,7 @@ import org.jetbrains.kotlin.gradle.ExperimentalWasmDsl
 | 
			
		||||
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
 | 
			
		||||
 | 
			
		||||
group = "net.sergeych"
 | 
			
		||||
version = "0.7.3-SNAPSHOT"
 | 
			
		||||
version = "0.7.4-SNAPSHOT"
 | 
			
		||||
 | 
			
		||||
buildscript {
 | 
			
		||||
    repositories {
 | 
			
		||||
 | 
			
		||||
@ -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)
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										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 -> {
 | 
			
		||||
                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()
 | 
			
		||||
        )
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@ -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")
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user