diff --git a/gradle.properties b/gradle.properties index 235d2dd..3fb3995 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,5 +1,5 @@ # -# Copyright 2025 Sergey S. Chernov real.sergeych@gmail.com +# Copyright 2026 Sergey S. Chernov real.sergeych@gmail.com # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -16,7 +16,7 @@ # #Gradle -org.gradle.jvmargs=-Xmx2048M -Dfile.encoding=UTF-8 -Dkotlin.daemon.jvm.options\="-Xmx2048M" +org.gradle.jvmargs=-Xmx4096M -Dfile.encoding=UTF-8 -Dkotlin.daemon.jvm.options\="-Xmx2048M" org.gradle.caching=true org.gradle.configuration-cache=true #Kotlin diff --git a/lyngio/src/commonMain/kotlin/net/sergeych/lyng/io/fs/LyngFsModule.kt b/lyngio/src/commonMain/kotlin/net/sergeych/lyng/io/fs/LyngFsModule.kt index 5c30fc2..8a77ff5 100644 --- a/lyngio/src/commonMain/kotlin/net/sergeych/lyng/io/fs/LyngFsModule.kt +++ b/lyngio/src/commonMain/kotlin/net/sergeych/lyng/io/fs/LyngFsModule.kt @@ -1,5 +1,5 @@ /* - * Copyright 2025 Sergey S. Chernov real.sergeych@gmail.com + * Copyright 2026 Sergey S. Chernov real.sergeych@gmail.com * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -23,9 +23,11 @@ package net.sergeych.lyng.io.fs import net.sergeych.lyng.ModuleScope import net.sergeych.lyng.Scope +import net.sergeych.lyng.ScopeCallable import net.sergeych.lyng.miniast.* import net.sergeych.lyng.obj.* import net.sergeych.lyng.pacman.ImportManager +import net.sergeych.lyng.pacman.ModuleBuilder import net.sergeych.lyngio.fs.LyngFS import net.sergeych.lyngio.fs.LyngFs import net.sergeych.lyngio.fs.LyngPath @@ -50,9 +52,11 @@ fun createFsModule(policy: FsAccessPolicy, manager: ImportManager): Boolean { // Avoid re-registering in this ImportManager if (manager.packageNames.contains(name)) return false - manager.addPackage(name) { module -> - buildFsModule(module, policy) - } + manager.addPackage(name, object : ModuleBuilder { + override suspend fun build(module: ModuleScope) { + buildFsModule(module, policy) + } + }) return true } @@ -78,322 +82,394 @@ private suspend fun buildFsModule(module: ModuleScope, policy: FsAccessPolicy) { name = "name", doc = "Base name of the path (last segment).", returns = type("lyng.String"), - moduleName = module.packageName - ) { - val self = thisAs() - self.path.name.toObj() - } + moduleName = module.packageName, + code = object : ScopeCallable { + override suspend fun call(scp: Scope): Obj { + val self = scp.thisAs() + return self.path.name.toObj() + } + } + ) addFnDoc( name = "parent", doc = "Parent directory as a Path or null if none.", returns = type("Path", nullable = true), - moduleName = module.packageName - ) { - val self = thisAs() - self.path.parent?.let { - ObjPath( this@apply, self.secured, it) - } ?: ObjNull - } + moduleName = module.packageName, + code = object : ScopeCallable { + override suspend fun call(scp: Scope): Obj { + val self = scp.thisAs() + return self.path.parent?.let { + ObjPath(this@apply, self.secured, it) + } ?: ObjNull + } + } + ) addFnDoc( name = "segments", doc = "List of path segments.", // returns: List returns = TypeGenericDoc(type("lyng.List"), listOf(type("lyng.String"))), - moduleName = module.packageName - ) { - val self = thisAs() - ObjList(self.path.segments.map { ObjString(it) }.toMutableList()) - } + moduleName = module.packageName, + code = object : ScopeCallable { + override suspend fun call(scp: Scope): Obj { + val self = scp.thisAs() + return ObjList(self.path.segments.map { ObjString(it) }.toMutableList()) + } + } + ) // exists(): Bool addFnDoc( name = "exists", doc = "Check whether this path exists.", returns = type("lyng.Bool"), - moduleName = module.packageName - ) { - fsGuard { - val self = this.thisObj as ObjPath - (self.secured.exists(self.path)).toObj() + moduleName = module.packageName, + code = object : ScopeCallable { + override suspend fun call(scp: Scope): Obj { + return scp.fsGuard { + val self = scp.thisObj as ObjPath + (self.secured.exists(self.path)).toObj() + } + } } - } + ) // isFile(): Bool — cached metadata addFnDoc( name = "isFile", doc = "True if this path is a regular file (based on cached metadata).", returns = type("lyng.Bool"), - moduleName = module.packageName - ) { - fsGuard { - val self = this.thisObj as ObjPath - self.ensureMetadata().let { ObjBool(it.isRegularFile) } + moduleName = module.packageName, + code = object : ScopeCallable { + override suspend fun call(scp: Scope): Obj { + return scp.fsGuard { + val self = scp.thisObj as ObjPath + self.ensureMetadata().let { ObjBool(it.isRegularFile) } + } + } } - } + ) // isDirectory(): Bool — cached metadata addFnDoc( name = "isDirectory", doc = "True if this path is a directory (based on cached metadata).", returns = type("lyng.Bool"), - moduleName = module.packageName - ) { - fsGuard { - val self = this.thisObj as ObjPath - self.ensureMetadata().let { ObjBool(it.isDirectory) } + moduleName = module.packageName, + code = object : ScopeCallable { + override suspend fun call(scp: Scope): Obj { + return scp.fsGuard { + val self = scp.thisObj as ObjPath + self.ensureMetadata().let { ObjBool(it.isDirectory) } + } + } } - } + ) // size(): Int? — null when unavailable addFnDoc( name = "size", doc = "File size in bytes, or null when unavailable.", returns = type("lyng.Int", nullable = true), - moduleName = module.packageName - ) { - fsGuard { - val self = this.thisObj as ObjPath - val m = self.ensureMetadata() - m.size?.let { ObjInt(it) } ?: ObjNull + moduleName = module.packageName, + code = object : ScopeCallable { + override suspend fun call(scp: Scope): Obj { + return scp.fsGuard { + val self = scp.thisObj as ObjPath + val m = self.ensureMetadata() + m.size?.let { ObjInt(it) } ?: ObjNull + } + } } - } + ) // createdAt(): Instant? — Lyng Instant, null when unavailable addFnDoc( name = "createdAt", doc = "Creation time as `Instant`, or null when unavailable.", returns = type("lyng.Instant", nullable = true), - moduleName = module.packageName - ) { - fsGuard { - val self = this.thisObj as ObjPath - val m = self.ensureMetadata() - m.createdAtMillis?.let { ObjInstant(kotlin.time.Instant.fromEpochMilliseconds(it)) } ?: ObjNull + moduleName = module.packageName, + code = object : ScopeCallable { + override suspend fun call(scp: Scope): Obj { + return scp.fsGuard { + val self = scp.thisObj as ObjPath + val m = self.ensureMetadata() + m.createdAtMillis?.let { ObjInstant(kotlin.time.Instant.fromEpochMilliseconds(it)) } ?: ObjNull + } + } } - } + ) // createdAtMillis(): Int? — milliseconds since epoch or null addFnDoc( name = "createdAtMillis", doc = "Creation time in milliseconds since epoch, or null when unavailable.", returns = type("lyng.Int", nullable = true), - moduleName = module.packageName - ) { - fsGuard { - val self = this.thisObj as ObjPath - val m = self.ensureMetadata() - m.createdAtMillis?.let { ObjInt(it) } ?: ObjNull + moduleName = module.packageName, + code = object : ScopeCallable { + override suspend fun call(scp: Scope): Obj { + return scp.fsGuard { + val self = scp.thisObj as ObjPath + val m = self.ensureMetadata() + m.createdAtMillis?.let { ObjInt(it) } ?: ObjNull + } + } } - } + ) // modifiedAt(): Instant? — Lyng Instant, null when unavailable addFnDoc( name = "modifiedAt", doc = "Last modification time as `Instant`, or null when unavailable.", returns = type("lyng.Instant", nullable = true), - moduleName = module.packageName - ) { - fsGuard { - val self = this.thisObj as ObjPath - val m = self.ensureMetadata() - m.modifiedAtMillis?.let { ObjInstant(kotlinx.datetime.Instant.fromEpochMilliseconds(it)) } ?: ObjNull + moduleName = module.packageName, + code = object : ScopeCallable { + override suspend fun call(scp: Scope): Obj { + return scp.fsGuard { + val self = scp.thisObj as ObjPath + val m = self.ensureMetadata() + m.modifiedAtMillis?.let { ObjInstant(kotlinx.datetime.Instant.fromEpochMilliseconds(it)) } ?: ObjNull + } + } } - } + ) // modifiedAtMillis(): Int? — milliseconds since epoch or null addFnDoc( name = "modifiedAtMillis", doc = "Last modification time in milliseconds since epoch, or null when unavailable.", returns = type("lyng.Int", nullable = true), - moduleName = module.packageName - ) { - fsGuard { - val self = this.thisObj as ObjPath - val m = self.ensureMetadata() - m.modifiedAtMillis?.let { ObjInt(it) } ?: ObjNull + moduleName = module.packageName, + code = object : ScopeCallable { + override suspend fun call(scp: Scope): Obj { + return scp.fsGuard { + val self = scp.thisObj as ObjPath + val m = self.ensureMetadata() + m.modifiedAtMillis?.let { ObjInt(it) } ?: ObjNull + } + } } - } + ) // list(): List addFnDoc( name = "list", doc = "List directory entries as `Path` objects.", returns = TypeGenericDoc(type("lyng.List"), listOf(type("Path"))), - moduleName = module.packageName - ) { - fsGuard { - val self = this.thisObj as ObjPath - val items = self.secured.list(self.path).map { ObjPath(self.objClass, self.secured, it) } - ObjList(items.toMutableList()) + moduleName = module.packageName, + code = object : ScopeCallable { + override suspend fun call(scp: Scope): Obj { + return scp.fsGuard { + val self = scp.thisObj as ObjPath + val items = self.secured.list(self.path).map { ObjPath(self.objClass, self.secured, it) } + ObjList(items.toMutableList()) + } + } } - } + ) // readBytes(): Buffer addFnDoc( name = "readBytes", doc = "Read the file into a binary buffer.", returns = type("lyng.Buffer"), - moduleName = module.packageName - ) { - fsGuard { - val self = this.thisObj as ObjPath - val bytes = self.secured.readBytes(self.path) - ObjBuffer(bytes.asUByteArray()) + moduleName = module.packageName, + code = object : ScopeCallable { + override suspend fun call(scp: Scope): Obj { + return scp.fsGuard { + val self = scp.thisObj as ObjPath + val bytes = self.secured.readBytes(self.path) + ObjBuffer(bytes.asUByteArray()) + } + } } - } + ) // writeBytes(bytes: Buffer) addFnDoc( name = "writeBytes", doc = "Write a binary buffer to the file, replacing content.", params = listOf(ParamDoc("bytes", type("lyng.Buffer"))), - moduleName = module.packageName - ) { - fsGuard { - val self = this.thisObj as ObjPath - val buf = requiredArg(0) - self.secured.writeBytes(self.path, buf.byteArray.asByteArray(), append = false) - ObjVoid + moduleName = module.packageName, + code = object : ScopeCallable { + override suspend fun call(scp: Scope): Obj { + return scp.fsGuard { + val self = scp.thisObj as ObjPath + val buf = scp.requiredArg(0) + self.secured.writeBytes(self.path, buf.byteArray.asByteArray(), append = false) + ObjVoid + } + } } - } + ) // appendBytes(bytes: Buffer) addFnDoc( name = "appendBytes", doc = "Append a binary buffer to the end of the file.", params = listOf(ParamDoc("bytes", type("lyng.Buffer"))), - moduleName = module.packageName - ) { - fsGuard { - val self = this.thisObj as ObjPath - val buf = requiredArg(0) - self.secured.writeBytes(self.path, buf.byteArray.asByteArray(), append = true) - ObjVoid + moduleName = module.packageName, + code = object : ScopeCallable { + override suspend fun call(scp: Scope): Obj { + return scp.fsGuard { + val self = scp.thisObj as ObjPath + val buf = scp.requiredArg(0) + self.secured.writeBytes(self.path, buf.byteArray.asByteArray(), append = true) + ObjVoid + } + } } - } + ) // readUtf8(): String addFnDoc( name = "readUtf8", doc = "Read the file as a UTF-8 string.", returns = type("lyng.String"), - moduleName = module.packageName - ) { - fsGuard { - val self = this.thisObj as ObjPath - self.secured.readUtf8(self.path).toObj() + moduleName = module.packageName, + code = object : ScopeCallable { + override suspend fun call(scp: Scope): Obj { + return scp.fsGuard { + val self = scp.thisObj as ObjPath + self.secured.readUtf8(self.path).toObj() + } + } } - } + ) // writeUtf8(text: String) addFnDoc( name = "writeUtf8", doc = "Write a UTF-8 string to the file, replacing content.", params = listOf(ParamDoc("text", type("lyng.String"))), - moduleName = module.packageName - ) { - fsGuard { - val self = this.thisObj as ObjPath - val text = requireOnlyArg().value - self.secured.writeUtf8(self.path, text, append = false) - ObjVoid + moduleName = module.packageName, + code = object : ScopeCallable { + override suspend fun call(scp: Scope): Obj { + return scp.fsGuard { + val self = scp.thisObj as ObjPath + val text = scp.requireOnlyArg().value + self.secured.writeUtf8(self.path, text, append = false) + ObjVoid + } + } } - } + ) // appendUtf8(text: String) addFnDoc( name = "appendUtf8", doc = "Append UTF-8 text to the end of the file.", params = listOf(ParamDoc("text", type("lyng.String"))), - moduleName = module.packageName - ) { - fsGuard { - val self = this.thisObj as ObjPath - val text = requireOnlyArg().value - self.secured.writeUtf8(self.path, text, append = true) - ObjVoid + moduleName = module.packageName, + code = object : ScopeCallable { + override suspend fun call(scp: Scope): Obj { + return scp.fsGuard { + val self = scp.thisObj as ObjPath + val text = scp.requireOnlyArg().value + self.secured.writeUtf8(self.path, text, append = true) + ObjVoid + } + } } - } + ) // metadata(): Map addFnDoc( name = "metadata", doc = "Fetch cached metadata as a map of fields: `isFile`, `isDirectory`, `size`, `createdAtMillis`, `modifiedAtMillis`, `isSymlink`.", returns = TypeGenericDoc(type("lyng.Map"), listOf(type("lyng.String"), type("lyng.Any"))), - moduleName = module.packageName - ) { - fsGuard { - val self = this.thisObj as ObjPath - val m = self.secured.metadata(self.path) - ObjMap(mutableMapOf( - ObjString("isFile") to ObjBool(m.isRegularFile), - ObjString("isDirectory") to ObjBool(m.isDirectory), - ObjString("size") to (m.size?.toLong() ?: 0L).toObj(), - ObjString("createdAtMillis") to ((m.createdAtMillis ?: 0L)).toObj(), - ObjString("modifiedAtMillis") to ((m.modifiedAtMillis ?: 0L)).toObj(), - ObjString("isSymlink") to ObjBool(m.isSymlink), - )) + moduleName = module.packageName, + code = object : ScopeCallable { + override suspend fun call(scp: Scope): Obj { + return scp.fsGuard { + val self = scp.thisObj as ObjPath + val m = self.secured.metadata(self.path) + ObjMap(mutableMapOf( + ObjString("isFile") to ObjBool(m.isRegularFile), + ObjString("isDirectory") to ObjBool(m.isDirectory), + ObjString("size") to (m.size?.toLong() ?: 0L).toObj(), + ObjString("createdAtMillis") to ((m.createdAtMillis ?: 0L)).toObj(), + ObjString("modifiedAtMillis") to ((m.modifiedAtMillis ?: 0L)).toObj(), + ObjString("isSymlink") to ObjBool(m.isSymlink), + )) + } + } } - } + ) // mkdirs(mustCreate: Bool=false) addFnDoc( name = "mkdirs", doc = "Create directories (like `mkdir -p`). If `mustCreate` is true and the path already exists, the call fails. Otherwise it is a no‑op when the directory exists.", params = listOf(ParamDoc("mustCreate", type("lyng.Bool"))), - moduleName = module.packageName - ) { - fsGuard { - val self = this.thisObj as ObjPath - val mustCreate = args.list.getOrNull(0)?.toBool() ?: false - self.secured.createDirectories(self.path, mustCreate) - ObjVoid + moduleName = module.packageName, + code = object : ScopeCallable { + override suspend fun call(scp: Scope): Obj { + return scp.fsGuard { + val self = scp.thisObj as ObjPath + val mustCreate = scp.args.list.getOrNull(0)?.toBool() ?: false + self.secured.createDirectories(self.path, mustCreate) + ObjVoid + } + } } - } + ) // move(to: Path|String, overwrite: Bool=false) addFnDoc( name = "move", doc = "Move this path to a new location. `to` may be a `Path` or `String`. When `overwrite` is false and the target exists, the operation fails (provider may throw `AccessDeniedException`).", // types vary; keep generic description in doc params = listOf(ParamDoc("to"), ParamDoc("overwrite", type("lyng.Bool"))), - moduleName = module.packageName - ) { - fsGuard { - val self = this.thisObj as ObjPath - val toPath = parsePathArg(this, self, requiredArg(0)) - val overwrite = args.list.getOrNull(1)?.toBool() ?: false - self.secured.move(self.path, toPath, overwrite) - ObjVoid + moduleName = module.packageName, + code = object : ScopeCallable { + override suspend fun call(scp: Scope): Obj { + return scp.fsGuard { + val self = scp.thisObj as ObjPath + val toPath = parsePathArg(scp, self, scp.requiredArg(0)) + val overwrite = scp.args.list.getOrNull(1)?.toBool() ?: false + self.secured.move(self.path, toPath, overwrite) + ObjVoid + } + } } - } + ) // delete(mustExist: Bool=false, recursively: Bool=false) addFnDoc( name = "delete", doc = "Delete this path. `mustExist=true` causes failure if the path does not exist. `recursively=true` removes directories with their contents. Providers can throw `AccessDeniedException` on policy violations.", params = listOf(ParamDoc("mustExist", type("lyng.Bool")), ParamDoc("recursively", type("lyng.Bool"))), - moduleName = module.packageName - ) { - fsGuard { - val self = this.thisObj as ObjPath - val mustExist = args.list.getOrNull(0)?.toBool() ?: false - val recursively = args.list.getOrNull(1)?.toBool() ?: false - self.secured.delete(self.path, mustExist, recursively) - ObjVoid + moduleName = module.packageName, + code = object : ScopeCallable { + override suspend fun call(scp: Scope): Obj { + return scp.fsGuard { + val self = scp.thisObj as ObjPath + val mustExist = scp.args.list.getOrNull(0)?.toBool() ?: false + val recursively = scp.args.list.getOrNull(1)?.toBool() ?: false + self.secured.delete(self.path, mustExist, recursively) + ObjVoid + } + } } - } + ) // copy(to: Path|String, overwrite: Bool=false) addFnDoc( name = "copy", doc = "Copy this path to a new location. `to` may be a `Path` or `String`. When `overwrite` is false and the target exists, the operation fails (provider may throw `AccessDeniedException`).", params = listOf(ParamDoc("to"), ParamDoc("overwrite", type("lyng.Bool"))), - moduleName = module.packageName - ) { - fsGuard { - val self = this.thisObj as ObjPath - val toPath = parsePathArg(this, self, requiredArg(0)) - val overwrite = args.list.getOrNull(1)?.toBool() ?: false - self.secured.copy(self.path, toPath, overwrite) - ObjVoid + moduleName = module.packageName, + code = object : ScopeCallable { + override suspend fun call(scp: Scope): Obj { + return scp.fsGuard { + val self = scp.thisObj as ObjPath + val toPath = parsePathArg(scp, self, scp.requiredArg(0)) + val overwrite = scp.args.list.getOrNull(1)?.toBool() ?: false + self.secured.copy(self.path, toPath, overwrite) + ObjVoid + } + } } - } + ) // glob(pattern: String): List addFnDoc( name = "glob", doc = "List entries matching a glob pattern (no recursion).", params = listOf(ParamDoc("pattern", type("lyng.String"))), returns = TypeGenericDoc(type("lyng.List"), listOf(type("Path"))), - moduleName = module.packageName - ) { - fsGuard { - val self = this.thisObj as ObjPath - val pattern = requireOnlyArg().value - val matches = self.secured.glob(self.path, pattern) - ObjList(matches.map { ObjPath(self.objClass, self.secured, it) }.toMutableList()) + moduleName = module.packageName, + code = object : ScopeCallable { + override suspend fun call(scp: Scope): Obj { + return scp.fsGuard { + val self = scp.thisObj as ObjPath + val pattern = scp.requireOnlyArg().value + val matches = self.secured.glob(self.path, pattern) + ObjList(matches.map { ObjPath(self.objClass, self.secured, it) }.toMutableList()) + } + } } - } + ) // --- streaming readers (initial version: chunk from whole content, API stable) --- @@ -403,15 +479,18 @@ private suspend fun buildFsModule(module: ModuleScope, policy: FsAccessPolicy) { doc = "Read file in fixed-size chunks as an iterator of `Buffer`.", params = listOf(ParamDoc("size", type("lyng.Int"))), returns = TypeGenericDoc(type("lyng.Iterator"), listOf(type("lyng.Buffer"))), - moduleName = module.packageName - ) { - fsGuard { - val self = this.thisObj as ObjPath - val size = args.list.getOrNull(0)?.toInt() ?: 65536 - val bytes = self.secured.readBytes(self.path) - ObjFsBytesIterator(bytes, size) + moduleName = module.packageName, + code = object : ScopeCallable { + override suspend fun call(scp: Scope): Obj { + return scp.fsGuard { + val self = scp.thisObj as ObjPath + val size = scp.args.list.getOrNull(0)?.toInt() ?: 65536 + val bytes = self.secured.readBytes(self.path) + ObjFsBytesIterator(bytes, size) + } + } } - } + ) // readUtf8Chunks(size: Int = 65536) -> Iterator addFnDoc( @@ -419,28 +498,34 @@ private suspend fun buildFsModule(module: ModuleScope, policy: FsAccessPolicy) { doc = "Read UTF-8 text in fixed-size chunks as an iterator of `String`.", params = listOf(ParamDoc("size", type("lyng.Int"))), returns = TypeGenericDoc(type("lyng.Iterator"), listOf(type("lyng.String"))), - moduleName = module.packageName - ) { - fsGuard { - val self = this.thisObj as ObjPath - val size = args.list.getOrNull(0)?.toInt() ?: 65536 - val text = self.secured.readUtf8(self.path) - ObjFsStringChunksIterator(text, size) + moduleName = module.packageName, + code = object : ScopeCallable { + override suspend fun call(scp: Scope): Obj { + return scp.fsGuard { + val self = scp.thisObj as ObjPath + val size = scp.args.list.getOrNull(0)?.toInt() ?: 65536 + val text = self.secured.readUtf8(self.path) + ObjFsStringChunksIterator(text, size) + } + } } - } + ) // lines() -> Iterator, implemented via readUtf8Chunks addFnDoc( name = "lines", doc = "Iterate lines of the file as `String` values.", returns = TypeGenericDoc(type("lyng.Iterator"), listOf(type("lyng.String"))), - moduleName = module.packageName - ) { - fsGuard { - val chunkIt = thisObj.invokeInstanceMethod(this, "readUtf8Chunks") - ObjFsLinesIterator(chunkIt) + moduleName = module.packageName, + code = object : ScopeCallable { + override suspend fun call(scp: Scope): Obj { + return scp.fsGuard { + val chunkIt = scp.thisObj.invokeInstanceMethod(scp, "readUtf8Chunks") + ObjFsLinesIterator(chunkIt) + } + } } - } + ) } // Export into the module scope with docs @@ -518,39 +603,51 @@ class ObjFsBytesIterator( name = "iterator", doc = "Return this iterator instance (enables `for` loops).", returns = type("BytesIterator"), - moduleName = "lyng.io.fs" - ) { thisObj } + moduleName = "lyng.io.fs", + code = object : ScopeCallable { + override suspend fun call(scp: Scope): Obj = scp.thisObj + } + ) addFnDoc( name = "hasNext", doc = "Whether there is another chunk available.", returns = type("lyng.Bool"), - moduleName = "lyng.io.fs" - ) { - val self = thisAs() - (self.pos < self.data.size).toObj() - } + moduleName = "lyng.io.fs", + code = object : ScopeCallable { + override suspend fun call(scp: Scope): Obj { + val self = scp.thisAs() + return (self.pos < self.data.size).toObj() + } + } + ) addFnDoc( name = "next", doc = "Return the next chunk as a `Buffer`.", returns = type("lyng.Buffer"), - moduleName = "lyng.io.fs" - ) { - val self = thisAs() - if (self.pos >= self.data.size) raiseIllegalState("iterator exhausted") - val end = minOf(self.pos + self.chunkSize, self.data.size) - val chunk = self.data.copyOfRange(self.pos, end) - self.pos = end - ObjBuffer(chunk.asUByteArray()) - } + moduleName = "lyng.io.fs", + code = object : ScopeCallable { + override suspend fun call(scp: Scope): Obj { + val self = scp.thisAs() + if (self.pos >= self.data.size) scp.raiseIllegalState("iterator exhausted") + val end = minOf(self.pos + self.chunkSize, self.data.size) + val chunk = self.data.copyOfRange(self.pos, end) + self.pos = end + return ObjBuffer(chunk.asUByteArray()) + } + } + ) addFnDoc( name = "cancelIteration", doc = "Stop the iteration early; subsequent `hasNext` returns false.", - moduleName = "lyng.io.fs" - ) { - val self = thisAs() - self.pos = self.data.size - ObjVoid - } + moduleName = "lyng.io.fs", + code = object : ScopeCallable { + override suspend fun call(scp: Scope): Obj { + val self = scp.thisAs() + self.pos = self.data.size + return ObjVoid + } + } + ) } } } @@ -573,35 +670,47 @@ class ObjFsStringChunksIterator( name = "iterator", doc = "Return this iterator instance (enables `for` loops).", returns = type("StringChunksIterator"), - moduleName = "lyng.io.fs" - ) { thisObj } + moduleName = "lyng.io.fs", + code = object : ScopeCallable { + override suspend fun call(scp: Scope): Obj = scp.thisObj + } + ) addFnDoc( name = "hasNext", doc = "Whether there is another chunk available.", returns = type("lyng.Bool"), - moduleName = "lyng.io.fs" - ) { - val self = thisAs() - (self.pos < self.text.length).toObj() - } + moduleName = "lyng.io.fs", + code = object : ScopeCallable { + override suspend fun call(scp: Scope): Obj { + val self = scp.thisAs() + return (self.pos < self.text.length).toObj() + } + } + ) addFnDoc( name = "next", doc = "Return the next UTF-8 chunk as a `String`.", returns = type("lyng.String"), - moduleName = "lyng.io.fs" - ) { - val self = thisAs() - if (self.pos >= self.text.length) raiseIllegalState("iterator exhausted") - val end = minOf(self.pos + self.chunkChars, self.text.length) - val chunk = self.text.substring(self.pos, end) - self.pos = end - ObjString(chunk) - } + moduleName = "lyng.io.fs", + code = object : ScopeCallable { + override suspend fun call(scp: Scope): Obj { + val self = scp.thisAs() + if (self.pos >= self.text.length) scp.raiseIllegalState("iterator exhausted") + val end = minOf(self.pos + self.chunkChars, self.text.length) + val chunk = self.text.substring(self.pos, end) + self.pos = end + return ObjString(chunk) + } + } + ) addFnDoc( name = "cancelIteration", doc = "Stop the iteration early; subsequent `hasNext` returns false.", - moduleName = "lyng.io.fs" - ) { ObjVoid } + moduleName = "lyng.io.fs", + code = object : ScopeCallable { + override suspend fun call(scp: Scope): Obj = ObjVoid + } + ) } } } @@ -624,46 +733,58 @@ class ObjFsLinesIterator( name = "iterator", doc = "Return this iterator instance (enables `for` loops).", returns = type("LinesIterator"), - moduleName = "lyng.io.fs" - ) { thisObj } + moduleName = "lyng.io.fs", + code = object : ScopeCallable { + override suspend fun call(scp: Scope): Obj = scp.thisObj + } + ) addFnDoc( name = "hasNext", doc = "Whether another line is available.", returns = type("lyng.Bool"), - moduleName = "lyng.io.fs" - ) { - val self = thisAs() - self.ensureBufferFilled(this) - (self.buffer.isNotEmpty() || !self.exhausted).toObj() - } + moduleName = "lyng.io.fs", + code = object : ScopeCallable { + override suspend fun call(scp: Scope): Obj { + val self = scp.thisAs() + self.ensureBufferFilled(scp) + return (self.buffer.isNotEmpty() || !self.exhausted).toObj() + } + } + ) addFnDoc( name = "next", doc = "Return the next line as `String`.", returns = type("lyng.String"), - moduleName = "lyng.io.fs" - ) { - val self = thisAs() - self.ensureBufferFilled(this) - if (self.buffer.isEmpty() && self.exhausted) raiseIllegalState("iterator exhausted") - val idx = self.buffer.indexOf('\n') - val line = if (idx >= 0) { - val l = self.buffer.substring(0, idx) - self.buffer = self.buffer.substring(idx + 1) - l - } else { - // last line without trailing newline - val l = self.buffer - self.buffer = "" - self.exhausted = true - l + moduleName = "lyng.io.fs", + code = object : ScopeCallable { + override suspend fun call(scp: Scope): Obj { + val self = scp.thisAs() + self.ensureBufferFilled(scp) + if (self.buffer.isEmpty() && self.exhausted) scp.raiseIllegalState("iterator exhausted") + val idx = self.buffer.indexOf('\n') + val line = if (idx >= 0) { + val l = self.buffer.substring(0, idx) + self.buffer = self.buffer.substring(idx + 1) + l + } else { + // last line without trailing newline + val l = self.buffer + self.buffer = "" + self.exhausted = true + l + } + return ObjString(line) + } } - ObjString(line) - } + ) addFnDoc( name = "cancelIteration", doc = "Stop the iteration early; subsequent `hasNext` returns false.", - moduleName = "lyng.io.fs" - ) { ObjVoid } + moduleName = "lyng.io.fs", + code = object : ScopeCallable { + override suspend fun call(scp: Scope): Obj = ObjVoid + } + ) } } } diff --git a/lyngio/src/commonMain/kotlin/net/sergeych/lyng/io/process/LyngProcessModule.kt b/lyngio/src/commonMain/kotlin/net/sergeych/lyng/io/process/LyngProcessModule.kt index e96dfe0..57457ca 100644 --- a/lyngio/src/commonMain/kotlin/net/sergeych/lyng/io/process/LyngProcessModule.kt +++ b/lyngio/src/commonMain/kotlin/net/sergeych/lyng/io/process/LyngProcessModule.kt @@ -20,9 +20,11 @@ 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.ScopeCallable import net.sergeych.lyng.miniast.* import net.sergeych.lyng.obj.* import net.sergeych.lyng.pacman.ImportManager +import net.sergeych.lyng.pacman.ModuleBuilder import net.sergeych.lyng.statement import net.sergeych.lyngio.process.* import net.sergeych.lyngio.process.security.ProcessAccessDeniedException @@ -39,9 +41,11 @@ fun createProcessModule(policy: ProcessAccessPolicy, manager: ImportManager): Bo val name = "lyng.io.process" if (manager.packageNames.contains(name)) return false - manager.addPackage(name) { module -> - buildProcessModule(module, policy) - } + manager.addPackage(name, object : ModuleBuilder { + override suspend fun build(module: ModuleScope) { + buildProcessModule(module, policy) + } + }) return true } @@ -59,59 +63,74 @@ private suspend fun buildProcessModule(module: ModuleScope, policy: ProcessAcces name = "stdout", doc = "Get standard output stream as a Flow of lines.", returns = type("lyng.Flow"), - moduleName = module.packageName - ) { - val self = thisAs() - self.process.stdout.toLyngFlow(this) - } + moduleName = module.packageName, + code = object : ScopeCallable { + override suspend fun call(scp: Scope): Obj { + val self = scp.thisAs() + return self.process.stdout.toLyngFlow(scp) + } + } + ) addFnDoc( name = "stderr", doc = "Get standard error stream as a Flow of lines.", returns = type("lyng.Flow"), - moduleName = module.packageName - ) { - val self = thisAs() - self.process.stderr.toLyngFlow(this) - } + moduleName = module.packageName, + code = object : ScopeCallable { + override suspend fun call(scp: Scope): Obj { + val self = scp.thisAs() + return self.process.stderr.toLyngFlow(scp) + } + } + ) addFnDoc( name = "signal", doc = "Send a signal to the process (e.g. 'SIGINT', 'SIGTERM', 'SIGKILL').", params = listOf(ParamDoc("signal", type("lyng.String"))), - moduleName = module.packageName - ) { - processGuard { - val sigStr = requireOnlyArg().value.uppercase() - val sig = try { - ProcessSignal.valueOf(sigStr) - } catch (e: Exception) { - try { - ProcessSignal.valueOf("SIG$sigStr") - } catch (e2: Exception) { - raiseIllegalArgument("Unknown signal: $sigStr") + moduleName = module.packageName, + code = object : ScopeCallable { + override suspend fun call(scp: Scope): Obj { + return scp.processGuard { + val sigStr = scp.requireOnlyArg().value.uppercase() + val sig = try { + ProcessSignal.valueOf(sigStr) + } catch (e: Exception) { + try { + ProcessSignal.valueOf("SIG$sigStr") + } catch (e2: Exception) { + scp.raiseIllegalArgument("Unknown signal: $sigStr") + } + } + scp.thisAs().process.sendSignal(sig) + ObjVoid } } - thisAs().process.sendSignal(sig) - ObjVoid } - } + ) addFnDoc( name = "waitFor", doc = "Wait for the process to exit and return its exit code.", returns = type("lyng.Int"), - moduleName = module.packageName - ) { - processGuard { - thisAs().process.waitFor().toObj() + moduleName = module.packageName, + code = object : ScopeCallable { + override suspend fun call(scp: Scope): Obj { + return scp.processGuard { + scp.thisAs().process.waitFor().toObj() + } + } } - } + ) addFnDoc( name = "destroy", doc = "Forcefully terminate the process.", - moduleName = module.packageName - ) { - thisAs().process.destroy() - ObjVoid - } + moduleName = module.packageName, + code = object : ScopeCallable { + override suspend fun call(scp: Scope): Obj { + scp.thisAs().process.destroy() + return ObjVoid + } + } + ) } val processType = object : ObjClass("Process") {} @@ -122,30 +141,36 @@ private suspend fun buildProcessModule(module: ModuleScope, policy: ProcessAcces doc = "Execute a process with arguments.", params = listOf(ParamDoc("executable", type("lyng.String")), ParamDoc("args", type("lyng.List"))), returns = type("RunningProcess"), - moduleName = module.packageName - ) { - if (runner == null) raiseError("Processes are not supported on this platform") - processGuard { - val executable = requiredArg(0).value - val args = requiredArg(1).list.map { it.toString() } - val lp = runner.execute(executable, args) - ObjRunningProcess(runningProcessType, lp) + moduleName = module.packageName, + code = object : ScopeCallable { + override suspend fun call(scp: Scope): Obj { + if (runner == null) scp.raiseError("Processes are not supported on this platform") + return scp.processGuard { + val executable = scp.requiredArg(0).value + val args = scp.requiredArg(1).list.map { it.toString() } + val lp = runner.execute(executable, args) + ObjRunningProcess(runningProcessType, lp) + } + } } - } + ) addClassFnDoc( name = "shell", doc = "Execute a command via system shell.", params = listOf(ParamDoc("command", type("lyng.String"))), returns = type("RunningProcess"), - moduleName = module.packageName - ) { - if (runner == null) raiseError("Processes are not supported on this platform") - processGuard { - val command = requireOnlyArg().value - val lp = runner.shell(command) - ObjRunningProcess(runningProcessType, lp) + moduleName = module.packageName, + code = object : ScopeCallable { + override suspend fun call(scp: Scope): Obj { + if (runner == null) scp.raiseError("Processes are not supported on this platform") + return scp.processGuard { + val command = scp.requireOnlyArg().value + val lp = runner.shell(command) + ObjRunningProcess(runningProcessType, lp) + } + } } - } + ) } val platformType = object : ObjClass("Platform") {} @@ -155,24 +180,28 @@ private suspend fun buildProcessModule(module: ModuleScope, policy: ProcessAcces name = "details", doc = "Get platform core details.", returns = type("lyng.Map"), - moduleName = module.packageName - ) { - val d = getPlatformDetails() - ObjMap(mutableMapOf( - ObjString("name") to ObjString(d.name), - ObjString("version") to ObjString(d.version), - ObjString("arch") to ObjString(d.arch), - ObjString("kernelVersion") to (d.kernelVersion?.toObj() ?: ObjNull) - )) - } + moduleName = module.packageName, + code = object : ScopeCallable { + override suspend fun call(scp: Scope): Obj { + val d = getPlatformDetails() + return ObjMap(mutableMapOf( + ObjString("name") to ObjString(d.name), + ObjString("version") to ObjString(d.version), + ObjString("arch") to ObjString(d.arch), + ObjString("kernelVersion") to (d.kernelVersion?.toObj() ?: ObjNull) + )) + } + } + ) addClassFnDoc( name = "isSupported", doc = "Check if processes are supported on this platform.", returns = type("lyng.Bool"), - moduleName = module.packageName - ) { - isProcessSupported().toObj() - } + moduleName = module.packageName, + code = object : ScopeCallable { + override suspend fun call(scp: Scope): Obj = isProcessSupported().toObj() + } + ) } module.addConstDoc( @@ -216,19 +245,21 @@ private suspend inline fun Scope.processGuard(crossinline block: suspend () -> O } private fun Flow.toLyngFlow(flowScope: Scope): ObjFlow { - val producer = statement { - val builder = (this as? net.sergeych.lyng.ClosureScope)?.callScope?.thisObj as? ObjFlowBuilder - ?: this.thisObj as? ObjFlowBuilder + val producer = statement(f = object : ScopeCallable { + override suspend fun call(scp: Scope): Obj { + val builder = (scp as? net.sergeych.lyng.ClosureScope)?.callScope?.thisObj as? ObjFlowBuilder + ?: scp.thisObj as? ObjFlowBuilder - this@toLyngFlow.collect { - try { - builder?.output?.send(ObjString(it)) - } catch (e: Exception) { - // Channel closed or other error, stop collecting - return@collect + this@toLyngFlow.collect { + try { + builder?.output?.send(ObjString(it)) + } catch (e: Exception) { + // Channel closed or other error, stop collecting + return@collect + } } + return ObjVoid } - ObjVoid - } + }) return ObjFlow(producer, flowScope) } diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/ArgsDeclaration.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/ArgsDeclaration.kt index e4b035c..cbd98fe 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/ArgsDeclaration.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/ArgsDeclaration.kt @@ -154,69 +154,73 @@ data class ArgsDeclaration(val params: List, val endTokenType: Token.Type) } } - // Helper: assign head part, consuming from headPos; stop at ellipsis - suspend fun processHead(index: Int, headPos: Int): Pair { - var i = index - var hp = headPos + // Locate ellipsis index within considered parameters + val ellipsisIndex = params.subList(0, paramsSize).indexOfFirst { it.isEllipsis } + + var headPos = 0 + var tailPos = callArgs.size - 1 + + if (ellipsisIndex >= 0) { + // Assign head first to know how many positionals are consumed from the start + var i = 0 while (i < paramsSize) { val a = params[i] if (a.isEllipsis) break if (assignedByName[i]) { assign(a, namedValues[i]!!) } else { - val value = if (hp < callArgs.size) callArgs[hp++] + val value = if (headPos < callArgs.size) callArgs[headPos++] else a.defaultValue?.execute(scope) ?: scope.raiseIllegalArgument("too few arguments for the call (missing ${a.name})") assign(a, value) } i++ } - return i to hp - } + val afterHead = i + val headConsumedTo = headPos - // Helper: assign tail part from the end, consuming from tailPos; stop before ellipsis index - // Do not consume elements below headPosBound to avoid overlap with head consumption - suspend fun processTail(startExclusive: Int, tailStart: Int, headPosBound: Int): Int { - var i = paramsSize - 1 - var tp = tailStart - while (i > startExclusive) { + // Then assign tail consuming from the end down to headConsumedTo boundary + i = paramsSize - 1 + var tp = tailPos + while (i > ellipsisIndex) { val a = params[i] if (a.isEllipsis) break if (i < assignedByName.size && assignedByName[i]) { assign(a, namedValues[i]!!) } else { - val value = if (tp >= headPosBound) callArgs[tp--] + val value = if (tp >= headConsumedTo) callArgs[tp--] else a.defaultValue?.execute(scope) ?: scope.raiseIllegalArgument("too few arguments for the call") assign(a, value) } i-- } - return tp - } + val tailConsumedFrom = tp - fun processEllipsis(index: Int, headPos: Int, tailPos: Int) { - val a = params[index] - val from = headPos - val to = tailPos + // Assign ellipsis list from remaining positionals between headConsumedTo..tailConsumedFrom + val a = params[ellipsisIndex] + val from = headConsumedTo + val to = tailConsumedFrom val l = if (from > to) ObjList() else ObjList(callArgs.subList(from, to + 1).toMutableList()) assign(a, l) - } - - // Locate ellipsis index within considered parameters - val ellipsisIndex = params.subList(0, paramsSize).indexOfFirst { it.isEllipsis } - - if (ellipsisIndex >= 0) { - // Assign head first to know how many positionals are consumed from the start - val (afterHead, headConsumedTo) = processHead(0, 0) - // Then assign tail consuming from the end down to headConsumedTo boundary - val tailConsumedFrom = processTail(ellipsisIndex, callArgs.size - 1, headConsumedTo) - // Assign ellipsis list from remaining positionals between headConsumedTo..tailConsumedFrom - processEllipsis(ellipsisIndex, headConsumedTo, tailConsumedFrom) } else { // No ellipsis: assign head only; any leftover positionals → error - val (_, headConsumedTo) = processHead(0, 0) + var i = 0 + while (i < paramsSize) { + val a = params[i] + if (a.isEllipsis) break + if (assignedByName[i]) { + assign(a, namedValues[i]!!) + } else { + val value = if (headPos < callArgs.size) callArgs[headPos++] + else a.defaultValue?.execute(scope) + ?: scope.raiseIllegalArgument("too few arguments for the call (missing ${a.name})") + assign(a, value) + } + i++ + } + val headConsumedTo = headPos if (headConsumedTo != callArgs.size) scope.raiseIllegalArgument("too many arguments for the call") } diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Arguments.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Arguments.kt index 1f91482..a76e837 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Arguments.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Arguments.kt @@ -1,5 +1,5 @@ /* - * Copyright 2025 Sergey S. Chernov real.sergeych@gmail.com + * Copyright 2026 Sergey S. Chernov real.sergeych@gmail.com * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -244,10 +244,16 @@ data class ParsedArgument( * Convert to list of kotlin objects, see [Obj.toKotlin]. */ suspend fun toKotlinList(scope: Scope): List { - return list.map { it.toKotlin(scope) } + val res = ArrayList(list.size) + for (i in list) res.add(i.toKotlin(scope)) + return res + } + + suspend fun inspect(scope: Scope): String { + val res = ArrayList(list.size) + for (i in list) res.add(i.inspect(scope)) + return res.joinToString(",") } - - suspend fun inspect(scope: Scope): String = list.map{ it.inspect(scope)}.joinToString(",") companion object { val EMPTY = Arguments(emptyList()) diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Compiler.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Compiler.kt index d2a587a..4e5d174 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Compiler.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Compiler.kt @@ -156,13 +156,20 @@ class Compiler( private val codeContexts = mutableListOf(CodeContext.Module(null)) + private val currentClassCtx: CodeContext.ClassBody? + get() = codeContexts.findLast { it is CodeContext.ClassBody } as? CodeContext.ClassBody + // Last parsed block range (for Mini-AST function body attachment) private var lastParsedBlockRange: MiniRange? = null - private suspend fun inCodeContext(context: CodeContext, f: suspend () -> T): T { + fun interface ContextBuilder { + suspend fun build(): T + } + + private suspend fun inCodeContext(context: CodeContext, f: ContextBuilder): T { codeContexts.add(context) try { - val res = f() + val res = f.build() if (context is CodeContext.ClassBody) { if (context.pendingInitializations.isNotEmpty()) { val (name, pos) = context.pendingInitializations.entries.first() @@ -243,10 +250,12 @@ class Compiler( } } val module = importManager.prepareImport(pos, name, null) - statements += statement { - module.importInto(this, null) - ObjVoid - } + statements += statement(pos, f = object : ScopeCallable { + override suspend fun call(scp: Scope): Obj { + module.importInto(scp, null) + return ObjVoid + } + }) continue } } @@ -300,7 +309,7 @@ class Compiler( return result.toString() } - private var lastAnnotation: (suspend (Scope, ObjString, Statement) -> Statement)? = null + private var lastAnnotation: AnnotationProcessor? = null private var isTransientFlag: Boolean = false private var lastLabel: String? = null @@ -376,7 +385,11 @@ class Compiler( private suspend fun parseExpression(): Statement? { val pos = cc.currentPos() - return parseExpressionLevel()?.let { a -> statement(pos) { a.evalValue(it) } } + return parseExpressionLevel()?.let { a -> + statement(pos, f = object : ScopeCallable { + override suspend fun call(scp: Scope): Obj = a.evalValue(scp) + }) + } } private suspend fun parseExpressionLevel(level: Int = 0): ObjRef? { @@ -496,7 +509,9 @@ class Compiler( cc.next() isCall = true val lambda = parseLambdaExpression() - val argStmt = statement { lambda.get(this).value } + val argStmt = statement(f = object : ScopeCallable { + override suspend fun call(scp: Scope): Obj = lambda.get(scp).value + }) val args = listOf(ParsedArgument(argStmt, next.pos)) operand = MethodCallRef(left, next.value, args, true, isOptional) } @@ -681,6 +696,53 @@ class Compiler( } } + private class LambdaStatement( + override val pos: Pos, + private val body: Statement, + private val argsDeclaration: ArgsDeclaration?, + private val label: String?, + private val closureScope: Scope + ) : Statement() { + override suspend fun execute(scope: Scope): Obj { + // and the source closure of the lambda which might have other thisObj. + val context = scope.applyClosure(closureScope) + // Execute lambda body in a closure-aware context. Blocks inside the lambda + // will create child scopes as usual, so re-declarations inside loops work. + if (argsDeclaration == null) { + // no args: automatic var 'it' + val l = scope.args.list + val itValue: Obj = when (l.size) { + // no args: it == void + 0 -> ObjVoid + // one args: it is this arg + 1 -> l[0] + // more args: it is a list of args + else -> ObjList(l.toMutableList()) + } + context.addItem("it", false, itValue, recordType = ObjRecord.Type.Argument) + } else { + // assign vars as declared the standard way + argsDeclaration.assignToContext(context, defaultAccessType = AccessType.Val) + } + return try { + body.execute(context) + } catch (e: ReturnException) { + if (e.label == null || e.label == label) e.result + else throw e + } + } + } + + private class LambdaValueFnRef( + private val body: Statement, + private val argsDeclaration: ArgsDeclaration?, + private val label: String? + ) : RecordProvider { + override suspend fun getRecord(scope: Scope): ObjRecord { + return LambdaStatement(body.pos, body, argsDeclaration, label, scope).asReadonly + } + } + /** * Parse lambda expression, leading '{' is already consumed */ @@ -698,43 +760,16 @@ class Compiler( val paramNames = argsDeclaration?.params?.map { it.name } ?: emptyList() label?.let { cc.labels.add(it) } - val body = inCodeContext(CodeContext.Function("")) { - withLocalNames(paramNames.toSet()) { - parseBlock(skipLeadingBrace = true) + val body = inCodeContext(CodeContext.Function(""), object : ContextBuilder { + override suspend fun build(): Statement { + return withLocalNames(paramNames.toSet()) { + parseBlock(skipLeadingBrace = true) + } } - } + }) label?.let { cc.labels.remove(it) } - return ValueFnRef { closureScope -> - statement(body.pos) { scope -> - // and the source closure of the lambda which might have other thisObj. - val context = scope.applyClosure(closureScope) - // Execute lambda body in a closure-aware context. Blocks inside the lambda - // will create child scopes as usual, so re-declarations inside loops work. - if (argsDeclaration == null) { - // no args: automatic var 'it' - val l = scope.args.list - val itValue: Obj = when (l.size) { - // no args: it == void - 0 -> ObjVoid - // one args: it is this arg - 1 -> l[0] - // more args: it is a list of args - else -> ObjList(l.toMutableList()) - } - context.addItem("it", false, itValue, recordType = ObjRecord.Type.Argument) - } else { - // assign vars as declared the standard way - argsDeclaration.assignToContext(context, defaultAccessType = AccessType.Val) - } - try { - body.execute(context) - } catch (e: ReturnException) { - if (e.label == null || e.label == label) e.result - else throw e - } - }.asReadonly - } + return ValueFnRef(LambdaValueFnRef(body, argsDeclaration, label)) } private suspend fun parseArrayLiteral(): List { @@ -774,9 +809,11 @@ class Compiler( val t = cc.next() if (t.type != Token.Type.ID) throw ScriptError(t.pos, "Expecting ID after ::") return when (t.value) { - "class" -> ValueFnRef { scope -> - operand.get(scope).value.objClass.asReadonly - } + "class" -> ValueFnRef(object : RecordProvider { + override suspend fun getRecord(scope: Scope): ObjRecord { + return operand.get(scope).value.objClass.asReadonly + } + }) else -> throw ScriptError(t.pos, "Unknown scope operation: ${t.value}") } @@ -1108,7 +1145,6 @@ class Compiler( * _following the parenthesis_ call: `(1,2) { ... }` */ private suspend fun parseArgs(): Pair, Boolean> { - val args = mutableListOf() suspend fun tryParseNamedArg(): ParsedArgument? { val save = cc.savePos() @@ -1122,7 +1158,9 @@ class Compiler( val next = cc.peekNextNonWhitespace() if (next.type == Token.Type.COMMA || next.type == Token.Type.RPAREN) { val localVar = LocalVarRef(name, t1.pos) - return ParsedArgument(statement(t1.pos) { localVar.evalValue(it) }, t1.pos, isSplat = false, name = name) + return ParsedArgument(statement(t1.pos, f = object : ScopeCallable { + override suspend fun call(scp: Scope): Obj = localVar.evalValue(scp) + }), t1.pos, isSplat = false, name = name) } val rhs = parseExpression() ?: t2.raiseSyntax("expected expression after named argument '${name}:'") return ParsedArgument(rhs, t1.pos, isSplat = false, name = name) @@ -1166,9 +1204,9 @@ class Compiler( val callableAccessor = parseLambdaExpression() args += ParsedArgument( // transform ObjRef to the callable value - statement { - callableAccessor.get(this).value - }, + statement(f = object : ScopeCallable { + override suspend fun call(scp: Scope): Obj = callableAccessor.get(scp).value + }), end.pos ) lastBlockArgument = true @@ -1194,7 +1232,9 @@ class Compiler( val next = cc.peekNextNonWhitespace() if (next.type == Token.Type.COMMA || next.type == Token.Type.RPAREN) { val localVar = LocalVarRef(name, t1.pos) - return ParsedArgument(statement(t1.pos) { localVar.evalValue(it) }, t1.pos, isSplat = false, name = name) + return ParsedArgument(statement(t1.pos, f = object : ScopeCallable { + override suspend fun call(scp: Scope): Obj = localVar.evalValue(scp) + }), t1.pos, isSplat = false, name = name) } val rhs = parseExpression() ?: t2.raiseSyntax("expected expression after named argument '${name}:'") return ParsedArgument(rhs, t1.pos, isSplat = false, name = name) @@ -1246,7 +1286,9 @@ class Compiler( // into the lambda body. This ensures expected order: // foo { ... }.bar() == (foo { ... }).bar() val callableAccessor = parseLambdaExpression() - val argStmt = statement { callableAccessor.get(this).value } + val argStmt = statement(f = object : ScopeCallable { + override suspend fun call(scp: Scope): Obj = callableAccessor.get(scp).value + }) listOf(ParsedArgument(argStmt, cc.currentPos())) } else { val r = parseArgs() @@ -1339,17 +1381,23 @@ class Compiler( return parseNumberOrNull(isPlus) ?: throw ScriptError(cc.currentPos(), "Expecting number") } - suspend fun parseAnnotation(t: Token): (suspend (Scope, ObjString, Statement) -> Statement) { + interface AnnotationProcessor { + suspend fun process(scope: Scope, name: ObjString, body: Statement): Statement + } + + suspend fun parseAnnotation(t: Token): AnnotationProcessor { val extraArgs = parseArgsOrNull() // println("annotation ${t.value}: args: $extraArgs") - return { scope, name, body -> - val extras = extraArgs?.first?.toArguments(scope, extraArgs.second)?.list - val required = listOf(name, body) - val args = extras?.let { required + it } ?: required - val fn = scope.get(t.value)?.value ?: scope.raiseSymbolNotFound("annotation not found: ${t.value}") - if (fn !is Statement) scope.raiseIllegalArgument("annotation must be callable, got ${fn.objClass}") - (fn.execute(scope.createChildScope(Arguments(args))) as? Statement) - ?: scope.raiseClassCastError("function annotation must return callable") + return object : AnnotationProcessor { + override suspend fun process(scope: Scope, name: ObjString, body: Statement): Statement { + val extras = extraArgs?.first?.toArguments(scope, extraArgs.second)?.list + val required = listOf(name, body) + val args = extras?.let { required + it } ?: required + val fn = scope.get(t.value)?.value ?: scope.raiseSymbolNotFound("annotation not found: ${t.value}") + if (fn !is Statement) scope.raiseIllegalArgument("annotation must be callable, got ${fn.objClass}") + return (fn.execute(scope.createChildScope(Arguments(args))) as? Statement) + ?: scope.raiseClassCastError("function annotation must return callable") + } } } @@ -1526,21 +1574,25 @@ class Compiler( lastParsedBlockRange?.let { range -> miniSink?.onInitDecl(MiniInitDecl(MiniRange(id.pos, range.end), id.pos)) } - val initStmt = statement(id.pos) { scp -> - val cls = scp.thisObj.objClass - val saved = scp.currentClassCtx - scp.currentClassCtx = cls - try { - block.execute(scp) - } finally { - scp.currentClassCtx = saved + val initStmt = statement(id.pos, f = object : ScopeCallable { + override suspend fun call(scp: Scope): Obj { + val cls = scp.thisObj.objClass + val saved = scp.currentClassCtx + scp.currentClassCtx = cls + try { + block.execute(scp) + } finally { + scp.currentClassCtx = saved + } + return ObjVoid } - ObjVoid - } - statement { - currentClassCtx?.instanceInitializers?.add(initStmt) - ObjVoid - } + }) + statement(f = object : ScopeCallable { + override suspend fun call(scp: Scope): Obj { + scp.currentClassCtx?.instanceInitializers?.add(initStmt) + return ObjVoid + } + }) } else null } @@ -1695,20 +1747,24 @@ class Compiler( // we need a copy in the closure: val isIn = t.type == Token.Type.IN val container = parseExpression() ?: throw ScriptError(cc.currentPos(), "type expected") - currentCondition += statement { - val r = container.execute(this).contains(this, whenValue) - ObjBool(if (isIn) r else !r) - } + currentCondition += statement(f = object : ScopeCallable { + override suspend fun call(scp: Scope): Obj { + val r = container.execute(scp).contains(scp, whenValue) + return ObjBool(if (isIn) r else !r) + } + }) } Token.Type.IS, Token.Type.NOTIS -> { // we need a copy in the closure: val isIn = t.type == Token.Type.IS val caseType = parseExpression() ?: throw ScriptError(cc.currentPos(), "type expected") - currentCondition += statement { - val r = whenValue.isInstanceOf(caseType.execute(this)) - ObjBool(if (isIn) r else !r) - } + currentCondition += statement(f = object : ScopeCallable { + override suspend fun call(scp: Scope): Obj { + val r = whenValue.isInstanceOf(caseType.execute(scp)) + return ObjBool(if (isIn) r else !r) + } + }) } Token.Type.COMMA -> @@ -1737,9 +1793,11 @@ class Compiler( cc.previous() val x = parseExpression() ?: throw ScriptError(cc.currentPos(), "when case condition expected") - currentCondition += statement { - ObjBool(x.execute(this).compareTo(this, whenValue) == 0) - } + currentCondition += statement(f = object : ScopeCallable { + override suspend fun call(scp: Scope): Obj { + return ObjBool(x.execute(scp).compareTo(scp, whenValue) == 0) + } + }) } } } @@ -1750,20 +1808,22 @@ class Compiler( for (c in currentCondition) cases += WhenCase(c, block) } } - statement { - var result: Obj = ObjVoid - // in / is and like uses whenValue from closure: - whenValue = value.execute(this) - var found = false - for (c in cases) - if (c.condition.execute(this).toBool()) { - result = c.block.execute(this) - found = true - break - } - if (!found && elseCase != null) result = elseCase.execute(this) - result - } + statement(f = object : ScopeCallable { + override suspend fun call(scp: Scope): Obj { + var result: Obj = ObjVoid + // in / is and like uses whenValue from closure: + whenValue = value.execute(scp) + var found = false + for (c in cases) + if (c.condition.execute(scp).toBool()) { + result = c.block.execute(scp) + found = true + break + } + if (!found && elseCase != null) result = elseCase.execute(scp) + return result + } + }) } else { // when { cond -> ... } TODO("when without object is not yet implemented") @@ -1774,30 +1834,33 @@ class Compiler( val throwStatement = parseStatement() ?: throw ScriptError(cc.currentPos(), "throw object expected") // Important: bind the created statement to the position of the `throw` keyword so that // any raised error reports the correct source location. - return statement(start) { sc -> - var errorObject = throwStatement.execute(sc) - // Rebind error scope to the throw-site position so ScriptError.pos is accurate - val throwScope = sc.createChildScope(pos = start) - if (errorObject is ObjString) { - errorObject = ObjException(throwScope, errorObject.value).apply { getStackTrace() } + return statement(start, f = object : ScopeCallable { + override suspend fun call(sc: Scope): Obj { + var errorObject = throwStatement.execute(sc) + // Rebind error scope to the throw-site position so ScriptError.pos is accurate + val throwScope = sc.createChildScope(pos = start) + if (errorObject is ObjString) { + errorObject = ObjException(throwScope, errorObject.value).apply { getStackTrace() } + } + if (!errorObject.isInstanceOf(ObjException.Root)) { + throwScope.raiseError("this is not an exception object: $errorObject") + } + if (errorObject is ObjException) { + errorObject = ObjException( + errorObject.exceptionClass, + throwScope, + errorObject.message, + errorObject.extraData, + errorObject.useStackTrace + ).apply { getStackTrace() } + throwScope.raiseError(errorObject) + } else { + val msg = errorObject.invokeInstanceMethod(sc, "message").toString(sc).value + throwScope.raiseError(errorObject, start, msg) + } + return ObjVoid } - if (!errorObject.isInstanceOf(ObjException.Root)) { - throwScope.raiseError("this is not an exception object: $errorObject") - } - if (errorObject is ObjException) { - errorObject = ObjException( - errorObject.exceptionClass, - throwScope, - errorObject.message, - errorObject.extraData, - errorObject.useStackTrace - ).apply { getStackTrace() } - throwScope.raiseError(errorObject) - } else { - val msg = errorObject.invokeInstanceMethod(sc, "message").toString(sc).value - throwScope.raiseError(errorObject, start, msg) - } - } + }) } private data class CatchBlockData( @@ -1868,50 +1931,52 @@ class Compiler( if (catches.isEmpty() && finallyClause == null) throw ScriptError(cc.currentPos(), "try block must have either catch or finally clause or both") - return statement { - var result: Obj = ObjVoid - try { - // body is a parsed block, it already has separate context - result = body.execute(this) - } catch (e: ReturnException) { - throw e - } catch (e: LoopBreakContinueException) { - throw e - } catch (e: Exception) { - // convert to appropriate exception - val caughtObj = when (e) { - is ExecutionError -> e.errorObject - else -> ObjUnknownException(this, e.message ?: e.toString()) - } - // let's see if we should catch it: - var isCaught = false - for (cdata in catches) { - var match: Obj? = null - for (exceptionClassName in cdata.classNames) { - val exObj = this[exceptionClassName]?.value as? ObjClass - ?: raiseSymbolNotFound("error class does not exist or is not a class: $exceptionClassName") - if (caughtObj.isInstanceOf(exObj)) { - match = caughtObj + return statement(f = object : ScopeCallable { + override suspend fun call(scp: Scope): Obj { + var result: Obj = ObjVoid + try { + // body is a parsed block, it already has separate context + result = body.execute(scp) + } catch (e: ReturnException) { + throw e + } catch (e: LoopBreakContinueException) { + throw e + } catch (e: Exception) { + // convert to appropriate exception + val caughtObj = when (e) { + is ExecutionError -> e.errorObject + else -> ObjUnknownException(scp, e.message ?: e.toString()) + } + // let's see if we should catch it: + var isCaught = false + for (cdata in catches) { + var match: Obj? = null + for (exceptionClassName in cdata.classNames) { + val exObj = scp[exceptionClassName]?.value as? ObjClass + ?: scp.raiseSymbolNotFound("error class does not exist or is not a class: $exceptionClassName") + if (caughtObj.isInstanceOf(exObj)) { + match = caughtObj + break + } + } + if (match != null) { + val catchContext = scp.createChildScope(pos = cdata.catchVar.pos) + catchContext.addItem(cdata.catchVar.value, false, caughtObj) + result = cdata.block.execute(catchContext) + isCaught = true break } } - if (match != null) { - val catchContext = this.createChildScope(pos = cdata.catchVar.pos) - catchContext.addItem(cdata.catchVar.value, false, caughtObj) - result = cdata.block.execute(catchContext) - isCaught = true - break - } + // rethrow if not caught this exception + if (!isCaught) + throw e + } finally { + // finally clause does not alter result! + finallyClause?.execute(scp) } - // rethrow if not caught this exception - if (!isCaught) - throw e - } finally { - // finally clause does not alter result! - finallyClause?.execute(this) + return result } - result - } + }) } private fun parseEnumDeclaration(isExtern: Boolean = false): Statement { @@ -1964,11 +2029,13 @@ class Compiler( ) ) - return statement { - ObjEnumClass.createSimpleEnum(nameToken.value, names).also { - addItem(nameToken.value, false, it, recordType = ObjRecord.Type.Enum) + return statement(f = object : ScopeCallable { + override suspend fun call(scp: Scope): Obj { + return ObjEnumClass.createSimpleEnum(nameToken!!.value, names).also { + scp.addItem(nameToken.value, false, it, recordType = ObjRecord.Type.Enum) + } } - } + }) } private suspend fun parseObjectDeclaration(isExtern: Boolean = false): Statement { @@ -2003,83 +2070,87 @@ class Compiler( // Robust body detection var classBodyRange: MiniRange? = null - val bodyInit: Statement? = inCodeContext(CodeContext.ClassBody(className, isExtern = isExtern)) { - val saved = cc.savePos() - val nextBody = cc.nextNonWhitespace() - if (nextBody.type == Token.Type.LBRACE) { - // Emit MiniClassDecl before body parsing to track members via enter/exit - run { - val node = MiniClassDecl( - range = MiniRange(startPos, cc.currentPos()), - name = className, - bases = baseSpecs.map { it.name }, - bodyRange = null, - doc = doc, - nameStart = nameToken?.pos ?: startPos, - isObject = true, - isExtern = isExtern - ) - miniSink?.onEnterClass(node) + val bodyInit: Statement? = inCodeContext(CodeContext.ClassBody(className, isExtern = isExtern), object : ContextBuilder { + override suspend fun build(): Statement? { + val saved = cc.savePos() + val nextBody = cc.nextNonWhitespace() + if (nextBody.type == Token.Type.LBRACE) { + // Emit MiniClassDecl before body parsing to track members via enter/exit + run { + val node = MiniClassDecl( + range = MiniRange(startPos, cc.currentPos()), + name = className, + bases = baseSpecs.map { it.name }, + bodyRange = null, + doc = doc, + nameStart = nameToken?.pos ?: startPos, + isObject = true, + isExtern = isExtern + ) + miniSink?.onEnterClass(node) + } + val bodyStart = nextBody.pos + val st = withLocalNames(emptySet()) { + parseScript() + } + val rbTok = cc.next() + if (rbTok.type != Token.Type.RBRACE) throw ScriptError(rbTok.pos, "unbalanced braces in object body") + classBodyRange = MiniRange(bodyStart, rbTok.pos) + miniSink?.onExitClass(rbTok.pos) + return st + } else { + // No body, but still emit the class + run { + val node = MiniClassDecl( + range = MiniRange(startPos, cc.currentPos()), + name = className, + bases = baseSpecs.map { it.name }, + bodyRange = null, + doc = doc, + nameStart = nameToken?.pos ?: startPos, + isObject = true, + isExtern = isExtern + ) + miniSink?.onClassDecl(node) + } + cc.restorePos(saved) + return null } - val bodyStart = nextBody.pos - val st = withLocalNames(emptySet()) { - parseScript() - } - val rbTok = cc.next() - if (rbTok.type != Token.Type.RBRACE) throw ScriptError(rbTok.pos, "unbalanced braces in object body") - classBodyRange = MiniRange(bodyStart, rbTok.pos) - miniSink?.onExitClass(rbTok.pos) - st - } else { - // No body, but still emit the class - run { - val node = MiniClassDecl( - range = MiniRange(startPos, cc.currentPos()), - name = className, - bases = baseSpecs.map { it.name }, - bodyRange = null, - doc = doc, - nameStart = nameToken?.pos ?: startPos, - isObject = true, - isExtern = isExtern - ) - miniSink?.onClassDecl(node) - } - cc.restorePos(saved) - null } - } + }) val initScope = popInitScope() - return statement(startPos) { context -> - val parentClasses = baseSpecs.map { baseSpec -> - val rec = context[baseSpec.name] ?: throw ScriptError(startPos, "unknown base class: ${baseSpec.name}") - (rec.value as? ObjClass) ?: throw ScriptError(startPos, "${baseSpec.name} is not a class") + return statement(startPos, f = object : ScopeCallable { + override suspend fun call(context: Scope): Obj { + val parentClasses = baseSpecs.map { baseSpec -> + val rec = context[baseSpec.name] ?: throw ScriptError(startPos, "unknown base class: ${baseSpec.name}") + (rec.value as? ObjClass) ?: throw ScriptError(startPos, "${baseSpec.name} is not a class") + } + + val newClass = ObjInstanceClass(className, *parentClasses.toTypedArray()) + newClass.isAnonymous = nameToken == null + newClass.constructorMeta = ArgsDeclaration(emptyList(), Token.Type.RPAREN) + for (i in parentClasses.indices) { + val argsList = baseSpecs[i].args + // In object, we evaluate parent args once at creation time + if (argsList != null) newClass.directParentArgs[parentClasses[i]] = argsList + } + + val classScope = context.createChildScope(newThisObj = newClass) + classScope.currentClassCtx = newClass + newClass.classScope = classScope + classScope.addConst("object", newClass) + + bodyInit?.execute(classScope) + + // Create instance (singleton) + val instance = newClass.callOn(context.createChildScope(Arguments.EMPTY)) + if (nameToken != null) + context.addItem(className, false, instance) + return instance } - - val newClass = ObjInstanceClass(className, *parentClasses.toTypedArray()) - newClass.isAnonymous = nameToken == null - newClass.constructorMeta = ArgsDeclaration(emptyList(), Token.Type.RPAREN) - for (i in parentClasses.indices) { - val argsList = baseSpecs[i].args - // In object, we evaluate parent args once at creation time - if (argsList != null) newClass.directParentArgs[parentClasses[i]] = argsList - } - - val classScope = context.createChildScope(newThisObj = newClass) - classScope.currentClassCtx = newClass - newClass.classScope = classScope - classScope.addConst("object", newClass) - - bodyInit?.execute(classScope) - - // Create instance (singleton) - val instance = newClass.callOn(context.createChildScope(Arguments.EMPTY)) - if (nameToken != null) - context.addItem(className, false, instance) - instance - } + }) } private suspend fun parseClassDeclaration(isAbstract: Boolean = false, isExtern: Boolean = false): Statement { @@ -2088,182 +2159,188 @@ class Compiler( val doc = pendingDeclDoc ?: consumePendingDoc() pendingDeclDoc = null pendingDeclStart = null - return inCodeContext(CodeContext.ClassBody(nameToken.value, isExtern = isExtern)) { - val constructorArgsDeclaration = - if (cc.skipTokenOfType(Token.Type.LPAREN, isOptional = true)) - parseArgsDeclaration(isClassDeclaration = true) - else ArgsDeclaration(emptyList(), Token.Type.RPAREN) + return inCodeContext(CodeContext.ClassBody(nameToken.value, isExtern = isExtern), object : ContextBuilder { + override suspend fun build(): Statement { + val constructorArgsDeclaration = + if (cc.skipTokenOfType(Token.Type.LPAREN, isOptional = true)) + parseArgsDeclaration(isClassDeclaration = true) + else ArgsDeclaration(emptyList(), Token.Type.RPAREN) - if (constructorArgsDeclaration != null && constructorArgsDeclaration.endTokenType != Token.Type.RPAREN) - throw ScriptError( - nameToken.pos, - "Bad class declaration: expected ')' at the end of the primary constructor" - ) + if (constructorArgsDeclaration != null && constructorArgsDeclaration.endTokenType != Token.Type.RPAREN) + throw ScriptError( + nameToken.pos, + "Bad class declaration: expected ')' at the end of the primary constructor" + ) - // Optional base list: ":" Base ("," Base)* where Base := ID ( "(" args? ")" )? - data class BaseSpec(val name: String, val args: List?) + // Optional base list: ":" Base ("," Base)* where Base := ID ( "(" args? ")" )? + data class BaseSpec(val name: String, val args: List?) - val baseSpecs = mutableListOf() - if (cc.skipTokenOfType(Token.Type.COLON, isOptional = true)) { - do { - val baseId = cc.requireToken(Token.Type.ID, "base class name expected") - var argsList: List? = null - // Optional constructor args of the base — parse and ignore for now (MVP), just to consume tokens - if (cc.skipTokenOfType(Token.Type.LPAREN, isOptional = true)) { - // Parse args without consuming any following block so that a class body can follow safely - argsList = parseArgsNoTailBlock() + val baseSpecs = mutableListOf() + if (cc.skipTokenOfType(Token.Type.COLON, isOptional = true)) { + do { + val baseId = cc.requireToken(Token.Type.ID, "base class name expected") + var argsList: List? = null + // Optional constructor args of the base — parse and ignore for now (MVP), just to consume tokens + if (cc.skipTokenOfType(Token.Type.LPAREN, isOptional = true)) { + // Parse args without consuming any following block so that a class body can follow safely + argsList = parseArgsNoTailBlock() + } + baseSpecs += BaseSpec(baseId.value, argsList) + } while (cc.skipTokenOfType(Token.Type.COMMA, isOptional = true)) + } + + cc.skipTokenOfType(Token.Type.NEWLINE, isOptional = true) + + pushInitScope() + + // Robust body detection: peek next non-whitespace token; if it's '{', consume and parse the body + var classBodyRange: MiniRange? = null + val bodyInit: Statement? = run { + val saved = cc.savePos() + val next = cc.nextNonWhitespace() + + val ctorFields = mutableListOf() + constructorArgsDeclaration?.let { ad -> + for (p in ad.params) { + val at = p.accessType + val mutable = at == AccessType.Var + ctorFields += MiniCtorField( + name = p.name, + mutable = mutable, + type = p.miniType, + nameStart = p.pos + ) + } } - baseSpecs += BaseSpec(baseId.value, argsList) - } while (cc.skipTokenOfType(Token.Type.COMMA, isOptional = true)) - } - cc.skipTokenOfType(Token.Type.NEWLINE, isOptional = true) - - pushInitScope() - - // Robust body detection: peek next non-whitespace token; if it's '{', consume and parse the body - var classBodyRange: MiniRange? = null - val bodyInit: Statement? = run { - val saved = cc.savePos() - val next = cc.nextNonWhitespace() - - val ctorFields = mutableListOf() - constructorArgsDeclaration?.let { ad -> - for (p in ad.params) { - val at = p.accessType - val mutable = at == AccessType.Var - ctorFields += MiniCtorField( - name = p.name, - mutable = mutable, - type = p.miniType, - nameStart = p.pos - ) + if (next.type == Token.Type.LBRACE) { + // Emit MiniClassDecl before body parsing to track members via enter/exit + run { + val node = MiniClassDecl( + range = MiniRange(startPos, cc.currentPos()), + name = nameToken.value, + bases = baseSpecs.map { it.name }, + bodyRange = null, + ctorFields = ctorFields, + doc = doc, + nameStart = nameToken.pos, + isExtern = isExtern + ) + miniSink?.onEnterClass(node) + } + // parse body + val bodyStart = next.pos + val st = withLocalNames(constructorArgsDeclaration?.params?.map { it.name }?.toSet() ?: emptySet()) { + parseScript() + } + val rbTok = cc.next() + if (rbTok.type != Token.Type.RBRACE) throw ScriptError(rbTok.pos, "unbalanced braces in class body") + classBodyRange = MiniRange(bodyStart, rbTok.pos) + miniSink?.onExitClass(rbTok.pos) + st + } else { + // No body, but still emit the class + run { + val node = MiniClassDecl( + range = MiniRange(startPos, cc.currentPos()), + name = nameToken.value, + bases = baseSpecs.map { it.name }, + bodyRange = null, + ctorFields = ctorFields, + doc = doc, + nameStart = nameToken.pos, + isExtern = isExtern + ) + miniSink?.onClassDecl(node) + } + // restore if no body starts here + cc.restorePos(saved) + null } } - if (next.type == Token.Type.LBRACE) { - // Emit MiniClassDecl before body parsing to track members via enter/exit - run { - val node = MiniClassDecl( - range = MiniRange(startPos, cc.currentPos()), - name = nameToken.value, - bases = baseSpecs.map { it.name }, - bodyRange = null, - ctorFields = ctorFields, - doc = doc, - nameStart = nameToken.pos, - isExtern = isExtern - ) - miniSink?.onEnterClass(node) - } - // parse body - val bodyStart = next.pos - val st = withLocalNames(constructorArgsDeclaration?.params?.map { it.name }?.toSet() ?: emptySet()) { - parseScript() - } - val rbTok = cc.next() - if (rbTok.type != Token.Type.RBRACE) throw ScriptError(rbTok.pos, "unbalanced braces in class body") - classBodyRange = MiniRange(bodyStart, rbTok.pos) - miniSink?.onExitClass(rbTok.pos) - st - } else { - // No body, but still emit the class - run { - val node = MiniClassDecl( - range = MiniRange(startPos, cc.currentPos()), - name = nameToken.value, - bases = baseSpecs.map { it.name }, - bodyRange = null, - ctorFields = ctorFields, - doc = doc, - nameStart = nameToken.pos, - isExtern = isExtern - ) - miniSink?.onClassDecl(node) - } - // restore if no body starts here - cc.restorePos(saved) - null - } - } + val initScope = popInitScope() - val initScope = popInitScope() - - // create class - val className = nameToken.value + // create class + val className = nameToken.value // @Suppress("UNUSED_VARIABLE") val defaultAccess = if (isStruct) AccessType.Variable else AccessType.Initialization // @Suppress("UNUSED_VARIABLE") val defaultVisibility = Visibility.Public - // create instance constructor - // create custom objClass with all fields and instance constructor + // create instance constructor + // create custom objClass with all fields and instance constructor - val constructorCode = statement { - // constructor code is registered with class instance and is called over - // new `thisObj` already set by class to ObjInstance.instanceContext - val instance = thisObj as ObjInstance - // Constructor parameters have been assigned to instance scope by ObjClass.callOn before - // invoking parent/child constructors. - // IMPORTANT: do not execute class body here; class body was executed once in the class scope - // to register methods and prepare initializers. Instance constructor should be empty unless - // we later add explicit constructor body syntax. - instance - } - statement { - // the main statement should create custom ObjClass instance with field - // accessors, constructor registration, etc. - // Resolve parent classes by name at execution time - val parentClasses = baseSpecs.map { baseSpec -> - val rec = - this[baseSpec.name] ?: throw ScriptError(nameToken.pos, "unknown base class: ${baseSpec.name}") - (rec.value as? ObjClass) ?: throw ScriptError(nameToken.pos, "${baseSpec.name} is not a class") - } - - val newClass = ObjInstanceClass(className, *parentClasses.toTypedArray()).also { - it.isAbstract = isAbstract - it.instanceConstructor = constructorCode - it.constructorMeta = constructorArgsDeclaration - // Attach per-parent constructor args (thunks) if provided - for (i in parentClasses.indices) { - val argsList = baseSpecs[i].args - if (argsList != null) it.directParentArgs[parentClasses[i]] = argsList + val constructorCode = statement(f = object : ScopeCallable { + override suspend fun call(scp: Scope): Obj { + // constructor code is registered with class instance and is called over + // new `thisObj` already set by class to ObjInstance.instanceContext + val instance = scp.thisObj as ObjInstance + // Constructor parameters have been assigned to instance scope by ObjClass.callOn before + // invoking parent/child constructors. + // IMPORTANT: do not execute class body here; class body was executed once in the class scope + // to register methods and prepare initializers. Instance constructor should be empty unless + // we later add explicit constructor body syntax. + return instance } - // Register constructor fields in the class members - constructorArgsDeclaration?.params?.forEach { p -> - if (p.accessType != null) { - it.createField( - p.name, ObjNull, - isMutable = p.accessType == AccessType.Var, - visibility = p.visibility ?: Visibility.Public, - declaringClass = it, - // Constructor fields are not currently supporting override/closed in parser - // but we should pass Pos.builtIn to skip validation for now if needed, - // or p.pos to allow it. - pos = Pos.builtIn, - isTransient = p.isTransient, - type = ObjRecord.Type.ConstructorField - ) + }) + return statement(f = object : ScopeCallable { + override suspend fun call(scp: Scope): Obj { + // the main statement should create custom ObjClass instance with field + // accessors, constructor registration, etc. + // Resolve parent classes by name at execution time + val parentClasses = baseSpecs.map { baseSpec -> + val rec = + scp[baseSpec.name] ?: throw ScriptError(nameToken.pos, "unknown base class: ${baseSpec.name}") + (rec.value as? ObjClass) ?: throw ScriptError(nameToken.pos, "${baseSpec.name} is not a class") } - } - } - addItem(className, false, newClass) - // Prepare class scope for class-scope members (static) and future registrations - val classScope = createChildScope(newThisObj = newClass) - // Set lexical class context for visibility tagging inside class body - classScope.currentClassCtx = newClass - newClass.classScope = classScope - // Execute class body once in class scope to register instance methods and prepare instance field initializers - bodyInit?.execute(classScope) - if (initScope.isNotEmpty()) { - for (s in initScope) - s.execute(classScope) - } - newClass.checkAbstractSatisfaction(nameToken.pos) - // Debug summary: list registered instance methods and class-scope functions for this class - newClass + val newClass = ObjInstanceClass(className, *parentClasses.toTypedArray()).also { + it.isAbstract = isAbstract + it.instanceConstructor = constructorCode + it.constructorMeta = constructorArgsDeclaration + // Attach per-parent constructor args (thunks) if provided + for (i in parentClasses.indices) { + val argsList = baseSpecs[i].args + if (argsList != null) it.directParentArgs[parentClasses[i]] = argsList + } + // Register constructor fields in the class members + constructorArgsDeclaration?.params?.forEach { p -> + if (p.accessType != null) { + it.createField( + p.name, ObjNull, + isMutable = p.accessType == AccessType.Var, + visibility = p.visibility ?: Visibility.Public, + declaringClass = it, + // Constructor fields are not currently supporting override/closed in parser + // but we should pass Pos.builtIn to skip validation for now if needed, + // or p.pos to allow it. + pos = Pos.builtIn, + isTransient = p.isTransient, + type = ObjRecord.Type.ConstructorField + ) + } + } + } + + scp.addItem(className, false, newClass) + // Prepare class scope for class-scope members (static) and future registrations + val classScope = scp.createChildScope(newThisObj = newClass) + // Set lexical class context for visibility tagging inside class body + classScope.currentClassCtx = newClass + newClass.classScope = classScope + // Execute class body once in class scope to register instance methods and prepare instance field initializers + bodyInit?.execute(classScope) + if (initScope.isNotEmpty()) { + for (s in initScope) + s.execute(classScope) + } + newClass.checkAbstractSatisfaction(nameToken.pos) + // Debug summary: list registered instance methods and class-scope functions for this class + return newClass + } + }) } - } + }) } @@ -2319,79 +2396,81 @@ class Compiler( Triple(loopParsed.first, loopParsed.second, elseStmt) } - return statement(body.pos) { cxt -> - val forContext = cxt.createChildScope(start) + return statement(body.pos, f = object : ScopeCallable { + override suspend fun call(cxt: Scope): Obj { + val forContext = cxt.createChildScope(start) - // loop var: StoredObject - val loopSO = forContext.addItem(tVar.value, true, ObjNull) + // loop var: StoredObject + val loopSO = forContext.addItem(tVar.value, true, ObjNull) - // insofar we suggest source object is enumerable. Later we might need to add checks - val sourceObj = source.execute(forContext) + // insofar we suggest source object is enumerable. Later we might need to add checks + val sourceObj = source.execute(forContext) - if (sourceObj is ObjRange && sourceObj.isIntRange && PerfFlags.PRIMITIVE_FASTOPS) { - loopIntRange( - forContext, - sourceObj.start!!.toLong(), - if (sourceObj.isEndInclusive) - sourceObj.end!!.toLong() + 1 - else - sourceObj.end!!.toLong(), - loopSO, - body, - elseStatement, - label, - canBreak - ) - } else if (sourceObj.isInstanceOf(ObjIterable)) { - loopIterable(forContext, sourceObj, loopSO, body, elseStatement, label, canBreak) - } else { - val size = runCatching { sourceObj.readField(forContext, "size").value.toInt() } - .getOrElse { - throw ScriptError( - tOp.pos, - "object is not enumerable: no size in $sourceObj", - it - ) - } - - var result: Obj = ObjVoid - var breakCaught = false - - if (size > 0) { - var current = runCatching { sourceObj.getAt(forContext, ObjInt.of(0)) } + if (sourceObj is ObjRange && sourceObj.isIntRange && PerfFlags.PRIMITIVE_FASTOPS) { + return loopIntRange( + forContext, + sourceObj.start!!.toLong(), + if (sourceObj.isEndInclusive) + sourceObj.end!!.toLong() + 1 + else + sourceObj.end!!.toLong(), + loopSO, + body, + elseStatement, + label, + canBreak + ) + } else if (sourceObj.isInstanceOf(ObjIterable)) { + return loopIterable(forContext, sourceObj, loopSO, body, elseStatement, label, canBreak) + } else { + val size = runCatching { sourceObj.readField(forContext, "size").value.toInt() } .getOrElse { throw ScriptError( tOp.pos, - "object is not enumerable: no index access for ${sourceObj.inspect(cxt)}", + "object is not enumerable: no size in $sourceObj", it ) } - var index = 0 - while (true) { - loopSO.value = current - try { - result = body.execute(forContext) - } catch (lbe: LoopBreakContinueException) { - if (lbe.label == label || lbe.label == null) { - breakCaught = true - if (lbe.doContinue) continue - else { - result = lbe.result - break - } - } else - throw lbe + + var result: Obj = ObjVoid + var breakCaught = false + + if (size > 0) { + var current = runCatching { sourceObj.getAt(forContext, ObjInt.of(0)) } + .getOrElse { + throw ScriptError( + tOp.pos, + "object is not enumerable: no index access for ${sourceObj.inspect(cxt)}", + it + ) + } + var index = 0 + while (true) { + loopSO.value = current + try { + result = body.execute(forContext) + } catch (lbe: LoopBreakContinueException) { + if (lbe.label == label || lbe.label == null) { + breakCaught = true + if (lbe.doContinue) continue + else { + result = lbe.result + break + } + } else + throw lbe + } + if (++index >= size) break + current = sourceObj.getAt(forContext, ObjInt.of(index.toLong())) } - if (++index >= size) break - current = sourceObj.getAt(forContext, ObjInt.of(index.toLong())) } + if (!breakCaught && elseStatement != null) { + result = elseStatement.execute(cxt) + } + return result } - if (!breakCaught && elseStatement != null) { - result = elseStatement.execute(cxt) - } - result } - } + }) } else { // maybe other loops? throw ScriptError(tOp.pos, "Unsupported for-loop syntax") @@ -2432,28 +2511,30 @@ class Compiler( ): Obj { var result: Obj = ObjVoid var breakCaught = false - sourceObj.enumerate(forScope) { item -> - loopVar.value = item - if (catchBreak) { - try { + sourceObj.enumerate(forScope, object : EnumerateCallback { + override suspend fun call(item: Obj): Boolean { + loopVar.value = item + if (catchBreak) { + try { + result = body.execute(forScope) + return true + } catch (lbe: LoopBreakContinueException) { + if (lbe.label == label || lbe.label == null) { + if (lbe.doContinue) return true + else { + result = lbe.result + breakCaught = true + return false + } + } else + throw lbe + } + } else { result = body.execute(forScope) - true - } catch (lbe: LoopBreakContinueException) { - if (lbe.label == label || lbe.label == null) { - if (lbe.doContinue) true - else { - result = lbe.result - breakCaught = true - false - } - } else - throw lbe + return true } - } else { - result = body.execute(forScope) - true } - } + }) return if (!breakCaught && elseStatement != null) { elseStatement.execute(forScope) } else result @@ -2484,32 +2565,34 @@ class Compiler( null } - return statement(body.pos) { - var wasBroken = false - var result: Obj = ObjVoid - while (true) { - val doScope = it.createChildScope().apply { skipScopeCreation = true } - try { - result = body.execute(doScope) - } catch (e: LoopBreakContinueException) { - if (e.label == label || e.label == null) { - if (!e.doContinue) { - result = e.result - wasBroken = true - break + return statement(body.pos, f = object : ScopeCallable { + override suspend fun call(scp: Scope): Obj { + var wasBroken = false + var result: Obj = ObjVoid + while (true) { + val doScope = scp.createChildScope().apply { skipScopeCreation = true } + try { + result = body.execute(doScope) + } catch (e: LoopBreakContinueException) { + if (e.label == label || e.label == null) { + if (!e.doContinue) { + result = e.result + wasBroken = true + break + } + // for continue: just fall through to condition check below + } else { + throw e } - // for continue: just fall through to condition check below - } else { - throw e + } + if (!condition.execute(doScope).toBool()) { + break } } - if (!condition.execute(doScope).toBool()) { - break - } + if (!wasBroken) elseStatement?.let { s -> result = s.execute(scp) } + return result } - if (!wasBroken) elseStatement?.let { s -> result = s.execute(it) } - result - } + }) } private suspend fun parseWhileStatement(): Statement { @@ -2532,31 +2615,33 @@ class Compiler( cc.previous() null } - return statement(body.pos) { - var result: Obj = ObjVoid - var wasBroken = false - while (condition.execute(it).toBool()) { - val loopScope = it.createChildScope() - if (canBreak) { - try { + return statement(body.pos, f = object : ScopeCallable { + override suspend fun call(scp: Scope): Obj { + var result: Obj = ObjVoid + var wasBroken = false + while (condition.execute(scp).toBool()) { + val loopScope = scp.createChildScope() + if (canBreak) { + try { + result = body.execute(loopScope) + } catch (lbe: LoopBreakContinueException) { + if (lbe.label == label || lbe.label == null) { + if (lbe.doContinue) continue + else { + result = lbe.result + wasBroken = true + break + } + } else + throw lbe + } + } else result = body.execute(loopScope) - } catch (lbe: LoopBreakContinueException) { - if (lbe.label == label || lbe.label == null) { - if (lbe.doContinue) continue - else { - result = lbe.result - wasBroken = true - break - } - } else - throw lbe - } - } else - result = body.execute(loopScope) + } + if (!wasBroken) elseStatement?.let { s -> result = s.execute(scp) } + return result } - if (!wasBroken) elseStatement?.let { s -> result = s.execute(it) } - result - } + }) } private suspend fun parseBreakStatement(start: Pos): Statement { @@ -2590,14 +2675,16 @@ class Compiler( cc.addBreak() - return statement(start) { - val returnValue = resultExpr?.execute(it)// ?: ObjVoid - throw LoopBreakContinueException( - doContinue = false, - label = label, - result = returnValue ?: ObjVoid - ) - } + return statement(start, f = object : ScopeCallable { + override suspend fun call(scp: Scope): Obj { + val returnValue = resultExpr?.execute(scp)// ?: ObjVoid + throw LoopBreakContinueException( + doContinue = false, + label = label, + result = returnValue ?: ObjVoid + ) + } + }) } private fun parseContinueStatement(start: Pos): Statement { @@ -2614,12 +2701,14 @@ class Compiler( } cc.addBreak() - return statement(start) { - throw LoopBreakContinueException( - doContinue = true, - label = label, - ) - } + return statement(start, f = object : ScopeCallable { + override suspend fun call(scp: Scope): Obj { + throw LoopBreakContinueException( + doContinue = true, + label = label, + ) + } + }) } private suspend fun parseReturnStatement(start: Pos): Statement { @@ -2648,10 +2737,12 @@ class Compiler( parseExpression() } else null - return statement(start) { - val returnValue = resultExpr?.execute(it) ?: ObjVoid - throw ReturnException(returnValue, label) - } + return statement(start, f = object : ScopeCallable { + override suspend fun call(scp: Scope): Obj { + val returnValue = resultExpr?.execute(scp) ?: ObjVoid + throw ReturnException(returnValue, label) + } + }) } private fun ensureRparen(): Pos { @@ -2686,20 +2777,24 @@ class Compiler( return if (t2.type == Token.Type.ID && t2.value == "else") { val elseBody = parseStatement() ?: throw ScriptError(pos, "Bad else statement: expected statement") - return statement(start) { - if (condition.execute(it).toBool()) - ifBody.execute(it) - else - elseBody.execute(it) - } + return statement(start, f = object : ScopeCallable { + override suspend fun call(scp: Scope): Obj { + return if (condition.execute(scp).toBool()) + ifBody.execute(scp) + else + elseBody.execute(scp) + } + }) } else { cc.previous() - statement(start) { - if (condition.execute(it).toBool()) - ifBody.execute(it) - else - ObjVoid - } + statement(start, f = object : ScopeCallable { + override suspend fun call(scp: Scope): Obj { + return if (condition.execute(scp).toBool()) + ifBody.execute(scp) + else + ObjVoid + } + }) } } @@ -2799,8 +2894,9 @@ class Compiler( } miniSink?.onEnterFunction(node) - return inCodeContext(CodeContext.Function(name)) { - cc.labels.add(name) + return inCodeContext(CodeContext.Function(name), object : ContextBuilder { + override suspend fun build(): Statement { + cc.labels.add(name) outerLabel?.let { cc.labels.add(it) } val paramNames: Set = argsDeclaration.params.map { it.name }.toSet() @@ -2809,7 +2905,9 @@ class Compiler( currentLocalDeclCount localDeclCountStack.add(0) val fnStatements = if (actualExtern) - statement { raiseError("extern function not provided: $name") } + statement(f = object : ScopeCallable { + override suspend fun call(scp: Scope): Obj = scp.raiseError("extern function not provided: $name") + }) else if (isAbstract || isDelegated) { null } else @@ -2821,9 +2919,9 @@ class Compiler( throw ScriptError(cc.currentPos(), "return is not allowed in shorthand function") val expr = parseExpression() ?: throw ScriptError(cc.currentPos(), "Expected function body expression") // Shorthand function returns the expression value - statement(expr.pos) { scope -> - expr.execute(scope) - } + statement(expr.pos, f = object : ScopeCallable { + override suspend fun call(scp: Scope): Obj = expr.execute(scp) + }) } else { parseBlock() } @@ -2833,152 +2931,164 @@ class Compiler( var closure: Scope? = null - val fnBody = statement(t.pos) { callerContext -> - callerContext.pos = start + val fnBody = statement(t.pos, f = object : ScopeCallable { + override suspend fun call(scp: Scope): Obj { + scp.pos = start - // restore closure where the function was defined, and making a copy of it - // for local space. If there is no closure, we are in, say, class context where - // the closure is in the class initialization and we needn't more: - val context = closure?.let { ClosureScope(callerContext, it) } - ?: callerContext + // restore closure where the function was defined, and making a copy of it + // for local space. If there is no closure, we are in, say, class context where + // the closure is in the class initialization and we needn't more: + val context = closure?.let { ClosureScope(scp, it) } + ?: scp - // Capacity hint: parameters + declared locals + small overhead - val capacityHint = paramNames.size + fnLocalDecls + 4 - context.hintLocalCapacity(capacityHint) + // Capacity hint: parameters + declared locals + small overhead + val capacityHint = paramNames.size + fnLocalDecls + 4 + context.hintLocalCapacity(capacityHint) - // load params from caller context - argsDeclaration.assignToContext(context, callerContext.args, defaultAccessType = AccessType.Val) - if (extTypeName != null) { - context.thisObj = callerContext.thisObj + // load params from caller context + argsDeclaration.assignToContext(context, scp.args, defaultAccessType = AccessType.Val) + if (extTypeName != null) { + context.thisObj = scp.thisObj + } + try { + return fnStatements?.execute(context) ?: ObjVoid + } catch (e: ReturnException) { + if (e.label == null || e.label == name || e.label == outerLabel) return e.result + else throw e + } } - try { - fnStatements?.execute(context) ?: ObjVoid - } catch (e: ReturnException) { - if (e.label == null || e.label == name || e.label == outerLabel) e.result - else throw e - } - } + }) cc.labels.remove(name) outerLabel?.let { cc.labels.remove(it) } // parentContext - val fnCreateStatement = statement(start) { context -> - if (isDelegated) { - val accessType = context.resolveQualifiedIdentifier("DelegateAccess.Callable") - val initValue = delegateExpression!!.execute(context) - val finalDelegate = try { - initValue.invokeInstanceMethod(context, "bind", Arguments(ObjString(name), accessType, context.thisObj)) - } catch (e: Exception) { - initValue - } - - if (extTypeName != null) { - val type = context[extTypeName]?.value ?: context.raiseSymbolNotFound("class $extTypeName not found") - if (type !is ObjClass) context.raiseClassCastError("$extTypeName is not the class instance") - context.addExtension(type, name, ObjRecord(ObjUnset, isMutable = false, visibility = visibility, declaringClass = null, type = ObjRecord.Type.Delegated).apply { - delegate = finalDelegate - }) - return@statement ObjVoid - } - - val th = context.thisObj - if (isStatic) { - (th as ObjClass).createClassField(name, ObjUnset, false, visibility, null, start, isTransient = isTransient, type = ObjRecord.Type.Delegated).apply { - delegate = finalDelegate + val fnCreateStatement = statement(start, f = object : ScopeCallable { + override suspend fun call(scp: Scope): Obj { + if (isDelegated) { + val accessType = scp.resolveQualifiedIdentifier("DelegateAccess.Callable") + val initValue = delegateExpression!!.execute(scp) + val finalDelegate = try { + initValue.invokeInstanceMethod(scp, "bind", Arguments(ObjString(name), accessType, scp.thisObj)) + } catch (e: Exception) { + initValue } - context.addItem(name, false, ObjUnset, visibility, recordType = ObjRecord.Type.Delegated, isTransient = isTransient).apply { - delegate = finalDelegate + + if (extTypeName != null) { + val type = scp[extTypeName]?.value ?: scp.raiseSymbolNotFound("class $extTypeName not found") + if (type !is ObjClass) scp.raiseClassCastError("$extTypeName is not the class instance") + scp.addExtension(type, name, ObjRecord(ObjUnset, isMutable = false, visibility = visibility, declaringClass = null, type = ObjRecord.Type.Delegated).apply { + delegate = finalDelegate + }) + return ObjVoid } - } else if (th is ObjClass) { - val cls: ObjClass = th - val storageName = "${cls.className}::$name" - cls.createField(name, ObjUnset, false, visibility, null, start, declaringClass = cls, isAbstract = isAbstract, isClosed = isClosed, isOverride = isOverride, isTransient = isTransient, type = ObjRecord.Type.Delegated) - cls.instanceInitializers += statement(start) { scp -> - val accessType2 = scp.resolveQualifiedIdentifier("DelegateAccess.Callable") - val initValue2 = delegateExpression.execute(scp) - val finalDelegate2 = try { - initValue2.invokeInstanceMethod(scp, "bind", Arguments(ObjString(name), accessType2, scp.thisObj)) - } catch (e: Exception) { - initValue2 + + val th = scp.thisObj + if (isStatic) { + (th as ObjClass).createClassField(name, ObjUnset, false, visibility, null, start, isTransient = isTransient, type = ObjRecord.Type.Delegated).apply { + delegate = finalDelegate } - scp.addItem(storageName, false, ObjUnset, visibility, null, recordType = ObjRecord.Type.Delegated, isAbstract = isAbstract, isClosed = isClosed, isOverride = isOverride, isTransient = isTransient).apply { - delegate = finalDelegate2 + scp.addItem(name, false, ObjUnset, visibility, recordType = ObjRecord.Type.Delegated, isTransient = isTransient).apply { + delegate = finalDelegate } - ObjVoid - } - } else { - context.addItem(name, false, ObjUnset, visibility, recordType = ObjRecord.Type.Delegated, isTransient = isTransient).apply { - delegate = finalDelegate - } - } - return@statement ObjVoid - } - - // we added fn in the context. now we must save closure - // for the function, unless we're in the class scope: - if (isStatic || parentContext !is CodeContext.ClassBody) - closure = context - - val annotatedFnBody = annotation?.invoke(context, ObjString(name), fnBody) - ?: fnBody - - extTypeName?.let { typeName -> - // class extension method - val type = context[typeName]?.value ?: context.raiseSymbolNotFound("class $typeName not found") - if (type !is ObjClass) context.raiseClassCastError("$typeName is not the class instance") - val stmt = statement { - // ObjInstance has a fixed instance scope, so we need to build a closure - (thisObj as? ObjInstance)?.let { i -> - annotatedFnBody.execute(ClosureScope(this, i.instanceScope)) - } - // other classes can create one-time scope for this rare case: - ?: annotatedFnBody.execute(thisObj.autoInstanceScope(this)) - } - context.addExtension(type, name, ObjRecord(stmt, isMutable = false, visibility = visibility, declaringClass = null)) - } - // regular function/method - ?: run { - val th = context.thisObj - if (!isStatic && th is ObjClass) { - // Instance method declared inside a class body: register on the class + } else if (th is ObjClass) { val cls: ObjClass = th - cls.addFn( - name, - isMutable = true, - visibility = visibility, - isAbstract = isAbstract, - isClosed = isClosed, - isOverride = isOverride, - pos = start - ) { - // Execute with the instance as receiver; set caller lexical class for visibility - val savedCtx = this.currentClassCtx - this.currentClassCtx = cls - try { - (thisObj as? ObjInstance)?.let { i -> - annotatedFnBody.execute(ClosureScope(this, i.instanceScope)) - } ?: annotatedFnBody.execute(thisObj.autoInstanceScope(this)) - } finally { - this.currentClassCtx = savedCtx + val storageName = "${cls.className}::$name" + cls.createField(name, ObjUnset, false, visibility, null, start, declaringClass = cls, isAbstract = isAbstract, isClosed = isClosed, isOverride = isOverride, isTransient = isTransient, type = ObjRecord.Type.Delegated) + cls.instanceInitializers += statement(start, f = object : ScopeCallable { + override suspend fun call(scp2: Scope): Obj { + val accessType2 = scp2.resolveQualifiedIdentifier("DelegateAccess.Callable") + val initValue2 = delegateExpression.execute(scp2) + val finalDelegate2 = try { + initValue2.invokeInstanceMethod(scp2, "bind", Arguments(ObjString(name), accessType2, scp2.thisObj)) + } catch (e: Exception) { + initValue2 + } + scp2.addItem(storageName, false, ObjUnset, visibility, null, recordType = ObjRecord.Type.Delegated, isAbstract = isAbstract, isClosed = isClosed, isOverride = isOverride, isTransient = isTransient).apply { + delegate = finalDelegate2 + } + return ObjVoid } - } - // also expose the symbol in the class scope for possible references - context.addItem(name, false, annotatedFnBody, visibility) - annotatedFnBody + }) } else { - // top-level or nested function - context.addItem(name, false, annotatedFnBody, visibility) + scp.addItem(name, false, ObjUnset, visibility, recordType = ObjRecord.Type.Delegated, isTransient = isTransient).apply { + delegate = finalDelegate + } } + return ObjVoid } - // as the function can be called from anywhere, we have - // saved the proper context in the closure - annotatedFnBody + + // we added fn in the context. now we must save closure + // for the function, unless we're in the class scope: + if (isStatic || parentContext !is CodeContext.ClassBody) + closure = scp + + val annotatedFnBody: Statement = annotation?.process(scp, ObjString(name), fnBody) + ?: fnBody + + extTypeName?.let { typeName -> + // class extension method + val type = scp[typeName]?.value ?: scp.raiseSymbolNotFound("class $typeName not found") + if (type !is ObjClass) scp.raiseClassCastError("$typeName is not the class instance") + val stmt = statement(f = object : ScopeCallable { + override suspend fun call(scp2: Scope): Obj { + // ObjInstance has a fixed instance scope, so we need to build a closure + return (scp2.thisObj as? ObjInstance)?.let { i -> + annotatedFnBody.execute(ClosureScope(scp2, i.instanceScope)) + } + // other classes can create one-time scope for this rare case: + ?: annotatedFnBody.execute(scp2.thisObj.autoInstanceScope(scp2)) + } + }) + scp.addExtension(type, name, ObjRecord(stmt, isMutable = false, visibility = visibility, declaringClass = null)) + } + // regular function/method + ?: run { + val th = scp.thisObj + if (!isStatic && th is ObjClass) { + // Instance method declared inside a class body: register on the class + val cls: ObjClass = th + cls.addFn( + name, + isMutable = true, + visibility = visibility, + isAbstract = isAbstract, + isClosed = isClosed, + isOverride = isOverride, + pos = start, + code = object : ScopeCallable { + override suspend fun call(scp2: Scope): Obj { + // Execute with the instance as receiver; set caller lexical class for visibility + val savedCtx = scp2.currentClassCtx + scp2.currentClassCtx = cls + try { + return (scp2.thisObj as? ObjInstance)?.let { i -> + annotatedFnBody.execute(ClosureScope(scp2, i.instanceScope)) + } ?: annotatedFnBody.execute(scp2.thisObj.autoInstanceScope(scp2)) + } finally { + scp2.currentClassCtx = savedCtx + } + } + } + ) + // also expose the symbol in the class scope for possible references + scp.addItem(name, false, annotatedFnBody, visibility) + annotatedFnBody + } else { + // top-level or nested function + scp.addItem(name, false, annotatedFnBody, visibility) + } + } + // as the function can be called from anywhere, we have + // saved the proper context in the closure + return annotatedFnBody + } + }) + return if (isStatic) { + currentInitScope += fnCreateStatement + NopStatement + } else + fnCreateStatement } - if (isStatic) { - currentInitScope += fnCreateStatement - NopStatement - } else - fnCreateStatement - }.also { + }).also { val bodyRange = lastParsedBlockRange // Also emit a post-parse MiniFunDecl to be robust in case early emission was skipped by some path val params = argsDeclaration.params.map { p -> @@ -3014,10 +3124,12 @@ class Compiler( throw ScriptError(t.pos, "Expected block body start: {") } val block = parseScript() - return statement(startPos) { - // block run on inner context: - block.execute(if (it.skipScopeCreation) it else it.createChildScope(startPos)) - }.also { + return statement(startPos, f = object : ScopeCallable { + override suspend fun call(scp: Scope): Obj { + // block run on inner context: + return block.execute(if (scp.skipScopeCreation) scp else scp.createChildScope(startPos)) + } + }).also { val t1 = cc.next() if (t1.type != Token.Type.RBRACE) throw ScriptError(t1.pos, "unbalanced braces: expected block body end: }") @@ -3079,23 +3191,25 @@ class Compiler( val names = mutableListOf() pattern.forEachVariable { names.add(it) } - return statement(start) { context -> - val value = initialExpression.execute(context) - for (name in names) { - context.addItem(name, true, ObjVoid, visibility, isTransient = isTransient) - } - pattern.setAt(start, context, value) - if (!isMutable) { + return statement(start, f = object : ScopeCallable { + override suspend fun call(scp: Scope): Obj { + val value = initialExpression.execute(scp) for (name in names) { - val rec = context.objects[name]!! - val immutableRec = rec.copy(isMutable = false) - context.objects[name] = immutableRec - context.localBindings[name] = immutableRec - context.updateSlotFor(name, immutableRec) + scp.addItem(name, true, ObjVoid, visibility, isTransient = isTransient) } + pattern.setAt(start, scp, value) + if (!isMutable) { + for (name in names) { + val rec = scp.objects[name]!! + val immutableRec = rec.copy(isMutable = false) + scp.objects[name] = immutableRec + scp.localBindings[name] = immutableRec + scp.updateSlotFor(name, immutableRec) + } + } + return ObjVoid } - ObjVoid - } + }) } if (nextToken.type != Token.Type.ID) @@ -3267,38 +3381,40 @@ class Compiler( // when creating instance, but we need to execute it in the class initializer which // is missing as for now. Add it to the compiler context? - currentInitScope += statement { - val initValue = initialExpression?.execute(this)?.byValueCopy() ?: ObjNull - if (isDelegate) { - val accessTypeStr = if (isMutable) "Var" else "Val" - val accessType = resolveQualifiedIdentifier("DelegateAccess.$accessTypeStr") - val finalDelegate = try { - initValue.invokeInstanceMethod(this, "bind", Arguments(ObjString(name), accessType, thisObj)) - } catch (e: Exception) { - initValue + currentInitScope += statement(f = object : ScopeCallable { + override suspend fun call(scp: Scope): Obj { + val initValue = initialExpression?.execute(scp)?.byValueCopy() ?: ObjNull + if (isDelegate) { + val accessTypeStr = if (isMutable) "Var" else "Val" + val accessType = scp.resolveQualifiedIdentifier("DelegateAccess.$accessTypeStr") + val finalDelegate = try { + initValue.invokeInstanceMethod(scp, "bind", Arguments(ObjString(name), accessType, scp.thisObj)) + } catch (e: Exception) { + initValue + } + (scp.thisObj as ObjClass).createClassField( + name, + ObjUnset, + isMutable, + visibility, + null, + start, + isTransient = isTransient, + type = ObjRecord.Type.Delegated + ).apply { + delegate = finalDelegate + } + // Also expose in current init scope + scp.addItem(name, isMutable, ObjUnset, visibility, null, ObjRecord.Type.Delegated, isTransient = isTransient).apply { + delegate = finalDelegate + } + } else { + (scp.thisObj as ObjClass).createClassField(name, initValue, isMutable, visibility, null, start, isTransient = isTransient) + scp.addItem(name, isMutable, initValue, visibility, null, ObjRecord.Type.Field, isTransient = isTransient) } - (thisObj as ObjClass).createClassField( - name, - ObjUnset, - isMutable, - visibility, - null, - start, - isTransient = isTransient, - type = ObjRecord.Type.Delegated - ).apply { - delegate = finalDelegate - } - // Also expose in current init scope - addItem(name, isMutable, ObjUnset, visibility, null, ObjRecord.Type.Delegated, isTransient = isTransient).apply { - delegate = finalDelegate - } - } else { - (thisObj as ObjClass).createClassField(name, initValue, isMutable, visibility, null, start, isTransient = isTransient) - addItem(name, isMutable, initValue, visibility, null, ObjRecord.Type.Field, isTransient = isTransient) + return ObjVoid } - ObjVoid - } + }) return NopStatement } @@ -3319,17 +3435,18 @@ class Compiler( miniSink?.onEnterFunction(null) getter = if (cc.peekNextNonWhitespace().type == Token.Type.LBRACE) { cc.skipWsTokens() - inCodeContext(CodeContext.Function("")) { - parseBlock() - } + inCodeContext(CodeContext.Function(""), object : ContextBuilder { + override suspend fun build(): Statement = parseBlock() + }) } else if (cc.peekNextNonWhitespace().type == Token.Type.ASSIGN) { cc.skipWsTokens() cc.next() // consume '=' - inCodeContext(CodeContext.Function("")) { - val expr = parseExpression() - ?: throw ScriptError(cc.current().pos, "Expected getter expression") - expr - } + inCodeContext(CodeContext.Function(""), object : ContextBuilder { + override suspend fun build(): Statement { + return parseExpression() + ?: throw ScriptError(cc.current().pos, "Expected getter expression") + } + }) } else { throw ScriptError(cc.current().pos, "Expected { or = after get()") } @@ -3346,27 +3463,33 @@ class Compiler( miniSink?.onEnterFunction(null) setter = if (cc.peekNextNonWhitespace().type == Token.Type.LBRACE) { cc.skipWsTokens() - val body = inCodeContext(CodeContext.Function("")) { - parseBlock() - } - statement(body.pos) { scope -> - val value = scope.args.list.firstOrNull() ?: ObjNull - scope.addItem(setArgName, true, value, recordType = ObjRecord.Type.Argument) - body.execute(scope) - } + val body = inCodeContext(CodeContext.Function(""), object : ContextBuilder { + override suspend fun build(): Statement = parseBlock() + }) + statement(body.pos, f = object : ScopeCallable { + override suspend fun call(scp: Scope): Obj { + val value = scp.args.list.firstOrNull() ?: ObjNull + scp.addItem(setArgName, true, value, recordType = ObjRecord.Type.Argument) + return body.execute(scp) + } + }) } else if (cc.peekNextNonWhitespace().type == Token.Type.ASSIGN) { cc.skipWsTokens() cc.next() // consume '=' - val expr = inCodeContext(CodeContext.Function("")) { - parseExpression() - ?: throw ScriptError(cc.current().pos, "Expected setter expression") - } + val expr = inCodeContext(CodeContext.Function(""), object : ContextBuilder { + override suspend fun build(): Statement { + return parseExpression() + ?: throw ScriptError(cc.current().pos, "Expected setter expression") + } + }) val st = expr - statement(st.pos) { scope -> - val value = scope.args.list.firstOrNull() ?: ObjNull - scope.addItem(setArgName, true, value, recordType = ObjRecord.Type.Argument) - st.execute(scope) - } + statement(st.pos, f = object : ScopeCallable { + override suspend fun call(scp: Scope): Obj { + val value = scp.args.list.firstOrNull() ?: ObjNull + scp.addItem(setArgName, true, value, recordType = ObjRecord.Type.Argument) + return st.execute(scp) + } + }) } else { throw ScriptError(cc.current().pos, "Expected { or = after set(...)") } @@ -3385,28 +3508,34 @@ class Compiler( miniSink?.onEnterFunction(null) val finalSetter = if (cc.peekNextNonWhitespace().type == Token.Type.LBRACE) { cc.skipWsTokens() - val body = inCodeContext(CodeContext.Function("")) { - parseBlock() - } - statement(body.pos) { scope -> - val value = scope.args.list.firstOrNull() ?: ObjNull - scope.addItem(setArg.value, true, value, recordType = ObjRecord.Type.Argument) - body.execute(scope) - } + val body = inCodeContext(CodeContext.Function(""), object : ContextBuilder { + override suspend fun build(): Statement = parseBlock() + }) + statement(body.pos, f = object : ScopeCallable { + override suspend fun call(scope: Scope): Obj { + val value = scope.args.list.firstOrNull() ?: ObjNull + scope.addItem(setArg.value, true, value, recordType = ObjRecord.Type.Argument) + return body.execute(scope) + } + }) } else if (cc.peekNextNonWhitespace().type == Token.Type.ASSIGN) { cc.skipWsTokens() cc.next() // consume '=' - val st = inCodeContext(CodeContext.Function("")) { - parseExpression() ?: throw ScriptError( - cc.current().pos, - "Expected setter expression" - ) - } - statement(st.pos) { scope -> - val value = scope.args.list.firstOrNull() ?: ObjNull - scope.addItem(setArg.value, true, value, recordType = ObjRecord.Type.Argument) - st.execute(scope) - } + val st = inCodeContext(CodeContext.Function(""), object : ContextBuilder { + override suspend fun build(): Statement { + return parseExpression() ?: throw ScriptError( + cc.current().pos, + "Expected setter expression" + ) + } + }) + statement(st.pos, f = object : ScopeCallable { + override suspend fun call(scope: Scope): Obj { + val value = scope.args.list.firstOrNull() ?: ObjNull + scope.addItem(setArg.value, true, value, recordType = ObjRecord.Type.Argument) + return st.execute(scope) + } + }) } else { throw ScriptError(cc.current().pos, "Expected { or = after set(...)") } @@ -3437,57 +3566,84 @@ class Compiler( } } - return statement(start) { context -> - if (extTypeName != null) { - val prop = if (getter != null || setter != null) { - ObjProperty(name, getter, setter) - } else { - // Simple val extension with initializer - val initExpr = initialExpression ?: throw ScriptError(start, "Extension val must be initialized") - ObjProperty(name, statement(initExpr.pos) { scp -> initExpr.execute(scp) }, null) + return statement(start, f = object : ScopeCallable { + override suspend fun call(scp: Scope): Obj { + if (extTypeName != null) { + val prop = if (getter != null || setter != null) { + ObjProperty(name, getter, setter) + } else { + // Simple val extension with initializer + val initExpr = initialExpression ?: throw ScriptError(start, "Extension val must be initialized") + ObjProperty(name, statement(initExpr.pos, f = object : ScopeCallable { + override suspend fun call(scp2: Scope): Obj = initExpr.execute(scp2) + }), null) + } + + val type = scp[extTypeName]?.value ?: scp.raiseSymbolNotFound("class $extTypeName not found") + if (type !is ObjClass) scp.raiseClassCastError("$extTypeName is not the class instance") + + scp.addExtension(type, name, ObjRecord(prop, isMutable = false, visibility = visibility, writeVisibility = setterVisibility, declaringClass = null, type = ObjRecord.Type.Property)) + + return prop + } + // In true class bodies (not inside a function), store fields under a class-qualified key to support MI collisions + // Do NOT infer declaring class from runtime thisObj here; only the compile-time captured + // ClassBody qualifies for class-field storage. Otherwise, this is a plain local. + isProperty = getter != null || setter != null + val declaringClassName = declaringClassNameCaptured + if (declaringClassName == null) { + if (scp.containsLocal(name)) + throw ScriptError(start, "Variable $name is already defined") } - val type = context[extTypeName]?.value ?: context.raiseSymbolNotFound("class $extTypeName not found") - if (type !is ObjClass) context.raiseClassCastError("$extTypeName is not the class instance") + // Register the local name so subsequent identifiers can be emitted as fast locals + if (!isStatic) declareLocalName(name) - context.addExtension(type, name, ObjRecord(prop, isMutable = false, visibility = visibility, writeVisibility = setterVisibility, declaringClass = null, type = ObjRecord.Type.Property)) - - return@statement prop - } - // In true class bodies (not inside a function), store fields under a class-qualified key to support MI collisions - // Do NOT infer declaring class from runtime thisObj here; only the compile-time captured - // ClassBody qualifies for class-field storage. Otherwise, this is a plain local. - isProperty = getter != null || setter != null - val declaringClassName = declaringClassNameCaptured - if (declaringClassName == null) { - if (context.containsLocal(name)) - throw ScriptError(start, "Variable $name is already defined") - } - - // Register the local name so subsequent identifiers can be emitted as fast locals - if (!isStatic) declareLocalName(name) - - if (isDelegate) { - val declaringClassName = declaringClassNameCaptured - if (declaringClassName != null) { - val storageName = "$declaringClassName::$name" - val isClassScope = context.thisObj is ObjClass && (context.thisObj !is ObjInstance) - if (isClassScope) { - val cls = context.thisObj as ObjClass - cls.createField( - name, - ObjUnset, - isMutable, - visibility, - setterVisibility, - start, - isTransient = isTransient, - type = ObjRecord.Type.Delegated, - isAbstract = isAbstract, - isClosed = isClosed, - isOverride = isOverride - ) - cls.instanceInitializers += statement(start) { scp -> + if (isDelegate) { + val declaringClassName = declaringClassNameCaptured + if (declaringClassName != null) { + val storageName = "$declaringClassName::$name" + val isClassScope = scp.thisObj is ObjClass && (scp.thisObj !is ObjInstance) + if (isClassScope) { + val cls = scp.thisObj as ObjClass + cls.createField( + name, + ObjUnset, + isMutable, + visibility, + setterVisibility, + start, + isTransient = isTransient, + type = ObjRecord.Type.Delegated, + isAbstract = isAbstract, + isClosed = isClosed, + isOverride = isOverride + ) + cls.instanceInitializers += statement(start, f = object : ScopeCallable { + override suspend fun call(scp2: Scope): Obj { + val initValue = initialExpression!!.execute(scp2) + val accessTypeStr = if (isMutable) "Var" else "Val" + val accessType = scp2.resolveQualifiedIdentifier("DelegateAccess.$accessTypeStr") + val finalDelegate = try { + initValue.invokeInstanceMethod(scp2, "bind", Arguments(ObjString(name), accessType, scp2.thisObj)) + } catch (e: Exception) { + initValue + } + scp2.addItem( + storageName, isMutable, ObjUnset, visibility, setterVisibility, + recordType = ObjRecord.Type.Delegated, + isAbstract = isAbstract, + isClosed = isClosed, + isOverride = isOverride, + isTransient = isTransient + ).apply { + delegate = finalDelegate + } + return ObjVoid + } + }) + return ObjVoid + } else { val initValue = initialExpression!!.execute(scp) val accessTypeStr = if (isMutable) "Var" else "Val" val accessType = scp.resolveQualifiedIdentifier("DelegateAccess.$accessTypeStr") @@ -3496,30 +3652,28 @@ class Compiler( } catch (e: Exception) { initValue } - scp.addItem( + val rec = scp.addItem( storageName, isMutable, ObjUnset, visibility, setterVisibility, recordType = ObjRecord.Type.Delegated, isAbstract = isAbstract, isClosed = isClosed, isOverride = isOverride, isTransient = isTransient - ).apply { - delegate = finalDelegate - } - ObjVoid + ) + rec.delegate = finalDelegate + return finalDelegate } - return@statement ObjVoid } else { - val initValue = initialExpression!!.execute(context) + val initValue = initialExpression!!.execute(scp) val accessTypeStr = if (isMutable) "Var" else "Val" - val accessType = context.resolveQualifiedIdentifier("DelegateAccess.$accessTypeStr") + val accessType = scp.resolveQualifiedIdentifier("DelegateAccess.$accessTypeStr") val finalDelegate = try { - initValue.invokeInstanceMethod(context, "bind", Arguments(ObjString(name), accessType, context.thisObj)) + initValue.invokeInstanceMethod(scp, "bind", Arguments(ObjString(name), accessType, ObjNull)) } catch (e: Exception) { initValue } - val rec = context.addItem( - storageName, isMutable, ObjUnset, visibility, setterVisibility, + val rec = scp.addItem( + name, isMutable, ObjUnset, visibility, setterVisibility, recordType = ObjRecord.Type.Delegated, isAbstract = isAbstract, isClosed = isClosed, @@ -3527,102 +3681,84 @@ class Compiler( isTransient = isTransient ) rec.delegate = finalDelegate - return@statement finalDelegate + return finalDelegate } - } else { - val initValue = initialExpression!!.execute(context) - val accessTypeStr = if (isMutable) "Var" else "Val" - val accessType = context.resolveQualifiedIdentifier("DelegateAccess.$accessTypeStr") - val finalDelegate = try { - initValue.invokeInstanceMethod(context, "bind", Arguments(ObjString(name), accessType, ObjNull)) - } catch (e: Exception) { - initValue - } - val rec = context.addItem( - name, isMutable, ObjUnset, visibility, setterVisibility, - recordType = ObjRecord.Type.Delegated, - isAbstract = isAbstract, - isClosed = isClosed, - isOverride = isOverride, - isTransient = isTransient - ) - rec.delegate = finalDelegate - return@statement finalDelegate - } - } else if (getter != null || setter != null) { - val declaringClassName = declaringClassNameCaptured!! - val storageName = "$declaringClassName::$name" - val prop = ObjProperty(name, getter, setter) + } else if (getter != null || setter != null) { + val declaringClassName = declaringClassNameCaptured!! + val storageName = "$declaringClassName::$name" + val prop = ObjProperty(name, getter, setter) - // If we are in class scope now (defining instance field), defer initialization to instance time - val isClassScope = context.thisObj is ObjClass && (context.thisObj !is ObjInstance) - if (isClassScope) { - val cls = context.thisObj as ObjClass - // Register in class members for reflection/MRO/satisfaction checks - if (isProperty) { - cls.addProperty( - name, - visibility = visibility, - writeVisibility = setterVisibility, - isAbstract = isAbstract, - isClosed = isClosed, - isOverride = isOverride, - pos = start, - prop = prop - ) - } else { - cls.createField( - name, - ObjNull, - isMutable = isMutable, - visibility = visibility, - writeVisibility = setterVisibility, - isAbstract = isAbstract, - isClosed = isClosed, - isOverride = isOverride, - isTransient = isTransient, - type = ObjRecord.Type.Field - ) - } - - // Register the property/field initialization thunk - if (!isAbstract) { - cls.instanceInitializers += statement(start) { scp -> - scp.addItem( - storageName, - isMutable, - prop, - visibility, - setterVisibility, - recordType = ObjRecord.Type.Property, + // If we are in class scope now (defining instance field), defer initialization to instance time + val isClassScope = scp.thisObj is ObjClass && (scp.thisObj !is ObjInstance) + if (isClassScope) { + val cls = scp.thisObj as ObjClass + // Register in class members for reflection/MRO/satisfaction checks + if (isProperty) { + cls.addProperty( + name, + visibility = visibility, + writeVisibility = setterVisibility, isAbstract = isAbstract, isClosed = isClosed, - isOverride = isOverride + isOverride = isOverride, + pos = start, + prop = prop + ) + } else { + cls.createField( + name, + ObjNull, + isMutable = isMutable, + visibility = visibility, + writeVisibility = setterVisibility, + isAbstract = isAbstract, + isClosed = isClosed, + isOverride = isOverride, + isTransient = isTransient, + type = ObjRecord.Type.Field ) - ObjVoid } + + // Register the property/field initialization thunk + if (!isAbstract) { + cls.instanceInitializers += statement(start, f = object : ScopeCallable { + override suspend fun call(scp2: Scope): Obj { + scp2.addItem( + storageName, + isMutable, + prop, + visibility, + setterVisibility, + recordType = ObjRecord.Type.Property, + isAbstract = isAbstract, + isClosed = isClosed, + isOverride = isOverride + ) + return ObjVoid + } + }) + } + return ObjVoid + } else { + // We are in instance scope already: perform initialization immediately + scp.addItem( + storageName, isMutable, prop, visibility, setterVisibility, + recordType = ObjRecord.Type.Property, + isAbstract = isAbstract, + isClosed = isClosed, + isOverride = isOverride, + isTransient = isTransient + ) + return prop } - ObjVoid } else { - // We are in instance scope already: perform initialization immediately - context.addItem( - storageName, isMutable, prop, visibility, setterVisibility, - recordType = ObjRecord.Type.Property, - isAbstract = isAbstract, - isClosed = isClosed, - isOverride = isOverride, - isTransient = isTransient - ) - prop - } - } else { val isLateInitVal = !isMutable && initialExpression == null if (declaringClassName != null && !isStatic) { val storageName = "$declaringClassName::$name" // If we are in class scope now (defining instance field), defer initialization to instance time - val isClassScope = context.thisObj is ObjClass && (context.thisObj !is ObjInstance) + val isClassScope = scp.thisObj is ObjClass && (scp.thisObj !is ObjInstance) if (isClassScope) { - val cls = context.thisObj as ObjClass + val cls = scp.thisObj as ObjClass // Register in class members for reflection/MRO/satisfaction checks cls.createField( name, @@ -3640,30 +3776,32 @@ class Compiler( // Defer: at instance construction, evaluate initializer in instance scope and store under mangled name if (!isAbstract) { - val initStmt = statement(start) { scp -> - val initValue = - initialExpression?.execute(scp)?.byValueCopy() - ?: if (isLateInitVal) ObjUnset else ObjNull - // Preserve mutability of declaration: do NOT use addOrUpdateItem here, as it creates mutable records - scp.addItem( - storageName, isMutable, initValue, visibility, setterVisibility, - recordType = ObjRecord.Type.Field, - isAbstract = isAbstract, - isClosed = isClosed, - isOverride = isOverride, - isTransient = isTransient - ) - ObjVoid - } + val initStmt = statement(start, f = object : ScopeCallable { + override suspend fun call(scp2: Scope): Obj { + val initValue = + initialExpression?.execute(scp2)?.byValueCopy() + ?: if (isLateInitVal) ObjUnset else ObjNull + // Preserve mutability of declaration: do NOT use addOrUpdateItem here, as it creates mutable records + scp2.addItem( + storageName, isMutable, initValue, visibility, setterVisibility, + recordType = ObjRecord.Type.Field, + isAbstract = isAbstract, + isClosed = isClosed, + isOverride = isOverride, + isTransient = isTransient + ) + return ObjVoid + } + }) cls.instanceInitializers += initStmt } - ObjVoid + return ObjVoid } else { // We are in instance scope already: perform initialization immediately val initValue = - initialExpression?.execute(context)?.byValueCopy() ?: if (isLateInitVal) ObjUnset else ObjNull + initialExpression?.execute(scp)?.byValueCopy() ?: if (isLateInitVal) ObjUnset else ObjNull // Preserve mutability of declaration: create record with correct mutability - context.addItem( + scp.addItem( storageName, isMutable, initValue, visibility, setterVisibility, recordType = ObjRecord.Type.Field, isAbstract = isAbstract, @@ -3671,16 +3809,17 @@ class Compiler( isOverride = isOverride, isTransient = isTransient ) - initValue + return initValue } } else { // Not in class body: regular local/var declaration - val initValue = initialExpression?.execute(context)?.byValueCopy() ?: ObjNull - context.addItem(name, isMutable, initValue, visibility, recordType = ObjRecord.Type.Other, isTransient = isTransient) - initValue + val initValue = initialExpression?.execute(scp)?.byValueCopy() ?: ObjNull + scp.addItem(name, isMutable, initValue, visibility, recordType = ObjRecord.Type.Other, isTransient = isTransient) + return initValue } + } } - } + }) } data class Operator( diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Scope.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Scope.kt index 4a1b0cc..70ab4e5 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Scope.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Scope.kt @@ -21,6 +21,23 @@ import net.sergeych.lyng.obj.* import net.sergeych.lyng.pacman.ImportManager import net.sergeych.lyng.pacman.ImportProvider +interface ScopeCallable { + suspend fun call(scope: Scope): Obj +} + +interface VoidScopeCallable { + suspend fun call(scope: Scope) +} + +interface ScopeBlock { + suspend fun call(scope: Scope): R +} + +private class FnStatement(val fn: ScopeCallable) : Statement() { + override val pos: Pos = Pos.builtIn + override suspend fun execute(scope: Scope): Obj = fn.call(scope) +} + // Simple per-frame id generator for perf caches (not thread-safe, fine for scripts) object FrameIdGen { var c: Long = 1L; fun nextId(): Long = c++ } fun nextFrameId(): Long = FrameIdGen.nextId() @@ -447,17 +464,17 @@ open class Scope( * Execute a block inside a child frame. Guarded for future pooling via [PerfFlags.SCOPE_POOL]. * Currently always creates a fresh child scope to preserve unique frameId semantics. */ - inline suspend fun withChildFrame(args: Arguments = Arguments.EMPTY, newThisObj: Obj? = null, crossinline block: suspend (Scope) -> R): R { + suspend fun withChildFrame(args: Arguments = Arguments.EMPTY, newThisObj: Obj? = null, block: ScopeBlock): R { if (PerfFlags.SCOPE_POOL) { val child = ScopePool.borrow(this, args, pos, newThisObj ?: thisObj) try { - return block(child) + return block.call(child) } finally { ScopePool.release(child) } } else { val child = createChildScope(args, newThisObj) - return block(child) + return block.call(child) } } @@ -563,20 +580,17 @@ open class Scope( return ns.objClass } - inline fun addVoidFn(vararg names: String, crossinline fn: suspend Scope.() -> Unit) { - addFn(*names) { - fn(this) - ObjVoid - } + fun addVoidFn(vararg names: String, fn: VoidScopeCallable) { + addFn(*names, fn = object : ScopeCallable { + override suspend fun call(scope: Scope): Obj { + fn.call(scope) + return ObjVoid + } + }) } - fun addFn(vararg names: String, fn: suspend Scope.() -> Obj) { - val newFn = object : Statement() { - override val pos: Pos = Pos.builtIn - - override suspend fun execute(scope: Scope): Obj = scope.fn() - - } + fun addFn(vararg names: String, fn: ScopeCallable) { + val newFn = FnStatement(fn) for (name in names) { addItem( name, diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Script.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Script.kt index f65ef7d..1cc2a9c 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Script.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Script.kt @@ -18,14 +18,13 @@ package net.sergeych.lyng import kotlinx.coroutines.delay -import kotlinx.coroutines.yield import net.sergeych.lyng.Script.Companion.defaultImportManager import net.sergeych.lyng.miniast.* import net.sergeych.lyng.obj.* import net.sergeych.lyng.pacman.ImportManager +import net.sergeych.lyng.pacman.ModuleBuilder import net.sergeych.lyng.stdlib_included.rootLyng import net.sergeych.lynon.ObjLynonClass -import net.sergeych.mp_tools.globalDefer import kotlin.math.* @Suppress("TYPE_INTERSECTION_AS_REIFIED_WARNING") @@ -59,186 +58,173 @@ class Script( internal val rootScope: Scope = Scope(null).apply { ObjException.addExceptionsToContext(this) 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) + addFn("print", fn = object : ScopeCallable { + override suspend fun call(scp: Scope): Obj { + for ((i, a) in scp.args.withIndex()) { + if (i > 0) print(' ' + a.toString(scp).value) + else print(a.toString(scp).value) + } + return ObjVoid } - ObjVoid - } - addFn("println") { - for ((i, a) in args.withIndex()) { - if (i > 0) print(' ' + a.toString(this).value) - else print(a.toString(this).value) + }) + addFn("println", fn = object : ScopeCallable { + override suspend fun call(scp: Scope): Obj { + for ((i, a) in scp.args.withIndex()) { + if (i > 0) print(' ' + a.toString(scp).value) + else print(a.toString(scp).value) + } + println() + return ObjVoid } - println() - ObjVoid - } - addFn("floor") { - val x = args.firstAndOnly() - (if (x is ObjInt) x - else ObjReal(floor(x.toDouble()))) - } - addFn("ceil") { - val x = args.firstAndOnly() - (if (x is ObjInt) x - else ObjReal(ceil(x.toDouble()))) - } - addFn("round") { - val x = args.firstAndOnly() - (if (x is ObjInt) x - else ObjReal(round(x.toDouble()))) - } + }) + addFn("floor", fn = object : ScopeCallable { + override suspend fun call(scp: Scope): Obj { + val x = scp.args.firstAndOnly() + return (if (x is ObjInt) x + else ObjReal(floor(x.toDouble()))) + } + }) + addFn("ceil", fn = object : ScopeCallable { + override suspend fun call(scp: Scope): Obj { + val x = scp.args.firstAndOnly() + return (if (x is ObjInt) x + else ObjReal(ceil(x.toDouble()))) + } + }) + addFn("round", fn = object : ScopeCallable { + override suspend fun call(scp: Scope): Obj { + val x = scp.args.firstAndOnly() + return (if (x is ObjInt) x + else ObjReal(round(x.toDouble()))) + } + }) - addFn("sin") { - ObjReal(sin(args.firstAndOnly().toDouble())) - } - addFn("cos") { - ObjReal(cos(args.firstAndOnly().toDouble())) - } - addFn("tan") { - ObjReal(tan(args.firstAndOnly().toDouble())) - } - addFn("asin") { - ObjReal(asin(args.firstAndOnly().toDouble())) - } - addFn("acos") { - ObjReal(acos(args.firstAndOnly().toDouble())) - } - addFn("atan") { - ObjReal(atan(args.firstAndOnly().toDouble())) - } + addFn("sin", fn = object : ScopeCallable { override suspend fun call(scp: Scope): Obj = ObjReal(sin(scp.args.firstAndOnly().toDouble())) }) + addFn("cos", fn = object : ScopeCallable { override suspend fun call(scp: Scope): Obj = ObjReal(cos(scp.args.firstAndOnly().toDouble())) }) + addFn("tan", fn = object : ScopeCallable { override suspend fun call(scp: Scope): Obj = ObjReal(tan(scp.args.firstAndOnly().toDouble())) }) + addFn("asin", fn = object : ScopeCallable { override suspend fun call(scp: Scope): Obj = ObjReal(asin(scp.args.firstAndOnly().toDouble())) }) + addFn("acos", fn = object : ScopeCallable { override suspend fun call(scp: Scope): Obj = ObjReal(acos(scp.args.firstAndOnly().toDouble())) }) + addFn("atan", fn = object : ScopeCallable { override suspend fun call(scp: Scope): Obj = ObjReal(atan(scp.args.firstAndOnly().toDouble())) }) - addFn("sinh") { - ObjReal(sinh(args.firstAndOnly().toDouble())) - } - addFn("cosh") { - ObjReal(cosh(args.firstAndOnly().toDouble())) - } - addFn("tanh") { - ObjReal(tanh(args.firstAndOnly().toDouble())) - } - addFn("asinh") { - ObjReal(asinh(args.firstAndOnly().toDouble())) - } - addFn("acosh") { - ObjReal(acosh(args.firstAndOnly().toDouble())) - } - addFn("atanh") { - ObjReal(atanh(args.firstAndOnly().toDouble())) - } + addFn("sinh", fn = object : ScopeCallable { override suspend fun call(scp: Scope): Obj = ObjReal(sinh(scp.args.firstAndOnly().toDouble())) }) + addFn("cosh", fn = object : ScopeCallable { override suspend fun call(scp: Scope): Obj = ObjReal(cosh(scp.args.firstAndOnly().toDouble())) }) + addFn("tanh", fn = object : ScopeCallable { override suspend fun call(scp: Scope): Obj = ObjReal(tanh(scp.args.firstAndOnly().toDouble())) }) + addFn("asinh", fn = object : ScopeCallable { override suspend fun call(scp: Scope): Obj = ObjReal(asinh(scp.args.firstAndOnly().toDouble())) }) + addFn("acosh", fn = object : ScopeCallable { override suspend fun call(scp: Scope): Obj = ObjReal(acosh(scp.args.firstAndOnly().toDouble())) }) + addFn("atanh", fn = object : ScopeCallable { override suspend fun call(scp: Scope): Obj = ObjReal(atanh(scp.args.firstAndOnly().toDouble())) }) - addFn("exp") { - ObjReal(exp(args.firstAndOnly().toDouble())) - } - addFn("ln") { - ObjReal(ln(args.firstAndOnly().toDouble())) - } + addFn("exp", fn = object : ScopeCallable { override suspend fun call(scp: Scope): Obj = ObjReal(exp(scp.args.firstAndOnly().toDouble())) }) + addFn("ln", fn = object : ScopeCallable { override suspend fun call(scp: Scope): Obj = ObjReal(ln(scp.args.firstAndOnly().toDouble())) }) - addFn("log10") { - ObjReal(log10(args.firstAndOnly().toDouble())) - } + addFn("log10", fn = object : ScopeCallable { override suspend fun call(scp: Scope): Obj = ObjReal(log10(scp.args.firstAndOnly().toDouble())) }) - addFn("log2") { - ObjReal(log2(args.firstAndOnly().toDouble())) - } + addFn("log2", fn = object : ScopeCallable { override suspend fun call(scp: Scope): Obj = ObjReal(log2(scp.args.firstAndOnly().toDouble())) }) - addFn("pow") { - requireExactCount(2) - ObjReal( - (args[0].toDouble()).pow(args[1].toDouble()) - ) - } - addFn("sqrt") { - ObjReal( - sqrt(args.firstAndOnly().toDouble()) - ) - } - addFn("abs") { - val x = args.firstAndOnly() - if (x is ObjInt) ObjInt(x.value.absoluteValue) else ObjReal(x.toDouble().absoluteValue) - } + addFn("pow", fn = object : ScopeCallable { + override suspend fun call(scp: Scope): Obj { + scp.requireExactCount(2) + return ObjReal((scp.args[0].toDouble()).pow(scp.args[1].toDouble())) + } + }) + addFn("sqrt", fn = object : ScopeCallable { override suspend fun call(scp: Scope): Obj = ObjReal(sqrt(scp.args.firstAndOnly().toDouble())) }) + addFn("abs", fn = object : ScopeCallable { + override suspend fun call(scp: Scope): Obj { + val x = scp.args.firstAndOnly() + return if (x is ObjInt) ObjInt(x.value.absoluteValue) else ObjReal(x.toDouble().absoluteValue) + } + }) - addFnDoc( + addFnDoc( "clamp", doc = "Clamps the value within the specified range. If the value is outside the range, it is set to the nearest boundary. Respects inclusive/exclusive range ends.", params = listOf(ParamDoc("value"), ParamDoc("range")), - moduleName = "lyng.stdlib" - ) { - val value = requiredArg(0) - val range = requiredArg(1) - - var result = value - if (range.start != null && !range.start.isNull) { - if (result.compareTo(this, range.start) < 0) { - result = range.start - } - } - if (range.end != null && !range.end.isNull) { - val cmp = range.end.compareTo(this, result) - if (range.isEndInclusive) { - if (cmp < 0) result = range.end - } else { - if (cmp <= 0) { - if (range.end is ObjInt) { - result = ObjInt.of(range.end.value - 1) - } else if (range.end is ObjChar) { - result = ObjChar((range.end.value.code - 1).toChar()) - } else { - // For types where we can't easily find "previous" value (like Real), - // we just return the exclusive boundary as a fallback. - result = range.end + moduleName = "lyng.stdlib", + fn = object : ScopeCallable { + override suspend fun call(scp: Scope): Obj { + val value = scp.requiredArg(0) + val range = scp.requiredArg(1) + + var result = value + if (range.start != null && !range.start.isNull) { + if (result.compareTo(scp, range.start) < 0) { + result = range.start } } + if (range.end != null && !range.end.isNull) { + val cmp = range.end.compareTo(scp, result) + if (range.isEndInclusive) { + if (cmp < 0) result = range.end + } else { + if (cmp <= 0) { + if (range.end is ObjInt) { + result = ObjInt.of(range.end.value - 1) + } else if (range.end is ObjChar) { + result = ObjChar((range.end.value.code - 1).toChar()) + } else { + // For types where we can't easily find "previous" value (like Real), + // we just return the exclusive boundary as a fallback. + result = range.end + } + } + } + } + return result } } - result - } + ) - addVoidFn("assert") { - val cond = requiredArg(0) - val message = if (args.size > 1) - ": " + (args[1] as Statement).execute(this).toString(this).value - else "" - if (!cond.value == true) - raiseError(ObjAssertionFailedException(this, "Assertion failed$message")) - } + addVoidFn("assert", fn = object : VoidScopeCallable { + override suspend fun call(scp: Scope) { + val cond = scp.requiredArg(0) + val message = if (scp.args.size > 1) + ": " + (scp.args[1] as Statement).execute(scp).toString(scp).value + else "" + if (!cond.value == true) + scp.raiseError(ObjAssertionFailedException(scp, "Assertion failed$message")) + } + }) - addVoidFn("assertEquals") { - val a = requiredArg(0) - val b = requiredArg(1) - if (a.compareTo(this, b) != 0) - raiseError( - ObjAssertionFailedException( - this, - "Assertion failed: ${a.inspect(this)} == ${b.inspect(this)}" + addVoidFn("assertEquals", fn = object : VoidScopeCallable { + override suspend fun call(scp: Scope) { + val a = scp.requiredArg(0) + val b = scp.requiredArg(1) + if (a.compareTo(scp, b) != 0) + scp.raiseError( + ObjAssertionFailedException( + scp, + "Assertion failed: ${a.inspect(scp)} == ${b.inspect(scp)}" + ) ) - ) - } + } + }) // alias used in tests - addVoidFn("assertEqual") { - val a = requiredArg(0) - val b = requiredArg(1) - if (a.compareTo(this, b) != 0) - raiseError( - ObjAssertionFailedException( - this, - "Assertion failed: ${a.inspect(this)} == ${b.inspect(this)}" + addVoidFn("assertEqual", fn = object : VoidScopeCallable { + override suspend fun call(scp: Scope) { + val a = scp.requiredArg(0) + val b = scp.requiredArg(1) + if (a.compareTo(scp, b) != 0) + scp.raiseError( + ObjAssertionFailedException( + scp, + "Assertion failed: ${a.inspect(scp)} == ${b.inspect(scp)}" + ) ) - ) - } - addVoidFn("assertNotEquals") { - val a = requiredArg(0) - val b = requiredArg(1) - if (a.compareTo(this, b) == 0) - raiseError( - ObjAssertionFailedException( - this, - "Assertion failed: ${a.inspect(this)} != ${b.inspect(this)}" + } + }) + addVoidFn("assertNotEquals", fn = object : VoidScopeCallable { + override suspend fun call(scp: Scope) { + val a = scp.requiredArg(0) + val b = scp.requiredArg(1) + if (a.compareTo(scp, b) == 0) + scp.raiseError( + ObjAssertionFailedException( + scp, + "Assertion failed: ${a.inspect(scp)} != ${b.inspect(scp)}" + ) ) - ) - } - addFnDoc( + } + }) + addFnDoc( "assertThrows", doc = """ Asserts that the provided code block throws an exception, with or without exception: @@ -249,83 +235,97 @@ class Script( If an expected exception class is provided, it checks that the thrown exception is of that class. If no expected class is provided, any exception will be accepted. - """.trimIndent() - ) { - val code: Statement - val expectedClass: ObjClass? - when (args.size) { - 1 -> { - code = requiredArg(0) - expectedClass = null - } + """.trimIndent(), + fn = object : ScopeCallable { + override suspend fun call(scp: Scope): Obj { + val code: Statement + val expectedClass: ObjClass? + when (scp.args.size) { + 1 -> { + code = scp.requiredArg(0) + expectedClass = null + } - 2 -> { - code = requiredArg(1) - expectedClass = requiredArg(0) - } + 2 -> { + code = scp.requiredArg(1) + expectedClass = scp.requiredArg(0) + } - else -> raiseIllegalArgument("Expected 1 or 2 arguments, got ${args.size}") - } - val result = try { - code.execute(this) - null - } catch (e: ExecutionError) { - e.errorObject - } catch (_: ScriptError) { - ObjNull - } - if (result == null) raiseError( - ObjAssertionFailedException( - this, - "Expected exception but nothing was thrown" - ) - ) - expectedClass?.let { - if (!result.isInstanceOf(it)) { - val actual = if (result is ObjException) result.exceptionClass else result.objClass - raiseError("Expected $it, got $actual") + else -> scp.raiseIllegalArgument("Expected 1 or 2 arguments, got ${scp.args.size}") + } + val result = try { + code.execute(scp) + null + } catch (e: ExecutionError) { + e.errorObject + } catch (_: ScriptError) { + ObjNull + } + if (result == null) scp.raiseError( + ObjAssertionFailedException( + scp, + "Expected exception but nothing was thrown" + ) + ) + expectedClass?.let { + if (!result.isInstanceOf(it)) { + val actual = if (result is ObjException) result.exceptionClass else result.objClass + scp.raiseError("Expected $it, got $actual") + } + } + return result ?: ObjNull } } - result - } + ) - addFn("dynamic") { - ObjDynamic.create(this, requireOnlyArg()) - } + addFn("dynamic", fn = object : ScopeCallable { + override suspend fun call(scp: Scope): Obj { + return ObjDynamic.create(scp, scp.requireOnlyArg()) + } + }) val root = this val mathClass = ObjClass("Math").apply { - addFn("sqrt") { - ObjReal(sqrt(args.firstAndOnly().toDouble())) - } + addFn("sqrt", fn = object : ScopeCallable { + override suspend fun call(scp: Scope): Obj { + return ObjReal(sqrt(scp.args.firstAndOnly().toDouble())) + } + }) } addItem("Math", false, ObjInstance(mathClass).apply { instanceScope = Scope(root, thisObj = this) }) - addFn("require") { - val condition = requiredArg(0) - if (!condition.value) { - var message = args.list.getOrNull(1) - if (message is Statement) message = message.execute(this) - raiseIllegalArgument(message?.toString() ?: "requirement not met") + addFn("require", fn = object : ScopeCallable { + override suspend fun call(scp: Scope): Obj { + val condition = scp.requiredArg(0) + if (!condition.value) { + var message = scp.args.list.getOrNull(1) + if (message is Statement) message = message.execute(scp) + scp.raiseIllegalArgument(message?.toString() ?: "requirement not met") + } + return ObjVoid } - ObjVoid - } - addFn("check") { - val condition = requiredArg(0) - if (!condition.value) { - var message = args.list.getOrNull(1) - if (message is Statement) message = message.execute(this) - raiseIllegalState(message?.toString() ?: "check failed") + }) + addFn("check", fn = object : ScopeCallable { + override suspend fun call(scp: Scope): Obj { + val condition = scp.requiredArg(0) + if (!condition.value) { + var message = scp.args.list.getOrNull(1) + if (message is Statement) message = message.execute(scp) + scp.raiseIllegalState(message?.toString() ?: "check failed") + } + return ObjVoid } - ObjVoid - } - addFn("traceScope") { - this.trace(args.getOrNull(0)?.toString() ?: "") - ObjVoid - } + }) + addFn("traceScope", fn = object : ScopeCallable { + override suspend fun call(scp: Scope): Obj { + scp.trace(scp.args.getOrNull(0)?.toString() ?: "") + return ObjVoid + } + }) +/* addVoidFn("delay") { val a = args.firstAndOnly() when (a) { @@ -335,7 +335,7 @@ class Script( else -> raiseIllegalArgument("Expected Int, Real or Duration, got ${a.inspect(this)}") } } - +*/ addConst("Object", rootObjectType) addConst("Real", ObjReal.type) addConst("String", ObjString.type) @@ -362,7 +362,7 @@ class Script( addConst("Flow", ObjFlow.type) addConst("Regex", ObjRegex.type) - +/* addFn("launch") { val callable = requireOnlyArg() ObjDeferred(globalDefer { @@ -380,7 +380,7 @@ class Script( // we'll need it for the producer ObjFlow(requireOnlyArg(), this) } - +*/ val pi = ObjReal(PI) addConstDoc( name = "π", @@ -403,60 +403,69 @@ class Script( addTextPackages( rootLyng ) - addPackage("lyng.buffer") { - it.addConstDoc( - name = "Buffer", - value = ObjBuffer.type, - doc = "Immutable sequence of bytes. Use for binary data and IO.", - type = type("lyng.Class") - ) - it.addConstDoc( - name = "MutableBuffer", - value = ObjMutableBuffer.type, - doc = "Mutable byte buffer. Supports in-place modifications.", - type = type("lyng.Class") - ) - } - addPackage("lyng.serialization") { - it.addConstDoc( - name = "Lynon", - value = ObjLynonClass, - doc = "Lynon serialization utilities: encode/decode data structures to a portable binary/text form.", - type = type("lyng.Class") - ) - } - addPackage("lyng.time") { - it.addConstDoc( - name = "Instant", - value = ObjInstant.type, - doc = "Point in time (epoch-based).", - type = type("lyng.Class") - ) - it.addConstDoc( - name = "DateTime", - value = ObjDateTime.type, - doc = "Point in time in a specific time zone.", - type = type("lyng.Class") - ) - it.addConstDoc( - name = "Duration", - value = ObjDuration.type, - doc = "Time duration with millisecond precision.", - type = type("lyng.Class") - ) - it.addVoidFnDoc( - "delay", - doc = "Suspend for the given time. Accepts Duration, Int seconds, or Real seconds." - ) { - val a = args.firstAndOnly() - when (a) { - 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)}") - } + addPackage("lyng.buffer", object : ModuleBuilder { + override suspend fun build(ms: ModuleScope) { + ms.addConstDoc( + name = "Buffer", + value = ObjBuffer.type, + doc = "Immutable sequence of bytes. Use for binary data and IO.", + type = type("lyng.Class") + ) + ms.addConstDoc( + name = "MutableBuffer", + value = ObjMutableBuffer.type, + doc = "Mutable byte buffer. Supports in-place modifications.", + type = type("lyng.Class") + ) } - } + }) + addPackage("lyng.serialization", object : ModuleBuilder { + override suspend fun build(ms: ModuleScope) { + ms.addConstDoc( + name = "Lynon", + value = ObjLynonClass, + doc = "Lynon serialization utilities: encode/decode data structures to a portable binary/text form.", + type = type("lyng.Class") + ) + } + }) + addPackage("lyng.time", object : ModuleBuilder { + override suspend fun build(ms: ModuleScope) { + ms.addConstDoc( + name = "Instant", + value = ObjInstant.type, + doc = "Point in time (epoch-based).", + type = type("lyng.Class") + ) + ms.addConstDoc( + name = "DateTime", + value = ObjDateTime.type, + doc = "Point in time in a specific time zone.", + type = type("lyng.Class") + ) + ms.addConstDoc( + name = "Duration", + value = ObjDuration.type, + doc = "Time duration with millisecond precision.", + type = type("lyng.Class") + ) + ms.addVoidFnDoc( + "delay", + doc = "Suspend for the given time. Accepts Duration, Int seconds, or Real seconds.", + fn = object : VoidScopeCallable { + override suspend fun call(scp: Scope) { + val a = scp.args.firstAndOnly() + when (a) { + is ObjInt -> delay(a.value * 1000) + is ObjReal -> delay((a.value * 1000).roundToLong()) + is ObjDuration -> delay(a.duration) + else -> scp.raiseIllegalArgument("Expected Duration, Int or Real, got ${a.inspect(scp)}") + } + } + } + ) + } + }) } } diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/miniast/DocRegistrationHelpers.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/miniast/DocRegistrationHelpers.kt index 62d963e..db8e539 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/miniast/DocRegistrationHelpers.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/miniast/DocRegistrationHelpers.kt @@ -17,9 +17,7 @@ package net.sergeych.lyng.miniast -import net.sergeych.lyng.ModuleScope -import net.sergeych.lyng.Scope -import net.sergeych.lyng.Visibility +import net.sergeych.lyng.* import net.sergeych.lyng.obj.Obj import net.sergeych.lyng.obj.ObjClass import net.sergeych.lyng.obj.ObjVoid @@ -39,10 +37,10 @@ inline fun Scope.addFnDoc( returns: TypeDoc? = null, tags: Map> = emptyMap(), moduleName: String? = null, - crossinline fn: suspend Scope.() -> T + fn: ScopeCallable ) { // Register runtime function(s) - addFn(*names) { fn() } + addFn(*names, fn = fn) // Determine module val mod = moduleName ?: findModuleNameOrUnknown() // Register docs once per name @@ -56,7 +54,7 @@ inline fun Scope.addVoidFnDoc( doc: String, tags: Map> = emptyMap(), moduleName: String? = null, - crossinline fn: suspend Scope.() -> Unit + fn: VoidScopeCallable ) { addFnDoc( *names, @@ -64,11 +62,14 @@ inline fun Scope.addVoidFnDoc( params = emptyList(), returns = null, tags = tags, - moduleName = moduleName - ) { - fn(this) - ObjVoid - } + moduleName = moduleName, + fn = object : ScopeCallable { + override suspend fun call(sc: Scope): Obj { + fn.call(sc) + return ObjVoid + } + } + ) } fun Scope.addConstDoc( @@ -97,7 +98,7 @@ fun ObjClass.addFnDoc( visibility: Visibility = Visibility.Public, tags: Map> = emptyMap(), moduleName: String? = null, - code: suspend Scope.() -> Obj + code: ScopeCallable ) { // Register runtime method addFn(name, isOpen, visibility, code = code) @@ -135,7 +136,7 @@ fun ObjClass.addClassFnDoc( isOpen: Boolean = false, tags: Map> = emptyMap(), moduleName: String? = null, - code: suspend Scope.() -> Obj + code: ScopeCallable ) { addClassFn(name, isOpen, code) BuiltinDocRegistry.module(moduleName ?: ownerModuleNameFromClassOrUnknown()) { @@ -151,8 +152,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: ScopeCallable? = null, + setter: ScopeCallable? = null ) { addProperty(name, getter, setter, visibility) BuiltinDocRegistry.module(moduleName ?: ownerModuleNameFromClassOrUnknown()) { diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/Accessor.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/Accessor.kt index a6d339c..83ce58c 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/Accessor.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/Accessor.kt @@ -1,5 +1,5 @@ /* - * Copyright 2025 Sergey S. Chernov real.sergeych@gmail.com + * Copyright 2026 Sergey S. Chernov real.sergeych@gmail.com * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -32,29 +32,41 @@ typealias DocCompiler = Compiler */ typealias Accessor = ObjRef +interface AccessorGetter { + suspend fun call(scope: Scope): ObjRecord +} + +interface AccessorSetter { + suspend fun call(scope: Scope, value: Obj) +} + /** Lambda-based reference for edge cases that still construct access via lambdas. */ private class LambdaRef( - private val getterFn: suspend (Scope) -> ObjRecord, - private val setterFn: (suspend (Pos, Scope, Obj) -> Unit)? = null + private val getterFn: AccessorGetter, + private val setterFn: AccessorSetter? = null ) : ObjRef { - override suspend fun get(scope: Scope): ObjRecord = getterFn(scope) + override suspend fun get(scope: Scope): ObjRecord = getterFn.call(scope) override suspend fun setAt(pos: Pos, scope: Scope, newValue: Obj) { val s = setterFn ?: throw ScriptError(pos, "can't assign value") - s(pos, scope, newValue) + s.call(scope, newValue) } } // Factory functions to preserve current call sites like `Accessor { ... }` -fun Accessor(getter: suspend (Scope) -> ObjRecord): Accessor = LambdaRef(getter) +fun Accessor(getter: AccessorGetter): Accessor = LambdaRef(getter) fun Accessor( - getter: suspend (Scope) -> ObjRecord, - setter: suspend (Scope, Obj) -> Unit -): Accessor = LambdaRef(getter) { _, scope, value -> setter(scope, value) } + getter: AccessorGetter, + setter: AccessorSetter +): Accessor = LambdaRef(getter, setter) // Compatibility shims used throughout Compiler: `.getter(...)` and `.setter(pos)` -val Accessor.getter: suspend (Scope) -> ObjRecord - get() = { scope -> this.get(scope) } +val Accessor.getter: AccessorGetter + get() = object : AccessorGetter { + override suspend fun call(scope: Scope): ObjRecord = this@getter.get(scope) + } -fun Accessor.setter(pos: Pos): suspend (Scope, Obj) -> Unit = { scope, newValue -> - this.setAt(pos, scope, newValue) +fun Accessor.setter(pos: Pos): AccessorSetter = object : AccessorSetter { + override suspend fun call(scope: Scope, value: Obj) { + this@setter.setAt(pos, scope, value) + } } \ No newline at end of file diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/Obj.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/Obj.kt index 9bbad7f..bab74e9 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/Obj.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/Obj.kt @@ -32,6 +32,14 @@ import net.sergeych.lynon.LynonDecoder import net.sergeych.lynon.LynonEncoder import net.sergeych.lynon.LynonType +fun interface OnNotFound { + suspend fun call(): Obj? +} + +fun interface EnumerateCallback { + suspend fun call(element: Obj): Boolean +} + open class Obj { open val isConst: Boolean = false @@ -91,7 +99,7 @@ open class Obj { scope: Scope, name: String, args: Arguments = Arguments.EMPTY, - onNotFoundResult: (suspend () -> Obj?)? = null + onNotFoundResult: OnNotFound? = null ): Obj { // 0. Prefer private member of current class context scope.currentClassCtx?.let { caller -> @@ -100,7 +108,7 @@ open class Obj { if (rec.type == ObjRecord.Type.Property) { if (args.isEmpty()) return (rec.value as ObjProperty).callGetter(scope, this, caller) } else if (rec.type != ObjRecord.Type.Delegated) { - return rec.value.invoke(scope, this, args, caller) + return rec.value.invokeCallable(scope, this, args, caller) } } } @@ -114,12 +122,12 @@ open class Obj { val decl = rec.declaringClass ?: cls val caller = scope.currentClassCtx if (!canAccessMember(rec.visibility, decl, caller, name)) - scope.raiseError(ObjIllegalAccessException(scope, "can't invoke ${name}: not visible (declared in ${decl.className}, caller ${caller?.className ?: "?"})")) + scope.raiseError(ObjIllegalAccessException(scope, "can't invokeCallable ${name}: not visible (declared in ${decl.className}, caller ${caller?.className ?: "?"})")) if (rec.type == ObjRecord.Type.Property) { if (args.isEmpty()) return (rec.value as ObjProperty).callGetter(scope, this, decl) } else if (rec.type != ObjRecord.Type.Delegated) { - return rec.value.invoke(scope, this, args, decl) + return rec.value.invokeCallable(scope, this, args, decl) } } } @@ -130,7 +138,7 @@ open class Obj { if (extension.type == ObjRecord.Type.Property) { if (args.isEmpty()) return (extension.value as ObjProperty).callGetter(scope, this, extension.declaringClass) } else if (extension.type != ObjRecord.Type.Delegated) { - return extension.value.invoke(scope, this, args) + return extension.value.invokeCallable(scope, this, args) } } @@ -141,18 +149,18 @@ open class Obj { val decl = rec.declaringClass ?: cls val caller = scope.currentClassCtx if (!canAccessMember(rec.visibility, decl, caller, name)) - scope.raiseError(ObjIllegalAccessException(scope, "can't invoke ${name}: not visible (declared in ${decl.className}, caller ${caller?.className ?: "?"})")) + scope.raiseError(ObjIllegalAccessException(scope, "can't invokeCallable ${name}: not visible (declared in ${decl.className}, caller ${caller?.className ?: "?"})")) if (rec.type == ObjRecord.Type.Property) { if (args.isEmpty()) return (rec.value as ObjProperty).callGetter(scope, this, decl) } else if (rec.type != ObjRecord.Type.Delegated) { - return rec.value.invoke(scope, this, args, decl) + return rec.value.invokeCallable(scope, this, args, decl) } } } } - return onNotFoundResult?.invoke() + return onNotFoundResult?.call() ?: scope.raiseError( "no such member: $name on ${objClass.className}. Considered order: ${objClass.renderLinearization(true)}. " + "Tip: try this@Base.$name(...) or (obj as Base).$name(...) if ambiguous" @@ -174,9 +182,11 @@ open class Obj { open suspend fun compareTo(scope: Scope, other: Obj): Int { if (other === this) return 0 if (other === ObjNull || other === ObjUnset || other === ObjVoid) return 2 - return invokeInstanceMethod(scope, "compareTo", Arguments(other)) { - scope.raiseNotImplemented("compareTo for ${objClass.className}") - }.cast(scope).toInt() + return invokeInstanceMethod(scope, "compareTo", Arguments(other), onNotFoundResult = object : OnNotFound { + override suspend fun call(): Obj? { + scope.raiseNotImplemented("compareTo for ${objClass.className}") + } + }).toInt() } open suspend fun equals(scope: Scope, other: Obj): Boolean { @@ -202,16 +212,16 @@ open class Obj { * * IF callback returns false, iteration is stopped. */ - open suspend fun enumerate(scope: Scope, callback: suspend (Obj) -> Boolean) { + open suspend fun enumerate(scope: Scope, callback: EnumerateCallback) { val iterator = invokeInstanceMethod(scope, "iterator") val hasNext = iterator.getInstanceMethod(scope, "hasNext") val next = iterator.getInstanceMethod(scope, "next") var closeIt = false try { - while (hasNext.invoke(scope, iterator).toBool()) { - val nextValue = next.invoke(scope, iterator) + while (hasNext.invokeCallable(scope, iterator).toBool()) { + val nextValue = next.invokeCallable(scope, iterator) val shouldContinue = try { - callback(nextValue) + callback.call(nextValue) } catch (e: Exception) { // iteration aborted due to exception in callback closeIt = true @@ -448,7 +458,7 @@ open class Obj { if (rec.visibility == Visibility.Private && !rec.isAbstract) { val resolved = resolveRecord(scope, rec, name, caller) if (resolved.type == ObjRecord.Type.Fun && resolved.value is Statement) - return resolved.copy(value = resolved.value.invoke(scope, this, Arguments.EMPTY, caller)) + return resolved.copy(value = resolved.value.invokeCallable(scope, this, Arguments.EMPTY, caller)) return resolved } } @@ -462,7 +472,7 @@ open class Obj { val decl = rec.declaringClass ?: cls val resolved = resolveRecord(scope, rec, name, decl) if (resolved.type == ObjRecord.Type.Fun && resolved.value is Statement) - return resolved.copy(value = resolved.value.invoke(scope, this, Arguments.EMPTY, decl)) + return resolved.copy(value = resolved.value.invokeCallable(scope, this, Arguments.EMPTY, decl)) return resolved } } @@ -472,7 +482,7 @@ open class Obj { if (extension != null) { val resolved = resolveRecord(scope, extension, name, extension.declaringClass) if (resolved.type == ObjRecord.Type.Fun && resolved.value is Statement) - return resolved.copy(value = resolved.value.invoke(scope, this, Arguments.EMPTY, extension.declaringClass)) + return resolved.copy(value = resolved.value.invokeCallable(scope, this, Arguments.EMPTY, extension.declaringClass)) return resolved } @@ -486,7 +496,7 @@ open class Obj { scope.raiseError(ObjIllegalAccessException(scope, "can't access field ${name}: not visible (declared in ${decl.className}, caller ${caller?.className ?: "?"})")) val resolved = resolveRecord(scope, rec, name, decl) if (resolved.type == ObjRecord.Type.Fun && resolved.value is Statement) - return resolved.copy(value = resolved.value.invoke(scope, this, Arguments.EMPTY, decl)) + return resolved.copy(value = resolved.value.invokeCallable(scope, this, Arguments.EMPTY, decl)) return resolved } } @@ -502,13 +512,13 @@ open class Obj { val del = obj.delegate ?: scope.raiseError("Internal error: delegated property $name has no delegate") val th = if (this === ObjVoid) ObjNull else this val res = del.invokeInstanceMethod(scope, "getValue", Arguments(th, ObjString(name)), onNotFoundResult = { - // If getValue not found, return a wrapper that calls invoke + // If getValue not found, return a wrapper that calls invokeCallable object : Statement() { override val pos: Pos = Pos.builtIn override suspend fun execute(s: Scope): Obj { val th2 = if (s.thisObj === ObjVoid) ObjNull else s.thisObj val allArgs = (listOf(th2, ObjString(name)) + s.args.list).toTypedArray() - return del.invokeInstanceMethod(s, "invoke", Arguments(*allArgs)) + return del.invokeInstanceMethod(s, "invokeCallable", Arguments(*allArgs)) } } }) @@ -605,18 +615,20 @@ open class Obj { scope.raiseNotImplemented() } - suspend fun invoke(scope: Scope, thisObj: Obj, args: Arguments, declaringClass: ObjClass? = null): Obj = + suspend fun invokeCallable(scope: Scope, thisObj: Obj, args: Arguments, declaringClass: ObjClass? = null): Obj = if (PerfFlags.SCOPE_POOL) - scope.withChildFrame(args, newThisObj = thisObj) { child -> - if (declaringClass != null) child.currentClassCtx = declaringClass - callOn(child) - } + scope.withChildFrame(args, newThisObj = thisObj, block = object : ScopeBlock { + override suspend fun call(child: Scope): Obj { + if (declaringClass != null) child.currentClassCtx = declaringClass + return callOn(child) + } + }) else callOn(scope.createChildScope(scope.pos, args = args, newThisObj = thisObj).also { if (declaringClass != null) it.currentClassCtx = declaringClass }) - suspend fun invoke(scope: Scope, thisObj: Obj, vararg args: Obj): Obj = + suspend fun invokeCallable(scope: Scope, thisObj: Obj, vararg args: Obj): Obj = callOn( scope.createChildScope( scope.pos, @@ -625,7 +637,7 @@ open class Obj { ) ) - suspend fun invoke(scope: Scope, thisObj: Obj): Obj = + suspend fun invokeCallable(scope: Scope, thisObj: Obj): Obj = callOn( scope.createChildScope( scope.pos, @@ -634,7 +646,7 @@ open class Obj { ) ) - suspend fun invoke(scope: Scope, atPos: Pos, thisObj: Obj, args: Arguments): Obj = + suspend fun invokeCallable(scope: Scope, atPos: Pos, thisObj: Obj, args: Arguments): Obj = callOn(scope.createChildScope(atPos, args = args, newThisObj = thisObj)) @@ -681,117 +693,138 @@ open class Obj { name = "toString", doc = "Returns a string representation of the object.", returns = type("lyng.String"), - moduleName = "lyng.stdlib" - ) { - thisObj.toString(this, true) - } + moduleName = "lyng.stdlib", + code = object : ScopeCallable { + override suspend fun call(scp: Scope): Obj = scp.thisObj.toString(scp, true) + } + ) addFnDoc( name = "inspect", doc = "Returns a detailed string representation for debugging.", returns = type("lyng.String"), - moduleName = "lyng.stdlib" - ) { - thisObj.inspect(this).toObj() - } + moduleName = "lyng.stdlib", + code = object : ScopeCallable { + override suspend fun call(scp: Scope): Obj = scp.thisObj.inspect(scp).toObj() + } + ) addFnDoc( name = "contains", doc = "Returns true if the object contains the given element.", params = listOf(ParamDoc("element")), returns = type("lyng.Bool"), - moduleName = "lyng.stdlib" - ) { - ObjBool(thisObj.contains(this, args.firstAndOnly())) - } + moduleName = "lyng.stdlib", + code = object : ScopeCallable { + override suspend fun call(scp: Scope): Obj = ObjBool(scp.thisObj.contains(scp, scp.args.firstAndOnly())) + } + ) // utilities addFnDoc( name = "let", doc = "Calls the specified function block with `this` value as its argument and returns its result.", params = listOf(ParamDoc("block")), - moduleName = "lyng.stdlib" - ) { - args.firstAndOnly().callOn(createChildScope(Arguments(thisObj))) - } + moduleName = "lyng.stdlib", + code = object : ScopeCallable { + override suspend fun call(scp: Scope): Obj = + scp.args.firstAndOnly().callOn(scp.createChildScope(Arguments(scp.thisObj))) + } + ) addFnDoc( name = "apply", doc = "Calls the specified function block with `this` value as its receiver and returns `this` value.", params = listOf(ParamDoc("block")), - moduleName = "lyng.stdlib" - ) { - val body = args.firstAndOnly() - (thisObj as? ObjInstance)?.let { - body.callOn(ApplyScope(this, it.instanceScope)) - } ?: run { - body.callOn(this) + moduleName = "lyng.stdlib", + code = object : ScopeCallable { + override suspend fun call(scp: Scope): Obj { + val body = scp.args.firstAndOnly() + (scp.thisObj as? ObjInstance)?.let { + body.callOn(ApplyScope(scp, it.instanceScope)) + } ?: run { + body.callOn(scp) + } + return scp.thisObj + } } - thisObj - } + ) addFnDoc( name = "also", doc = "Calls the specified function block with `this` value as its argument and returns `this` value.", params = listOf(ParamDoc("block")), - moduleName = "lyng.stdlib" - ) { - args.firstAndOnly().callOn(createChildScope(Arguments(thisObj))) - thisObj - } + moduleName = "lyng.stdlib", + code = object : ScopeCallable { + override suspend fun call(scp: Scope): Obj { + scp.args.firstAndOnly().callOn(scp.createChildScope(Arguments(scp.thisObj))) + return scp.thisObj + } + } + ) addFnDoc( name = "run", doc = "Calls the specified function block with `this` value as its receiver and returns its result.", params = listOf(ParamDoc("block")), - moduleName = "lyng.stdlib" - ) { - args.firstAndOnly().callOn(this) - } - addFn("getAt") { - requireExactCount(1) - thisObj.getAt(this, requiredArg(0)) - } - addFn("putAt") { - requireExactCount(2) - val newValue = args[1] - thisObj.putAt(this, requiredArg(0), newValue) - newValue - } + moduleName = "lyng.stdlib", + code = object : ScopeCallable { + override suspend fun call(scp: Scope): Obj = scp.args.firstAndOnly().callOn(scp) + } + ) + addFn("getAt", code = object : ScopeCallable { + override suspend fun call(scp: Scope): Obj { + scp.requireExactCount(1) + return scp.thisObj.getAt(scp, scp.requiredArg(0)) + } + }) + addFn("putAt", code = object : ScopeCallable { + override suspend fun call(scp: Scope): Obj { + scp.requireExactCount(2) + val newValue = scp.args[1] + scp.thisObj.putAt(scp, scp.requiredArg(0), newValue) + return newValue + } + }) addFnDoc( name = "toJsonString", doc = "Encodes this object to a JSON string.", returns = type("lyng.String"), - moduleName = "lyng.stdlib" - ) { - thisObj.toJson(this).toString().toObj() - } + moduleName = "lyng.stdlib", + code = object : ScopeCallable { + override suspend fun call(scp: Scope): Obj = + scp.thisObj.toJson(scp).toString().toObj() + } + ) addFnDoc( name = "clamp", doc = "Clamps this value within the specified range. If the value is outside the range, it is set to the nearest boundary. Respects inclusive/exclusive range ends.", params = listOf(ParamDoc("range")), - moduleName = "lyng.stdlib" - ) { - val range = requiredArg(0) + moduleName = "lyng.stdlib", + code = object : ScopeCallable { + override suspend fun call(scp: Scope): Obj { + val range = scp.requiredArg(0) - var result = thisObj - if (range.start != null && !range.start.isNull) { - if (result.compareTo(this, range.start) < 0) { - result = range.start - } - } - if (range.end != null && !range.end.isNull) { - val cmp = range.end.compareTo(this, result) - if (range.isEndInclusive) { - if (cmp < 0) result = range.end - } else { - if (cmp <= 0) { - if (range.end is ObjInt) { - result = ObjInt.of(range.end.value - 1) - } else if (range.end is ObjChar) { - result = ObjChar((range.end.value.code - 1).toChar()) - } else { - result = range.end + var result = scp.thisObj + if (range.start != null && !range.start.isNull) { + if (result.compareTo(scp, range.start) < 0) { + result = range.start } } + if (range.end != null && !range.end.isNull) { + val cmp = range.end.compareTo(scp, result) + if (range.isEndInclusive) { + if (cmp < 0) result = range.end + } else { + if (cmp <= 0) { + if (range.end is ObjInt) { + result = ObjInt.of(range.end.value - 1) + } else if (range.end is ObjChar) { + result = ObjChar((range.end.value.code - 1).toChar()) + } else { + result = range.end + } + } + } + } + return result } } - result - } + ) } @@ -919,7 +952,7 @@ object ObjUnset : Obj() { scope: Scope, name: String, args: Arguments, - onNotFoundResult: (suspend () -> Obj?)? + onNotFoundResult: OnNotFound? ): Obj = scope.raiseUnset() override suspend fun getAt(scope: Scope, index: Obj): Obj = scope.raiseUnset() diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjArray.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjArray.kt index 4209d06..814b793 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjArray.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjArray.kt @@ -17,6 +17,8 @@ package net.sergeych.lyng.obj +import net.sergeych.lyng.Scope +import net.sergeych.lyng.ScopeCallable import net.sergeych.lyng.miniast.* val ObjArray by lazy { @@ -31,8 +33,11 @@ val ObjArray by lazy { name = "iterator", 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) } } + moduleName = "lyng.stdlib", + code = object : ScopeCallable { + override suspend fun call(scp: Scope): Obj = ObjArrayIterator(scp.thisObj).also { it.init(scp) } + } + ) addFnDoc( name = "contains", @@ -40,26 +45,31 @@ val ObjArray by lazy { params = listOf(ParamDoc("element")), returns = type("lyng.Bool"), isOpen = true, - moduleName = "lyng.stdlib" - ) { - val obj = args.firstAndOnly() - for (i in 0.. return@addFnDoc (mid).toObj() - cmp > 0 -> high = mid - 1 - else -> low = mid + 1 + val cmp = midVal.compareTo(scp, target) + when { + cmp == 0 -> return (mid).toObj() + cmp > 0 -> high = mid - 1 + else -> low = mid + 1 + } + } + + return (-low - 1).toObj() } } - - (-low - 1).toObj() - } + ) } } \ No newline at end of file diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjArrayIterator.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjArrayIterator.kt index 6a332a2..b62cfe3 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjArrayIterator.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjArrayIterator.kt @@ -1,5 +1,5 @@ /* - * Copyright 2025 Sergey S. Chernov real.sergeych@gmail.com + * Copyright 2026 Sergey S. Chernov real.sergeych@gmail.com * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,6 +18,7 @@ package net.sergeych.lyng.obj import net.sergeych.lyng.Scope +import net.sergeych.lyng.ScopeCallable class ObjArrayIterator(val array: Obj) : Obj() { @@ -35,16 +36,20 @@ class ObjArrayIterator(val array: Obj) : Obj() { companion object { val type by lazy { ObjClass("ArrayIterator", ObjIterator).apply { - addFn("next") { - val self = thisAs() - if (self.nextIndex < self.lastIndex) { - self.array.invokeInstanceMethod(this, "getAt", (self.nextIndex++).toObj()) - } else raiseError(ObjIterationFinishedException(this)) - } - addFn("hasNext") { - val self = thisAs() - if (self.nextIndex < self.lastIndex) ObjTrue else ObjFalse - } + addFn("next", code = object : ScopeCallable { + override suspend fun call(scp: Scope): Obj { + val self = scp.thisAs() + return if (self.nextIndex < self.lastIndex) { + self.array.invokeInstanceMethod(scp, "getAt", (self.nextIndex++).toObj()) + } else scp.raiseError(ObjIterationFinishedException(scp)) + } + }) + addFn("hasNext", code = object : ScopeCallable { + override suspend fun call(scp: Scope): Obj { + val self = scp.thisAs() + return if (self.nextIndex < self.lastIndex) ObjTrue else ObjFalse + } + }) } } } diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjBitBuffer.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjBitBuffer.kt index 2eaebf9..3338e3a 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjBitBuffer.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjBitBuffer.kt @@ -19,6 +19,7 @@ package net.sergeych.lyng.obj import net.sergeych.bintools.toDump import net.sergeych.lyng.Scope +import net.sergeych.lyng.ScopeCallable import net.sergeych.lyng.miniast.addPropertyDoc import net.sergeych.lyng.miniast.type import net.sergeych.lynon.BitArray @@ -35,29 +36,37 @@ class ObjBitBuffer(val bitArray: BitArray) : Obj() { val type = object: ObjClass("BitBuffer", ObjArray) { }.apply { - addFn("toBuffer") { - requireNoArgs() - ObjBuffer(thisAs().bitArray.asUByteArray()) - } - addFn("toDump") { - requireNoArgs() - ObjString( - thisAs().bitArray.asUByteArray().toDump() - ) - } + addFn("toBuffer", code = object : ScopeCallable { + override suspend fun call(scp: Scope): Obj { + scp.requireNoArgs() + return ObjBuffer(scp.thisAs().bitArray.asUByteArray()) + } + }) + addFn("toDump", code = object : ScopeCallable { + override suspend fun call(scp: Scope): Obj { + scp.requireNoArgs() + return ObjString( + scp.thisAs().bitArray.asUByteArray().toByteArray().toDump() + ) + } + }) addPropertyDoc( name = "size", doc = "Size of the bit buffer in bits.", type = type("lyng.Int"), moduleName = "lyng.stdlib", - getter = { thisAs().bitArray.size.toObj() } + getter = object : ScopeCallable { + override suspend fun call(scp: Scope): Obj = scp.thisAs().bitArray.size.toObj() + } ) addPropertyDoc( name = "sizeInBytes", doc = "Size of the bit buffer in full bytes (rounded up).", type = type("lyng.Int"), moduleName = "lyng.stdlib", - getter = { ObjInt((thisAs().bitArray.size + 7) shr 3) } + getter = object : ScopeCallable { + override suspend fun call(scp: Scope): Obj = ObjInt(((scp.thisAs().bitArray.size + 7) shr 3).toLong()) + } ) } } diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjBuffer.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjBuffer.kt index 563d232..f5d4c3a 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjBuffer.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjBuffer.kt @@ -23,6 +23,8 @@ import net.sergeych.bintools.decodeHex import net.sergeych.bintools.encodeToHex import net.sergeych.bintools.toDump import net.sergeych.lyng.Scope +import net.sergeych.lyng.ScopeCallable +import net.sergeych.lyng.miniast.addFnDoc import net.sergeych.lyng.miniast.addPropertyDoc import net.sergeych.lyng.miniast.type import net.sergeych.lynon.BitArray @@ -169,51 +171,85 @@ open class ObjBuffer(val byteArray: UByteArray) : Obj() { }) }.apply { - addClassFn("decodeBase64") { - ObjBuffer(requireOnlyArg().toString().decodeBase64Url().asUByteArray()) - } - addClassFn("decodeHex") { - ObjBuffer(requireOnlyArg().toString().decodeHex().asUByteArray()) - } + addClassFn("decodeBase64", code = object : ScopeCallable { + override suspend fun call(scp: Scope): Obj = + ObjBuffer(scp.requireOnlyArg().toString().decodeBase64Url().asUByteArray()) + }) + addClassFn("decodeHex", code = object : ScopeCallable { + override suspend fun call(scp: Scope): Obj = + ObjBuffer(scp.requireOnlyArg().toString().decodeHex().asUByteArray()) + }) addPropertyDoc( name = "size", doc = "Number of bytes in this buffer.", type = type("lyng.Int"), moduleName = "lyng.stdlib", - getter = { (this.thisObj as ObjBuffer).byteArray.size.toObj() } + getter = object : ScopeCallable { + override suspend fun call(scp: Scope): Obj = (scp.thisObj as ObjBuffer).byteArray.size.toObj() + } ) addPropertyDoc( name = "hex", doc = "Hexadecimal string representation of the buffer.", type = type("lyng.String"), moduleName = "lyng.stdlib", - getter = { thisAs().hex.toObj() } + getter = object : ScopeCallable { + override suspend fun call(scp: Scope): Obj = scp.thisAs().hex.toObj() + } ) addPropertyDoc( name = "base64", doc = "Base64 (URL-safe) string representation of the buffer.", type = type("lyng.String"), moduleName = "lyng.stdlib", - getter = { thisAs().base64.toObj() } + getter = object : ScopeCallable { + override suspend fun call(scp: Scope): Obj = scp.thisAs().base64.toObj() + } + ) + addFnDoc( + name = "decodeUtf8", + doc = "Decode the buffer content as a UTF-8 string.", + returns = type("lyng.String"), + moduleName = "lyng.stdlib", + code = object : ScopeCallable { + override suspend fun call(scp: Scope): Obj = + ObjString(scp.thisAs().byteArray.toByteArray().decodeToString()) + } + ) + addFnDoc( + name = "toMutable", + doc = "Return a mutable copy of this buffer.", + returns = type("lyng.MutableBuffer"), + moduleName = "lyng.stdlib", + code = object : ScopeCallable { + override suspend fun call(scp: Scope): Obj { + scp.requireNoArgs() + return ObjMutableBuffer(scp.thisAs().byteArray.copyOf()) + } + } + ) + addFnDoc( + name = "toDump", + doc = "Return a hexadecimal dump string of the buffer.", + returns = type("lyng.String"), + moduleName = "lyng.stdlib", + code = object : ScopeCallable { + override suspend fun call(scp: Scope): Obj { + scp.requireNoArgs() + return ObjString(scp.thisAs().byteArray.toByteArray().toDump()) + } + } + ) + addFnDoc( + name = "toBitInput", + doc = "Return a bit buffer for reading bits from this buffer.", + returns = type("lyng.BitBuffer"), + moduleName = "lyng.stdlib", + code = object : ScopeCallable { + override suspend fun call(scp: Scope): Obj = + ObjBitBuffer(BitArray(scp.thisAs().byteArray, 8)) + } ) - addFn("decodeUtf8") { - ObjString( - thisAs().byteArray.toByteArray().decodeToString() - ) - } - addFn("toMutable") { - requireNoArgs() - ObjMutableBuffer(thisAs().byteArray.copyOf()) - } - addFn("toDump") { - requireNoArgs() - ObjString( - thisAs().byteArray.toByteArray().toDump() - ) - } - addFn("toBitInput") { - ObjBitBuffer(BitArray(thisAs().byteArray, 8)) - } } } } \ No newline at end of file diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjChar.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjChar.kt index fbecdce..6885e6f 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjChar.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjChar.kt @@ -18,6 +18,7 @@ package net.sergeych.lyng.obj import net.sergeych.lyng.Scope +import net.sergeych.lyng.ScopeCallable import net.sergeych.lyng.miniast.addPropertyDoc import net.sergeych.lyng.miniast.type @@ -52,14 +53,16 @@ class ObjChar(val value: Char): Obj() { doc = "Unicode code point (UTF-16 code unit) of this character.", type = type("lyng.Int"), moduleName = "lyng.stdlib", - getter = { ObjInt((this.thisObj as ObjChar).value.code.toLong()) } + getter = object : ScopeCallable { + override suspend fun call(scp: Scope): Obj = ObjInt((scp.thisObj as ObjChar).value.code.toLong()) + } ) - addFn("isDigit") { - thisAs().value.isDigit().toObj() - } - addFn("isSpace") { - thisAs().value.isWhitespace().toObj() - } + addFn("isDigit", code = object : ScopeCallable { + override suspend fun call(scp: Scope): Obj = scp.thisAs().value.isDigit().toObj() + }) + addFn("isSpace", code = object : ScopeCallable { + override suspend fun call(scp: Scope): Obj = scp.thisAs().value.isWhitespace().toObj() + }) } } } \ No newline at end of file diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjClass.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjClass.kt index d00ebb4..93769eb 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjClass.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjClass.kt @@ -40,14 +40,18 @@ val ObjClassType by lazy { doc = "Full name of this class including package if available.", type = type("lyng.String"), moduleName = "lyng.stdlib", - getter = { (this.thisObj as ObjClass).classNameObj } + getter = object : ScopeCallable { + override suspend fun call(scp: Scope): Obj = (scp.thisObj as ObjClass).classNameObj + } ) addPropertyDoc( name = "name", doc = "Simple name of this class (without package).", type = type("lyng.String"), moduleName = "lyng.stdlib", - getter = { (this.thisObj as ObjClass).classNameObj } + getter = object : ScopeCallable { + override suspend fun call(scp: Scope): Obj = (scp.thisObj as ObjClass).classNameObj + } ) addPropertyDoc( @@ -55,16 +59,18 @@ val ObjClassType by lazy { doc = "Declared instance fields of this class and its ancestors (C3 order), without duplicates.", type = TypeGenericDoc(type("lyng.List"), listOf(type("lyng.String"))), moduleName = "lyng.stdlib", - getter = { - val cls = this.thisObj as ObjClass - val seen = hashSetOf() - val names = mutableListOf() - for (c in cls.mro) { - for ((n, rec) in c.members) { - if (rec.value !is Statement && seen.add(n)) names += ObjString(n) + getter = object : ScopeCallable { + override suspend fun call(scp: Scope): Obj { + val cls = scp.thisObj as ObjClass + val seen = hashSetOf() + val names = mutableListOf() + for (c in cls.mro) { + for ((n, rec) in c.members) { + if (rec.value !is Statement && seen.add(n)) names += ObjString(n) + } } + return ObjList(names.toMutableList()) } - ObjList(names.toMutableList()) } ) @@ -73,16 +79,18 @@ val ObjClassType by lazy { doc = "Declared instance methods of this class and its ancestors (C3 order), without duplicates.", type = TypeGenericDoc(type("lyng.List"), listOf(type("lyng.String"))), moduleName = "lyng.stdlib", - getter = { - val cls = this.thisObj as ObjClass - val seen = hashSetOf() - val names = mutableListOf() - for (c in cls.mro) { - for ((n, rec) in c.members) { - if (rec.value is Statement && seen.add(n)) names += ObjString(n) + getter = object : ScopeCallable { + override suspend fun call(scp: Scope): Obj { + val cls = scp.thisObj as ObjClass + val seen = hashSetOf() + val names = mutableListOf() + for (c in cls.mro) { + for ((n, rec) in c.members) { + if (rec.value is Statement && seen.add(n)) names += ObjString(n) + } } + return ObjList(names.toMutableList()) } - ObjList(names.toMutableList()) } ) @@ -91,13 +99,16 @@ val ObjClassType by lazy { doc = "Lookup a member by name in this class (including ancestors) and return it, or null if absent.", params = listOf(ParamDoc("name", type("lyng.String"))), returns = type("lyng.Any", nullable = true), - moduleName = "lyng.stdlib" - ) { - val cls = thisAs() - val name = requiredArg(0).value - val rec = cls.getInstanceMemberOrNull(name) - rec?.value ?: ObjNull - } + moduleName = "lyng.stdlib", + code = object : ScopeCallable { + override suspend fun call(scp: Scope): Obj { + val cls = scp.thisAs() + val name = scp.requiredArg(0).value + val rec = cls.getInstanceMemberOrNull(name) + return rec?.value ?: ObjNull + } + } + ) } } @@ -547,9 +558,9 @@ open class ObjClass( isClosed: Boolean = false, isOverride: Boolean = false, pos: Pos = Pos.builtIn, - code: (suspend Scope.() -> Obj)? = null + code: ScopeCallable? = null ) { - val stmt = code?.let { statement { it() } } ?: ObjNull + val stmt = code?.let { statement(pos, f = it) } ?: ObjNull createField( name, stmt, isMutable, visibility, writeVisibility, pos, declaringClass, isAbstract = isAbstract, isClosed = isClosed, isOverride = isOverride, @@ -561,8 +572,8 @@ open class ObjClass( fun addProperty( name: String, - getter: (suspend Scope.() -> Obj)? = null, - setter: (suspend Scope.(Obj) -> Unit)? = null, + getter: ScopeCallable? = null, + setter: ScopeCallable? = null, visibility: Visibility = Visibility.Public, writeVisibility: Visibility? = null, declaringClass: ObjClass? = this, @@ -572,8 +583,8 @@ open class ObjClass( pos: Pos = Pos.builtIn, prop: ObjProperty? = null ) { - val g = getter?.let { statement { it() } } - val s = setter?.let { statement { it(requiredArg(0)); ObjVoid } } + val g = getter?.let { statement(pos, f = it) } + val s = setter?.let { statement(pos, f = it) } val finalProp = prop ?: if (isAbstract) ObjNull else ObjProperty(name, g, s) createField( name, finalProp, false, visibility, writeVisibility, pos, declaringClass, @@ -583,8 +594,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, statement { code() }, isOpen, type = ObjRecord.Type.Fun) + fun addClassFn(name: String, isOpen: Boolean = false, code: ScopeCallable) { + createClassField(name, statement(f = code), isOpen, type = ObjRecord.Type.Fun) } @@ -694,25 +705,25 @@ open class ObjClass( override suspend fun invokeInstanceMethod( scope: Scope, name: String, args: Arguments, - onNotFoundResult: (suspend () -> Obj?)? + onNotFoundResult: OnNotFound? ): Obj { getInstanceMemberOrNull(name)?.let { rec -> val decl = rec.declaringClass ?: findDeclaringClassOf(name) ?: this if (rec.type == ObjRecord.Type.Delegated) { val del = rec.delegate ?: scope.raiseError("Internal error: delegated member $name has no delegate") val allArgs = (listOf(this, ObjString(name)) + args.list).toTypedArray() - return del.invokeInstanceMethod(scope, "invoke", Arguments(*allArgs), onNotFoundResult = { + return del.invokeInstanceMethod(scope, "invokeCallable", Arguments(*allArgs), onNotFoundResult = { // Fallback: property delegation val propVal = del.invokeInstanceMethod(scope, "getValue", Arguments(this, ObjString(name))) - propVal.invoke(scope, this, args, decl) + propVal.invokeCallable(scope, this, args, decl) }) } if (rec.type == ObjRecord.Type.Fun) { - return rec.value.invoke(scope, this, args, decl) + return rec.value.invokeCallable(scope, this, args, decl) } else { // Resolved field or property value val resolved = readField(scope, name) - return resolved.value.invoke(scope, this, args, decl) + return resolved.value.invokeCallable(scope, this, args, decl) } } return super.invokeInstanceMethod(scope, name, args, onNotFoundResult) diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjCompletableDeferred.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjCompletableDeferred.kt index 74e6db6..002b641 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjCompletableDeferred.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjCompletableDeferred.kt @@ -1,5 +1,5 @@ /* - * Copyright 2025 Sergey S. Chernov real.sergeych@gmail.com + * Copyright 2026 Sergey S. Chernov real.sergeych@gmail.com * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,6 +19,7 @@ package net.sergeych.lyng.obj import kotlinx.coroutines.CompletableDeferred import net.sergeych.lyng.Scope +import net.sergeych.lyng.ScopeCallable import net.sergeych.lyng.miniast.ParamDoc import net.sergeych.lyng.miniast.addFnDoc import net.sergeych.lyng.miniast.type @@ -38,11 +39,14 @@ class ObjCompletableDeferred(val completableDeferred: CompletableDeferred): doc = "Complete this deferred with the given value. Subsequent calls have no effect.", params = listOf(ParamDoc("value")), returns = type("lyng.Void"), - moduleName = "lyng.stdlib" - ) { - thisAs().completableDeferred.complete(args.firstAndOnly()) - ObjVoid - } + moduleName = "lyng.stdlib", + code = object : ScopeCallable { + override suspend fun call(scp: Scope): Obj { + scp.thisAs().completableDeferred.complete(scp.args.firstAndOnly()) + return ObjVoid + } + } + ) } } } \ No newline at end of file diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjDateTime.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjDateTime.kt index 002c6f6..41d7658 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjDateTime.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjDateTime.kt @@ -21,6 +21,7 @@ import kotlinx.datetime.* import kotlinx.serialization.json.JsonElement import kotlinx.serialization.json.JsonPrimitive import net.sergeych.lyng.Scope +import net.sergeych.lyng.ScopeCallable import net.sergeych.lyng.Statement import net.sergeych.lyng.miniast.addClassFnDoc import net.sergeych.lyng.miniast.addFnDoc @@ -54,7 +55,9 @@ class ObjDateTime(val instant: Instant, val timeZone: TimeZone) : Obj() { } if (rec.type == ObjRecord.Type.Fun || rec.value is Statement) { val s = rec.value as Statement - return ObjRecord(net.sergeych.lyng.statement { s.execute(this.createChildScope(newThisObj = this@ObjDateTime)) }, rec.isMutable) + return ObjRecord(net.sergeych.lyng.statement(f = object : ScopeCallable { + override suspend fun call(scp: Scope): Obj = s.execute(scp.createChildScope(newThisObj = this@ObjDateTime)) + }), rec.isMutable) } return resolveRecord(scope, rec, name, rec.declaringClass ?: cls) } @@ -172,122 +175,129 @@ class ObjDateTime(val instant: Instant, val timeZone: TimeZone) : Obj() { } }.apply { addPropertyDoc("year", "The year component.", type("lyng.Int"), moduleName = "lyng.time", - getter = { thisAs().localDateTime.year.toObj() }) + getter = object : ScopeCallable { override suspend fun call(scp: Scope): Obj = scp.thisAs().localDateTime.year.toObj() }) addPropertyDoc("month", "The month component (1..12).", type("lyng.Int"), moduleName = "lyng.time", - getter = { thisAs().localDateTime.monthNumber.toObj() }) + getter = object : ScopeCallable { override suspend fun call(scp: Scope): Obj = scp.thisAs().localDateTime.monthNumber.toObj() }) addPropertyDoc("dayOfMonth", "The day of month component.", type("lyng.Int"), moduleName = "lyng.time", - getter = { thisAs().localDateTime.dayOfMonth.toObj() }) + getter = object : ScopeCallable { override suspend fun call(scp: Scope): Obj = scp.thisAs().localDateTime.dayOfMonth.toObj() }) addPropertyDoc("day", "Alias to dayOfMonth.", type("lyng.Int"), moduleName = "lyng.time", - getter = { thisAs().localDateTime.dayOfMonth.toObj() }) + getter = object : ScopeCallable { override suspend fun call(scp: Scope): Obj = scp.thisAs().localDateTime.dayOfMonth.toObj() }) addPropertyDoc("hour", "The hour component (0..23).", type("lyng.Int"), moduleName = "lyng.time", - getter = { thisAs().localDateTime.hour.toObj() }) + getter = object : ScopeCallable { override suspend fun call(scp: Scope): Obj = scp.thisAs().localDateTime.hour.toObj() }) addPropertyDoc("minute", "The minute component (0..59).", type("lyng.Int"), moduleName = "lyng.time", - getter = { thisAs().localDateTime.minute.toObj() }) + getter = object : ScopeCallable { override suspend fun call(scp: Scope): Obj = scp.thisAs().localDateTime.minute.toObj() }) addPropertyDoc("second", "The second component (0..59).", type("lyng.Int"), moduleName = "lyng.time", - getter = { thisAs().localDateTime.second.toObj() }) + getter = object : ScopeCallable { override suspend fun call(scp: Scope): Obj = scp.thisAs().localDateTime.second.toObj() }) addPropertyDoc("dayOfWeek", "The day of week (1=Monday, 7=Sunday).", type("lyng.Int"), moduleName = "lyng.time", - getter = { thisAs().localDateTime.dayOfWeek.isoDayNumber.toObj() }) + getter = object : ScopeCallable { override suspend fun call(scp: Scope): Obj = scp.thisAs().localDateTime.dayOfWeek.isoDayNumber.toObj() }) addPropertyDoc("timeZone", "The time zone ID (e.g. 'Z', '+02:00', 'Europe/Prague').", type("lyng.String"), moduleName = "lyng.time", - getter = { thisAs().timeZone.id.toObj() }) + getter = object : ScopeCallable { override suspend fun call(scp: Scope): Obj = scp.thisAs().timeZone.id.toObj() }) - addFnDoc("toInstant", "Convert this localized date time back to an absolute Instant.", returns = type("lyng.Instant"), moduleName = "lyng.time") { - ObjInstant(thisAs().instant) - } - addFnDoc("toEpochSeconds", "Return the number of full seconds since the Unix epoch (UTC).", returns = type("lyng.Int"), moduleName = "lyng.time") { - thisAs().instant.epochSeconds.toObj() - } - addFnDoc("toRFC3339", "Return the RFC3339 string representation of this date time, including its timezone offset.", returns = type("lyng.String"), moduleName = "lyng.time") { - thisAs().toRFC3339().toObj() - } - addFnDoc("toSortableString", "Alias to toRFC3339.", returns = type("lyng.String"), moduleName = "lyng.time") { - thisAs().toRFC3339().toObj() - } - - addFnDoc("toEpochMilliseconds", "Return the number of milliseconds since the Unix epoch (UTC).", returns = type("lyng.Int"), moduleName = "lyng.time") { - thisAs().instant.toEpochMilliseconds().toObj() - } + addFnDoc("toInstant", "Convert this localized date time back to an absolute Instant.", returns = type("lyng.Instant"), moduleName = "lyng.time", + code = object : ScopeCallable { override suspend fun call(scp: Scope): Obj = ObjInstant(scp.thisAs().instant) }) + addFnDoc("toEpochSeconds", "Return the number of full seconds since the Unix epoch (UTC).", returns = type("lyng.Int"), moduleName = "lyng.time", + code = object : ScopeCallable { override suspend fun call(scp: Scope): Obj = scp.thisAs().instant.epochSeconds.toObj() }) + addFnDoc("toRFC3339", "Return the RFC3339 string representation of this date time, including its timezone offset.", returns = type("lyng.String"), moduleName = "lyng.time", + code = object : ScopeCallable { override suspend fun call(scp: Scope): Obj = scp.thisAs().toRFC3339().toObj() }) + addFnDoc("toSortableString", "Alias to toRFC3339.", returns = type("lyng.String"), moduleName = "lyng.time", + code = object : ScopeCallable { override suspend fun call(scp: Scope): Obj = scp.thisAs().toRFC3339().toObj() }) + + addFnDoc("toEpochMilliseconds", "Return the number of milliseconds since the Unix epoch (UTC).", returns = type("lyng.Int"), moduleName = "lyng.time", + code = object : ScopeCallable { override suspend fun call(scp: Scope): Obj = scp.thisAs().instant.toEpochMilliseconds().toObj() }) addFnDoc("toTimeZone", "Return a new DateTime representing the same instant but in a different time zone. " + "Accepts a timezone ID string (e.g., 'UTC', '+02:00') or an integer offset in seconds.", params = listOf(net.sergeych.lyng.miniast.ParamDoc("tz", type = type("lyng.Any"))), - returns = type("lyng.DateTime"), moduleName = "lyng.time") { - val tz = when (val a = args.list.getOrNull(0)) { - is ObjString -> TimeZone.of(a.value) - is ObjInt -> UtcOffset(seconds = a.value.toInt()).asTimeZone() - else -> raiseIllegalArgument("invalid timezone: $a") - } - ObjDateTime(thisAs().instant, tz) - } - addFnDoc("toUTC", "Shortcut to convert this date time to the UTC time zone.", returns = type("lyng.DateTime"), moduleName = "lyng.time") { - ObjDateTime(thisAs().instant, TimeZone.UTC) - } + returns = type("lyng.DateTime"), moduleName = "lyng.time", + code = object : ScopeCallable { + override suspend fun call(scp: Scope): Obj { + val tz = when (val a = scp.args.list.getOrNull(0)) { + is ObjString -> TimeZone.of(a.value) + is ObjInt -> UtcOffset(seconds = a.value.toInt()).asTimeZone() + else -> scp.raiseIllegalArgument("invalid timezone: $a") + } + return ObjDateTime(scp.thisAs().instant, tz) + } + }) + addFnDoc("toUTC", "Shortcut to convert this date time to the UTC time zone.", returns = type("lyng.DateTime"), moduleName = "lyng.time", + code = object : ScopeCallable { override suspend fun call(scp: Scope): Obj = ObjDateTime(scp.thisAs().instant, TimeZone.UTC) }) addFnDoc("addMonths", "Return a new DateTime with the specified number of months added (or subtracted if negative). " + "Normalizes the day of month if necessary (e.g., Jan 31 + 1 month = Feb 28/29).", params = listOf(net.sergeych.lyng.miniast.ParamDoc("months", type = type("lyng.Int"))), - returns = type("lyng.DateTime"), moduleName = "lyng.time") { - val n = args.list.getOrNull(0)?.toInt() ?: 0 - val res = thisAs().instant.plus(n, DateTimeUnit.MONTH, thisAs().timeZone) - ObjDateTime(res, thisAs().timeZone) - } + returns = type("lyng.DateTime"), moduleName = "lyng.time", + code = object : ScopeCallable { + override suspend fun call(scp: Scope): Obj { + val n = scp.args.list.getOrNull(0)?.toInt() ?: 0 + val res = scp.thisAs().instant.plus(n, DateTimeUnit.MONTH, scp.thisAs().timeZone) + return ObjDateTime(res, scp.thisAs().timeZone) + } + }) addFnDoc("addYears", "Return a new DateTime with the specified number of years added (or subtracted if negative).", params = listOf(net.sergeych.lyng.miniast.ParamDoc("years", type = type("lyng.Int"))), - returns = type("lyng.DateTime"), moduleName = "lyng.time") { - val n = args.list.getOrNull(0)?.toInt() ?: 0 - val res = thisAs().instant.plus(n, DateTimeUnit.YEAR, thisAs().timeZone) - ObjDateTime(res, thisAs().timeZone) - } + returns = type("lyng.DateTime"), moduleName = "lyng.time", + code = object : ScopeCallable { + override suspend fun call(scp: Scope): Obj { + val n = scp.args.list.getOrNull(0)?.toInt() ?: 0 + val res = scp.thisAs().instant.plus(n, DateTimeUnit.YEAR, scp.thisAs().timeZone) + return ObjDateTime(res, scp.thisAs().timeZone) + } + }) - addClassFn("now") { - val tz = when (val a = args.list.getOrNull(0)) { - null -> TimeZone.currentSystemDefault() - is ObjString -> TimeZone.of(a.value) - is ObjInt -> UtcOffset(seconds = a.value.toInt()).asTimeZone() - else -> raiseIllegalArgument("invalid timezone: $a") + addClassFn("now", code = object : ScopeCallable { + override suspend fun call(scp: Scope): Obj { + val tz = when (val a = scp.args.list.getOrNull(0)) { + null -> TimeZone.currentSystemDefault() + is ObjString -> TimeZone.of(a.value) + is ObjInt -> UtcOffset(seconds = a.value.toInt()).asTimeZone() + else -> scp.raiseIllegalArgument("invalid timezone: $a") + } + return ObjDateTime(kotlin.time.Clock.System.now(), tz) } - ObjDateTime(kotlin.time.Clock.System.now(), tz) - } + }) addClassFnDoc("parseRFC3339", "Parse an RFC3339 string into a DateTime object. " + "Note: if the string does not specify a timezone, UTC is assumed.", params = listOf(net.sergeych.lyng.miniast.ParamDoc("string", type = type("lyng.String"))), returns = type("lyng.DateTime"), - moduleName = "lyng.time") { - val s = (args.firstAndOnly() as ObjString).value - // kotlinx-datetime's Instant.parse handles RFC3339 - // But we want to preserve the offset if present for DateTime. - // However, Instant.parse("...") always gives an Instant. - // If we want the specific offset from the string, we might need a more complex parse. - // For now, let's stick to parsing it as Instant and converting to UTC or specified TZ. - // Actually, if the string has an offset, Instant.parse handles it but returns UTC instant. - - // Let's try to detect if there is an offset in the string. - // If not, use UTC. - val instant = Instant.parse(s) - - // RFC3339 can have Z or +/-HH:mm or +/-HHmm or +/-HH - val tz = try { - if (s.endsWith("Z", ignoreCase = true)) { - TimeZone.of("Z") - } else { - // Look for the last + or - which is likely the start of the offset - val lastPlus = s.lastIndexOf('+') - val lastMinus = s.lastIndexOf('-') - val offsetStart = if (lastPlus > lastMinus) lastPlus else lastMinus - if (offsetStart > s.lastIndexOf('T')) { - // Likely an offset - val offsetStr = s.substring(offsetStart) - TimeZone.of(offsetStr) - } else { + moduleName = "lyng.time", + code = object : ScopeCallable { + override suspend fun call(scp: Scope): Obj { + val s = (scp.args.firstAndOnly() as ObjString).value + // kotlinx-datetime's Instant.parse handles RFC3339 + // But we want to preserve the offset if present for DateTime. + // However, Instant.parse("...") always gives an Instant. + // If we want the specific offset from the string, we might need a more complex parse. + // For now, let's stick to parsing it as Instant and converting to UTC or specified TZ. + // Actually, if the string has an offset, Instant.parse handles it but returns UTC instant. + + // Let's try to detect if there is an offset in the string. + // If not, use UTC. + val instant = Instant.parse(s) + + // RFC3339 can have Z or +/-HH:mm or +/-HHmm or +/-HH + val tz = try { + if (s.endsWith("Z", ignoreCase = true)) { + TimeZone.of("Z") + } else { + // Look for the last + or - which is likely the start of the offset + val lastPlus = s.lastIndexOf('+') + val lastMinus = s.lastIndexOf('-') + val offsetStart = if (lastPlus > lastMinus) lastPlus else lastMinus + if (offsetStart > s.lastIndexOf('T')) { + // Likely an offset + val offsetStr = s.substring(offsetStart) + TimeZone.of(offsetStr) + } else { + TimeZone.UTC + } + } + } catch (e: Exception) { TimeZone.UTC } + return ObjDateTime(instant, tz) } - } catch (e: Exception) { - TimeZone.UTC - } - - ObjDateTime(instant, tz) - } + }) } } } diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjDeferred.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjDeferred.kt index 49ff90f..f0ae40f 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjDeferred.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjDeferred.kt @@ -19,6 +19,7 @@ package net.sergeych.lyng.obj import kotlinx.coroutines.Deferred import net.sergeych.lyng.Scope +import net.sergeych.lyng.ScopeCallable import net.sergeych.lyng.miniast.addFnDoc import net.sergeych.lyng.miniast.addPropertyDoc import net.sergeych.lyng.miniast.type @@ -37,23 +38,30 @@ open class ObjDeferred(val deferred: Deferred): Obj() { name = "await", doc = "Suspend until completion and return the result value (or throw if failed).", returns = type("lyng.Any"), - moduleName = "lyng.stdlib" - ) { thisAs().deferred.await() } + moduleName = "lyng.stdlib", + code = object : ScopeCallable { + override suspend fun call(scp: Scope): Obj = scp.thisAs().deferred.await() + } + ) addPropertyDoc( name = "isCompleted", doc = "Whether this deferred has completed (successfully or with an error).", type = type("lyng.Bool"), moduleName = "lyng.stdlib", - getter = { thisAs().deferred.isCompleted.toObj() } + getter = object : ScopeCallable { + override suspend fun call(scp: Scope): Obj = scp.thisAs().deferred.isCompleted.toObj() + } ) addPropertyDoc( name = "isActive", doc = "Whether this deferred is currently active (not completed and not cancelled).", type = type("lyng.Bool"), moduleName = "lyng.stdlib", - getter = { - val d = thisAs().deferred - (d.isActive || (!d.isCompleted && !d.isCancelled)).toObj() + getter = object : ScopeCallable { + override suspend fun call(scp: Scope): Obj { + val d = scp.thisAs().deferred + return (d.isActive || (!d.isCompleted && !d.isCancelled)).toObj() + } } ) addPropertyDoc( @@ -61,7 +69,9 @@ open class ObjDeferred(val deferred: Deferred): Obj() { doc = "Whether this deferred was cancelled.", type = type("lyng.Bool"), moduleName = "lyng.stdlib", - getter = { thisAs().deferred.isCancelled.toObj() } + getter = object : ScopeCallable { + override suspend fun call(scp: Scope): Obj = scp.thisAs().deferred.isCancelled.toObj() + } ) } } diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjDuration.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjDuration.kt index 6f080fd..6bd4b48 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjDuration.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjDuration.kt @@ -18,6 +18,7 @@ package net.sergeych.lyng.obj import net.sergeych.lyng.Scope +import net.sergeych.lyng.ScopeCallable import net.sergeych.lyng.miniast.addPropertyDoc import net.sergeych.lyng.miniast.type import kotlin.time.Duration @@ -79,42 +80,54 @@ class ObjDuration(val duration: Duration) : Obj() { doc = "Return this duration as a real number of days.", type = type("lyng.Real"), moduleName = "lyng.time", - getter = { thisAs().duration.toDouble(DurationUnit.DAYS).toObj() } + getter = object : ScopeCallable { + override suspend fun call(scp: Scope): Obj = scp.thisAs().duration.toDouble(DurationUnit.DAYS).toObj() + } ) addPropertyDoc( name = "hours", doc = "Return this duration as a real number of hours.", type = type("lyng.Real"), moduleName = "lyng.time", - getter = { thisAs().duration.toDouble(DurationUnit.HOURS).toObj() } + getter = object : ScopeCallable { + override suspend fun call(scp: Scope): Obj = scp.thisAs().duration.toDouble(DurationUnit.HOURS).toObj() + } ) addPropertyDoc( name = "minutes", doc = "Return this duration as a real number of minutes.", type = type("lyng.Real"), moduleName = "lyng.time", - getter = { thisAs().duration.toDouble(DurationUnit.MINUTES).toObj() } + getter = object : ScopeCallable { + override suspend fun call(scp: Scope): Obj = scp.thisAs().duration.toDouble(DurationUnit.MINUTES).toObj() + } ) addPropertyDoc( name = "seconds", doc = "Return this duration as a real number of seconds.", type = type("lyng.Real"), moduleName = "lyng.time", - getter = { thisAs().duration.toDouble(DurationUnit.SECONDS).toObj() } + getter = object : ScopeCallable { + override suspend fun call(scp: Scope): Obj = scp.thisAs().duration.toDouble(DurationUnit.SECONDS).toObj() + } ) addPropertyDoc( name = "milliseconds", doc = "Return this duration as a real number of milliseconds.", type = type("lyng.Real"), moduleName = "lyng.time", - getter = { thisAs().duration.toDouble(DurationUnit.MILLISECONDS).toObj() } + getter = object : ScopeCallable { + override suspend fun call(scp: Scope): Obj = scp.thisAs().duration.toDouble(DurationUnit.MILLISECONDS).toObj() + } ) addPropertyDoc( name = "microseconds", doc = "Return this duration as a real number of microseconds.", type = type("lyng.Real"), moduleName = "lyng.time", - getter = { thisAs().duration.toDouble(DurationUnit.MICROSECONDS).toObj() } + getter = object : ScopeCallable { + override suspend fun call(scp: Scope): Obj = scp.thisAs().duration.toDouble(DurationUnit.MICROSECONDS).toObj() + } ) // extensions @@ -123,7 +136,9 @@ class ObjDuration(val duration: Duration) : Obj() { doc = "Construct a `Duration` equal to this integer number of seconds.", type = type("lyng.Duration"), moduleName = "lyng.time", - getter = { ObjDuration(thisAs().value.seconds) } + getter = object : ScopeCallable { + override suspend fun call(scp: Scope): Obj = ObjDuration(scp.thisAs().value.seconds) + } ) ObjInt.type.addPropertyDoc( @@ -131,14 +146,18 @@ class ObjDuration(val duration: Duration) : Obj() { doc = "Construct a `Duration` equal to this integer number of seconds.", type = type("lyng.Duration"), moduleName = "lyng.time", - getter = { ObjDuration(thisAs().value.seconds) } + getter = object : ScopeCallable { + override suspend fun call(scp: Scope): Obj = ObjDuration(scp.thisAs().value.seconds) + } ) ObjInt.type.addPropertyDoc( name = "milliseconds", doc = "Construct a `Duration` equal to this integer number of milliseconds.", type = type("lyng.Duration"), moduleName = "lyng.time", - getter = { ObjDuration(thisAs().value.milliseconds) } + getter = object : ScopeCallable { + override suspend fun call(scp: Scope): Obj = ObjDuration(scp.thisAs().value.milliseconds) + } ) ObjInt.type.addPropertyDoc( @@ -146,14 +165,18 @@ class ObjDuration(val duration: Duration) : Obj() { doc = "Construct a `Duration` equal to this integer number of milliseconds.", type = type("lyng.Duration"), moduleName = "lyng.time", - getter = { ObjDuration(thisAs().value.milliseconds) } + getter = object : ScopeCallable { + override suspend fun call(scp: Scope): Obj = ObjDuration(scp.thisAs().value.milliseconds) + } ) ObjReal.type.addPropertyDoc( name = "seconds", doc = "Construct a `Duration` equal to this real number of seconds.", type = type("lyng.Duration"), moduleName = "lyng.time", - getter = { ObjDuration(thisAs().value.seconds) } + getter = object : ScopeCallable { + override suspend fun call(scp: Scope): Obj = ObjDuration(scp.thisAs().value.seconds) + } ) ObjReal.type.addPropertyDoc( @@ -161,7 +184,9 @@ class ObjDuration(val duration: Duration) : Obj() { doc = "Construct a `Duration` equal to this real number of seconds.", type = type("lyng.Duration"), moduleName = "lyng.time", - getter = { ObjDuration(thisAs().value.seconds) } + getter = object : ScopeCallable { + override suspend fun call(scp: Scope): Obj = ObjDuration(scp.thisAs().value.seconds) + } ) ObjReal.type.addPropertyDoc( @@ -169,14 +194,18 @@ class ObjDuration(val duration: Duration) : Obj() { doc = "Construct a `Duration` equal to this real number of milliseconds.", type = type("lyng.Duration"), moduleName = "lyng.time", - getter = { ObjDuration(thisAs().value.milliseconds) } + getter = object : ScopeCallable { + override suspend fun call(scp: Scope): Obj = ObjDuration(scp.thisAs().value.milliseconds) + } ) ObjReal.type.addPropertyDoc( name = "millisecond", doc = "Construct a `Duration` equal to this real number of milliseconds.", type = type("lyng.Duration"), moduleName = "lyng.time", - getter = { ObjDuration(thisAs().value.milliseconds) } + getter = object : ScopeCallable { + override suspend fun call(scp: Scope): Obj = ObjDuration(scp.thisAs().value.milliseconds) + } ) ObjInt.type.addPropertyDoc( @@ -184,84 +213,108 @@ class ObjDuration(val duration: Duration) : Obj() { doc = "Construct a `Duration` equal to this integer number of minutes.", type = type("lyng.Duration"), moduleName = "lyng.time", - getter = { ObjDuration(thisAs().value.minutes) } + getter = object : ScopeCallable { + override suspend fun call(scp: Scope): Obj = ObjDuration(scp.thisAs().value.minutes) + } ) ObjReal.type.addPropertyDoc( name = "minutes", doc = "Construct a `Duration` equal to this real number of minutes.", type = type("lyng.Duration"), moduleName = "lyng.time", - getter = { ObjDuration(thisAs().value.minutes) } + getter = object : ScopeCallable { + override suspend fun call(scp: Scope): Obj = ObjDuration(scp.thisAs().value.minutes) + } ) ObjInt.type.addPropertyDoc( name = "minute", doc = "Construct a `Duration` equal to this integer number of minutes.", type = type("lyng.Duration"), moduleName = "lyng.time", - getter = { ObjDuration(thisAs().value.minutes) } + getter = object : ScopeCallable { + override suspend fun call(scp: Scope): Obj = ObjDuration(scp.thisAs().value.minutes) + } ) ObjReal.type.addPropertyDoc( name = "minute", doc = "Construct a `Duration` equal to this real number of minutes.", type = type("lyng.Duration"), moduleName = "lyng.time", - getter = { ObjDuration(thisAs().value.minutes) } + getter = object : ScopeCallable { + override suspend fun call(scp: Scope): Obj = ObjDuration(scp.thisAs().value.minutes) + } ) ObjInt.type.addPropertyDoc( name = "hours", doc = "Construct a `Duration` equal to this integer number of hours.", type = type("lyng.Duration"), moduleName = "lyng.time", - getter = { ObjDuration(thisAs().value.hours) } + getter = object : ScopeCallable { + override suspend fun call(scp: Scope): Obj = ObjDuration(scp.thisAs().value.hours) + } ) ObjReal.type.addPropertyDoc( name = "hours", doc = "Construct a `Duration` equal to this real number of hours.", type = type("lyng.Duration"), moduleName = "lyng.time", - getter = { ObjDuration(thisAs().value.hours) } + getter = object : ScopeCallable { + override suspend fun call(scp: Scope): Obj = ObjDuration(scp.thisAs().value.hours) + } ) ObjInt.type.addPropertyDoc( name = "hour", doc = "Construct a `Duration` equal to this integer number of hours.", type = type("lyng.Duration"), moduleName = "lyng.time", - getter = { ObjDuration(thisAs().value.hours) } + getter = object : ScopeCallable { + override suspend fun call(scp: Scope): Obj = ObjDuration(scp.thisAs().value.hours) + } ) ObjReal.type.addPropertyDoc( name = "hour", doc = "Construct a `Duration` equal to this real number of hours.", type = type("lyng.Duration"), moduleName = "lyng.time", - getter = { ObjDuration(thisAs().value.hours) } + getter = object : ScopeCallable { + override suspend fun call(scp: Scope): Obj = ObjDuration(scp.thisAs().value.hours) + } ) ObjInt.type.addPropertyDoc( name = "days", doc = "Construct a `Duration` equal to this integer number of days.", type = type("lyng.Duration"), moduleName = "lyng.time", - getter = { ObjDuration(thisAs().value.days) } + getter = object : ScopeCallable { + override suspend fun call(scp: Scope): Obj = ObjDuration(scp.thisAs().value.days) + } ) ObjReal.type.addPropertyDoc( name = "days", doc = "Construct a `Duration` equal to this real number of days.", type = type("lyng.Duration"), moduleName = "lyng.time", - getter = { ObjDuration(thisAs().value.days) } + getter = object : ScopeCallable { + override suspend fun call(scp: Scope): Obj = ObjDuration(scp.thisAs().value.days) + } ) ObjInt.type.addPropertyDoc( name = "day", doc = "Construct a `Duration` equal to this integer number of days.", type = type("lyng.Duration"), moduleName = "lyng.time", - getter = { ObjDuration(thisAs().value.days) } + getter = object : ScopeCallable { + override suspend fun call(scp: Scope): Obj = ObjDuration(scp.thisAs().value.days) + } ) ObjReal.type.addPropertyDoc( name = "day", doc = "Construct a `Duration` equal to this real number of days.", type = type("lyng.Duration"), moduleName = "lyng.time", - getter = { ObjDuration(thisAs().value.days) } + getter = object : ScopeCallable { + override suspend fun call(scp: Scope): Obj = ObjDuration(scp.thisAs().value.days) + } ) diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjDynamic.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjDynamic.kt index d00c1b0..f435a97 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjDynamic.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjDynamic.kt @@ -1,5 +1,5 @@ /* - * Copyright 2025 Sergey S. Chernov real.sergeych@gmail.com + * Copyright 2026 Sergey S. Chernov real.sergeych@gmail.com * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,31 +17,32 @@ package net.sergeych.lyng.obj -import net.sergeych.lyng.Arguments -import net.sergeych.lyng.ClosureScope -import net.sergeych.lyng.Scope -import net.sergeych.lyng.Statement +import net.sergeych.lyng.* class ObjDynamicContext(val delegate: ObjDynamic) : Obj() { override val objClass: ObjClass get() = type companion object { val type = ObjClass("DelegateContext").apply { - addFn("get") { - val d = thisAs().delegate - if (d.readCallback != null) - raiseIllegalState("get already defined") - d.readCallback = requireOnlyArg() - ObjVoid - } + addFn("get", code = object : ScopeCallable { + override suspend fun call(scp: Scope): Obj { + val d = scp.thisAs().delegate + if (d.readCallback != null) + scp.raiseIllegalState("get already defined") + d.readCallback = scp.requireOnlyArg() + return ObjVoid + } + }) - addFn("set") { - val d = thisAs().delegate - if (d.writeCallback != null) - raiseIllegalState("set already defined") - d.writeCallback = requireOnlyArg() - ObjVoid - } + addFn("set", code = object : ScopeCallable { + override suspend fun call(scp: Scope): Obj { + val d = scp.thisAs().delegate + if (d.writeCallback != null) + scp.raiseIllegalState("set already defined") + d.writeCallback = scp.requireOnlyArg() + return ObjVoid + } + }) } @@ -81,11 +82,11 @@ open class ObjDynamic(var readCallback: Statement? = null, var writeCallback: St scope: Scope, name: String, args: Arguments, - onNotFoundResult: (suspend () -> Obj?)? + onNotFoundResult: OnNotFound? ): Obj { val execBase = builderScope?.let { ClosureScope(scope, it) } ?: scope val over = readCallback?.execute(execBase.createChildScope(Arguments(ObjString(name)))) - return over?.invoke(scope, scope.thisObj, args) + return over?.invokeCallable(scope, scope.thisObj, args) ?: super.invokeInstanceMethod(scope, name, args, onNotFoundResult) } diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjEnum.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjEnum.kt index d7e9864..60c10fd 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjEnum.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjEnum.kt @@ -35,6 +35,7 @@ package net.sergeych.lyng.obj/* import kotlinx.serialization.json.JsonElement import kotlinx.serialization.json.JsonPrimitive import net.sergeych.lyng.Scope +import net.sergeych.lyng.ScopeCallable import net.sergeych.lyng.miniast.addPropertyDoc import net.sergeych.lyng.miniast.type import net.sergeych.lynon.LynonDecoder @@ -74,12 +75,18 @@ class ObjEnumClass(val name: String) : ObjClass(name, EnumBase) { init { addClassConst("entries", objEntries ) - addClassFn("valueOf") { - val name = requireOnlyArg() - byName[name] ?: raiseSymbolNotFound("does not exists: enum ${className}.$name") - } - addPropertyDoc("name", doc = "Entry name as string", type = type("lyng.String"), getter = { thisAs().name }) - addPropertyDoc("ordinal", doc = "Entry ordinal position", type = type("lyng.Int"), getter = { thisAs().ordinal }) + addClassFn("valueOf", code = object : ScopeCallable { + override suspend fun call(scp: Scope): Obj { + val name = scp.requireOnlyArg() + return byName[name] ?: scp.raiseSymbolNotFound("does not exists: enum ${className}.$name") + } + }) + addPropertyDoc("name", doc = "Entry name as string", type = type("lyng.String"), getter = object : ScopeCallable { + override suspend fun call(scp: Scope): Obj = scp.thisAs().name + }) + addPropertyDoc("ordinal", doc = "Entry ordinal position", type = type("lyng.Int"), getter = object : ScopeCallable { + override suspend fun call(scp: Scope): Obj = scp.thisAs().ordinal + }) } diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjException.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjException.kt index 9843b42..b112cf9 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjException.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjException.kt @@ -125,7 +125,9 @@ open class ObjException( class ExceptionClass(val name: String, vararg parents: ObjClass) : ObjClass(name, *parents) { init { constructorMeta = ArgsDeclaration( - listOf(ArgsDeclaration.Item("message", defaultValue = statement { ObjString(name) })), + listOf(ArgsDeclaration.Item("message", defaultValue = statement(f = object : ScopeCallable { + override suspend fun call(scope: Scope): Obj = ObjString(name) + }))), Token.Type.RPAREN ) } @@ -163,27 +165,33 @@ open class ObjException( } val Root = ExceptionClass("Exception").apply { - instanceInitializers.add(statement { - if (thisObj is ObjInstance) { - val msg = get("message")?.value ?: ObjString("Exception") - (thisObj as ObjInstance).instanceScope.addItem("Exception::message", false, msg) + instanceInitializers.add(statement(f = object : ScopeCallable { + override suspend fun call(scp: Scope): Obj { + if (scp.thisObj is ObjInstance) { + val msg = scp.get("message")?.value ?: ObjString("Exception") + (scp.thisObj as ObjInstance).instanceScope.addItem("Exception::message", false, msg) - val stack = captureStackTrace(this) - (thisObj as ObjInstance).instanceScope.addItem("Exception::stackTrace", false, stack) + val stack = captureStackTrace(scp) + (scp.thisObj as ObjInstance).instanceScope.addItem("Exception::stackTrace", false, stack) + } + return ObjVoid } - ObjVoid + })) + instanceConstructor = statement(f = object : ScopeCallable { + override suspend fun call(scp: Scope): Obj = ObjVoid }) - instanceConstructor = statement { ObjVoid } addPropertyDoc( name = "message", doc = "Human‑readable error message.", type = type("lyng.String"), moduleName = "lyng.stdlib", - getter = { - when (val t = this.thisObj) { - is ObjException -> t.message - is ObjInstance -> t.instanceScope.get("Exception::message")?.value ?: ObjNull - else -> ObjNull + getter = object : ScopeCallable { + override suspend fun call(scp: Scope): Obj { + return when (val t = scp.thisObj) { + is ObjException -> t.message + is ObjInstance -> t.instanceScope.get("Exception::message")?.value ?: ObjNull + else -> ObjNull + } } } ) @@ -192,10 +200,12 @@ open class ObjException( doc = "Extra data associated with the exception.", type = type("lyng.Any", nullable = true), moduleName = "lyng.stdlib", - getter = { - when (val t = this.thisObj) { - is ObjException -> t.extraData - else -> ObjNull + getter = object : ScopeCallable { + override suspend fun call(scp: Scope): Obj { + return when (val t = scp.thisObj) { + is ObjException -> t.extraData + else -> ObjNull + } } } ) @@ -204,11 +214,13 @@ open class ObjException( doc = "Stack trace captured at throw site as a list of `StackTraceEntry`.", type = TypeGenericDoc(type("lyng.List"), listOf(type("lyng.StackTraceEntry"))), moduleName = "lyng.stdlib", - getter = { - when (val t = this.thisObj) { - is ObjException -> t.getStackTrace() - is ObjInstance -> t.instanceScope.get("Exception::stackTrace")?.value as? ObjList ?: ObjList() - else -> ObjList() + getter = object : ScopeCallable { + override suspend fun call(scp: Scope): Obj { + return when (val t = scp.thisObj) { + is ObjException -> t.getStackTrace() + is ObjInstance -> t.instanceScope.get("Exception::stackTrace")?.value as? ObjList ?: ObjList() + else -> ObjList() + } } } ) @@ -216,23 +228,26 @@ open class ObjException( name = "toString", doc = "Human‑readable string representation of the error.", returns = type("lyng.String"), - moduleName = "lyng.stdlib" - ) { - val msg = when (val t = thisObj) { - is ObjException -> t.message.value - is ObjInstance -> (t.instanceScope.get("Exception::message")?.value as? ObjString)?.value - ?: t.objClass.className + moduleName = "lyng.stdlib", + code = object : ScopeCallable { + override suspend fun call(scp: Scope): Obj { + val msg = when (val t = scp.thisObj) { + is ObjException -> t.message.value + is ObjInstance -> (t.instanceScope.get("Exception::message")?.value as? ObjString)?.value + ?: t.objClass.className - else -> t.objClass.className + else -> t.objClass.className + } + val stack = when (val t = scp.thisObj) { + is ObjException -> t.getStackTrace() + is ObjInstance -> t.instanceScope.get("Exception::stackTrace")?.value as? ObjList ?: ObjList() + else -> ObjList() + } + val at = stack.list.firstOrNull()?.toString(scp) ?: ObjString("(unknown)") + return ObjString("${scp.thisObj.objClass.className}: $msg at $at") + } } - val stack = when (val t = thisObj) { - is ObjException -> t.getStackTrace() - is ObjInstance -> t.instanceScope.get("Exception::stackTrace")?.value as? ObjList ?: ObjList() - else -> ObjList() - } - val at = stack.list.firstOrNull()?.toString(this) ?: ObjString("(unknown)") - ObjString("${thisObj.objClass.className}: $msg at $at") - } + ) } private val op = ProtectedOp() diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjFlow.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjFlow.kt index 1692e12..826c5dc 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjFlow.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjFlow.kt @@ -1,5 +1,5 @@ /* - * Copyright 2025 Sergey S. Chernov real.sergeych@gmail.com + * Copyright 2026 Sergey S. Chernov real.sergeych@gmail.com * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -45,28 +45,31 @@ class ObjFlowBuilder(val output: SendChannel) : Obj() { doc = "Send a value to the flow consumer. Suspends if back‑pressured; no‑ops after consumer stops.", params = listOf(ParamDoc("value", type("lyng.Any"))), returns = type("lyng.Void"), - moduleName = "lyng.stdlib" - ) { - val data = requireOnlyArg() - try { - val channel = thisAs().output - if (!channel.isClosedForSend) - channel.send(data) - else - // Flow consumer is no longer collecting; signal producer to stop - throw ScriptFlowIsNoMoreCollected() - } catch (x: Exception) { - // Any failure to send (including closed channel) should gracefully stop the producer. - if (x is CancellationException) { - // Cancellation is a normal control-flow event - throw ScriptFlowIsNoMoreCollected() - } else { - // Treat other send failures as normal flow termination as well - throw ScriptFlowIsNoMoreCollected() + moduleName = "lyng.stdlib", + code = object : ScopeCallable { + override suspend fun call(scp: Scope): Obj { + val data = scp.requireOnlyArg() + try { + val channel = scp.thisAs().output + if (!channel.isClosedForSend) + channel.send(data) + else + // Flow consumer is no longer collecting; signal producer to stop + throw ScriptFlowIsNoMoreCollected() + } catch (x: Exception) { + // Any failure to send (including closed channel) should gracefully stop the producer. + if (x is CancellationException) { + // Cancellation is a normal control-flow event + throw ScriptFlowIsNoMoreCollected() + } else { + // Treat other send failures as normal flow termination as well + throw ScriptFlowIsNoMoreCollected() + } + } + return ObjVoid } } - ObjVoid - } + ) } } } @@ -103,15 +106,21 @@ class ObjFlow(val producer: Statement, val scope: Scope) : Obj() { name = "iterator", doc = "Create a pull‑based iterator over this flow. Each step resumes the producer as needed.", returns = TypeGenericDoc(type("lyng.Iterator"), listOf(type("lyng.Any"))), - moduleName = "lyng.stdlib" - ) { - val objFlow = thisAs() - ObjFlowIterator(statement { - objFlow.producer.execute( - ClosureScope(this, objFlow.scope) - ) - }) - } + moduleName = "lyng.stdlib", + code = object : ScopeCallable { + override suspend fun call(scp: Scope): Obj { + val objFlow = scp.thisAs() + return ObjFlowIterator(statement(f = object : ScopeCallable { + override suspend fun call(scp2: Scope): Obj { + objFlow.producer.execute( + ClosureScope(scp2, objFlow.scope) + ) + return ObjVoid + } + })) + } + } + ) } } } @@ -164,27 +173,36 @@ class ObjFlowIterator(val producer: Statement) : Obj() { name = "hasNext", doc = "Whether another element is available from the flow.", returns = type("lyng.Bool"), - moduleName = "lyng.stdlib" - ) { thisAs().hasNext(this).toObj() } + moduleName = "lyng.stdlib", + code = object : ScopeCallable { + override suspend fun call(scp: Scope): Obj = scp.thisAs().hasNext(scp).toObj() + } + ) addFnDoc( name = "next", doc = "Receive the next element from the flow or throw if completed.", returns = type("lyng.Any"), - moduleName = "lyng.stdlib" - ) { - val x = thisAs() - x.next(this) - } + moduleName = "lyng.stdlib", + code = object : ScopeCallable { + override suspend fun call(scp: Scope): Obj { + val x = scp.thisAs() + return x.next(scp) + } + } + ) addFnDoc( name = "cancelIteration", doc = "Stop iteration and cancel the underlying flow producer.", returns = type("lyng.Void"), - moduleName = "lyng.stdlib" - ) { - val x = thisAs() - x.cancel() - ObjVoid - } + moduleName = "lyng.stdlib", + code = object : ScopeCallable { + override suspend fun call(scp: Scope): Obj { + val x = scp.thisAs() + x.cancel() + return ObjVoid + } + } + ) } } } diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjInstance.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjInstance.kt index 52bf542..f218326 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjInstance.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjInstance.kt @@ -218,7 +218,7 @@ class ObjInstance(override val objClass: ObjClass) : Obj() { override suspend fun invokeInstanceMethod( scope: Scope, name: String, args: Arguments, - onNotFoundResult: (suspend () -> Obj?)? + onNotFoundResult: OnNotFound? ): Obj { val caller = scope.currentClassCtx @@ -231,7 +231,7 @@ class ObjInstance(override val objClass: ObjClass) : Obj() { if (rec.type == ObjRecord.Type.Property) { if (args.isEmpty()) return (rec.value as ObjProperty).callGetter(scope, this, decl) } else if (rec.type == ObjRecord.Type.Fun) { - return rec.value.invoke(instanceScope, this, args, decl) + return rec.value.invokeCallable(instanceScope, this, args, decl) } } } @@ -246,7 +246,7 @@ class ObjInstance(override val objClass: ObjClass) : Obj() { if (rec.type == ObjRecord.Type.Property) { if (args.isEmpty()) return (rec.value as ObjProperty).callGetter(scope, this, c) } else if (rec.type == ObjRecord.Type.Fun) { - return rec.value.invoke(instanceScope, this, args, c) + return rec.value.invokeCallable(instanceScope, this, args, c) } } } @@ -255,7 +255,7 @@ class ObjInstance(override val objClass: ObjClass) : Obj() { if (rec.type == ObjRecord.Type.Property) { if (args.isEmpty()) return (rec.value as ObjProperty).callGetter(scope, this, c) } else if (rec.type == ObjRecord.Type.Fun) { - return rec.value.invoke(instanceScope, this, args, c) + return rec.value.invokeCallable(instanceScope, this, args, c) } } } @@ -271,12 +271,12 @@ class ObjInstance(override val objClass: ObjClass) : Obj() { val del = instanceScope[storageName]?.delegate ?: rec.delegate ?: scope.raiseError("Internal error: delegated member $name has no delegate (tried $storageName)") - // For delegated member, try 'invoke' first if it's a function-like call + // For delegated member, try 'invokeCallable' first if it's a function-like call val allArgs = (listOf(this, ObjString(name)) + args.list).toTypedArray() - return del.invokeInstanceMethod(scope, "invoke", Arguments(*allArgs), onNotFoundResult = { + return del.invokeInstanceMethod(scope, "invokeCallable", Arguments(*allArgs), onNotFoundResult = { // Fallback: property delegation (getValue then call result) val propVal = del.invokeInstanceMethod(scope, "getValue", Arguments(this, ObjString(name))) - propVal.invoke(scope, this, args, rec.declaringClass ?: cls) + propVal.invokeCallable(scope, this, args, rec.declaringClass ?: cls) }) } val decl = rec.declaringClass ?: cls @@ -285,14 +285,14 @@ class ObjInstance(override val objClass: ObjClass) : Obj() { scope.raiseError( ObjIllegalAccessException( scope, - "can't invoke method $name (declared in ${decl.className})" + "can't invokeCallable method $name (declared in ${decl.className})" ) ) if (rec.type == ObjRecord.Type.Property) { if (args.isEmpty()) return (rec.value as ObjProperty).callGetter(scope, this, decl) } else if (rec.type == ObjRecord.Type.Fun) { - return rec.value.invoke( + return rec.value.invokeCallable( instanceScope, this, args, @@ -300,7 +300,7 @@ class ObjInstance(override val objClass: ObjClass) : Obj() { ) } else if (rec.type == ObjRecord.Type.Field || rec.type == ObjRecord.Type.ConstructorField || rec.type == ObjRecord.Type.Argument) { val resolved = readField(scope, name) - return resolved.value.invoke(scope, this, args, resolved.declaringClass) + return resolved.value.invokeCallable(scope, this, args, resolved.declaringClass) } } } @@ -507,18 +507,18 @@ class ObjQualifiedView(val instance: ObjInstance, private val startClass: ObjCla scope: Scope, name: String, args: Arguments, - onNotFoundResult: (suspend () -> Obj?)? + onNotFoundResult: OnNotFound? ): Obj { // Qualified method dispatch must start from the specified ancestor, not from the instance scope. memberFromAncestor(name)?.let { rec -> val decl = rec.declaringClass ?: startClass val caller = scope.currentClassCtx if (!canAccessMember(rec.visibility, decl, caller, name)) - scope.raiseError(ObjIllegalAccessException(scope, "can't invoke method $name (declared in ${decl.className})")) + scope.raiseError(ObjIllegalAccessException(scope, "can't invokeCallable method $name (declared in ${decl.className})")) val saved = instance.instanceScope.currentClassCtx instance.instanceScope.currentClassCtx = decl try { - return rec.value.invoke(instance.instanceScope, instance, args) + return rec.value.invokeCallable(instance.instanceScope, instance, args) } finally { instance.instanceScope.currentClassCtx = saved } @@ -532,19 +532,19 @@ class ObjQualifiedView(val instance: ObjInstance, private val startClass: ObjCla scope.raiseError( ObjIllegalAccessException( scope, - "can't invoke method $name (declared in ${decl?.className ?: "?"})" + "can't invokeCallable method $name (declared in ${decl?.className ?: "?"})" ) ) val saved = instance.instanceScope.currentClassCtx instance.instanceScope.currentClassCtx = decl try { - return rec.value.invoke(instance.instanceScope, instance, args) + return rec.value.invokeCallable(instance.instanceScope, instance, args) } finally { instance.instanceScope.currentClassCtx = saved } } } - return onNotFoundResult?.invoke() ?: scope.raiseSymbolNotFound(name) + return onNotFoundResult?.call() ?: scope.raiseSymbolNotFound(name) } override fun toString(): String = instance.toString() diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjInstant.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjInstant.kt index ed50dfc..f1e2fe6 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjInstant.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjInstant.kt @@ -18,17 +18,16 @@ package net.sergeych.lyng.obj import kotlinx.datetime.* +import kotlinx.datetime.Instant import kotlinx.serialization.json.JsonElement import kotlinx.serialization.json.JsonPrimitive import net.sergeych.lyng.Scope -import net.sergeych.lyng.miniast.addFnDoc -import net.sergeych.lyng.miniast.addPropertyDoc -import net.sergeych.lyng.miniast.type +import net.sergeych.lyng.ScopeCallable +import net.sergeych.lyng.miniast.* import net.sergeych.lynon.LynonDecoder import net.sergeych.lynon.LynonEncoder import net.sergeych.lynon.LynonSettings import net.sergeych.lynon.LynonType -import kotlin.time.Clock import kotlin.time.isDistantFuture import kotlin.time.isDistantPast @@ -113,7 +112,7 @@ class ObjInstant(val instant: Instant,val truncateMode: LynonSettings.InstantTru return ObjInstant( when (a0) { null -> { - val t = Clock.System.now() + val t = kotlin.time.Clock.System.now() Instant.fromEpochSeconds(t.epochSeconds, t.nanosecondsOfSecond) } is ObjInt -> Instant.fromEpochSeconds(a0.value) @@ -157,9 +156,11 @@ class ObjInstant(val instant: Instant,val truncateMode: LynonSettings.InstantTru doc = "Return the number of seconds since the Unix epoch as a real number (including fractions).", type = type("lyng.Real"), moduleName = "lyng.time", - getter = { - val instant = thisAs().instant - ObjReal(instant.epochSeconds + instant.nanosecondsOfSecond * 1e-9) + getter = object : ScopeCallable { + override suspend fun call(scp: Scope): Obj { + val instant = scp.thisAs().instant + return ObjReal(instant.epochSeconds + instant.nanosecondsOfSecond * 1e-9) + } } ) addPropertyDoc( @@ -167,92 +168,114 @@ class ObjInstant(val instant: Instant,val truncateMode: LynonSettings.InstantTru doc = "Whether this instant represents the distant future.", type = type("lyng.Bool"), moduleName = "lyng.time", - getter = { thisAs().instant.isDistantFuture.toObj() } + getter = object : ScopeCallable { + override suspend fun call(scp: Scope): Obj = scp.thisAs().instant.isDistantFuture.toObj() + } ) addPropertyDoc( name = "isDistantPast", doc = "Whether this instant represents the distant past.", type = type("lyng.Bool"), moduleName = "lyng.time", - getter = { thisAs().instant.isDistantPast.toObj() } + getter = object : ScopeCallable { + override suspend fun call(scp: Scope): Obj = scp.thisAs().instant.isDistantPast.toObj() + } ) addPropertyDoc( name = "epochWholeSeconds", doc = "Return the number of full seconds since the Unix epoch.", type = type("lyng.Int"), moduleName = "lyng.time", - getter = { ObjInt(thisAs().instant.epochSeconds) } + getter = object : ScopeCallable { + override suspend fun call(scp: Scope): Obj = ObjInt(scp.thisAs().instant.epochSeconds) + } ) addPropertyDoc( name = "nanosecondsOfSecond", doc = "The number of nanoseconds within the current second.", type = type("lyng.Int"), moduleName = "lyng.time", - getter = { ObjInt(thisAs().instant.nanosecondsOfSecond.toLong()) } + getter = object : ScopeCallable { + override suspend fun call(scp: Scope): Obj = ObjInt(scp.thisAs().instant.nanosecondsOfSecond.toLong()) + } ) addFnDoc( name = "truncateToMinute", doc = "Truncate this instant to the nearest minute.", returns = type("lyng.Instant"), - moduleName = "lyng.time" - ) { - val t = thisAs().instant - val tz = TimeZone.UTC - val dt = t.toLocalDateTime(tz) - val truncated = LocalDateTime(dt.year, dt.month, dt.dayOfMonth, dt.hour, dt.minute, 0, 0) - ObjInstant(truncated.toInstant(tz), LynonSettings.InstantTruncateMode.Second) - } + moduleName = "lyng.time", + code = object : ScopeCallable { + override suspend fun call(scp: Scope): Obj { + val t = scp.thisAs().instant + val tz = TimeZone.UTC + val dt = t.toLocalDateTime(tz) + val truncated = LocalDateTime(dt.year, dt.month, dt.dayOfMonth, dt.hour, dt.minute, 0, 0) + return ObjInstant(truncated.toInstant(tz), LynonSettings.InstantTruncateMode.Second) + } + } + ) addFnDoc( name = "truncateToSecond", doc = "Truncate this instant to the nearest second.", returns = type("lyng.Instant"), - moduleName = "lyng.time" - ) { - val t = thisAs().instant - ObjInstant(Instant.fromEpochSeconds(t.epochSeconds), LynonSettings.InstantTruncateMode.Second) - } + moduleName = "lyng.time", + code = object : ScopeCallable { + override suspend fun call(scp: Scope): Obj { + val t = scp.thisAs().instant + return ObjInstant(Instant.fromEpochSeconds(t.epochSeconds), LynonSettings.InstantTruncateMode.Second) + } + } + ) addFnDoc( name = "truncateToMillisecond", doc = "Truncate this instant to the nearest millisecond.", returns = type("lyng.Instant"), - moduleName = "lyng.time" - ) { - val t = thisAs().instant - ObjInstant( - Instant.fromEpochSeconds(t.epochSeconds, t.nanosecondsOfSecond / 1_000_000 * 1_000_000), - LynonSettings.InstantTruncateMode.Millisecond - ) - } + moduleName = "lyng.time", + code = object : ScopeCallable { + override suspend fun call(scp: Scope): Obj { + val t = scp.thisAs().instant + return ObjInstant( + Instant.fromEpochSeconds(t.epochSeconds, t.nanosecondsOfSecond / 1_000_000 * 1_000_000), + LynonSettings.InstantTruncateMode.Millisecond + ) + } + } + ) addFnDoc( name = "truncateToMicrosecond", doc = "Truncate this instant to the nearest microsecond.", returns = type("lyng.Instant"), - moduleName = "lyng.time" - ) { - val t = thisAs().instant - ObjInstant( - Instant.fromEpochSeconds(t.epochSeconds, t.nanosecondsOfSecond / 1_000 * 1_000), - LynonSettings.InstantTruncateMode.Microsecond - ) - } + moduleName = "lyng.time", + code = object : ScopeCallable { + override suspend fun call(scp: Scope): Obj { + val t = scp.thisAs().instant + return ObjInstant( + Instant.fromEpochSeconds(t.epochSeconds, t.nanosecondsOfSecond / 1_000 * 1_000), + LynonSettings.InstantTruncateMode.Microsecond + ) + } + } + ) addFnDoc( name = "toRFC3339", doc = "Return the RFC3339 string representation of this instant in UTC (e.g., '1970-01-01T00:00:00Z').", returns = type("lyng.String"), - moduleName = "lyng.time" - ) { - thisAs().instant.toString().toObj() - } + moduleName = "lyng.time", + code = object : ScopeCallable { + override suspend fun call(scp: Scope): Obj = scp.thisAs().instant.toString().toObj() + } + ) addFnDoc( name = "toSortableString", doc = "Alias to toRFC3339.", returns = type("lyng.String"), - moduleName = "lyng.time" - ) { - thisAs().instant.toString().toObj() - } + moduleName = "lyng.time", + code = object : ScopeCallable { + override suspend fun call(scp: Scope): Obj = scp.thisAs().instant.toString().toObj() + } + ) addFnDoc( name = "toDateTime", @@ -261,24 +284,27 @@ class ObjInstant(val instant: Instant,val truncateMode: LynonSettings.InstantTru "If no argument is provided, the system's current default time zone is used.", params = listOf(net.sergeych.lyng.miniast.ParamDoc("tz", type = type("lyng.Any", true))), returns = type("lyng.DateTime"), - moduleName = "lyng.time" - ) { - val tz = when (val a = args.list.getOrNull(0)) { - null -> TimeZone.currentSystemDefault() - is ObjString -> TimeZone.of(a.value) - is ObjInt -> UtcOffset(seconds = a.value.toInt()).asTimeZone() - else -> raiseIllegalArgument("invalid timezone: $a") + moduleName = "lyng.time", + code = object : ScopeCallable { + override suspend fun call(scp: Scope): Obj { + val tz = when (val a = scp.args.list.getOrNull(0)) { + null -> TimeZone.currentSystemDefault() + is ObjString -> TimeZone.of(a.value) + is ObjInt -> UtcOffset(seconds = a.value.toInt()).asTimeZone() + else -> scp.raiseIllegalArgument("invalid timezone: $a") + } + return ObjDateTime(scp.thisAs().instant, tz) + } } - ObjDateTime(thisAs().instant, tz) - } + ) // class members addClassConst("distantFuture", distantFuture) addClassConst("distantPast", distantPast) - addClassFn("now") { - ObjInstant(Clock.System.now()) - } + addClassFn("now", code = object : ScopeCallable { + override suspend fun call(scp: Scope): Obj = ObjInstant(kotlin.time.Clock.System.now()) + }) // addFn("epochMilliseconds") { // ObjInt(instant.toEpochMilliseconds()) // } diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjInt.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjInt.kt index 0671102..ee0da02 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjInt.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjInt.kt @@ -1,5 +1,5 @@ /* - * Copyright 2025 Sergey S. Chernov real.sergeych@gmail.com + * Copyright 2026 Sergey S. Chernov real.sergeych@gmail.com * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,6 +20,7 @@ package net.sergeych.lyng.obj import kotlinx.serialization.json.JsonElement import kotlinx.serialization.json.JsonPrimitive import net.sergeych.lyng.Scope +import net.sergeych.lyng.ScopeCallable import net.sergeych.lyng.miniast.addFnDoc import net.sergeych.lynon.LynonDecoder import net.sergeych.lynon.LynonEncoder @@ -183,10 +184,11 @@ class ObjInt(val value: Long, override val isConst: Boolean = false) : Obj(), Nu name = "toInt", doc = "Returns this integer (identity operation).", returns = net.sergeych.lyng.miniast.type("lyng.Int"), - moduleName = "lyng.stdlib" - ) { - thisObj - } + moduleName = "lyng.stdlib", + code = object : ScopeCallable { + override suspend fun call(scp: Scope): Obj = scp.thisObj + } + ) } } } diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjIterable.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjIterable.kt index 1a273b2..d812f60 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjIterable.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjIterable.kt @@ -18,6 +18,8 @@ package net.sergeych.lyng.obj import net.sergeych.lyng.Arguments +import net.sergeych.lyng.Scope +import net.sergeych.lyng.ScopeCallable import net.sergeych.lyng.Statement import net.sergeych.lyng.miniast.ParamDoc import net.sergeych.lyng.miniast.addFnDoc @@ -35,13 +37,15 @@ val ObjIterable by lazy { doc = "Collect elements of this iterable into a new list.", type = type("lyng.List"), moduleName = "lyng.stdlib", - getter = { - val result = mutableListOf() - val it = this.thisObj.invokeInstanceMethod(this, "iterator") - while (it.invokeInstanceMethod(this, "hasNext").toBool()) { - result.add(it.invokeInstanceMethod(this, "next")) + getter = object : ScopeCallable { + override suspend fun call(scp: Scope): Obj { + val result = mutableListOf() + val it = scp.thisObj.invokeInstanceMethod(scp, "iterator") + while (it.invokeInstanceMethod(scp, "hasNext").toBool()) { + result.add(it.invokeInstanceMethod(scp, "next")) + } + return ObjList(result) } - ObjList(result) } ) @@ -52,16 +56,19 @@ val ObjIterable by lazy { params = listOf(ParamDoc("element")), returns = type("lyng.Bool"), isOpen = true, - moduleName = "lyng.stdlib" - ) { - val obj = args.firstAndOnly() - val it = thisObj.invokeInstanceMethod(this, "iterator") - while (it.invokeInstanceMethod(this, "hasNext").toBool()) { - if (obj.equals(this, it.invokeInstanceMethod(this, "next"))) - return@addFnDoc ObjTrue + moduleName = "lyng.stdlib", + code = object : ScopeCallable { + override suspend fun call(scp: Scope): Obj { + val obj = scp.args.firstAndOnly() + val it = scp.thisObj.invokeInstanceMethod(scp, "iterator") + while (it.invokeInstanceMethod(scp, "hasNext").toBool()) { + if (obj.equals(scp, it.invokeInstanceMethod(scp, "next"))) + return ObjTrue + } + return ObjFalse + } } - ObjFalse - } + ) addFnDoc( name = "indexOf", @@ -69,34 +76,39 @@ val ObjIterable by lazy { params = listOf(ParamDoc("element")), returns = type("lyng.Int"), isOpen = true, - moduleName = "lyng.stdlib" - ) { - 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"))) - return@addFnDoc ObjInt(index.toLong()) - index++ + moduleName = "lyng.stdlib", + code = object : ScopeCallable { + override suspend fun call(scp: Scope): Obj { + val obj = scp.args.firstAndOnly() + var index = 0 + val it = scp.thisObj.invokeInstanceMethod(scp, "iterator") + while (it.invokeInstanceMethod(scp, "hasNext").toBool()) { + if (obj.equals(scp, it.invokeInstanceMethod(scp, "next"))) + return ObjInt(index.toLong()) + index++ + } + return ObjInt(-1L) + } } - ObjInt(-1L) - } + ) addPropertyDoc( name = "toSet", doc = "Collect elements of this iterable into a new set.", type = type("lyng.Set"), moduleName = "lyng.stdlib", - getter = { - if( this.thisObj.isInstanceOf(ObjSet.type) ) - this.thisObj - else { - val result = mutableSetOf() - val it = this.thisObj.invokeInstanceMethod(this, "iterator") - while (it.invokeInstanceMethod(this, "hasNext").toBool()) { - result.add(it.invokeInstanceMethod(this, "next")) + getter = object : ScopeCallable { + override suspend fun call(scp: Scope): Obj { + return if( scp.thisObj.isInstanceOf(ObjSet.type) ) + scp.thisObj + else { + val result = mutableSetOf() + val it = scp.thisObj.invokeInstanceMethod(scp, "iterator") + while (it.invokeInstanceMethod(scp, "hasNext").toBool()) { + result.add(it.invokeInstanceMethod(scp, "next")) + } + ObjSet(result) } - ObjSet(result) } } ) @@ -106,16 +118,20 @@ val ObjIterable by lazy { doc = "Collect pairs into a map using [0] as key and [1] as value for each element.", type = type("lyng.Map"), moduleName = "lyng.stdlib", - getter = { - val result = mutableMapOf() - this.thisObj.enumerate(this) { pair -> - when (pair) { - is ObjMapEntry -> result[pair.key] = pair.value - else -> result[pair.getAt(this, 0)] = pair.getAt(this, 1) - } - true + getter = object : ScopeCallable { + override suspend fun call(scp: Scope): Obj { + val result = mutableMapOf() + scp.thisObj.enumerate(scp, object : EnumerateCallback { + override suspend fun call(pair: Obj): Boolean { + when (pair) { + is ObjMapEntry -> result[pair.key] = pair.value + else -> result[pair.getAt(scp, ObjInt(0))] = pair.getAt(scp, ObjInt(1)) + } + return true + } + }) + return ObjMap(result) } - ObjMap(result) } ) @@ -124,31 +140,37 @@ val ObjIterable by lazy { doc = "Build a map from elements using the lambda result as key.", params = listOf(ParamDoc("keySelector")), returns = type("lyng.Map"), - moduleName = "lyng.stdlib" - ) { - val association = requireOnlyArg() - val result = ObjMap() - thisObj.toFlow(this).collect { - result.map[association.call(this, it)] = it + moduleName = "lyng.stdlib", + code = object : ScopeCallable { + override suspend fun call(scp: Scope): Obj { + val association = scp.requireOnlyArg() + val result = ObjMap() + scp.thisObj.toFlow(scp).collect { + result.map[association.invokeCallable(scp, scp.thisObj, it)] = it + } + return result + } } - result - } + ) addFnDoc( name = "forEach", doc = "Apply the lambda to each element in iteration order.", params = listOf(ParamDoc("action")), isOpen = true, - moduleName = "lyng.stdlib" - ) { - val it = thisObj.invokeInstanceMethod(this, "iterator") - val fn = requiredArg(0) - while (it.invokeInstanceMethod(this, "hasNext").toBool()) { - val x = it.invokeInstanceMethod(this, "next") - fn.execute(this.createChildScope(Arguments(listOf(x)))) + moduleName = "lyng.stdlib", + code = object : ScopeCallable { + override suspend fun call(scp: Scope): Obj { + val it = scp.thisObj.invokeInstanceMethod(scp, "iterator") + val fn = scp.requiredArg(0) + while (it.invokeInstanceMethod(scp, "hasNext").toBool()) { + val x = it.invokeInstanceMethod(scp, "next") + fn.execute(scp.createChildScope(Arguments(listOf(x)))) + } + return ObjVoid + } } - ObjVoid - } + ) addFnDoc( name = "map", @@ -156,15 +178,18 @@ val ObjIterable by lazy { params = listOf(ParamDoc("transform")), returns = type("lyng.List"), isOpen = true, - moduleName = "lyng.stdlib" - ) { - val fn = requiredArg(0) - val result = mutableListOf() - thisObj.toFlow(this).collect { - result.add(fn.call(this, it)) + moduleName = "lyng.stdlib", + code = object : ScopeCallable { + override suspend fun call(scp: Scope): Obj { + val fn = scp.requiredArg(0) + val result = mutableListOf() + scp.thisObj.toFlow(scp).collect { + result.add(fn.invokeCallable(scp, scp.thisObj, it)) + } + return ObjList(result) + } } - ObjList(result) - } + ) addFnDoc( name = "mapNotNull", @@ -172,46 +197,56 @@ val ObjIterable by lazy { params = listOf(ParamDoc("transform")), returns = type("lyng.List"), isOpen = true, - moduleName = "lyng.stdlib" - ) { - val fn = requiredArg(0) - val result = mutableListOf() - thisObj.toFlow(this).collect { - val transformed = fn.call(this, it) - if( transformed != ObjNull) result.add(transformed) + moduleName = "lyng.stdlib", + code = object : ScopeCallable { + override suspend fun call(scp: Scope): Obj { + val fn = scp.requiredArg(0) + val result = mutableListOf() + scp.thisObj.toFlow(scp).collect { + val transformed = fn.invokeCallable(scp, scp.thisObj, it) + if( transformed != ObjNull) result.add(transformed) + } + return ObjList(result) + } } - ObjList(result) - } + ) addFnDoc( name = "take", doc = "Take the first N elements and return them as a list.", params = listOf(ParamDoc("n", type("lyng.Int"))), returns = type("lyng.List"), - moduleName = "lyng.stdlib" - ) { - var n = requireOnlyArg().value.toInt() - val result = mutableListOf() - if (n > 0) { - thisObj.enumerate(this) { - result.add(it) - --n > 0 + moduleName = "lyng.stdlib", + code = object : ScopeCallable { + override suspend fun call(scp: Scope): Obj { + var n = scp.requireOnlyArg().value.toInt() + val result = mutableListOf() + if (n > 0) { + scp.thisObj.enumerate(scp, object : EnumerateCallback { + override suspend fun call(element: Obj): Boolean { + result.add(element) + return --n > 0 + } + }) + } + return ObjList(result) } } - ObjList(result) - } + ) addPropertyDoc( name = "isEmpty", doc = "Whether the iterable has no elements.", type = type("lyng.Bool"), moduleName = "lyng.stdlib", - getter = { - ObjBool( - this.thisObj.invokeInstanceMethod(this, "iterator") - .invokeInstanceMethod(this, "hasNext").toBool() - .not() - ) + getter = object : ScopeCallable { + override suspend fun call(scp: Scope): Obj { + return ObjBool( + scp.thisObj.invokeInstanceMethod(scp, "iterator") + .invokeInstanceMethod(scp, "hasNext").toBool() + .not() + ) + } } ) @@ -220,25 +255,31 @@ val ObjIterable by lazy { doc = "Return a new list sorted using the provided comparator `(a, b) -> Int`.", params = listOf(ParamDoc("comparator")), returns = type("lyng.List"), - moduleName = "lyng.stdlib" - ) { - val list = thisObj.callMethod(this, "toList") - val comparator = requireOnlyArg() - list.quicksort { a, b -> - comparator.call(this, a, b).toInt() + moduleName = "lyng.stdlib", + code = object : ScopeCallable { + override suspend fun call(scp: Scope): Obj { + val list = scp.thisObj.callMethod(scp, "toList") + val comparator = scp.requireOnlyArg() + list.quicksort { a, b -> + comparator.invokeCallable(scp, scp.thisObj, a, b).toInt() + } + return list + } } - list - } + ) addFnDoc( name = "reversed", doc = "Return a new list with elements in reverse order.", returns = type("lyng.List"), - moduleName = "lyng.stdlib" - ) { - val list = thisObj.callMethod(this, "toList") - list.list.reverse() - list - } + moduleName = "lyng.stdlib", + code = object : ScopeCallable { + override suspend fun call(scp: Scope): Obj { + val list = scp.thisObj.callMethod(scp, "toList") + list.list.reverse() + return list + } + } + ) } } \ No newline at end of file diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjIterator.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjIterator.kt index 319f7f0..64c41b9 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjIterator.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjIterator.kt @@ -1,5 +1,5 @@ /* - * Copyright 2025 Sergey S. Chernov real.sergeych@gmail.com + * Copyright 2026 Sergey S. Chernov real.sergeych@gmail.com * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,6 +17,8 @@ package net.sergeych.lyng.obj +import net.sergeych.lyng.Scope +import net.sergeych.lyng.ScopeCallable import net.sergeych.lyng.miniast.TypeGenericDoc import net.sergeych.lyng.miniast.addFnDoc import net.sergeych.lyng.miniast.type @@ -38,44 +40,50 @@ val ObjIterator by lazy { doc = "Optional hint to stop iteration early and free resources.", returns = type("lyng.Void"), isOpen = true, - moduleName = "lyng.stdlib" - ) { - ObjVoid - } + moduleName = "lyng.stdlib", + code = object : ScopeCallable { + override suspend fun call(scp: Scope): Obj = ObjVoid + } + ) addFnDoc( name = "hasNext", doc = "Whether another element is available.", returns = type("lyng.Bool"), isOpen = true, - moduleName = "lyng.stdlib" - ) { - raiseNotImplemented("hasNext() is not implemented") - } + moduleName = "lyng.stdlib", + code = object : ScopeCallable { + override suspend fun call(scp: Scope): Obj = scp.raiseNotImplemented("hasNext() is not implemented") + } + ) addFnDoc( name = "next", doc = "Return the next element.", returns = type("lyng.Any"), isOpen = true, - moduleName = "lyng.stdlib" - ) { - raiseNotImplemented("next() is not implemented") - } + moduleName = "lyng.stdlib", + code = object : ScopeCallable { + override suspend fun call(scp: Scope): Obj = scp.raiseNotImplemented("next() is not implemented") + } + ) // Helper to consume iterator into a list addFnDoc( name = "toList", doc = "Consume this iterator and collect elements into a list.", returns = TypeGenericDoc(type("lyng.List"), listOf(type("lyng.Any"))), - moduleName = "lyng.stdlib" - ) { - val out = mutableListOf() - while (true) { - val has = thisObj.invokeInstanceMethod(this, "hasNext").toBool() - if (!has) break - val v = thisObj.invokeInstanceMethod(this, "next") - out += v + moduleName = "lyng.stdlib", + code = object : ScopeCallable { + override suspend fun call(scp: Scope): Obj { + val out = mutableListOf() + while (true) { + val has = scp.thisObj.invokeInstanceMethod(scp, "hasNext").toBool() + if (!has) break + val v = scp.thisObj.invokeInstanceMethod(scp, "next") + out += v + } + return ObjList(out.toMutableList()) + } } - ObjList(out.toMutableList()) - } + ) } } diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjKotlinIterator.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjKotlinIterator.kt index 0f606af..5f1ed8c 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjKotlinIterator.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjKotlinIterator.kt @@ -1,5 +1,5 @@ /* - * Copyright 2025 Sergey S. Chernov real.sergeych@gmail.com + * Copyright 2026 Sergey S. Chernov real.sergeych@gmail.com * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -22,6 +22,7 @@ package net.sergeych.lyng.obj import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.flow import net.sergeych.lyng.Scope +import net.sergeych.lyng.ScopeCallable /** * Iterator wrapper to allow Kotlin collections to be returned from Lyng objects; @@ -33,8 +34,12 @@ class ObjKotlinIterator(val iterator: Iterator) : Obj() { companion object { val type = ObjClass("KotlinIterator", ObjIterator).apply { - addFn("next") { thisAs().iterator.next().toObj() } - addFn("hasNext") { thisAs().iterator.hasNext().toObj() } + addFn("next", code = object : ScopeCallable { + override suspend fun call(scp: Scope): Obj = scp.thisAs().iterator.next().toObj() + }) + addFn("hasNext", code = object : ScopeCallable { + override suspend fun call(scp: Scope): Obj = scp.thisAs().iterator.hasNext().toObj() + }) } } @@ -50,12 +55,14 @@ class ObjKotlinObjIterator(val iterator: Iterator) : Obj() { companion object { val type = ObjClass("KotlinIterator", ObjIterator).apply { - addFn("next") { - thisAs().iterator.next() - } - addFn("hasNext") { - thisAs().iterator.hasNext().toObj() - } + addFn("next", code = object : ScopeCallable { + override suspend fun call(scp: Scope): Obj = + scp.thisAs().iterator.next() + }) + addFn("hasNext", code = object : ScopeCallable { + override suspend fun call(scp: Scope): Obj = + scp.thisAs().iterator.hasNext().toObj() + }) } } @@ -71,8 +78,8 @@ fun Obj.toFlow(scope: Scope): Flow = flow { val iterator = invokeInstanceMethod(scope, "iterator") val hasNext = iterator.getInstanceMethod(scope, "hasNext") val next = iterator.getInstanceMethod(scope, "next") - while (hasNext.invoke(scope, iterator).toBool()) { - emit(next.invoke(scope, iterator)) + while (hasNext.invokeCallable(scope, iterator).toBool()) { + emit(next.invokeCallable(scope, iterator)) } } diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjList.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjList.kt index 60d8b4b..e6ee78e 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjList.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjList.kt @@ -20,6 +20,7 @@ package net.sergeych.lyng.obj import kotlinx.serialization.json.JsonArray import kotlinx.serialization.json.JsonElement import net.sergeych.lyng.Scope +import net.sergeych.lyng.ScopeCallable import net.sergeych.lyng.Statement import net.sergeych.lyng.miniast.ParamDoc import net.sergeych.lyng.miniast.addFnDoc @@ -111,13 +112,13 @@ class ObjList(val list: MutableList = mutableListOf()) : Obj() { val next2 = it2.getInstanceMethod(scope, "next") while (it1.hasNext()) { - if (!hasNext2.invoke(scope, it2).toBool()) return 1 // I'm longer + if (!hasNext2.invokeCallable(scope, it2).toBool()) return 1 // I'm longer val v1 = it1.next() - val v2 = next2.invoke(scope, it2) + val v2 = next2.invokeCallable(scope, it2) val d = v1.compareTo(scope, v2) if (d != 0) return d } - return if (hasNext2.invoke(scope, it2).toBool()) -1 else 0 + return if (hasNext2.invokeCallable(scope, it2).toBool()) -1 else 0 } return -2 } @@ -169,9 +170,9 @@ class ObjList(val list: MutableList = mutableListOf()) : Obj() { return list.contains(other) } - override suspend fun enumerate(scope: Scope, callback: suspend (Obj) -> Boolean) { + override suspend fun enumerate(scope: Scope, callback: EnumerateCallback) { for (item in list) { - if (!callback(item)) break + if (!callback.call(item)) break } } @@ -179,7 +180,9 @@ class ObjList(val list: MutableList = mutableListOf()) : Obj() { get() = type override suspend fun toKotlin(scope: Scope): Any { - return list.map { it.toKotlin(scope) } + val res = ArrayList(list.size) + for (i in list) res.add(i.toKotlin(scope)) + return res } suspend fun quicksort(compare: suspend (Obj, Obj) -> Int) = quicksort(compare, 0, list.size - 1) @@ -230,7 +233,9 @@ class ObjList(val list: MutableList = mutableListOf()) : Obj() { override suspend fun lynonType(): LynonType = LynonType.List override suspend fun toJson(scope: Scope): JsonElement { - return JsonArray(list.map { it.toJson(scope) }) + val res = ArrayList(list.size) + for (i in list) res.add(i.toJson(scope)) + return JsonArray(res) } override suspend fun defaultToString(scope: Scope): ObjString { @@ -256,256 +261,290 @@ class ObjList(val list: MutableList = mutableListOf()) : Obj() { doc = "Number of elements in this list.", type = type("lyng.Int"), moduleName = "lyng.stdlib", - getter = { - val s = (this.thisObj as ObjList).list.size - s.toObj() + getter = object : ScopeCallable { + override suspend fun call(scp: Scope): Obj { + return (scp.thisObj as ObjList).list.size.toObj() + } } ) addFnDoc( name = "add", doc = "Append one or more elements to the end of this list.", - moduleName = "lyng.stdlib" - ) { - val l = thisAs().list - for (a in args) l.add(a) - ObjVoid - } + moduleName = "lyng.stdlib", + code = object : ScopeCallable { + override suspend fun call(scp: Scope): Obj { + val l = scp.thisAs().list + for (a in scp.args) l.add(a) + return ObjVoid + } + } + ) addFnDoc( name = "insertAt", doc = "Insert elements starting at the given index.", params = listOf(ParamDoc("index", type("lyng.Int"))), - moduleName = "lyng.stdlib" - ) { - if (args.size < 2) raiseError("addAt takes 2+ arguments") - val l = thisAs() - var index = requiredArg(0).value.toInt() - for (i in 1..() + var index = scp.requiredArg(0).value.toInt() + for (i in 1..() - val start = requiredArg(0).value.toInt() - if (args.size == 2) { - val end = requireOnlyArg().value.toInt() - self.list.subList(start, end).clear() - } else - self.list.removeAt(start) - self - } + moduleName = "lyng.stdlib", + code = object : ScopeCallable { + override suspend fun call(scp: Scope): Obj { + val self = scp.thisAs() + val start = scp.requiredArg(0).value.toInt() + if (scp.args.size == 2) { + val end = scp.requireOnlyArg().value.toInt() + self.list.subList(start, end).clear() + } else + self.list.removeAt(start) + return self + } + } + ) addFnDoc( name = "removeLast", doc = "Remove the last element or the last N elements if a count is provided. Returns the list.", params = listOf(ParamDoc("count", type("lyng.Int"))), - moduleName = "lyng.stdlib" - ) { - val self = thisAs() - if (args.isNotEmpty()) { - val count = requireOnlyArg().value.toInt() - val size = self.list.size - if (count >= size) self.list.clear() - else self.list.subList(size - count, size).clear() - } else self.list.removeLast() - self - } + moduleName = "lyng.stdlib", + code = object : ScopeCallable { + override suspend fun call(scp: Scope): Obj { + val self = scp.thisAs() + if (scp.args.isNotEmpty()) { + val count = scp.requireOnlyArg().value.toInt() + val size = self.list.size + if (count >= size) self.list.clear() + else self.list.subList(size - count, size).clear() + } else self.list.removeLast() + return self + } + } + ) addFnDoc( name = "removeRange", doc = "Remove a range of elements. Accepts a Range or (start, endInclusive). Returns the list.", params = listOf(ParamDoc("range")), - moduleName = "lyng.stdlib" - ) { - val self = thisAs() - val list = self.list - val range = requiredArg(0) - if (range is ObjRange) { - val index = range - when { - index.start is ObjInt && index.end is ObjInt -> { - if (index.isEndInclusive) - list.subList(index.start.toInt(), index.end.toInt() + 1) - else - list.subList(index.start.toInt(), index.end.toInt()) - } + moduleName = "lyng.stdlib", + code = object : ScopeCallable { + override suspend fun call(scp: Scope): Obj { + val self = scp.thisAs() + val list = self.list + val range = scp.requiredArg(0) + if (range is ObjRange) { + val index = range + when { + index.start is ObjInt && index.end is ObjInt -> { + if (index.isEndInclusive) + list.subList(index.start.toInt(), index.end.toInt() + 1) + else + list.subList(index.start.toInt(), index.end.toInt()) + } - index.isOpenStart && !index.isOpenEnd -> { - if (index.isEndInclusive) - list.subList(0, index.end!!.toInt() + 1) - else - list.subList(0, index.end!!.toInt()) - } + index.isOpenStart && !index.isOpenEnd -> { + if (index.isEndInclusive) + list.subList(0, index.end!!.toInt() + 1) + else + list.subList(0, index.end!!.toInt()) + } - index.isOpenEnd && !index.isOpenStart -> { - list.subList(index.start!!.toInt(), list.size) - } + index.isOpenEnd && !index.isOpenStart -> { + list.subList(index.start!!.toInt(), list.size) + } - index.isOpenStart && index.isOpenEnd -> { - list - } + index.isOpenStart && index.isOpenEnd -> { + list + } - else -> { - throw RuntimeException("Can't apply range for index: $index") + else -> { + throw RuntimeException("Can't apply range for index: $index") + } + }.clear() + } else { + val start = range.toInt() + val end = scp.requiredArg(1).value.toInt() + 1 + self.list.subList(start, end).clear() } - }.clear() - } else { - val start = range.toInt() - val end = requiredArg(1).value.toInt() + 1 - self.list.subList(start, end).clear() + return self + } } - self - } + ) addFnDoc( name = "sortWith", doc = "Sort this list in-place using a comparator function (a, b) -> Int.", params = listOf(ParamDoc("comparator")), - moduleName = "lyng.stdlib" - ) { - val comparator = requireOnlyArg() - thisAs().quicksort { a, b -> comparator.call(this, a, b).toInt() } - ObjVoid - } + moduleName = "lyng.stdlib", + code = object : ScopeCallable { + override suspend fun call(scp: Scope): Obj { + val comparator = scp.requireOnlyArg() + scp.thisAs().quicksort { a, b -> comparator.invokeCallable(scp, scp.thisObj, a, b).toInt() } + return ObjVoid + } + } + ) addFnDoc( name = "shuffle", doc = "Shuffle elements of this list in-place.", - moduleName = "lyng.stdlib" - ) { - thisAs().list.shuffle() - ObjVoid - } + moduleName = "lyng.stdlib", + code = object : ScopeCallable { + override suspend fun call(scp: Scope): Obj { + scp.thisAs().list.shuffle() + return ObjVoid + } + } + ) addFnDoc( name = "sum", doc = "Sum elements using dynamic '+' or optimized integer path. Returns null for empty lists.", - moduleName = "lyng.stdlib" - ) { - val self = thisAs() - val l = self.list - if (l.isEmpty()) return@addFnDoc ObjNull - if (net.sergeych.lyng.PerfFlags.PRIMITIVE_FASTOPS) { - // Fast path: all ints → accumulate as long - var i = 0 - var acc: Long = 0 - while (i < l.size) { - val v = l[i] - if (v is ObjInt) { - acc += v.value - i++ - } else { - // Fallback to generic dynamic '+' accumulation starting from current acc - var res: Obj = ObjInt(acc) + moduleName = "lyng.stdlib", + code = object : ScopeCallable { + override suspend fun call(scp: Scope): Obj { + val self = scp.thisAs() + val l = self.list + if (l.isEmpty()) return ObjNull + if (net.sergeych.lyng.PerfFlags.PRIMITIVE_FASTOPS) { + // Fast path: all ints → accumulate as long + var i = 0 + var acc: Long = 0 while (i < l.size) { - res = res.plus(this, l[i]) - i++ + val v = l[i] + if (v is ObjInt) { + acc += v.value + i++ + } else { + // Fallback to generic dynamic '+' accumulation starting from current acc + var res: Obj = ObjInt(acc) + while (i < l.size) { + res = res.plus(scp, l[i]) + i++ + } + return res + } } - return@addFnDoc res + return ObjInt(acc) } + // Generic path: dynamic '+' starting from first element + var res: Obj = l[0] + var k = 1 + while (k < l.size) { + res = res.plus(scp, l[k]) + k++ + } + return res } - return@addFnDoc ObjInt(acc) } - // Generic path: dynamic '+' starting from first element - var res: Obj = l[0] - var k = 1 - while (k < l.size) { - res = res.plus(this, l[k]) - k++ - } - res - } + ) addFnDoc( name = "min", doc = "Minimum element by natural order. Returns null for empty lists.", - moduleName = "lyng.stdlib" - ) { - val l = thisAs().list - if (l.isEmpty()) return@addFnDoc ObjNull - if (net.sergeych.lyng.PerfFlags.PRIMITIVE_FASTOPS) { - var i = 0 - var hasOnlyInts = true - var minVal: Long = Long.MAX_VALUE - while (i < l.size) { - val v = l[i] - if (v is ObjInt) { - if (v.value < minVal) minVal = v.value - } else { - hasOnlyInts = false - break + moduleName = "lyng.stdlib", + code = object : ScopeCallable { + override suspend fun call(scp: Scope): Obj { + val l = scp.thisAs().list + if (l.isEmpty()) return ObjNull + if (net.sergeych.lyng.PerfFlags.PRIMITIVE_FASTOPS) { + var i = 0 + var hasOnlyInts = true + var minVal: Long = Long.MAX_VALUE + while (i < l.size) { + val v = l[i] + if (v is ObjInt) { + if (v.value < minVal) minVal = v.value + } else { + hasOnlyInts = false + break + } + i++ + } + if (hasOnlyInts) return ObjInt(minVal) } - i++ + var res: Obj = l[0] + var i = 1 + while (i < l.size) { + val v = l[i] + if (v.compareTo(scp, res) < 0) res = v + i++ + } + return res } - if (hasOnlyInts) return@addFnDoc ObjInt(minVal) } - var res: Obj = l[0] - var i = 1 - while (i < l.size) { - val v = l[i] - if (v.compareTo(this, res) < 0) res = v - i++ - } - res - } + ) addFnDoc( name = "max", doc = "Maximum element by natural order. Returns null for empty lists.", - moduleName = "lyng.stdlib" - ) { - val l = thisAs().list - if (l.isEmpty()) return@addFnDoc ObjNull - if (net.sergeych.lyng.PerfFlags.PRIMITIVE_FASTOPS) { - var i = 0 - var hasOnlyInts = true - var maxVal: Long = Long.MIN_VALUE - while (i < l.size) { - val v = l[i] - if (v is ObjInt) { - if (v.value > maxVal) maxVal = v.value - } else { - hasOnlyInts = false - break + moduleName = "lyng.stdlib", + code = object : ScopeCallable { + override suspend fun call(scp: Scope): Obj { + val l = scp.thisAs().list + if (l.isEmpty()) return ObjNull + if (net.sergeych.lyng.PerfFlags.PRIMITIVE_FASTOPS) { + var i = 0 + var hasOnlyInts = true + var maxVal: Long = Long.MIN_VALUE + while (i < l.size) { + val v = l[i] + if (v is ObjInt) { + if (v.value > maxVal) maxVal = v.value + } else { + hasOnlyInts = false + break + } + i++ + } + if (hasOnlyInts) return ObjInt(maxVal) } - i++ + var res: Obj = l[0] + var i = 1 + while (i < l.size) { + val v = l[i] + if (v.compareTo(scp, res) > 0) res = v + i++ + } + return res } - if (hasOnlyInts) return@addFnDoc ObjInt(maxVal) } - var res: Obj = l[0] - var i = 1 - while (i < l.size) { - val v = l[i] - if (v.compareTo(this, res) > 0) res = v - i++ - } - res - } + ) addFnDoc( name = "indexOf", doc = "Index of the first occurrence of the given element, or -1 if not found.", params = listOf(ParamDoc("element")), returns = type("lyng.Int"), - moduleName = "lyng.stdlib" - ) { - val l = thisAs().list - val needle = args.firstAndOnly() - if (net.sergeych.lyng.PerfFlags.PRIMITIVE_FASTOPS && needle is ObjInt) { - var i = 0 - while (i < l.size) { - val v = l[i] - if (v is ObjInt && v.value == needle.value) return@addFnDoc ObjInt(i.toLong()) - i++ + moduleName = "lyng.stdlib", + code = object : ScopeCallable { + override suspend fun call(scp: Scope): Obj { + val l = scp.thisAs().list + val needle = scp.args.firstAndOnly() + if (net.sergeych.lyng.PerfFlags.PRIMITIVE_FASTOPS && needle is ObjInt) { + var i = 0 + while (i < l.size) { + val v = l[i] + if (v is ObjInt && v.value == needle.value) return ObjInt(i.toLong()) + i++ + } + return ObjInt((-1).toLong()) + } + var i = 0 + while (i < l.size) { + if (l[i].compareTo(scp, needle) == 0) return ObjInt(i.toLong()) + i++ + } + return ObjInt((-1).toLong()) } - return@addFnDoc ObjInt((-1).toLong()) } - var i = 0 - while (i < l.size) { - if (l[i].compareTo(this, needle) == 0) return@addFnDoc ObjInt(i.toLong()) - i++ - } - ObjInt((-1).toLong()) - } + ) } } } diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjMap.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjMap.kt index 7095494..70063b0 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjMap.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjMap.kt @@ -20,6 +20,7 @@ package net.sergeych.lyng.obj import kotlinx.serialization.json.JsonElement import kotlinx.serialization.json.JsonObject import net.sergeych.lyng.Scope +import net.sergeych.lyng.ScopeCallable import net.sergeych.lyng.Statement import net.sergeych.lyng.miniast.* import net.sergeych.lynon.LynonDecoder @@ -78,21 +79,27 @@ class ObjMapEntry(val key: Obj, val value: Obj) : Obj() { doc = "Key component of this map entry.", type = type("lyng.Any"), moduleName = "lyng.stdlib", - getter = { thisAs().key } + getter = object : ScopeCallable { + override suspend fun call(scp: Scope): Obj = scp.thisAs().key + } ) addPropertyDoc( name = "value", doc = "Value component of this map entry.", type = type("lyng.Any"), moduleName = "lyng.stdlib", - getter = { thisAs().value } + getter = object : ScopeCallable { + override suspend fun call(scp: Scope): Obj = scp.thisAs().value + } ) addPropertyDoc( name = "size", doc = "Number of components in this entry (always 2).", type = type("lyng.Int"), moduleName = "lyng.stdlib", - getter = { 2.toObj() } + getter = object : ScopeCallable { + override suspend fun call(scp: Scope): Obj = 2.toObj() + } ) } } @@ -181,9 +188,11 @@ class ObjMap(val map: MutableMap = mutableMapOf()) : Obj() { } override suspend fun toJson(scope: Scope): JsonElement { - return JsonObject( - map.map { it.key.toString(scope).value to it.value.toJson(scope) }.toMap() - ) + val res = mutableMapOf() + for ((k, v) in map) { + res[k.toString(scope).value] = v.toJson(scope) + } + return JsonObject(res) } companion object { @@ -224,94 +233,119 @@ class ObjMap(val map: MutableMap = mutableMapOf()) : Obj() { } }.apply { implementingNames.add("Delegate") - addFn("getValue") { - val self = thisAs() - val key = requiredArg(1) - self.map[key] ?: ObjNull - } - addFn("setValue") { - val self = thisAs() - val key = requiredArg(1) - val value = requiredArg(2) - self.map[key] = value - self - } - addFn("bind") { - val mode = requiredArg(1) - if( mode.ordinal.value > 1) - raiseIllegalArgument("Map can be delegated only to val or var, got ${mode.name.value}") - thisObj - } + addFn("getValue", code = object : ScopeCallable { + override suspend fun call(scp: Scope): Obj { + val self = scp.thisAs() + val key = scp.requiredArg(1) + return self.map[key] ?: ObjNull + } + }) + addFn("setValue", code = object : ScopeCallable { + override suspend fun call(scp: Scope): Obj { + val self = scp.thisAs() + val key = scp.requiredArg(1) + val value = scp.requiredArg(2) + self.map[key] = value + return self + } + }) + addFn("bind", code = object : ScopeCallable { + override suspend fun call(scp: Scope): Obj { + val mode = scp.requiredArg(1) + if (mode.ordinal.value > 1) + scp.raiseIllegalArgument("Map can be delegated only to val or var, got ${mode.name.value}") + return scp.thisObj + } + }) addFnDoc( name = "getOrNull", doc = "Get value by key or return null if the key is absent.", params = listOf(ParamDoc("key")), returns = type("lyng.Any", nullable = true), - moduleName = "lyng.stdlib" - ) { - val key = args.firstAndOnly(pos) - thisAs().map.getOrElse(key) { ObjNull } - } + moduleName = "lyng.stdlib", + code = object : ScopeCallable { + override suspend fun call(scp: Scope): Obj { + val key = scp.args.firstAndOnly(scp.pos) + return scp.thisAs().map.getOrElse(key) { ObjNull } + } + } + ) addFnDoc( name = "getOrPut", doc = "Get value by key or compute, store, and return the default from a lambda.", params = listOf(ParamDoc("key"), ParamDoc("default")), returns = type("lyng.Any"), - moduleName = "lyng.stdlib" - ) { - val key = requiredArg(0) - thisAs().map.getOrPut(key) { - val lambda = requiredArg(1) - lambda.execute(this) + moduleName = "lyng.stdlib", + code = object : ScopeCallable { + override suspend fun call(scp: Scope): Obj { + val key = scp.requiredArg(0) + return scp.thisAs().map.getOrPut(key) { + val lambda = scp.requiredArg(1) + lambda.execute(scp) + } + } } - } + ) addPropertyDoc( name = "size", doc = "Number of entries in the map.", type = type("lyng.Int"), moduleName = "lyng.stdlib", - getter = { (this.thisObj as ObjMap).map.size.toObj() } + getter = object : ScopeCallable { + override suspend fun call(scp: Scope): Obj = (scp.thisObj as ObjMap).map.size.toObj() + } ) addFnDoc( name = "remove", doc = "Remove the entry by key and return the previous value or null if absent.", params = listOf(ParamDoc("key")), returns = type("lyng.Any", nullable = true), - moduleName = "lyng.stdlib" - ) { - thisAs().map.remove(requiredArg(0))?.toObj() ?: ObjNull - } + moduleName = "lyng.stdlib", + code = object : ScopeCallable { + override suspend fun call(scp: Scope): Obj { + return scp.thisAs().map.remove(scp.requiredArg(0))?.toObj() ?: ObjNull + } + } + ) addFnDoc( name = "clear", doc = "Remove all entries from this map. Returns the map.", returns = type("lyng.Map"), - moduleName = "lyng.stdlib" - ) { - thisAs().map.clear() - thisObj - } + moduleName = "lyng.stdlib", + code = object : ScopeCallable { + override suspend fun call(scp: Scope): Obj { + scp.thisAs().map.clear() + return scp.thisObj + } + } + ) addPropertyDoc( name = "keys", doc = "List of keys in this map.", type = TypeGenericDoc(type("lyng.List"), listOf(type("lyng.Any"))), moduleName = "lyng.stdlib", - getter = { thisAs().map.keys.toObj() } + getter = object : ScopeCallable { + override suspend fun call(scp: Scope): Obj = scp.thisAs().map.keys.toObj() + } ) addPropertyDoc( name = "values", doc = "List of values in this map.", type = TypeGenericDoc(type("lyng.List"), listOf(type("lyng.Any"))), moduleName = "lyng.stdlib", - getter = { ObjList(thisAs().map.values.toMutableList()) } + getter = object : ScopeCallable { + override suspend fun call(scp: Scope): Obj = ObjList(scp.thisAs().map.values.toMutableList()) + } ) addFnDoc( name = "iterator", doc = "Iterator over map entries as MapEntry objects.", returns = TypeGenericDoc(type("lyng.Iterator"), listOf(type("lyng.MapEntry"))), - moduleName = "lyng.stdlib" - ) { - ObjKotlinIterator(thisAs().map.entries.iterator()) - } + moduleName = "lyng.stdlib", + code = object : ScopeCallable { + override suspend fun call(scp: Scope): Obj = ObjKotlinIterator(scp.thisAs().map.entries.iterator()) + } + ) } } diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjMutex.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjMutex.kt index 54197c8..aaa4a79 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjMutex.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjMutex.kt @@ -1,5 +1,5 @@ /* - * Copyright 2025 Sergey S. Chernov real.sergeych@gmail.com + * Copyright 2026 Sergey S. Chernov real.sergeych@gmail.com * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -40,12 +40,12 @@ class ObjMutex(val mutex: Mutex): Obj() { params = listOf(ParamDoc("action")), returns = type("lyng.Any"), moduleName = "lyng.stdlib" - ) { - val f = requiredArg(0) + ) { scp -> + val f = scp.requiredArg(0) // Execute user lambda directly in the current scope to preserve the active scope // ancestry across suspension points. The lambda still constructs a ClosureScope // on top of this frame, and parseLambdaExpression sets skipScopeCreation for its body. - thisAs().mutex.withLock { f.execute(this) } + scp.thisAs().mutex.withLock { f.execute(scp) } } } } diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjRange.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjRange.kt index 0c631d0..d9d0130 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjRange.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjRange.kt @@ -18,6 +18,7 @@ package net.sergeych.lyng.obj import net.sergeych.lyng.Scope +import net.sergeych.lyng.ScopeCallable import net.sergeych.lyng.miniast.TypeGenericDoc import net.sergeych.lyng.miniast.addFnDoc import net.sergeych.lyng.miniast.addPropertyDoc @@ -115,17 +116,17 @@ class ObjRange(val start: Obj?, val end: Obj?, val isEndInclusive: Boolean) : Ob start is ObjChar && end is ObjChar } - override suspend fun enumerate(scope: Scope, callback: suspend (Obj) -> Boolean) { + override suspend fun enumerate(scope: Scope, callback: EnumerateCallback) { if (start is ObjInt && end is ObjInt) { val s = start.value val e = end.value if (isEndInclusive) { for (i in s..e) { - if (!callback(ObjInt.of(i))) break + if (!callback.call(ObjInt.of(i))) break } } else { for (i in s..().start ?: ObjNull } + getter = object : ScopeCallable { + override suspend fun call(scp: Scope): Obj = scp.thisAs().start ?: ObjNull + } ) addPropertyDoc( name = "end", doc = "End bound of the range or null if open.", type = type("lyng.Any", nullable = true), moduleName = "lyng.stdlib", - getter = { thisAs().end ?: ObjNull } + getter = object : ScopeCallable { + override suspend fun call(scp: Scope): Obj = scp.thisAs().end ?: ObjNull + } ) addPropertyDoc( name = "isOpen", doc = "Whether the range is open on either side (no start or no end).", type = type("lyng.Bool"), moduleName = "lyng.stdlib", - getter = { thisAs().let { it.start == null || it.end == null }.toObj() } + getter = object : ScopeCallable { + override suspend fun call(scp: Scope): Obj = scp.thisAs().let { it.start == null || it.end == null }.toObj() + } ) addPropertyDoc( name = "isIntRange", doc = "True if both bounds are Int values.", type = type("lyng.Bool"), moduleName = "lyng.stdlib", - getter = { thisAs().isIntRange.toObj() } + getter = object : ScopeCallable { + override suspend fun call(scp: Scope): Obj = scp.thisAs().isIntRange.toObj() + } ) addPropertyDoc( name = "isCharRange", doc = "True if both bounds are Char values.", type = type("lyng.Bool"), moduleName = "lyng.stdlib", - getter = { thisAs().isCharRange.toObj() } + getter = object : ScopeCallable { + override suspend fun call(scp: Scope): Obj = scp.thisAs().isCharRange.toObj() + } ) addPropertyDoc( name = "isEndInclusive", doc = "Whether the end bound is inclusive.", type = type("lyng.Bool"), moduleName = "lyng.stdlib", - getter = { thisAs().isEndInclusive.toObj() } + getter = object : ScopeCallable { + override suspend fun call(scp: Scope): Obj = scp.thisAs().isEndInclusive.toObj() + } ) addFnDoc( name = "iterator", doc = "Iterator over elements in this range (optimized for Int ranges).", returns = TypeGenericDoc(type("lyng.Iterator"), listOf(type("lyng.Any"))), - moduleName = "lyng.stdlib" - ) { - val self = thisAs() - if (net.sergeych.lyng.PerfFlags.RANGE_FAST_ITER) { - val s = self.start - val e = self.end - if (s is ObjInt && e is ObjInt) { - val start = s.value.toInt() - val endExclusive = (if (self.isEndInclusive) e.value.toInt() + 1 else e.value.toInt()) - // Only for ascending simple ranges; fall back otherwise - if (start <= endExclusive) { - return@addFnDoc ObjFastIntRangeIterator(start, endExclusive) + moduleName = "lyng.stdlib", + code = object : ScopeCallable { + override suspend fun call(scp: Scope): Obj { + val self = scp.thisAs() + if (net.sergeych.lyng.PerfFlags.RANGE_FAST_ITER) { + val s = self.start + val e = self.end + if (s is ObjInt && e is ObjInt) { + val start = s.value.toInt() + val endExclusive = (if (self.isEndInclusive) e.value.toInt() + 1 else e.value.toInt()) + // Only for ascending simple ranges; fall back otherwise + if (start <= endExclusive) { + return ObjFastIntRangeIterator(start, endExclusive) + } + } } + return ObjRangeIterator(self).apply { init(scp) } } } - ObjRangeIterator(self).apply { init() } - } + ) } } } diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjRangeIterator.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjRangeIterator.kt index faa5dd5..6f9eb33 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjRangeIterator.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjRangeIterator.kt @@ -1,5 +1,5 @@ /* - * Copyright 2025 Sergey S. Chernov real.sergeych@gmail.com + * Copyright 2026 Sergey S. Chernov real.sergeych@gmail.com * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,6 +19,7 @@ package net.sergeych.lyng.obj import net.sergeych.lyng.PerfFlags import net.sergeych.lyng.Scope +import net.sergeych.lyng.ScopeCallable class ObjRangeIterator(val self: ObjRange) : Obj() { @@ -28,7 +29,7 @@ class ObjRangeIterator(val self: ObjRange) : Obj() { override val objClass: ObjClass get() = type - fun Scope.init() { + fun init(scope: Scope) { val s = self.start val e = self.end if (s is ObjInt && e is ObjInt) { @@ -43,7 +44,7 @@ class ObjRangeIterator(val self: ObjRange) : Obj() { else (e.value.code - s.value.code) } else { - raiseError("not implemented iterator for range of $this") + scope.raiseError("not implemented iterator for range of $this") } } @@ -66,12 +67,14 @@ class ObjRangeIterator(val self: ObjRange) : Obj() { companion object { val type = ObjClass("RangeIterator", ObjIterator).apply { - addFn("hasNext") { - thisAs().hasNext().toObj() - } - addFn("next") { - thisAs().next(this) - } + addFn("hasNext", code = object : ScopeCallable { + override suspend fun call(scp: Scope): Obj = + scp.thisAs().hasNext().toObj() + }) + addFn("next", code = object : ScopeCallable { + override suspend fun call(scp: Scope): Obj = + scp.thisAs().next(scp) + }) } } } @@ -94,8 +97,12 @@ class ObjFastIntRangeIterator(private val start: Int, private val endExclusive: companion object { val type = ObjClass("FastIntRangeIterator", ObjIterator).apply { - addFn("hasNext") { thisAs().hasNext().toObj() } - addFn("next") { thisAs().next(this) } + addFn("hasNext", code = object : ScopeCallable { + override suspend fun call(scp: Scope): Obj = scp.thisAs().hasNext().toObj() + }) + addFn("next", code = object : ScopeCallable { + override suspend fun call(scp: Scope): Obj = scp.thisAs().next(scp) + }) } } } \ No newline at end of file diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjReal.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjReal.kt index e8ce96f..3cb209a 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjReal.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjReal.kt @@ -1,5 +1,5 @@ /* - * Copyright 2025 Sergey S. Chernov real.sergeych@gmail.com + * Copyright 2026 Sergey S. Chernov real.sergeych@gmail.com * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,6 +21,7 @@ import kotlinx.serialization.json.JsonElement import kotlinx.serialization.json.JsonPrimitive import net.sergeych.lyng.Pos import net.sergeych.lyng.Scope +import net.sergeych.lyng.ScopeCallable import net.sergeych.lyng.miniast.addConstDoc import net.sergeych.lyng.miniast.addFnDoc import net.sergeych.lyng.miniast.type @@ -126,9 +127,9 @@ data class ObjReal(val value: Double) : Obj(), Numeric { // roundToInt: number rounded to the nearest integer addConstDoc( name = "roundToInt", - value = statement(Pos.builtIn) { - (it.thisObj as ObjReal).value.roundToLong().toObj() - }, + value = statement(Pos.builtIn, f = object : ScopeCallable { + override suspend fun call(scp: Scope): Obj = (scp.thisObj as ObjReal).value.roundToLong().toObj() + }), doc = "This real number rounded to the nearest integer.", type = type("lyng.Int"), moduleName = "lyng.stdlib" @@ -137,10 +138,11 @@ data class ObjReal(val value: Double) : Obj(), Numeric { name = "toInt", doc = "Truncate this real number toward zero to an integer.", returns = type("lyng.Int"), - moduleName = "lyng.stdlib" - ) { - ObjInt.of(thisAs().value.toLong()) - } + moduleName = "lyng.stdlib", + code = object : ScopeCallable { + override suspend fun call(scp: Scope): Obj = ObjInt.of(scp.thisAs().value.toLong()) + } + ) } } } \ No newline at end of file diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjRef.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjRef.kt index 52153ad..8f93070 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjRef.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjRef.kt @@ -64,9 +64,40 @@ sealed interface ObjRef { } } +interface RecordProvider { + suspend fun getRecord(scope: Scope): ObjRecord +} + +fun interface FieldGetter { + suspend fun call(obj: Obj, scope: Scope): ObjRecord +} + +fun interface FieldSetter { + suspend fun call(obj: Obj, scope: Scope, value: Obj) +} + +fun interface IndexGetter { + suspend fun call(obj: Obj, scope: Scope, index: Obj): Obj +} + +fun interface IndexSetter { + suspend fun call(obj: Obj, scope: Scope, index: Obj, value: Obj) +} + +fun interface MethodInvoker { + suspend fun call(obj: Obj, scope: Scope, args: Arguments): Obj +} + /** Runtime-computed read-only reference backed by a lambda. */ -class ValueFnRef(private val fn: suspend (Scope) -> ObjRecord) : ObjRef { - override suspend fun get(scope: Scope): ObjRecord = fn(scope) +class ValueFnRef(private val provider: RecordProvider) : ObjRef { + + constructor(fn: suspend (Scope) -> ObjRecord) : this(object : RecordProvider { + override suspend fun getRecord(scope: Scope): ObjRecord = fn(scope) + }) + + override suspend fun get(scope: Scope): ObjRecord { + return provider.getRecord(scope) + } } /** Unary operations supported by ObjRef. */ @@ -523,16 +554,16 @@ class FieldRef( ) : ObjRef { // 4-entry PIC for reads/writes (guarded by PerfFlags.FIELD_PIC) // Reads - private var rKey1: Long = 0L; private var rVer1: Int = -1; private var rGetter1: (suspend (Obj, Scope) -> ObjRecord)? = null - private var rKey2: Long = 0L; private var rVer2: Int = -1; private var rGetter2: (suspend (Obj, Scope) -> ObjRecord)? = null - private var rKey3: Long = 0L; private var rVer3: Int = -1; private var rGetter3: (suspend (Obj, Scope) -> ObjRecord)? = null - private var rKey4: Long = 0L; private var rVer4: Int = -1; private var rGetter4: (suspend (Obj, Scope) -> ObjRecord)? = null + private var rKey1: Long = 0L; private var rVer1: Int = -1; private var rGetter1: FieldGetter? = null + private var rKey2: Long = 0L; private var rVer2: Int = -1; private var rGetter2: FieldGetter? = null + private var rKey3: Long = 0L; private var rVer3: Int = -1; private var rGetter3: FieldGetter? = null + private var rKey4: Long = 0L; private var rVer4: Int = -1; private var rGetter4: FieldGetter? = null // Writes - private var wKey1: Long = 0L; private var wVer1: Int = -1; private var wSetter1: (suspend (Obj, Scope, Obj) -> Unit)? = null - private var wKey2: Long = 0L; private var wVer2: Int = -1; private var wSetter2: (suspend (Obj, Scope, Obj) -> Unit)? = null - private var wKey3: Long = 0L; private var wVer3: Int = -1; private var wSetter3: (suspend (Obj, Scope, Obj) -> Unit)? = null - private var wKey4: Long = 0L; private var wVer4: Int = -1; private var wSetter4: (suspend (Obj, Scope, Obj) -> Unit)? = null + private var wKey1: Long = 0L; private var wVer1: Int = -1; private var wSetter1: FieldSetter? = null + private var wKey2: Long = 0L; private var wVer2: Int = -1; private var wSetter2: FieldSetter? = null + private var wKey3: Long = 0L; private var wVer3: Int = -1; private var wSetter3: FieldSetter? = null + private var wKey4: Long = 0L; private var wVer4: Int = -1; private var wSetter4: FieldSetter? = null // Transient per-step cache to optimize read-then-write sequences within the same frame private var tKey: Long = 0L; private var tVer: Int = -1; private var tFrameId: Long = -1L; private var tRecord: ObjRecord? = null @@ -598,7 +629,7 @@ class FieldRef( rGetter1?.let { g -> if (key == rKey1 && ver == rVer1) { if (picCounters) PerfStats.fieldPicHit++ noteReadHit() - val rec0 = g(base, scope) + val rec0 = g.call(base, scope) if (base is ObjClass) { val idx0 = base.classScope?.getSlotIndexOf(name) if (idx0 != null) { tKey = key; tVer = ver; tFrameId = scope.frameId; tRecord = rec0 } else { tRecord = null } @@ -612,7 +643,7 @@ class FieldRef( val tK = rKey2; val tV = rVer2; val tG = rGetter2 rKey2 = rKey1; rVer2 = rVer1; rGetter2 = rGetter1 rKey1 = tK; rVer1 = tV; rGetter1 = tG - val rec0 = g(base, scope) + val rec0 = g.call(base, scope) if (base is ObjClass) { val idx0 = base.classScope?.getSlotIndexOf(name) if (idx0 != null) { tKey = key; tVer = ver; tFrameId = scope.frameId; tRecord = rec0 } else { tRecord = null } @@ -627,7 +658,7 @@ class FieldRef( rKey3 = rKey2; rVer3 = rVer2; rGetter3 = rGetter2 rKey2 = rKey1; rVer2 = rVer1; rGetter2 = rGetter1 rKey1 = tK; rVer1 = tV; rGetter1 = tG - val rec0 = g(base, scope) + val rec0 = g.call(base, scope) if (base is ObjClass) { val idx0 = base.classScope?.getSlotIndexOf(name) if (idx0 != null) { tKey = key; tVer = ver; tFrameId = scope.frameId; tRecord = rec0 } else { tRecord = null } @@ -643,7 +674,7 @@ class FieldRef( rKey3 = rKey2; rVer3 = rVer2; rGetter3 = rGetter2 rKey2 = rKey1; rVer2 = rVer1; rGetter2 = rGetter1 rKey1 = tK; rVer1 = tV; rGetter1 = tG - val rec0 = g(base, scope) + val rec0 = g.call(base, scope) if (base is ObjClass) { val idx0 = base.classScope?.getSlotIndexOf(name) if (idx0 != null) { tKey = key; tVer = ver; tFrameId = scope.frameId; tRecord = rec0 } else { tRecord = null } @@ -660,7 +691,7 @@ class FieldRef( rKey4 = rKey3; rVer4 = rVer3; rGetter4 = rGetter3 rKey3 = rKey2; rVer3 = rVer2; rGetter3 = rGetter2 rKey2 = rKey1; rVer2 = rVer1; rGetter2 = rGetter1 - rKey1 = key; rVer1 = ver; rGetter1 = { _, sc -> sc.raiseError(e.message ?: "no such field: $name") } + rKey1 = key; rVer1 = ver; rGetter1 = FieldGetter { _, sc -> sc.raiseError(e.message ?: "no such field: $name") } throw e } // Install move-to-front with a handle-aware getter; honor PIC size flag @@ -674,7 +705,7 @@ class FieldRef( val clsScope = base.classScope val capturedIdx = clsScope?.getSlotIndexOf(name) if (clsScope != null && capturedIdx != null) { - rKey1 = key; rVer1 = ver; rGetter1 = { obj, sc -> + rKey1 = key; rVer1 = ver; rGetter1 = FieldGetter { obj, sc -> val scope0 = (obj as ObjClass).classScope!! val r0 = scope0.getSlotRecord(capturedIdx) if (!r0.visibility.isPublic) @@ -682,14 +713,14 @@ class FieldRef( r0 } } else { - rKey1 = key; rVer1 = ver; rGetter1 = { obj, sc -> obj.readField(sc, name) } + rKey1 = key; rVer1 = ver; rGetter1 = FieldGetter { obj, sc -> obj.readField(sc, name) } } } is ObjInstance -> { val cls = base.objClass val effectiveKey = cls.publicMemberResolution[name] if (effectiveKey != null) { - rKey1 = key; rVer1 = ver; rGetter1 = { obj, sc -> + rKey1 = key; rVer1 = ver; rGetter1 = FieldGetter { obj, sc -> if (obj is ObjInstance && obj.objClass === cls) { val rec = obj.instanceScope.objects[effectiveKey] if (rec != null && rec.type != ObjRecord.Type.Delegated) rec @@ -697,12 +728,12 @@ class FieldRef( } else obj.readField(sc, name) } } else { - rKey1 = key; rVer1 = ver; rGetter1 = { obj, sc -> obj.readField(sc, name) } + rKey1 = key; rVer1 = ver; rGetter1 = FieldGetter { obj, sc -> obj.readField(sc, name) } } } else -> { // For instances and other types, fall back to name-based lookup per access (slot index may differ per instance) - rKey1 = key; rVer1 = ver; rGetter1 = { obj, sc -> obj.readField(sc, name) } + rKey1 = key; rVer1 = ver; rGetter1 = FieldGetter { obj, sc -> obj.readField(sc, name) } } } return rec @@ -745,7 +776,7 @@ class FieldRef( wSetter1?.let { s -> if (key == wKey1 && ver == wVer1) { if (picCounters) PerfStats.fieldPicSetHit++ noteWriteHit() - return s(base, scope, newValue) + return s.call(base, scope, newValue) } } wSetter2?.let { s -> if (key == wKey2 && ver == wVer2) { if (picCounters) PerfStats.fieldPicSetHit++ @@ -754,7 +785,7 @@ class FieldRef( val tK = wKey2; val tV = wVer2; val tS = wSetter2 wKey2 = wKey1; wVer2 = wVer1; wSetter2 = wSetter1 wKey1 = tK; wVer1 = tV; wSetter1 = tS - return s(base, scope, newValue) + return s.call(base, scope, newValue) } } if (size4WritesEnabled()) wSetter3?.let { s -> if (key == wKey3 && ver == wVer3) { if (picCounters) PerfStats.fieldPicSetHit++ @@ -764,7 +795,7 @@ class FieldRef( wKey3 = wKey2; wVer3 = wVer2; wSetter3 = wSetter2 wKey2 = wKey1; wVer2 = wVer1; wSetter2 = wSetter1 wKey1 = tK; wVer1 = tV; wSetter1 = tS - return s(base, scope, newValue) + return s.call(base, scope, newValue) } } if (size4WritesEnabled()) wSetter4?.let { s -> if (key == wKey4 && ver == wVer4) { if (picCounters) PerfStats.fieldPicSetHit++ @@ -775,7 +806,7 @@ class FieldRef( wKey3 = wKey2; wVer3 = wVer2; wSetter3 = wSetter2 wKey2 = wKey1; wVer2 = wVer1; wSetter2 = wSetter1 wKey1 = tK; wVer1 = tV; wSetter1 = tS - return s(base, scope, newValue) + return s.call(base, scope, newValue) } } // Slow path if (picCounters) PerfStats.fieldPicSetMiss++ @@ -792,7 +823,7 @@ class FieldRef( val clsScope = base.classScope val capturedIdx = clsScope?.getSlotIndexOf(name) if (clsScope != null && capturedIdx != null) { - wKey1 = key; wVer1 = ver; wSetter1 = { obj, sc, v -> + wKey1 = key; wVer1 = ver; wSetter1 = FieldSetter { obj, sc, v -> val scope0 = (obj as ObjClass).classScope!! val r0 = scope0.getSlotRecord(capturedIdx) if (!r0.isMutable) @@ -800,14 +831,14 @@ class FieldRef( if (r0.value.assign(sc, v) == null) r0.value = v } } else { - wKey1 = key; wVer1 = ver; wSetter1 = { obj, sc, v -> obj.writeField(sc, name, v) } + wKey1 = key; wVer1 = ver; wSetter1 = FieldSetter { obj, sc, v -> obj.writeField(sc, name, v) } } } is ObjInstance -> { val cls = base.objClass val effectiveKey = cls.publicMemberResolution[name] if (effectiveKey != null) { - wKey1 = key; wVer1 = ver; wSetter1 = { obj, sc, nv -> + wKey1 = key; wVer1 = ver; wSetter1 = FieldSetter { obj, sc, nv -> if (obj is ObjInstance && obj.objClass === cls) { val rec = obj.instanceScope.objects[effectiveKey] if (rec != null && rec.effectiveWriteVisibility == Visibility.Public && rec.isMutable && rec.type == ObjRecord.Type.Field) { @@ -816,12 +847,12 @@ class FieldRef( } else obj.writeField(sc, name, nv) } } else { - wKey1 = key; wVer1 = ver; wSetter1 = { obj, sc, v -> obj.writeField(sc, name, v) } + wKey1 = key; wVer1 = ver; wSetter1 = FieldSetter { obj, sc, v -> obj.writeField(sc, name, v) } } } else -> { // For instances and other types, fall back to generic write (instance slot indices may differ per instance) - wKey1 = key; wVer1 = ver; wSetter1 = { obj, sc, v -> obj.writeField(sc, name, v) } + wKey1 = key; wVer1 = ver; wSetter1 = FieldSetter { obj, sc, v -> obj.writeField(sc, name, v) } } } return @@ -853,14 +884,14 @@ class FieldRef( if (key != 0L) { rGetter1?.let { g -> if (key == rKey1 && ver == rVer1) { if (picCounters) PerfStats.fieldPicHit++ - return g(base, scope).value + return g.call(base, scope).value } } rGetter2?.let { g -> if (key == rKey2 && ver == rVer2) { if (picCounters) PerfStats.fieldPicHit++ val tK = rKey2; val tV = rVer2; val tG = rGetter2 rKey2 = rKey1; rVer2 = rVer1; rGetter2 = rGetter1 rKey1 = tK; rVer1 = tV; rGetter1 = tG - return g(base, scope).value + return g.call(base, scope).value } } if (size4ReadsEnabled()) rGetter3?.let { g -> if (key == rKey3 && ver == rVer3) { if (picCounters) PerfStats.fieldPicHit++ @@ -868,7 +899,7 @@ class FieldRef( rKey3 = rKey2; rVer3 = rVer2; rGetter3 = rGetter2 rKey2 = rKey1; rVer2 = rVer1; rGetter2 = rGetter1 rKey1 = tK; rVer1 = tV; rGetter1 = tG - return g(base, scope).value + return g.call(base, scope).value } } if (size4ReadsEnabled()) rGetter4?.let { g -> if (key == rKey4 && ver == rVer4) { if (picCounters) PerfStats.fieldPicHit++ @@ -877,12 +908,12 @@ class FieldRef( rKey3 = rKey2; rVer3 = rVer2; rGetter3 = rGetter2 rKey2 = rKey1; rVer2 = rVer1; rGetter2 = rGetter1 rKey1 = tK; rVer1 = tV; rGetter1 = tG - return g(base, scope).value + return g.call(base, scope).value } } if (picCounters) PerfStats.fieldPicMiss++ val rec = base.readField(scope, name) // install primary generic getter for this shape - rKey1 = key; rVer1 = ver; rGetter1 = { obj, sc -> obj.readField(sc, name) } + rKey1 = key; rVer1 = ver; rGetter1 = FieldGetter { obj, sc -> obj.readField(sc, name) } return rec.value } } @@ -899,16 +930,16 @@ class IndexRef( private val isOptional: Boolean, ) : ObjRef { // Tiny 4-entry PIC for index reads (guarded implicitly by RVAL_FASTPATH); move-to-front on hits - private var rKey1: Long = 0L; private var rVer1: Int = -1; private var rGetter1: (suspend (Obj, Scope, Obj) -> Obj)? = null - private var rKey2: Long = 0L; private var rVer2: Int = -1; private var rGetter2: (suspend (Obj, Scope, Obj) -> Obj)? = null - private var rKey3: Long = 0L; private var rVer3: Int = -1; private var rGetter3: (suspend (Obj, Scope, Obj) -> Obj)? = null - private var rKey4: Long = 0L; private var rVer4: Int = -1; private var rGetter4: (suspend (Obj, Scope, Obj) -> Obj)? = null + private var rKey1: Long = 0L; private var rVer1: Int = -1; private var rGetter1: IndexGetter? = null + private var rKey2: Long = 0L; private var rVer2: Int = -1; private var rGetter2: IndexGetter? = null + private var rKey3: Long = 0L; private var rVer3: Int = -1; private var rGetter3: IndexGetter? = null + private var rKey4: Long = 0L; private var rVer4: Int = -1; private var rGetter4: IndexGetter? = null // Tiny 4-entry PIC for index writes - private var wKey1: Long = 0L; private var wVer1: Int = -1; private var wSetter1: (suspend (Obj, Scope, Obj, Obj) -> Unit)? = null - private var wKey2: Long = 0L; private var wVer2: Int = -1; private var wSetter2: (suspend (Obj, Scope, Obj, Obj) -> Unit)? = null - private var wKey3: Long = 0L; private var wVer3: Int = -1; private var wSetter3: (suspend (Obj, Scope, Obj, Obj) -> Unit)? = null - private var wKey4: Long = 0L; private var wVer4: Int = -1; private var wSetter4: (suspend (Obj, Scope, Obj, Obj) -> Unit)? = null + private var wKey1: Long = 0L; private var wVer1: Int = -1; private var wSetter1: IndexSetter? = null + private var wKey2: Long = 0L; private var wVer2: Int = -1; private var wSetter2: IndexSetter? = null + private var wKey3: Long = 0L; private var wVer3: Int = -1; private var wSetter3: IndexSetter? = null + private var wKey4: Long = 0L; private var wVer4: Int = -1; private var wSetter4: IndexSetter? = null private fun receiverKeyAndVersion(obj: Obj): Pair = when (obj) { is ObjInstance -> obj.objClass.classId to obj.objClass.layoutVersion @@ -949,14 +980,14 @@ class IndexRef( if (key != 0L) { rGetter1?.let { g -> if (key == rKey1 && ver == rVer1) { if (picCounters) PerfStats.indexPicHit++ - return g(base, scope, idx).asMutable + return g.call(base, scope, idx).asMutable } } rGetter2?.let { g -> if (key == rKey2 && ver == rVer2) { if (picCounters) PerfStats.indexPicHit++ val tk = rKey2; val tv = rVer2; val tg = rGetter2 rKey2 = rKey1; rVer2 = rVer1; rGetter2 = rGetter1 rKey1 = tk; rVer1 = tv; rGetter1 = tg - return g(base, scope, idx).asMutable + return g.call(base, scope, idx).asMutable } } if (PerfFlags.INDEX_PIC_SIZE_4) rGetter3?.let { g -> if (key == rKey3 && ver == rVer3) { if (picCounters) PerfStats.indexPicHit++ @@ -964,7 +995,7 @@ class IndexRef( rKey3 = rKey2; rVer3 = rVer2; rGetter3 = rGetter2 rKey2 = rKey1; rVer2 = rVer1; rGetter2 = rGetter1 rKey1 = tk; rVer1 = tv; rGetter1 = tg - return g(base, scope, idx).asMutable + return g.call(base, scope, idx).asMutable } } if (PerfFlags.INDEX_PIC_SIZE_4) rGetter4?.let { g -> if (key == rKey4 && ver == rVer4) { if (picCounters) PerfStats.indexPicHit++ @@ -973,7 +1004,7 @@ class IndexRef( rKey3 = rKey2; rVer3 = rVer2; rGetter3 = rGetter2 rKey2 = rKey1; rVer2 = rVer1; rGetter2 = rGetter1 rKey1 = tk; rVer1 = tv; rGetter1 = tg - return g(base, scope, idx).asMutable + return g.call(base, scope, idx).asMutable } } // Miss: resolve and install generic handler if (picCounters) PerfStats.indexPicMiss++ @@ -983,7 +1014,7 @@ class IndexRef( rKey3 = rKey2; rVer3 = rVer2; rGetter3 = rGetter2 } rKey2 = rKey1; rVer2 = rVer1; rGetter2 = rGetter1 - rKey1 = key; rVer1 = ver; rGetter1 = { obj, sc, ix -> obj.getAt(sc, ix) } + rKey1 = key; rVer1 = ver; rGetter1 = IndexGetter { obj, sc, ix -> obj.getAt(sc, ix) } return v.asMutable } } @@ -1023,14 +1054,14 @@ class IndexRef( if (key != 0L) { rGetter1?.let { g -> if (key == rKey1 && ver == rVer1) { if (picCounters) PerfStats.indexPicHit++ - return g(base, scope, idx) + return g.call(base, scope, idx) } } rGetter2?.let { g -> if (key == rKey2 && ver == rVer2) { if (picCounters) PerfStats.indexPicHit++ val tk = rKey2; val tv = rVer2; val tg = rGetter2 rKey2 = rKey1; rVer2 = rVer1; rGetter2 = rGetter1 rKey1 = tk; rVer1 = tv; rGetter1 = tg - return g(base, scope, idx) + return g.call(base, scope, idx) } } if (PerfFlags.INDEX_PIC_SIZE_4) rGetter3?.let { g -> if (key == rKey3 && ver == rVer3) { if (picCounters) PerfStats.indexPicHit++ @@ -1038,7 +1069,7 @@ class IndexRef( rKey3 = rKey2; rVer3 = rVer2; rGetter3 = rGetter2 rKey2 = rKey1; rVer2 = rVer1; rGetter2 = rGetter1 rKey1 = tk; rVer1 = tv; rGetter1 = tg - return g(base, scope, idx) + return g.call(base, scope, idx) } } if (PerfFlags.INDEX_PIC_SIZE_4) rGetter4?.let { g -> if (key == rKey4 && ver == rVer4) { if (picCounters) PerfStats.indexPicHit++ @@ -1047,7 +1078,7 @@ class IndexRef( rKey3 = rKey2; rVer3 = rVer2; rGetter3 = rGetter2 rKey2 = rKey1; rVer2 = rVer1; rGetter2 = rGetter1 rKey1 = tk; rVer1 = tv; rGetter1 = tg - return g(base, scope, idx) + return g.call(base, scope, idx) } } if (picCounters) PerfStats.indexPicMiss++ val v = base.getAt(scope, idx) @@ -1056,7 +1087,7 @@ class IndexRef( rKey3 = rKey2; rVer3 = rVer2; rGetter3 = rGetter2 } rKey2 = rKey1; rVer2 = rVer1; rGetter2 = rGetter1 - rKey1 = key; rVer1 = ver; rGetter1 = { obj, sc, ix -> obj.getAt(sc, ix) } + rKey1 = key; rVer1 = ver; rGetter1 = IndexGetter { obj, sc, ix -> obj.getAt(sc, ix) } return v } } @@ -1093,19 +1124,19 @@ class IndexRef( else -> { key = 0L; ver = -1 } } if (key != 0L) { - wSetter1?.let { s -> if (key == wKey1 && ver == wVer1) { s(base, scope, idx, newValue); return } } + wSetter1?.let { s -> if (key == wKey1 && ver == wVer1) { s.call(base, scope, idx, newValue); return } } wSetter2?.let { s -> if (key == wKey2 && ver == wVer2) { val tk = wKey2; val tv = wVer2; val ts = wSetter2 wKey2 = wKey1; wVer2 = wVer1; wSetter2 = wSetter1 wKey1 = tk; wVer1 = tv; wSetter1 = ts - s(base, scope, idx, newValue); return + s.call(base, scope, idx, newValue); return } } if (PerfFlags.INDEX_PIC_SIZE_4) wSetter3?.let { s -> if (key == wKey3 && ver == wVer3) { val tk = wKey3; val tv = wVer3; val ts = wSetter3 wKey3 = wKey2; wVer3 = wVer2; wSetter3 = wSetter2 wKey2 = wKey1; wVer2 = wVer1; wSetter2 = wSetter1 wKey1 = tk; wVer1 = tv; wSetter1 = ts - s(base, scope, idx, newValue); return + s.call(base, scope, idx, newValue); return } } if (PerfFlags.INDEX_PIC_SIZE_4) wSetter4?.let { s -> if (key == wKey4 && ver == wVer4) { val tk = wKey4; val tv = wVer4; val ts = wSetter4 @@ -1113,7 +1144,7 @@ class IndexRef( wKey3 = wKey2; wVer3 = wVer2; wSetter3 = wSetter2 wKey2 = wKey1; wVer2 = wVer1; wSetter2 = wSetter1 wKey1 = tk; wVer1 = tv; wSetter1 = ts - s(base, scope, idx, newValue); return + s.call(base, scope, idx, newValue); return } } // Miss: perform write and install generic handler base.putAt(scope, idx, newValue) @@ -1122,7 +1153,7 @@ class IndexRef( wKey3 = wKey2; wVer3 = wVer2; wSetter3 = wSetter2 } wKey2 = wKey1; wVer2 = wVer1; wSetter2 = wSetter1 - wKey1 = key; wVer1 = ver; wSetter1 = { obj, sc, ix, v -> obj.putAt(sc, ix, v) } + wKey1 = key; wVer1 = ver; wSetter1 = IndexSetter { obj, sc, ix, v -> obj.putAt(sc, ix, v) } return } } @@ -1173,10 +1204,10 @@ class MethodCallRef( private val isOptional: Boolean, ) : ObjRef { // 4-entry PIC for method invocations (guarded by PerfFlags.METHOD_PIC) - private var mKey1: Long = 0L; private var mVer1: Int = -1; private var mInvoker1: (suspend (Obj, Scope, Arguments) -> Obj)? = null - private var mKey2: Long = 0L; private var mVer2: Int = -1; private var mInvoker2: (suspend (Obj, Scope, Arguments) -> Obj)? = null - private var mKey3: Long = 0L; private var mVer3: Int = -1; private var mInvoker3: (suspend (Obj, Scope, Arguments) -> Obj)? = null - private var mKey4: Long = 0L; private var mVer4: Int = -1; private var mInvoker4: (suspend (Obj, Scope, Arguments) -> Obj)? = null + private var mKey1: Long = 0L; private var mVer1: Int = -1; private var mInvoker1: MethodInvoker? = null + private var mKey2: Long = 0L; private var mVer2: Int = -1; private var mInvoker2: MethodInvoker? = null + private var mKey3: Long = 0L; private var mVer3: Int = -1; private var mInvoker3: MethodInvoker? = null + private var mKey4: Long = 0L; private var mVer4: Int = -1; private var mInvoker4: MethodInvoker? = null // Adaptive PIC (2→4) for methods private var mAccesses: Int = 0; private var mMisses: Int = 0; private var mPromotedTo4: Boolean = false @@ -1274,7 +1305,7 @@ class MethodCallRef( if (key == mKey1 && ver == mVer1) { if (picCounters) PerfStats.methodPicHit++ noteMethodHit() - return inv(base, scope, callArgs) + return inv.call(base, scope, callArgs) } } mInvoker2?.let { inv -> @@ -1285,7 +1316,7 @@ class MethodCallRef( val tK = mKey2; val tV = mVer2; val tI = mInvoker2 mKey2 = mKey1; mVer2 = mVer1; mInvoker2 = mInvoker1 mKey1 = tK; mVer1 = tV; mInvoker1 = tI - return inv(base, scope, callArgs) + return inv.call(base, scope, callArgs) } } if (size4MethodsEnabled()) mInvoker3?.let { inv -> @@ -1297,7 +1328,7 @@ class MethodCallRef( mKey3 = mKey2; mVer3 = mVer2; mInvoker3 = mInvoker2 mKey2 = mKey1; mVer2 = mVer1; mInvoker2 = mInvoker1 mKey1 = tK; mVer1 = tV; mInvoker1 = tI - return inv(base, scope, callArgs) + return inv.call(base, scope, callArgs) } } if (size4MethodsEnabled()) mInvoker4?.let { inv -> @@ -1310,7 +1341,7 @@ class MethodCallRef( mKey3 = mKey2; mVer3 = mVer2; mInvoker3 = mInvoker2 mKey2 = mKey1; mVer2 = mVer1; mInvoker2 = mInvoker1 mKey1 = tK; mVer1 = tV; mInvoker1 = tI - return inv(base, scope, callArgs) + return inv.call(base, scope, callArgs) } } // Slow path @@ -1323,7 +1354,7 @@ class MethodCallRef( mKey4 = mKey3; mVer4 = mVer3; mInvoker4 = mInvoker3 mKey3 = mKey2; mVer3 = mVer2; mInvoker3 = mInvoker2 mKey2 = mKey1; mVer2 = mVer1; mInvoker2 = mInvoker1 - mKey1 = key; mVer1 = ver; mInvoker1 = { _, sc, _ -> sc.raiseError(e.message ?: "method not found: $name") } + mKey1 = key; mVer1 = ver; mInvoker1 = MethodInvoker { _, sc, _ -> sc.raiseError(e.message ?: "method not found: $name") } throw e } // Install move-to-front with a handle-aware invoker; honor PIC size flag @@ -1361,15 +1392,15 @@ class MethodCallRef( val visibility = hierarchyMember.visibility val callable = hierarchyMember.value val decl = hierarchyMember.declaringClass ?: base.objClass - mKey1 = key; mVer1 = ver; mInvoker1 = { obj, sc, a -> + mKey1 = key; mVer1 = ver; mInvoker1 = MethodInvoker { obj, sc, a -> val inst = obj as ObjInstance if (!visibility.isPublic && !canAccessMember(visibility, decl, sc.currentClassCtx, name)) - sc.raiseError(ObjIllegalAccessException(sc, "can't invoke non-public method $name")) - callable.invoke(inst.instanceScope, inst, a) + sc.raiseError(ObjIllegalAccessException(sc, "can't invokeCallable non-public method $name")) + callable.invokeCallable(inst.instanceScope, inst, a) } } else { // Fallback to name-based lookup per call (handles extensions and root members) - mKey1 = key; mVer1 = ver; mInvoker1 = { obj, sc, a -> obj.invokeInstanceMethod(sc, name, a) } + mKey1 = key; mVer1 = ver; mInvoker1 = MethodInvoker { obj, sc, a -> obj.invokeInstanceMethod(sc, name, a) } } } is ObjClass -> { @@ -1377,13 +1408,13 @@ class MethodCallRef( val rec = clsScope?.get(name) if (rec != null) { val callable = rec.value - mKey1 = key; mVer1 = ver; mInvoker1 = { obj, sc, a -> callable.invoke(sc, obj, a) } + mKey1 = key; mVer1 = ver; mInvoker1 = MethodInvoker { obj, sc, a -> callable.invokeCallable(sc, obj, a) } } else { - mKey1 = key; mVer1 = ver; mInvoker1 = { obj, sc, a -> obj.invokeInstanceMethod(sc, name, a) } + mKey1 = key; mVer1 = ver; mInvoker1 = MethodInvoker { obj, sc, a -> obj.invokeInstanceMethod(sc, name, a) } } } else -> { - mKey1 = key; mVer1 = ver; mInvoker1 = { obj, sc, a -> obj.invokeInstanceMethod(sc, name, a) } + mKey1 = key; mVer1 = ver; mInvoker1 = MethodInvoker { obj, sc, a -> obj.invokeInstanceMethod(sc, name, a) } } } return result diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjRegex.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjRegex.kt index c51f129..fe6002b 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjRegex.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjRegex.kt @@ -20,6 +20,7 @@ package net.sergeych.lyng.obj import net.sergeych.lyng.PerfFlags import net.sergeych.lyng.RegexCache import net.sergeych.lyng.Scope +import net.sergeych.lyng.ScopeCallable import net.sergeych.lyng.miniast.* class ObjRegex(val regex: Regex) : Obj() { @@ -49,29 +50,36 @@ class ObjRegex(val regex: Regex) : Obj() { doc = "Whether the entire string matches this regular expression.", params = listOf(ParamDoc("text", type("lyng.String"))), returns = type("lyng.Bool"), - moduleName = "lyng.stdlib" - ) { - ObjBool(args.firstAndOnly().toString().matches(thisAs().regex)) - } + moduleName = "lyng.stdlib", + code = object : ScopeCallable { + override suspend fun call(scp: Scope): Obj = + ObjBool(scp.args.firstAndOnly().toString().matches(scp.thisAs().regex)) + } + ) addFnDoc( name = "find", doc = "Find the first match in the given string.", params = listOf(ParamDoc("text", type("lyng.String"))), returns = type("lyng.RegexMatch", nullable = true), - moduleName = "lyng.stdlib" - ) { - thisAs().find(requireOnlyArg()) - } + moduleName = "lyng.stdlib", + code = object : ScopeCallable { + override suspend fun call(scp: Scope): Obj = + scp.thisAs().find(scp.requireOnlyArg()) + } + ) addFnDoc( name = "findAll", doc = "Find all matches in the given string.", params = listOf(ParamDoc("text", type("lyng.String"))), returns = TypeGenericDoc(type("lyng.List"), listOf(type("lyng.RegexMatch"))), - moduleName = "lyng.stdlib" - ) { - val s = requireOnlyArg().value - ObjList(thisAs().regex.findAll(s).map { ObjRegexMatch(it) }.toMutableList()) - } + moduleName = "lyng.stdlib", + code = object : ScopeCallable { + override suspend fun call(scp: Scope): Obj { + val s = scp.requireOnlyArg().value + return ObjList(scp.thisAs().regex.findAll(s).map { ObjRegexMatch(it) }.toMutableList()) + } + } + ) } } } @@ -125,21 +133,27 @@ class ObjRegexMatch(val match: MatchResult) : Obj() { doc = "List of captured groups with index 0 as the whole match.", type = TypeGenericDoc(type("lyng.List"), listOf(type("lyng.String"))), moduleName = "lyng.stdlib", - getter = { thisAs().objGroups } + getter = object : ScopeCallable { + override suspend fun call(scp: Scope): Obj = scp.thisAs().objGroups + } ) addPropertyDoc( name = "value", doc = "The matched substring.", type = type("lyng.String"), moduleName = "lyng.stdlib", - getter = { thisAs().objValue } + getter = object : ScopeCallable { + override suspend fun call(scp: Scope): Obj = scp.thisAs().objValue + } ) addPropertyDoc( name = "range", doc = "Range of the match in the input (end-exclusive).", type = type("lyng.Range"), moduleName = "lyng.stdlib", - getter = { thisAs().objRange } + getter = object : ScopeCallable { + override suspend fun call(scp: Scope): Obj = scp.thisAs().objRange + } ) } } diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjRingBuffer.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjRingBuffer.kt index 7099723..e5e46b9 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjRingBuffer.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjRingBuffer.kt @@ -18,6 +18,7 @@ package net.sergeych.lyng.obj import net.sergeych.lyng.Scope +import net.sergeych.lyng.ScopeCallable import net.sergeych.lyng.miniast.* class RingBuffer(val maxSize: Int) : Iterable { @@ -96,39 +97,55 @@ class ObjRingBuffer(val capacity: Int) : Obj() { doc = "Maximum number of elements the buffer can hold.", type = type("lyng.Int"), moduleName = "lyng.stdlib", - getter = { thisAs().capacity.toObj() } + getter = object : ScopeCallable { + override suspend fun call(scp: Scope): Obj = scp.thisAs().capacity.toObj() + } ) addPropertyDoc( name = "size", doc = "Current number of elements in the buffer.", type = type("lyng.Int"), moduleName = "lyng.stdlib", - getter = { thisAs().buffer.size.toObj() } + getter = object : ScopeCallable { + override suspend fun call(scp: Scope): Obj = scp.thisAs().buffer.size.toObj() + } ) addFnDoc( name = "iterator", doc = "Iterator over elements in insertion order (oldest to newest).", returns = TypeGenericDoc(type("lyng.Iterator"), listOf(type("lyng.Any"))), - moduleName = "lyng.stdlib" - ) { - val buffer = thisAs().buffer - ObjKotlinObjIterator(buffer.iterator()) - } + moduleName = "lyng.stdlib", + code = object : ScopeCallable { + override suspend fun call(scp: Scope): Obj { + val buffer = scp.thisAs().buffer + return ObjKotlinObjIterator(buffer.iterator()) + } + } + ) addFnDoc( name = "add", doc = "Append an element; if full, the oldest element is dropped.", params = listOf(ParamDoc("value", type("lyng.Any"))), returns = type("lyng.Void"), - moduleName = "lyng.stdlib" - ) { thisAs().apply { buffer.add(requireOnlyArg()) } } + moduleName = "lyng.stdlib", + code = object : ScopeCallable { + override suspend fun call(scp: Scope): Obj { + val self = scp.thisAs() + self.buffer.add(scp.requireOnlyArg()) + return ObjVoid + } + } + ) addPropertyDoc( name = "first", doc = "Return the oldest element in the buffer.", type = type("lyng.Any"), moduleName = "lyng.stdlib", - getter = { - val buffer = (this.thisObj as ObjRingBuffer).buffer - if (buffer.size == 0) ObjNull else buffer.first() + getter = object : ScopeCallable { + override suspend fun call(scp: Scope): Obj { + val buffer = (scp.thisObj as ObjRingBuffer).buffer + return if (buffer.size == 0) ObjNull else buffer.iterator().next() + } } ) } diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjSet.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjSet.kt index 11b120c..5062382 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjSet.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjSet.kt @@ -18,6 +18,7 @@ package net.sergeych.lyng.obj import net.sergeych.lyng.Scope +import net.sergeych.lyng.ScopeCallable import net.sergeych.lyng.miniast.ParamDoc import net.sergeych.lyng.miniast.TypeGenericDoc import net.sergeych.lyng.miniast.addFnDoc @@ -46,9 +47,9 @@ class ObjSet(val set: MutableSet = mutableSetOf()) : Obj() { return set.contains(other) } - override suspend fun enumerate(scope: Scope, callback: suspend (Obj) -> Boolean) { + override suspend fun enumerate(scope: Scope, callback: EnumerateCallback) { for (item in set) { - if (!callback(item)) break + if (!callback.call(item)) break } } @@ -164,56 +165,64 @@ class ObjSet(val set: MutableSet = mutableSetOf()) : Obj() { name = "size", doc = "Number of elements in this set.", returns = type("lyng.Int"), - moduleName = "lyng.stdlib" - ) { - thisAs().set.size.toObj() - } + moduleName = "lyng.stdlib", + code = object : ScopeCallable { + override suspend fun call(scp: Scope): Obj = scp.thisAs().set.size.toObj() + } + ) addFnDoc( name = "intersect", doc = "Intersection with another set. Returns a new set.", params = listOf(ParamDoc("other", type("lyng.Set"))), returns = type("lyng.Set"), - moduleName = "lyng.stdlib" - ) { - thisAs().mul(this, args.firstAndOnly()) - } + moduleName = "lyng.stdlib", + code = object : ScopeCallable { + override suspend fun call(scp: Scope): Obj = scp.thisAs().mul(scp, scp.args.firstAndOnly()) + } + ) addFnDoc( name = "iterator", doc = "Iterator over elements of this set.", returns = TypeGenericDoc(type("lyng.Iterator"), listOf(type("lyng.Any"))), - moduleName = "lyng.stdlib" - ) { - thisAs().set.iterator().toObj() - } + moduleName = "lyng.stdlib", + code = object : ScopeCallable { + override suspend fun call(scp: Scope): Obj = ObjKotlinIterator(scp.thisAs().set.iterator()) + } + ) addFnDoc( name = "union", doc = "Union with another set or iterable. Returns a new set.", params = listOf(ParamDoc("other")), returns = type("lyng.Set"), - moduleName = "lyng.stdlib" - ) { - thisAs().plus(this, args.firstAndOnly()) - } + moduleName = "lyng.stdlib", + code = object : ScopeCallable { + override suspend fun call(scp: Scope): Obj = scp.thisAs().plus(scp, scp.args.firstAndOnly()) + } + ) addFnDoc( name = "subtract", doc = "Subtract another set or iterable from this set. Returns a new set.", params = listOf(ParamDoc("other")), returns = type("lyng.Set"), - moduleName = "lyng.stdlib" - ) { - thisAs().minus(this, args.firstAndOnly()) - } + moduleName = "lyng.stdlib", + code = object : ScopeCallable { + override suspend fun call(scp: Scope): Obj = scp.thisAs().minus(scp, scp.args.firstAndOnly()) + } + ) addFnDoc( name = "remove", doc = "Remove one or more elements. Returns true if the set changed.", returns = type("lyng.Bool"), - moduleName = "lyng.stdlib" - ) { - val set = thisAs().set - val n = set.size - for( x in args.list ) set -= x - if( n == set.size ) ObjFalse else ObjTrue - } + moduleName = "lyng.stdlib", + code = object : ScopeCallable { + override suspend fun call(scp: Scope): Obj { + val set = scp.thisAs().set + val n = set.size + for( x in scp.args.list ) set -= x + return if( n == set.size ) ObjFalse else ObjTrue + } + } + ) } } } \ No newline at end of file diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjString.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjString.kt index 25ae4d6..ee909dc 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjString.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjString.kt @@ -24,6 +24,7 @@ import kotlinx.serialization.json.JsonPrimitive import net.sergeych.lyng.PerfFlags import net.sergeych.lyng.RegexCache import net.sergeych.lyng.Scope +import net.sergeych.lyng.ScopeCallable import net.sergeych.lyng.miniast.* import net.sergeych.lynon.LynonDecoder import net.sergeych.lynon.LynonEncoder @@ -139,205 +140,274 @@ data class ObjString(val value: String) : Obj() { name = "iterator", doc = "Iterator over characters of this string.", returns = TypeGenericDoc(type("lyng.Iterator"), listOf(type("lyng.Char"))), - moduleName = "lyng.stdlib" - ) { ObjKotlinIterator(thisAs().value.iterator()) } + moduleName = "lyng.stdlib", + code = object : ScopeCallable { + override suspend fun call(scp: Scope): Obj = ObjKotlinIterator(scp.thisAs().value.iterator()) + } + ) addFnDoc( name = "toInt", doc = "Parse this string as an integer or throw if it is not a valid integer.", returns = type("lyng.Int"), - moduleName = "lyng.stdlib" - ) { - ObjInt.of( - thisAs().value.toLongOrNull() - ?: raiseIllegalArgument("can't convert to int: $thisObj") - ) - } + moduleName = "lyng.stdlib", + code = object : ScopeCallable { + override suspend fun call(scp: Scope): Obj { + return ObjInt.of( + scp.thisAs().value.toLongOrNull() + ?: scp.raiseIllegalArgument("can't convert to int: ${scp.thisObj}") + ) + } + } + ) addFnDoc( name = "startsWith", doc = "Whether this string starts with the given prefix.", params = listOf(ParamDoc("prefix", type("lyng.String"))), returns = type("lyng.Bool"), - moduleName = "lyng.stdlib" - ) { - ObjBool(thisAs().value.startsWith(requiredArg(0).value)) - } + moduleName = "lyng.stdlib", + code = object : ScopeCallable { + override suspend fun call(scp: Scope): Obj { + return ObjBool(scp.thisAs().value.startsWith(scp.requiredArg(0).value)) + } + } + ) addFnDoc( name = "endsWith", doc = "Whether this string ends with the given suffix.", params = listOf(ParamDoc("suffix", type("lyng.String"))), returns = type("lyng.Bool"), - moduleName = "lyng.stdlib" - ) { - ObjBool(thisAs().value.endsWith(requiredArg(0).value)) - } + moduleName = "lyng.stdlib", + code = object : ScopeCallable { + override suspend fun call(scp: Scope): Obj { + return ObjBool(scp.thisAs().value.endsWith(scp.requiredArg(0).value)) + } + } + ) addPropertyDoc( name = "length", doc = "Number of UTF-16 code units in this string.", type = type("lyng.Int"), moduleName = "lyng.stdlib", - getter = { ObjInt.of((this.thisObj as ObjString).value.length.toLong()) } + getter = object : ScopeCallable { + override suspend fun call(scp: Scope): Obj = ObjInt.of((scp.thisObj as ObjString).value.length.toLong()) + } ) addFnDoc( name = "takeLast", doc = "Return a string with the last N characters.", params = listOf(ParamDoc("n", type("lyng.Int"))), returns = type("lyng.String"), - moduleName = "lyng.stdlib" - ) { - thisAs().value.takeLast( - requiredArg(0).toInt() - ).let(::ObjString) - } + moduleName = "lyng.stdlib", + code = object : ScopeCallable { + override suspend fun call(scp: Scope): Obj { + return ObjString( + scp.thisAs().value.takeLast( + scp.requiredArg(0).toInt() + ) + ) + } + } + ) addFnDoc( name = "take", doc = "Return a string with the first N characters.", params = listOf(ParamDoc("n", type("lyng.Int"))), returns = type("lyng.String"), - moduleName = "lyng.stdlib" - ) { - thisAs().value.take( - requiredArg(0).toInt() - ).let(::ObjString) - } + moduleName = "lyng.stdlib", + code = object : ScopeCallable { + override suspend fun call(scp: Scope): Obj { + return ObjString( + scp.thisAs().value.take( + scp.requiredArg(0).toInt() + ) + ) + } + } + ) addFnDoc( name = "drop", doc = "Drop the first N characters and return the remainder.", params = listOf(ParamDoc("n", type("lyng.Int"))), returns = type("lyng.String"), - moduleName = "lyng.stdlib" - ) { - thisAs().value.drop( - requiredArg(0).toInt() - ).let(::ObjString) - } + moduleName = "lyng.stdlib", + code = object : ScopeCallable { + override suspend fun call(scp: Scope): Obj { + return ObjString( + scp.thisAs().value.drop( + scp.requiredArg(0).toInt() + ) + ) + } + } + ) addFnDoc( name = "dropLast", doc = "Drop the last N characters and return the remainder.", params = listOf(ParamDoc("n", type("lyng.Int"))), returns = type("lyng.String"), - moduleName = "lyng.stdlib" - ) { - thisAs().value.dropLast( - requiredArg(0).toInt() - ).let(::ObjString) - } + moduleName = "lyng.stdlib", + code = object : ScopeCallable { + override suspend fun call(scp: Scope): Obj { + return ObjString( + scp.thisAs().value.dropLast( + scp.requiredArg(0).toInt() + ) + ) + } + } + ) addFnDoc( name = "lower", doc = "Lowercase version of this string (default locale).", returns = type("lyng.String"), - moduleName = "lyng.stdlib" - ) { - thisAs().value.lowercase().let(::ObjString) - } + moduleName = "lyng.stdlib", + code = object : ScopeCallable { + override suspend fun call(scp: Scope): Obj = ObjString(scp.thisAs().value.lowercase()) + } + ) addFnDoc( name = "lowercase", doc = "Lowercase version of this string (default locale).", returns = type("lyng.String"), - moduleName = "lyng.stdlib" - ) { - thisAs().value.lowercase().let(::ObjString) - } + moduleName = "lyng.stdlib", + code = object : ScopeCallable { + override suspend fun call(scp: Scope): Obj = ObjString(scp.thisAs().value.lowercase()) + } + ) addFnDoc( name = "upper", doc = "Uppercase version of this string (default locale).", returns = type("lyng.String"), - moduleName = "lyng.stdlib" - ) { - thisAs().value.uppercase().let(::ObjString) - } + moduleName = "lyng.stdlib", + code = object : ScopeCallable { + override suspend fun call(scp: Scope): Obj = ObjString(scp.thisAs().value.uppercase()) + } + ) addFnDoc( name = "uppercase", doc = "Uppercase version of this string (default locale).", returns = type("lyng.String"), - moduleName = "lyng.stdlib" - ) { - thisAs().value.uppercase().let(::ObjString) - } + moduleName = "lyng.stdlib", + code = object : ScopeCallable { + override suspend fun call(scp: Scope): Obj = ObjString(scp.thisAs().value.uppercase()) + } + ) addPropertyDoc( name = "characters", doc = "List of characters of this string.", type = TypeGenericDoc(type("lyng.List"), listOf(type("lyng.Char"))), moduleName = "lyng.stdlib", - getter = { - ObjList( - (this.thisObj as ObjString).value.map { ObjChar(it) }.toMutableList() - ) + getter = object : ScopeCallable { + override suspend fun call(scp: Scope): Obj { + return ObjList( + (scp.thisObj as ObjString).value.map { ObjChar(it) }.toMutableList() + ) + } } ) addFnDoc( name = "last", doc = "The last character of this string or throw if the string is empty.", returns = type("lyng.Char"), - moduleName = "lyng.stdlib" - ) { - ObjChar(thisAs().value.lastOrNull() ?: raiseNoSuchElement("empty string")) - } + moduleName = "lyng.stdlib", + code = object : ScopeCallable { + override suspend fun call(scp: Scope): Obj { + return ObjChar(scp.thisAs().value.lastOrNull() ?: scp.raiseNoSuchElement("empty string")) + } + } + ) addFnDoc( name = "encodeUtf8", doc = "Encode this string as UTF-8 bytes.", returns = type("lyng.Buffer"), - moduleName = "lyng.stdlib" - ) { ObjBuffer(thisAs().value.encodeToByteArray().asUByteArray()) } + moduleName = "lyng.stdlib", + code = object : ScopeCallable { + override suspend fun call(scp: Scope): Obj = + ObjBuffer(scp.thisAs().value.encodeToByteArray().asUByteArray()) + } + ) addPropertyDoc( name = "size", doc = "Alias for length: the number of characters (code units) in this string.", type = type("lyng.Int"), moduleName = "lyng.stdlib", - getter = { ObjInt.of((this.thisObj as ObjString).value.length.toLong()) } + getter = object : ScopeCallable { + override suspend fun call(scp: Scope): Obj = ObjInt.of((scp.thisObj as ObjString).value.length.toLong()) + } ) addFnDoc( name = "toReal", doc = "Parse this string as a real number (floating point).", returns = type("lyng.Real"), - moduleName = "lyng.stdlib" - ) { - ObjReal.of(thisAs().value.toDouble()) - } + moduleName = "lyng.stdlib", + code = object : ScopeCallable { + override suspend fun call(scp: Scope): Obj = ObjReal.of(scp.thisAs().value.toDouble()) + } + ) addFnDoc( name = "trim", doc = "Return a copy of this string with leading and trailing whitespace removed.", returns = type("lyng.String"), - moduleName = "lyng.stdlib" - ) { - thisAs().value.trim().let(::ObjString) - } - addFnDoc("isBlank", "Whether this string is empty or contains only whitespace characters.", - returns = type("lyng.Bool"), moduleName = "lyng.stdlib") { - ObjBool(thisAs().value.isBlank()) - } - addFnDoc("isEmpty", "Whether this string is empty.", - returns = type("lyng.Bool"), moduleName = "lyng.stdlib") { - ObjBool(thisAs().value.isEmpty()) - } - addFnDoc("isNotEmpty", "Whether this string is not empty.", - returns = type("lyng.Bool"), moduleName = "lyng.stdlib") { - ObjBool(thisAs().value.isNotEmpty()) - } + moduleName = "lyng.stdlib", + code = object : ScopeCallable { + override suspend fun call(scp: Scope): Obj = ObjString(scp.thisAs().value.trim()) + } + ) + addFnDoc( + name = "isBlank", + doc = "Whether this string is empty or contains only whitespace characters.", + returns = type("lyng.Bool"), + moduleName = "lyng.stdlib", + code = object : ScopeCallable { + override suspend fun call(scp: Scope): Obj = ObjBool(scp.thisAs().value.isBlank()) + } + ) + addFnDoc( + name = "isEmpty", + doc = "Whether this string is empty.", + returns = type("lyng.Bool"), + moduleName = "lyng.stdlib", + code = object : ScopeCallable { + override suspend fun call(scp: Scope): Obj = ObjBool(scp.thisAs().value.isEmpty()) + } + ) + addFnDoc( + name = "isNotEmpty", + doc = "Whether this string is not empty.", + returns = type("lyng.Bool"), + moduleName = "lyng.stdlib", + code = object : ScopeCallable { + override suspend fun call(scp: Scope): Obj = ObjBool(scp.thisAs().value.isNotEmpty()) + } + ) addFnDoc( name = "matches", doc = "Whether this string matches the given regular expression or pattern string.", params = listOf(ParamDoc("pattern")), returns = type("lyng.Bool"), - moduleName = "lyng.stdlib" - ) { - val s = requireOnlyArg() - val self = thisAs().value - ObjBool( - when (s) { - is ObjRegex -> self.matches(s.regex) - is ObjString -> { - if (s.value == ".*") true - else { - val re = if (PerfFlags.REGEX_CACHE) RegexCache.get(s.value) else s.value.toRegex() - self.matches(re) - } - } + moduleName = "lyng.stdlib", + code = object : ScopeCallable { + override suspend fun call(scp: Scope): Obj { + val s = scp.requireOnlyArg() + val self = scp.thisAs().value + return ObjBool( + when (s) { + is ObjRegex -> self.matches(s.regex) + is ObjString -> { + if (s.value == ".*") true + else { + val re = if (PerfFlags.REGEX_CACHE) RegexCache.get(s.value) else s.value.toRegex() + self.matches(re) + } + } - else -> - raiseIllegalArgument("can't match ${s.objClass.className}: required Regex or String") + else -> + scp.raiseIllegalArgument("can't match ${s.objClass.className}: required Regex or String") + } + ) } - ) - } + } + ) } } } \ No newline at end of file diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/pacman/ImportManager.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/pacman/ImportManager.kt index b464814..b406258 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/pacman/ImportManager.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/pacman/ImportManager.kt @@ -1,5 +1,5 @@ /* - * Copyright 2025 Sergey S. Chernov real.sergeych@gmail.com + * Copyright 2026 Sergey S. Chernov real.sergeych@gmail.com * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,6 +21,10 @@ import net.sergeych.lyng.* import net.sergeych.synctools.ProtectedOp import net.sergeych.synctools.withLock +interface ModuleBuilder { + suspend fun build(scope: ModuleScope) +} + /** * Import manager allow to register packages with builder lambdas and act as an * [ImportProvider]. Note that packages _must be registered_ first with [addPackage], @@ -40,16 +44,16 @@ class ImportManager( private inner class Entry( val packageName: String, - val builder: suspend (ModuleScope) -> Unit, + val builder: ModuleBuilder, var cachedScope: ModuleScope? = null ) { suspend fun getScope(pos: Pos): ModuleScope { cachedScope?.let { return it } - return ModuleScope(inner, pos, packageName).apply { - cachedScope = this - builder(this) - } + val ms = ModuleScope(inner, pos, packageName) + cachedScope = ms + builder.build(ms) + return ms } } @@ -90,7 +94,7 @@ class ImportManager( * @param name package name * @param builder lambda to create actual package using the given [ModuleScope] */ - fun addPackage(name: String, builder: suspend (ModuleScope) -> Unit) { + fun addPackage(name: String, builder: ModuleBuilder) { op.withLock { if (name in imports) throw IllegalArgumentException("Package $name already exists") @@ -102,7 +106,7 @@ class ImportManager( * Bulk [addPackage] with slightly better performance */ @Suppress("unused") - fun addPackages(registrationData: List Unit>>) { + fun addPackages(registrationData: List>) { op.withLock { for (pp in registrationData) { if (pp.first in imports) @@ -129,9 +133,11 @@ class ImportManager( */ fun addSourcePackages(vararg sources: Source) { for (s in sources) { - addPackage(s.extractPackageName()) { - it.eval(s) - } + addPackage(s.extractPackageName(), object : ModuleBuilder { + override suspend fun build(scope: ModuleScope) { + scope.eval(s) + } + }) } } @@ -144,7 +150,11 @@ class ImportManager( var source = Source("tmp", s) val packageName = source.extractPackageName() source = Source(packageName, s) - addPackage(packageName) { it.eval(source) } + addPackage(packageName, object : ModuleBuilder { + override suspend fun build(scope: ModuleScope) { + scope.eval(source) + } + }) } } diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/statements.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/statements.kt index f24b3ca..e29211c 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/statements.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/statements.kt @@ -72,16 +72,16 @@ fun Statement.require(cond: Boolean, message: () -> String) { if (!cond) raise(message()) } -fun statement(pos: Pos, isStaticConst: Boolean = false, isConst: Boolean = false, f: suspend (Scope) -> Obj): Statement = +fun statement(pos: Pos, isStaticConst: Boolean = false, isConst: Boolean = false, f: ScopeCallable): Statement = object : Statement(isStaticConst, isConst) { override val pos: Pos = pos - override suspend fun execute(scope: Scope): Obj = f(scope) + override suspend fun execute(scope: Scope): Obj = f.call(scope) } -fun statement(isStaticConst: Boolean = false, isConst: Boolean = false, f: suspend Scope.() -> Obj): Statement = +fun statement(isStaticConst: Boolean = false, isConst: Boolean = false, f: ScopeCallable): Statement = object : Statement(isStaticConst, isConst) { override val pos: Pos = Pos.builtIn - override suspend fun execute(scope: Scope): Obj = f(scope) + override suspend fun execute(scope: Scope): Obj = f.call(scope) } object NopStatement: Statement(true, true, ObjType.Void) { diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lynon/packer.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lynon/packer.kt index bcf1093..ebb2005 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lynon/packer.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lynon/packer.kt @@ -41,11 +41,11 @@ object ObjLynonClass : ObjClass("Lynon") { init { addClassConst("test", ObjString("test_const")) - addClassFn("encode") { - encodeAny(this, requireOnlyArg()) + addClassFn("encode") { scp -> + encodeAny(scp, scp.requireOnlyArg()) } - addClassFn("decode") { - decodeAny(this, requireOnlyArg()) + addClassFn("decode") { scp -> + decodeAny(scp, scp.requireOnlyArg()) } } } diff --git a/lynglib/src/commonTest/kotlin/BindingHighlightTest.kt b/lynglib/src/commonTest/kotlin/BindingHighlightTest.kt index d218556..dc9c1e6 100644 --- a/lynglib/src/commonTest/kotlin/BindingHighlightTest.kt +++ b/lynglib/src/commonTest/kotlin/BindingHighlightTest.kt @@ -171,7 +171,7 @@ class BindingHighlightTest { // Find the specific usage inside string-literal invocation: "%s is directory"(name) val pattern = "\"%s is directory\"(name)" val lineIdx = text.indexOf(pattern) - assertTrue(lineIdx >= 0, "Pattern with string invoke should be present in the snippet") + assertTrue(lineIdx >= 0, "Pattern with string invokeCallable should be present in the snippet") val nameStart = lineIdx + pattern.indexOf("name") val nameEnd = nameStart + "name".length diff --git a/lynglib/src/commonTest/kotlin/OOTest.kt b/lynglib/src/commonTest/kotlin/OOTest.kt index 26d3e79..6338d49 100644 --- a/lynglib/src/commonTest/kotlin/OOTest.kt +++ b/lynglib/src/commonTest/kotlin/OOTest.kt @@ -865,8 +865,8 @@ class OOTest { callBar """.trimIndent()) as Statement val s2 = Script.newScope() - assertEquals(44L, fn.invoke(scope, fn).toKotlin(s2)) - assertEquals(45L, fn.invoke(s2, fn).toKotlin(s2)) + assertEquals(44L, fn.invokeCallable(scope, fn).toKotlin(s2)) + assertEquals(45L, fn.invokeCallable(s2, fn).toKotlin(s2)) } @Test diff --git a/lynglib/src/commonTest/kotlin/ScriptTest.kt b/lynglib/src/commonTest/kotlin/ScriptTest.kt index 80a05fe..d04dc50 100644 --- a/lynglib/src/commonTest/kotlin/ScriptTest.kt +++ b/lynglib/src/commonTest/kotlin/ScriptTest.kt @@ -138,10 +138,10 @@ class ScriptTest { companion object { val type = ObjClass("TestIterable", ObjIterable).apply { - addFn("iterator") { - ObjTestIterator(thisAs()) + addFn("iterator") { scp -> + ObjTestIterator(scp.thisAs()) } - addFn("cancelCount") { thisAs().cancelCount.toObj() } + addFn("cancelCount") { scp -> scp.thisAs().cancelCount.toObj() } } } } @@ -158,10 +158,10 @@ class ScriptTest { companion object { val type = ObjClass("TestIterator", ObjIterator).apply { - addFn("hasNext") { thisAs().hasNext().toObj() } - addFn("next") { thisAs().next() } - addFn("cancelIteration") { - thisAs().cancelIteration() + addFn("hasNext") { scp -> scp.thisAs().hasNext().toObj() } + addFn("next") { scp -> scp.thisAs().next() } + addFn("cancelIteration") { scp -> + scp.thisAs().cancelIteration() ObjVoid } } @@ -2229,8 +2229,8 @@ class ScriptTest { @Test fun testThrowFromKotlin() = runTest { val c = Script.newScope() - c.addFn("callThrow") { - raiseIllegalArgument("fromKotlin") + c.addFn("callThrow") { scp -> + scp.raiseIllegalArgument("fromKotlin") } c.eval( """ @@ -2738,8 +2738,8 @@ class ScriptTest { companion object { val klass = ObjClass("TestFoo").apply { - addFn("test") { - thisAs().value + addFn("test") { scp -> + scp.thisAs().value } } } @@ -4480,7 +4480,7 @@ class ScriptTest { val dummyThis = Obj() // but we should be able to call it directly val otherScope = baseScope.createChildScope() - val r = (exports["exportedFunction".toObj()] as Statement).invoke(otherScope, dummyThis, ObjInt(50)) + val r = (exports["exportedFunction".toObj()] as Statement).invokeCallable(otherScope, dummyThis, ObjInt(50)) println(r) assertEquals(51, r.toInt()) } diff --git a/lynglib/src/commonTest/kotlin/net/sergeych/lyng/DelegationTest.kt b/lynglib/src/commonTest/kotlin/net/sergeych/lyng/DelegationTest.kt index c114bc2..0fdfab1 100644 --- a/lynglib/src/commonTest/kotlin/net/sergeych/lyng/DelegationTest.kt +++ b/lynglib/src/commonTest/kotlin/net/sergeych/lyng/DelegationTest.kt @@ -90,7 +90,7 @@ class DelegationTest { fun testFunDelegation() = runTest { eval(""" class ActionDelegate() { - fun invoke(thisRef, name, args...) { + fun invokeCallable(thisRef, name, args...) { "Called %s with %d args: %s"(name, args.size, args.joinToString(",")) } } diff --git a/lynglib/src/wasmJsMain/kotlin/net/sergeych/lyng/PerfDefaults.wasmJs.kt b/lynglib/src/wasmJsMain/kotlin/net/sergeych/lyng/PerfDefaults.wasmJs.kt index e5fb671..98fe589 100644 --- a/lynglib/src/wasmJsMain/kotlin/net/sergeych/lyng/PerfDefaults.wasmJs.kt +++ b/lynglib/src/wasmJsMain/kotlin/net/sergeych/lyng/PerfDefaults.wasmJs.kt @@ -18,15 +18,15 @@ package net.sergeych.lyng actual object PerfDefaults { - actual val LOCAL_SLOT_PIC: Boolean = true - actual val EMIT_FAST_LOCAL_REFS: Boolean = true + actual val LOCAL_SLOT_PIC: Boolean = false + actual val EMIT_FAST_LOCAL_REFS: Boolean = false - actual val ARG_BUILDER: Boolean = true - actual val SKIP_ARGS_ON_NULL_RECEIVER: Boolean = true - actual val SCOPE_POOL: Boolean = true + actual val ARG_BUILDER: Boolean = false + actual val SKIP_ARGS_ON_NULL_RECEIVER: Boolean = false + actual val SCOPE_POOL: Boolean = false - actual val FIELD_PIC: Boolean = true - actual val METHOD_PIC: Boolean = true + actual val FIELD_PIC: Boolean = false + actual val METHOD_PIC: Boolean = false actual val FIELD_PIC_SIZE_4: Boolean = false actual val METHOD_PIC_SIZE_4: Boolean = false actual val PIC_ADAPTIVE_2_TO_4: Boolean = false