fix #23 string formatting and manipulations
This commit is contained in:
		
							parent
							
								
									1db1f12be3
								
							
						
					
					
						commit
						19eae213ec
					
				
							
								
								
									
										0
									
								
								docs/String.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								docs/String.md
									
									
									
									
									
										Normal file
									
								
							@ -1051,31 +1051,79 @@ Are the same as in string literals with little difference:
 | 
			
		||||
 | 
			
		||||
## String details
 | 
			
		||||
 | 
			
		||||
Strings are much like Kotlin ones:
 | 
			
		||||
Strings are arrays of Unicode characters. It can be indexed, and indexing will
 | 
			
		||||
return a valid Unicode character at position. No utf hassle:
 | 
			
		||||
 | 
			
		||||
    "Hello".length
 | 
			
		||||
    "Парашют"[5]
 | 
			
		||||
    >>> 'ю'
 | 
			
		||||
 | 
			
		||||
Its length is, of course, in characters:
 | 
			
		||||
 | 
			
		||||
    "разум".length
 | 
			
		||||
    >>> 5
 | 
			
		||||
 | 
			
		||||
And supports growing set of kotlin-borrowed operations, see below, for example:
 | 
			
		||||
 | 
			
		||||
    assertEquals("Hell", "Hello".dropLast(1))
 | 
			
		||||
    >>> void
 | 
			
		||||
 | 
			
		||||
To format a string use sprintf-style modifiers like:
 | 
			
		||||
 | 
			
		||||
    val a = "hello"
 | 
			
		||||
    val b = 11
 | 
			
		||||
    assertEquals( "hello:11", "%s:%d"(a, b) )
 | 
			
		||||
    assertEquals( " hello:    11", "%6s:%6d"(a, b) )
 | 
			
		||||
    assertEquals( "hello :11    ", "%-6s:%-6d"(a, b) )
 | 
			
		||||
    >>> void
 | 
			
		||||
 | 
			
		||||
