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 -> {
|
Token.Type.INT, Token.Type.REAL, Token.Type.HEX -> {
|
||||||
cc.previous()
|
cc.previous()
|
||||||
val n = parseNumber(true, cc)
|
val n = parseNumber(true, cc)
|
||||||
Accessor{ n.asReadonly }
|
Accessor{
|
||||||
|
n.asReadonly
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Token.Type.STRING -> Accessor { ObjString(t.value).asReadonly }
|
Token.Type.STRING -> Accessor { ObjString(t.value).asReadonly }
|
||||||
@ -494,6 +496,8 @@ class Compiler(
|
|||||||
var result: Obj = ObjVoid
|
var result: Obj = ObjVoid
|
||||||
while (condition.execute(it).toBool()) {
|
while (condition.execute(it).toBool()) {
|
||||||
try {
|
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)
|
result = body.execute(it)
|
||||||
} catch (lbe: LoopBreakContinueException) {
|
} catch (lbe: LoopBreakContinueException) {
|
||||||
if (lbe.label == label || lbe.label == null) {
|
if (lbe.label == label || lbe.label == null) {
|
||||||
@ -711,6 +715,7 @@ class Compiler(
|
|||||||
if (nameToken.type != Token.Type.ID)
|
if (nameToken.type != Token.Type.ID)
|
||||||
throw ScriptError(nameToken.pos, "Expected identifier after '$kind'")
|
throw ScriptError(nameToken.pos, "Expected identifier after '$kind'")
|
||||||
val name = nameToken.value
|
val name = nameToken.value
|
||||||
|
|
||||||
val eqToken = tokens.next()
|
val eqToken = tokens.next()
|
||||||
var setNull = false
|
var setNull = false
|
||||||
if (eqToken.type != Token.Type.ASSIGN) {
|
if (eqToken.type != Token.Type.ASSIGN) {
|
||||||
@ -721,12 +726,18 @@ class Compiler(
|
|||||||
setNull = true
|
setNull = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val initialExpression = if (setNull) null else parseStatement(tokens)
|
val initialExpression = if (setNull) null else parseStatement(tokens)
|
||||||
?: throw ScriptError(eqToken.pos, "Expected initializer expression")
|
?: throw ScriptError(eqToken.pos, "Expected initializer expression")
|
||||||
|
|
||||||
return statement(nameToken.pos) { context ->
|
return statement(nameToken.pos) { context ->
|
||||||
if (context.containsLocal(name))
|
if (context.containsLocal(name))
|
||||||
throw ScriptError(nameToken.pos, "Variable $name is already defined")
|
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)
|
context.addItem(name, mutable, initValue)
|
||||||
ObjVoid
|
ObjVoid
|
||||||
}
|
}
|
||||||
@ -843,6 +854,12 @@ class Compiler(
|
|||||||
Operator.simple(Token.Type.PERCENT, lastPrty) { ctx, a, b -> a.mod(ctx, b) },
|
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 lastLevel = lastPrty + 1
|
||||||
|
|
||||||
val byLevel: List<Map<Token.Type, Operator>> = (0..<lastLevel).map { l ->
|
val byLevel: List<Map<Token.Type, Operator>> = (0..<lastLevel).map { l ->
|
||||||
|
@ -57,13 +57,8 @@ class Context(
|
|||||||
val newFn = object : Statement() {
|
val newFn = object : Statement() {
|
||||||
override val pos: Pos = Pos.builtIn
|
override val pos: Pos = Pos.builtIn
|
||||||
|
|
||||||
override suspend fun execute(context: Context): Obj {
|
override suspend fun execute(context: Context): Obj = context.fn()
|
||||||
return try {
|
|
||||||
context.fn()
|
|
||||||
} catch (e: Exception) {
|
|
||||||
raise(e.message ?: "unexpected error")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
for (name in names) {
|
for (name in names) {
|
||||||
addItem(
|
addItem(
|
||||||
|
@ -31,6 +31,13 @@ sealed class Obj {
|
|||||||
// private val memberMutex = Mutex()
|
// private val memberMutex = Mutex()
|
||||||
private val parentInstances = listOf<Obj>()
|
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.
|
* 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 toObjInt: ObjInt by lazy { ObjInt(longValue) }
|
||||||
override val toObjReal: ObjReal by lazy { ObjReal(value) }
|
override val toObjReal: ObjReal by lazy { ObjReal(value) }
|
||||||
|
|
||||||
|
override fun byValueCopy(): Obj = ObjReal(value)
|
||||||
|
|
||||||
override suspend fun compareTo(context: Context, other: Obj): Int {
|
override suspend fun compareTo(context: Context, other: Obj): Int {
|
||||||
if (other !is Numeric) context.raiseError("cannot compare $this with $other")
|
if (other !is Numeric) context.raiseError("cannot compare $this with $other")
|
||||||
return value.compareTo(other.doubleValue)
|
return value.compareTo(other.doubleValue)
|
||||||
@ -316,6 +325,8 @@ data class ObjInt(var value: Long) : Obj(), Numeric {
|
|||||||
override val toObjInt get() = this
|
override val toObjInt get() = this
|
||||||
override val toObjReal = ObjReal(doubleValue)
|
override val toObjReal = ObjReal(doubleValue)
|
||||||
|
|
||||||
|
override fun byValueCopy(): Obj = ObjInt(value)
|
||||||
|
|
||||||
override suspend fun getAndIncrement(context: Context): Obj {
|
override suspend fun getAndIncrement(context: Context): Obj {
|
||||||
return ObjInt(value).also { value++ }
|
return ObjInt(value).also { value++ }
|
||||||
}
|
}
|
||||||
@ -368,6 +379,17 @@ data class ObjInt(var value: Long) : Obj(), Numeric {
|
|||||||
ObjInt(this.value % other.value)
|
ObjInt(this.value % other.value)
|
||||||
else ObjReal(this.value.toDouble() % other.toDouble())
|
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 {
|
companion object {
|
||||||
val type = ObjClass("Int")
|
val type = ObjClass("Int")
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,7 @@ package net.sergeych.ling
|
|||||||
|
|
||||||
data class Pos(val source: Source, val line: Int, val column: Int) {
|
data class Pos(val source: Source, val line: Int, val column: Int) {
|
||||||
override fun toString(): String {
|
override fun toString(): String {
|
||||||
return "${source.fileName}:$line:$column"
|
return "${source.fileName}:${line+1}:${column}"
|
||||||
}
|
}
|
||||||
|
|
||||||
fun back(): Pos =
|
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
|
@Test
|
||||||
fun whileNonLocalBreakTest() = runTest {
|
fun whileNonLocalBreakTest() = runTest {
|
||||||
assertEquals(
|
assertEquals(
|
||||||
@ -459,14 +515,18 @@ class ScriptTest {
|
|||||||
var t1 = 10
|
var t1 = 10
|
||||||
outer@ while( t1 > 0 ) {
|
outer@ while( t1 > 0 ) {
|
||||||
var t2 = 10
|
var t2 = 10
|
||||||
|
println("starting t2 = " + t2)
|
||||||
while( t2 > 0 ) {
|
while( t2 > 0 ) {
|
||||||
t2 = t2 - 1
|
t2 = t2 - 1
|
||||||
println("t2 " + t2 + " t1 " + t1)
|
println("t2 " + t2 + " t1 " + t1)
|
||||||
if( t2 == 3 && t1 == 7) {
|
if( t2 == 3 && t1 == 7) {
|
||||||
|
println("will break")
|
||||||
break@outer "ok2:"+t2+":"+t1
|
break@outer "ok2:"+t2+":"+t1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
println("next t1")
|
||||||
t1 = t1 - 1
|
t1 = t1 - 1
|
||||||
|
println("t1 now "+t1)
|
||||||
t1
|
t1
|
||||||
}
|
}
|
||||||
""".trimIndent()
|
""".trimIndent()
|
||||||
@ -607,12 +667,15 @@ class ScriptTest {
|
|||||||
fun testAssign1() = runTest {
|
fun testAssign1() = runTest {
|
||||||
assertEquals(10, eval("var x = 5; x=10; x").toInt())
|
assertEquals(10, eval("var x = 5; x=10; x").toInt())
|
||||||
val ctx = Context()
|
val ctx = Context()
|
||||||
ctx.eval("""
|
ctx.eval(
|
||||||
|
"""
|
||||||
var a = 1
|
var a = 1
|
||||||
""".trimIndent())
|
var b = 1
|
||||||
|
""".trimIndent()
|
||||||
|
)
|
||||||
assertEquals(3, ctx.eval("a + a + 1").toInt())
|
assertEquals(3, ctx.eval("a + a + 1").toInt())
|
||||||
assertEquals(12, ctx.eval("a + (a = 10) + 1").toInt())
|
assertEquals(12, ctx.eval("a + (b = 10) + 1").toInt())
|
||||||
assertEquals(10, ctx.eval("a").toInt())
|
assertEquals(10, ctx.eval("b").toInt())
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
Loading…
x
Reference in New Issue
Block a user