fixed execution on JS and native platforms
This commit is contained in:
parent
28e8648794
commit
852383e3b1
@ -25,21 +25,41 @@ import net.sergeych.lyng.obj.ObjRecord
|
||||
* from [closureScope] with proper precedence
|
||||
*/
|
||||
class ClosureScope(val callScope: Scope, val closureScope: Scope) :
|
||||
Scope(callScope, callScope.args, thisObj = callScope.thisObj) {
|
||||
// Important: use closureScope.thisObj so unqualified members (e.g., fields) resolve to the instance
|
||||
// we captured, not to the caller's `this` (e.g., FlowBuilder).
|
||||
Scope(callScope, callScope.args, thisObj = closureScope.thisObj) {
|
||||
|
||||
override fun get(name: String): ObjRecord? {
|
||||
// we take arguments from the callerScope, the rest
|
||||
// from the closure.
|
||||
// Priority:
|
||||
// 1) Arguments from the caller scope (if present in this frame)
|
||||
// 2) Instance/class members of the captured receiver (`closureScope.thisObj`), e.g., fields like `coll`, `factor`
|
||||
// 3) Symbols from the captured closure scope (its locals and parents)
|
||||
// 4) Instance members of the caller's `this` (e.g., FlowBuilder.emit)
|
||||
// 5) Fallback to the standard chain (this frame -> parent (callScope) -> class members)
|
||||
|
||||
// note using super, not callScope, as arguments are assigned by the constructor
|
||||
// and are not assigned yet to vars in callScope self:
|
||||
super.objects[name]?.let {
|
||||
// if( name == "predicate" ) {
|
||||
// println("predicate: ${it.type.isArgument}: ${it.value}")
|
||||
// }
|
||||
if( it.type.isArgument ) return it
|
||||
}
|
||||
return closureScope.get(name)
|
||||
// and are not yet exposed via callScope.get at this point:
|
||||
super.objects[name]?.let { if (it.type.isArgument) return it }
|
||||
|
||||
// Prefer instance fields/methods declared on the captured receiver:
|
||||
// First, resolve real instance fields stored in the instance scope (constructor vars like `coll`, `factor`)
|
||||
(closureScope.thisObj as? net.sergeych.lyng.obj.ObjInstance)
|
||||
?.instanceScope
|
||||
?.objects
|
||||
?.get(name)
|
||||
?.let { return it }
|
||||
|
||||
// Then, try class-declared members (methods/properties declared in the class body)
|
||||
closureScope.thisObj.objClass.getInstanceMemberOrNull(name)?.let { return it }
|
||||
|
||||
// Then delegate to the full closure scope chain (locals, parents, etc.)
|
||||
closureScope.get(name)?.let { return it }
|
||||
|
||||
// Allow resolving instance members of the caller's `this` (e.g., FlowBuilder.emit)
|
||||
callScope.thisObj.objClass.getInstanceMemberOrNull(name)?.let { return it }
|
||||
|
||||
// Fallback to the standard lookup chain: this frame -> parent (callScope) -> class members
|
||||
return super.get(name)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -1432,22 +1432,25 @@ class Compiler(
|
||||
var wasBroken = false
|
||||
var result: Obj = ObjVoid
|
||||
lateinit var doScope: Scope
|
||||
do {
|
||||
while (true) {
|
||||
doScope = it.createChildScope().apply { skipScopeCreation = true }
|
||||
try {
|
||||
result = body.execute(doScope)
|
||||
} catch (e: LoopBreakContinueException) {
|
||||
if (e.label == label || e.label == null) {
|
||||
if (e.doContinue) continue
|
||||
else {
|
||||
if (!e.doContinue) {
|
||||
result = e.result
|
||||
wasBroken = true
|
||||
break
|
||||
}
|
||||
// for continue: just fall through to condition check below
|
||||
} else {
|
||||
// Not our label, let outer loops handle it
|
||||
throw e
|
||||
}
|
||||
throw e
|
||||
}
|
||||
} while (condition.execute(doScope).toBool())
|
||||
if (!condition.execute(doScope).toBool()) break
|
||||
}
|
||||
if (!wasBroken) elseStatement?.let { s -> result = s.execute(it) }
|
||||
result
|
||||
}
|
||||
|
||||
@ -218,8 +218,10 @@ class Script(
|
||||
ObjVoid
|
||||
}
|
||||
|
||||
// Delay in milliseconds (plain numeric). For time-aware variants use lyng.time.Duration API.
|
||||
addVoidFn("delay") {
|
||||
delay((this.args.firstAndOnly().toDouble()/1000.0).roundToLong())
|
||||
val ms = (this.args.firstAndOnly().toDouble()).roundToLong()
|
||||
delay(ms)
|
||||
}
|
||||
|
||||
addConst("Object", rootObjectType)
|
||||
|
||||
@ -51,19 +51,24 @@ open class ObjException(
|
||||
suspend fun getStackTrace(): ObjList {
|
||||
return cachedStackTrace.get {
|
||||
val result = ObjList()
|
||||
val cls = scope.get("StackTraceEntry")!!.value as ObjClass
|
||||
val maybeCls = scope.get("StackTraceEntry")?.value as? ObjClass
|
||||
var s: Scope? = scope
|
||||
var lastPos: Pos? = null
|
||||
while (s != null) {
|
||||
val pos = s.pos
|
||||
if (pos != lastPos && !pos.currentLine.isEmpty()) {
|
||||
result.list += cls.callWithArgs(
|
||||
scope,
|
||||
pos.source.objSourceName,
|
||||
ObjInt(pos.line.toLong()),
|
||||
ObjInt(pos.column.toLong()),
|
||||
ObjString(pos.currentLine)
|
||||
)
|
||||
if (maybeCls != null) {
|
||||
result.list += maybeCls.callWithArgs(
|
||||
scope,
|
||||
pos.source.objSourceName,
|
||||
ObjInt(pos.line.toLong()),
|
||||
ObjInt(pos.column.toLong()),
|
||||
ObjString(pos.currentLine)
|
||||
)
|
||||
} else {
|
||||
// Fallback textual entry if StackTraceEntry class is not available in this scope
|
||||
result.list += ObjString("${'$'}{pos.source.objSourceName}:${'$'}{pos.line}:${'$'}{pos.column}: ${'$'}{pos.currentLine}")
|
||||
}
|
||||
}
|
||||
s = s.parent
|
||||
lastPos = pos
|
||||
|
||||
@ -41,7 +41,19 @@ data class ObjReal(val value: Double) : Obj(), Numeric {
|
||||
return value.compareTo(other.doubleValue)
|
||||
}
|
||||
|
||||
override fun toString(): String = value.toString()
|
||||
override fun toString(): String {
|
||||
// Normalize scientific notation to match tests across platforms.
|
||||
// Kotlin/JVM prints 1e-6 as "1.0E-6" by default; tests accept "1E-6" (or a plain decimal).
|
||||
val s = value.toString()
|
||||
val ePos = s.indexOf('E').let { if (it >= 0) it else s.indexOf('e') }
|
||||
if (ePos >= 0) {
|
||||
val mantissa = s.substring(0, ePos)
|
||||
val exponent = s.substring(ePos + 1) // skip the 'E'/'e'
|
||||
val mantissaNorm = if (mantissa.endsWith(".0")) mantissa.dropLast(2) else mantissa
|
||||
return mantissaNorm + "E" + exponent
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
return value.hashCode()
|
||||
|
||||
@ -19,11 +19,13 @@ import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.toList
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import kotlinx.coroutines.withTimeout
|
||||
import net.sergeych.lyng.*
|
||||
import net.sergeych.lyng.obj.*
|
||||
import net.sergeych.lyng.pacman.InlineSourcesImportProvider
|
||||
import net.sergeych.tools.bm
|
||||
import kotlin.test.*
|
||||
import kotlin.time.Duration.Companion.seconds
|
||||
|
||||
class ScriptTest {
|
||||
|
||||
@ -1765,7 +1767,11 @@ class ScriptTest {
|
||||
|
||||
@Test
|
||||
fun testIntExponentRealForm() = runTest {
|
||||
assertEquals("1.0E-6", eval("1e-6").toString())
|
||||
when(val x = eval("1e-6").toString()) {
|
||||
"0.000001", "1E-6", "1e-6" -> true
|
||||
else -> fail("Excepted 1e-6 got $x")
|
||||
}
|
||||
// assertEquals("1.0E-6", eval("1e-6").toString())
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -2126,8 +2132,9 @@ class ScriptTest {
|
||||
|
||||
@Test
|
||||
fun doWhileValuesLabelTest() = runTest {
|
||||
eval(
|
||||
"""
|
||||
withTimeout(5.seconds) {
|
||||
eval(
|
||||
"""
|
||||
var count = 0
|
||||
var count2 = 0
|
||||
var count3 = 0
|
||||
@ -2148,7 +2155,8 @@ class ScriptTest {
|
||||
assertEquals("found 11/5", result)
|
||||
assertEquals( 4, count3)
|
||||
""".trimIndent()
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user