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
|
## 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
|
>>> 5
|
||||||
|
|
||||||
And supports growing set of kotlin-borrowed operations, see below, for example:
|
And supports growing set of kotlin-borrowed operations, see below, for example:
|
||||||
|
|
||||||
assertEquals("Hell", "Hello".dropLast(1))
|
assertEquals("Hell", "Hello".dropLast(1))
|
||||||
>>> void
|
>>> 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
|
### String operations
|
||||||
|
|
||||||
Concatenation is a `+`: `"hello " + name` works as expected. No confusion.
|
Concatenation is a `+`: `"hello " + name` works as expected. No confusion.
|
||||||
|
|
||||||
Typical set of String functions includes:
|
Typical set of String functions includes:
|
||||||
|
|
||||||
| fun/prop | description / notes |
|
| fun/prop | description / notes |
|
||||||
|------------------|------------------------------------------------------------|
|
|--------------------|------------------------------------------------------------|
|
||||||
| lower() | change case to unicode upper |
|
| lower() | change case to unicode upper |
|
||||||
| upper() | change case to unicode lower |
|
| upper() | change case to unicode lower |
|
||||||
| startsWith(prefix) | true if starts with a prefix |
|
| startsWith(prefix) | true if starts with a prefix |
|
||||||
| take(n) | get a new string from up to n first characters |
|
| endsWith(prefix) | true if ends with a prefix |
|
||||||
| takeLast(n) | get a new string from up to n last characters |
|
| take(n) | get a new string from up to n first characters |
|
||||||
| drop(n) | get a new string dropping n first chars, or empty string |
|
| takeLast(n) | get a new string from up to n last characters |
|
||||||
| dropLast(n) | get a new string dropping n last chars, or empty string |
|
| drop(n) | get a new string dropping n first chars, or empty string |
|
||||||
| size | size in characters like `length` because String is [Array] |
|
| 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
|
[Iterator]: Iterator.md
|
||||||
[Real]: Real.md
|
[Real]: Real.md
|
||||||
[Range]: Range.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
|
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
|
||||||
|
|
||||||
group = "net.sergeych"
|
group = "net.sergeych"
|
||||||
version = "0.6.-SNAPSHOT"
|
version = "0.6.4-SNAPSHOT"
|
||||||
|
|
||||||
buildscript {
|
buildscript {
|
||||||
repositories {
|
repositories {
|
||||||
|
@ -33,6 +33,13 @@ data class Arguments(val list: List<Obj>,val tailBlockMode: Boolean = false) : L
|
|||||||
return list.first()
|
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 {
|
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())
|
||||||
|
@ -59,7 +59,7 @@ class Compiler(
|
|||||||
parseBlock(cc)
|
parseBlock(cc)
|
||||||
}
|
}
|
||||||
|
|
||||||
Token.Type.RBRACE -> {
|
Token.Type.RBRACE, Token.Type.RBRACKET -> {
|
||||||
cc.previous()
|
cc.previous()
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
@ -226,8 +226,7 @@ class Compiler(
|
|||||||
val index = parseStatement(cc) ?: throw ScriptError(t.pos, "Expecting index expression")
|
val index = parseStatement(cc) ?: throw ScriptError(t.pos, "Expecting index expression")
|
||||||
cc.skipTokenOfType(Token.Type.RBRACKET, "missing ']' at the end of the list literal")
|
cc.skipTokenOfType(Token.Type.RBRACKET, "missing ']' at the end of the list literal")
|
||||||
operand = Accessor({ cxt ->
|
operand = Accessor({ cxt ->
|
||||||
val i = (index.execute(cxt) as? ObjInt)?.value?.toInt()
|
val i = index.execute(cxt)
|
||||||
?: cxt.raiseError("index must be integer")
|
|
||||||
val x = left.getter(cxt).value
|
val x = left.getter(cxt).value
|
||||||
if( x == ObjNull && isOptional) ObjNull.asReadonly
|
if( x == ObjNull && isOptional) ObjNull.asReadonly
|
||||||
else x.getAt(cxt, i).asMutable
|
else x.getAt(cxt, i).asMutable
|
||||||
@ -341,10 +340,10 @@ class Compiler(
|
|||||||
}
|
}
|
||||||
|
|
||||||
Token.Type.DOTDOT, Token.Type.DOTDOTLT -> {
|
Token.Type.DOTDOT, Token.Type.DOTDOTLT -> {
|
||||||
// closed-range operator
|
// range operator
|
||||||
val isEndInclusive = t.type == Token.Type.DOTDOT
|
val isEndInclusive = t.type == Token.Type.DOTDOT
|
||||||
val left = operand
|
val left = operand
|
||||||
val right = parseStatement(cc)
|
val right = parseExpression(cc)
|
||||||
operand = Accessor {
|
operand = Accessor {
|
||||||
ObjRange(
|
ObjRange(
|
||||||
left?.getter?.invoke(it)?.value ?: ObjNull,
|
left?.getter?.invoke(it)?.value ?: ObjNull,
|
||||||
@ -366,11 +365,15 @@ class Compiler(
|
|||||||
} ?: parseLambdaExpression(cc)
|
} ?: parseLambdaExpression(cc)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Token.Type.RBRACKET -> {
|
||||||
|
cc.previous()
|
||||||
|
return operand
|
||||||
|
}
|
||||||
|
|
||||||
else -> {
|
else -> {
|
||||||
cc.previous()
|
cc.previous()
|
||||||
operand?.let { return it }
|
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
|
var breakCaught = false
|
||||||
|
|
||||||
if (size > 0) {
|
if (size > 0) {
|
||||||
var current = runCatching { sourceObj.getAt(forContext, 0) }
|
var current = runCatching { sourceObj.getAt(forContext, ObjInt(0)) }
|
||||||
.getOrElse {
|
.getOrElse {
|
||||||
throw ScriptError(
|
throw ScriptError(
|
||||||
tOp.pos,
|
tOp.pos,
|
||||||
@ -1142,7 +1145,7 @@ class Compiler(
|
|||||||
}
|
}
|
||||||
} else result = body.execute(forContext)
|
} else result = body.execute(forContext)
|
||||||
if (++index >= size) break
|
if (++index >= size) break
|
||||||
current = sourceObj.getAt(forContext, index)
|
current = sourceObj.getAt(forContext, ObjInt(index.toLong()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!breakCaught && elseStatement != null) {
|
if (!breakCaught && elseStatement != null) {
|
||||||
|
@ -42,6 +42,9 @@ data class Accessor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
open class Obj {
|
open class Obj {
|
||||||
|
|
||||||
|
val isNull by lazy { this === ObjNull }
|
||||||
|
|
||||||
var isFrozen: Boolean = false
|
var isFrozen: Boolean = false
|
||||||
|
|
||||||
private val monitor = Mutex()
|
private val monitor = Mutex()
|
||||||
@ -176,6 +179,13 @@ open class Obj {
|
|||||||
context.raiseNotImplemented()
|
context.raiseNotImplemented()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert Lyng object to its Kotlin counterpart
|
||||||
|
*/
|
||||||
|
open suspend fun toKotlin(context: Context): Any? {
|
||||||
|
return toString()
|
||||||
|
}
|
||||||
|
|
||||||
fun willMutate(context: Context) {
|
fun willMutate(context: Context) {
|
||||||
if (isFrozen) context.raiseError("attempt to mutate frozen object")
|
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")
|
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")
|
context.raiseNotImplemented("indexing")
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -297,7 +307,7 @@ object ObjNull : Obj() {
|
|||||||
context.raiseNPE()
|
context.raiseNPE()
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun getAt(context: Context, index: Int): Obj {
|
override suspend fun getAt(context: Context, index: Obj): Obj {
|
||||||
context.raiseNPE()
|
context.raiseNPE()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -310,6 +320,10 @@ object ObjNull : Obj() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun toString(): String = "null"
|
override fun toString(): String = "null"
|
||||||
|
|
||||||
|
override suspend fun toKotlin(context: Context): Any? {
|
||||||
|
return null
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Numeric {
|
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 logicalOr(context: Context, other: Obj): Obj = ObjBool(value || other.toBool())
|
||||||
|
|
||||||
|
override suspend fun toKotlin(context: Context): Any {
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
val type = ObjClass("Bool")
|
val type = ObjClass("Bool")
|
||||||
}
|
}
|
||||||
|
@ -162,7 +162,7 @@ val ObjArray by lazy {
|
|||||||
addFn("contains", isOpen = true) {
|
addFn("contains", isOpen = true) {
|
||||||
val obj = args.firstAndOnly()
|
val obj = args.firstAndOnly()
|
||||||
for( i in 0..< thisObj.invokeInstanceMethod(this, "size").toInt()) {
|
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
|
ObjFalse
|
||||||
}
|
}
|
||||||
|
@ -72,6 +72,10 @@ data class ObjInt(var value: Long) : Obj(), Numeric {
|
|||||||
} else null
|
} else null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override suspend fun toKotlin(context: Context): Any {
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
val type = ObjClass("Int")
|
val type = ObjClass("Int")
|
||||||
}
|
}
|
||||||
|
@ -11,15 +11,15 @@ class ObjList(val list: MutableList<Obj> = mutableListOf()) : Obj() {
|
|||||||
list.joinToString(separator = ", ") { it.inspect() }
|
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
|
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}")
|
if (i !in list.indices) context.raiseError("index $index out of bounds for size ${list.size}")
|
||||||
return i
|
return i
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun getAt(context: Context, index: Int): Obj {
|
override suspend fun getAt(context: Context, index: Obj): Obj {
|
||||||
val i = normalize(context, index)
|
val i = normalize(context, index.toInt())
|
||||||
return list[i]
|
return list[i]
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -82,6 +82,10 @@ class ObjList(val list: MutableList<Obj> = mutableListOf()) : Obj() {
|
|||||||
override val objClass: ObjClass
|
override val objClass: ObjClass
|
||||||
get() = type
|
get() = type
|
||||||
|
|
||||||
|
override suspend fun toKotlin(context: Context): Any {
|
||||||
|
return list.map { it.toKotlin(context) }
|
||||||
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
val type = ObjClass("List", ObjArray).apply {
|
val type = ObjClass("List", ObjArray).apply {
|
||||||
|
|
||||||
@ -92,7 +96,7 @@ class ObjList(val list: MutableList<Obj> = mutableListOf()) : Obj() {
|
|||||||
)
|
)
|
||||||
addFn("getAt") {
|
addFn("getAt") {
|
||||||
requireExactCount(1)
|
requireExactCount(1)
|
||||||
thisAs<ObjList>().getAt(this, requiredArg<ObjInt>(0).value.toInt())
|
thisAs<ObjList>().getAt(this, requiredArg<Obj>(0))
|
||||||
}
|
}
|
||||||
addFn("putAt") {
|
addFn("putAt") {
|
||||||
requireExactCount(2)
|
requireExactCount(2)
|
||||||
@ -113,7 +117,7 @@ class ObjList(val list: MutableList<Obj> = mutableListOf()) : Obj() {
|
|||||||
val l = thisAs<ObjList>()
|
val l = thisAs<ObjList>()
|
||||||
var index = l.normalize(
|
var index = l.normalize(
|
||||||
this, requiredArg<ObjInt>(0).value.toInt(),
|
this, requiredArg<ObjInt>(0).value.toInt(),
|
||||||
allowisEndInclusive = true
|
allowsEndInclusive = true
|
||||||
)
|
)
|
||||||
for (i in 1..<args.size) l.list.add(index++, args[i])
|
for (i in 1..<args.size) l.list.add(index++, args[i])
|
||||||
ObjVoid
|
ObjVoid
|
||||||
|
@ -36,6 +36,13 @@ data class ObjReal(val value: Double) : Obj(), Numeric {
|
|||||||
override suspend fun mod(context: Context, other: Obj): Obj =
|
override suspend fun mod(context: Context, other: Obj): Obj =
|
||||||
ObjReal(this.value % other.toDouble())
|
ObjReal(this.value % other.toDouble())
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns unboxed Double value
|
||||||
|
*/
|
||||||
|
override suspend fun toKotlin(context: Context): Any {
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
val type: ObjClass = ObjClass("Real").apply {
|
val type: ObjClass = ObjClass("Real").apply {
|
||||||
createField(
|
createField(
|
||||||
|
@ -2,11 +2,20 @@ package net.sergeych.lyng
|
|||||||
|
|
||||||
import kotlinx.serialization.SerialName
|
import kotlinx.serialization.SerialName
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
|
import net.sergeych.sprintf.sprintf
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
@SerialName("string")
|
@SerialName("string")
|
||||||
data class ObjString(val value: String) : Obj() {
|
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 {
|
override suspend fun compareTo(context: Context, other: Obj): Int {
|
||||||
if (other !is ObjString) return -2
|
if (other !is ObjString) return -2
|
||||||
return this.value.compareTo(other.value)
|
return this.value.compareTo(other.value)
|
||||||
@ -27,8 +36,21 @@ data class ObjString(val value: String) : Obj() {
|
|||||||
return ObjString(value + other.asStr.value)
|
return ObjString(value + other.asStr.value)
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun getAt(context: Context, index: Int): Obj {
|
override suspend fun getAt(context: Context, index: Obj): Obj {
|
||||||
return ObjChar(value[index])
|
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 {
|
override suspend fun contains(context: Context, other: Obj): Boolean {
|
||||||
@ -41,10 +63,12 @@ data class ObjString(val value: String) : Obj() {
|
|||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
val type = ObjClass("String").apply {
|
val type = ObjClass("String").apply {
|
||||||
addConst("startsWith",
|
addFn("startsWith") {
|
||||||
statement {
|
ObjBool(thisAs<ObjString>().value.startsWith(requiredArg<ObjString>(0).value))
|
||||||
ObjBool(thisAs<ObjString>().value.startsWith(requiredArg<ObjString>(0).value))
|
}
|
||||||
})
|
addFn("endsWith") {
|
||||||
|
ObjBool(thisAs<ObjString>().value.endsWith(requiredArg<ObjString>(0).value))
|
||||||
|
}
|
||||||
addConst("length",
|
addConst("length",
|
||||||
statement { ObjInt(thisAs<ObjString>().value.length.toLong()) }
|
statement { ObjInt(thisAs<ObjString>().value.length.toLong()) }
|
||||||
)
|
)
|
||||||
|
@ -1056,7 +1056,7 @@ class ScriptTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun testIntOpenRangeInclusive() = runTest {
|
fun testIntClosedRangeInclusive() = runTest {
|
||||||
eval(
|
eval(
|
||||||
"""
|
"""
|
||||||
val r = 10 .. 20
|
val r = 10 .. 20
|
||||||
@ -1090,7 +1090,7 @@ class ScriptTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun testIntOpenRangeExclusive() = runTest {
|
fun testIntClosedRangeExclusive() = runTest {
|
||||||
eval(
|
eval(
|
||||||
"""
|
"""
|
||||||
val r = 10 ..< 20
|
val r = 10 ..< 20
|
||||||
@ -1126,7 +1126,7 @@ class ScriptTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun testIntOpenRangeInExclusive() = runTest {
|
fun testIntClosedRangeInExclusive() = runTest {
|
||||||
eval(
|
eval(
|
||||||
"""
|
"""
|
||||||
assert( (1..3) !in (1..<3) )
|
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
|
@Test
|
||||||
fun testCharacterRange() = runTest {
|
fun testCharacterRange() = runTest {
|
||||||
eval(
|
eval(
|
||||||
@ -2133,4 +2166,23 @@ class ScriptTest {
|
|||||||
assert(s.length == 2)
|
assert(s.length == 2)
|
||||||
""".trimIndent())
|
""".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