Remove interpreter paths and enforce bytecode-only execution

This commit is contained in:
Sergey Chernov 2026-02-14 01:20:13 +03:00
parent e73fe0d3c5
commit 34678068ac
45 changed files with 434 additions and 1878 deletions

View File

@ -23,6 +23,8 @@ package net.sergeych.lyng.io.fs
import net.sergeych.lyng.ModuleScope
import net.sergeych.lyng.Scope
import net.sergeych.lyng.ScopeFacade
import net.sergeych.lyng.requireScope
import net.sergeych.lyng.miniast.*
import net.sergeych.lyng.obj.*
import net.sergeych.lyng.pacman.ImportManager
@ -437,7 +439,7 @@ private suspend fun buildFsModule(module: ModuleScope, policy: FsAccessPolicy) {
moduleName = module.packageName
) {
fsGuard {
val chunkIt = thisObj.invokeInstanceMethod(this, "readUtf8Chunks")
val chunkIt = thisObj.invokeInstanceMethod(requireScope(), "readUtf8Chunks")
ObjFsLinesIterator(chunkIt)
}
}
@ -463,7 +465,7 @@ private suspend fun buildFsModule(module: ModuleScope, policy: FsAccessPolicy) {
// --- Helper classes and utilities ---
private fun parsePathArg(scope: Scope, self: ObjPath, arg: Obj): LyngPath {
private fun parsePathArg(scope: ScopeFacade, self: ObjPath, arg: Obj): LyngPath {
return when (arg) {
is ObjString -> arg.value.toPath()
is ObjPath -> arg.path
@ -472,11 +474,11 @@ private fun parsePathArg(scope: Scope, self: ObjPath, arg: Obj): LyngPath {
}
// Map Fs access denials to Lyng runtime exceptions for script-friendly errors
private suspend inline fun Scope.fsGuard(crossinline block: suspend () -> Obj): Obj {
private suspend inline fun ScopeFacade.fsGuard(crossinline block: suspend () -> Obj): Obj {
return try {
block()
} catch (e: AccessDeniedException) {
raiseError(ObjIllegalOperationException(this, e.reasonDetail ?: "access denied"))
raiseError(ObjIllegalOperationException(requireScope(), e.reasonDetail ?: "access denied"))
}
}
@ -668,16 +670,17 @@ class ObjFsLinesIterator(
}
}
private suspend fun ensureBufferFilled(scope: Scope) {
private suspend fun ensureBufferFilled(scope: ScopeFacade) {
if (buffer.contains('\n') || exhausted) return
val actualScope = scope.requireScope()
// Pull next chunk from the underlying iterator
val it = chunksIterator.invokeInstanceMethod(scope, "iterator")
val hasNext = it.invokeInstanceMethod(scope, "hasNext").toBool()
val it = chunksIterator.invokeInstanceMethod(actualScope, "iterator")
val hasNext = it.invokeInstanceMethod(actualScope, "hasNext").toBool()
if (!hasNext) {
exhausted = true
return
}
val next = it.invokeInstanceMethod(scope, "next")
val next = it.invokeInstanceMethod(actualScope, "next")
buffer += next.toString()
}
}

View File

@ -20,6 +20,8 @@ package net.sergeych.lyng.io.process
import kotlinx.coroutines.flow.Flow
import net.sergeych.lyng.ModuleScope
import net.sergeych.lyng.Scope
import net.sergeych.lyng.ScopeFacade
import net.sergeych.lyng.requireScope
import net.sergeych.lyng.miniast.*
import net.sergeych.lyng.obj.*
import net.sergeych.lyng.pacman.ImportManager
@ -204,20 +206,21 @@ class ObjRunningProcess(
override fun toString(): String = "RunningProcess($process)"
}
private suspend inline fun Scope.processGuard(crossinline block: suspend () -> Obj): Obj {
private suspend inline fun ScopeFacade.processGuard(crossinline block: suspend () -> Obj): Obj {
return try {
block()
} catch (e: ProcessAccessDeniedException) {
raiseError(ObjIllegalOperationException(this, e.reasonDetail ?: "process access denied"))
raiseError(ObjIllegalOperationException(requireScope(), e.reasonDetail ?: "process access denied"))
} catch (e: Exception) {
raiseError(ObjIllegalOperationException(this, e.message ?: "process error"))
raiseError(ObjIllegalOperationException(requireScope(), e.message ?: "process error"))
}
}
private fun Flow<String>.toLyngFlow(flowScope: Scope): ObjFlow {
val producer = ObjNativeCallable {
val builder = (this as? net.sergeych.lyng.BytecodeClosureScope)?.callScope?.thisObj as? ObjFlowBuilder
?: this.thisObj as? ObjFlowBuilder
private fun Flow<String>.toLyngFlow(flowScope: ScopeFacade): ObjFlow {
val producer = net.sergeych.lyng.obj.ObjExternCallable.fromBridge {
val scope = requireScope()
val builder = (scope as? net.sergeych.lyng.BytecodeClosureScope)?.callScope?.thisObj as? ObjFlowBuilder
?: scope.thisObj as? ObjFlowBuilder
this@toLyngFlow.collect {
try {
@ -229,5 +232,5 @@ private fun Flow<String>.toLyngFlow(flowScope: Scope): ObjFlow {
}
ObjVoid
}
return ObjFlow(producer, flowScope)
return ObjFlow(producer, flowScope.requireScope())
}

View File

@ -28,7 +28,7 @@ class BlockStatement(
override val pos: Pos = startPos
override suspend fun execute(scope: Scope): Obj {
return interpreterDisabled(scope, "block statement")
return bytecodeOnly(scope, "block statement")
}
fun statements(): List<Statement> = block.debugStatements()

View File

@ -24,7 +24,7 @@ class ClassInstanceInitDeclStatement(
override val pos: Pos,
) : Statement() {
override suspend fun execute(scope: Scope): Obj {
return interpreterDisabled(scope, "class instance init declaration")
return bytecodeOnly(scope, "class instance init declaration")
}
}
@ -42,7 +42,7 @@ class ClassInstanceFieldDeclStatement(
override val pos: Pos,
) : Statement() {
override suspend fun execute(scope: Scope): Obj {
return interpreterDisabled(scope, "class instance field declaration")
return bytecodeOnly(scope, "class instance field declaration")
}
}
@ -61,7 +61,7 @@ class ClassInstancePropertyDeclStatement(
override val pos: Pos,
) : Statement() {
override suspend fun execute(scope: Scope): Obj {
return interpreterDisabled(scope, "class instance property declaration")
return bytecodeOnly(scope, "class instance property declaration")
}
}
@ -79,6 +79,6 @@ class ClassInstanceDelegatedDeclStatement(
override val pos: Pos,
) : Statement() {
override suspend fun execute(scope: Scope): Obj {
return interpreterDisabled(scope, "class instance delegated declaration")
return bytecodeOnly(scope, "class instance delegated declaration")
}
}

View File

@ -23,7 +23,7 @@ import net.sergeych.lyng.obj.ObjRecord
/**
* Bytecode-oriented closure scope that keeps the call scope parent chain for stack traces
* while carrying the lexical closure for `this` variants and module resolution.
* Unlike interpreter closure scopes, it does not override name lookup.
* Unlike legacy closure scopes, it does not override name lookup.
*/
class BytecodeClosureScope(
val callScope: Scope,

View File

@ -32,6 +32,6 @@ class DelegatedVarDeclStatement(
override val pos: Pos = startPos
override suspend fun execute(context: Scope): Obj {
return interpreterDisabled(context, "delegated var declaration")
return bytecodeOnly(context, "delegated var declaration")
}
}

View File

@ -29,6 +29,6 @@ class DestructuringVarDeclStatement(
override val pos: Pos,
) : Statement() {
override suspend fun execute(context: Scope): Obj {
return interpreterDisabled(context, "destructuring declaration")
return bytecodeOnly(context, "destructuring declaration")
}
}

View File

@ -28,7 +28,7 @@ class EnumDeclStatement(
override val pos: Pos = startPos
override suspend fun execute(scope: Scope): Obj {
return interpreterDisabled(scope, "enum declaration")
return bytecodeOnly(scope, "enum declaration")
}
override suspend fun callOn(scope: Scope): Obj {

View File

@ -29,6 +29,6 @@ class ExtensionPropertyDeclStatement(
override val pos: Pos = startPos
override suspend fun execute(context: Scope): Obj {
return interpreterDisabled(context, "extension property declaration")
return bytecodeOnly(context, "extension property declaration")
}
}

View File

@ -20,7 +20,7 @@ package net.sergeych.lyng
/**
* Tiny, size-bounded cache for compiled Regex patterns. Activated only when [PerfFlags.REGEX_CACHE] is true.
* This is a very simple FIFO-ish cache sufficient for micro-benchmarks and common repeated patterns.
* Not thread-safe by design; the interpreter typically runs scripts on confined executors.
* Not thread-safe by design; the runtime typically runs scripts on confined executors.
*/
object RegexCache {
private const val MAX = 64
@ -48,4 +48,4 @@ object RegexCache {
}
fun clear() = map.clear()
}
}

View File

@ -725,7 +725,7 @@ open class Scope(
return ns.objClass
}
inline fun addVoidFn(vararg names: String, crossinline fn: suspend Scope.() -> Unit) {
inline fun addVoidFn(vararg names: String, crossinline fn: suspend ScopeFacade.() -> Unit) {
addFn(*names) {
fn(this)
ObjVoid
@ -741,8 +741,8 @@ open class Scope(
return CmdDisassembler.disassemble(bytecode)
}
fun addFn(vararg names: String, callSignature: CallSignature? = null, fn: suspend Scope.() -> Obj) {
val newFn = net.sergeych.lyng.obj.ObjNativeCallable { fn() }
fun addFn(vararg names: String, callSignature: CallSignature? = null, fn: suspend ScopeFacade.() -> Obj) {
val newFn = net.sergeych.lyng.obj.ObjExternCallable.fromBridge { fn() }
for (name in names) {
addItem(
name,

View File

@ -18,6 +18,7 @@ package net.sergeych.lyng
import net.sergeych.lyng.obj.Obj
import net.sergeych.lyng.obj.ObjRecord
import net.sergeych.lyng.obj.ObjString
/**
* Limited facade for Kotlin bridge callables.
@ -31,11 +32,20 @@ interface ScopeFacade {
suspend fun resolve(rec: ObjRecord, name: String): Obj
suspend fun assign(rec: ObjRecord, name: String, newValue: Obj)
fun raiseError(message: String): Nothing
fun raiseError(obj: net.sergeych.lyng.obj.ObjException): Nothing
fun raiseClassCastError(message: String): Nothing
fun raiseIllegalArgument(message: String): Nothing
fun raiseNoSuchElement(message: String = "No such element"): Nothing
fun raiseSymbolNotFound(name: String): Nothing
fun raiseIllegalState(message: String = "Illegal argument error"): Nothing
fun raiseNotImplemented(what: String = "operation"): Nothing
suspend fun call(callee: Obj, args: Arguments = Arguments.EMPTY, newThisObj: Obj? = null): Obj
suspend fun toStringOf(obj: Obj, forInspect: Boolean = false): ObjString
suspend fun inspect(obj: Obj): String
fun trace(text: String = "")
}
internal class ScopeBridge(private val scope: Scope) : ScopeFacade {
internal class ScopeBridge(internal val scope: Scope) : ScopeFacade {
override val args: Arguments
get() = scope.args
override var pos: Pos
@ -48,6 +58,50 @@ internal class ScopeBridge(private val scope: Scope) : ScopeFacade {
override suspend fun resolve(rec: ObjRecord, name: String): Obj = scope.resolve(rec, name)
override suspend fun assign(rec: ObjRecord, name: String, newValue: Obj) = scope.assign(rec, name, newValue)
override fun raiseError(message: String): Nothing = scope.raiseError(message)
override fun raiseError(obj: net.sergeych.lyng.obj.ObjException): Nothing = scope.raiseError(obj)
override fun raiseClassCastError(message: String): Nothing = scope.raiseClassCastError(message)
override fun raiseIllegalArgument(message: String): Nothing = scope.raiseIllegalArgument(message)
override fun raiseNoSuchElement(message: String): Nothing = scope.raiseNoSuchElement(message)
override fun raiseSymbolNotFound(name: String): Nothing = scope.raiseSymbolNotFound(name)
override fun raiseIllegalState(message: String): Nothing = scope.raiseIllegalState(message)
override fun raiseNotImplemented(what: String): Nothing = scope.raiseNotImplemented(what)
override suspend fun call(callee: Obj, args: Arguments, newThisObj: Obj?): Obj {
return callee.callOn(scope.createChildScope(scope.pos, args = args, newThisObj = newThisObj))
}
override suspend fun toStringOf(obj: Obj, forInspect: Boolean): ObjString = obj.toString(scope, forInspect)
override suspend fun inspect(obj: Obj): String = obj.inspect(scope)
override fun trace(text: String) = scope.trace(text)
}
inline fun <reified T : Obj> ScopeFacade.requiredArg(index: Int): T {
if (args.list.size <= index) raiseError("Expected at least ${index + 1} argument, got ${args.list.size}")
return (args.list[index].byValueCopy() as? T)
?: raiseClassCastError("Expected type ${T::class.simpleName}, got ${args.list[index]::class.simpleName}")
}
inline fun <reified T : Obj> ScopeFacade.requireOnlyArg(): T {
if (args.list.size != 1) raiseError("Expected exactly 1 argument, got ${args.list.size}")
return requiredArg(0)
}
fun ScopeFacade.requireExactCount(count: Int) {
if (args.list.size != count) {
raiseError("Expected exactly $count arguments, got ${args.list.size}")
}
}
fun ScopeFacade.requireNoArgs() {
if (args.list.isNotEmpty()) {
raiseError("This function does not accept any arguments")
}
}
inline fun <reified T : Obj> ScopeFacade.thisAs(): T {
val obj = thisObj
return (obj as? T) ?: raiseClassCastError(
"Cannot cast ${obj.objClass.className} to ${T::class.simpleName}"
)
}
fun ScopeFacade.requireScope(): Scope =
(this as? ScopeBridge)?.scope ?: raiseIllegalState("ScopeFacade requires ScopeBridge")

View File

@ -90,7 +90,7 @@ class Script(
}
}
if (statements.isNotEmpty()) {
scope.raiseIllegalState("interpreter execution is not supported; missing module bytecode")
scope.raiseIllegalState("bytecode-only execution is required; missing module bytecode")
}
return ObjVoid
}
@ -212,15 +212,15 @@ class Script(
addConst("Unset", ObjUnset)
addFn("print") {
for ((i, a) in args.withIndex()) {
if (i > 0) print(' ' + a.toString(this).value)
else print(a.toString(this).value)
if (i > 0) print(' ' + toStringOf(a).value)
else print(toStringOf(a).value)
}
ObjVoid
}
addFn("println") {
for ((i, a) in args.withIndex()) {
if (i > 0) print(' ' + a.toString(this).value)
else print(a.toString(this).value)
if (i > 0) print(' ' + toStringOf(a).value)
else print(toStringOf(a).value)
}
println()
ObjVoid
@ -233,7 +233,7 @@ class Script(
} else {
Arguments.EMPTY
}
callee.callOn(createChildScope(pos, args = rest))
call(callee, rest)
}
addFn("floor") {
val x = args.firstAndOnly()
@ -331,12 +331,12 @@ class Script(
var result = value
if (range.start != null && !range.start.isNull) {
if (result.compareTo(this, range.start) < 0) {
if (result.compareTo(requireScope(), range.start) < 0) {
result = range.start
}
}
if (range.end != null && !range.end.isNull) {
val cmp = range.end.compareTo(this, result)
val cmp = range.end.compareTo(requireScope(), result)
if (range.isEndInclusive) {
if (cmp < 0) result = range.end
} else {
@ -359,20 +359,20 @@ class Script(
addVoidFn("assert") {
val cond = requiredArg<ObjBool>(0)
val message = if (args.size > 1)
": " + (args[1] as Obj).callOn(this).toString(this).value
": " + toStringOf(call(args[1] as Obj)).value
else ""
if (!cond.value == true)
raiseError(ObjAssertionFailedException(this, "Assertion failed$message"))
raiseError(ObjAssertionFailedException(requireScope(), "Assertion failed$message"))
}
addVoidFn("assertEquals") {
val a = requiredArg<Obj>(0)
val b = requiredArg<Obj>(1)
if (a.compareTo(this, b) != 0)
if (a.compareTo(requireScope(), b) != 0)
raiseError(
ObjAssertionFailedException(
this,
"Assertion failed: ${a.inspect(this)} == ${b.inspect(this)}"
requireScope(),
"Assertion failed: ${inspect(a)} == ${inspect(b)}"
)
)
}
@ -380,22 +380,22 @@ class Script(
addVoidFn("assertEqual") {
val a = requiredArg<Obj>(0)
val b = requiredArg<Obj>(1)
if (a.compareTo(this, b) != 0)
if (a.compareTo(requireScope(), b) != 0)
raiseError(
ObjAssertionFailedException(
this,
"Assertion failed: ${a.inspect(this)} == ${b.inspect(this)}"
requireScope(),
"Assertion failed: ${inspect(a)} == ${inspect(b)}"
)
)
}
addVoidFn("assertNotEquals") {
val a = requiredArg<Obj>(0)
val b = requiredArg<Obj>(1)
if (a.compareTo(this, b) == 0)
if (a.compareTo(requireScope(), b) == 0)
raiseError(
ObjAssertionFailedException(
this,
"Assertion failed: ${a.inspect(this)} != ${b.inspect(this)}"
requireScope(),
"Assertion failed: ${inspect(a)} != ${inspect(b)}"
)
)
}
@ -428,7 +428,7 @@ class Script(
else -> raiseIllegalArgument("Expected 1 or 2 arguments, got ${args.size}")
}
val result = try {
code.callOn(this)
call(code)
null
} catch (e: ExecutionError) {
e.errorObject
@ -437,7 +437,7 @@ class Script(
}
if (result == null) raiseError(
ObjAssertionFailedException(
this,
requireScope(),
"Expected exception but nothing was thrown"
)
)
@ -451,7 +451,7 @@ class Script(
}
addFn("dynamic", callSignature = CallSignature(tailBlockReceiverType = "DelegateContext")) {
ObjDynamic.create(this, requireOnlyArg())
ObjDynamic.create(requireScope(), requireOnlyArg())
}
val root = this
@ -468,7 +468,7 @@ class Script(
val condition = requiredArg<ObjBool>(0)
if (!condition.value) {
var message = args.list.getOrNull(1)
if (message is Obj && message.objClass == Statement.type) message = message.callOn(this)
if (message is Obj && message.objClass == Statement.type) message = call(message)
raiseIllegalArgument(message?.toString() ?: "requirement not met")
}
ObjVoid
@ -477,26 +477,26 @@ class Script(
val condition = requiredArg<ObjBool>(0)
if (!condition.value) {
var message = args.list.getOrNull(1)
if (message is Obj && message.objClass == Statement.type) message = message.callOn(this)
if (message is Obj && message.objClass == Statement.type) message = call(message)
raiseIllegalState(message?.toString() ?: "check failed")
}
ObjVoid
}
addFn("traceScope") {
this.trace(args.getOrNull(0)?.toString() ?: "")
trace(args.getOrNull(0)?.toString() ?: "")
ObjVoid
}
addFn("run") {
requireOnlyArg<Obj>().callOn(this)
call(requireOnlyArg())
}
addFn("cached") {
val builder = requireOnlyArg<Obj>()
val capturedScope = this
var calculated = false
var cachedValue: Obj = ObjVoid
ObjNativeCallable {
net.sergeych.lyng.obj.ObjExternCallable.fromBridge {
if (!calculated) {
cachedValue = builder.callOn(capturedScope)
cachedValue = capturedScope.call(builder)
calculated = true
}
cachedValue
@ -504,7 +504,7 @@ class Script(
}
addFn("lazy") {
val builder = requireOnlyArg<Obj>()
ObjLazyDelegate(builder, this)
ObjLazyDelegate(builder, requireScope())
}
addVoidFn("delay") {
val a = args.firstAndOnly()
@ -512,7 +512,7 @@ class Script(
is ObjInt -> delay(a.value)
is ObjReal -> delay((a.value * 1000).roundToLong())
is ObjDuration -> delay(a.duration)
else -> raiseIllegalArgument("Expected Int, Real or Duration, got ${a.inspect(this)}")
else -> raiseIllegalArgument("Expected Int, Real or Duration, got ${inspect(a)}")
}
}
@ -551,8 +551,9 @@ class Script(
addFn("launch") {
val callable = requireOnlyArg<Obj>()
val captured = this
ObjDeferred(globalDefer {
callable.callOn(this@addFn)
captured.call(callable)
})
}
@ -564,7 +565,7 @@ class Script(
addFn("flow", callSignature = CallSignature(tailBlockReceiverType = "FlowBuilder")) {
// important is: current context contains closure often used in call;
// we'll need it for the producer
ObjFlow(requireOnlyArg<Obj>(), this)
ObjFlow(requireOnlyArg<Obj>(), requireScope())
}
val pi = ObjReal(PI)
@ -639,7 +640,7 @@ class Script(
is ObjInt -> delay(a.value * 1000)
is ObjReal -> delay((a.value * 1000).roundToLong())
is ObjDuration -> delay(a.duration)
else -> raiseIllegalArgument("Expected Duration, Int or Real, got ${a.inspect(this)}")
else -> raiseIllegalArgument("Expected Duration, Int or Real, got ${inspect(a)}")
}
}
}

View File

@ -36,7 +36,7 @@ class TryStatement(
)
override suspend fun execute(scope: Scope): Obj {
return interpreterDisabled(scope, "try statement")
return bytecodeOnly(scope, "try statement")
}
private fun resolveExceptionClass(scope: Scope, name: String): ObjClass {

View File

@ -33,6 +33,6 @@ class VarDeclStatement(
override val pos: Pos = startPos
override suspend fun execute(context: Scope): Obj {
return interpreterDisabled(context, "var declaration")
return bytecodeOnly(context, "var declaration")
}
}

View File

@ -19,8 +19,8 @@ package net.sergeych.lyng
import net.sergeych.lyng.obj.Obj
sealed class WhenCondition(open val expr: Statement, open val pos: Pos) {
protected fun interpreterDisabled(scope: Scope): Nothing {
return scope.raiseIllegalState("interpreter execution is not supported; when condition requires bytecode")
protected fun bytecodeOnly(scope: Scope): Nothing {
return scope.raiseIllegalState("bytecode-only execution is required; when condition needs compiled bytecode")
}
abstract suspend fun matches(scope: Scope, value: Obj): Boolean
@ -31,7 +31,7 @@ class WhenEqualsCondition(
override val pos: Pos,
) : WhenCondition(expr, pos) {
override suspend fun matches(scope: Scope, value: Obj): Boolean {
return interpreterDisabled(scope)
return bytecodeOnly(scope)
}
}
@ -41,7 +41,7 @@ class WhenInCondition(
override val pos: Pos,
) : WhenCondition(expr, pos) {
override suspend fun matches(scope: Scope, value: Obj): Boolean {
return interpreterDisabled(scope)
return bytecodeOnly(scope)
}
}
@ -51,7 +51,7 @@ class WhenIsCondition(
override val pos: Pos,
) : WhenCondition(expr, pos) {
override suspend fun matches(scope: Scope, value: Obj): Boolean {
return interpreterDisabled(scope)
return bytecodeOnly(scope)
}
}
@ -64,6 +64,6 @@ class WhenStatement(
override val pos: Pos,
) : Statement() {
override suspend fun execute(scope: Scope): Obj {
return interpreterDisabled(scope, "when statement")
return bytecodeOnly(scope, "when statement")
}
}

View File

@ -1880,13 +1880,23 @@ private suspend fun assignDestructurePattern(frame: CmdFrame, pattern: ListLiter
private suspend fun assignDestructureTarget(frame: CmdFrame, ref: ObjRef, value: Obj, pos: Pos) {
when (ref) {
is ListLiteralRef -> assignDestructurePattern(frame, ref, value, pos)
is ListLiteralRef -> {
assignDestructurePattern(frame, ref, value, pos)
return
}
is LocalSlotRef -> {
val index = resolveLocalSlotIndex(frame.fn, ref.name, preferCapture = ref.captureOwnerScopeId != null)
if (index != null) {
frame.frame.setObj(index, value)
return
}
val scopeSlot = frame.fn.scopeSlotNames.indexOfFirst { it == ref.name }
if (scopeSlot >= 0) {
val target = frame.scopeTarget(scopeSlot)
val slotIndex = frame.ensureScopeSlot(target, scopeSlot)
target.setSlotValue(slotIndex, value)
return
}
}
is LocalVarRef -> {
val index = resolveLocalSlotIndex(frame.fn, ref.name, preferCapture = false)
@ -1894,6 +1904,13 @@ private suspend fun assignDestructureTarget(frame: CmdFrame, ref: ObjRef, value:
frame.frame.setObj(index, value)
return
}
val scopeSlot = frame.fn.scopeSlotNames.indexOfFirst { it == ref.name }
if (scopeSlot >= 0) {
val target = frame.scopeTarget(scopeSlot)
val slotIndex = frame.ensureScopeSlot(target, scopeSlot)
target.setSlotValue(slotIndex, value)
return
}
}
is FastLocalVarRef -> {
val index = resolveLocalSlotIndex(frame.fn, ref.name, preferCapture = false)
@ -1901,6 +1918,13 @@ private suspend fun assignDestructureTarget(frame: CmdFrame, ref: ObjRef, value:
frame.frame.setObj(index, value)
return
}
val scopeSlot = frame.fn.scopeSlotNames.indexOfFirst { it == ref.name }
if (scopeSlot >= 0) {
val target = frame.scopeTarget(scopeSlot)
val slotIndex = frame.ensureScopeSlot(target, scopeSlot)
target.setSlotValue(slotIndex, value)
return
}
}
else -> {}
}

View File

@ -19,6 +19,7 @@ package net.sergeych.lyng.miniast
import net.sergeych.lyng.ModuleScope
import net.sergeych.lyng.Scope
import net.sergeych.lyng.ScopeFacade
import net.sergeych.lyng.Visibility
import net.sergeych.lyng.obj.Obj
import net.sergeych.lyng.obj.ObjClass
@ -40,7 +41,7 @@ inline fun <reified T : Obj> Scope.addFnDoc(
tags: Map<String, List<String>> = emptyMap(),
moduleName: String? = null,
callSignature: net.sergeych.lyng.CallSignature? = null,
crossinline fn: suspend Scope.() -> T
crossinline fn: suspend ScopeFacade.() -> T
) {
// Register runtime function(s)
addFn(*names, callSignature = callSignature) { fn() }
@ -57,7 +58,7 @@ inline fun Scope.addVoidFnDoc(
doc: String,
tags: Map<String, List<String>> = emptyMap(),
moduleName: String? = null,
crossinline fn: suspend Scope.() -> Unit
crossinline fn: suspend ScopeFacade.() -> Unit
) {
addFnDoc<ObjVoid>(
*names,
@ -98,7 +99,7 @@ fun ObjClass.addFnDoc(
visibility: Visibility = Visibility.Public,
tags: Map<String, List<String>> = emptyMap(),
moduleName: String? = null,
code: suspend Scope.() -> Obj
code: suspend ScopeFacade.() -> Obj
) {
// Register runtime method
addFn(name, isOpen, visibility, code = code)
@ -136,7 +137,7 @@ fun ObjClass.addClassFnDoc(
isOpen: Boolean = false,
tags: Map<String, List<String>> = emptyMap(),
moduleName: String? = null,
code: suspend Scope.() -> Obj
code: suspend ScopeFacade.() -> Obj
) {
addClassFn(name, isOpen, code)
BuiltinDocRegistry.module(moduleName ?: ownerModuleNameFromClassOrUnknown()) {
@ -152,8 +153,8 @@ fun ObjClass.addPropertyDoc(
type: TypeDoc? = null,
visibility: Visibility = Visibility.Public,
moduleName: String? = null,
getter: (suspend Scope.() -> Obj)? = null,
setter: (suspend Scope.(Obj) -> Unit)? = null
getter: (suspend ScopeFacade.() -> Obj)? = null,
setter: (suspend ScopeFacade.(Obj) -> Unit)? = null
) {
addProperty(name, getter, setter, visibility)
BuiltinDocRegistry.module(moduleName ?: ownerModuleNameFromClassOrUnknown()) {

View File

@ -20,7 +20,6 @@ package net.sergeych.lyng.obj
import net.sergeych.lyng.Compiler
import net.sergeych.lyng.Pos
import net.sergeych.lyng.Scope
import net.sergeych.lyng.ScriptError
// avoid KDOC bug: keep it
@Suppress("unused")
@ -37,10 +36,11 @@ private class LambdaRef(
private val getterFn: suspend (Scope) -> ObjRecord,
private val setterFn: (suspend (Pos, Scope, Obj) -> Unit)? = null
) : ObjRef {
override suspend fun get(scope: Scope): ObjRecord = getterFn(scope)
override suspend fun get(scope: Scope): ObjRecord {
return scope.raiseIllegalState("bytecode-only execution is required; Accessor evaluation is disabled")
}
override suspend fun setAt(pos: Pos, scope: Scope, newValue: Obj) {
val s = setterFn ?: throw ScriptError(pos, "can't assign value")
s(pos, scope, newValue)
scope.raiseIllegalState("bytecode-only execution is required; Accessor assignment is disabled")
}
}
@ -57,4 +57,4 @@ val Accessor.getter: suspend (Scope) -> ObjRecord
fun Accessor.setter(pos: Pos): suspend (Scope, Obj) -> Unit = { scope, newValue ->
this.setAt(pos, scope, newValue)
}
}

View File

@ -562,10 +562,10 @@ open class Obj {
else -> del.objClass.getInstanceMemberOrNull("getValue")
}
if (getValueRec == null || getValueRec.declaringClass?.className == "Delegate") {
val wrapper = ObjNativeCallable {
val wrapper = ObjExternCallable.fromBridge {
val th2 = if (thisObj === ObjVoid) ObjNull else thisObj
val allArgs = (listOf(th2, ObjString(name)) + args.list).toTypedArray()
del.invokeInstanceMethod(this, "invoke", Arguments(*allArgs))
del.invokeInstanceMethod(requireScope(), "invoke", Arguments(*allArgs))
}
return obj.copy(
value = wrapper,
@ -740,7 +740,7 @@ open class Obj {
returns = type("lyng.String"),
moduleName = "lyng.stdlib"
) {
thisObj.toString(this, true)
toStringOf(thisObj, true)
}
addFnDoc(
name = "inspect",
@ -748,7 +748,7 @@ open class Obj {
returns = type("lyng.String"),
moduleName = "lyng.stdlib"
) {
thisObj.inspect(this).toObj()
inspect(thisObj).toObj()
}
addFnDoc(
name = "contains",
@ -757,7 +757,7 @@ open class Obj {
returns = type("lyng.Bool"),
moduleName = "lyng.stdlib"
) {
ObjBool(thisObj.contains(this, args.firstAndOnly()))
ObjBool(thisObj.contains(requireScope(), args.firstAndOnly()))
}
// utilities
addFnDoc(
@ -766,7 +766,7 @@ open class Obj {
params = listOf(ParamDoc("block")),
moduleName = "lyng.stdlib"
) {
args.firstAndOnly().callOn(createChildScope(Arguments(thisObj)))
call(args.firstAndOnly(), Arguments(thisObj))
}
addFnDoc(
name = "apply",
@ -775,11 +775,12 @@ open class Obj {
moduleName = "lyng.stdlib"
) {
val body = args.firstAndOnly()
val scope = requireScope()
(thisObj as? ObjInstance)?.let {
body.callOn(ApplyScope(this, it.instanceScope))
body.callOn(ApplyScope(scope, it.instanceScope))
} ?: run {
val appliedScope = createChildScope(newThisObj = thisObj)
body.callOn(ApplyScope(this, appliedScope))
val appliedScope = scope.createChildScope(newThisObj = thisObj)
body.callOn(ApplyScope(scope, appliedScope))
}
thisObj
}
@ -789,7 +790,7 @@ open class Obj {
params = listOf(ParamDoc("block")),
moduleName = "lyng.stdlib"
) {
args.firstAndOnly().callOn(createChildScope(Arguments(thisObj)))
call(args.firstAndOnly(), Arguments(thisObj))
thisObj
}
addFnDoc(
@ -798,16 +799,16 @@ open class Obj {
params = listOf(ParamDoc("block")),
moduleName = "lyng.stdlib"
) {
args.firstAndOnly().callOn(this)
call(args.firstAndOnly())
}
addFn("getAt") {
requireExactCount(1)
thisObj.getAt(this, requiredArg<Obj>(0))
thisObj.getAt(requireScope(), requiredArg<Obj>(0))
}
addFn("putAt") {
requireExactCount(2)
val newValue = args[1]
thisObj.putAt(this, requiredArg<Obj>(0), newValue)
thisObj.putAt(requireScope(), requiredArg<Obj>(0), newValue)
newValue
}
addFnDoc(
@ -816,7 +817,7 @@ open class Obj {
returns = type("lyng.String"),
moduleName = "lyng.stdlib"
) {
thisObj.toJson(this).toString().toObj()
thisObj.toJson(requireScope()).toString().toObj()
}
addFnDoc(
name = "toJsonString",
@ -824,7 +825,7 @@ open class Obj {
returns = type("lyng.String"),
moduleName = "lyng.stdlib"
) {
thisObj.toJson(this).toString().toObj()
thisObj.toJson(requireScope()).toString().toObj()
}
addFnDoc(
name = "clamp",
@ -836,12 +837,12 @@ open class Obj {
var result = thisObj
if (range.start != null && !range.start.isNull) {
if (result.compareTo(this, range.start) < 0) {
if (result.compareTo(requireScope(), range.start) < 0) {
result = range.start
}
}
if (range.end != null && !range.end.isNull) {
val cmp = range.end.compareTo(this, result)
val cmp = range.end.compareTo(requireScope(), result)
if (range.isEndInclusive) {
if (cmp < 0) result = range.end
} else {

View File

@ -32,7 +32,7 @@ val ObjArray by lazy {
doc = "Iterator over elements of this array using its indexer.",
returns = TypeGenericDoc(type("lyng.Iterator"), listOf(type("lyng.Any"))),
moduleName = "lyng.stdlib"
) { ObjArrayIterator(thisObj).also { it.init(this) } }
) { ObjArrayIterator(thisObj).also { it.init(requireScope()) } }
addFnDoc(
name = "contains",
@ -42,9 +42,10 @@ val ObjArray by lazy {
isOpen = true,
moduleName = "lyng.stdlib"
) {
val scope = requireScope()
val obj = args.firstAndOnly()
for (i in 0..<thisObj.invokeInstanceMethod(this, "size").toInt()) {
if (thisObj.getAt(this, ObjInt(i.toLong())).compareTo(this, obj) == 0) return@addFnDoc ObjTrue
for (i in 0..<thisObj.invokeInstanceMethod(scope, "size").toInt()) {
if (thisObj.getAt(scope, ObjInt(i.toLong())).compareTo(scope, obj) == 0) return@addFnDoc ObjTrue
}
ObjFalse
}
@ -55,10 +56,11 @@ val ObjArray by lazy {
type = type("lyng.Any"),
moduleName = "lyng.stdlib",
getter = {
val scope = requireScope()
this.thisObj.invokeInstanceMethod(
this,
scope,
"getAt",
(this.thisObj.invokeInstanceMethod(this, "size").toInt() - 1).toObj()
(this.thisObj.invokeInstanceMethod(scope, "size").toInt() - 1).toObj()
)
}
)
@ -68,7 +70,10 @@ val ObjArray by lazy {
doc = "Index of the last element (size - 1).",
type = type("lyng.Int"),
moduleName = "lyng.stdlib",
getter = { (this.thisObj.invokeInstanceMethod(this, "size").toInt() - 1).toObj() }
getter = {
val scope = requireScope()
(this.thisObj.invokeInstanceMethod(scope, "size").toInt() - 1).toObj()
}
)
addPropertyDoc(
@ -76,7 +81,10 @@ val ObjArray by lazy {
doc = "Range of valid indices for this array.",
type = type("lyng.Range"),
moduleName = "lyng.stdlib",
getter = { ObjRange(0.toObj(), this.thisObj.invokeInstanceMethod(this, "size"), false) }
getter = {
val scope = requireScope()
ObjRange(0.toObj(), this.thisObj.invokeInstanceMethod(scope, "size"), false)
}
)
addFnDoc(
@ -86,15 +94,16 @@ val ObjArray by lazy {
returns = type("lyng.Int"),
moduleName = "lyng.stdlib"
) {
val scope = requireScope()
val target = args.firstAndOnly()
var low = 0
var high = thisObj.invokeInstanceMethod(this, "size").toInt() - 1
var high = thisObj.invokeInstanceMethod(scope, "size").toInt() - 1
while (low <= high) {
val mid = (low + high) / 2
val midVal = thisObj.getAt(this, ObjInt(mid.toLong()))
val midVal = thisObj.getAt(scope, ObjInt(mid.toLong()))
val cmp = midVal.compareTo(this, target)
val cmp = midVal.compareTo(scope, target)
when {
cmp == 0 -> return@addFnDoc (mid).toObj()
cmp > 0 -> high = mid - 1
@ -105,4 +114,4 @@ val ObjArray by lazy {
(-low - 1).toObj()
}
}
}
}

View File

@ -38,8 +38,8 @@ class ObjArrayIterator(val array: Obj) : Obj() {
addFn("next") {
val self = thisAs<ObjArrayIterator>()
if (self.nextIndex < self.lastIndex) {
self.array.invokeInstanceMethod(this, "getAt", (self.nextIndex++).toObj())
} else raiseError(ObjIterationFinishedException(this))
self.array.invokeInstanceMethod(requireScope(), "getAt", (self.nextIndex++).toObj())
} else raiseError(ObjIterationFinishedException(requireScope()))
}
addFn("hasNext") {
val self = thisAs<ObjArrayIterator>()
@ -48,4 +48,4 @@ class ObjArrayIterator(val array: Obj) : Obj() {
}
}
}
}
}

View File

@ -930,9 +930,9 @@ open class ObjClass(
isOverride: Boolean = false,
pos: Pos = Pos.builtIn,
methodId: Int? = null,
code: (suspend Scope.() -> Obj)? = null
code: (suspend net.sergeych.lyng.ScopeFacade.() -> Obj)? = null
) {
val stmt = code?.let { ObjNativeCallable { it() } } ?: ObjNull
val stmt = code?.let { ObjExternCallable.fromBridge { it() } } ?: ObjNull
createField(
name, stmt, isMutable, visibility, writeVisibility, pos, declaringClass,
isAbstract = isAbstract, isClosed = isClosed, isOverride = isOverride,
@ -945,8 +945,8 @@ open class ObjClass(
fun addProperty(
name: String,
getter: (suspend Scope.() -> Obj)? = null,
setter: (suspend Scope.(Obj) -> Unit)? = null,
getter: (suspend net.sergeych.lyng.ScopeFacade.() -> Obj)? = null,
setter: (suspend net.sergeych.lyng.ScopeFacade.(Obj) -> Unit)? = null,
visibility: Visibility = Visibility.Public,
writeVisibility: Visibility? = null,
declaringClass: ObjClass? = this,
@ -957,8 +957,8 @@ open class ObjClass(
prop: ObjProperty? = null,
methodId: Int? = null
) {
val g = getter?.let { ObjNativeCallable { it() } }
val s = setter?.let { ObjNativeCallable { it(requiredArg(0)); ObjVoid } }
val g = getter?.let { ObjExternCallable.fromBridge { it() } }
val s = setter?.let { ObjExternCallable.fromBridge { it(requiredArg(0)); ObjVoid } }
val finalProp = prop ?: if (isAbstract) ObjNull else ObjProperty(name, g, s)
createField(
name, finalProp, false, visibility, writeVisibility, pos, declaringClass,
@ -969,8 +969,8 @@ open class ObjClass(
}
fun addClassConst(name: String, value: Obj) = createClassField(name, value)
fun addClassFn(name: String, isOpen: Boolean = false, code: suspend Scope.() -> Obj) {
createClassField(name, ObjNativeCallable { code() }, isOpen, type = ObjRecord.Type.Fun)
fun addClassFn(name: String, isOpen: Boolean = false, code: suspend net.sergeych.lyng.ScopeFacade.() -> Obj) {
createClassField(name, ObjExternCallable.fromBridge { code() }, isOpen, type = ObjRecord.Type.Fun)
}

View File

@ -53,9 +53,8 @@ class ObjDateTime(val instant: Instant, val timeZone: TimeZone) : Obj() {
if (rec.type == ObjRecord.Type.Fun) {
val target = rec.value
return ObjRecord(
ObjNativeCallable {
val callScope = createChildScope(args = args, newThisObj = this@ObjDateTime)
target.callOn(callScope)
ObjExternCallable.fromBridge {
call(target, args, newThisObj = this@ObjDateTime)
},
rec.isMutable
)

View File

@ -139,7 +139,7 @@ open class ObjException(
class ExceptionClass(val name: String, vararg parents: ObjClass) : ObjClass(name, *parents) {
init {
constructorMeta = ArgsDeclaration(
listOf(ArgsDeclaration.Item("message", defaultValue = ObjNativeCallable { ObjString(name) })),
listOf(ArgsDeclaration.Item("message", defaultValue = ObjExternCallable.fromBridge { ObjString(name) })),
Token.Type.RPAREN
)
}
@ -177,17 +177,17 @@ open class ObjException(
}
val Root = ExceptionClass("Exception").apply {
instanceInitializers.add(ObjNativeCallable {
instanceInitializers.add(ObjExternCallable.fromBridge {
if (thisObj is ObjInstance) {
val msg = get("message")?.value ?: ObjString("Exception")
(thisObj as ObjInstance).instanceScope.addItem("Exception::message", false, msg)
val stack = captureStackTrace(this)
val stack = captureStackTrace(requireScope())
(thisObj as ObjInstance).instanceScope.addItem("Exception::stackTrace", false, stack)
}
ObjVoid
})
instanceConstructor = ObjNativeCallable { ObjVoid }
instanceConstructor = ObjExternCallable.fromBridge { ObjVoid }
addPropertyDoc(
name = "message",
doc = "Human‑readable error message.",
@ -244,7 +244,7 @@ open class ObjException(
is ObjInstance -> t.instanceScope.get("Exception::stackTrace")?.value as? ObjList ?: ObjList()
else -> ObjList()
}
val at = stack.list.firstOrNull()?.toString(this) ?: ObjString("(unknown)")
val at = stack.list.firstOrNull()?.let { toStringOf(it) } ?: ObjString("(unknown)")
ObjString("${thisObj.objClass.className}: $msg at $at")
}
}

View File

@ -106,8 +106,8 @@ class ObjFlow(val producer: Obj, val scope: Scope) : Obj() {
moduleName = "lyng.stdlib"
) {
val objFlow = thisAs<ObjFlow>()
ObjFlowIterator(ObjNativeCallable {
objFlow.producer.callOn(this)
ObjFlowIterator(ObjExternCallable.fromBridge {
call(objFlow.producer)
})
}
}
@ -164,7 +164,7 @@ class ObjFlowIterator(val producer: Obj) : Obj() {
doc = "Whether another element is available from the flow.",
returns = type("lyng.Bool"),
moduleName = "lyng.stdlib"
) { thisAs<ObjFlowIterator>().hasNext(this).toObj() }
) { thisAs<ObjFlowIterator>().hasNext(requireScope()).toObj() }
addFnDoc(
name = "next",
doc = "Receive the next element from the flow or throw if completed.",
@ -172,7 +172,7 @@ class ObjFlowIterator(val producer: Obj) : Obj() {
moduleName = "lyng.stdlib"
) {
val x = thisAs<ObjFlowIterator>()
x.next(this)
x.next(requireScope())
}
addFnDoc(
name = "cancelIteration",

View File

@ -177,10 +177,10 @@ class ObjInstance(override val objClass: ObjClass) : Obj() {
else -> del.objClass.getInstanceMemberOrNull("getValue")
}
if (getValueRec == null || getValueRec.declaringClass?.className == "Delegate") {
val wrapper = ObjNativeCallable {
val wrapper = ObjExternCallable.fromBridge {
val th2 = if (thisObj === ObjVoid) ObjNull else thisObj
val allArgs = (listOf(th2, ObjString(name)) + args.list).toTypedArray()
del.invokeInstanceMethod(this, "invoke", Arguments(*allArgs))
del.invokeInstanceMethod(requireScope(), "invoke", Arguments(*allArgs))
}
return obj.copy(value = wrapper, type = ObjRecord.Type.Other)
}

View File

@ -41,10 +41,11 @@ val ObjIterable by lazy {
returns = type("lyng.List"),
moduleName = "lyng.stdlib"
) {
val scope = requireScope()
val result = mutableListOf<Obj>()
val it = thisObj.invokeInstanceMethod(this, "iterator")
while (it.invokeInstanceMethod(this, "hasNext").toBool()) {
result.add(it.invokeInstanceMethod(this, "next"))
val it = thisObj.invokeInstanceMethod(scope, "iterator")
while (it.invokeInstanceMethod(scope, "hasNext").toBool()) {
result.add(it.invokeInstanceMethod(scope, "next"))
}
ObjList(result)
}
@ -58,10 +59,11 @@ val ObjIterable by lazy {
isOpen = true,
moduleName = "lyng.stdlib"
) {
val scope = requireScope()
val obj = args.firstAndOnly()
val it = thisObj.invokeInstanceMethod(this, "iterator")
while (it.invokeInstanceMethod(this, "hasNext").toBool()) {
if (obj.equals(this, it.invokeInstanceMethod(this, "next")))
val it = thisObj.invokeInstanceMethod(scope, "iterator")
while (it.invokeInstanceMethod(scope, "hasNext").toBool()) {
if (obj.equals(scope, it.invokeInstanceMethod(scope, "next")))
return@addFnDoc ObjTrue
}
ObjFalse
@ -75,11 +77,12 @@ val ObjIterable by lazy {
isOpen = true,
moduleName = "lyng.stdlib"
) {
val scope = requireScope()
val obj = args.firstAndOnly()
var index = 0
val it = thisObj.invokeInstanceMethod(this, "iterator")
while (it.invokeInstanceMethod(this, "hasNext").toBool()) {
if (obj.equals(this, it.invokeInstanceMethod(this, "next")))
val it = thisObj.invokeInstanceMethod(scope, "iterator")
while (it.invokeInstanceMethod(scope, "hasNext").toBool()) {
if (obj.equals(scope, it.invokeInstanceMethod(scope, "next")))
return@addFnDoc ObjInt(index.toLong())
index++
}
@ -96,9 +99,10 @@ val ObjIterable by lazy {
this.thisObj
else {
val result = mutableSetOf<Obj>()
val it = this.thisObj.invokeInstanceMethod(this, "iterator")
while (it.invokeInstanceMethod(this, "hasNext").toBool()) {
result.add(it.invokeInstanceMethod(this, "next"))
val scope = requireScope()
val it = this.thisObj.invokeInstanceMethod(scope, "iterator")
while (it.invokeInstanceMethod(scope, "hasNext").toBool()) {
result.add(it.invokeInstanceMethod(scope, "next"))
}
ObjSet(result)
}
@ -112,10 +116,11 @@ val ObjIterable by lazy {
moduleName = "lyng.stdlib",
getter = {
val result = mutableMapOf<Obj, Obj>()
this.thisObj.enumerate(this) { pair ->
val scope = requireScope()
this.thisObj.enumerate(scope) { pair ->
when (pair) {
is ObjMapEntry -> result[pair.key] = pair.value
else -> result[pair.getAt(this, 0)] = pair.getAt(this, 1)
else -> result[pair.getAt(scope, 0)] = pair.getAt(scope, 1)
}
true
}
@ -132,9 +137,8 @@ val ObjIterable by lazy {
) {
val association = requireOnlyArg<Obj>()
val result = ObjMap()
thisObj.toFlow(this).collect {
val callScope = createChildScope(args = Arguments(it))
result.map[association.callOn(callScope)] = it
thisObj.toFlow(requireScope()).collect {
result.map[call(association, Arguments(it))] = it
}
result
}
@ -146,11 +150,12 @@ val ObjIterable by lazy {
isOpen = true,
moduleName = "lyng.stdlib"
) {
val it = thisObj.invokeInstanceMethod(this, "iterator")
val scope = requireScope()
val it = thisObj.invokeInstanceMethod(scope, "iterator")
val fn = requiredArg<Obj>(0)
while (it.invokeInstanceMethod(this, "hasNext").toBool()) {
val x = it.invokeInstanceMethod(this, "next")
fn.callOn(this.createChildScope(Arguments(listOf(x))))
while (it.invokeInstanceMethod(scope, "hasNext").toBool()) {
val x = it.invokeInstanceMethod(scope, "next")
call(fn, Arguments(listOf(x)))
}
ObjVoid
}
@ -165,9 +170,8 @@ val ObjIterable by lazy {
) {
val fn = requiredArg<Obj>(0)
val result = mutableListOf<Obj>()
thisObj.toFlow(this).collect {
val callScope = createChildScope(args = Arguments(it))
result.add(fn.callOn(callScope))
thisObj.toFlow(requireScope()).collect {
result.add(call(fn, Arguments(it)))
}
ObjList(result)
}
@ -182,9 +186,8 @@ val ObjIterable by lazy {
) {
val fn = requiredArg<Obj>(0)
val result = mutableListOf<Obj>()
thisObj.toFlow(this).collect {
val callScope = createChildScope(args = Arguments(it))
val transformed = fn.callOn(callScope)
thisObj.toFlow(requireScope()).collect {
val transformed = call(fn, Arguments(it))
if( transformed != ObjNull) result.add(transformed)
}
ObjList(result)
@ -200,7 +203,7 @@ val ObjIterable by lazy {
var n = requireOnlyArg<ObjInt>().value.toInt()
val result = mutableListOf<Obj>()
if (n > 0) {
thisObj.enumerate(this) {
thisObj.enumerate(requireScope()) {
result.add(it)
--n > 0
}
@ -215,8 +218,8 @@ val ObjIterable by lazy {
moduleName = "lyng.stdlib",
getter = {
ObjBool(
this.thisObj.invokeInstanceMethod(this, "iterator")
.invokeInstanceMethod(this, "hasNext").toBool()
this.thisObj.invokeInstanceMethod(requireScope(), "iterator")
.invokeInstanceMethod(requireScope(), "hasNext").toBool()
.not()
)
}
@ -229,11 +232,10 @@ val ObjIterable by lazy {
returns = type("lyng.List"),
moduleName = "lyng.stdlib"
) {
val list = thisObj.callMethod<ObjList>(this, "toList")
val list = thisObj.callMethod<ObjList>(requireScope(), "toList")
val comparator = requireOnlyArg<Obj>()
list.quicksort { a, b ->
val callScope = createChildScope(args = Arguments(a, b))
comparator.callOn(callScope).toInt()
call(comparator, Arguments(a, b)).toInt()
}
list
}
@ -244,7 +246,7 @@ val ObjIterable by lazy {
returns = type("lyng.List"),
moduleName = "lyng.stdlib"
) {
val list = thisObj.callMethod<ObjList>(this, "toList")
val list = thisObj.callMethod<ObjList>(requireScope(), "toList")
list.list.reverse()
list
}

View File

@ -69,13 +69,12 @@ val ObjIterator by lazy {
) {
val out = mutableListOf<Obj>()
while (true) {
val has = thisObj.invokeInstanceMethod(this, "hasNext").toBool()
val has = thisObj.invokeInstanceMethod(requireScope(), "hasNext").toBool()
if (!has) break
val v = thisObj.invokeInstanceMethod(this, "next")
val v = thisObj.invokeInstanceMethod(requireScope(), "next")
out += v
}
ObjList(out.toMutableList())
}
}
}

View File

@ -373,8 +373,7 @@ class ObjList(val list: MutableList<Obj> = mutableListOf()) : Obj() {
) {
val comparator = requireOnlyArg<Obj>()
thisAs<ObjList>().quicksort { a, b ->
val callScope = createChildScope(args = Arguments(a, b))
comparator.callOn(callScope).toInt()
call(comparator, Arguments(a, b)).toInt()
}
ObjVoid
}
@ -394,6 +393,7 @@ class ObjList(val list: MutableList<Obj> = mutableListOf()) : Obj() {
val self = thisAs<ObjList>()
val l = self.list
if (l.isEmpty()) return@addFnDoc ObjNull
val scope = requireScope()
if (net.sergeych.lyng.PerfFlags.PRIMITIVE_FASTOPS) {
// Fast path: all ints → accumulate as long
var i = 0
@ -407,7 +407,7 @@ class ObjList(val list: MutableList<Obj> = mutableListOf()) : Obj() {
// Fallback to generic dynamic '+' accumulation starting from current acc
var res: Obj = ObjInt(acc)
while (i < l.size) {
res = res.plus(this, l[i])
res = res.plus(scope, l[i])
i++
}
return@addFnDoc res
@ -419,7 +419,7 @@ class ObjList(val list: MutableList<Obj> = mutableListOf()) : Obj() {
var res: Obj = l[0]
var k = 1
while (k < l.size) {
res = res.plus(this, l[k])
res = res.plus(scope, l[k])
k++
}
res
@ -431,6 +431,7 @@ class ObjList(val list: MutableList<Obj> = mutableListOf()) : Obj() {
) {
val l = thisAs<ObjList>().list
if (l.isEmpty()) return@addFnDoc ObjNull
val scope = requireScope()
if (net.sergeych.lyng.PerfFlags.PRIMITIVE_FASTOPS) {
var i = 0
var hasOnlyInts = true
@ -451,7 +452,7 @@ class ObjList(val list: MutableList<Obj> = mutableListOf()) : Obj() {
var i = 1
while (i < l.size) {
val v = l[i]
if (v.compareTo(this, res) < 0) res = v
if (v.compareTo(scope, res) < 0) res = v
i++
}
res
@ -463,6 +464,7 @@ class ObjList(val list: MutableList<Obj> = mutableListOf()) : Obj() {
) {
val l = thisAs<ObjList>().list
if (l.isEmpty()) return@addFnDoc ObjNull
val scope = requireScope()
if (net.sergeych.lyng.PerfFlags.PRIMITIVE_FASTOPS) {
var i = 0
var hasOnlyInts = true
@ -483,7 +485,7 @@ class ObjList(val list: MutableList<Obj> = mutableListOf()) : Obj() {
var i = 1
while (i < l.size) {
val v = l[i]
if (v.compareTo(this, res) > 0) res = v
if (v.compareTo(scope, res) > 0) res = v
i++
}
res
@ -497,6 +499,7 @@ class ObjList(val list: MutableList<Obj> = mutableListOf()) : Obj() {
) {
val l = thisAs<ObjList>().list
val needle = args.firstAndOnly()
val scope = requireScope()
if (net.sergeych.lyng.PerfFlags.PRIMITIVE_FASTOPS && needle is ObjInt) {
var i = 0
while (i < l.size) {
@ -508,7 +511,7 @@ class ObjList(val list: MutableList<Obj> = mutableListOf()) : Obj() {
}
var i = 0
while (i < l.size) {
if (l[i].compareTo(this, needle) == 0) return@addFnDoc ObjInt(i.toLong())
if (l[i].compareTo(scope, needle) == 0) return@addFnDoc ObjInt(i.toLong())
i++
}
ObjInt((-1).toLong())

View File

@ -261,7 +261,7 @@ class ObjMap(val map: MutableMap<Obj, Obj> = mutableMapOf()) : Obj() {
val key = requiredArg<Obj>(0)
thisAs<ObjMap>().map.getOrPut(key) {
val lambda = requiredArg<Obj>(1)
lambda.callOn(this)
call(lambda)
}
}
addPropertyDoc(

View File

@ -44,7 +44,7 @@ class ObjMutex(val mutex: Mutex): Obj() {
// Execute user lambda directly in the current scope to preserve the active scope
// ancestry across suspension points. The lambda still constructs a closure scope
// on top of this frame, and parseLambdaExpression sets skipScopeCreation for its body.
thisAs<ObjMutex>().mutex.withLock { f.callOn(this) }
thisAs<ObjMutex>().mutex.withLock { call(f) }
}
}
}

View File

@ -1,33 +0,0 @@
/*
* Copyright 2026 Sergey S. Chernov
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package net.sergeych.lyng.obj
import net.sergeych.lyng.Scope
import net.sergeych.lyng.Statement
class ObjNativeCallable(
private val fn: suspend Scope.() -> Obj
) : Obj() {
override val objClass: ObjClass
get() = Statement.type
override suspend fun callOn(scope: Scope): Obj = scope.fn()
override fun toString(): String = "NativeCallable@${hashCode()}"
}

View File

@ -270,7 +270,7 @@ class ObjRange(
moduleName = "lyng.stdlib"
) {
val self = thisAs<ObjRange>()
self.buildIterator(this)
self.buildIterator(requireScope())
}
}
}

View File

@ -65,10 +65,10 @@ class ObjRangeIterator(
companion object {
val type = ObjClass("RangeIterator", ObjIterator).apply {
addFn("hasNext") {
thisAs<ObjRangeIterator>().hasNext(this).toObj()
thisAs<ObjRangeIterator>().hasNext(requireScope()).toObj()
}
addFn("next") {
thisAs<ObjRangeIterator>().next(this)
thisAs<ObjRangeIterator>().next(requireScope())
}
}
}
@ -98,7 +98,7 @@ class ObjFastIntRangeIterator(private val start: Int, private val endExclusive:
companion object {
val type = ObjClass("FastIntRangeIterator", ObjIterator).apply {
addFn("hasNext") { thisAs<ObjFastIntRangeIterator>().hasNext().toObj() }
addFn("next") { thisAs<ObjFastIntRangeIterator>().next(this) }
addFn("next") { thisAs<ObjFastIntRangeIterator>().next(requireScope()) }
}
}
}

View File

@ -125,7 +125,7 @@ data class ObjReal(val value: Double) : Obj(), Numeric {
// roundToInt: number rounded to the nearest integer
addConstDoc(
name = "roundToInt",
value = ObjNativeCallable {
value = ObjExternCallable.fromBridge {
(thisObj as ObjReal).value.roundToLong().toObj()
},
doc = "This real number rounded to the nearest integer.",

File diff suppressed because it is too large Load Diff

View File

@ -21,6 +21,7 @@ import net.sergeych.lyng.PerfFlags
import net.sergeych.lyng.Pos
import net.sergeych.lyng.RegexCache
import net.sergeych.lyng.Scope
import net.sergeych.lyng.requireScope
import net.sergeych.lyng.miniast.*
class ObjRegex(val regex: Regex) : Obj() {
@ -75,9 +76,10 @@ class ObjRegex(val regex: Regex) : Obj() {
}
createField(
name = "operatorMatch",
initialValue = ObjNativeCallable {
initialValue = ObjExternCallable.fromBridge {
val other = args.firstAndOnly(Pos.builtIn)
val targetScope = parent ?: this
val scope = requireScope()
val targetScope = scope.parent ?: scope
(thisObj as ObjRegex).operatorMatch(targetScope, other)
},
type = ObjRecord.Type.Fun

View File

@ -175,7 +175,7 @@ class ObjSet(val set: MutableSet<Obj> = mutableSetOf()) : Obj() {
returns = type("lyng.Set"),
moduleName = "lyng.stdlib"
) {
thisAs<ObjSet>().mul(this, args.firstAndOnly())
thisAs<ObjSet>().mul(requireScope(), args.firstAndOnly())
}
addFnDoc(
name = "iterator",
@ -192,7 +192,7 @@ class ObjSet(val set: MutableSet<Obj> = mutableSetOf()) : Obj() {
returns = type("lyng.Set"),
moduleName = "lyng.stdlib"
) {
thisAs<ObjSet>().plus(this, args.firstAndOnly())
thisAs<ObjSet>().plus(requireScope(), args.firstAndOnly())
}
addFnDoc(
name = "subtract",
@ -201,7 +201,7 @@ class ObjSet(val set: MutableSet<Obj> = mutableSetOf()) : Obj() {
returns = type("lyng.Set"),
moduleName = "lyng.stdlib"
) {
thisAs<ObjSet>().minus(this, args.firstAndOnly())
thisAs<ObjSet>().minus(requireScope(), args.firstAndOnly())
}
addFnDoc(
name = "remove",
@ -216,4 +216,4 @@ class ObjSet(val set: MutableSet<Obj> = mutableSetOf()) : Obj() {
}
}
}
}
}

View File

@ -25,6 +25,7 @@ import net.sergeych.lyng.PerfFlags
import net.sergeych.lyng.Pos
import net.sergeych.lyng.RegexCache
import net.sergeych.lyng.Scope
import net.sergeych.lyng.requireScope
import net.sergeych.lyng.miniast.*
import net.sergeych.lynon.LynonDecoder
import net.sergeych.lynon.LynonEncoder
@ -343,7 +344,7 @@ data class ObjString(val value: String) : Obj() {
name = "re",
initialValue = ObjProperty(
name = "re",
getter = ObjNativeCallable {
getter = ObjExternCallable.fromBridge {
val pattern = (thisObj as ObjString).value
val re = if (PerfFlags.REGEX_CACHE) RegexCache.get(pattern) else pattern.toRegex()
ObjRegex(re)
@ -354,9 +355,10 @@ data class ObjString(val value: String) : Obj() {
)
createField(
name = "operatorMatch",
initialValue = ObjNativeCallable {
initialValue = ObjExternCallable.fromBridge {
val other = args.firstAndOnly(Pos.builtIn)
val targetScope = parent ?: this
val scope = requireScope()
val targetScope = scope.parent ?: scope
(thisObj as ObjString).operatorMatch(targetScope, other)
},
type = ObjRecord.Type.Fun

View File

@ -0,0 +1,38 @@
/*
* Copyright 2026 Sergey S. Chernov
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.sergeych.lyng.obj
import net.sergeych.lyng.Scope
import net.sergeych.lyng.ScopeFacade
import net.sergeych.lyng.requireExactCount as coreRequireExactCount
import net.sergeych.lyng.requireNoArgs as coreRequireNoArgs
import net.sergeych.lyng.requireOnlyArg as coreRequireOnlyArg
import net.sergeych.lyng.requiredArg as coreRequiredArg
import net.sergeych.lyng.requireScope as coreRequireScope
import net.sergeych.lyng.thisAs as coreThisAs
inline fun <reified T : Obj> ScopeFacade.requiredArg(index: Int): T = coreRequiredArg(index)
inline fun <reified T : Obj> ScopeFacade.requireOnlyArg(): T = coreRequireOnlyArg()
fun ScopeFacade.requireExactCount(count: Int) = coreRequireExactCount(count)
fun ScopeFacade.requireNoArgs() = coreRequireNoArgs()
inline fun <reified T : Obj> ScopeFacade.thisAs(): T = coreThisAs()
internal fun ScopeFacade.requireScope(): Scope = coreRequireScope()

View File

@ -60,8 +60,8 @@ abstract class Statement(
suspend fun call(scope: Scope, vararg args: Obj) = execute(scope.createChildScope(args = Arguments(*args)))
protected fun interpreterDisabled(scope: Scope, label: String): Nothing {
return scope.raiseIllegalState("interpreter execution is not supported; $label requires bytecode")
protected fun bytecodeOnly(scope: Scope, label: String): Nothing {
return scope.raiseIllegalState("bytecode-only execution is required; $label needs compiled bytecode")
}
}
@ -73,7 +73,7 @@ class IfStatement(
override val pos: Pos,
) : Statement() {
override suspend fun execute(scope: Scope): Obj {
return interpreterDisabled(scope, "if statement")
return bytecodeOnly(scope, "if statement")
}
}
@ -92,7 +92,7 @@ class ForInStatement(
override val pos: Pos,
) : Statement() {
override suspend fun execute(scope: Scope): Obj {
return interpreterDisabled(scope, "for-in statement")
return bytecodeOnly(scope, "for-in statement")
}
}
@ -106,7 +106,7 @@ class WhileStatement(
override val pos: Pos,
) : Statement() {
override suspend fun execute(scope: Scope): Obj {
return interpreterDisabled(scope, "while statement")
return bytecodeOnly(scope, "while statement")
}
}
@ -119,7 +119,7 @@ class DoWhileStatement(
override val pos: Pos,
) : Statement() {
override suspend fun execute(scope: Scope): Obj {
return interpreterDisabled(scope, "do-while statement")
return bytecodeOnly(scope, "do-while statement")
}
}
@ -129,7 +129,7 @@ class BreakStatement(
override val pos: Pos,
) : Statement() {
override suspend fun execute(scope: Scope): Obj {
return interpreterDisabled(scope, "break statement")
return bytecodeOnly(scope, "break statement")
}
}
@ -138,7 +138,7 @@ class ContinueStatement(
override val pos: Pos,
) : Statement() {
override suspend fun execute(scope: Scope): Obj {
return interpreterDisabled(scope, "continue statement")
return bytecodeOnly(scope, "continue statement")
}
}
@ -148,7 +148,7 @@ class ReturnStatement(
override val pos: Pos,
) : Statement() {
override suspend fun execute(scope: Scope): Obj {
return interpreterDisabled(scope, "return statement")
return bytecodeOnly(scope, "return statement")
}
}
@ -157,7 +157,7 @@ class ThrowStatement(
override val pos: Pos,
) : Statement() {
override suspend fun execute(scope: Scope): Obj {
return interpreterDisabled(scope, "throw statement")
return bytecodeOnly(scope, "throw statement")
}
}
@ -165,7 +165,7 @@ class ExpressionStatement(
val ref: net.sergeych.lyng.obj.ObjRef,
override val pos: Pos
) : Statement() {
override suspend fun execute(scope: Scope): Obj = interpreterDisabled(scope, "expression statement")
override suspend fun execute(scope: Scope): Obj = bytecodeOnly(scope, "expression statement")
}
fun Statement.raise(text: String): Nothing {
@ -180,5 +180,5 @@ fun Statement.require(cond: Boolean, message: () -> String) {
object NopStatement: Statement(true, true, ObjType.Void) {
override val pos: Pos = Pos.builtIn
override suspend fun execute(scope: Scope): Obj = interpreterDisabled(scope, "nop statement")
override suspend fun execute(scope: Scope): Obj = bytecodeOnly(scope, "nop statement")
}

View File

@ -18,6 +18,8 @@
package net.sergeych.lynon
import net.sergeych.lyng.Scope
import net.sergeych.lyng.requireOnlyArg
import net.sergeych.lyng.requireScope
import net.sergeych.lyng.obj.*
// Most often used types:
@ -42,10 +44,10 @@ object ObjLynonClass : ObjClass("Lynon") {
init {
addClassConst("test", ObjString("test_const"))
addClassFn("encode") {
encodeAny(this, requireOnlyArg<Obj>())
encodeAny(requireScope(), requireOnlyArg<Obj>())
}
addClassFn("decode") {
decodeAny(this, requireOnlyArg<Obj>())
decodeAny(requireScope(), requireOnlyArg<Obj>())
}
}
}

View File

@ -30,6 +30,7 @@ import kotlinx.serialization.json.JsonPrimitive
import kotlinx.serialization.json.encodeToJsonElement
import net.sergeych.lyng.*
import net.sergeych.lyng.obj.*
import net.sergeych.lyng.thisAs
import net.sergeych.lyng.pacman.InlineSourcesImportProvider
import net.sergeych.mp_tools.globalDefer
import net.sergeych.tools.bm

View File

@ -178,11 +178,11 @@ suspend fun DocTest.test(_scope: Scope? = null) {
scope.apply {
addFn("println") {
if( bookMode ) {
println("${currentTest.fileNamePart}:${currentTest.line}> ${args.map{it.toString(this).value}.joinToString(" ")}")
println("${currentTest.fileNamePart}:${currentTest.line}> ${args.map{toStringOf(it).value}.joinToString(" ")}")
}
else {
for ((i, a) in args.withIndex()) {
if (i > 0) collectedOutput.append(' '); collectedOutput.append(a.toString(this).value)
if (i > 0) collectedOutput.append(' '); collectedOutput.append(toStringOf(a).value)
collectedOutput.append('\n')
}
}