Ranges!
This commit is contained in:
parent
3908b8ee9f
commit
b82a97ed20
61
docs/Range.md
Normal file
61
docs/Range.md
Normal file
@ -0,0 +1,61 @@
|
||||
# Range
|
||||
|
||||
Range is diapason between two values. Open range has at least one end open, e.g. ±∞, closed range has both ends open.
|
||||
|
||||
## Closed ranges
|
||||
|
||||
The syntax is intuitive and adopted from Kotlin:
|
||||
|
||||
// end inclusive:
|
||||
val r = 1..5
|
||||
assert(5 in r)
|
||||
assert(6 !in r)
|
||||
assert(0 !in r)
|
||||
assert(2 in r)
|
||||
>>> void
|
||||
|
||||
Exclusive end ranges are adopted from kotlin either:
|
||||
|
||||
// end inclusive:
|
||||
val r = 1..<5
|
||||
assert(5 !in r)
|
||||
assert(6 !in r)
|
||||
assert(0 !in r)
|
||||
assert(2 in r)
|
||||
assert(4 in r)
|
||||
>>> void
|
||||
|
||||
In any case, we can test an object to belong to using `in` and `!in` and
|
||||
access limits:
|
||||
|
||||
val r = 0..5
|
||||
(r.end - r.start)/2
|
||||
>>> 2
|
||||
|
||||
Notice, start and end are ints, so midpoint here is int too.
|
||||
|
||||
It is possible to test that one range is included in another range too,
|
||||
one range is defined as _contained_ in another ifm and only if, it begin and end
|
||||
are equal or within another, taking into account the end-inclusiveness:
|
||||
|
||||
assert( (1..3) in (1..3) )
|
||||
assert( (0..3) !in (1..3) )
|
||||
assert( (1..2) in (1..<3) )
|
||||
assert( (1..<2) in (1..<3) )
|
||||
assert( (1..<3) in (1..3) )
|
||||
>>> void
|
||||
|
||||
|
||||
# Instance members
|
||||
|
||||
| member | description | args |
|
||||
|-----------------|----------------------------|---------------|
|
||||
| contains(other) | used in `in` | Range, or Any |
|
||||
| inclusiveEnd | true for '..' | Bool |
|
||||
| isOpen | at any end | Bool |
|
||||
| isIntRange | both start and end are Int | Bool |
|
||||
| start | | Bool |
|
||||
| end | | Bool |
|
||||
| | | |
|
||||
| | | |
|
||||
| | | |
|
@ -23,9 +23,9 @@ class Compiler(
|
||||
return Script(start, statements)
|
||||
}
|
||||
|
||||
private fun parseStatement(tokens: CompilerContext): Statement? {
|
||||
private fun parseStatement(cc: CompilerContext): Statement? {
|
||||
while (true) {
|
||||
val t = tokens.next()
|
||||
val t = cc.next()
|
||||
return when (t.type) {
|
||||
Token.Type.ID -> {
|
||||
// could be keyword, assignment or just the expression
|
||||
@ -44,16 +44,16 @@ class Compiler(
|
||||
// get back the token which is not '=':
|
||||
// tokens.previous()
|
||||
// try keyword statement
|
||||
parseKeywordStatement(t, tokens)
|
||||
parseKeywordStatement(t, cc)
|
||||
?: run {
|
||||
tokens.previous()
|
||||
parseExpression(tokens)
|
||||
cc.previous()
|
||||
parseExpression(cc)
|
||||
}
|
||||
}
|
||||
|
||||
Token.Type.PLUS2, Token.Type.MINUS2 -> {
|
||||
tokens.previous()
|
||||
parseExpression(tokens)
|
||||
cc.previous()
|
||||
parseExpression(cc)
|
||||
}
|
||||
|
||||
Token.Type.LABEL -> continue
|
||||
@ -64,12 +64,12 @@ class Compiler(
|
||||
Token.Type.SEMICOLON -> continue
|
||||
|
||||
Token.Type.LBRACE -> {
|
||||
tokens.previous()
|
||||
parseBlock(tokens)
|
||||
cc.previous()
|
||||
parseBlock(cc)
|
||||
}
|
||||
|
||||
Token.Type.RBRACE -> {
|
||||
tokens.previous()
|
||||
cc.previous()
|
||||
return null
|
||||
}
|
||||
|
||||
@ -77,8 +77,8 @@ class Compiler(
|
||||
|
||||
else -> {
|
||||
// could be expression
|
||||
tokens.previous()
|
||||
parseExpression(tokens)
|
||||
cc.previous()
|
||||
parseExpression(cc)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -299,7 +299,20 @@ class Compiler(
|
||||
}.value.decrementAndGet(ctx).asReadonly
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Token.Type.DOTDOT, Token.Type.DOTDOTLT -> {
|
||||
// closed-range operator
|
||||
val inclusiveEnd = t.type == Token.Type.DOTDOT
|
||||
val left = operand
|
||||
val right = parseStatement(cc)
|
||||
operand = Accessor {
|
||||
ObjRange(
|
||||
left?.getter?.invoke(it)?.value ?: ObjNull,
|
||||
right?.execute(it) ?: ObjNull,
|
||||
inclusiveEnd = inclusiveEnd
|
||||
).asReadonly
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -560,7 +573,7 @@ class Compiler(
|
||||
current = sourceObj.getAt(forContext, index)
|
||||
}
|
||||
}
|
||||
if( !breakCaught && elseStatement != null) {
|
||||
if (!breakCaught && elseStatement != null) {
|
||||
result = elseStatement.execute(it)
|
||||
}
|
||||
result
|
||||
@ -859,7 +872,7 @@ class Compiler(
|
||||
|
||||
private var lastPrty = 0
|
||||
val allOps = listOf(
|
||||
// assignments
|
||||
// assignments, lowest priority
|
||||
Operator(Token.Type.ASSIGN, lastPrty) { pos, a, b ->
|
||||
Accessor {
|
||||
val value = b.getter(it).value
|
||||
@ -942,6 +955,9 @@ class Compiler(
|
||||
Operator.simple(Token.Type.LT, lastPrty) { c, a, b -> ObjBool(a.compareTo(c, b) < 0) },
|
||||
Operator.simple(Token.Type.GTE, lastPrty) { c, a, b -> ObjBool(a.compareTo(c, b) >= 0) },
|
||||
Operator.simple(Token.Type.GT, lastPrty) { c, a, b -> ObjBool(a.compareTo(c, b) > 0) },
|
||||
// in, is:
|
||||
Operator.simple(Token.Type.IN, lastPrty) { c, a, b -> ObjBool(b.contains(c, a)) },
|
||||
Operator.simple(Token.Type.NOTIN, lastPrty) { c, a, b -> ObjBool(!b.contains(c, a)) },
|
||||
// shuttle <=> 6
|
||||
// bit shifts 7
|
||||
Operator.simple(Token.Type.PLUS, ++lastPrty) { ctx, a, b -> a.plus(ctx, b) },
|
||||
|
@ -4,8 +4,6 @@ import kotlinx.coroutines.sync.Mutex
|
||||
import kotlinx.coroutines.sync.withLock
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlin.math.floor
|
||||
import kotlin.math.roundToLong
|
||||
|
||||
//typealias InstanceMethod = (Context, Obj) -> Obj
|
||||
|
||||
@ -67,6 +65,10 @@ sealed class Obj {
|
||||
context.raiseNotImplemented()
|
||||
}
|
||||
|
||||
open suspend fun contains(context: Context, other: Obj): Boolean {
|
||||
context.raiseNotImplemented()
|
||||
}
|
||||
|
||||
open val asStr: ObjString by lazy {
|
||||
if (this is ObjString) this else ObjString(this.toString())
|
||||
}
|
||||
@ -75,7 +77,16 @@ sealed class Obj {
|
||||
* Class of the object: definition of member functions (top-level), etc.
|
||||
* Note that using lazy allows to avoid endless recursion here
|
||||
*/
|
||||
open val objClass: ObjClass by lazy { ObjClass("Obj") }
|
||||
open val objClass: ObjClass by lazy {
|
||||
ObjClass("Obj").apply {
|
||||
addFn("toString") {
|
||||
thisObj.asStr
|
||||
}
|
||||
addFn("contains") {
|
||||
ObjBool(thisObj.contains(this, args.firstAndOnly()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
open suspend fun plus(context: Context, other: Obj): Obj {
|
||||
context.raiseNotImplemented()
|
||||
@ -198,6 +209,8 @@ sealed class Obj {
|
||||
suspend fun invoke(context: Context, atPos: Pos, thisObj: Obj, args: Arguments): Obj =
|
||||
callOn(context.copy(atPos, args = args, newThisObj = thisObj))
|
||||
|
||||
|
||||
|
||||
val asReadonly: WithAccess<Obj> by lazy { WithAccess(this, false) }
|
||||
val asMutable: WithAccess<Obj> by lazy { WithAccess(this, true) }
|
||||
|
||||
@ -274,176 +287,83 @@ fun Obj.toBool(): Boolean =
|
||||
(this as? ObjBool)?.value ?: throw IllegalArgumentException("cannot convert to boolean $this")
|
||||
|
||||
|
||||
data class ObjReal(val value: Double) : Obj(), Numeric {
|
||||
override val asStr by lazy { ObjString(value.toString()) }
|
||||
override val longValue: Long by lazy { floor(value).toLong() }
|
||||
override val doubleValue: Double by lazy { value }
|
||||
override val toObjInt: ObjInt by lazy { ObjInt(longValue) }
|
||||
override val toObjReal: ObjReal by lazy { ObjReal(value) }
|
||||
|
||||
override fun byValueCopy(): Obj = ObjReal(value)
|
||||
|
||||
override suspend fun compareTo(context: Context, other: Obj): Int {
|
||||
if (other !is Numeric) return -2
|
||||
return value.compareTo(other.doubleValue)
|
||||
}
|
||||
|
||||
override fun toString(): String = value.toString()
|
||||
class ObjRange(val start: Obj?, val end: Obj?,val inclusiveEnd: Boolean) : Obj() {
|
||||
|
||||
override val objClass: ObjClass = type
|
||||
|
||||
override suspend fun plus(context: Context, other: Obj): Obj =
|
||||
ObjReal(this.value + other.toDouble())
|
||||
|
||||
override suspend fun minus(context: Context, other: Obj): Obj =
|
||||
ObjReal(this.value - other.toDouble())
|
||||
|
||||
override suspend fun mul(context: Context, other: Obj): Obj =
|
||||
ObjReal(this.value * other.toDouble())
|
||||
|
||||
override suspend fun div(context: Context, other: Obj): Obj =
|
||||
ObjReal(this.value / other.toDouble())
|
||||
|
||||
override suspend fun mod(context: Context, other: Obj): Obj =
|
||||
ObjReal(this.value % other.toDouble())
|
||||
|
||||
companion object {
|
||||
val type: ObjClass = ObjClass("Real").apply {
|
||||
createField(
|
||||
"roundToInt",
|
||||
statement(Pos.builtIn) {
|
||||
(it.thisObj as ObjReal).value.roundToLong().toObj()
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
data class ObjInt(var value: Long) : Obj(), Numeric {
|
||||
override val asStr get() = ObjString(value.toString())
|
||||
override val longValue get() = value
|
||||
override val doubleValue get() = value.toDouble()
|
||||
override val toObjInt get() = this
|
||||
override val toObjReal = ObjReal(doubleValue)
|
||||
|
||||
override fun byValueCopy(): Obj = ObjInt(value)
|
||||
|
||||
override suspend fun getAndIncrement(context: Context): Obj {
|
||||
return ObjInt(value).also { value++ }
|
||||
override fun toString(): String {
|
||||
val result = StringBuilder()
|
||||
result.append("${start ?: '∞'} ..")
|
||||
if( !inclusiveEnd) result.append('<')
|
||||
result.append(" ${end ?: '∞'}")
|
||||
return result.toString()
|
||||
}
|
||||
|
||||
override suspend fun getAndDecrement(context: Context): Obj {
|
||||
return ObjInt(value).also { value-- }
|
||||
suspend fun containsRange(context: Context, other: ObjRange): Boolean {
|
||||
if( start != null ) {
|
||||
// our start is not -∞ so other start should be GTE or is not contained:
|
||||
if( other.start != null && start.compareTo(context, other.start) > 0) return false
|
||||
}
|
||||
if( end != null ) {
|
||||
// same with the end: if it is open, it can't be contained in ours:
|
||||
if( other.end == null ) return false
|
||||
// both exists, now there could be 4 cases:
|
||||
return when {
|
||||
other.inclusiveEnd && inclusiveEnd ->
|
||||
end.compareTo(context, other.end) >= 0
|
||||
!other.inclusiveEnd && !inclusiveEnd ->
|
||||
end.compareTo(context, other.end) >= 0
|
||||
other.inclusiveEnd && !inclusiveEnd ->
|
||||
end.compareTo(context, other.end) > 0
|
||||
!other.inclusiveEnd && inclusiveEnd ->
|
||||
end.compareTo(context, other.end) >= 0
|
||||
else -> throw IllegalStateException("unknown comparison")
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
override suspend fun incrementAndGet(context: Context): Obj {
|
||||
return ObjInt(++value)
|
||||
override suspend fun contains(context: Context, other: Obj): Boolean {
|
||||
|
||||
if( other is ObjRange)
|
||||
return containsRange(context, other)
|
||||
|
||||
if (start == null && end == null) return true
|
||||
if (start != null) {
|
||||
if (start.compareTo(context, other) > 0) return false
|
||||
}
|
||||
if (end != null) {
|
||||
val cmp = end.compareTo(context, other)
|
||||
if (inclusiveEnd && cmp < 0 || !inclusiveEnd && cmp <= 0) return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
override suspend fun decrementAndGet(context: Context): Obj {
|
||||
return ObjInt(--value)
|
||||
}
|
||||
|
||||
override suspend fun compareTo(context: Context, other: Obj): Int {
|
||||
if (other !is Numeric) return -2
|
||||
return value.compareTo(other.doubleValue)
|
||||
}
|
||||
|
||||
override fun toString(): String = value.toString()
|
||||
|
||||
override val objClass: ObjClass = type
|
||||
|
||||
override suspend fun plus(context: Context, other: Obj): Obj =
|
||||
if (other is ObjInt)
|
||||
ObjInt(this.value + other.value)
|
||||
else
|
||||
ObjReal(this.doubleValue + other.toDouble())
|
||||
|
||||
override suspend fun minus(context: Context, other: Obj): Obj =
|
||||
if (other is ObjInt)
|
||||
ObjInt(this.value - other.value)
|
||||
else
|
||||
ObjReal(this.doubleValue - other.toDouble())
|
||||
|
||||
override suspend fun mul(context: Context, other: Obj): Obj =
|
||||
if (other is ObjInt) {
|
||||
ObjInt(this.value * other.value)
|
||||
} else ObjReal(this.value * other.toDouble())
|
||||
|
||||
override suspend fun div(context: Context, other: Obj): Obj =
|
||||
if (other is ObjInt)
|
||||
ObjInt(this.value / other.value)
|
||||
else ObjReal(this.value / other.toDouble())
|
||||
|
||||
override suspend fun mod(context: Context, other: Obj): Obj =
|
||||
if (other is ObjInt)
|
||||
ObjInt(this.value % other.value)
|
||||
else ObjReal(this.value.toDouble() % other.toDouble())
|
||||
|
||||
/**
|
||||
* We are by-value type ([byValueCopy] is implemented) so we can do in-place
|
||||
* assignment
|
||||
*/
|
||||
override suspend fun assign(context: Context, other: Obj): Obj? {
|
||||
return if (other is ObjInt) {
|
||||
value = other.value
|
||||
this
|
||||
} else null
|
||||
val isIntRange: Boolean by lazy {
|
||||
start is ObjInt && end is ObjInt
|
||||
}
|
||||
|
||||
companion object {
|
||||
val type = ObjClass("Int")
|
||||
val type = ObjClass("Range").apply {
|
||||
addFn("start" ) {
|
||||
thisAs<ObjRange>().start ?: ObjNull
|
||||
}
|
||||
}
|
||||
|
||||
data class ObjBool(val value: Boolean) : Obj() {
|
||||
override val asStr by lazy { ObjString(value.toString()) }
|
||||
|
||||
override suspend fun compareTo(context: Context, other: Obj): Int {
|
||||
if (other !is ObjBool) return -2
|
||||
return value.compareTo(other.value)
|
||||
addFn("end") {
|
||||
thisAs<ObjRange>().end ?: ObjNull
|
||||
}
|
||||
|
||||
override fun toString(): String = value.toString()
|
||||
|
||||
override val objClass: ObjClass = type
|
||||
|
||||
override suspend fun logicalNot(context: Context): Obj = ObjBool(!value)
|
||||
|
||||
override suspend fun logicalAnd(context: Context, other: Obj): Obj = ObjBool(value && other.toBool())
|
||||
|
||||
override suspend fun logicalOr(context: Context, other: Obj): Obj = ObjBool(value || other.toBool())
|
||||
|
||||
companion object {
|
||||
val type = ObjClass("Bool")
|
||||
addFn("isOpen") {
|
||||
thisAs<ObjRange>().let { it.start == null || it.end == null }.toObj()
|
||||
}
|
||||
addFn("isIntRange") {
|
||||
thisAs<ObjRange>().isIntRange.toObj()
|
||||
}
|
||||
addFn("inclusiveEnd") {
|
||||
thisAs<ObjRange>().inclusiveEnd.toObj()
|
||||
}
|
||||
}
|
||||
|
||||
//open class ObjProperty(var value: Obj =ObjVoid) {
|
||||
// open suspend fun get(context: Context): Obj = value
|
||||
// open suspend fun set(context: Context,newValue: Obj): Obj {
|
||||
// return value.also { value = newValue }
|
||||
// }
|
||||
//}
|
||||
class ObjChar(val value: Char): Obj() {
|
||||
|
||||
override val objClass: ObjClass = type
|
||||
|
||||
override suspend fun compareTo(context: Context, other: Obj): Int =
|
||||
(other as? ObjChar)?.let { value.compareTo(it.value) } ?: -1
|
||||
|
||||
override fun toString(): String = value.toString()
|
||||
|
||||
override fun inspect(): String = "'$value'"
|
||||
|
||||
companion object {
|
||||
val type = ObjClass("Char").apply {
|
||||
addFn("toInt") { ObjInt(thisAs<ObjChar>().value.code.toLong()) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
data class ObjNamespace(val name: String) : Obj() {
|
||||
override fun toString(): String {
|
||||
return "namespace ${name}"
|
||||
|
24
library/src/commonMain/kotlin/net/sergeych/ling/ObjBool.kt
Normal file
24
library/src/commonMain/kotlin/net/sergeych/ling/ObjBool.kt
Normal file
@ -0,0 +1,24 @@
|
||||
package net.sergeych.ling
|
||||
|
||||
data class ObjBool(val value: Boolean) : Obj() {
|
||||
override val asStr by lazy { ObjString(value.toString()) }
|
||||
|
||||
override suspend fun compareTo(context: Context, other: Obj): Int {
|
||||
if (other !is ObjBool) return -2
|
||||
return value.compareTo(other.value)
|
||||
}
|
||||
|
||||
override fun toString(): String = value.toString()
|
||||
|
||||
override val objClass: ObjClass = type
|
||||
|
||||
override suspend fun logicalNot(context: Context): Obj = ObjBool(!value)
|
||||
|
||||
override suspend fun logicalAnd(context: Context, other: Obj): Obj = ObjBool(value && other.toBool())
|
||||
|
||||
override suspend fun logicalOr(context: Context, other: Obj): Obj = ObjBool(value || other.toBool())
|
||||
|
||||
companion object {
|
||||
val type = ObjClass("Bool")
|
||||
}
|
||||
}
|
78
library/src/commonMain/kotlin/net/sergeych/ling/ObjInt.kt
Normal file
78
library/src/commonMain/kotlin/net/sergeych/ling/ObjInt.kt
Normal file
@ -0,0 +1,78 @@
|
||||
package net.sergeych.ling
|
||||
|
||||
data class ObjInt(var value: Long) : Obj(), Numeric {
|
||||
override val asStr get() = ObjString(value.toString())
|
||||
override val longValue get() = value
|
||||
override val doubleValue get() = value.toDouble()
|
||||
override val toObjInt get() = this
|
||||
override val toObjReal = ObjReal(doubleValue)
|
||||
|
||||
override fun byValueCopy(): Obj = ObjInt(value)
|
||||
|
||||
override suspend fun getAndIncrement(context: Context): Obj {
|
||||
return ObjInt(value).also { value++ }
|
||||
}
|
||||
|
||||
override suspend fun getAndDecrement(context: Context): Obj {
|
||||
return ObjInt(value).also { value-- }
|
||||
}
|
||||
|
||||
override suspend fun incrementAndGet(context: Context): Obj {
|
||||
return ObjInt(++value)
|
||||
}
|
||||
|
||||
override suspend fun decrementAndGet(context: Context): Obj {
|
||||
return ObjInt(--value)
|
||||
}
|
||||
|
||||
override suspend fun compareTo(context: Context, other: Obj): Int {
|
||||
if (other !is Numeric) return -2
|
||||
return value.compareTo(other.doubleValue)
|
||||
}
|
||||
|
||||
override fun toString(): String = value.toString()
|
||||
|
||||
override val objClass: ObjClass = type
|
||||
|
||||
override suspend fun plus(context: Context, other: Obj): Obj =
|
||||
if (other is ObjInt)
|
||||
ObjInt(this.value + other.value)
|
||||
else
|
||||
ObjReal(this.doubleValue + other.toDouble())
|
||||
|
||||
override suspend fun minus(context: Context, other: Obj): Obj =
|
||||
if (other is ObjInt)
|
||||
ObjInt(this.value - other.value)
|
||||
else
|
||||
ObjReal(this.doubleValue - other.toDouble())
|
||||
|
||||
override suspend fun mul(context: Context, other: Obj): Obj =
|
||||
if (other is ObjInt) {
|
||||
ObjInt(this.value * other.value)
|
||||
} else ObjReal(this.value * other.toDouble())
|
||||
|
||||
override suspend fun div(context: Context, other: Obj): Obj =
|
||||
if (other is ObjInt)
|
||||
ObjInt(this.value / other.value)
|
||||
else ObjReal(this.value / other.toDouble())
|
||||
|
||||
override suspend fun mod(context: Context, other: Obj): Obj =
|
||||
if (other is ObjInt)
|
||||
ObjInt(this.value % other.value)
|
||||
else ObjReal(this.value.toDouble() % other.toDouble())
|
||||
|
||||
/**
|
||||
* We are by-value type ([byValueCopy] is implemented) so we can do in-place
|
||||
* assignment
|
||||
*/
|
||||
override suspend fun assign(context: Context, other: Obj): Obj? {
|
||||
return if (other is ObjInt) {
|
||||
value = other.value
|
||||
this
|
||||
} else null
|
||||
}
|
||||
|
||||
companion object {
|
||||
val type = ObjClass("Int")
|
||||
}
|
||||
}
|
49
library/src/commonMain/kotlin/net/sergeych/ling/ObjReal.kt
Normal file
49
library/src/commonMain/kotlin/net/sergeych/ling/ObjReal.kt
Normal file
@ -0,0 +1,49 @@
|
||||
package net.sergeych.ling
|
||||
|
||||
import kotlin.math.floor
|
||||
import kotlin.math.roundToLong
|
||||
|
||||
data class ObjReal(val value: Double) : Obj(), Numeric {
|
||||
override val asStr by lazy { ObjString(value.toString()) }
|
||||
override val longValue: Long by lazy { floor(value).toLong() }
|
||||
override val doubleValue: Double by lazy { value }
|
||||
override val toObjInt: ObjInt by lazy { ObjInt(longValue) }
|
||||
override val toObjReal: ObjReal by lazy { ObjReal(value) }
|
||||
|
||||
override fun byValueCopy(): Obj = ObjReal(value)
|
||||
|
||||
override suspend fun compareTo(context: Context, other: Obj): Int {
|
||||
if (other !is Numeric) return -2
|
||||
return value.compareTo(other.doubleValue)
|
||||
}
|
||||
|
||||
override fun toString(): String = value.toString()
|
||||
|
||||
override val objClass: ObjClass = type
|
||||
|
||||
override suspend fun plus(context: Context, other: Obj): Obj =
|
||||
ObjReal(this.value + other.toDouble())
|
||||
|
||||
override suspend fun minus(context: Context, other: Obj): Obj =
|
||||
ObjReal(this.value - other.toDouble())
|
||||
|
||||
override suspend fun mul(context: Context, other: Obj): Obj =
|
||||
ObjReal(this.value * other.toDouble())
|
||||
|
||||
override suspend fun div(context: Context, other: Obj): Obj =
|
||||
ObjReal(this.value / other.toDouble())
|
||||
|
||||
override suspend fun mod(context: Context, other: Obj): Obj =
|
||||
ObjReal(this.value % other.toDouble())
|
||||
|
||||
companion object {
|
||||
val type: ObjClass = ObjClass("Real").apply {
|
||||
createField(
|
||||
"roundToInt",
|
||||
statement(Pos.builtIn) {
|
||||
(it.thisObj as ObjReal).value.roundToLong().toObj()
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
@ -45,11 +45,10 @@ private class Parser(fromPos: Pos) {
|
||||
'=' -> {
|
||||
if (pos.currentChar == '=') {
|
||||
pos.advance()
|
||||
if( currentChar == '=' ) {
|
||||
if (currentChar == '=') {
|
||||
pos.advance()
|
||||
Token("===", from, Token.Type.REF_EQ)
|
||||
}
|
||||
else
|
||||
} else
|
||||
Token("==", from, Token.Type.EQ)
|
||||
} else
|
||||
Token("=", from, Token.Type.ASSIGN)
|
||||
@ -127,9 +126,9 @@ private class Parser(fromPos: Pos) {
|
||||
pos.advance()
|
||||
Token("...", from, Token.Type.ELLIPSIS)
|
||||
} else if (currentChar == '<') {
|
||||
pos.advance()
|
||||
Token("..<", from, Token.Type.DOTDOTLT)
|
||||
} else {
|
||||
pos.back()
|
||||
Token("..", from, Token.Type.DOTDOT)
|
||||
}
|
||||
} else
|
||||
@ -153,13 +152,22 @@ private class Parser(fromPos: Pos) {
|
||||
}
|
||||
|
||||
'!' -> {
|
||||
if (currentChar == 'i') {
|
||||
pos.advance()
|
||||
if( currentChar == 'n') {
|
||||
pos.advance()
|
||||
Token("!in", from, Token.Type.NOTIN)
|
||||
} else {
|
||||
pos.back()
|
||||
Token("!", from, Token.Type.NOT)
|
||||
}
|
||||
} else
|
||||
if (currentChar == '=') {
|
||||
pos.advance()
|
||||
if( currentChar == '=' ) {
|
||||
if (currentChar == '=') {
|
||||
pos.advance()
|
||||
Token("!==", from, Token.Type.REF_NEQ)
|
||||
}
|
||||
else
|
||||
} else
|
||||
Token("!=", from, Token.Type.NEQ)
|
||||
} else
|
||||
Token("!", from, Token.Type.NOT)
|
||||
@ -210,7 +218,7 @@ private class Parser(fromPos: Pos) {
|
||||
if (currentChar == '\\') {
|
||||
value = currentChar
|
||||
pos.advance()
|
||||
value = when(value) {
|
||||
value = when (value) {
|
||||
'n' -> '\n'
|
||||
'r' -> '\r'
|
||||
't' -> '\t'
|
||||
@ -218,12 +226,13 @@ private class Parser(fromPos: Pos) {
|
||||
else -> throw ScriptError(currentPos, "unsupported escape character: $value")
|
||||
}
|
||||
}
|
||||
if( currentChar != '\'' ) throw ScriptError(currentPos, "expected end of character literal: '")
|
||||
if (currentChar != '\'') throw ScriptError(currentPos, "expected end of character literal: '")
|
||||
pos.advance()
|
||||
Token(value.toString(), start, Token.Type.CHAR)
|
||||
}
|
||||
|
||||
else -> {
|
||||
// text infix operators:
|
||||
// Labels processing is complicated!
|
||||
// some@ statement: label 'some', ID 'statement'
|
||||
// statement@some: ID 'statement', LABEL 'some'!
|
||||
@ -238,7 +247,10 @@ private class Parser(fromPos: Pos) {
|
||||
} else
|
||||
Token(text, from, Token.Type.LABEL)
|
||||
} else
|
||||
Token(text, from, Token.Type.ID)
|
||||
when (text) {
|
||||
"in" -> Token("in", from, Token.Type.IN)
|
||||
else -> Token(text, from, Token.Type.ID)
|
||||
}
|
||||
} else
|
||||
raise("can't parse token")
|
||||
}
|
||||
|
@ -59,6 +59,7 @@ class Script(
|
||||
addConst("Bool", ObjBool.type)
|
||||
addConst("Char", ObjChar.type)
|
||||
addConst("List", ObjList.type)
|
||||
addConst("Range", ObjRange.type)
|
||||
val pi = ObjReal(PI)
|
||||
addConst("π", pi)
|
||||
getOrCreateNamespace("Math").apply {
|
||||
|
@ -11,6 +11,7 @@ data class Token(val value: String, val pos: Pos, val type: Type) {
|
||||
PLUS, MINUS, STAR, SLASH, PERCENT,
|
||||
ASSIGN, PLUSASSIGN, MINUSASSIGN, STARASSIGN, SLASHASSIGN, PERCENTASSIGN,
|
||||
PLUS2, MINUS2,
|
||||
IN, NOTIN, IS, NOTIS,
|
||||
EQ, NEQ, LT, LTE, GT, GTE, REF_EQ, REF_NEQ,
|
||||
AND, BITAND, OR, BITOR, NOT, BITNOT, DOT, ARROW, QUESTION, COLONCOLON,
|
||||
SINLGE_LINE_COMMENT, MULTILINE_COMMENT,
|
||||
|
@ -65,6 +65,36 @@ class ScriptTest {
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
fun parseRangeTest() {
|
||||
var tt = parseLing("5 .. 4".toSource())
|
||||
|
||||
assertEquals(Token.Type.INT, tt[0].type)
|
||||
assertEquals(Token.Type.DOTDOT, tt[1].type)
|
||||
assertEquals(Token.Type.INT, tt[2].type)
|
||||
|
||||
tt = parseLing("5 ..< 4".toSource())
|
||||
|
||||
assertEquals(Token.Type.INT, tt[0].type)
|
||||
assertEquals(Token.Type.DOTDOTLT, tt[1].type)
|
||||
assertEquals(Token.Type.INT, tt[2].type)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun parseInTest() {
|
||||
var tt = parseLing("5 in 4".toSource())
|
||||
|
||||
assertEquals(Token.Type.INT, tt[0].type)
|
||||
assertEquals(Token.Type.IN, tt[1].type)
|
||||
assertEquals(Token.Type.INT, tt[2].type)
|
||||
|
||||
tt = parseLing("5 ..< 4".toSource())
|
||||
|
||||
assertEquals(Token.Type.INT, tt[0].type)
|
||||
assertEquals(Token.Type.DOTDOTLT, tt[1].type)
|
||||
assertEquals(Token.Type.INT, tt[2].type)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun parserLabelsTest() {
|
||||
val src = "label@ break@label".toSource()
|
||||
@ -748,35 +778,42 @@ class ScriptTest {
|
||||
|
||||
@Test
|
||||
fun testListLiteral() = runTest {
|
||||
eval("""
|
||||
eval(
|
||||
"""
|
||||
val list = [1,22,3]
|
||||
assert(list[0] == 1)
|
||||
assert(list[1] == 22)
|
||||
assert(list[2] == 3)
|
||||
""".trimIndent())
|
||||
""".trimIndent()
|
||||
)
|
||||
|
||||
eval("""
|
||||
eval(
|
||||
"""
|
||||
val x0 = 100
|
||||
val list = [x0 + 1, x0 * 10, 3]
|
||||
assert(list[0] == 101)
|
||||
assert(list[1] == 1000)
|
||||
assert(list[2] == 3)
|
||||
""".trimIndent())
|
||||
""".trimIndent()
|
||||
)
|
||||
|
||||
eval("""
|
||||
eval(
|
||||
"""
|
||||
val x0 = 100
|
||||
val list = [x0 + 1, x0 * 10, if(x0 < 100) "low" else "high", 5]
|
||||
assert(list[0] == 101)
|
||||
assert(list[1] == 1000)
|
||||
assert(list[2] == "high")
|
||||
assert(list[3] == 5)
|
||||
""".trimIndent())
|
||||
""".trimIndent()
|
||||
)
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testListLiteralSpread() = runTest {
|
||||
eval("""
|
||||
eval(
|
||||
"""
|
||||
val list1 = [1,22,3]
|
||||
val list = ["start", ...list1, "end"]
|
||||
assert(list[0] == "start")
|
||||
@ -784,40 +821,48 @@ class ScriptTest {
|
||||
assert(list[2] == 22)
|
||||
assert(list[3] == 3)
|
||||
assert(list[4] == "end")
|
||||
""".trimIndent())
|
||||
""".trimIndent()
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testListSize() = runTest {
|
||||
eval("""
|
||||
eval(
|
||||
"""
|
||||
val a = [4,3]
|
||||
assert(a.size == 2)
|
||||
""".trimIndent())
|
||||
""".trimIndent()
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testArrayCompare() = runTest {
|
||||
eval("""
|
||||
eval(
|
||||
"""
|
||||
val a = [4,3]
|
||||
val b = [4,3]
|
||||
assert(a == b)
|
||||
assert( a === a )
|
||||
assert( !(a === b) )
|
||||
assert( a !== b )
|
||||
""".trimIndent())
|
||||
""".trimIndent()
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun forLoop1() = runTest {
|
||||
eval("""
|
||||
eval(
|
||||
"""
|
||||
var sum = 0
|
||||
for(i in [1,2,3]) {
|
||||
println(i)
|
||||
sum += i
|
||||
}
|
||||
assert(sum == 6)
|
||||
""".trimIndent())
|
||||
eval("""
|
||||
""".trimIndent()
|
||||
)
|
||||
eval(
|
||||
"""
|
||||
fun test1(array) {
|
||||
var sum = 0
|
||||
for(i in array) {
|
||||
@ -827,12 +872,14 @@ class ScriptTest {
|
||||
}
|
||||
println("result=",test1([1,2]))
|
||||
println("result=",test1([1,2,3]))
|
||||
""".trimIndent())
|
||||
""".trimIndent()
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun forLoop2() = runTest {
|
||||
println(eval(
|
||||
println(
|
||||
eval(
|
||||
"""
|
||||
fun search(haystack, needle) {
|
||||
for(ch in haystack) {
|
||||
@ -844,7 +891,88 @@ class ScriptTest {
|
||||
assert( search("hello", 'l') == "found")
|
||||
assert( search("hello", 'z') == null)
|
||||
""".trimIndent()
|
||||
).toString())
|
||||
).toString()
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testIntOpenRangeInclusive() = runTest {
|
||||
eval(
|
||||
"""
|
||||
val r = 10 .. 20
|
||||
assert( r::class == Range)
|
||||
assert(r.isOpen == false)
|
||||
assert(r.start == 10)
|
||||
assert(r.end == 20)
|
||||
assert(r.inclusiveEnd == true)
|
||||
assert(r.isIntRange)
|
||||
|
||||
assert(12 in r)
|
||||
assert(10 in r)
|
||||
assert(20 in r)
|
||||
|
||||
assert(9 !in r)
|
||||
assert(21 !in r)
|
||||
|
||||
assert( (11..12) in r)
|
||||
assert( (10..11) in r)
|
||||
assert( (11..20) in r)
|
||||
assert( (10..20) in r)
|
||||
|
||||
assert( (9..12) !in r)
|
||||
assert( (1..9) !in r)
|
||||
assert( (17..22) !in r)
|
||||
assert( (21..22) !in r)
|
||||
|
||||
// assert(r.size == 11)
|
||||
""".trimIndent()
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testIntOpenRangeExclusive() = runTest {
|
||||
eval(
|
||||
"""
|
||||
val r = 10 ..< 20
|
||||
assert( r::class == Range)
|
||||
assert(r.isOpen == false)
|
||||
assert(r.start == 10)
|
||||
assert(r.end == 20)
|
||||
assert(r.inclusiveEnd == false)
|
||||
assert(r.isIntRange)
|
||||
|
||||
assert(12 in r)
|
||||
assert(10 in r)
|
||||
assert(20 !in r)
|
||||
|
||||
assert(9 !in r)
|
||||
assert(21 !in r)
|
||||
|
||||
assert( (11..12) in r)
|
||||
assert( (10..11) in r)
|
||||
assert( (11..20) !in r)
|
||||
assert( (10..20) !in r)
|
||||
|
||||
assert( (10..<20) in r)
|
||||
|
||||
assert( (9..12) !in r)
|
||||
assert( (1..9) !in r)
|
||||
assert( (17..22) !in r)
|
||||
assert( (21..22) !in r)
|
||||
|
||||
|
||||
""".trimIndent()
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testIntOpenRangeInExclusive() = runTest {
|
||||
eval(
|
||||
"""
|
||||
assert( (1..3) !in (1..<3) )
|
||||
assert( (1..<3) in (1..3) )
|
||||
""".trimIndent()
|
||||
)
|
||||
}
|
||||
|
||||
// @Test
|
||||
|
@ -205,4 +205,9 @@ class BookTest {
|
||||
runDocTests("../docs/List.md")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testFromRange() = runTest {
|
||||
runDocTests("../docs/Range.md")
|
||||
}
|
||||
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user