fixed problem with var initialization with val
added support for by-value types and in-place assignment
This commit is contained in:
parent
ca93d73b9c
commit
f881faf89f
@ -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 ->
|
||||
|
@ -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(
|
||||
|
@ -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")
|
||||
}
|
||||
|
@ -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 =
|
||||
|
@ -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
|
||||
|
Loading…
x
Reference in New Issue
Block a user