fix #53 chained calls and multiline expressions

This commit is contained in:
Sergey Chernov 2025-08-20 10:57:15 +03:00
parent cb333ab6bd
commit cad6ba936d
5 changed files with 104 additions and 8 deletions

View File

@ -138,6 +138,7 @@ publishing {
}
}
//mavenPublishing {
// publishToMavenCentral(SonatypeHost.CENTRAL_PORTAL)
//

View File

@ -211,11 +211,32 @@ class Compiler(
private suspend fun parseTerm(): Accessor? {
var operand: Accessor? = null
// newlines _before_
cc.skipWsTokens()
while (true) {
val t = cc.next()
val startPos = t.pos
when (t.type) {
Token.Type.NEWLINE, Token.Type.SEMICOLON, Token.Type.EOF, Token.Type.RBRACE, Token.Type.COMMA -> {
// Token.Type.NEWLINE, Token.Type.SINLGE_LINE_COMMENT, Token.Type.MULTILINE_COMMENT-> {
// continue
// }
// very special case chained calls: call()<NL>.call2 {}.call3()
Token.Type.NEWLINE -> {
val saved = cc.savePos()
if( cc.peekNextNonWhitespace().type == Token.Type.DOT) {
// chained call continue from it
continue
} else {
// restore position and stop parsing as a term:
cc.restorePos(saved)
cc.previous()
return operand
}
}
Token.Type.SEMICOLON, Token.Type.EOF, Token.Type.RBRACE, Token.Type.COMMA -> {
cc.previous()
return operand
}
@ -468,7 +489,13 @@ class Compiler(
// range operator
val isEndInclusive = t.type == Token.Type.DOTDOT
val left = operand
val right = parseExpression()
// if it is an open end range, then the end of line could be here that we do not want
// to skip in parseExpression:
val current = cc.current()
val right = if( current.type == Token.Type.NEWLINE || current.type == Token.Type.SINLGE_LINE_COMMENT)
null
else
parseExpression()
operand = Accessor {
ObjRange(
left?.getter?.invoke(it)?.value ?: ObjNull,
@ -955,7 +982,7 @@ class Compiler(
private suspend fun parseWhenStatement(): Statement {
// has a value, when(value) ?
var t = cc.skipWsTokens()
var t = cc.nextNonWhitespace()
return if (t.type == Token.Type.LPAREN) {
// when(value)
val value = parseStatement() ?: throw ScriptError(cc.currentPos(), "when(value) expected")
@ -978,7 +1005,7 @@ class Compiler(
// loop conditions
while (true) {
t = cc.skipWsTokens()
t = cc.nextNonWhitespace()
when (t.type) {
Token.Type.IN,
@ -1191,11 +1218,11 @@ class Compiler(
cc.skipTokenOfType(Token.Type.LBRACE)
do {
val t = cc.skipWsTokens()
val t = cc.nextNonWhitespace()
when (t.type) {
Token.Type.ID -> {
names += t.value
val t1 = cc.skipWsTokens()
val t1 = cc.nextNonWhitespace()
when (t1.type) {
Token.Type.COMMA ->
continue

View File

@ -39,7 +39,6 @@ class CompilerContext(val tokens: List<Token>) {
fun next() =
if (currentIndex < tokens.size) tokens[currentIndex++]
else Token("", tokens.last().pos, Token.Type.EOF)
// throw IllegalStateException("No more tokens")
fun previous() = if (!hasPrevious()) throw IllegalStateException("No previous token") else tokens[--currentIndex]
@ -131,6 +130,28 @@ class CompilerContext(val tokens: List<Token>) {
previous()
}
fun nextNonWhitespace(): Token {
while (true) {
val t = next()
if (t.type !in wstokens) return t
}
}
/**
* Find next non-whitespace token and return it. The token is not extracted,
* is will be returned on [next] call.
* @return next non-whitespace token without extracting it from tokens list
*/
fun peekNextNonWhitespace(): Token {
while (true) {
val t = next()
if (t.type !in wstokens) {
previous()
return t
}
}
}
inline fun ifNextIs(typeId: Token.Type, f: (Token) -> Unit): Boolean {
val t = next()
@ -207,7 +228,7 @@ class CompilerContext(val tokens: List<Token>) {
while (current().type in wstokens) {
next()
}
return next()
return current()
}
companion object {

View File

@ -436,6 +436,9 @@ object ObjNull : Obj() {
}
}
/**
* TODO: get rid of it. Maybe we ise some Lyng inheritance instead
*/
interface Numeric {
val longValue: Long
val doubleValue: Double

View File

@ -1184,6 +1184,35 @@ class ScriptTest {
)
}
@Test
fun testOpenEndRanges2() = runTest {
eval(
"""
var r = 5..; var r2 = 6..
val r3 = 7.. // open end
assert( r::class == Range)
assert( r.end == null)
assert( r.start == 5)
assert( r3::class == Range)
assertEquals( r3.end, null)
assert( r3.start == 7)
""".trimIndent()
)
}
@Test
fun testOpenEndRanges3() = runTest {
eval(
"""
val r3 = 7.. // open end with comment
assert( r3::class == Range)
assertEquals( r3.end, null)
assert( r3.start == 7)
""".trimIndent()
)
}
@Test
fun testCharacterRange() = runTest {
eval(
@ -2991,5 +3020,20 @@ class ScriptTest {
""".trimIndent())
}
@Test
fun testNewlinesAnsCommentsInExpressions() = runTest {
assertEquals( 2, (Scope().eval("""
val e = 1 + 4 -
3
""".trimIndent())).toInt())
eval("""
val x = [1,2,3]
.map { it * 10 }
.map { it + 1 }
assertEquals( [11,21,31], x)
""".trimIndent())
}
}