fixed problem with var initialization with val

added support for by-value types and in-place assignment
This commit is contained in:
Sergey Chernov 2025-05-29 11:48:04 +04:00
parent ca93d73b9c
commit f881faf89f
5 changed files with 111 additions and 14 deletions

View File

@ -389,7 +389,9 @@ class Compiler(
Token.Type.INT, Token.Type.REAL, Token.Type.HEX -> {
cc.previous()
val n = parseNumber(true, cc)
Accessor{ n.asReadonly }
Accessor{
n.asReadonly
}
}
Token.Type.STRING -> Accessor { ObjString(t.value).asReadonly }
@ -494,6 +496,8 @@ class Compiler(
var result: Obj = ObjVoid
while (condition.execute(it).toBool()) {
try {
// we don't need to create new context here: if body is a block,
// parse block will do it, otherwise single statement doesn't need it:
result = body.execute(it)
} catch (lbe: LoopBreakContinueException) {
if (lbe.label == label || lbe.label == null) {
@ -711,6 +715,7 @@ class Compiler(
if (nameToken.type != Token.Type.ID)
throw ScriptError(nameToken.pos, "Expected identifier after '$kind'")
val name = nameToken.value
val eqToken = tokens.next()
var setNull = false
if (eqToken.type != Token.Type.ASSIGN) {
@ -721,12 +726,18 @@ class Compiler(
setNull = true
}
}
val initialExpression = if (setNull) null else parseStatement(tokens)
?: throw ScriptError(eqToken.pos, "Expected initializer expression")
return statement(nameToken.pos) { context ->
if (context.containsLocal(name))
throw ScriptError(nameToken.pos, "Variable $name is already defined")
val initValue = initialExpression?.execute(context) ?: ObjNull
// init value could be a val; when we init by-value type var with it, we need to
// create a separate copy:
val initValue = initialExpression?.execute(context)?.byValueCopy() ?: ObjNull
context.addItem(name, mutable, initValue)
ObjVoid
}
@ -843,6 +854,12 @@ class Compiler(
Operator.simple(Token.Type.PERCENT, lastPrty) { ctx, a, b -> a.mod(ctx, b) },
)
// private val assigner = allOps.first { it.tokenType == Token.Type.ASSIGN }
//
// fun performAssignment(context: Context, left: Accessor, right: Accessor) {
// assigner.generate(context.pos, left, right)
// }
val lastLevel = lastPrty + 1
val byLevel: List<Map<Token.Type, Operator>> = (0..<lastLevel).map { l ->

View File

@ -57,13 +57,8 @@ class Context(
val newFn = object : Statement() {
override val pos: Pos = Pos.builtIn
override suspend fun execute(context: Context): Obj {
return try {
context.fn()
} catch (e: Exception) {
raise(e.message ?: "unexpected error")
}
}
override suspend fun execute(context: Context): Obj = context.fn()
}
for (name in names) {
addItem(

View File

@ -31,6 +31,13 @@ sealed class Obj {
// private val memberMutex = Mutex()
private val parentInstances = listOf<Obj>()
/**
* Some objects are by-value, historically [ObjInt] and [ObjReal] are usually treated as such.
* When initializing a var with it, by value objects must be copied. By-reference ones aren't.
*
* Almost all objects are by-reference.
*/
open fun byValueCopy(): Obj = this
/**
* Get instance member traversing the hierarchy if needed. Its meaning is different for different objects.
@ -273,6 +280,8 @@ data class ObjReal(val value: Double) : Obj(), Numeric {
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) context.raiseError("cannot compare $this with $other")
return value.compareTo(other.doubleValue)
@ -316,6 +325,8 @@ data class ObjInt(var value: Long) : Obj(), Numeric {
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++ }
}
@ -368,6 +379,17 @@ data class ObjInt(var value: Long) : Obj(), Numeric {
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")
}

View File

@ -2,7 +2,7 @@ package net.sergeych.ling
data class Pos(val source: Source, val line: Int, val column: Int) {
override fun toString(): String {
return "${source.fileName}:$line:$column"
return "${source.fileName}:${line+1}:${column}"
}
fun back(): Pos =

View File

@ -451,6 +451,62 @@ class ScriptTest {
)
}
@Test
fun testWhileBlockIsolation1() = runTest {
eval(
"""
var x = 100
var cnt = 2
while( cnt-- > 0 ) {
var x = cnt + 1
assert(x == cnt + 1)
}
assert( x == 100 )
assert( cnt == -1 )
""".trimIndent()
)
}
@Test
fun testWhileBlockIsolation2() = runTest {
assertFails {
eval(
"""
var cnt = 2
while( cnt-- > 0 ) {
var inner = cnt + 1
assert(inner == cnt + 1)
}
println("inner "+inner)
""".trimIndent()
)
}
}
@Test
fun testWhileBlockIsolation3() = runTest {
eval("""
var outer = 7
var sum = 0
var cnt1 = 0
val initialForCnt2 = 0
while( ++cnt1 < 3 ) {
var cnt2 = initialForCnt2
assert(cnt2 == 0)
assert(outer == 7)
while(++cnt2 < 5) {
assert(initialForCnt2 == 0)
var outer = 1
sum = sum + outer
}
}
println("sum "+sum)
""".trimIndent()
)
}
@Test
fun whileNonLocalBreakTest() = runTest {
assertEquals(
@ -459,14 +515,18 @@ class ScriptTest {
var t1 = 10
outer@ while( t1 > 0 ) {
var t2 = 10
println("starting t2 = " + t2)
while( t2 > 0 ) {
t2 = t2 - 1
println("t2 " + t2 + " t1 " + t1)
if( t2 == 3 && t1 == 7) {
println("will break")
break@outer "ok2:"+t2+":"+t1
}
}
println("next t1")
t1 = t1 - 1
println("t1 now "+t1)
t1
}
""".trimIndent()
@ -607,12 +667,15 @@ class ScriptTest {
fun testAssign1() = runTest {
assertEquals(10, eval("var x = 5; x=10; x").toInt())
val ctx = Context()
ctx.eval("""
ctx.eval(
"""
var a = 1
""".trimIndent())
var b = 1
""".trimIndent()
)
assertEquals(3, ctx.eval("a + a + 1").toInt())
assertEquals(12, ctx.eval("a + (a = 10) + 1").toInt())
assertEquals(10, ctx.eval("a").toInt())
assertEquals(12, ctx.eval("a + (b = 10) + 1").toInt())
assertEquals(10, ctx.eval("b").toInt())
}
@Test