fix #80 edge case !isSomething() bug fixed

+String.last()
This commit is contained in:
Sergey Chernov 2025-12-06 15:07:38 +01:00
parent f616326383
commit 2e96d75b9f
4 changed files with 60 additions and 4 deletions

View File

@ -1385,6 +1385,7 @@ Typical set of String functions includes:
| trim() | trim space chars from both ends |
| startsWith(prefix) | true if starts with a prefix |
| endsWith(prefix) | true if ends with a prefix |
| last() | get last character of a string or throw |
| 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 |

View File

@ -213,25 +213,40 @@ private class Parser(fromPos: Pos) {
'!' -> {
if (currentChar == 'i') {
// Potentially !in / !is, but only if a word boundary follows
pos.advance()
when (currentChar) {
'n' -> {
pos.advance()
Token("!in", from, Token.Type.NOTIN)
// if next char continues an identifier, it's actually '!'+identifier starting with "in..."
if (idNextChars(currentChar)) {
// backtrack to right after '!'
pos.back()
pos.back()
Token("!", from, Token.Type.NOT)
} else
Token("!in", from, Token.Type.NOTIN)
}
's' -> {
pos.advance()
Token("!is", from, Token.Type.NOTIS)
// if next char continues an identifier, it's actually '!'+identifier starting with "is..."
if (idNextChars(currentChar)) {
// backtrack to right after '!'
pos.back()
pos.back()
Token("!", from, Token.Type.NOT)
} else
Token("!is", from, Token.Type.NOTIS)
}
else -> {
// it was just '!i' followed by something else; revert one step and return '!'
pos.back()
Token("!", from, Token.Type.NOT)
}
}
} else
if (currentChar == '=') {
} else if (currentChar == '=') {
pos.advance()
if (currentChar == '=') {
pos.advance()

View File

@ -173,6 +173,9 @@ data class ObjString(val value: String) : Obj() {
thisAs<ObjString>().value.map { ObjChar(it) }.toMutableList()
)
}
addFn("last") {
ObjChar(thisAs<ObjString>().value.lastOrNull() ?: raiseNoSuchElement("empty string"))
}
addFn("encodeUtf8") { ObjBuffer(thisAs<ObjString>().value.encodeToByteArray().asUByteArray()) }
addFn("size") { ObjInt(thisAs<ObjString>().value.length.toLong()) }
addFn("toReal") {

View File

@ -4012,4 +4012,41 @@ class ScriptTest {
""".trimIndent()).decodeSerializable<TestJson4>()
assertEquals( TestJson4(TestEnum.One), x)
}
@Test
fun testLogicalNot() = runTest {
eval("""
val vf = false
fun f() { false }
assert( !false )
assert( !vf )
assert( !f() )
val vt = true
fun ft() { true }
if( !true )
throw "impossible"
if( !ft() )
throw "impossible"
if( !vt )
throw "impossible"
// real world sample
fun isSignedByAdmin() {
// just ok
true
}
fun requireAdmin() {
// this caused compilation error:
if( !isSignedByAdmin() )
throw "Admin signature required"
}
""".trimIndent()
)
}
}