List of format specifiers closely resembles C sprintf() one. See [format specifiers](https://github.com/sergeych/mp_stools?tab=readme-ov-file#sprintf-syntax-summary), this is doe using [mp_stools kotlin multiplatform library](https://github.com/sergeych/mp_stools). Currently supported Lyng types are `String`, `Int`, `Real`, `Bool`, the rest are displayed using their `toString()` representation.
 | 
			
		||||
 | 
			
		||||
This list will be extended.
 | 
			
		||||
 | 
			
		||||
To get the substring use:
 | 
			
		||||
 | 
			
		||||
    assertEquals("pult", "catapult".takeLast(4))
 | 
			
		||||
    assertEquals("cat", "catapult".take(3))
 | 
			
		||||
    assertEquals("cat", "catapult".dropLast(5))
 | 
			
		||||
    assertEquals("pult", "catapult".drop(4))
 | 
			
		||||
    >>> void
 | 
			
		||||
 | 
			
		||||
And to get a portion you can slice it with range as the index:
 | 
			
		||||
 | 
			
		||||
    assertEquals( "tap", "catapult"[ 2 .. 4 ])
 | 
			
		||||
    assertEquals( "tap", "catapult"[ 2 ..< 5 ])
 | 
			
		||||
    >>> void
 | 
			
		||||
 | 
			
		||||
Open-ended ranges could be used to get start and end too:
 | 
			
		||||
 | 
			
		||||
    assertEquals( "cat", "catapult"[ ..< 3 ])
 | 
			
		||||
    assertEquals( "cat", "catapult"[ .. 2 ])
 | 
			
		||||
    assertEquals( "pult", "catapult"[ 4.. ])
 | 
			
		||||
    >>> void
 | 
			
		||||
 | 
			
		||||
### String operations
 | 
			
		||||
 | 
			
		||||
Concatenation is a `+`: `"hello " + name` works as expected. No confusion.
 | 
			
		||||
 | 
			
		||||
Typical set of String functions includes:
 | 
			
		||||
 | 
			
		||||
| fun/prop         | description / notes                                        |
 | 
			
		||||
|------------------|------------------------------------------------------------|
 | 
			
		||||
| lower()          | change case to unicode upper                               |
 | 
			
		||||
| upper()          | change case to unicode lower                               |
 | 
			
		||||
| fun/prop           | description / notes                                        |
 | 
			
		||||
|--------------------|------------------------------------------------------------|
 | 
			
		||||
| lower()            | change case to unicode upper                               |
 | 
			
		||||
| upper()            | change case to unicode lower                               |
 | 
			
		||||
| startsWith(prefix) | true if starts with a prefix                               |
 | 
			
		||||
| take(n)          | get a new string from up to n first characters             |
 | 
			
		||||
| takeLast(n)      | get a new string from up to n last characters              |
 | 
			
		||||
| drop(n)          | get a new string dropping n first chars, or empty string   |
 | 
			
		||||
| dropLast(n)      | get a new string dropping n last chars, or empty string    |
 | 
			
		||||
| size             | size in characters like `length` because String is [Array] |
 | 
			
		||||
| endsWith(prefix)   | true if ends with a prefix                                 |
 | 
			
		||||
| take(n)            | get a new string from up to n first characters             |
 | 
			
		||||
| takeLast(n)        | get a new string from up to n last characters              |
 | 
			
		||||
| drop(n)            | get a new string dropping n first chars, or empty string   |
 | 
			
		||||
| dropLast(n)        | get a new string dropping n last chars, or empty string    |
 | 
			
		||||
| size               | size in characters like `length` because String is [Array] |
 | 
			
		||||
| (args...)          | sprintf-like formatting, see [string formatting]           |
 | 
			
		||||
| [index]            | character at index                                         | 
 | 
			
		||||
| [Range]            | substring at range                                         |
 | 
			
		||||
| s1 + s2            | concatenation                                              |
 | 
			
		||||
| s1 += s2           | self-modifying concatenation                               |
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -1111,5 +1159,5 @@ See [math functions](math.md). Other general purpose functions are:
 | 
			
		||||
[Iterator]: Iterator.md
 | 
			
		||||
[Real]: Real.md
 | 
			
		||||
[Range]: Range.md
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
[String]: String.md
 | 
			
		||||
[string formatting]: https://github.com/sergeych/mp_stools?tab=readme-ov-file#sprintf-syntax-summary
 | 
			
		||||
 | 
			
		||||
@ -4,7 +4,7 @@ import org.jetbrains.kotlin.gradle.ExperimentalWasmDsl
 | 
			
		||||
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
 | 
			
		||||
 | 
			
		||||
group = "net.sergeych"
 | 
			
		||||
version = "0.6.-SNAPSHOT"
 | 
			
		||||
version = "0.6.4-SNAPSHOT"
 | 
			
		||||
 | 
			
		||||
buildscript {
 | 
			
		||||
    repositories {
 | 
			
		||||
 | 
			
		||||
@ -33,6 +33,13 @@ data class Arguments(val list: List<Obj>,val tailBlockMode: Boolean = false) : L
 | 
			
		||||
        return list.first()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Convert to list of kotlin objects, see [Obj.toKotlin].
 | 
			
		||||
     */
 | 
			
		||||
    suspend fun toKotlinList(context: Context): List<Any?> {
 | 
			
		||||
        return list.map { it.toKotlin(context) }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    companion object {
 | 
			
		||||
        val EMPTY = Arguments(emptyList())
 | 
			
		||||
        fun from(values: Collection<Obj>) = Arguments(values.toList())
 | 
			
		||||
 | 
			
		||||
@ -59,7 +59,7 @@ class Compiler(
 | 
			
		||||
                        parseBlock(cc)
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                Token.Type.RBRACE -> {
 | 
			
		||||
                Token.Type.RBRACE, Token.Type.RBRACKET -> {
 | 
			
		||||
                    cc.previous()
 | 
			
		||||
                    return null
 | 
			
		||||
                }
 | 
			
		||||
@ -226,8 +226,7 @@ class Compiler(
 | 
			
		||||
                        val index = parseStatement(cc) ?: throw ScriptError(t.pos, "Expecting index expression")
 | 
			
		||||
                        cc.skipTokenOfType(Token.Type.RBRACKET, "missing ']' at the end of the list literal")
 | 
			
		||||
                        operand = Accessor({ cxt ->
 | 
			
		||||
                            val i = (index.execute(cxt) as? ObjInt)?.value?.toInt()
 | 
			
		||||
                                ?: cxt.raiseError("index must be integer")
 | 
			
		||||
                            val i = index.execute(cxt)
 | 
			
		||||
                            val x = left.getter(cxt).value
 | 
			
		||||
                            if( x == ObjNull && isOptional) ObjNull.asReadonly
 | 
			
		||||
                            else x.getAt(cxt, i).asMutable
 | 
			
		||||
@ -341,10 +340,10 @@ class Compiler(
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                Token.Type.DOTDOT, Token.Type.DOTDOTLT -> {
 | 
			
		||||
                    // closed-range operator
 | 
			
		||||
                    // range operator
 | 
			
		||||
                    val isEndInclusive = t.type == Token.Type.DOTDOT
 | 
			
		||||
                    val left = operand
 | 
			
		||||
                    val right = parseStatement(cc)
 | 
			
		||||
                    val right = parseExpression(cc)
 | 
			
		||||
                    operand = Accessor {
 | 
			
		||||
                        ObjRange(
 | 
			
		||||
                            left?.getter?.invoke(it)?.value ?: ObjNull,
 | 
			
		||||
@ -366,11 +365,15 @@ class Compiler(
 | 
			
		||||
                    } ?: parseLambdaExpression(cc)
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                Token.Type.RBRACKET -> {
 | 
			
		||||
                    cc.previous()
 | 
			
		||||
                    return operand
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                else -> {
 | 
			
		||||
                    cc.previous()
 | 
			
		||||
                    operand?.let { return it }
 | 
			
		||||
                    operand = parseAccessor(cc) ?: throw ScriptError(t.pos, "Expecting expression")
 | 
			
		||||
                    operand = parseAccessor(cc) ?: return null //throw ScriptError(t.pos, "Expecting expression")
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
@ -1115,7 +1118,7 @@ class Compiler(
 | 
			
		||||
                    var breakCaught = false
 | 
			
		||||
 | 
			
		||||
                    if (size > 0) {
 | 
			
		||||
                        var current = runCatching { sourceObj.getAt(forContext, 0) }
 | 
			
		||||
                        var current = runCatching { sourceObj.getAt(forContext, ObjInt(0)) }
 | 
			
		||||
                            .getOrElse {
 | 
			
		||||
                                throw ScriptError(
 | 
			
		||||
                                    tOp.pos,
 | 
			
		||||
@ -1142,7 +1145,7 @@ class Compiler(
 | 
			
		||||
                                }
 | 
			
		||||
                            } else result = body.execute(forContext)
 | 
			
		||||
                            if (++index >= size) break
 | 
			
		||||
                            current = sourceObj.getAt(forContext, index)
 | 
			
		||||
                            current = sourceObj.getAt(forContext, ObjInt(index.toLong()))
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                    if (!breakCaught && elseStatement != null) {
 | 
			
		||||
 | 
			
		||||
@ -42,6 +42,9 @@ data class Accessor(
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
open class Obj {
 | 
			
		||||
 | 
			
		||||
    val isNull by lazy { this === ObjNull }
 | 
			
		||||
 | 
			
		||||
    var isFrozen: Boolean = false
 | 
			
		||||
 | 
			
		||||
    private val monitor = Mutex()
 | 
			
		||||
@ -176,6 +179,13 @@ open class Obj {
 | 
			
		||||
        context.raiseNotImplemented()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Convert Lyng object to its Kotlin counterpart
 | 
			
		||||
     */
 | 
			
		||||
    open suspend fun toKotlin(context: Context): Any? {
 | 
			
		||||
        return toString()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fun willMutate(context: Context) {
 | 
			
		||||
        if (isFrozen) context.raiseError("attempt to mutate frozen object")
 | 
			
		||||
    }
 | 
			
		||||
@ -201,7 +211,7 @@ open class Obj {
 | 
			
		||||
        if (field.isMutable) field.value = newValue else context.raiseError("can't assign to read-only field: $name")
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    open suspend fun getAt(context: Context, index: Int): Obj {
 | 
			
		||||
    open suspend fun getAt(context: Context, index: Obj): Obj {
 | 
			
		||||
        context.raiseNotImplemented("indexing")
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -297,7 +307,7 @@ object ObjNull : Obj() {
 | 
			
		||||
        context.raiseNPE()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override suspend fun getAt(context: Context, index: Int): Obj {
 | 
			
		||||
    override suspend fun getAt(context: Context, index: Obj): Obj {
 | 
			
		||||
        context.raiseNPE()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -310,6 +320,10 @@ object ObjNull : Obj() {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun toString(): String = "null"
 | 
			
		||||
 | 
			
		||||
    override suspend fun toKotlin(context: Context): Any? {
 | 
			
		||||
        return null
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
interface Numeric {
 | 
			
		||||
 | 
			
		||||
@ -18,6 +18,10 @@ data class ObjBool(val value: Boolean) : Obj() {
 | 
			
		||||
 | 
			
		||||
    override suspend fun logicalOr(context: Context, other: Obj): Obj = ObjBool(value || other.toBool())
 | 
			
		||||
 | 
			
		||||
    override suspend fun toKotlin(context: Context): Any {
 | 
			
		||||
        return value
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    companion object {
 | 
			
		||||
        val type = ObjClass("Bool")
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -162,7 +162,7 @@ val ObjArray by lazy {
 | 
			
		||||
        addFn("contains", isOpen = true) {
 | 
			
		||||
            val obj = args.firstAndOnly()
 | 
			
		||||
            for( i in 0..< thisObj.invokeInstanceMethod(this, "size").toInt()) {
 | 
			
		||||
                if( thisObj.getAt(this, i).compareTo(this, obj) == 0 ) return@addFn ObjTrue
 | 
			
		||||
                if( thisObj.getAt(this, ObjInt(i.toLong())).compareTo(this, obj) == 0 ) return@addFn ObjTrue
 | 
			
		||||
            }
 | 
			
		||||
            ObjFalse
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
@ -72,6 +72,10 @@ data class ObjInt(var value: Long) : Obj(), Numeric {
 | 
			
		||||
        } else null
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override suspend fun toKotlin(context: Context): Any {
 | 
			
		||||
        return value
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    companion object {
 | 
			
		||||
        val type = ObjClass("Int")
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -11,15 +11,15 @@ class ObjList(val list: MutableList<Obj> = mutableListOf()) : Obj() {
 | 
			
		||||
        list.joinToString(separator = ", ") { it.inspect() }
 | 
			
		||||
    }]"
 | 
			
		||||
 | 
			
		||||
    fun normalize(context: Context, index: Int, allowisEndInclusive: Boolean = false): Int {
 | 
			
		||||
    fun normalize(context: Context, index: Int, allowsEndInclusive: Boolean = false): Int {
 | 
			
		||||
        val i = if (index < 0) list.size + index else index
 | 
			
		||||
        if (allowisEndInclusive && i == list.size) return i
 | 
			
		||||
        if (allowsEndInclusive && i == list.size) return i
 | 
			
		||||
        if (i !in list.indices) context.raiseError("index $index out of bounds for size ${list.size}")
 | 
			
		||||
        return i
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override suspend fun getAt(context: Context, index: Int): Obj {
 | 
			
		||||
        val i = normalize(context, index)
 | 
			
		||||
    override suspend fun getAt(context: Context, index: Obj): Obj {
 | 
			
		||||
        val i = normalize(context, index.toInt())
 | 
			
		||||
        return list[i]
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -82,6 +82,10 @@ class ObjList(val list: MutableList<Obj> = mutableListOf()) : Obj() {
 | 
			
		||||
    override val objClass: ObjClass
 | 
			
		||||
        get() = type
 | 
			
		||||
 | 
			
		||||
    override suspend fun toKotlin(context: Context): Any {
 | 
			
		||||
        return list.map { it.toKotlin(context) }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    companion object {
 | 
			
		||||
        val type = ObjClass("List", ObjArray).apply {
 | 
			
		||||
 | 
			
		||||
@ -92,7 +96,7 @@ class ObjList(val list: MutableList<Obj> = mutableListOf()) : Obj() {
 | 
			
		||||
            )
 | 
			
		||||
            addFn("getAt") {
 | 
			
		||||
                requireExactCount(1)
 | 
			
		||||
                thisAs<ObjList>().getAt(this, requiredArg<ObjInt>(0).value.toInt())
 | 
			
		||||
                thisAs<ObjList>().getAt(this, requiredArg<Obj>(0))
 | 
			
		||||
            }
 | 
			
		||||
            addFn("putAt") {
 | 
			
		||||
                requireExactCount(2)
 | 
			
		||||
@ -113,7 +117,7 @@ class ObjList(val list: MutableList<Obj> = mutableListOf()) : Obj() {
 | 
			
		||||
                    val l = thisAs<ObjList>()
 | 
			
		||||
                    var index = l.normalize(
 | 
			
		||||
                        this, requiredArg<ObjInt>(0).value.toInt(),
 | 
			
		||||
                        allowisEndInclusive = true
 | 
			
		||||
                        allowsEndInclusive = true
 | 
			
		||||
                    )
 | 
			
		||||
                    for (i in 1..<args.size) l.list.add(index++, args[i])
 | 
			
		||||
                    ObjVoid
 | 
			
		||||
 | 
			
		||||
@ -36,6 +36,13 @@ data class ObjReal(val value: Double) : Obj(), Numeric {
 | 
			
		||||
    override suspend fun mod(context: Context, other: Obj): Obj =
 | 
			
		||||
        ObjReal(this.value % other.toDouble())
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Returns unboxed Double value
 | 
			
		||||
     */
 | 
			
		||||
    override suspend fun toKotlin(context: Context): Any {
 | 
			
		||||
        return value
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    companion object {
 | 
			
		||||
        val type: ObjClass = ObjClass("Real").apply {
 | 
			
		||||
            createField(
 | 
			
		||||
 | 
			
		||||
@ -2,11 +2,20 @@ package net.sergeych.lyng
 | 
			
		||||
 | 
			
		||||
import kotlinx.serialization.SerialName
 | 
			
		||||
import kotlinx.serialization.Serializable
 | 
			
		||||
import net.sergeych.sprintf.sprintf
 | 
			
		||||
 | 
			
		||||
@Serializable
 | 
			
		||||
@SerialName("string")
 | 
			
		||||
data class ObjString(val value: String) : Obj() {
 | 
			
		||||
 | 
			
		||||
//    fun normalize(context: Context, index: Int, allowsEndInclusive: Boolean = false): Int {
 | 
			
		||||
//        val i = if (index < 0) value.length + index else index
 | 
			
		||||
//        if (allowsEndInclusive && i == value.length) return i
 | 
			
		||||
//        if (i !in value.indices) context.raiseError("index $index out of bounds for length ${value.length} of \"$value\"")
 | 
			
		||||
//        return i
 | 
			
		||||
//    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    override suspend fun compareTo(context: Context, other: Obj): Int {
 | 
			
		||||
        if (other !is ObjString) return -2
 | 
			
		||||
        return this.value.compareTo(other.value)
 | 
			
		||||
@ -27,8 +36,21 @@ data class ObjString(val value: String) : Obj() {
 | 
			
		||||
        return ObjString(value + other.asStr.value)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override suspend fun getAt(context: Context, index: Int): Obj {
 | 
			
		||||
        return ObjChar(value[index])
 | 
			
		||||
    override suspend fun getAt(context: Context, index: Obj): Obj {
 | 
			
		||||
        if( index is ObjInt ) return ObjChar(value[index.toInt()])
 | 
			
		||||
        if( index is ObjRange ) {
 | 
			
		||||
            val start = if(index.start == null || index.start.isNull) 0 else  index.start.toInt()
 | 
			
		||||
            val end = if( index.end  == null || index.end.isNull ) value.length else  {
 | 
			
		||||
                val e = index.end.toInt()
 | 
			
		||||
                if( index.isEndInclusive) e + 1 else e
 | 
			
		||||
            }
 | 
			
		||||
            return ObjString(value.substring(start, end))
 | 
			
		||||
        }
 | 
			
		||||
        context.raiseArgumentError("String index must be Int or Range")
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override suspend fun callOn(context: Context): Obj {
 | 
			
		||||
        return ObjString(this.value.sprintf(*context.args.toKotlinList(context).toTypedArray()))
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override suspend fun contains(context: Context, other: Obj): Boolean {
 | 
			
		||||
@ -41,10 +63,12 @@ data class ObjString(val value: String) : Obj() {
 | 
			
		||||
 | 
			
		||||
    companion object {
 | 
			
		||||
        val type = ObjClass("String").apply {
 | 
			
		||||
            addConst("startsWith",
 | 
			
		||||
                statement {
 | 
			
		||||
                    ObjBool(thisAs<ObjString>().value.startsWith(requiredArg<ObjString>(0).value))
 | 
			
		||||
                })
 | 
			
		||||
            addFn("startsWith") {
 | 
			
		||||
                ObjBool(thisAs<ObjString>().value.startsWith(requiredArg<ObjString>(0).value))
 | 
			
		||||
            }
 | 
			
		||||
            addFn("endsWith") {
 | 
			
		||||
                ObjBool(thisAs<ObjString>().value.endsWith(requiredArg<ObjString>(0).value))
 | 
			
		||||
            }
 | 
			
		||||
            addConst("length",
 | 
			
		||||
                statement { ObjInt(thisAs<ObjString>().value.length.toLong()) }
 | 
			
		||||
            )
 | 
			
		||||
 | 
			
		||||
@ -1056,7 +1056,7 @@ class ScriptTest {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    fun testIntOpenRangeInclusive() = runTest {
 | 
			
		||||
    fun testIntClosedRangeInclusive() = runTest {
 | 
			
		||||
        eval(
 | 
			
		||||
            """
 | 
			
		||||
            val r = 10 .. 20
 | 
			
		||||
@ -1090,7 +1090,7 @@ class ScriptTest {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    fun testIntOpenRangeExclusive() = runTest {
 | 
			
		||||
    fun testIntClosedRangeExclusive() = runTest {
 | 
			
		||||
        eval(
 | 
			
		||||
            """
 | 
			
		||||
            val r = 10 ..< 20
 | 
			
		||||
@ -1126,7 +1126,7 @@ class ScriptTest {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    fun testIntOpenRangeInExclusive() = runTest {
 | 
			
		||||
    fun testIntClosedRangeInExclusive() = runTest {
 | 
			
		||||
        eval(
 | 
			
		||||
            """
 | 
			
		||||
                assert( (1..3) !in (1..<3) )
 | 
			
		||||
@ -1135,6 +1135,39 @@ class ScriptTest {
 | 
			
		||||
        )
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    fun testOpenStartRanges() = runTest {
 | 
			
		||||
        eval("""
 | 
			
		||||
            var r = ..5
 | 
			
		||||
            assert( r::class == Range)
 | 
			
		||||
            assert( r.start == null)
 | 
			
		||||
            assert( r.end == 5)
 | 
			
		||||
            assert( r.isEndInclusive)
 | 
			
		||||
            
 | 
			
		||||
            r = ..< 5
 | 
			
		||||
            assert( r::class == Range)
 | 
			
		||||
            assert( r.start == null)
 | 
			
		||||
            assert( r.end == 5)
 | 
			
		||||
            assert( !r.isEndInclusive)
 | 
			
		||||
            
 | 
			
		||||
            assert( r.start == null)
 | 
			
		||||
            
 | 
			
		||||
            assert( (-2..3) in r)
 | 
			
		||||
            assert( (-2..12) !in r)
 | 
			
		||||
            
 | 
			
		||||
        """.trimIndent())
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    fun testOpenEndRanges() = runTest {
 | 
			
		||||
        eval("""
 | 
			
		||||
            var r = 5..
 | 
			
		||||
            assert( r::class == Range)
 | 
			
		||||
            assert( r.end == null)
 | 
			
		||||
            assert( r.start == 5)
 | 
			
		||||
        """.trimIndent())
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    fun testCharacterRange() = runTest {
 | 
			
		||||
        eval(
 | 
			
		||||
@ -2133,4 +2166,23 @@ class ScriptTest {
 | 
			
		||||
            assert(s.length == 2)
 | 
			
		||||
        """.trimIndent())
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    fun testSprintf() = runTest {
 | 
			
		||||
        eval(""" 
 | 
			
		||||
            assertEquals( "123.45", "%3.2f"(123.451678) )
 | 
			
		||||
            assertEquals( "123.45: hello", "%3.2f: %s"(123.451678, "hello") )
 | 
			
		||||
            assertEquals( "123.45: true", "%3.2f: %s"(123.451678, true) )
 | 
			
		||||
        """.trimIndent())
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Test
 | 
			
		||||
    fun testSubstringRangeFailure() = runTest {
 | 
			
		||||
        eval(""" 
 | 
			
		||||
            assertEquals("pult", "catapult"[4..])
 | 
			
		||||
            assertEquals("cat", "catapult"[..2])
 | 
			
		||||
            """.trimIndent()
 | 
			
		||||
        )
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user