heroic attempt to fix kotlin 3.2.0 wasmJS compiler

(incomplete)
This commit is contained in:
Sergey Chernov 2026-01-21 21:50:05 +03:00
parent 7b1ba71ef0
commit f9ae00b7f4
50 changed files with 3815 additions and 2913 deletions

View File

@ -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"); # Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License. # you may not use this file except in compliance with the License.
@ -16,7 +16,7 @@
# #
#Gradle #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.caching=true
org.gradle.configuration-cache=true org.gradle.configuration-cache=true
#Kotlin #Kotlin

View File

@ -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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with 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.ModuleScope
import net.sergeych.lyng.Scope import net.sergeych.lyng.Scope
import net.sergeych.lyng.ScopeCallable
import net.sergeych.lyng.miniast.* import net.sergeych.lyng.miniast.*
import net.sergeych.lyng.obj.* import net.sergeych.lyng.obj.*
import net.sergeych.lyng.pacman.ImportManager 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.LyngFs import net.sergeych.lyngio.fs.LyngFs
import net.sergeych.lyngio.fs.LyngPath import net.sergeych.lyngio.fs.LyngPath
@ -50,9 +52,11 @@ fun createFsModule(policy: FsAccessPolicy, manager: ImportManager): Boolean {
// Avoid re-registering in this ImportManager // Avoid re-registering in this ImportManager
if (manager.packageNames.contains(name)) return false if (manager.packageNames.contains(name)) return false
manager.addPackage(name) { module -> manager.addPackage(name, object : ModuleBuilder {
buildFsModule(module, policy) override suspend fun build(module: ModuleScope) {
} buildFsModule(module, policy)
}
})
return true return true
} }
@ -78,322 +82,394 @@ private suspend fun buildFsModule(module: ModuleScope, policy: FsAccessPolicy) {
name = "name", name = "name",
doc = "Base name of the path (last segment).", doc = "Base name of the path (last segment).",
returns = type("lyng.String"), returns = type("lyng.String"),
moduleName = module.packageName moduleName = module.packageName,
) { code = object : ScopeCallable {
val self = thisAs<ObjPath>() override suspend fun call(scp: Scope): Obj {
self.path.name.toObj() val self = scp.thisAs<ObjPath>()
} return self.path.name.toObj()
}
}
)
addFnDoc( addFnDoc(
name = "parent", name = "parent",
doc = "Parent directory as a Path or null if none.", doc = "Parent directory as a Path or null if none.",
returns = type("Path", nullable = true), returns = type("Path", nullable = true),
moduleName = module.packageName moduleName = module.packageName,
) { code = object : ScopeCallable {
val self = thisAs<ObjPath>() override suspend fun call(scp: Scope): Obj {
self.path.parent?.let { val self = scp.thisAs<ObjPath>()
ObjPath( this@apply, self.secured, it) return self.path.parent?.let {
} ?: ObjNull ObjPath(this@apply, self.secured, it)
} } ?: ObjNull
}
}
)
addFnDoc( addFnDoc(
name = "segments", name = "segments",
doc = "List of path segments.", doc = "List of path segments.",
// returns: List<String> // returns: List<String>
returns = TypeGenericDoc(type("lyng.List"), listOf(type("lyng.String"))), returns = TypeGenericDoc(type("lyng.List"), listOf(type("lyng.String"))),
moduleName = module.packageName moduleName = module.packageName,
) { code = object : ScopeCallable {
val self = thisAs<ObjPath>() override suspend fun call(scp: Scope): Obj {
ObjList(self.path.segments.map { ObjString(it) }.toMutableList()) val self = scp.thisAs<ObjPath>()
} return ObjList(self.path.segments.map { ObjString(it) }.toMutableList())
}
}
)
// exists(): Bool // exists(): Bool
addFnDoc( addFnDoc(
name = "exists", name = "exists",
doc = "Check whether this path exists.", doc = "Check whether this path exists.",
returns = type("lyng.Bool"), returns = type("lyng.Bool"),
moduleName = module.packageName moduleName = module.packageName,
) { code = object : ScopeCallable {
fsGuard { override suspend fun call(scp: Scope): Obj {
val self = this.thisObj as ObjPath return scp.fsGuard {
(self.secured.exists(self.path)).toObj() val self = scp.thisObj as ObjPath
(self.secured.exists(self.path)).toObj()
}
}
} }
} )
// isFile(): Bool — cached metadata // isFile(): Bool — cached metadata
addFnDoc( addFnDoc(
name = "isFile", name = "isFile",
doc = "True if this path is a regular file (based on cached metadata).", doc = "True if this path is a regular file (based on cached metadata).",
returns = type("lyng.Bool"), returns = type("lyng.Bool"),
moduleName = module.packageName moduleName = module.packageName,
) { code = object : ScopeCallable {
fsGuard { override suspend fun call(scp: Scope): Obj {
val self = this.thisObj as ObjPath return scp.fsGuard {
self.ensureMetadata().let { ObjBool(it.isRegularFile) } val self = scp.thisObj as ObjPath
self.ensureMetadata().let { ObjBool(it.isRegularFile) }
}
}
} }
} )
// isDirectory(): Bool — cached metadata // isDirectory(): Bool — cached metadata
addFnDoc( addFnDoc(
name = "isDirectory", name = "isDirectory",
doc = "True if this path is a directory (based on cached metadata).", doc = "True if this path is a directory (based on cached metadata).",
returns = type("lyng.Bool"), returns = type("lyng.Bool"),
moduleName = module.packageName moduleName = module.packageName,
) { code = object : ScopeCallable {
fsGuard { override suspend fun call(scp: Scope): Obj {
val self = this.thisObj as ObjPath return scp.fsGuard {
self.ensureMetadata().let { ObjBool(it.isDirectory) } val self = scp.thisObj as ObjPath
self.ensureMetadata().let { ObjBool(it.isDirectory) }
}
}
} }
} )
// size(): Int? — null when unavailable // size(): Int? — null when unavailable
addFnDoc( addFnDoc(
name = "size", name = "size",
doc = "File size in bytes, or null when unavailable.", doc = "File size in bytes, or null when unavailable.",
returns = type("lyng.Int", nullable = true), returns = type("lyng.Int", nullable = true),
moduleName = module.packageName moduleName = module.packageName,
) { code = object : ScopeCallable {
fsGuard { override suspend fun call(scp: Scope): Obj {
val self = this.thisObj as ObjPath return scp.fsGuard {
val m = self.ensureMetadata() val self = scp.thisObj as ObjPath
m.size?.let { ObjInt(it) } ?: ObjNull val m = self.ensureMetadata()
m.size?.let { ObjInt(it) } ?: ObjNull
}
}
} }
} )
// createdAt(): Instant? — Lyng Instant, null when unavailable // createdAt(): Instant? — Lyng Instant, null when unavailable
addFnDoc( addFnDoc(
name = "createdAt", name = "createdAt",
doc = "Creation time as `Instant`, or null when unavailable.", doc = "Creation time as `Instant`, or null when unavailable.",
returns = type("lyng.Instant", nullable = true), returns = type("lyng.Instant", nullable = true),
moduleName = module.packageName moduleName = module.packageName,
) { code = object : ScopeCallable {
fsGuard { override suspend fun call(scp: Scope): Obj {
val self = this.thisObj as ObjPath return scp.fsGuard {
val m = self.ensureMetadata() val self = scp.thisObj as ObjPath
m.createdAtMillis?.let { ObjInstant(kotlin.time.Instant.fromEpochMilliseconds(it)) } ?: ObjNull val m = self.ensureMetadata()
m.createdAtMillis?.let { ObjInstant(kotlin.time.Instant.fromEpochMilliseconds(it)) } ?: ObjNull
}
}
} }
} )
// createdAtMillis(): Int? — milliseconds since epoch or null // createdAtMillis(): Int? — milliseconds since epoch or null
addFnDoc( addFnDoc(
name = "createdAtMillis", name = "createdAtMillis",
doc = "Creation time in milliseconds since epoch, or null when unavailable.", doc = "Creation time in milliseconds since epoch, or null when unavailable.",
returns = type("lyng.Int", nullable = true), returns = type("lyng.Int", nullable = true),
moduleName = module.packageName moduleName = module.packageName,
) { code = object : ScopeCallable {
fsGuard { override suspend fun call(scp: Scope): Obj {
val self = this.thisObj as ObjPath return scp.fsGuard {
val m = self.ensureMetadata() val self = scp.thisObj as ObjPath
m.createdAtMillis?.let { ObjInt(it) } ?: ObjNull val m = self.ensureMetadata()
m.createdAtMillis?.let { ObjInt(it) } ?: ObjNull
}
}
} }
} )
// modifiedAt(): Instant? — Lyng Instant, null when unavailable // modifiedAt(): Instant? — Lyng Instant, null when unavailable
addFnDoc( addFnDoc(
name = "modifiedAt", name = "modifiedAt",
doc = "Last modification time as `Instant`, or null when unavailable.", doc = "Last modification time as `Instant`, or null when unavailable.",
returns = type("lyng.Instant", nullable = true), returns = type("lyng.Instant", nullable = true),
moduleName = module.packageName moduleName = module.packageName,
) { code = object : ScopeCallable {
fsGuard { override suspend fun call(scp: Scope): Obj {
val self = this.thisObj as ObjPath return scp.fsGuard {
val m = self.ensureMetadata() val self = scp.thisObj as ObjPath
m.modifiedAtMillis?.let { ObjInstant(kotlinx.datetime.Instant.fromEpochMilliseconds(it)) } ?: ObjNull val m = self.ensureMetadata()
m.modifiedAtMillis?.let { ObjInstant(kotlinx.datetime.Instant.fromEpochMilliseconds(it)) } ?: ObjNull
}
}
} }
} )
// modifiedAtMillis(): Int? — milliseconds since epoch or null // modifiedAtMillis(): Int? — milliseconds since epoch or null
addFnDoc( addFnDoc(
name = "modifiedAtMillis", name = "modifiedAtMillis",
doc = "Last modification time in milliseconds since epoch, or null when unavailable.", doc = "Last modification time in milliseconds since epoch, or null when unavailable.",
returns = type("lyng.Int", nullable = true), returns = type("lyng.Int", nullable = true),
moduleName = module.packageName moduleName = module.packageName,
) { code = object : ScopeCallable {
fsGuard { override suspend fun call(scp: Scope): Obj {
val self = this.thisObj as ObjPath return scp.fsGuard {
val m = self.ensureMetadata() val self = scp.thisObj as ObjPath
m.modifiedAtMillis?.let { ObjInt(it) } ?: ObjNull val m = self.ensureMetadata()
m.modifiedAtMillis?.let { ObjInt(it) } ?: ObjNull
}
}
} }
} )
// list(): List<Path> // list(): List<Path>
addFnDoc( addFnDoc(
name = "list", name = "list",
doc = "List directory entries as `Path` objects.", doc = "List directory entries as `Path` objects.",
returns = TypeGenericDoc(type("lyng.List"), listOf(type("Path"))), returns = TypeGenericDoc(type("lyng.List"), listOf(type("Path"))),
moduleName = module.packageName moduleName = module.packageName,
) { code = object : ScopeCallable {
fsGuard { override suspend fun call(scp: Scope): Obj {
val self = this.thisObj as ObjPath return scp.fsGuard {
val items = self.secured.list(self.path).map { ObjPath(self.objClass, self.secured, it) } val self = scp.thisObj as ObjPath
ObjList(items.toMutableList()) val items = self.secured.list(self.path).map { ObjPath(self.objClass, self.secured, it) }
ObjList(items.toMutableList())
}
}
} }
} )
// readBytes(): Buffer // readBytes(): Buffer
addFnDoc( addFnDoc(
name = "readBytes", name = "readBytes",
doc = "Read the file into a binary buffer.", doc = "Read the file into a binary buffer.",
returns = type("lyng.Buffer"), returns = type("lyng.Buffer"),
moduleName = module.packageName moduleName = module.packageName,
) { code = object : ScopeCallable {
fsGuard { override suspend fun call(scp: Scope): Obj {
val self = this.thisObj as ObjPath return scp.fsGuard {
val bytes = self.secured.readBytes(self.path) val self = scp.thisObj as ObjPath
ObjBuffer(bytes.asUByteArray()) val bytes = self.secured.readBytes(self.path)
ObjBuffer(bytes.asUByteArray())
}
}
} }
} )
// writeBytes(bytes: Buffer) // writeBytes(bytes: Buffer)
addFnDoc( addFnDoc(
name = "writeBytes", name = "writeBytes",
doc = "Write a binary buffer to the file, replacing content.", doc = "Write a binary buffer to the file, replacing content.",
params = listOf(ParamDoc("bytes", type("lyng.Buffer"))), params = listOf(ParamDoc("bytes", type("lyng.Buffer"))),
moduleName = module.packageName moduleName = module.packageName,
) { code = object : ScopeCallable {
fsGuard { override suspend fun call(scp: Scope): Obj {
val self = this.thisObj as ObjPath return scp.fsGuard {
val buf = requiredArg<ObjBuffer>(0) val self = scp.thisObj as ObjPath
self.secured.writeBytes(self.path, buf.byteArray.asByteArray(), append = false) val buf = scp.requiredArg<ObjBuffer>(0)
ObjVoid self.secured.writeBytes(self.path, buf.byteArray.asByteArray(), append = false)
ObjVoid
}
}
} }
} )
// appendBytes(bytes: Buffer) // appendBytes(bytes: Buffer)
addFnDoc( addFnDoc(
name = "appendBytes", name = "appendBytes",
doc = "Append a binary buffer to the end of the file.", doc = "Append a binary buffer to the end of the file.",
params = listOf(ParamDoc("bytes", type("lyng.Buffer"))), params = listOf(ParamDoc("bytes", type("lyng.Buffer"))),
moduleName = module.packageName moduleName = module.packageName,
) { code = object : ScopeCallable {
fsGuard { override suspend fun call(scp: Scope): Obj {
val self = this.thisObj as ObjPath return scp.fsGuard {
val buf = requiredArg<ObjBuffer>(0) val self = scp.thisObj as ObjPath
self.secured.writeBytes(self.path, buf.byteArray.asByteArray(), append = true) val buf = scp.requiredArg<ObjBuffer>(0)
ObjVoid self.secured.writeBytes(self.path, buf.byteArray.asByteArray(), append = true)
ObjVoid
}
}
} }
} )
// readUtf8(): String // readUtf8(): String
addFnDoc( addFnDoc(
name = "readUtf8", name = "readUtf8",
doc = "Read the file as a UTF-8 string.", doc = "Read the file as a UTF-8 string.",
returns = type("lyng.String"), returns = type("lyng.String"),
moduleName = module.packageName moduleName = module.packageName,
) { code = object : ScopeCallable {
fsGuard { override suspend fun call(scp: Scope): Obj {
val self = this.thisObj as ObjPath return scp.fsGuard {
self.secured.readUtf8(self.path).toObj() val self = scp.thisObj as ObjPath
self.secured.readUtf8(self.path).toObj()
}
}
} }
} )
// writeUtf8(text: String) // writeUtf8(text: String)
addFnDoc( addFnDoc(
name = "writeUtf8", name = "writeUtf8",
doc = "Write a UTF-8 string to the file, replacing content.", doc = "Write a UTF-8 string to the file, replacing content.",
params = listOf(ParamDoc("text", type("lyng.String"))), params = listOf(ParamDoc("text", type("lyng.String"))),
moduleName = module.packageName moduleName = module.packageName,
) { code = object : ScopeCallable {
fsGuard { override suspend fun call(scp: Scope): Obj {
val self = this.thisObj as ObjPath return scp.fsGuard {
val text = requireOnlyArg<ObjString>().value val self = scp.thisObj as ObjPath
self.secured.writeUtf8(self.path, text, append = false) val text = scp.requireOnlyArg<ObjString>().value
ObjVoid self.secured.writeUtf8(self.path, text, append = false)
ObjVoid
}
}
} }
} )
// appendUtf8(text: String) // appendUtf8(text: String)
addFnDoc( addFnDoc(
name = "appendUtf8", name = "appendUtf8",
doc = "Append UTF-8 text to the end of the file.", doc = "Append UTF-8 text to the end of the file.",
params = listOf(ParamDoc("text", type("lyng.String"))), params = listOf(ParamDoc("text", type("lyng.String"))),
moduleName = module.packageName moduleName = module.packageName,
) { code = object : ScopeCallable {
fsGuard { override suspend fun call(scp: Scope): Obj {
val self = this.thisObj as ObjPath return scp.fsGuard {
val text = requireOnlyArg<ObjString>().value val self = scp.thisObj as ObjPath
self.secured.writeUtf8(self.path, text, append = true) val text = scp.requireOnlyArg<ObjString>().value
ObjVoid self.secured.writeUtf8(self.path, text, append = true)
ObjVoid
}
}
} }
} )
// metadata(): Map // metadata(): Map
addFnDoc( addFnDoc(
name = "metadata", name = "metadata",
doc = "Fetch cached metadata as a map of fields: `isFile`, `isDirectory`, `size`, `createdAtMillis`, `modifiedAtMillis`, `isSymlink`.", 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"))), returns = TypeGenericDoc(type("lyng.Map"), listOf(type("lyng.String"), type("lyng.Any"))),
moduleName = module.packageName moduleName = module.packageName,
) { code = object : ScopeCallable {
fsGuard { override suspend fun call(scp: Scope): Obj {
val self = this.thisObj as ObjPath return scp.fsGuard {
val m = self.secured.metadata(self.path) val self = scp.thisObj as ObjPath
ObjMap(mutableMapOf( val m = self.secured.metadata(self.path)
ObjString("isFile") to ObjBool(m.isRegularFile), ObjMap(mutableMapOf(
ObjString("isDirectory") to ObjBool(m.isDirectory), ObjString("isFile") to ObjBool(m.isRegularFile),
ObjString("size") to (m.size?.toLong() ?: 0L).toObj(), ObjString("isDirectory") to ObjBool(m.isDirectory),
ObjString("createdAtMillis") to ((m.createdAtMillis ?: 0L)).toObj(), ObjString("size") to (m.size?.toLong() ?: 0L).toObj(),
ObjString("modifiedAtMillis") to ((m.modifiedAtMillis ?: 0L)).toObj(), ObjString("createdAtMillis") to ((m.createdAtMillis ?: 0L)).toObj(),
ObjString("isSymlink") to ObjBool(m.isSymlink), ObjString("modifiedAtMillis") to ((m.modifiedAtMillis ?: 0L)).toObj(),
)) ObjString("isSymlink") to ObjBool(m.isSymlink),
))
}
}
} }
} )
// mkdirs(mustCreate: Bool=false) // mkdirs(mustCreate: Bool=false)
addFnDoc( addFnDoc(
name = "mkdirs", 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.", 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"))), params = listOf(ParamDoc("mustCreate", type("lyng.Bool"))),
moduleName = module.packageName moduleName = module.packageName,
) { code = object : ScopeCallable {
fsGuard { override suspend fun call(scp: Scope): Obj {
val self = this.thisObj as ObjPath return scp.fsGuard {
val mustCreate = args.list.getOrNull(0)?.toBool() ?: false val self = scp.thisObj as ObjPath
self.secured.createDirectories(self.path, mustCreate) val mustCreate = scp.args.list.getOrNull(0)?.toBool() ?: false
ObjVoid self.secured.createDirectories(self.path, mustCreate)
ObjVoid
}
}
} }
} )
// move(to: Path|String, overwrite: Bool=false) // move(to: Path|String, overwrite: Bool=false)
addFnDoc( addFnDoc(
name = "move", 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`).", 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 // types vary; keep generic description in doc
params = listOf(ParamDoc("to"), ParamDoc("overwrite", type("lyng.Bool"))), params = listOf(ParamDoc("to"), ParamDoc("overwrite", type("lyng.Bool"))),
moduleName = module.packageName moduleName = module.packageName,
) { code = object : ScopeCallable {
fsGuard { override suspend fun call(scp: Scope): Obj {
val self = this.thisObj as ObjPath return scp.fsGuard {
val toPath = parsePathArg(this, self, requiredArg<Obj>(0)) val self = scp.thisObj as ObjPath
val overwrite = args.list.getOrNull(1)?.toBool() ?: false val toPath = parsePathArg(scp, self, scp.requiredArg<Obj>(0))
self.secured.move(self.path, toPath, overwrite) val overwrite = scp.args.list.getOrNull(1)?.toBool() ?: false
ObjVoid self.secured.move(self.path, toPath, overwrite)
ObjVoid
}
}
} }
} )
// delete(mustExist: Bool=false, recursively: Bool=false) // delete(mustExist: Bool=false, recursively: Bool=false)
addFnDoc( addFnDoc(
name = "delete", 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.", 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"))), params = listOf(ParamDoc("mustExist", type("lyng.Bool")), ParamDoc("recursively", type("lyng.Bool"))),
moduleName = module.packageName moduleName = module.packageName,
) { code = object : ScopeCallable {
fsGuard { override suspend fun call(scp: Scope): Obj {
val self = this.thisObj as ObjPath return scp.fsGuard {
val mustExist = args.list.getOrNull(0)?.toBool() ?: false val self = scp.thisObj as ObjPath
val recursively = args.list.getOrNull(1)?.toBool() ?: false val mustExist = scp.args.list.getOrNull(0)?.toBool() ?: false
self.secured.delete(self.path, mustExist, recursively) val recursively = scp.args.list.getOrNull(1)?.toBool() ?: false
ObjVoid self.secured.delete(self.path, mustExist, recursively)
ObjVoid
}
}
} }
} )
// copy(to: Path|String, overwrite: Bool=false) // copy(to: Path|String, overwrite: Bool=false)
addFnDoc( addFnDoc(
name = "copy", 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`).", 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"))), params = listOf(ParamDoc("to"), ParamDoc("overwrite", type("lyng.Bool"))),
moduleName = module.packageName moduleName = module.packageName,
) { code = object : ScopeCallable {
fsGuard { override suspend fun call(scp: Scope): Obj {
val self = this.thisObj as ObjPath return scp.fsGuard {
val toPath = parsePathArg(this, self, requiredArg<Obj>(0)) val self = scp.thisObj as ObjPath
val overwrite = args.list.getOrNull(1)?.toBool() ?: false val toPath = parsePathArg(scp, self, scp.requiredArg<Obj>(0))
self.secured.copy(self.path, toPath, overwrite) val overwrite = scp.args.list.getOrNull(1)?.toBool() ?: false
ObjVoid self.secured.copy(self.path, toPath, overwrite)
ObjVoid
}
}
} }
} )
// glob(pattern: String): List<Path> // glob(pattern: String): List<Path>
addFnDoc( addFnDoc(
name = "glob", name = "glob",
doc = "List entries matching a glob pattern (no recursion).", doc = "List entries matching a glob pattern (no recursion).",
params = listOf(ParamDoc("pattern", type("lyng.String"))), params = listOf(ParamDoc("pattern", type("lyng.String"))),
returns = TypeGenericDoc(type("lyng.List"), listOf(type("Path"))), returns = TypeGenericDoc(type("lyng.List"), listOf(type("Path"))),
moduleName = module.packageName moduleName = module.packageName,
) { code = object : ScopeCallable {
fsGuard { override suspend fun call(scp: Scope): Obj {
val self = this.thisObj as ObjPath return scp.fsGuard {
val pattern = requireOnlyArg<ObjString>().value val self = scp.thisObj as ObjPath
val matches = self.secured.glob(self.path, pattern) val pattern = scp.requireOnlyArg<ObjString>().value
ObjList(matches.map { ObjPath(self.objClass, self.secured, it) }.toMutableList()) 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) --- // --- 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`.", doc = "Read file in fixed-size chunks as an iterator of `Buffer`.",
params = listOf(ParamDoc("size", type("lyng.Int"))), params = listOf(ParamDoc("size", type("lyng.Int"))),
returns = TypeGenericDoc(type("lyng.Iterator"), listOf(type("lyng.Buffer"))), returns = TypeGenericDoc(type("lyng.Iterator"), listOf(type("lyng.Buffer"))),
moduleName = module.packageName moduleName = module.packageName,
) { code = object : ScopeCallable {
fsGuard { override suspend fun call(scp: Scope): Obj {
val self = this.thisObj as ObjPath return scp.fsGuard {
val size = args.list.getOrNull(0)?.toInt() ?: 65536 val self = scp.thisObj as ObjPath
val bytes = self.secured.readBytes(self.path) val size = scp.args.list.getOrNull(0)?.toInt() ?: 65536
ObjFsBytesIterator(bytes, size) val bytes = self.secured.readBytes(self.path)
ObjFsBytesIterator(bytes, size)
}
}
} }
} )
// readUtf8Chunks(size: Int = 65536) -> Iterator<String> // readUtf8Chunks(size: Int = 65536) -> Iterator<String>
addFnDoc( 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`.", doc = "Read UTF-8 text in fixed-size chunks as an iterator of `String`.",
params = listOf(ParamDoc("size", type("lyng.Int"))), params = listOf(ParamDoc("size", type("lyng.Int"))),
returns = TypeGenericDoc(type("lyng.Iterator"), listOf(type("lyng.String"))), returns = TypeGenericDoc(type("lyng.Iterator"), listOf(type("lyng.String"))),
moduleName = module.packageName moduleName = module.packageName,
) { code = object : ScopeCallable {
fsGuard { override suspend fun call(scp: Scope): Obj {
val self = this.thisObj as ObjPath return scp.fsGuard {
val size = args.list.getOrNull(0)?.toInt() ?: 65536 val self = scp.thisObj as ObjPath
val text = self.secured.readUtf8(self.path) val size = scp.args.list.getOrNull(0)?.toInt() ?: 65536
ObjFsStringChunksIterator(text, size) val text = self.secured.readUtf8(self.path)
ObjFsStringChunksIterator(text, size)
}
}
} }
} )
// lines() -> Iterator<String>, implemented via readUtf8Chunks // lines() -> Iterator<String>, implemented via readUtf8Chunks
addFnDoc( addFnDoc(
name = "lines", name = "lines",
doc = "Iterate lines of the file as `String` values.", doc = "Iterate lines of the file as `String` values.",
returns = TypeGenericDoc(type("lyng.Iterator"), listOf(type("lyng.String"))), returns = TypeGenericDoc(type("lyng.Iterator"), listOf(type("lyng.String"))),
moduleName = module.packageName moduleName = module.packageName,
) { code = object : ScopeCallable {
fsGuard { override suspend fun call(scp: Scope): Obj {
val chunkIt = thisObj.invokeInstanceMethod(this, "readUtf8Chunks") return scp.fsGuard {
ObjFsLinesIterator(chunkIt) val chunkIt = scp.thisObj.invokeInstanceMethod(scp, "readUtf8Chunks")
ObjFsLinesIterator(chunkIt)
}
}
} }
} )
} }
// Export into the module scope with docs // Export into the module scope with docs
@ -518,39 +603,51 @@ class ObjFsBytesIterator(
name = "iterator", name = "iterator",
doc = "Return this iterator instance (enables `for` loops).", doc = "Return this iterator instance (enables `for` loops).",
returns = type("BytesIterator"), returns = type("BytesIterator"),
moduleName = "lyng.io.fs" moduleName = "lyng.io.fs",
) { thisObj } code = object : ScopeCallable {
override suspend fun call(scp: Scope): Obj = scp.thisObj
}
)
addFnDoc( addFnDoc(
name = "hasNext", name = "hasNext",
doc = "Whether there is another chunk available.", doc = "Whether there is another chunk available.",
returns = type("lyng.Bool"), returns = type("lyng.Bool"),
moduleName = "lyng.io.fs" moduleName = "lyng.io.fs",
) { code = object : ScopeCallable {
val self = thisAs<ObjFsBytesIterator>() override suspend fun call(scp: Scope): Obj {
(self.pos < self.data.size).toObj() val self = scp.thisAs<ObjFsBytesIterator>()
} return (self.pos < self.data.size).toObj()
}
}
)
addFnDoc( addFnDoc(
name = "next", name = "next",
doc = "Return the next chunk as a `Buffer`.", doc = "Return the next chunk as a `Buffer`.",
returns = type("lyng.Buffer"), returns = type("lyng.Buffer"),
moduleName = "lyng.io.fs" moduleName = "lyng.io.fs",
) { code = object : ScopeCallable {
val self = thisAs<ObjFsBytesIterator>() override suspend fun call(scp: Scope): Obj {
if (self.pos >= self.data.size) raiseIllegalState("iterator exhausted") val self = scp.thisAs<ObjFsBytesIterator>()
val end = minOf(self.pos + self.chunkSize, self.data.size) if (self.pos >= self.data.size) scp.raiseIllegalState("iterator exhausted")
val chunk = self.data.copyOfRange(self.pos, end) val end = minOf(self.pos + self.chunkSize, self.data.size)
self.pos = end val chunk = self.data.copyOfRange(self.pos, end)
ObjBuffer(chunk.asUByteArray()) self.pos = end
} return ObjBuffer(chunk.asUByteArray())
}
}
)
addFnDoc( addFnDoc(
name = "cancelIteration", name = "cancelIteration",
doc = "Stop the iteration early; subsequent `hasNext` returns false.", doc = "Stop the iteration early; subsequent `hasNext` returns false.",
moduleName = "lyng.io.fs" moduleName = "lyng.io.fs",
) { code = object : ScopeCallable {
val self = thisAs<ObjFsBytesIterator>() override suspend fun call(scp: Scope): Obj {
self.pos = self.data.size val self = scp.thisAs<ObjFsBytesIterator>()
ObjVoid self.pos = self.data.size
} return ObjVoid
}
}
)
} }
} }
} }
@ -573,35 +670,47 @@ class ObjFsStringChunksIterator(
name = "iterator", name = "iterator",
doc = "Return this iterator instance (enables `for` loops).", doc = "Return this iterator instance (enables `for` loops).",
returns = type("StringChunksIterator"), returns = type("StringChunksIterator"),
moduleName = "lyng.io.fs" moduleName = "lyng.io.fs",
) { thisObj } code = object : ScopeCallable {
override suspend fun call(scp: Scope): Obj = scp.thisObj
}
)
addFnDoc( addFnDoc(
name = "hasNext", name = "hasNext",
doc = "Whether there is another chunk available.", doc = "Whether there is another chunk available.",
returns = type("lyng.Bool"), returns = type("lyng.Bool"),
moduleName = "lyng.io.fs" moduleName = "lyng.io.fs",
) { code = object : ScopeCallable {
val self = thisAs<ObjFsStringChunksIterator>() override suspend fun call(scp: Scope): Obj {
(self.pos < self.text.length).toObj() val self = scp.thisAs<ObjFsStringChunksIterator>()
} return (self.pos < self.text.length).toObj()
}
}
)
addFnDoc( addFnDoc(
name = "next", name = "next",
doc = "Return the next UTF-8 chunk as a `String`.", doc = "Return the next UTF-8 chunk as a `String`.",
returns = type("lyng.String"), returns = type("lyng.String"),
moduleName = "lyng.io.fs" moduleName = "lyng.io.fs",
) { code = object : ScopeCallable {
val self = thisAs<ObjFsStringChunksIterator>() override suspend fun call(scp: Scope): Obj {
if (self.pos >= self.text.length) raiseIllegalState("iterator exhausted") val self = scp.thisAs<ObjFsStringChunksIterator>()
val end = minOf(self.pos + self.chunkChars, self.text.length) if (self.pos >= self.text.length) scp.raiseIllegalState("iterator exhausted")
val chunk = self.text.substring(self.pos, end) val end = minOf(self.pos + self.chunkChars, self.text.length)
self.pos = end val chunk = self.text.substring(self.pos, end)
ObjString(chunk) self.pos = end
} return ObjString(chunk)
}
}
)
addFnDoc( addFnDoc(
name = "cancelIteration", name = "cancelIteration",
doc = "Stop the iteration early; subsequent `hasNext` returns false.", doc = "Stop the iteration early; subsequent `hasNext` returns false.",
moduleName = "lyng.io.fs" moduleName = "lyng.io.fs",
) { ObjVoid } code = object : ScopeCallable {
override suspend fun call(scp: Scope): Obj = ObjVoid
}
)
} }
} }
} }
@ -624,46 +733,58 @@ class ObjFsLinesIterator(
name = "iterator", name = "iterator",
doc = "Return this iterator instance (enables `for` loops).", doc = "Return this iterator instance (enables `for` loops).",
returns = type("LinesIterator"), returns = type("LinesIterator"),
moduleName = "lyng.io.fs" moduleName = "lyng.io.fs",
) { thisObj } code = object : ScopeCallable {
override suspend fun call(scp: Scope): Obj = scp.thisObj
}
)
addFnDoc( addFnDoc(
name = "hasNext", name = "hasNext",
doc = "Whether another line is available.", doc = "Whether another line is available.",
returns = type("lyng.Bool"), returns = type("lyng.Bool"),
moduleName = "lyng.io.fs" moduleName = "lyng.io.fs",
) { code = object : ScopeCallable {
val self = thisAs<ObjFsLinesIterator>() override suspend fun call(scp: Scope): Obj {
self.ensureBufferFilled(this) val self = scp.thisAs<ObjFsLinesIterator>()
(self.buffer.isNotEmpty() || !self.exhausted).toObj() self.ensureBufferFilled(scp)
} return (self.buffer.isNotEmpty() || !self.exhausted).toObj()
}
}
)
addFnDoc( addFnDoc(
name = "next", name = "next",
doc = "Return the next line as `String`.", doc = "Return the next line as `String`.",
returns = type("lyng.String"), returns = type("lyng.String"),
moduleName = "lyng.io.fs" moduleName = "lyng.io.fs",
) { code = object : ScopeCallable {
val self = thisAs<ObjFsLinesIterator>() override suspend fun call(scp: Scope): Obj {
self.ensureBufferFilled(this) val self = scp.thisAs<ObjFsLinesIterator>()
if (self.buffer.isEmpty() && self.exhausted) raiseIllegalState("iterator exhausted") self.ensureBufferFilled(scp)
val idx = self.buffer.indexOf('\n') if (self.buffer.isEmpty() && self.exhausted) scp.raiseIllegalState("iterator exhausted")
val line = if (idx >= 0) { val idx = self.buffer.indexOf('\n')
val l = self.buffer.substring(0, idx) val line = if (idx >= 0) {
self.buffer = self.buffer.substring(idx + 1) val l = self.buffer.substring(0, idx)
l self.buffer = self.buffer.substring(idx + 1)
} else { l
// last line without trailing newline } else {
val l = self.buffer // last line without trailing newline
self.buffer = "" val l = self.buffer
self.exhausted = true self.buffer = ""
l self.exhausted = true
l
}
return ObjString(line)
}
} }
ObjString(line) )
}
addFnDoc( addFnDoc(
name = "cancelIteration", name = "cancelIteration",
doc = "Stop the iteration early; subsequent `hasNext` returns false.", doc = "Stop the iteration early; subsequent `hasNext` returns false.",
moduleName = "lyng.io.fs" moduleName = "lyng.io.fs",
) { ObjVoid } code = object : ScopeCallable {
override suspend fun call(scp: Scope): Obj = ObjVoid
}
)
} }
} }
} }

View File

@ -20,9 +20,11 @@ package net.sergeych.lyng.io.process
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import net.sergeych.lyng.ModuleScope import net.sergeych.lyng.ModuleScope
import net.sergeych.lyng.Scope import net.sergeych.lyng.Scope
import net.sergeych.lyng.ScopeCallable
import net.sergeych.lyng.miniast.* import net.sergeych.lyng.miniast.*
import net.sergeych.lyng.obj.* import net.sergeych.lyng.obj.*
import net.sergeych.lyng.pacman.ImportManager import net.sergeych.lyng.pacman.ImportManager
import net.sergeych.lyng.pacman.ModuleBuilder
import net.sergeych.lyng.statement import net.sergeych.lyng.statement
import net.sergeych.lyngio.process.* import net.sergeych.lyngio.process.*
import net.sergeych.lyngio.process.security.ProcessAccessDeniedException import net.sergeych.lyngio.process.security.ProcessAccessDeniedException
@ -39,9 +41,11 @@ fun createProcessModule(policy: ProcessAccessPolicy, manager: ImportManager): Bo
val name = "lyng.io.process" val name = "lyng.io.process"
if (manager.packageNames.contains(name)) return false if (manager.packageNames.contains(name)) return false
manager.addPackage(name) { module -> manager.addPackage(name, object : ModuleBuilder {
buildProcessModule(module, policy) override suspend fun build(module: ModuleScope) {
} buildProcessModule(module, policy)
}
})
return true return true
} }
@ -59,59 +63,74 @@ private suspend fun buildProcessModule(module: ModuleScope, policy: ProcessAcces
name = "stdout", name = "stdout",
doc = "Get standard output stream as a Flow of lines.", doc = "Get standard output stream as a Flow of lines.",
returns = type("lyng.Flow"), returns = type("lyng.Flow"),
moduleName = module.packageName moduleName = module.packageName,
) { code = object : ScopeCallable {
val self = thisAs<ObjRunningProcess>() override suspend fun call(scp: Scope): Obj {
self.process.stdout.toLyngFlow(this) val self = scp.thisAs<ObjRunningProcess>()
} return self.process.stdout.toLyngFlow(scp)
}
}
)
addFnDoc( addFnDoc(
name = "stderr", name = "stderr",
doc = "Get standard error stream as a Flow of lines.", doc = "Get standard error stream as a Flow of lines.",
returns = type("lyng.Flow"), returns = type("lyng.Flow"),
moduleName = module.packageName moduleName = module.packageName,
) { code = object : ScopeCallable {
val self = thisAs<ObjRunningProcess>() override suspend fun call(scp: Scope): Obj {
self.process.stderr.toLyngFlow(this) val self = scp.thisAs<ObjRunningProcess>()
} return self.process.stderr.toLyngFlow(scp)
}
}
)
addFnDoc( addFnDoc(
name = "signal", name = "signal",
doc = "Send a signal to the process (e.g. 'SIGINT', 'SIGTERM', 'SIGKILL').", doc = "Send a signal to the process (e.g. 'SIGINT', 'SIGTERM', 'SIGKILL').",
params = listOf(ParamDoc("signal", type("lyng.String"))), params = listOf(ParamDoc("signal", type("lyng.String"))),
moduleName = module.packageName moduleName = module.packageName,
) { code = object : ScopeCallable {
processGuard { override suspend fun call(scp: Scope): Obj {
val sigStr = requireOnlyArg<ObjString>().value.uppercase() return scp.processGuard {
val sig = try { val sigStr = scp.requireOnlyArg<ObjString>().value.uppercase()
ProcessSignal.valueOf(sigStr) val sig = try {
} catch (e: Exception) { ProcessSignal.valueOf(sigStr)
try { } catch (e: Exception) {
ProcessSignal.valueOf("SIG$sigStr") try {
} catch (e2: Exception) { ProcessSignal.valueOf("SIG$sigStr")
raiseIllegalArgument("Unknown signal: $sigStr") } catch (e2: Exception) {
scp.raiseIllegalArgument("Unknown signal: $sigStr")
}
}
scp.thisAs<ObjRunningProcess>().process.sendSignal(sig)
ObjVoid
} }
} }
thisAs<ObjRunningProcess>().process.sendSignal(sig)
ObjVoid
} }
} )
addFnDoc( addFnDoc(
name = "waitFor", name = "waitFor",
doc = "Wait for the process to exit and return its exit code.", doc = "Wait for the process to exit and return its exit code.",
returns = type("lyng.Int"), returns = type("lyng.Int"),
moduleName = module.packageName moduleName = module.packageName,
) { code = object : ScopeCallable {
processGuard { override suspend fun call(scp: Scope): Obj {
thisAs<ObjRunningProcess>().process.waitFor().toObj() return scp.processGuard {
scp.thisAs<ObjRunningProcess>().process.waitFor().toObj()
}
}
} }
} )
addFnDoc( addFnDoc(
name = "destroy", name = "destroy",
doc = "Forcefully terminate the process.", doc = "Forcefully terminate the process.",
moduleName = module.packageName moduleName = module.packageName,
) { code = object : ScopeCallable {
thisAs<ObjRunningProcess>().process.destroy() override suspend fun call(scp: Scope): Obj {
ObjVoid scp.thisAs<ObjRunningProcess>().process.destroy()
} return ObjVoid
}
}
)
} }
val processType = object : ObjClass("Process") {} val processType = object : ObjClass("Process") {}
@ -122,30 +141,36 @@ private suspend fun buildProcessModule(module: ModuleScope, policy: ProcessAcces
doc = "Execute a process with arguments.", doc = "Execute a process with arguments.",
params = listOf(ParamDoc("executable", type("lyng.String")), ParamDoc("args", type("lyng.List"))), params = listOf(ParamDoc("executable", type("lyng.String")), ParamDoc("args", type("lyng.List"))),
returns = type("RunningProcess"), returns = type("RunningProcess"),
moduleName = module.packageName moduleName = module.packageName,
) { code = object : ScopeCallable {
if (runner == null) raiseError("Processes are not supported on this platform") override suspend fun call(scp: Scope): Obj {
processGuard { if (runner == null) scp.raiseError("Processes are not supported on this platform")
val executable = requiredArg<ObjString>(0).value return scp.processGuard {
val args = requiredArg<ObjList>(1).list.map { it.toString() } val executable = scp.requiredArg<ObjString>(0).value
val lp = runner.execute(executable, args) val args = scp.requiredArg<ObjList>(1).list.map { it.toString() }
ObjRunningProcess(runningProcessType, lp) val lp = runner.execute(executable, args)
ObjRunningProcess(runningProcessType, lp)
}
}
} }
} )
addClassFnDoc( addClassFnDoc(
name = "shell", name = "shell",
doc = "Execute a command via system shell.", doc = "Execute a command via system shell.",
params = listOf(ParamDoc("command", type("lyng.String"))), params = listOf(ParamDoc("command", type("lyng.String"))),
returns = type("RunningProcess"), returns = type("RunningProcess"),
moduleName = module.packageName moduleName = module.packageName,
) { code = object : ScopeCallable {
if (runner == null) raiseError("Processes are not supported on this platform") override suspend fun call(scp: Scope): Obj {
processGuard { if (runner == null) scp.raiseError("Processes are not supported on this platform")
val command = requireOnlyArg<ObjString>().value return scp.processGuard {
val lp = runner.shell(command) val command = scp.requireOnlyArg<ObjString>().value
ObjRunningProcess(runningProcessType, lp) val lp = runner.shell(command)
ObjRunningProcess(runningProcessType, lp)
}
}
} }
} )
} }
val platformType = object : ObjClass("Platform") {} val platformType = object : ObjClass("Platform") {}
@ -155,24 +180,28 @@ private suspend fun buildProcessModule(module: ModuleScope, policy: ProcessAcces
name = "details", name = "details",
doc = "Get platform core details.", doc = "Get platform core details.",
returns = type("lyng.Map"), returns = type("lyng.Map"),
moduleName = module.packageName moduleName = module.packageName,
) { code = object : ScopeCallable {
val d = getPlatformDetails() override suspend fun call(scp: Scope): Obj {
ObjMap(mutableMapOf( val d = getPlatformDetails()
ObjString("name") to ObjString(d.name), return ObjMap(mutableMapOf(
ObjString("version") to ObjString(d.version), ObjString("name") to ObjString(d.name),
ObjString("arch") to ObjString(d.arch), ObjString("version") to ObjString(d.version),
ObjString("kernelVersion") to (d.kernelVersion?.toObj() ?: ObjNull) ObjString("arch") to ObjString(d.arch),
)) ObjString("kernelVersion") to (d.kernelVersion?.toObj() ?: ObjNull)
} ))
}
}
)
addClassFnDoc( addClassFnDoc(
name = "isSupported", name = "isSupported",
doc = "Check if processes are supported on this platform.", doc = "Check if processes are supported on this platform.",
returns = type("lyng.Bool"), returns = type("lyng.Bool"),
moduleName = module.packageName moduleName = module.packageName,
) { code = object : ScopeCallable {
isProcessSupported().toObj() override suspend fun call(scp: Scope): Obj = isProcessSupported().toObj()
} }
)
} }
module.addConstDoc( module.addConstDoc(
@ -216,19 +245,21 @@ private suspend inline fun Scope.processGuard(crossinline block: suspend () -> O
} }
private fun Flow<String>.toLyngFlow(flowScope: Scope): ObjFlow { private fun Flow<String>.toLyngFlow(flowScope: Scope): ObjFlow {
val producer = statement { val producer = statement(f = object : ScopeCallable {
val builder = (this as? net.sergeych.lyng.ClosureScope)?.callScope?.thisObj as? ObjFlowBuilder override suspend fun call(scp: Scope): Obj {
?: this.thisObj as? ObjFlowBuilder val builder = (scp as? net.sergeych.lyng.ClosureScope)?.callScope?.thisObj as? ObjFlowBuilder
?: scp.thisObj as? ObjFlowBuilder
this@toLyngFlow.collect { this@toLyngFlow.collect {
try { try {
builder?.output?.send(ObjString(it)) builder?.output?.send(ObjString(it))
} catch (e: Exception) { } catch (e: Exception) {
// Channel closed or other error, stop collecting // Channel closed or other error, stop collecting
return@collect return@collect
}
} }
return ObjVoid
} }
ObjVoid })
}
return ObjFlow(producer, flowScope) return ObjFlow(producer, flowScope)
} }

View File

@ -154,69 +154,73 @@ data class ArgsDeclaration(val params: List<Item>, val endTokenType: Token.Type)
} }
} }
// Helper: assign head part, consuming from headPos; stop at ellipsis // Locate ellipsis index within considered parameters
suspend fun processHead(index: Int, headPos: Int): Pair<Int, Int> { val ellipsisIndex = params.subList(0, paramsSize).indexOfFirst { it.isEllipsis }
var i = index
var hp = headPos 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) { while (i < paramsSize) {
val a = params[i] val a = params[i]
if (a.isEllipsis) break if (a.isEllipsis) break
if (assignedByName[i]) { if (assignedByName[i]) {
assign(a, namedValues[i]!!) assign(a, namedValues[i]!!)
} else { } else {
val value = if (hp < callArgs.size) callArgs[hp++] val value = if (headPos < callArgs.size) callArgs[headPos++]
else a.defaultValue?.execute(scope) else a.defaultValue?.execute(scope)
?: scope.raiseIllegalArgument("too few arguments for the call (missing ${a.name})") ?: scope.raiseIllegalArgument("too few arguments for the call (missing ${a.name})")
assign(a, value) assign(a, value)
} }
i++ 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 // Then assign tail consuming from the end down to headConsumedTo boundary
// Do not consume elements below headPosBound to avoid overlap with head consumption i = paramsSize - 1
suspend fun processTail(startExclusive: Int, tailStart: Int, headPosBound: Int): Int { var tp = tailPos
var i = paramsSize - 1 while (i > ellipsisIndex) {
var tp = tailStart
while (i > startExclusive) {
val a = params[i] val a = params[i]
if (a.isEllipsis) break if (a.isEllipsis) break
if (i < assignedByName.size && assignedByName[i]) { if (i < assignedByName.size && assignedByName[i]) {
assign(a, namedValues[i]!!) assign(a, namedValues[i]!!)
} else { } else {
val value = if (tp >= headPosBound) callArgs[tp--] val value = if (tp >= headConsumedTo) callArgs[tp--]
else a.defaultValue?.execute(scope) else a.defaultValue?.execute(scope)
?: scope.raiseIllegalArgument("too few arguments for the call") ?: scope.raiseIllegalArgument("too few arguments for the call")
assign(a, value) assign(a, value)
} }
i-- i--
} }
return tp val tailConsumedFrom = tp
}
fun processEllipsis(index: Int, headPos: Int, tailPos: Int) { // Assign ellipsis list from remaining positionals between headConsumedTo..tailConsumedFrom
val a = params[index] val a = params[ellipsisIndex]
val from = headPos val from = headConsumedTo
val to = tailPos val to = tailConsumedFrom
val l = if (from > to) ObjList() val l = if (from > to) ObjList()
else ObjList(callArgs.subList(from, to + 1).toMutableList()) else ObjList(callArgs.subList(from, to + 1).toMutableList())
assign(a, l) 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 { } else {
// No ellipsis: assign head only; any leftover positionals → error // 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) if (headConsumedTo != callArgs.size)
scope.raiseIllegalArgument("too many arguments for the call") scope.raiseIllegalArgument("too many arguments for the call")
} }

View File

@ -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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with 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]. * Convert to list of kotlin objects, see [Obj.toKotlin].
*/ */
suspend fun toKotlinList(scope: Scope): List<Any?> { suspend fun toKotlinList(scope: Scope): List<Any?> {
return list.map { it.toKotlin(scope) } val res = ArrayList<Any?>(list.size)
for (i in list) res.add(i.toKotlin(scope))
return res
}
suspend fun inspect(scope: Scope): String {
val res = ArrayList<String>(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 { companion object {
val EMPTY = Arguments(emptyList()) val EMPTY = Arguments(emptyList())

File diff suppressed because it is too large Load Diff

View File

@ -21,6 +21,23 @@ import net.sergeych.lyng.obj.*
import net.sergeych.lyng.pacman.ImportManager import net.sergeych.lyng.pacman.ImportManager
import net.sergeych.lyng.pacman.ImportProvider import net.sergeych.lyng.pacman.ImportProvider
interface ScopeCallable {
suspend fun call(scope: Scope): Obj
}
interface VoidScopeCallable {
suspend fun call(scope: Scope)
}
interface ScopeBlock<R> {
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) // Simple per-frame id generator for perf caches (not thread-safe, fine for scripts)
object FrameIdGen { var c: Long = 1L; fun nextId(): Long = c++ } object FrameIdGen { var c: Long = 1L; fun nextId(): Long = c++ }
fun nextFrameId(): Long = FrameIdGen.nextId() 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]. * 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. * Currently always creates a fresh child scope to preserve unique frameId semantics.
*/ */
inline suspend fun <R> withChildFrame(args: Arguments = Arguments.EMPTY, newThisObj: Obj? = null, crossinline block: suspend (Scope) -> R): R { suspend fun <R> withChildFrame(args: Arguments = Arguments.EMPTY, newThisObj: Obj? = null, block: ScopeBlock<R>): R {
if (PerfFlags.SCOPE_POOL) { if (PerfFlags.SCOPE_POOL) {
val child = ScopePool.borrow(this, args, pos, newThisObj ?: thisObj) val child = ScopePool.borrow(this, args, pos, newThisObj ?: thisObj)
try { try {
return block(child) return block.call(child)
} finally { } finally {
ScopePool.release(child) ScopePool.release(child)
} }
} else { } else {
val child = createChildScope(args, newThisObj) val child = createChildScope(args, newThisObj)
return block(child) return block.call(child)
} }
} }
@ -563,20 +580,17 @@ open class Scope(
return ns.objClass return ns.objClass
} }
inline fun addVoidFn(vararg names: String, crossinline fn: suspend Scope.() -> Unit) { fun addVoidFn(vararg names: String, fn: VoidScopeCallable) {
addFn(*names) { addFn(*names, fn = object : ScopeCallable {
fn(this) override suspend fun call(scope: Scope): Obj {
ObjVoid fn.call(scope)
} return ObjVoid
}
})
} }
fun addFn(vararg names: String, fn: suspend Scope.() -> Obj) { fun addFn(vararg names: String, fn: ScopeCallable) {
val newFn = object : Statement() { val newFn = FnStatement(fn)
override val pos: Pos = Pos.builtIn
override suspend fun execute(scope: Scope): Obj = scope.fn()
}
for (name in names) { for (name in names) {
addItem( addItem(
name, name,

View File

@ -18,14 +18,13 @@
package net.sergeych.lyng package net.sergeych.lyng
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import kotlinx.coroutines.yield
import net.sergeych.lyng.Script.Companion.defaultImportManager import net.sergeych.lyng.Script.Companion.defaultImportManager
import net.sergeych.lyng.miniast.* import net.sergeych.lyng.miniast.*
import net.sergeych.lyng.obj.* import net.sergeych.lyng.obj.*
import net.sergeych.lyng.pacman.ImportManager import net.sergeych.lyng.pacman.ImportManager
import net.sergeych.lyng.pacman.ModuleBuilder
import net.sergeych.lyng.stdlib_included.rootLyng import net.sergeych.lyng.stdlib_included.rootLyng
import net.sergeych.lynon.ObjLynonClass import net.sergeych.lynon.ObjLynonClass
import net.sergeych.mp_tools.globalDefer
import kotlin.math.* import kotlin.math.*
@Suppress("TYPE_INTERSECTION_AS_REIFIED_WARNING") @Suppress("TYPE_INTERSECTION_AS_REIFIED_WARNING")
@ -59,186 +58,173 @@ class Script(
internal val rootScope: Scope = Scope(null).apply { internal val rootScope: Scope = Scope(null).apply {
ObjException.addExceptionsToContext(this) ObjException.addExceptionsToContext(this)
addConst("Unset", ObjUnset) addConst("Unset", ObjUnset)
addFn("print") { addFn("print", fn = object : ScopeCallable {
for ((i, a) in args.withIndex()) { override suspend fun call(scp: Scope): Obj {
if (i > 0) print(' ' + a.toString(this).value) for ((i, a) in scp.args.withIndex()) {
else print(a.toString(this).value) if (i > 0) print(' ' + a.toString(scp).value)
else print(a.toString(scp).value)
}
return ObjVoid
} }
ObjVoid })
} addFn("println", fn = object : ScopeCallable {
addFn("println") { override suspend fun call(scp: Scope): Obj {
for ((i, a) in args.withIndex()) { for ((i, a) in scp.args.withIndex()) {
if (i > 0) print(' ' + a.toString(this).value) if (i > 0) print(' ' + a.toString(scp).value)
else print(a.toString(this).value) else print(a.toString(scp).value)
}
println()
return ObjVoid
} }
println() })
ObjVoid addFn("floor", fn = object : ScopeCallable {
} override suspend fun call(scp: Scope): Obj {
addFn("floor") { val x = scp.args.firstAndOnly()
val x = args.firstAndOnly() return (if (x is ObjInt) x
(if (x is ObjInt) x else ObjReal(floor(x.toDouble())))
else ObjReal(floor(x.toDouble()))) }
} })
addFn("ceil") { addFn("ceil", fn = object : ScopeCallable {
val x = args.firstAndOnly() override suspend fun call(scp: Scope): Obj {
(if (x is ObjInt) x val x = scp.args.firstAndOnly()
else ObjReal(ceil(x.toDouble()))) return (if (x is ObjInt) x
} else ObjReal(ceil(x.toDouble())))
addFn("round") { }
val x = args.firstAndOnly() })
(if (x is ObjInt) x addFn("round", fn = object : ScopeCallable {
else ObjReal(round(x.toDouble()))) 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") { addFn("sin", fn = object : ScopeCallable { override suspend fun call(scp: Scope): Obj = ObjReal(sin(scp.args.firstAndOnly().toDouble())) })
ObjReal(sin(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("cos") { addFn("asin", fn = object : ScopeCallable { override suspend fun call(scp: Scope): Obj = ObjReal(asin(scp.args.firstAndOnly().toDouble())) })
ObjReal(cos(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("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("sinh") { addFn("sinh", fn = object : ScopeCallable { override suspend fun call(scp: Scope): Obj = ObjReal(sinh(scp.args.firstAndOnly().toDouble())) })
ObjReal(sinh(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("cosh") { addFn("asinh", fn = object : ScopeCallable { override suspend fun call(scp: Scope): Obj = ObjReal(asinh(scp.args.firstAndOnly().toDouble())) })
ObjReal(cosh(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("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("exp") { addFn("exp", fn = object : ScopeCallable { override suspend fun call(scp: Scope): Obj = ObjReal(exp(scp.args.firstAndOnly().toDouble())) })
ObjReal(exp(args.firstAndOnly().toDouble())) addFn("ln", fn = object : ScopeCallable { override suspend fun call(scp: Scope): Obj = ObjReal(ln(scp.args.firstAndOnly().toDouble())) })
}
addFn("ln") {
ObjReal(ln(args.firstAndOnly().toDouble()))
}
addFn("log10") { addFn("log10", fn = object : ScopeCallable { override suspend fun call(scp: Scope): Obj = ObjReal(log10(scp.args.firstAndOnly().toDouble())) })
ObjReal(log10(args.firstAndOnly().toDouble()))
}
addFn("log2") { addFn("log2", fn = object : ScopeCallable { override suspend fun call(scp: Scope): Obj = ObjReal(log2(scp.args.firstAndOnly().toDouble())) })
ObjReal(log2(args.firstAndOnly().toDouble()))
}
addFn("pow") { addFn("pow", fn = object : ScopeCallable {
requireExactCount(2) override suspend fun call(scp: Scope): Obj {
ObjReal( scp.requireExactCount(2)
(args[0].toDouble()).pow(args[1].toDouble()) return ObjReal((scp.args[0].toDouble()).pow(scp.args[1].toDouble()))
) }
} })
addFn("sqrt") { addFn("sqrt", fn = object : ScopeCallable { override suspend fun call(scp: Scope): Obj = ObjReal(sqrt(scp.args.firstAndOnly().toDouble())) })
ObjReal( addFn("abs", fn = object : ScopeCallable {
sqrt(args.firstAndOnly().toDouble()) 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)
addFn("abs") { }
val x = args.firstAndOnly() })
if (x is ObjInt) ObjInt(x.value.absoluteValue) else ObjReal(x.toDouble().absoluteValue)
}
addFnDoc( addFnDoc<Obj>(
"clamp", "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.", 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")), params = listOf(ParamDoc("value"), ParamDoc("range")),
moduleName = "lyng.stdlib" moduleName = "lyng.stdlib",
) { fn = object : ScopeCallable {
val value = requiredArg<Obj>(0) override suspend fun call(scp: Scope): Obj {
val range = requiredArg<ObjRange>(1) val value = scp.requiredArg<Obj>(0)
val range = scp.requiredArg<ObjRange>(1)
var result = value
if (range.start != null && !range.start.isNull) { var result = value
if (result.compareTo(this, range.start) < 0) { if (range.start != null && !range.start.isNull) {
result = range.start if (result.compareTo(scp, 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
} }
} }
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") { addVoidFn("assert", fn = object : VoidScopeCallable {
val cond = requiredArg<ObjBool>(0) override suspend fun call(scp: Scope) {
val message = if (args.size > 1) val cond = scp.requiredArg<ObjBool>(0)
": " + (args[1] as Statement).execute(this).toString(this).value val message = if (scp.args.size > 1)
else "" ": " + (scp.args[1] as Statement).execute(scp).toString(scp).value
if (!cond.value == true) else ""
raiseError(ObjAssertionFailedException(this, "Assertion failed$message")) if (!cond.value == true)
} scp.raiseError(ObjAssertionFailedException(scp, "Assertion failed$message"))
}
})
addVoidFn("assertEquals") { addVoidFn("assertEquals", fn = object : VoidScopeCallable {
val a = requiredArg<Obj>(0) override suspend fun call(scp: Scope) {
val b = requiredArg<Obj>(1) val a = scp.requiredArg<Obj>(0)
if (a.compareTo(this, b) != 0) val b = scp.requiredArg<Obj>(1)
raiseError( if (a.compareTo(scp, b) != 0)
ObjAssertionFailedException( scp.raiseError(
this, ObjAssertionFailedException(
"Assertion failed: ${a.inspect(this)} == ${b.inspect(this)}" scp,
"Assertion failed: ${a.inspect(scp)} == ${b.inspect(scp)}"
)
) )
) }
} })
// alias used in tests // alias used in tests
addVoidFn("assertEqual") { addVoidFn("assertEqual", fn = object : VoidScopeCallable {
val a = requiredArg<Obj>(0) override suspend fun call(scp: Scope) {
val b = requiredArg<Obj>(1) val a = scp.requiredArg<Obj>(0)
if (a.compareTo(this, b) != 0) val b = scp.requiredArg<Obj>(1)
raiseError( if (a.compareTo(scp, b) != 0)
ObjAssertionFailedException( scp.raiseError(
this, ObjAssertionFailedException(
"Assertion failed: ${a.inspect(this)} == ${b.inspect(this)}" scp,
"Assertion failed: ${a.inspect(scp)} == ${b.inspect(scp)}"
)
) )
) }
} })
addVoidFn("assertNotEquals") { addVoidFn("assertNotEquals", fn = object : VoidScopeCallable {
val a = requiredArg<Obj>(0) override suspend fun call(scp: Scope) {
val b = requiredArg<Obj>(1) val a = scp.requiredArg<Obj>(0)
if (a.compareTo(this, b) == 0) val b = scp.requiredArg<Obj>(1)
raiseError( if (a.compareTo(scp, b) == 0)
ObjAssertionFailedException( scp.raiseError(
this, ObjAssertionFailedException(
"Assertion failed: ${a.inspect(this)} != ${b.inspect(this)}" scp,
"Assertion failed: ${a.inspect(scp)} != ${b.inspect(scp)}"
)
) )
) }
} })
addFnDoc( addFnDoc<Obj>(
"assertThrows", "assertThrows",
doc = """ doc = """
Asserts that the provided code block throws an exception, with or without exception: 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, 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 it checks that the thrown exception is of that class. If no expected class is provided, any exception
will be accepted. will be accepted.
""".trimIndent() """.trimIndent(),
) { fn = object : ScopeCallable {
val code: Statement override suspend fun call(scp: Scope): Obj {
val expectedClass: ObjClass? val code: Statement
when (args.size) { val expectedClass: ObjClass?
1 -> { when (scp.args.size) {
code = requiredArg<Statement>(0) 1 -> {
expectedClass = null code = scp.requiredArg<Statement>(0)
} expectedClass = null
}
2 -> { 2 -> {
code = requiredArg<Statement>(1) code = scp.requiredArg<Statement>(1)
expectedClass = requiredArg<ObjClass>(0) expectedClass = scp.requiredArg<ObjClass>(0)
} }
else -> raiseIllegalArgument("Expected 1 or 2 arguments, got ${args.size}") else -> scp.raiseIllegalArgument("Expected 1 or 2 arguments, got ${scp.args.size}")
} }
val result = try { val result = try {
code.execute(this) code.execute(scp)
null null
} catch (e: ExecutionError) { } catch (e: ExecutionError) {
e.errorObject e.errorObject
} catch (_: ScriptError) { } catch (_: ScriptError) {
ObjNull ObjNull
} }
if (result == null) raiseError( if (result == null) scp.raiseError(
ObjAssertionFailedException( ObjAssertionFailedException(
this, scp,
"Expected exception but nothing was thrown" "Expected exception but nothing was thrown"
) )
) )
expectedClass?.let { expectedClass?.let {
if (!result.isInstanceOf(it)) { if (!result.isInstanceOf(it)) {
val actual = if (result is ObjException) result.exceptionClass else result.objClass val actual = if (result is ObjException) result.exceptionClass else result.objClass
raiseError("Expected $it, got $actual") scp.raiseError("Expected $it, got $actual")
}
}
return result ?: ObjNull
} }
} }
result )
}
addFn("dynamic") { addFn("dynamic", fn = object : ScopeCallable {
ObjDynamic.create(this, requireOnlyArg()) override suspend fun call(scp: Scope): Obj {
} return ObjDynamic.create(scp, scp.requireOnlyArg())
}
})
val root = this val root = this
val mathClass = ObjClass("Math").apply { val mathClass = ObjClass("Math").apply {
addFn("sqrt") { addFn("sqrt", fn = object : ScopeCallable {
ObjReal(sqrt(args.firstAndOnly().toDouble())) override suspend fun call(scp: Scope): Obj {
} return ObjReal(sqrt(scp.args.firstAndOnly().toDouble()))
}
})
} }
addItem("Math", false, ObjInstance(mathClass).apply { addItem("Math", false, ObjInstance(mathClass).apply {
instanceScope = Scope(root, thisObj = this) instanceScope = Scope(root, thisObj = this)
}) })
addFn("require") { addFn("require", fn = object : ScopeCallable {
val condition = requiredArg<ObjBool>(0) override suspend fun call(scp: Scope): Obj {
if (!condition.value) { val condition = scp.requiredArg<ObjBool>(0)
var message = args.list.getOrNull(1) if (!condition.value) {
if (message is Statement) message = message.execute(this) var message = scp.args.list.getOrNull(1)
raiseIllegalArgument(message?.toString() ?: "requirement not met") if (message is Statement) message = message.execute(scp)
scp.raiseIllegalArgument(message?.toString() ?: "requirement not met")
}
return ObjVoid
} }
ObjVoid })
} addFn("check", fn = object : ScopeCallable {
addFn("check") { override suspend fun call(scp: Scope): Obj {
val condition = requiredArg<ObjBool>(0) val condition = scp.requiredArg<ObjBool>(0)
if (!condition.value) { if (!condition.value) {
var message = args.list.getOrNull(1) var message = scp.args.list.getOrNull(1)
if (message is Statement) message = message.execute(this) if (message is Statement) message = message.execute(scp)
raiseIllegalState(message?.toString() ?: "check failed") scp.raiseIllegalState(message?.toString() ?: "check failed")
}
return ObjVoid
} }
ObjVoid })
} addFn("traceScope", fn = object : ScopeCallable {
addFn("traceScope") { override suspend fun call(scp: Scope): Obj {
this.trace(args.getOrNull(0)?.toString() ?: "") scp.trace(scp.args.getOrNull(0)?.toString() ?: "")
ObjVoid return ObjVoid
} }
})
/*
addVoidFn("delay") { addVoidFn("delay") {
val a = args.firstAndOnly() val a = args.firstAndOnly()
when (a) { when (a) {
@ -335,7 +335,7 @@ class Script(
else -> raiseIllegalArgument("Expected Int, Real or Duration, got ${a.inspect(this)}") else -> raiseIllegalArgument("Expected Int, Real or Duration, got ${a.inspect(this)}")
} }
} }
*/
addConst("Object", rootObjectType) addConst("Object", rootObjectType)
addConst("Real", ObjReal.type) addConst("Real", ObjReal.type)
addConst("String", ObjString.type) addConst("String", ObjString.type)
@ -362,7 +362,7 @@ class Script(
addConst("Flow", ObjFlow.type) addConst("Flow", ObjFlow.type)
addConst("Regex", ObjRegex.type) addConst("Regex", ObjRegex.type)
/*
addFn("launch") { addFn("launch") {
val callable = requireOnlyArg<Statement>() val callable = requireOnlyArg<Statement>()
ObjDeferred(globalDefer { ObjDeferred(globalDefer {
@ -380,7 +380,7 @@ class Script(
// we'll need it for the producer // we'll need it for the producer
ObjFlow(requireOnlyArg<Statement>(), this) ObjFlow(requireOnlyArg<Statement>(), this)
} }
*/
val pi = ObjReal(PI) val pi = ObjReal(PI)
addConstDoc( addConstDoc(
name = "π", name = "π",
@ -403,60 +403,69 @@ class Script(
addTextPackages( addTextPackages(
rootLyng rootLyng
) )
addPackage("lyng.buffer") { addPackage("lyng.buffer", object : ModuleBuilder {
it.addConstDoc( override suspend fun build(ms: ModuleScope) {
name = "Buffer", ms.addConstDoc(
value = ObjBuffer.type, name = "Buffer",
doc = "Immutable sequence of bytes. Use for binary data and IO.", value = ObjBuffer.type,
type = type("lyng.Class") doc = "Immutable sequence of bytes. Use for binary data and IO.",
) type = type("lyng.Class")
it.addConstDoc( )
name = "MutableBuffer", ms.addConstDoc(
value = ObjMutableBuffer.type, name = "MutableBuffer",
doc = "Mutable byte buffer. Supports in-place modifications.", value = ObjMutableBuffer.type,
type = type("lyng.Class") 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.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)}")
}
}
}
)
}
})
} }
} }

View File

@ -17,9 +17,7 @@
package net.sergeych.lyng.miniast package net.sergeych.lyng.miniast
import net.sergeych.lyng.ModuleScope import net.sergeych.lyng.*
import net.sergeych.lyng.Scope
import net.sergeych.lyng.Visibility
import net.sergeych.lyng.obj.Obj import net.sergeych.lyng.obj.Obj
import net.sergeych.lyng.obj.ObjClass import net.sergeych.lyng.obj.ObjClass
import net.sergeych.lyng.obj.ObjVoid import net.sergeych.lyng.obj.ObjVoid
@ -39,10 +37,10 @@ inline fun <reified T : Obj> Scope.addFnDoc(
returns: TypeDoc? = null, returns: TypeDoc? = null,
tags: Map<String, List<String>> = emptyMap(), tags: Map<String, List<String>> = emptyMap(),
moduleName: String? = null, moduleName: String? = null,
crossinline fn: suspend Scope.() -> T fn: ScopeCallable
) { ) {
// Register runtime function(s) // Register runtime function(s)
addFn(*names) { fn() } addFn(*names, fn = fn)
// Determine module // Determine module
val mod = moduleName ?: findModuleNameOrUnknown() val mod = moduleName ?: findModuleNameOrUnknown()
// Register docs once per name // Register docs once per name
@ -56,7 +54,7 @@ inline fun Scope.addVoidFnDoc(
doc: String, doc: String,
tags: Map<String, List<String>> = emptyMap(), tags: Map<String, List<String>> = emptyMap(),
moduleName: String? = null, moduleName: String? = null,
crossinline fn: suspend Scope.() -> Unit fn: VoidScopeCallable
) { ) {
addFnDoc<ObjVoid>( addFnDoc<ObjVoid>(
*names, *names,
@ -64,11 +62,14 @@ inline fun Scope.addVoidFnDoc(
params = emptyList(), params = emptyList(),
returns = null, returns = null,
tags = tags, tags = tags,
moduleName = moduleName moduleName = moduleName,
) { fn = object : ScopeCallable {
fn(this) override suspend fun call(sc: Scope): Obj {
ObjVoid fn.call(sc)
} return ObjVoid
}
}
)
} }
fun Scope.addConstDoc( fun Scope.addConstDoc(
@ -97,7 +98,7 @@ fun ObjClass.addFnDoc(
visibility: Visibility = Visibility.Public, visibility: Visibility = Visibility.Public,
tags: Map<String, List<String>> = emptyMap(), tags: Map<String, List<String>> = emptyMap(),
moduleName: String? = null, moduleName: String? = null,
code: suspend Scope.() -> Obj code: ScopeCallable
) { ) {
// Register runtime method // Register runtime method
addFn(name, isOpen, visibility, code = code) addFn(name, isOpen, visibility, code = code)
@ -135,7 +136,7 @@ fun ObjClass.addClassFnDoc(
isOpen: Boolean = false, isOpen: Boolean = false,
tags: Map<String, List<String>> = emptyMap(), tags: Map<String, List<String>> = emptyMap(),
moduleName: String? = null, moduleName: String? = null,
code: suspend Scope.() -> Obj code: ScopeCallable
) { ) {
addClassFn(name, isOpen, code) addClassFn(name, isOpen, code)
BuiltinDocRegistry.module(moduleName ?: ownerModuleNameFromClassOrUnknown()) { BuiltinDocRegistry.module(moduleName ?: ownerModuleNameFromClassOrUnknown()) {
@ -151,8 +152,8 @@ fun ObjClass.addPropertyDoc(
type: TypeDoc? = null, type: TypeDoc? = null,
visibility: Visibility = Visibility.Public, visibility: Visibility = Visibility.Public,
moduleName: String? = null, moduleName: String? = null,
getter: (suspend Scope.() -> Obj)? = null, getter: ScopeCallable? = null,
setter: (suspend Scope.(Obj) -> Unit)? = null setter: ScopeCallable? = null
) { ) {
addProperty(name, getter, setter, visibility) addProperty(name, getter, setter, visibility)
BuiltinDocRegistry.module(moduleName ?: ownerModuleNameFromClassOrUnknown()) { BuiltinDocRegistry.module(moduleName ?: ownerModuleNameFromClassOrUnknown()) {

View File

@ -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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -32,29 +32,41 @@ typealias DocCompiler = Compiler
*/ */
typealias Accessor = ObjRef 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. */ /** Lambda-based reference for edge cases that still construct access via lambdas. */
private class LambdaRef( private class LambdaRef(
private val getterFn: suspend (Scope) -> ObjRecord, private val getterFn: AccessorGetter,
private val setterFn: (suspend (Pos, Scope, Obj) -> Unit)? = null private val setterFn: AccessorSetter? = null
) : ObjRef { ) : 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) { override suspend fun setAt(pos: Pos, scope: Scope, newValue: Obj) {
val s = setterFn ?: throw ScriptError(pos, "can't assign value") 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 { ... }` // 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( fun Accessor(
getter: suspend (Scope) -> ObjRecord, getter: AccessorGetter,
setter: suspend (Scope, Obj) -> Unit setter: AccessorSetter
): Accessor = LambdaRef(getter) { _, scope, value -> setter(scope, value) } ): Accessor = LambdaRef(getter, setter)
// Compatibility shims used throughout Compiler: `.getter(...)` and `.setter(pos)` // Compatibility shims used throughout Compiler: `.getter(...)` and `.setter(pos)`
val Accessor.getter: suspend (Scope) -> ObjRecord val Accessor.getter: AccessorGetter
get() = { scope -> this.get(scope) } 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 -> fun Accessor.setter(pos: Pos): AccessorSetter = object : AccessorSetter {
this.setAt(pos, scope, newValue) override suspend fun call(scope: Scope, value: Obj) {
this@setter.setAt(pos, scope, value)
}
} }

View File

@ -32,6 +32,14 @@ import net.sergeych.lynon.LynonDecoder
import net.sergeych.lynon.LynonEncoder import net.sergeych.lynon.LynonEncoder
import net.sergeych.lynon.LynonType 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 class Obj {
open val isConst: Boolean = false open val isConst: Boolean = false
@ -91,7 +99,7 @@ open class Obj {
scope: Scope, scope: Scope,
name: String, name: String,
args: Arguments = Arguments.EMPTY, args: Arguments = Arguments.EMPTY,
onNotFoundResult: (suspend () -> Obj?)? = null onNotFoundResult: OnNotFound? = null
): Obj { ): Obj {
// 0. Prefer private member of current class context // 0. Prefer private member of current class context
scope.currentClassCtx?.let { caller -> scope.currentClassCtx?.let { caller ->
@ -100,7 +108,7 @@ open class Obj {
if (rec.type == ObjRecord.Type.Property) { if (rec.type == ObjRecord.Type.Property) {
if (args.isEmpty()) return (rec.value as ObjProperty).callGetter(scope, this, caller) if (args.isEmpty()) return (rec.value as ObjProperty).callGetter(scope, this, caller)
} else if (rec.type != ObjRecord.Type.Delegated) { } 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 decl = rec.declaringClass ?: cls
val caller = scope.currentClassCtx val caller = scope.currentClassCtx
if (!canAccessMember(rec.visibility, decl, caller, name)) 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 (rec.type == ObjRecord.Type.Property) {
if (args.isEmpty()) return (rec.value as ObjProperty).callGetter(scope, this, decl) if (args.isEmpty()) return (rec.value as ObjProperty).callGetter(scope, this, decl)
} else if (rec.type != ObjRecord.Type.Delegated) { } 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 (extension.type == ObjRecord.Type.Property) {
if (args.isEmpty()) return (extension.value as ObjProperty).callGetter(scope, this, extension.declaringClass) if (args.isEmpty()) return (extension.value as ObjProperty).callGetter(scope, this, extension.declaringClass)
} else if (extension.type != ObjRecord.Type.Delegated) { } 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 decl = rec.declaringClass ?: cls
val caller = scope.currentClassCtx val caller = scope.currentClassCtx
if (!canAccessMember(rec.visibility, decl, caller, name)) 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 (rec.type == ObjRecord.Type.Property) {
if (args.isEmpty()) return (rec.value as ObjProperty).callGetter(scope, this, decl) if (args.isEmpty()) return (rec.value as ObjProperty).callGetter(scope, this, decl)
} else if (rec.type != ObjRecord.Type.Delegated) { } 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( ?: scope.raiseError(
"no such member: $name on ${objClass.className}. Considered order: ${objClass.renderLinearization(true)}. " + "no such member: $name on ${objClass.className}. Considered order: ${objClass.renderLinearization(true)}. " +
"Tip: try this@Base.$name(...) or (obj as Base).$name(...) if ambiguous" "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 { open suspend fun compareTo(scope: Scope, other: Obj): Int {
if (other === this) return 0 if (other === this) return 0
if (other === ObjNull || other === ObjUnset || other === ObjVoid) return 2 if (other === ObjNull || other === ObjUnset || other === ObjVoid) return 2
return invokeInstanceMethod(scope, "compareTo", Arguments(other)) { return invokeInstanceMethod(scope, "compareTo", Arguments(other), onNotFoundResult = object : OnNotFound {
scope.raiseNotImplemented("compareTo for ${objClass.className}") override suspend fun call(): Obj? {
}.cast<ObjInt>(scope).toInt() scope.raiseNotImplemented("compareTo for ${objClass.className}")
}
}).toInt()
} }
open suspend fun equals(scope: Scope, other: Obj): Boolean { open suspend fun equals(scope: Scope, other: Obj): Boolean {
@ -202,16 +212,16 @@ open class Obj {
* *
* IF callback returns false, iteration is stopped. * 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 iterator = invokeInstanceMethod(scope, "iterator")
val hasNext = iterator.getInstanceMethod(scope, "hasNext") val hasNext = iterator.getInstanceMethod(scope, "hasNext")
val next = iterator.getInstanceMethod(scope, "next") val next = iterator.getInstanceMethod(scope, "next")
var closeIt = false var closeIt = false
try { try {
while (hasNext.invoke(scope, iterator).toBool()) { while (hasNext.invokeCallable(scope, iterator).toBool()) {
val nextValue = next.invoke(scope, iterator) val nextValue = next.invokeCallable(scope, iterator)
val shouldContinue = try { val shouldContinue = try {
callback(nextValue) callback.call(nextValue)
} catch (e: Exception) { } catch (e: Exception) {
// iteration aborted due to exception in callback // iteration aborted due to exception in callback
closeIt = true closeIt = true
@ -448,7 +458,7 @@ open class Obj {
if (rec.visibility == Visibility.Private && !rec.isAbstract) { if (rec.visibility == Visibility.Private && !rec.isAbstract) {
val resolved = resolveRecord(scope, rec, name, caller) val resolved = resolveRecord(scope, rec, name, caller)
if (resolved.type == ObjRecord.Type.Fun && resolved.value is Statement) 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 return resolved
} }
} }
@ -462,7 +472,7 @@ open class Obj {
val decl = rec.declaringClass ?: cls val decl = rec.declaringClass ?: cls
val resolved = resolveRecord(scope, rec, name, decl) val resolved = resolveRecord(scope, rec, name, decl)
if (resolved.type == ObjRecord.Type.Fun && resolved.value is Statement) 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 return resolved
} }
} }
@ -472,7 +482,7 @@ open class Obj {
if (extension != null) { if (extension != null) {
val resolved = resolveRecord(scope, extension, name, extension.declaringClass) val resolved = resolveRecord(scope, extension, name, extension.declaringClass)
if (resolved.type == ObjRecord.Type.Fun && resolved.value is Statement) 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 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 ?: "?"})")) 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) val resolved = resolveRecord(scope, rec, name, decl)
if (resolved.type == ObjRecord.Type.Fun && resolved.value is Statement) 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 return resolved
} }
} }
@ -502,13 +512,13 @@ open class Obj {
val del = obj.delegate ?: scope.raiseError("Internal error: delegated property $name has no delegate") val del = obj.delegate ?: scope.raiseError("Internal error: delegated property $name has no delegate")
val th = if (this === ObjVoid) ObjNull else this val th = if (this === ObjVoid) ObjNull else this
val res = del.invokeInstanceMethod(scope, "getValue", Arguments(th, ObjString(name)), onNotFoundResult = { 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() { object : Statement() {
override val pos: Pos = Pos.builtIn override val pos: Pos = Pos.builtIn
override suspend fun execute(s: Scope): Obj { override suspend fun execute(s: Scope): Obj {
val th2 = if (s.thisObj === ObjVoid) ObjNull else s.thisObj val th2 = if (s.thisObj === ObjVoid) ObjNull else s.thisObj
val allArgs = (listOf(th2, ObjString(name)) + s.args.list).toTypedArray() 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() 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) if (PerfFlags.SCOPE_POOL)
scope.withChildFrame(args, newThisObj = thisObj) { child -> scope.withChildFrame(args, newThisObj = thisObj, block = object : ScopeBlock<Obj> {
if (declaringClass != null) child.currentClassCtx = declaringClass override suspend fun call(child: Scope): Obj {
callOn(child) if (declaringClass != null) child.currentClassCtx = declaringClass
} return callOn(child)
}
})
else else
callOn(scope.createChildScope(scope.pos, args = args, newThisObj = thisObj).also { callOn(scope.createChildScope(scope.pos, args = args, newThisObj = thisObj).also {
if (declaringClass != null) it.currentClassCtx = declaringClass 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( callOn(
scope.createChildScope( scope.createChildScope(
scope.pos, 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( callOn(
scope.createChildScope( scope.createChildScope(
scope.pos, 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)) callOn(scope.createChildScope(atPos, args = args, newThisObj = thisObj))
@ -681,117 +693,138 @@ open class Obj {
name = "toString", name = "toString",
doc = "Returns a string representation of the object.", doc = "Returns a string representation of the object.",
returns = type("lyng.String"), returns = type("lyng.String"),
moduleName = "lyng.stdlib" moduleName = "lyng.stdlib",
) { code = object : ScopeCallable {
thisObj.toString(this, true) override suspend fun call(scp: Scope): Obj = scp.thisObj.toString(scp, true)
} }
)
addFnDoc( addFnDoc(
name = "inspect", name = "inspect",
doc = "Returns a detailed string representation for debugging.", doc = "Returns a detailed string representation for debugging.",
returns = type("lyng.String"), returns = type("lyng.String"),
moduleName = "lyng.stdlib" moduleName = "lyng.stdlib",
) { code = object : ScopeCallable {
thisObj.inspect(this).toObj() override suspend fun call(scp: Scope): Obj = scp.thisObj.inspect(scp).toObj()
} }
)
addFnDoc( addFnDoc(
name = "contains", name = "contains",
doc = "Returns true if the object contains the given element.", doc = "Returns true if the object contains the given element.",
params = listOf(ParamDoc("element")), params = listOf(ParamDoc("element")),
returns = type("lyng.Bool"), returns = type("lyng.Bool"),
moduleName = "lyng.stdlib" moduleName = "lyng.stdlib",
) { code = object : ScopeCallable {
ObjBool(thisObj.contains(this, args.firstAndOnly())) override suspend fun call(scp: Scope): Obj = ObjBool(scp.thisObj.contains(scp, scp.args.firstAndOnly()))
} }
)
// utilities // utilities
addFnDoc( addFnDoc(
name = "let", name = "let",
doc = "Calls the specified function block with `this` value as its argument and returns its result.", doc = "Calls the specified function block with `this` value as its argument and returns its result.",
params = listOf(ParamDoc("block")), params = listOf(ParamDoc("block")),
moduleName = "lyng.stdlib" moduleName = "lyng.stdlib",
) { code = object : ScopeCallable {
args.firstAndOnly().callOn(createChildScope(Arguments(thisObj))) override suspend fun call(scp: Scope): Obj =
} scp.args.firstAndOnly().callOn(scp.createChildScope(Arguments(scp.thisObj)))
}
)
addFnDoc( addFnDoc(
name = "apply", name = "apply",
doc = "Calls the specified function block with `this` value as its receiver and returns `this` value.", doc = "Calls the specified function block with `this` value as its receiver and returns `this` value.",
params = listOf(ParamDoc("block")), params = listOf(ParamDoc("block")),
moduleName = "lyng.stdlib" moduleName = "lyng.stdlib",
) { code = object : ScopeCallable {
val body = args.firstAndOnly() override suspend fun call(scp: Scope): Obj {
(thisObj as? ObjInstance)?.let { val body = scp.args.firstAndOnly()
body.callOn(ApplyScope(this, it.instanceScope)) (scp.thisObj as? ObjInstance)?.let {
} ?: run { body.callOn(ApplyScope(scp, it.instanceScope))
body.callOn(this) } ?: run {
body.callOn(scp)
}
return scp.thisObj
}
} }
thisObj )
}
addFnDoc( addFnDoc(
name = "also", name = "also",
doc = "Calls the specified function block with `this` value as its argument and returns `this` value.", doc = "Calls the specified function block with `this` value as its argument and returns `this` value.",
params = listOf(ParamDoc("block")), params = listOf(ParamDoc("block")),
moduleName = "lyng.stdlib" moduleName = "lyng.stdlib",
) { code = object : ScopeCallable {
args.firstAndOnly().callOn(createChildScope(Arguments(thisObj))) override suspend fun call(scp: Scope): Obj {
thisObj scp.args.firstAndOnly().callOn(scp.createChildScope(Arguments(scp.thisObj)))
} return scp.thisObj
}
}
)
addFnDoc( addFnDoc(
name = "run", name = "run",
doc = "Calls the specified function block with `this` value as its receiver and returns its result.", doc = "Calls the specified function block with `this` value as its receiver and returns its result.",
params = listOf(ParamDoc("block")), params = listOf(ParamDoc("block")),
moduleName = "lyng.stdlib" moduleName = "lyng.stdlib",
) { code = object : ScopeCallable {
args.firstAndOnly().callOn(this) override suspend fun call(scp: Scope): Obj = scp.args.firstAndOnly().callOn(scp)
} }
addFn("getAt") { )
requireExactCount(1) addFn("getAt", code = object : ScopeCallable {
thisObj.getAt(this, requiredArg<Obj>(0)) override suspend fun call(scp: Scope): Obj {
} scp.requireExactCount(1)
addFn("putAt") { return scp.thisObj.getAt(scp, scp.requiredArg<Obj>(0))
requireExactCount(2) }
val newValue = args[1] })
thisObj.putAt(this, requiredArg<Obj>(0), newValue) addFn("putAt", code = object : ScopeCallable {
newValue override suspend fun call(scp: Scope): Obj {
} scp.requireExactCount(2)
val newValue = scp.args[1]
scp.thisObj.putAt(scp, scp.requiredArg<Obj>(0), newValue)
return newValue
}
})
addFnDoc( addFnDoc(
name = "toJsonString", name = "toJsonString",
doc = "Encodes this object to a JSON string.", doc = "Encodes this object to a JSON string.",
returns = type("lyng.String"), returns = type("lyng.String"),
moduleName = "lyng.stdlib" moduleName = "lyng.stdlib",
) { code = object : ScopeCallable {
thisObj.toJson(this).toString().toObj() override suspend fun call(scp: Scope): Obj =
} scp.thisObj.toJson(scp).toString().toObj()
}
)
addFnDoc( addFnDoc(
name = "clamp", 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.", 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")), params = listOf(ParamDoc("range")),
moduleName = "lyng.stdlib" moduleName = "lyng.stdlib",
) { code = object : ScopeCallable {
val range = requiredArg<ObjRange>(0) override suspend fun call(scp: Scope): Obj {
val range = scp.requiredArg<ObjRange>(0)
var result = thisObj var result = scp.thisObj
if (range.start != null && !range.start.isNull) { if (range.start != null && !range.start.isNull) {
if (result.compareTo(this, range.start) < 0) { if (result.compareTo(scp, range.start) < 0) {
result = range.start 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
} }
} }
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, scope: Scope,
name: String, name: String,
args: Arguments, args: Arguments,
onNotFoundResult: (suspend () -> Obj?)? onNotFoundResult: OnNotFound?
): Obj = scope.raiseUnset() ): Obj = scope.raiseUnset()
override suspend fun getAt(scope: Scope, index: Obj): Obj = scope.raiseUnset() override suspend fun getAt(scope: Scope, index: Obj): Obj = scope.raiseUnset()

View File

@ -17,6 +17,8 @@
package net.sergeych.lyng.obj package net.sergeych.lyng.obj
import net.sergeych.lyng.Scope
import net.sergeych.lyng.ScopeCallable
import net.sergeych.lyng.miniast.* import net.sergeych.lyng.miniast.*
val ObjArray by lazy { val ObjArray by lazy {
@ -31,8 +33,11 @@ val ObjArray by lazy {
name = "iterator", name = "iterator",
doc = "Iterator over elements of this array using its indexer.", doc = "Iterator over elements of this array using its indexer.",
returns = TypeGenericDoc(type("lyng.Iterator"), listOf(type("lyng.Any"))), returns = TypeGenericDoc(type("lyng.Iterator"), listOf(type("lyng.Any"))),
moduleName = "lyng.stdlib" moduleName = "lyng.stdlib",
) { ObjArrayIterator(thisObj).also { it.init(this) } } code = object : ScopeCallable {
override suspend fun call(scp: Scope): Obj = ObjArrayIterator(scp.thisObj).also { it.init(scp) }
}
)
addFnDoc( addFnDoc(
name = "contains", name = "contains",
@ -40,26 +45,31 @@ val ObjArray by lazy {
params = listOf(ParamDoc("element")), params = listOf(ParamDoc("element")),
returns = type("lyng.Bool"), returns = type("lyng.Bool"),
isOpen = true, isOpen = true,
moduleName = "lyng.stdlib" moduleName = "lyng.stdlib",
) { code = object : ScopeCallable {
val obj = args.firstAndOnly() override suspend fun call(scp: Scope): Obj {
for (i in 0..<thisObj.invokeInstanceMethod(this, "size").toInt()) { val obj = scp.args.firstAndOnly()
if (thisObj.getAt(this, ObjInt(i.toLong())).compareTo(this, obj) == 0) return@addFnDoc ObjTrue for (i in 0..<scp.thisObj.invokeInstanceMethod(scp, "size").toInt()) {
if (scp.thisObj.getAt(scp, ObjInt(i.toLong())).compareTo(scp, obj) == 0) return ObjTrue
}
return ObjFalse
}
} }
ObjFalse )
}
addPropertyDoc( addPropertyDoc(
name = "last", name = "last",
doc = "The last element of this array.", doc = "The last element of this array.",
type = type("lyng.Any"), type = type("lyng.Any"),
moduleName = "lyng.stdlib", moduleName = "lyng.stdlib",
getter = { getter = object : ScopeCallable {
this.thisObj.invokeInstanceMethod( override suspend fun call(scp: Scope): Obj {
this, return scp.thisObj.invokeInstanceMethod(
"getAt", scp,
(this.thisObj.invokeInstanceMethod(this, "size").toInt() - 1).toObj() "getAt",
) (scp.thisObj.invokeInstanceMethod(scp, "size").toInt() - 1).toObj()
)
}
} }
) )
@ -68,7 +78,9 @@ val ObjArray by lazy {
doc = "Index of the last element (size - 1).", doc = "Index of the last element (size - 1).",
type = type("lyng.Int"), type = type("lyng.Int"),
moduleName = "lyng.stdlib", moduleName = "lyng.stdlib",
getter = { (this.thisObj.invokeInstanceMethod(this, "size").toInt() - 1).toObj() } getter = object : ScopeCallable {
override suspend fun call(scp: Scope): Obj = (scp.thisObj.invokeInstanceMethod(scp, "size").toInt() - 1).toObj()
}
) )
addPropertyDoc( addPropertyDoc(
@ -76,7 +88,9 @@ val ObjArray by lazy {
doc = "Range of valid indices for this array.", doc = "Range of valid indices for this array.",
type = type("lyng.Range"), type = type("lyng.Range"),
moduleName = "lyng.stdlib", moduleName = "lyng.stdlib",
getter = { ObjRange(0.toObj(), this.thisObj.invokeInstanceMethod(this, "size"), false) } getter = object : ScopeCallable {
override suspend fun call(scp: Scope): Obj = ObjRange(0.toObj(), scp.thisObj.invokeInstanceMethod(scp, "size"), false)
}
) )
addFnDoc( addFnDoc(
@ -84,25 +98,28 @@ val ObjArray by lazy {
doc = "Binary search for a target in a sorted array. Returns index or negative insertion point - 1.", doc = "Binary search for a target in a sorted array. Returns index or negative insertion point - 1.",
params = listOf(ParamDoc("target")), params = listOf(ParamDoc("target")),
returns = type("lyng.Int"), returns = type("lyng.Int"),
moduleName = "lyng.stdlib" moduleName = "lyng.stdlib",
) { code = object : ScopeCallable {
val target = args.firstAndOnly() override suspend fun call(scp: Scope): Obj {
var low = 0 val target = scp.args.firstAndOnly()
var high = thisObj.invokeInstanceMethod(this, "size").toInt() - 1 var low = 0
var high = scp.thisObj.invokeInstanceMethod(scp, "size").toInt() - 1
while (low <= high) { while (low <= high) {
val mid = (low + high) / 2 val mid = (low + high) / 2
val midVal = thisObj.getAt(this, ObjInt(mid.toLong())) val midVal = scp.thisObj.getAt(scp, ObjInt(mid.toLong()))
val cmp = midVal.compareTo(this, target) val cmp = midVal.compareTo(scp, target)
when { when {
cmp == 0 -> return@addFnDoc (mid).toObj() cmp == 0 -> return (mid).toObj()
cmp > 0 -> high = mid - 1 cmp > 0 -> high = mid - 1
else -> low = mid + 1 else -> low = mid + 1
}
}
return (-low - 1).toObj()
} }
} }
)
(-low - 1).toObj()
}
} }
} }

View File

@ -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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -18,6 +18,7 @@
package net.sergeych.lyng.obj package net.sergeych.lyng.obj
import net.sergeych.lyng.Scope import net.sergeych.lyng.Scope
import net.sergeych.lyng.ScopeCallable
class ObjArrayIterator(val array: Obj) : Obj() { class ObjArrayIterator(val array: Obj) : Obj() {
@ -35,16 +36,20 @@ class ObjArrayIterator(val array: Obj) : Obj() {
companion object { companion object {
val type by lazy { val type by lazy {
ObjClass("ArrayIterator", ObjIterator).apply { ObjClass("ArrayIterator", ObjIterator).apply {
addFn("next") { addFn("next", code = object : ScopeCallable {
val self = thisAs<ObjArrayIterator>() override suspend fun call(scp: Scope): Obj {
if (self.nextIndex < self.lastIndex) { val self = scp.thisAs<ObjArrayIterator>()
self.array.invokeInstanceMethod(this, "getAt", (self.nextIndex++).toObj()) return if (self.nextIndex < self.lastIndex) {
} else raiseError(ObjIterationFinishedException(this)) self.array.invokeInstanceMethod(scp, "getAt", (self.nextIndex++).toObj())
} } else scp.raiseError(ObjIterationFinishedException(scp))
addFn("hasNext") { }
val self = thisAs<ObjArrayIterator>() })
if (self.nextIndex < self.lastIndex) ObjTrue else ObjFalse addFn("hasNext", code = object : ScopeCallable {
} override suspend fun call(scp: Scope): Obj {
val self = scp.thisAs<ObjArrayIterator>()
return if (self.nextIndex < self.lastIndex) ObjTrue else ObjFalse
}
})
} }
} }
} }

View File

@ -19,6 +19,7 @@ package net.sergeych.lyng.obj
import net.sergeych.bintools.toDump import net.sergeych.bintools.toDump
import net.sergeych.lyng.Scope import net.sergeych.lyng.Scope
import net.sergeych.lyng.ScopeCallable
import net.sergeych.lyng.miniast.addPropertyDoc import net.sergeych.lyng.miniast.addPropertyDoc
import net.sergeych.lyng.miniast.type import net.sergeych.lyng.miniast.type
import net.sergeych.lynon.BitArray import net.sergeych.lynon.BitArray
@ -35,29 +36,37 @@ class ObjBitBuffer(val bitArray: BitArray) : Obj() {
val type = object: ObjClass("BitBuffer", ObjArray) { val type = object: ObjClass("BitBuffer", ObjArray) {
}.apply { }.apply {
addFn("toBuffer") { addFn("toBuffer", code = object : ScopeCallable {
requireNoArgs() override suspend fun call(scp: Scope): Obj {
ObjBuffer(thisAs<ObjBitBuffer>().bitArray.asUByteArray()) scp.requireNoArgs()
} return ObjBuffer(scp.thisAs<ObjBitBuffer>().bitArray.asUByteArray())
addFn("toDump") { }
requireNoArgs() })
ObjString( addFn("toDump", code = object : ScopeCallable {
thisAs<ObjBitBuffer>().bitArray.asUByteArray().toDump() override suspend fun call(scp: Scope): Obj {
) scp.requireNoArgs()
} return ObjString(
scp.thisAs<ObjBitBuffer>().bitArray.asUByteArray().toByteArray().toDump()
)
}
})
addPropertyDoc( addPropertyDoc(
name = "size", name = "size",
doc = "Size of the bit buffer in bits.", doc = "Size of the bit buffer in bits.",
type = type("lyng.Int"), type = type("lyng.Int"),
moduleName = "lyng.stdlib", moduleName = "lyng.stdlib",
getter = { thisAs<ObjBitBuffer>().bitArray.size.toObj() } getter = object : ScopeCallable {
override suspend fun call(scp: Scope): Obj = scp.thisAs<ObjBitBuffer>().bitArray.size.toObj()
}
) )
addPropertyDoc( addPropertyDoc(
name = "sizeInBytes", name = "sizeInBytes",
doc = "Size of the bit buffer in full bytes (rounded up).", doc = "Size of the bit buffer in full bytes (rounded up).",
type = type("lyng.Int"), type = type("lyng.Int"),
moduleName = "lyng.stdlib", moduleName = "lyng.stdlib",
getter = { ObjInt((thisAs<ObjBitBuffer>().bitArray.size + 7) shr 3) } getter = object : ScopeCallable {
override suspend fun call(scp: Scope): Obj = ObjInt(((scp.thisAs<ObjBitBuffer>().bitArray.size + 7) shr 3).toLong())
}
) )
} }
} }

View File

@ -23,6 +23,8 @@ import net.sergeych.bintools.decodeHex
import net.sergeych.bintools.encodeToHex import net.sergeych.bintools.encodeToHex
import net.sergeych.bintools.toDump import net.sergeych.bintools.toDump
import net.sergeych.lyng.Scope 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.addPropertyDoc
import net.sergeych.lyng.miniast.type import net.sergeych.lyng.miniast.type
import net.sergeych.lynon.BitArray import net.sergeych.lynon.BitArray
@ -169,51 +171,85 @@ open class ObjBuffer(val byteArray: UByteArray) : Obj() {
}) })
}.apply { }.apply {
addClassFn("decodeBase64") { addClassFn("decodeBase64", code = object : ScopeCallable {
ObjBuffer(requireOnlyArg<Obj>().toString().decodeBase64Url().asUByteArray()) override suspend fun call(scp: Scope): Obj =
} ObjBuffer(scp.requireOnlyArg<Obj>().toString().decodeBase64Url().asUByteArray())
addClassFn("decodeHex") { })
ObjBuffer(requireOnlyArg<Obj>().toString().decodeHex().asUByteArray()) addClassFn("decodeHex", code = object : ScopeCallable {
} override suspend fun call(scp: Scope): Obj =
ObjBuffer(scp.requireOnlyArg<Obj>().toString().decodeHex().asUByteArray())
})
addPropertyDoc( addPropertyDoc(
name = "size", name = "size",
doc = "Number of bytes in this buffer.", doc = "Number of bytes in this buffer.",
type = type("lyng.Int"), type = type("lyng.Int"),
moduleName = "lyng.stdlib", 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( addPropertyDoc(
name = "hex", name = "hex",
doc = "Hexadecimal string representation of the buffer.", doc = "Hexadecimal string representation of the buffer.",
type = type("lyng.String"), type = type("lyng.String"),
moduleName = "lyng.stdlib", moduleName = "lyng.stdlib",
getter = { thisAs<ObjBuffer>().hex.toObj() } getter = object : ScopeCallable {
override suspend fun call(scp: Scope): Obj = scp.thisAs<ObjBuffer>().hex.toObj()
}
) )
addPropertyDoc( addPropertyDoc(
name = "base64", name = "base64",
doc = "Base64 (URL-safe) string representation of the buffer.", doc = "Base64 (URL-safe) string representation of the buffer.",
type = type("lyng.String"), type = type("lyng.String"),
moduleName = "lyng.stdlib", moduleName = "lyng.stdlib",
getter = { thisAs<ObjBuffer>().base64.toObj() } getter = object : ScopeCallable {
override suspend fun call(scp: Scope): Obj = scp.thisAs<ObjBuffer>().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<ObjBuffer>().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<ObjBuffer>().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<ObjBuffer>().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<ObjBuffer>().byteArray, 8))
}
) )
addFn("decodeUtf8") {
ObjString(
thisAs<ObjBuffer>().byteArray.toByteArray().decodeToString()
)
}
addFn("toMutable") {
requireNoArgs()
ObjMutableBuffer(thisAs<ObjBuffer>().byteArray.copyOf())
}
addFn("toDump") {
requireNoArgs()
ObjString(
thisAs<ObjBuffer>().byteArray.toByteArray().toDump()
)
}
addFn("toBitInput") {
ObjBitBuffer(BitArray(thisAs<ObjBuffer>().byteArray, 8))
}
} }
} }
} }

View File

@ -18,6 +18,7 @@
package net.sergeych.lyng.obj package net.sergeych.lyng.obj
import net.sergeych.lyng.Scope import net.sergeych.lyng.Scope
import net.sergeych.lyng.ScopeCallable
import net.sergeych.lyng.miniast.addPropertyDoc import net.sergeych.lyng.miniast.addPropertyDoc
import net.sergeych.lyng.miniast.type 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.", doc = "Unicode code point (UTF-16 code unit) of this character.",
type = type("lyng.Int"), type = type("lyng.Int"),
moduleName = "lyng.stdlib", 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") { addFn("isDigit", code = object : ScopeCallable {
thisAs<ObjChar>().value.isDigit().toObj() override suspend fun call(scp: Scope): Obj = scp.thisAs<ObjChar>().value.isDigit().toObj()
} })
addFn("isSpace") { addFn("isSpace", code = object : ScopeCallable {
thisAs<ObjChar>().value.isWhitespace().toObj() override suspend fun call(scp: Scope): Obj = scp.thisAs<ObjChar>().value.isWhitespace().toObj()
} })
} }
} }
} }

View File

@ -40,14 +40,18 @@ val ObjClassType by lazy {
doc = "Full name of this class including package if available.", doc = "Full name of this class including package if available.",
type = type("lyng.String"), type = type("lyng.String"),
moduleName = "lyng.stdlib", 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( addPropertyDoc(
name = "name", name = "name",
doc = "Simple name of this class (without package).", doc = "Simple name of this class (without package).",
type = type("lyng.String"), type = type("lyng.String"),
moduleName = "lyng.stdlib", 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( addPropertyDoc(
@ -55,16 +59,18 @@ val ObjClassType by lazy {
doc = "Declared instance fields of this class and its ancestors (C3 order), without duplicates.", doc = "Declared instance fields of this class and its ancestors (C3 order), without duplicates.",
type = TypeGenericDoc(type("lyng.List"), listOf(type("lyng.String"))), type = TypeGenericDoc(type("lyng.List"), listOf(type("lyng.String"))),
moduleName = "lyng.stdlib", moduleName = "lyng.stdlib",
getter = { getter = object : ScopeCallable {
val cls = this.thisObj as ObjClass override suspend fun call(scp: Scope): Obj {
val seen = hashSetOf<String>() val cls = scp.thisObj as ObjClass
val names = mutableListOf<Obj>() val seen = hashSetOf<String>()
for (c in cls.mro) { val names = mutableListOf<Obj>()
for ((n, rec) in c.members) { for (c in cls.mro) {
if (rec.value !is Statement && seen.add(n)) names += ObjString(n) 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.", doc = "Declared instance methods of this class and its ancestors (C3 order), without duplicates.",
type = TypeGenericDoc(type("lyng.List"), listOf(type("lyng.String"))), type = TypeGenericDoc(type("lyng.List"), listOf(type("lyng.String"))),
moduleName = "lyng.stdlib", moduleName = "lyng.stdlib",
getter = { getter = object : ScopeCallable {
val cls = this.thisObj as ObjClass override suspend fun call(scp: Scope): Obj {
val seen = hashSetOf<String>() val cls = scp.thisObj as ObjClass
val names = mutableListOf<Obj>() val seen = hashSetOf<String>()
for (c in cls.mro) { val names = mutableListOf<Obj>()
for ((n, rec) in c.members) { for (c in cls.mro) {
if (rec.value is Statement && seen.add(n)) names += ObjString(n) 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.", 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"))), params = listOf(ParamDoc("name", type("lyng.String"))),
returns = type("lyng.Any", nullable = true), returns = type("lyng.Any", nullable = true),
moduleName = "lyng.stdlib" moduleName = "lyng.stdlib",
) { code = object : ScopeCallable {
val cls = thisAs<ObjClass>() override suspend fun call(scp: Scope): Obj {
val name = requiredArg<ObjString>(0).value val cls = scp.thisAs<ObjClass>()
val rec = cls.getInstanceMemberOrNull(name) val name = scp.requiredArg<ObjString>(0).value
rec?.value ?: ObjNull val rec = cls.getInstanceMemberOrNull(name)
} return rec?.value ?: ObjNull
}
}
)
} }
} }
@ -547,9 +558,9 @@ open class ObjClass(
isClosed: Boolean = false, isClosed: Boolean = false,
isOverride: Boolean = false, isOverride: Boolean = false,
pos: Pos = Pos.builtIn, 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( createField(
name, stmt, isMutable, visibility, writeVisibility, pos, declaringClass, name, stmt, isMutable, visibility, writeVisibility, pos, declaringClass,
isAbstract = isAbstract, isClosed = isClosed, isOverride = isOverride, isAbstract = isAbstract, isClosed = isClosed, isOverride = isOverride,
@ -561,8 +572,8 @@ open class ObjClass(
fun addProperty( fun addProperty(
name: String, name: String,
getter: (suspend Scope.() -> Obj)? = null, getter: ScopeCallable? = null,
setter: (suspend Scope.(Obj) -> Unit)? = null, setter: ScopeCallable? = null,
visibility: Visibility = Visibility.Public, visibility: Visibility = Visibility.Public,
writeVisibility: Visibility? = null, writeVisibility: Visibility? = null,
declaringClass: ObjClass? = this, declaringClass: ObjClass? = this,
@ -572,8 +583,8 @@ open class ObjClass(
pos: Pos = Pos.builtIn, pos: Pos = Pos.builtIn,
prop: ObjProperty? = null prop: ObjProperty? = null
) { ) {
val g = getter?.let { statement { it() } } val g = getter?.let { statement(pos, f = it) }
val s = setter?.let { statement { it(requiredArg(0)); ObjVoid } } val s = setter?.let { statement(pos, f = it) }
val finalProp = prop ?: if (isAbstract) ObjNull else ObjProperty(name, g, s) val finalProp = prop ?: if (isAbstract) ObjNull else ObjProperty(name, g, s)
createField( createField(
name, finalProp, false, visibility, writeVisibility, pos, declaringClass, name, finalProp, false, visibility, writeVisibility, pos, declaringClass,
@ -583,8 +594,8 @@ open class ObjClass(
} }
fun addClassConst(name: String, value: Obj) = createClassField(name, value) fun addClassConst(name: String, value: Obj) = createClassField(name, value)
fun addClassFn(name: String, isOpen: Boolean = false, code: suspend Scope.() -> Obj) { fun addClassFn(name: String, isOpen: Boolean = false, code: ScopeCallable) {
createClassField(name, statement { code() }, isOpen, type = ObjRecord.Type.Fun) createClassField(name, statement(f = code), isOpen, type = ObjRecord.Type.Fun)
} }
@ -694,25 +705,25 @@ open class ObjClass(
override suspend fun invokeInstanceMethod( override suspend fun invokeInstanceMethod(
scope: Scope, name: String, args: Arguments, scope: Scope, name: String, args: Arguments,
onNotFoundResult: (suspend () -> Obj?)? onNotFoundResult: OnNotFound?
): Obj { ): Obj {
getInstanceMemberOrNull(name)?.let { rec -> getInstanceMemberOrNull(name)?.let { rec ->
val decl = rec.declaringClass ?: findDeclaringClassOf(name) ?: this val decl = rec.declaringClass ?: findDeclaringClassOf(name) ?: this
if (rec.type == ObjRecord.Type.Delegated) { if (rec.type == ObjRecord.Type.Delegated) {
val del = rec.delegate ?: scope.raiseError("Internal error: delegated member $name has no delegate") val del = rec.delegate ?: scope.raiseError("Internal error: delegated member $name has no delegate")
val allArgs = (listOf(this, ObjString(name)) + args.list).toTypedArray() 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 // Fallback: property delegation
val propVal = del.invokeInstanceMethod(scope, "getValue", Arguments(this, ObjString(name))) 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) { if (rec.type == ObjRecord.Type.Fun) {
return rec.value.invoke(scope, this, args, decl) return rec.value.invokeCallable(scope, this, args, decl)
} else { } else {
// Resolved field or property value // Resolved field or property value
val resolved = readField(scope, name) 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) return super.invokeInstanceMethod(scope, name, args, onNotFoundResult)

View File

@ -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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with 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 kotlinx.coroutines.CompletableDeferred
import net.sergeych.lyng.Scope import net.sergeych.lyng.Scope
import net.sergeych.lyng.ScopeCallable
import net.sergeych.lyng.miniast.ParamDoc import net.sergeych.lyng.miniast.ParamDoc
import net.sergeych.lyng.miniast.addFnDoc import net.sergeych.lyng.miniast.addFnDoc
import net.sergeych.lyng.miniast.type import net.sergeych.lyng.miniast.type
@ -38,11 +39,14 @@ class ObjCompletableDeferred(val completableDeferred: CompletableDeferred<Obj>):
doc = "Complete this deferred with the given value. Subsequent calls have no effect.", doc = "Complete this deferred with the given value. Subsequent calls have no effect.",
params = listOf(ParamDoc("value")), params = listOf(ParamDoc("value")),
returns = type("lyng.Void"), returns = type("lyng.Void"),
moduleName = "lyng.stdlib" moduleName = "lyng.stdlib",
) { code = object : ScopeCallable {
thisAs<ObjCompletableDeferred>().completableDeferred.complete(args.firstAndOnly()) override suspend fun call(scp: Scope): Obj {
ObjVoid scp.thisAs<ObjCompletableDeferred>().completableDeferred.complete(scp.args.firstAndOnly())
} return ObjVoid
}
}
)
} }
} }
} }

View File

@ -21,6 +21,7 @@ import kotlinx.datetime.*
import kotlinx.serialization.json.JsonElement import kotlinx.serialization.json.JsonElement
import kotlinx.serialization.json.JsonPrimitive import kotlinx.serialization.json.JsonPrimitive
import net.sergeych.lyng.Scope import net.sergeych.lyng.Scope
import net.sergeych.lyng.ScopeCallable
import net.sergeych.lyng.Statement import net.sergeych.lyng.Statement
import net.sergeych.lyng.miniast.addClassFnDoc import net.sergeych.lyng.miniast.addClassFnDoc
import net.sergeych.lyng.miniast.addFnDoc 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) { if (rec.type == ObjRecord.Type.Fun || rec.value is Statement) {
val s = rec.value as 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) return resolveRecord(scope, rec, name, rec.declaringClass ?: cls)
} }
@ -172,122 +175,129 @@ class ObjDateTime(val instant: Instant, val timeZone: TimeZone) : Obj() {
} }
}.apply { }.apply {
addPropertyDoc("year", "The year component.", type("lyng.Int"), moduleName = "lyng.time", addPropertyDoc("year", "The year component.", type("lyng.Int"), moduleName = "lyng.time",
getter = { thisAs<ObjDateTime>().localDateTime.year.toObj() }) getter = object : ScopeCallable { override suspend fun call(scp: Scope): Obj = scp.thisAs<ObjDateTime>().localDateTime.year.toObj() })
addPropertyDoc("month", "The month component (1..12).", type("lyng.Int"), moduleName = "lyng.time", addPropertyDoc("month", "The month component (1..12).", type("lyng.Int"), moduleName = "lyng.time",
getter = { thisAs<ObjDateTime>().localDateTime.monthNumber.toObj() }) getter = object : ScopeCallable { override suspend fun call(scp: Scope): Obj = scp.thisAs<ObjDateTime>().localDateTime.monthNumber.toObj() })
addPropertyDoc("dayOfMonth", "The day of month component.", type("lyng.Int"), moduleName = "lyng.time", addPropertyDoc("dayOfMonth", "The day of month component.", type("lyng.Int"), moduleName = "lyng.time",
getter = { thisAs<ObjDateTime>().localDateTime.dayOfMonth.toObj() }) getter = object : ScopeCallable { override suspend fun call(scp: Scope): Obj = scp.thisAs<ObjDateTime>().localDateTime.dayOfMonth.toObj() })
addPropertyDoc("day", "Alias to dayOfMonth.", type("lyng.Int"), moduleName = "lyng.time", addPropertyDoc("day", "Alias to dayOfMonth.", type("lyng.Int"), moduleName = "lyng.time",
getter = { thisAs<ObjDateTime>().localDateTime.dayOfMonth.toObj() }) getter = object : ScopeCallable { override suspend fun call(scp: Scope): Obj = scp.thisAs<ObjDateTime>().localDateTime.dayOfMonth.toObj() })
addPropertyDoc("hour", "The hour component (0..23).", type("lyng.Int"), moduleName = "lyng.time", addPropertyDoc("hour", "The hour component (0..23).", type("lyng.Int"), moduleName = "lyng.time",
getter = { thisAs<ObjDateTime>().localDateTime.hour.toObj() }) getter = object : ScopeCallable { override suspend fun call(scp: Scope): Obj = scp.thisAs<ObjDateTime>().localDateTime.hour.toObj() })
addPropertyDoc("minute", "The minute component (0..59).", type("lyng.Int"), moduleName = "lyng.time", addPropertyDoc("minute", "The minute component (0..59).", type("lyng.Int"), moduleName = "lyng.time",
getter = { thisAs<ObjDateTime>().localDateTime.minute.toObj() }) getter = object : ScopeCallable { override suspend fun call(scp: Scope): Obj = scp.thisAs<ObjDateTime>().localDateTime.minute.toObj() })
addPropertyDoc("second", "The second component (0..59).", type("lyng.Int"), moduleName = "lyng.time", addPropertyDoc("second", "The second component (0..59).", type("lyng.Int"), moduleName = "lyng.time",
getter = { thisAs<ObjDateTime>().localDateTime.second.toObj() }) getter = object : ScopeCallable { override suspend fun call(scp: Scope): Obj = scp.thisAs<ObjDateTime>().localDateTime.second.toObj() })
addPropertyDoc("dayOfWeek", "The day of week (1=Monday, 7=Sunday).", type("lyng.Int"), moduleName = "lyng.time", addPropertyDoc("dayOfWeek", "The day of week (1=Monday, 7=Sunday).", type("lyng.Int"), moduleName = "lyng.time",
getter = { thisAs<ObjDateTime>().localDateTime.dayOfWeek.isoDayNumber.toObj() }) getter = object : ScopeCallable { override suspend fun call(scp: Scope): Obj = scp.thisAs<ObjDateTime>().localDateTime.dayOfWeek.isoDayNumber.toObj() })
addPropertyDoc("timeZone", "The time zone ID (e.g. 'Z', '+02:00', 'Europe/Prague').", type("lyng.String"), moduleName = "lyng.time", addPropertyDoc("timeZone", "The time zone ID (e.g. 'Z', '+02:00', 'Europe/Prague').", type("lyng.String"), moduleName = "lyng.time",
getter = { thisAs<ObjDateTime>().timeZone.id.toObj() }) getter = object : ScopeCallable { override suspend fun call(scp: Scope): Obj = scp.thisAs<ObjDateTime>().timeZone.id.toObj() })
addFnDoc("toInstant", "Convert this localized date time back to an absolute Instant.", returns = type("lyng.Instant"), moduleName = "lyng.time") { addFnDoc("toInstant", "Convert this localized date time back to an absolute Instant.", returns = type("lyng.Instant"), moduleName = "lyng.time",
ObjInstant(thisAs<ObjDateTime>().instant) code = object : ScopeCallable { override suspend fun call(scp: Scope): Obj = ObjInstant(scp.thisAs<ObjDateTime>().instant) })
} addFnDoc("toEpochSeconds", "Return the number of full seconds since the Unix epoch (UTC).", returns = type("lyng.Int"), moduleName = "lyng.time",
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<ObjDateTime>().instant.epochSeconds.toObj() })
thisAs<ObjDateTime>().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<ObjDateTime>().toRFC3339().toObj() })
addFnDoc("toRFC3339", "Return the RFC3339 string representation of this date time, including its timezone offset.", returns = type("lyng.String"), moduleName = "lyng.time") { addFnDoc("toSortableString", "Alias to toRFC3339.", returns = type("lyng.String"), moduleName = "lyng.time",
thisAs<ObjDateTime>().toRFC3339().toObj() code = object : ScopeCallable { override suspend fun call(scp: Scope): Obj = scp.thisAs<ObjDateTime>().toRFC3339().toObj() })
}
addFnDoc("toSortableString", "Alias to toRFC3339.", returns = type("lyng.String"), moduleName = "lyng.time") { addFnDoc("toEpochMilliseconds", "Return the number of milliseconds since the Unix epoch (UTC).", returns = type("lyng.Int"), moduleName = "lyng.time",
thisAs<ObjDateTime>().toRFC3339().toObj() code = object : ScopeCallable { override suspend fun call(scp: Scope): Obj = scp.thisAs<ObjDateTime>().instant.toEpochMilliseconds().toObj() })
}
addFnDoc("toEpochMilliseconds", "Return the number of milliseconds since the Unix epoch (UTC).", returns = type("lyng.Int"), moduleName = "lyng.time") {
thisAs<ObjDateTime>().instant.toEpochMilliseconds().toObj()
}
addFnDoc("toTimeZone", "Return a new DateTime representing the same instant but in a different time zone. " + 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.", "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"))), params = listOf(net.sergeych.lyng.miniast.ParamDoc("tz", type = type("lyng.Any"))),
returns = type("lyng.DateTime"), moduleName = "lyng.time") { returns = type("lyng.DateTime"), moduleName = "lyng.time",
val tz = when (val a = args.list.getOrNull(0)) { code = object : ScopeCallable {
is ObjString -> TimeZone.of(a.value) override suspend fun call(scp: Scope): Obj {
is ObjInt -> UtcOffset(seconds = a.value.toInt()).asTimeZone() val tz = when (val a = scp.args.list.getOrNull(0)) {
else -> raiseIllegalArgument("invalid timezone: $a") is ObjString -> TimeZone.of(a.value)
} is ObjInt -> UtcOffset(seconds = a.value.toInt()).asTimeZone()
ObjDateTime(thisAs<ObjDateTime>().instant, tz) else -> scp.raiseIllegalArgument("invalid timezone: $a")
} }
addFnDoc("toUTC", "Shortcut to convert this date time to the UTC time zone.", returns = type("lyng.DateTime"), moduleName = "lyng.time") { return ObjDateTime(scp.thisAs<ObjDateTime>().instant, tz)
ObjDateTime(thisAs<ObjDateTime>().instant, TimeZone.UTC) }
} })
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<ObjDateTime>().instant, TimeZone.UTC) })
addFnDoc("addMonths", "Return a new DateTime with the specified number of months added (or subtracted if negative). " + 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).", "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"))), params = listOf(net.sergeych.lyng.miniast.ParamDoc("months", type = type("lyng.Int"))),
returns = type("lyng.DateTime"), moduleName = "lyng.time") { returns = type("lyng.DateTime"), moduleName = "lyng.time",
val n = args.list.getOrNull(0)?.toInt() ?: 0 code = object : ScopeCallable {
val res = thisAs<ObjDateTime>().instant.plus(n, DateTimeUnit.MONTH, thisAs<ObjDateTime>().timeZone) override suspend fun call(scp: Scope): Obj {
ObjDateTime(res, thisAs<ObjDateTime>().timeZone) val n = scp.args.list.getOrNull(0)?.toInt() ?: 0
} val res = scp.thisAs<ObjDateTime>().instant.plus(n, DateTimeUnit.MONTH, scp.thisAs<ObjDateTime>().timeZone)
return ObjDateTime(res, scp.thisAs<ObjDateTime>().timeZone)
}
})
addFnDoc("addYears", "Return a new DateTime with the specified number of years added (or subtracted if negative).", 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"))), params = listOf(net.sergeych.lyng.miniast.ParamDoc("years", type = type("lyng.Int"))),
returns = type("lyng.DateTime"), moduleName = "lyng.time") { returns = type("lyng.DateTime"), moduleName = "lyng.time",
val n = args.list.getOrNull(0)?.toInt() ?: 0 code = object : ScopeCallable {
val res = thisAs<ObjDateTime>().instant.plus(n, DateTimeUnit.YEAR, thisAs<ObjDateTime>().timeZone) override suspend fun call(scp: Scope): Obj {
ObjDateTime(res, thisAs<ObjDateTime>().timeZone) val n = scp.args.list.getOrNull(0)?.toInt() ?: 0
} val res = scp.thisAs<ObjDateTime>().instant.plus(n, DateTimeUnit.YEAR, scp.thisAs<ObjDateTime>().timeZone)
return ObjDateTime(res, scp.thisAs<ObjDateTime>().timeZone)
}
})
addClassFn("now") { addClassFn("now", code = object : ScopeCallable {
val tz = when (val a = args.list.getOrNull(0)) { override suspend fun call(scp: Scope): Obj {
null -> TimeZone.currentSystemDefault() val tz = when (val a = scp.args.list.getOrNull(0)) {
is ObjString -> TimeZone.of(a.value) null -> TimeZone.currentSystemDefault()
is ObjInt -> UtcOffset(seconds = a.value.toInt()).asTimeZone() is ObjString -> TimeZone.of(a.value)
else -> raiseIllegalArgument("invalid timezone: $a") 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", addClassFnDoc("parseRFC3339",
"Parse an RFC3339 string into a DateTime object. " + "Parse an RFC3339 string into a DateTime object. " +
"Note: if the string does not specify a timezone, UTC is assumed.", "Note: if the string does not specify a timezone, UTC is assumed.",
params = listOf(net.sergeych.lyng.miniast.ParamDoc("string", type = type("lyng.String"))), params = listOf(net.sergeych.lyng.miniast.ParamDoc("string", type = type("lyng.String"))),
returns = type("lyng.DateTime"), returns = type("lyng.DateTime"),
moduleName = "lyng.time") { moduleName = "lyng.time",
val s = (args.firstAndOnly() as ObjString).value code = object : ScopeCallable {
// kotlinx-datetime's Instant.parse handles RFC3339 override suspend fun call(scp: Scope): Obj {
// But we want to preserve the offset if present for DateTime. val s = (scp.args.firstAndOnly() as ObjString).value
// However, Instant.parse("...") always gives an Instant. // kotlinx-datetime's Instant.parse handles RFC3339
// If we want the specific offset from the string, we might need a more complex parse. // But we want to preserve the offset if present for DateTime.
// For now, let's stick to parsing it as Instant and converting to UTC or specified TZ. // However, Instant.parse("...") always gives an Instant.
// Actually, if the string has an offset, Instant.parse handles it but returns UTC 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.
// Let's try to detect if there is an offset in the string. // Actually, if the string has an offset, Instant.parse handles it but returns UTC instant.
// If not, use UTC.
val instant = Instant.parse(s) // Let's try to detect if there is an offset in the string.
// If not, use UTC.
// RFC3339 can have Z or +/-HH:mm or +/-HHmm or +/-HH val instant = Instant.parse(s)
val tz = try {
if (s.endsWith("Z", ignoreCase = true)) { // RFC3339 can have Z or +/-HH:mm or +/-HHmm or +/-HH
TimeZone.of("Z") val tz = try {
} else { if (s.endsWith("Z", ignoreCase = true)) {
// Look for the last + or - which is likely the start of the offset TimeZone.of("Z")
val lastPlus = s.lastIndexOf('+') } else {
val lastMinus = s.lastIndexOf('-') // Look for the last + or - which is likely the start of the offset
val offsetStart = if (lastPlus > lastMinus) lastPlus else lastMinus val lastPlus = s.lastIndexOf('+')
if (offsetStart > s.lastIndexOf('T')) { val lastMinus = s.lastIndexOf('-')
// Likely an offset val offsetStart = if (lastPlus > lastMinus) lastPlus else lastMinus
val offsetStr = s.substring(offsetStart) if (offsetStart > s.lastIndexOf('T')) {
TimeZone.of(offsetStr) // Likely an offset
} else { val offsetStr = s.substring(offsetStart)
TimeZone.of(offsetStr)
} else {
TimeZone.UTC
}
}
} catch (e: Exception) {
TimeZone.UTC TimeZone.UTC
} }
return ObjDateTime(instant, tz)
} }
} catch (e: Exception) { })
TimeZone.UTC
}
ObjDateTime(instant, tz)
}
} }
} }
} }

View File

@ -19,6 +19,7 @@ package net.sergeych.lyng.obj
import kotlinx.coroutines.Deferred import kotlinx.coroutines.Deferred
import net.sergeych.lyng.Scope import net.sergeych.lyng.Scope
import net.sergeych.lyng.ScopeCallable
import net.sergeych.lyng.miniast.addFnDoc import net.sergeych.lyng.miniast.addFnDoc
import net.sergeych.lyng.miniast.addPropertyDoc import net.sergeych.lyng.miniast.addPropertyDoc
import net.sergeych.lyng.miniast.type import net.sergeych.lyng.miniast.type
@ -37,23 +38,30 @@ open class ObjDeferred(val deferred: Deferred<Obj>): Obj() {
name = "await", name = "await",
doc = "Suspend until completion and return the result value (or throw if failed).", doc = "Suspend until completion and return the result value (or throw if failed).",
returns = type("lyng.Any"), returns = type("lyng.Any"),
moduleName = "lyng.stdlib" moduleName = "lyng.stdlib",
) { thisAs<ObjDeferred>().deferred.await() } code = object : ScopeCallable {
override suspend fun call(scp: Scope): Obj = scp.thisAs<ObjDeferred>().deferred.await()
}
)
addPropertyDoc( addPropertyDoc(
name = "isCompleted", name = "isCompleted",
doc = "Whether this deferred has completed (successfully or with an error).", doc = "Whether this deferred has completed (successfully or with an error).",
type = type("lyng.Bool"), type = type("lyng.Bool"),
moduleName = "lyng.stdlib", moduleName = "lyng.stdlib",
getter = { thisAs<ObjDeferred>().deferred.isCompleted.toObj() } getter = object : ScopeCallable {
override suspend fun call(scp: Scope): Obj = scp.thisAs<ObjDeferred>().deferred.isCompleted.toObj()
}
) )
addPropertyDoc( addPropertyDoc(
name = "isActive", name = "isActive",
doc = "Whether this deferred is currently active (not completed and not cancelled).", doc = "Whether this deferred is currently active (not completed and not cancelled).",
type = type("lyng.Bool"), type = type("lyng.Bool"),
moduleName = "lyng.stdlib", moduleName = "lyng.stdlib",
getter = { getter = object : ScopeCallable {
val d = thisAs<ObjDeferred>().deferred override suspend fun call(scp: Scope): Obj {
(d.isActive || (!d.isCompleted && !d.isCancelled)).toObj() val d = scp.thisAs<ObjDeferred>().deferred
return (d.isActive || (!d.isCompleted && !d.isCancelled)).toObj()
}
} }
) )
addPropertyDoc( addPropertyDoc(
@ -61,7 +69,9 @@ open class ObjDeferred(val deferred: Deferred<Obj>): Obj() {
doc = "Whether this deferred was cancelled.", doc = "Whether this deferred was cancelled.",
type = type("lyng.Bool"), type = type("lyng.Bool"),
moduleName = "lyng.stdlib", moduleName = "lyng.stdlib",
getter = { thisAs<ObjDeferred>().deferred.isCancelled.toObj() } getter = object : ScopeCallable {
override suspend fun call(scp: Scope): Obj = scp.thisAs<ObjDeferred>().deferred.isCancelled.toObj()
}
) )
} }
} }

View File

@ -18,6 +18,7 @@
package net.sergeych.lyng.obj package net.sergeych.lyng.obj
import net.sergeych.lyng.Scope import net.sergeych.lyng.Scope
import net.sergeych.lyng.ScopeCallable
import net.sergeych.lyng.miniast.addPropertyDoc import net.sergeych.lyng.miniast.addPropertyDoc
import net.sergeych.lyng.miniast.type import net.sergeych.lyng.miniast.type
import kotlin.time.Duration import kotlin.time.Duration
@ -79,42 +80,54 @@ class ObjDuration(val duration: Duration) : Obj() {
doc = "Return this duration as a real number of days.", doc = "Return this duration as a real number of days.",
type = type("lyng.Real"), type = type("lyng.Real"),
moduleName = "lyng.time", moduleName = "lyng.time",
getter = { thisAs<ObjDuration>().duration.toDouble(DurationUnit.DAYS).toObj() } getter = object : ScopeCallable {
override suspend fun call(scp: Scope): Obj = scp.thisAs<ObjDuration>().duration.toDouble(DurationUnit.DAYS).toObj()
}
) )
addPropertyDoc( addPropertyDoc(
name = "hours", name = "hours",
doc = "Return this duration as a real number of hours.", doc = "Return this duration as a real number of hours.",
type = type("lyng.Real"), type = type("lyng.Real"),
moduleName = "lyng.time", moduleName = "lyng.time",
getter = { thisAs<ObjDuration>().duration.toDouble(DurationUnit.HOURS).toObj() } getter = object : ScopeCallable {
override suspend fun call(scp: Scope): Obj = scp.thisAs<ObjDuration>().duration.toDouble(DurationUnit.HOURS).toObj()
}
) )
addPropertyDoc( addPropertyDoc(
name = "minutes", name = "minutes",
doc = "Return this duration as a real number of minutes.", doc = "Return this duration as a real number of minutes.",
type = type("lyng.Real"), type = type("lyng.Real"),
moduleName = "lyng.time", moduleName = "lyng.time",
getter = { thisAs<ObjDuration>().duration.toDouble(DurationUnit.MINUTES).toObj() } getter = object : ScopeCallable {
override suspend fun call(scp: Scope): Obj = scp.thisAs<ObjDuration>().duration.toDouble(DurationUnit.MINUTES).toObj()
}
) )
addPropertyDoc( addPropertyDoc(
name = "seconds", name = "seconds",
doc = "Return this duration as a real number of seconds.", doc = "Return this duration as a real number of seconds.",
type = type("lyng.Real"), type = type("lyng.Real"),
moduleName = "lyng.time", moduleName = "lyng.time",
getter = { thisAs<ObjDuration>().duration.toDouble(DurationUnit.SECONDS).toObj() } getter = object : ScopeCallable {
override suspend fun call(scp: Scope): Obj = scp.thisAs<ObjDuration>().duration.toDouble(DurationUnit.SECONDS).toObj()
}
) )
addPropertyDoc( addPropertyDoc(
name = "milliseconds", name = "milliseconds",
doc = "Return this duration as a real number of milliseconds.", doc = "Return this duration as a real number of milliseconds.",
type = type("lyng.Real"), type = type("lyng.Real"),
moduleName = "lyng.time", moduleName = "lyng.time",
getter = { thisAs<ObjDuration>().duration.toDouble(DurationUnit.MILLISECONDS).toObj() } getter = object : ScopeCallable {
override suspend fun call(scp: Scope): Obj = scp.thisAs<ObjDuration>().duration.toDouble(DurationUnit.MILLISECONDS).toObj()
}
) )
addPropertyDoc( addPropertyDoc(
name = "microseconds", name = "microseconds",
doc = "Return this duration as a real number of microseconds.", doc = "Return this duration as a real number of microseconds.",
type = type("lyng.Real"), type = type("lyng.Real"),
moduleName = "lyng.time", moduleName = "lyng.time",
getter = { thisAs<ObjDuration>().duration.toDouble(DurationUnit.MICROSECONDS).toObj() } getter = object : ScopeCallable {
override suspend fun call(scp: Scope): Obj = scp.thisAs<ObjDuration>().duration.toDouble(DurationUnit.MICROSECONDS).toObj()
}
) )
// extensions // extensions
@ -123,7 +136,9 @@ class ObjDuration(val duration: Duration) : Obj() {
doc = "Construct a `Duration` equal to this integer number of seconds.", doc = "Construct a `Duration` equal to this integer number of seconds.",
type = type("lyng.Duration"), type = type("lyng.Duration"),
moduleName = "lyng.time", moduleName = "lyng.time",
getter = { ObjDuration(thisAs<ObjInt>().value.seconds) } getter = object : ScopeCallable {
override suspend fun call(scp: Scope): Obj = ObjDuration(scp.thisAs<ObjInt>().value.seconds)
}
) )
ObjInt.type.addPropertyDoc( ObjInt.type.addPropertyDoc(
@ -131,14 +146,18 @@ class ObjDuration(val duration: Duration) : Obj() {
doc = "Construct a `Duration` equal to this integer number of seconds.", doc = "Construct a `Duration` equal to this integer number of seconds.",
type = type("lyng.Duration"), type = type("lyng.Duration"),
moduleName = "lyng.time", moduleName = "lyng.time",
getter = { ObjDuration(thisAs<ObjInt>().value.seconds) } getter = object : ScopeCallable {
override suspend fun call(scp: Scope): Obj = ObjDuration(scp.thisAs<ObjInt>().value.seconds)
}
) )
ObjInt.type.addPropertyDoc( ObjInt.type.addPropertyDoc(
name = "milliseconds", name = "milliseconds",
doc = "Construct a `Duration` equal to this integer number of milliseconds.", doc = "Construct a `Duration` equal to this integer number of milliseconds.",
type = type("lyng.Duration"), type = type("lyng.Duration"),
moduleName = "lyng.time", moduleName = "lyng.time",
getter = { ObjDuration(thisAs<ObjInt>().value.milliseconds) } getter = object : ScopeCallable {
override suspend fun call(scp: Scope): Obj = ObjDuration(scp.thisAs<ObjInt>().value.milliseconds)
}
) )
ObjInt.type.addPropertyDoc( ObjInt.type.addPropertyDoc(
@ -146,14 +165,18 @@ class ObjDuration(val duration: Duration) : Obj() {
doc = "Construct a `Duration` equal to this integer number of milliseconds.", doc = "Construct a `Duration` equal to this integer number of milliseconds.",
type = type("lyng.Duration"), type = type("lyng.Duration"),
moduleName = "lyng.time", moduleName = "lyng.time",
getter = { ObjDuration(thisAs<ObjInt>().value.milliseconds) } getter = object : ScopeCallable {
override suspend fun call(scp: Scope): Obj = ObjDuration(scp.thisAs<ObjInt>().value.milliseconds)
}
) )
ObjReal.type.addPropertyDoc( ObjReal.type.addPropertyDoc(
name = "seconds", name = "seconds",
doc = "Construct a `Duration` equal to this real number of seconds.", doc = "Construct a `Duration` equal to this real number of seconds.",
type = type("lyng.Duration"), type = type("lyng.Duration"),
moduleName = "lyng.time", moduleName = "lyng.time",
getter = { ObjDuration(thisAs<ObjReal>().value.seconds) } getter = object : ScopeCallable {
override suspend fun call(scp: Scope): Obj = ObjDuration(scp.thisAs<ObjReal>().value.seconds)
}
) )
ObjReal.type.addPropertyDoc( ObjReal.type.addPropertyDoc(
@ -161,7 +184,9 @@ class ObjDuration(val duration: Duration) : Obj() {
doc = "Construct a `Duration` equal to this real number of seconds.", doc = "Construct a `Duration` equal to this real number of seconds.",
type = type("lyng.Duration"), type = type("lyng.Duration"),
moduleName = "lyng.time", moduleName = "lyng.time",
getter = { ObjDuration(thisAs<ObjReal>().value.seconds) } getter = object : ScopeCallable {
override suspend fun call(scp: Scope): Obj = ObjDuration(scp.thisAs<ObjReal>().value.seconds)
}
) )
ObjReal.type.addPropertyDoc( ObjReal.type.addPropertyDoc(
@ -169,14 +194,18 @@ class ObjDuration(val duration: Duration) : Obj() {
doc = "Construct a `Duration` equal to this real number of milliseconds.", doc = "Construct a `Duration` equal to this real number of milliseconds.",
type = type("lyng.Duration"), type = type("lyng.Duration"),
moduleName = "lyng.time", moduleName = "lyng.time",
getter = { ObjDuration(thisAs<ObjReal>().value.milliseconds) } getter = object : ScopeCallable {
override suspend fun call(scp: Scope): Obj = ObjDuration(scp.thisAs<ObjReal>().value.milliseconds)
}
) )
ObjReal.type.addPropertyDoc( ObjReal.type.addPropertyDoc(
name = "millisecond", name = "millisecond",
doc = "Construct a `Duration` equal to this real number of milliseconds.", doc = "Construct a `Duration` equal to this real number of milliseconds.",
type = type("lyng.Duration"), type = type("lyng.Duration"),
moduleName = "lyng.time", moduleName = "lyng.time",
getter = { ObjDuration(thisAs<ObjReal>().value.milliseconds) } getter = object : ScopeCallable {
override suspend fun call(scp: Scope): Obj = ObjDuration(scp.thisAs<ObjReal>().value.milliseconds)
}
) )
ObjInt.type.addPropertyDoc( ObjInt.type.addPropertyDoc(
@ -184,84 +213,108 @@ class ObjDuration(val duration: Duration) : Obj() {
doc = "Construct a `Duration` equal to this integer number of minutes.", doc = "Construct a `Duration` equal to this integer number of minutes.",
type = type("lyng.Duration"), type = type("lyng.Duration"),
moduleName = "lyng.time", moduleName = "lyng.time",
getter = { ObjDuration(thisAs<ObjInt>().value.minutes) } getter = object : ScopeCallable {
override suspend fun call(scp: Scope): Obj = ObjDuration(scp.thisAs<ObjInt>().value.minutes)
}
) )
ObjReal.type.addPropertyDoc( ObjReal.type.addPropertyDoc(
name = "minutes", name = "minutes",
doc = "Construct a `Duration` equal to this real number of minutes.", doc = "Construct a `Duration` equal to this real number of minutes.",
type = type("lyng.Duration"), type = type("lyng.Duration"),
moduleName = "lyng.time", moduleName = "lyng.time",
getter = { ObjDuration(thisAs<ObjReal>().value.minutes) } getter = object : ScopeCallable {
override suspend fun call(scp: Scope): Obj = ObjDuration(scp.thisAs<ObjReal>().value.minutes)
}
) )
ObjInt.type.addPropertyDoc( ObjInt.type.addPropertyDoc(
name = "minute", name = "minute",
doc = "Construct a `Duration` equal to this integer number of minutes.", doc = "Construct a `Duration` equal to this integer number of minutes.",
type = type("lyng.Duration"), type = type("lyng.Duration"),
moduleName = "lyng.time", moduleName = "lyng.time",
getter = { ObjDuration(thisAs<ObjInt>().value.minutes) } getter = object : ScopeCallable {
override suspend fun call(scp: Scope): Obj = ObjDuration(scp.thisAs<ObjInt>().value.minutes)
}
) )
ObjReal.type.addPropertyDoc( ObjReal.type.addPropertyDoc(
name = "minute", name = "minute",
doc = "Construct a `Duration` equal to this real number of minutes.", doc = "Construct a `Duration` equal to this real number of minutes.",
type = type("lyng.Duration"), type = type("lyng.Duration"),
moduleName = "lyng.time", moduleName = "lyng.time",
getter = { ObjDuration(thisAs<ObjReal>().value.minutes) } getter = object : ScopeCallable {
override suspend fun call(scp: Scope): Obj = ObjDuration(scp.thisAs<ObjReal>().value.minutes)
}
) )
ObjInt.type.addPropertyDoc( ObjInt.type.addPropertyDoc(
name = "hours", name = "hours",
doc = "Construct a `Duration` equal to this integer number of hours.", doc = "Construct a `Duration` equal to this integer number of hours.",
type = type("lyng.Duration"), type = type("lyng.Duration"),
moduleName = "lyng.time", moduleName = "lyng.time",
getter = { ObjDuration(thisAs<ObjInt>().value.hours) } getter = object : ScopeCallable {
override suspend fun call(scp: Scope): Obj = ObjDuration(scp.thisAs<ObjInt>().value.hours)
}
) )
ObjReal.type.addPropertyDoc( ObjReal.type.addPropertyDoc(
name = "hours", name = "hours",
doc = "Construct a `Duration` equal to this real number of hours.", doc = "Construct a `Duration` equal to this real number of hours.",
type = type("lyng.Duration"), type = type("lyng.Duration"),
moduleName = "lyng.time", moduleName = "lyng.time",
getter = { ObjDuration(thisAs<ObjReal>().value.hours) } getter = object : ScopeCallable {
override suspend fun call(scp: Scope): Obj = ObjDuration(scp.thisAs<ObjReal>().value.hours)
}
) )
ObjInt.type.addPropertyDoc( ObjInt.type.addPropertyDoc(
name = "hour", name = "hour",
doc = "Construct a `Duration` equal to this integer number of hours.", doc = "Construct a `Duration` equal to this integer number of hours.",
type = type("lyng.Duration"), type = type("lyng.Duration"),
moduleName = "lyng.time", moduleName = "lyng.time",
getter = { ObjDuration(thisAs<ObjInt>().value.hours) } getter = object : ScopeCallable {
override suspend fun call(scp: Scope): Obj = ObjDuration(scp.thisAs<ObjInt>().value.hours)
}
) )
ObjReal.type.addPropertyDoc( ObjReal.type.addPropertyDoc(
name = "hour", name = "hour",
doc = "Construct a `Duration` equal to this real number of hours.", doc = "Construct a `Duration` equal to this real number of hours.",
type = type("lyng.Duration"), type = type("lyng.Duration"),
moduleName = "lyng.time", moduleName = "lyng.time",
getter = { ObjDuration(thisAs<ObjReal>().value.hours) } getter = object : ScopeCallable {
override suspend fun call(scp: Scope): Obj = ObjDuration(scp.thisAs<ObjReal>().value.hours)
}
) )
ObjInt.type.addPropertyDoc( ObjInt.type.addPropertyDoc(
name = "days", name = "days",
doc = "Construct a `Duration` equal to this integer number of days.", doc = "Construct a `Duration` equal to this integer number of days.",
type = type("lyng.Duration"), type = type("lyng.Duration"),
moduleName = "lyng.time", moduleName = "lyng.time",
getter = { ObjDuration(thisAs<ObjInt>().value.days) } getter = object : ScopeCallable {
override suspend fun call(scp: Scope): Obj = ObjDuration(scp.thisAs<ObjInt>().value.days)
}
) )
ObjReal.type.addPropertyDoc( ObjReal.type.addPropertyDoc(
name = "days", name = "days",
doc = "Construct a `Duration` equal to this real number of days.", doc = "Construct a `Duration` equal to this real number of days.",
type = type("lyng.Duration"), type = type("lyng.Duration"),
moduleName = "lyng.time", moduleName = "lyng.time",
getter = { ObjDuration(thisAs<ObjReal>().value.days) } getter = object : ScopeCallable {
override suspend fun call(scp: Scope): Obj = ObjDuration(scp.thisAs<ObjReal>().value.days)
}
) )
ObjInt.type.addPropertyDoc( ObjInt.type.addPropertyDoc(
name = "day", name = "day",
doc = "Construct a `Duration` equal to this integer number of days.", doc = "Construct a `Duration` equal to this integer number of days.",
type = type("lyng.Duration"), type = type("lyng.Duration"),
moduleName = "lyng.time", moduleName = "lyng.time",
getter = { ObjDuration(thisAs<ObjInt>().value.days) } getter = object : ScopeCallable {
override suspend fun call(scp: Scope): Obj = ObjDuration(scp.thisAs<ObjInt>().value.days)
}
) )
ObjReal.type.addPropertyDoc( ObjReal.type.addPropertyDoc(
name = "day", name = "day",
doc = "Construct a `Duration` equal to this real number of days.", doc = "Construct a `Duration` equal to this real number of days.",
type = type("lyng.Duration"), type = type("lyng.Duration"),
moduleName = "lyng.time", moduleName = "lyng.time",
getter = { ObjDuration(thisAs<ObjReal>().value.days) } getter = object : ScopeCallable {
override suspend fun call(scp: Scope): Obj = ObjDuration(scp.thisAs<ObjReal>().value.days)
}
) )

View File

@ -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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -17,31 +17,32 @@
package net.sergeych.lyng.obj package net.sergeych.lyng.obj
import net.sergeych.lyng.Arguments import net.sergeych.lyng.*
import net.sergeych.lyng.ClosureScope
import net.sergeych.lyng.Scope
import net.sergeych.lyng.Statement
class ObjDynamicContext(val delegate: ObjDynamic) : Obj() { class ObjDynamicContext(val delegate: ObjDynamic) : Obj() {
override val objClass: ObjClass get() = type override val objClass: ObjClass get() = type
companion object { companion object {
val type = ObjClass("DelegateContext").apply { val type = ObjClass("DelegateContext").apply {
addFn("get") { addFn("get", code = object : ScopeCallable {
val d = thisAs<ObjDynamicContext>().delegate override suspend fun call(scp: Scope): Obj {
if (d.readCallback != null) val d = scp.thisAs<ObjDynamicContext>().delegate
raiseIllegalState("get already defined") if (d.readCallback != null)
d.readCallback = requireOnlyArg() scp.raiseIllegalState("get already defined")
ObjVoid d.readCallback = scp.requireOnlyArg()
} return ObjVoid
}
})
addFn("set") { addFn("set", code = object : ScopeCallable {
val d = thisAs<ObjDynamicContext>().delegate override suspend fun call(scp: Scope): Obj {
if (d.writeCallback != null) val d = scp.thisAs<ObjDynamicContext>().delegate
raiseIllegalState("set already defined") if (d.writeCallback != null)
d.writeCallback = requireOnlyArg() scp.raiseIllegalState("set already defined")
ObjVoid d.writeCallback = scp.requireOnlyArg()
} return ObjVoid
}
})
} }
@ -81,11 +82,11 @@ open class ObjDynamic(var readCallback: Statement? = null, var writeCallback: St
scope: Scope, scope: Scope,
name: String, name: String,
args: Arguments, args: Arguments,
onNotFoundResult: (suspend () -> Obj?)? onNotFoundResult: OnNotFound?
): Obj { ): Obj {
val execBase = builderScope?.let { ClosureScope(scope, it) } ?: scope val execBase = builderScope?.let { ClosureScope(scope, it) } ?: scope
val over = readCallback?.execute(execBase.createChildScope(Arguments(ObjString(name)))) 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) ?: super.invokeInstanceMethod(scope, name, args, onNotFoundResult)
} }

View File

@ -35,6 +35,7 @@ package net.sergeych.lyng.obj/*
import kotlinx.serialization.json.JsonElement import kotlinx.serialization.json.JsonElement
import kotlinx.serialization.json.JsonPrimitive import kotlinx.serialization.json.JsonPrimitive
import net.sergeych.lyng.Scope import net.sergeych.lyng.Scope
import net.sergeych.lyng.ScopeCallable
import net.sergeych.lyng.miniast.addPropertyDoc import net.sergeych.lyng.miniast.addPropertyDoc
import net.sergeych.lyng.miniast.type import net.sergeych.lyng.miniast.type
import net.sergeych.lynon.LynonDecoder import net.sergeych.lynon.LynonDecoder
@ -74,12 +75,18 @@ class ObjEnumClass(val name: String) : ObjClass(name, EnumBase) {
init { init {
addClassConst("entries", objEntries ) addClassConst("entries", objEntries )
addClassFn("valueOf") { addClassFn("valueOf", code = object : ScopeCallable {
val name = requireOnlyArg<ObjString>() override suspend fun call(scp: Scope): Obj {
byName[name] ?: raiseSymbolNotFound("does not exists: enum ${className}.$name") val name = scp.requireOnlyArg<ObjString>()
} return byName[name] ?: scp.raiseSymbolNotFound("does not exists: enum ${className}.$name")
addPropertyDoc("name", doc = "Entry name as string", type = type("lyng.String"), getter = { thisAs<ObjEnumEntry>().name }) }
addPropertyDoc("ordinal", doc = "Entry ordinal position", type = type("lyng.Int"), getter = { thisAs<ObjEnumEntry>().ordinal }) })
addPropertyDoc("name", doc = "Entry name as string", type = type("lyng.String"), getter = object : ScopeCallable {
override suspend fun call(scp: Scope): Obj = scp.thisAs<ObjEnumEntry>().name
})
addPropertyDoc("ordinal", doc = "Entry ordinal position", type = type("lyng.Int"), getter = object : ScopeCallable {
override suspend fun call(scp: Scope): Obj = scp.thisAs<ObjEnumEntry>().ordinal
})
} }

View File

@ -125,7 +125,9 @@ open class ObjException(
class ExceptionClass(val name: String, vararg parents: ObjClass) : ObjClass(name, *parents) { class ExceptionClass(val name: String, vararg parents: ObjClass) : ObjClass(name, *parents) {
init { init {
constructorMeta = ArgsDeclaration( 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 Token.Type.RPAREN
) )
} }
@ -163,27 +165,33 @@ open class ObjException(
} }
val Root = ExceptionClass("Exception").apply { val Root = ExceptionClass("Exception").apply {
instanceInitializers.add(statement { instanceInitializers.add(statement(f = object : ScopeCallable {
if (thisObj is ObjInstance) { override suspend fun call(scp: Scope): Obj {
val msg = get("message")?.value ?: ObjString("Exception") if (scp.thisObj is ObjInstance) {
(thisObj as ObjInstance).instanceScope.addItem("Exception::message", false, msg) val msg = scp.get("message")?.value ?: ObjString("Exception")
(scp.thisObj as ObjInstance).instanceScope.addItem("Exception::message", false, msg)
val stack = captureStackTrace(this) val stack = captureStackTrace(scp)
(thisObj as ObjInstance).instanceScope.addItem("Exception::stackTrace", false, stack) (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( addPropertyDoc(
name = "message", name = "message",
doc = "Human‑readable error message.", doc = "Human‑readable error message.",
type = type("lyng.String"), type = type("lyng.String"),
moduleName = "lyng.stdlib", moduleName = "lyng.stdlib",
getter = { getter = object : ScopeCallable {
when (val t = this.thisObj) { override suspend fun call(scp: Scope): Obj {
is ObjException -> t.message return when (val t = scp.thisObj) {
is ObjInstance -> t.instanceScope.get("Exception::message")?.value ?: ObjNull is ObjException -> t.message
else -> ObjNull 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.", doc = "Extra data associated with the exception.",
type = type("lyng.Any", nullable = true), type = type("lyng.Any", nullable = true),
moduleName = "lyng.stdlib", moduleName = "lyng.stdlib",
getter = { getter = object : ScopeCallable {
when (val t = this.thisObj) { override suspend fun call(scp: Scope): Obj {
is ObjException -> t.extraData return when (val t = scp.thisObj) {
else -> ObjNull 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`.", doc = "Stack trace captured at throw site as a list of `StackTraceEntry`.",
type = TypeGenericDoc(type("lyng.List"), listOf(type("lyng.StackTraceEntry"))), type = TypeGenericDoc(type("lyng.List"), listOf(type("lyng.StackTraceEntry"))),
moduleName = "lyng.stdlib", moduleName = "lyng.stdlib",
getter = { getter = object : ScopeCallable {
when (val t = this.thisObj) { override suspend fun call(scp: Scope): Obj {
is ObjException -> t.getStackTrace() return when (val t = scp.thisObj) {
is ObjInstance -> t.instanceScope.get("Exception::stackTrace")?.value as? ObjList ?: ObjList() is ObjException -> t.getStackTrace()
else -> ObjList() is ObjInstance -> t.instanceScope.get("Exception::stackTrace")?.value as? ObjList ?: ObjList()
else -> ObjList()
}
} }
} }
) )
@ -216,23 +228,26 @@ open class ObjException(
name = "toString", name = "toString",
doc = "Human‑readable string representation of the error.", doc = "Human‑readable string representation of the error.",
returns = type("lyng.String"), returns = type("lyng.String"),
moduleName = "lyng.stdlib" moduleName = "lyng.stdlib",
) { code = object : ScopeCallable {
val msg = when (val t = thisObj) { override suspend fun call(scp: Scope): Obj {
is ObjException -> t.message.value val msg = when (val t = scp.thisObj) {
is ObjInstance -> (t.instanceScope.get("Exception::message")?.value as? ObjString)?.value is ObjException -> t.message.value
?: t.objClass.className 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() private val op = ProtectedOp()

View File

@ -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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -45,28 +45,31 @@ class ObjFlowBuilder(val output: SendChannel<Obj>) : Obj() {
doc = "Send a value to the flow consumer. Suspends if back‑pressured; no‑ops after consumer stops.", doc = "Send a value to the flow consumer. Suspends if back‑pressured; no‑ops after consumer stops.",
params = listOf(ParamDoc("value", type("lyng.Any"))), params = listOf(ParamDoc("value", type("lyng.Any"))),
returns = type("lyng.Void"), returns = type("lyng.Void"),
moduleName = "lyng.stdlib" moduleName = "lyng.stdlib",
) { code = object : ScopeCallable {
val data = requireOnlyArg<Obj>() override suspend fun call(scp: Scope): Obj {
try { val data = scp.requireOnlyArg<Obj>()
val channel = thisAs<ObjFlowBuilder>().output try {
if (!channel.isClosedForSend) val channel = scp.thisAs<ObjFlowBuilder>().output
channel.send(data) if (!channel.isClosedForSend)
else channel.send(data)
// Flow consumer is no longer collecting; signal producer to stop else
throw ScriptFlowIsNoMoreCollected() // Flow consumer is no longer collecting; signal producer to stop
} catch (x: Exception) { throw ScriptFlowIsNoMoreCollected()
// Any failure to send (including closed channel) should gracefully stop the producer. } catch (x: Exception) {
if (x is CancellationException) { // Any failure to send (including closed channel) should gracefully stop the producer.
// Cancellation is a normal control-flow event if (x is CancellationException) {
throw ScriptFlowIsNoMoreCollected() // Cancellation is a normal control-flow event
} else { throw ScriptFlowIsNoMoreCollected()
// Treat other send failures as normal flow termination as well } else {
throw ScriptFlowIsNoMoreCollected() // 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", name = "iterator",
doc = "Create a pull‑based iterator over this flow. Each step resumes the producer as needed.", 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"))), returns = TypeGenericDoc(type("lyng.Iterator"), listOf(type("lyng.Any"))),
moduleName = "lyng.stdlib" moduleName = "lyng.stdlib",
) { code = object : ScopeCallable {
val objFlow = thisAs<ObjFlow>() override suspend fun call(scp: Scope): Obj {
ObjFlowIterator(statement { val objFlow = scp.thisAs<ObjFlow>()
objFlow.producer.execute( return ObjFlowIterator(statement(f = object : ScopeCallable {
ClosureScope(this, objFlow.scope) 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", name = "hasNext",
doc = "Whether another element is available from the flow.", doc = "Whether another element is available from the flow.",
returns = type("lyng.Bool"), returns = type("lyng.Bool"),
moduleName = "lyng.stdlib" moduleName = "lyng.stdlib",
) { thisAs<ObjFlowIterator>().hasNext(this).toObj() } code = object : ScopeCallable {
override suspend fun call(scp: Scope): Obj = scp.thisAs<ObjFlowIterator>().hasNext(scp).toObj()
}
)
addFnDoc( addFnDoc(
name = "next", name = "next",
doc = "Receive the next element from the flow or throw if completed.", doc = "Receive the next element from the flow or throw if completed.",
returns = type("lyng.Any"), returns = type("lyng.Any"),
moduleName = "lyng.stdlib" moduleName = "lyng.stdlib",
) { code = object : ScopeCallable {
val x = thisAs<ObjFlowIterator>() override suspend fun call(scp: Scope): Obj {
x.next(this) val x = scp.thisAs<ObjFlowIterator>()
} return x.next(scp)
}
}
)
addFnDoc( addFnDoc(
name = "cancelIteration", name = "cancelIteration",
doc = "Stop iteration and cancel the underlying flow producer.", doc = "Stop iteration and cancel the underlying flow producer.",
returns = type("lyng.Void"), returns = type("lyng.Void"),
moduleName = "lyng.stdlib" moduleName = "lyng.stdlib",
) { code = object : ScopeCallable {
val x = thisAs<ObjFlowIterator>() override suspend fun call(scp: Scope): Obj {
x.cancel() val x = scp.thisAs<ObjFlowIterator>()
ObjVoid x.cancel()
} return ObjVoid
}
}
)
} }
} }
} }

View File

@ -218,7 +218,7 @@ class ObjInstance(override val objClass: ObjClass) : Obj() {
override suspend fun invokeInstanceMethod( override suspend fun invokeInstanceMethod(
scope: Scope, name: String, args: Arguments, scope: Scope, name: String, args: Arguments,
onNotFoundResult: (suspend () -> Obj?)? onNotFoundResult: OnNotFound?
): Obj { ): Obj {
val caller = scope.currentClassCtx val caller = scope.currentClassCtx
@ -231,7 +231,7 @@ class ObjInstance(override val objClass: ObjClass) : Obj() {
if (rec.type == ObjRecord.Type.Property) { if (rec.type == ObjRecord.Type.Property) {
if (args.isEmpty()) return (rec.value as ObjProperty).callGetter(scope, this, decl) if (args.isEmpty()) return (rec.value as ObjProperty).callGetter(scope, this, decl)
} else if (rec.type == ObjRecord.Type.Fun) { } 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 (rec.type == ObjRecord.Type.Property) {
if (args.isEmpty()) return (rec.value as ObjProperty).callGetter(scope, this, c) if (args.isEmpty()) return (rec.value as ObjProperty).callGetter(scope, this, c)
} else if (rec.type == ObjRecord.Type.Fun) { } 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 (rec.type == ObjRecord.Type.Property) {
if (args.isEmpty()) return (rec.value as ObjProperty).callGetter(scope, this, c) if (args.isEmpty()) return (rec.value as ObjProperty).callGetter(scope, this, c)
} else if (rec.type == ObjRecord.Type.Fun) { } 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 val del = instanceScope[storageName]?.delegate ?: rec.delegate
?: scope.raiseError("Internal error: delegated member $name has no delegate (tried $storageName)") ?: 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() 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) // Fallback: property delegation (getValue then call result)
val propVal = del.invokeInstanceMethod(scope, "getValue", Arguments(this, ObjString(name))) 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 val decl = rec.declaringClass ?: cls
@ -285,14 +285,14 @@ class ObjInstance(override val objClass: ObjClass) : Obj() {
scope.raiseError( scope.raiseError(
ObjIllegalAccessException( ObjIllegalAccessException(
scope, 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 (rec.type == ObjRecord.Type.Property) {
if (args.isEmpty()) return (rec.value as ObjProperty).callGetter(scope, this, decl) if (args.isEmpty()) return (rec.value as ObjProperty).callGetter(scope, this, decl)
} else if (rec.type == ObjRecord.Type.Fun) { } else if (rec.type == ObjRecord.Type.Fun) {
return rec.value.invoke( return rec.value.invokeCallable(
instanceScope, instanceScope,
this, this,
args, 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) { } else if (rec.type == ObjRecord.Type.Field || rec.type == ObjRecord.Type.ConstructorField || rec.type == ObjRecord.Type.Argument) {
val resolved = readField(scope, name) 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, scope: Scope,
name: String, name: String,
args: Arguments, args: Arguments,
onNotFoundResult: (suspend () -> Obj?)? onNotFoundResult: OnNotFound?
): Obj { ): Obj {
// Qualified method dispatch must start from the specified ancestor, not from the instance scope. // Qualified method dispatch must start from the specified ancestor, not from the instance scope.
memberFromAncestor(name)?.let { rec -> memberFromAncestor(name)?.let { rec ->
val decl = rec.declaringClass ?: startClass val decl = rec.declaringClass ?: startClass
val caller = scope.currentClassCtx val caller = scope.currentClassCtx
if (!canAccessMember(rec.visibility, decl, caller, name)) 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 val saved = instance.instanceScope.currentClassCtx
instance.instanceScope.currentClassCtx = decl instance.instanceScope.currentClassCtx = decl
try { try {
return rec.value.invoke(instance.instanceScope, instance, args) return rec.value.invokeCallable(instance.instanceScope, instance, args)
} finally { } finally {
instance.instanceScope.currentClassCtx = saved instance.instanceScope.currentClassCtx = saved
} }
@ -532,19 +532,19 @@ class ObjQualifiedView(val instance: ObjInstance, private val startClass: ObjCla
scope.raiseError( scope.raiseError(
ObjIllegalAccessException( ObjIllegalAccessException(
scope, scope,
"can't invoke method $name (declared in ${decl?.className ?: "?"})" "can't invokeCallable method $name (declared in ${decl?.className ?: "?"})"
) )
) )
val saved = instance.instanceScope.currentClassCtx val saved = instance.instanceScope.currentClassCtx
instance.instanceScope.currentClassCtx = decl instance.instanceScope.currentClassCtx = decl
try { try {
return rec.value.invoke(instance.instanceScope, instance, args) return rec.value.invokeCallable(instance.instanceScope, instance, args)
} finally { } finally {
instance.instanceScope.currentClassCtx = saved instance.instanceScope.currentClassCtx = saved
} }
} }
} }
return onNotFoundResult?.invoke() ?: scope.raiseSymbolNotFound(name) return onNotFoundResult?.call() ?: scope.raiseSymbolNotFound(name)
} }
override fun toString(): String = instance.toString() override fun toString(): String = instance.toString()

View File

@ -18,17 +18,16 @@
package net.sergeych.lyng.obj package net.sergeych.lyng.obj
import kotlinx.datetime.* import kotlinx.datetime.*
import kotlinx.datetime.Instant
import kotlinx.serialization.json.JsonElement import kotlinx.serialization.json.JsonElement
import kotlinx.serialization.json.JsonPrimitive import kotlinx.serialization.json.JsonPrimitive
import net.sergeych.lyng.Scope import net.sergeych.lyng.Scope
import net.sergeych.lyng.miniast.addFnDoc import net.sergeych.lyng.ScopeCallable
import net.sergeych.lyng.miniast.addPropertyDoc import net.sergeych.lyng.miniast.*
import net.sergeych.lyng.miniast.type
import net.sergeych.lynon.LynonDecoder import net.sergeych.lynon.LynonDecoder
import net.sergeych.lynon.LynonEncoder import net.sergeych.lynon.LynonEncoder
import net.sergeych.lynon.LynonSettings import net.sergeych.lynon.LynonSettings
import net.sergeych.lynon.LynonType import net.sergeych.lynon.LynonType
import kotlin.time.Clock
import kotlin.time.isDistantFuture import kotlin.time.isDistantFuture
import kotlin.time.isDistantPast import kotlin.time.isDistantPast
@ -113,7 +112,7 @@ class ObjInstant(val instant: Instant,val truncateMode: LynonSettings.InstantTru
return ObjInstant( return ObjInstant(
when (a0) { when (a0) {
null -> { null -> {
val t = Clock.System.now() val t = kotlin.time.Clock.System.now()
Instant.fromEpochSeconds(t.epochSeconds, t.nanosecondsOfSecond) Instant.fromEpochSeconds(t.epochSeconds, t.nanosecondsOfSecond)
} }
is ObjInt -> Instant.fromEpochSeconds(a0.value) 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).", doc = "Return the number of seconds since the Unix epoch as a real number (including fractions).",
type = type("lyng.Real"), type = type("lyng.Real"),
moduleName = "lyng.time", moduleName = "lyng.time",
getter = { getter = object : ScopeCallable {
val instant = thisAs<ObjInstant>().instant override suspend fun call(scp: Scope): Obj {
ObjReal(instant.epochSeconds + instant.nanosecondsOfSecond * 1e-9) val instant = scp.thisAs<ObjInstant>().instant
return ObjReal(instant.epochSeconds + instant.nanosecondsOfSecond * 1e-9)
}
} }
) )
addPropertyDoc( addPropertyDoc(
@ -167,92 +168,114 @@ class ObjInstant(val instant: Instant,val truncateMode: LynonSettings.InstantTru
doc = "Whether this instant represents the distant future.", doc = "Whether this instant represents the distant future.",
type = type("lyng.Bool"), type = type("lyng.Bool"),
moduleName = "lyng.time", moduleName = "lyng.time",
getter = { thisAs<ObjInstant>().instant.isDistantFuture.toObj() } getter = object : ScopeCallable {
override suspend fun call(scp: Scope): Obj = scp.thisAs<ObjInstant>().instant.isDistantFuture.toObj()
}
) )
addPropertyDoc( addPropertyDoc(
name = "isDistantPast", name = "isDistantPast",
doc = "Whether this instant represents the distant past.", doc = "Whether this instant represents the distant past.",
type = type("lyng.Bool"), type = type("lyng.Bool"),
moduleName = "lyng.time", moduleName = "lyng.time",
getter = { thisAs<ObjInstant>().instant.isDistantPast.toObj() } getter = object : ScopeCallable {
override suspend fun call(scp: Scope): Obj = scp.thisAs<ObjInstant>().instant.isDistantPast.toObj()
}
) )
addPropertyDoc( addPropertyDoc(
name = "epochWholeSeconds", name = "epochWholeSeconds",
doc = "Return the number of full seconds since the Unix epoch.", doc = "Return the number of full seconds since the Unix epoch.",
type = type("lyng.Int"), type = type("lyng.Int"),
moduleName = "lyng.time", moduleName = "lyng.time",
getter = { ObjInt(thisAs<ObjInstant>().instant.epochSeconds) } getter = object : ScopeCallable {
override suspend fun call(scp: Scope): Obj = ObjInt(scp.thisAs<ObjInstant>().instant.epochSeconds)
}
) )
addPropertyDoc( addPropertyDoc(
name = "nanosecondsOfSecond", name = "nanosecondsOfSecond",
doc = "The number of nanoseconds within the current second.", doc = "The number of nanoseconds within the current second.",
type = type("lyng.Int"), type = type("lyng.Int"),
moduleName = "lyng.time", moduleName = "lyng.time",
getter = { ObjInt(thisAs<ObjInstant>().instant.nanosecondsOfSecond.toLong()) } getter = object : ScopeCallable {
override suspend fun call(scp: Scope): Obj = ObjInt(scp.thisAs<ObjInstant>().instant.nanosecondsOfSecond.toLong())
}
) )
addFnDoc( addFnDoc(
name = "truncateToMinute", name = "truncateToMinute",
doc = "Truncate this instant to the nearest minute.", doc = "Truncate this instant to the nearest minute.",
returns = type("lyng.Instant"), returns = type("lyng.Instant"),
moduleName = "lyng.time" moduleName = "lyng.time",
) { code = object : ScopeCallable {
val t = thisAs<ObjInstant>().instant override suspend fun call(scp: Scope): Obj {
val tz = TimeZone.UTC val t = scp.thisAs<ObjInstant>().instant
val dt = t.toLocalDateTime(tz) val tz = TimeZone.UTC
val truncated = LocalDateTime(dt.year, dt.month, dt.dayOfMonth, dt.hour, dt.minute, 0, 0) val dt = t.toLocalDateTime(tz)
ObjInstant(truncated.toInstant(tz), LynonSettings.InstantTruncateMode.Second) val truncated = LocalDateTime(dt.year, dt.month, dt.dayOfMonth, dt.hour, dt.minute, 0, 0)
} return ObjInstant(truncated.toInstant(tz), LynonSettings.InstantTruncateMode.Second)
}
}
)
addFnDoc( addFnDoc(
name = "truncateToSecond", name = "truncateToSecond",
doc = "Truncate this instant to the nearest second.", doc = "Truncate this instant to the nearest second.",
returns = type("lyng.Instant"), returns = type("lyng.Instant"),
moduleName = "lyng.time" moduleName = "lyng.time",
) { code = object : ScopeCallable {
val t = thisAs<ObjInstant>().instant override suspend fun call(scp: Scope): Obj {
ObjInstant(Instant.fromEpochSeconds(t.epochSeconds), LynonSettings.InstantTruncateMode.Second) val t = scp.thisAs<ObjInstant>().instant
} return ObjInstant(Instant.fromEpochSeconds(t.epochSeconds), LynonSettings.InstantTruncateMode.Second)
}
}
)
addFnDoc( addFnDoc(
name = "truncateToMillisecond", name = "truncateToMillisecond",
doc = "Truncate this instant to the nearest millisecond.", doc = "Truncate this instant to the nearest millisecond.",
returns = type("lyng.Instant"), returns = type("lyng.Instant"),
moduleName = "lyng.time" moduleName = "lyng.time",
) { code = object : ScopeCallable {
val t = thisAs<ObjInstant>().instant override suspend fun call(scp: Scope): Obj {
ObjInstant( val t = scp.thisAs<ObjInstant>().instant
Instant.fromEpochSeconds(t.epochSeconds, t.nanosecondsOfSecond / 1_000_000 * 1_000_000), return ObjInstant(
LynonSettings.InstantTruncateMode.Millisecond Instant.fromEpochSeconds(t.epochSeconds, t.nanosecondsOfSecond / 1_000_000 * 1_000_000),
) LynonSettings.InstantTruncateMode.Millisecond
} )
}
}
)
addFnDoc( addFnDoc(
name = "truncateToMicrosecond", name = "truncateToMicrosecond",
doc = "Truncate this instant to the nearest microsecond.", doc = "Truncate this instant to the nearest microsecond.",
returns = type("lyng.Instant"), returns = type("lyng.Instant"),
moduleName = "lyng.time" moduleName = "lyng.time",
) { code = object : ScopeCallable {
val t = thisAs<ObjInstant>().instant override suspend fun call(scp: Scope): Obj {
ObjInstant( val t = scp.thisAs<ObjInstant>().instant
Instant.fromEpochSeconds(t.epochSeconds, t.nanosecondsOfSecond / 1_000 * 1_000), return ObjInstant(
LynonSettings.InstantTruncateMode.Microsecond Instant.fromEpochSeconds(t.epochSeconds, t.nanosecondsOfSecond / 1_000 * 1_000),
) LynonSettings.InstantTruncateMode.Microsecond
} )
}
}
)
addFnDoc( addFnDoc(
name = "toRFC3339", name = "toRFC3339",
doc = "Return the RFC3339 string representation of this instant in UTC (e.g., '1970-01-01T00:00:00Z').", doc = "Return the RFC3339 string representation of this instant in UTC (e.g., '1970-01-01T00:00:00Z').",
returns = type("lyng.String"), returns = type("lyng.String"),
moduleName = "lyng.time" moduleName = "lyng.time",
) { code = object : ScopeCallable {
thisAs<ObjInstant>().instant.toString().toObj() override suspend fun call(scp: Scope): Obj = scp.thisAs<ObjInstant>().instant.toString().toObj()
} }
)
addFnDoc( addFnDoc(
name = "toSortableString", name = "toSortableString",
doc = "Alias to toRFC3339.", doc = "Alias to toRFC3339.",
returns = type("lyng.String"), returns = type("lyng.String"),
moduleName = "lyng.time" moduleName = "lyng.time",
) { code = object : ScopeCallable {
thisAs<ObjInstant>().instant.toString().toObj() override suspend fun call(scp: Scope): Obj = scp.thisAs<ObjInstant>().instant.toString().toObj()
} }
)
addFnDoc( addFnDoc(
name = "toDateTime", 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.", "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))), params = listOf(net.sergeych.lyng.miniast.ParamDoc("tz", type = type("lyng.Any", true))),
returns = type("lyng.DateTime"), returns = type("lyng.DateTime"),
moduleName = "lyng.time" moduleName = "lyng.time",
) { code = object : ScopeCallable {
val tz = when (val a = args.list.getOrNull(0)) { override suspend fun call(scp: Scope): Obj {
null -> TimeZone.currentSystemDefault() val tz = when (val a = scp.args.list.getOrNull(0)) {
is ObjString -> TimeZone.of(a.value) null -> TimeZone.currentSystemDefault()
is ObjInt -> UtcOffset(seconds = a.value.toInt()).asTimeZone() is ObjString -> TimeZone.of(a.value)
else -> raiseIllegalArgument("invalid timezone: $a") is ObjInt -> UtcOffset(seconds = a.value.toInt()).asTimeZone()
else -> scp.raiseIllegalArgument("invalid timezone: $a")
}
return ObjDateTime(scp.thisAs<ObjInstant>().instant, tz)
}
} }
ObjDateTime(thisAs<ObjInstant>().instant, tz) )
}
// class members // class members
addClassConst("distantFuture", distantFuture) addClassConst("distantFuture", distantFuture)
addClassConst("distantPast", distantPast) addClassConst("distantPast", distantPast)
addClassFn("now") { addClassFn("now", code = object : ScopeCallable {
ObjInstant(Clock.System.now()) override suspend fun call(scp: Scope): Obj = ObjInstant(kotlin.time.Clock.System.now())
} })
// addFn("epochMilliseconds") { // addFn("epochMilliseconds") {
// ObjInt(instant.toEpochMilliseconds()) // ObjInt(instant.toEpochMilliseconds())
// } // }

View File

@ -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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with 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.JsonElement
import kotlinx.serialization.json.JsonPrimitive import kotlinx.serialization.json.JsonPrimitive
import net.sergeych.lyng.Scope import net.sergeych.lyng.Scope
import net.sergeych.lyng.ScopeCallable
import net.sergeych.lyng.miniast.addFnDoc import net.sergeych.lyng.miniast.addFnDoc
import net.sergeych.lynon.LynonDecoder import net.sergeych.lynon.LynonDecoder
import net.sergeych.lynon.LynonEncoder import net.sergeych.lynon.LynonEncoder
@ -183,10 +184,11 @@ class ObjInt(val value: Long, override val isConst: Boolean = false) : Obj(), Nu
name = "toInt", name = "toInt",
doc = "Returns this integer (identity operation).", doc = "Returns this integer (identity operation).",
returns = net.sergeych.lyng.miniast.type("lyng.Int"), returns = net.sergeych.lyng.miniast.type("lyng.Int"),
moduleName = "lyng.stdlib" moduleName = "lyng.stdlib",
) { code = object : ScopeCallable {
thisObj override suspend fun call(scp: Scope): Obj = scp.thisObj
} }
)
} }
} }
} }

View File

@ -18,6 +18,8 @@
package net.sergeych.lyng.obj package net.sergeych.lyng.obj
import net.sergeych.lyng.Arguments import net.sergeych.lyng.Arguments
import net.sergeych.lyng.Scope
import net.sergeych.lyng.ScopeCallable
import net.sergeych.lyng.Statement import net.sergeych.lyng.Statement
import net.sergeych.lyng.miniast.ParamDoc import net.sergeych.lyng.miniast.ParamDoc
import net.sergeych.lyng.miniast.addFnDoc import net.sergeych.lyng.miniast.addFnDoc
@ -35,13 +37,15 @@ val ObjIterable by lazy {
doc = "Collect elements of this iterable into a new list.", doc = "Collect elements of this iterable into a new list.",
type = type("lyng.List"), type = type("lyng.List"),
moduleName = "lyng.stdlib", moduleName = "lyng.stdlib",
getter = { getter = object : ScopeCallable {
val result = mutableListOf<Obj>() override suspend fun call(scp: Scope): Obj {
val it = this.thisObj.invokeInstanceMethod(this, "iterator") val result = mutableListOf<Obj>()
while (it.invokeInstanceMethod(this, "hasNext").toBool()) { val it = scp.thisObj.invokeInstanceMethod(scp, "iterator")
result.add(it.invokeInstanceMethod(this, "next")) 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")), params = listOf(ParamDoc("element")),
returns = type("lyng.Bool"), returns = type("lyng.Bool"),
isOpen = true, isOpen = true,
moduleName = "lyng.stdlib" moduleName = "lyng.stdlib",
) { code = object : ScopeCallable {
val obj = args.firstAndOnly() override suspend fun call(scp: Scope): Obj {
val it = thisObj.invokeInstanceMethod(this, "iterator") val obj = scp.args.firstAndOnly()
while (it.invokeInstanceMethod(this, "hasNext").toBool()) { val it = scp.thisObj.invokeInstanceMethod(scp, "iterator")
if (obj.equals(this, it.invokeInstanceMethod(this, "next"))) while (it.invokeInstanceMethod(scp, "hasNext").toBool()) {
return@addFnDoc ObjTrue if (obj.equals(scp, it.invokeInstanceMethod(scp, "next")))
return ObjTrue
}
return ObjFalse
}
} }
ObjFalse )
}
addFnDoc( addFnDoc(
name = "indexOf", name = "indexOf",
@ -69,34 +76,39 @@ val ObjIterable by lazy {
params = listOf(ParamDoc("element")), params = listOf(ParamDoc("element")),
returns = type("lyng.Int"), returns = type("lyng.Int"),
isOpen = true, isOpen = true,
moduleName = "lyng.stdlib" moduleName = "lyng.stdlib",
) { code = object : ScopeCallable {
val obj = args.firstAndOnly() override suspend fun call(scp: Scope): Obj {
var index = 0 val obj = scp.args.firstAndOnly()
val it = thisObj.invokeInstanceMethod(this, "iterator") var index = 0
while (it.invokeInstanceMethod(this, "hasNext").toBool()) { val it = scp.thisObj.invokeInstanceMethod(scp, "iterator")
if (obj.equals(this, it.invokeInstanceMethod(this, "next"))) while (it.invokeInstanceMethod(scp, "hasNext").toBool()) {
return@addFnDoc ObjInt(index.toLong()) if (obj.equals(scp, it.invokeInstanceMethod(scp, "next")))
index++ return ObjInt(index.toLong())
index++
}
return ObjInt(-1L)
}
} }
ObjInt(-1L) )
}
addPropertyDoc( addPropertyDoc(
name = "toSet", name = "toSet",
doc = "Collect elements of this iterable into a new set.", doc = "Collect elements of this iterable into a new set.",
type = type("lyng.Set"), type = type("lyng.Set"),
moduleName = "lyng.stdlib", moduleName = "lyng.stdlib",
getter = { getter = object : ScopeCallable {
if( this.thisObj.isInstanceOf(ObjSet.type) ) override suspend fun call(scp: Scope): Obj {
this.thisObj return if( scp.thisObj.isInstanceOf(ObjSet.type) )
else { scp.thisObj
val result = mutableSetOf<Obj>() else {
val it = this.thisObj.invokeInstanceMethod(this, "iterator") val result = mutableSetOf<Obj>()
while (it.invokeInstanceMethod(this, "hasNext").toBool()) { val it = scp.thisObj.invokeInstanceMethod(scp, "iterator")
result.add(it.invokeInstanceMethod(this, "next")) 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.", doc = "Collect pairs into a map using [0] as key and [1] as value for each element.",
type = type("lyng.Map"), type = type("lyng.Map"),
moduleName = "lyng.stdlib", moduleName = "lyng.stdlib",
getter = { getter = object : ScopeCallable {
val result = mutableMapOf<Obj, Obj>() override suspend fun call(scp: Scope): Obj {
this.thisObj.enumerate(this) { pair -> val result = mutableMapOf<Obj, Obj>()
when (pair) { scp.thisObj.enumerate(scp, object : EnumerateCallback {
is ObjMapEntry -> result[pair.key] = pair.value override suspend fun call(pair: Obj): Boolean {
else -> result[pair.getAt(this, 0)] = pair.getAt(this, 1) when (pair) {
} is ObjMapEntry -> result[pair.key] = pair.value
true 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.", doc = "Build a map from elements using the lambda result as key.",
params = listOf(ParamDoc("keySelector")), params = listOf(ParamDoc("keySelector")),
returns = type("lyng.Map"), returns = type("lyng.Map"),
moduleName = "lyng.stdlib" moduleName = "lyng.stdlib",
) { code = object : ScopeCallable {
val association = requireOnlyArg<Statement>() override suspend fun call(scp: Scope): Obj {
val result = ObjMap() val association = scp.requireOnlyArg<Statement>()
thisObj.toFlow(this).collect { val result = ObjMap()
result.map[association.call(this, it)] = it scp.thisObj.toFlow(scp).collect {
result.map[association.invokeCallable(scp, scp.thisObj, it)] = it
}
return result
}
} }
result )
}
addFnDoc( addFnDoc(
name = "forEach", name = "forEach",
doc = "Apply the lambda to each element in iteration order.", doc = "Apply the lambda to each element in iteration order.",
params = listOf(ParamDoc("action")), params = listOf(ParamDoc("action")),
isOpen = true, isOpen = true,
moduleName = "lyng.stdlib" moduleName = "lyng.stdlib",
) { code = object : ScopeCallable {
val it = thisObj.invokeInstanceMethod(this, "iterator") override suspend fun call(scp: Scope): Obj {
val fn = requiredArg<Statement>(0) val it = scp.thisObj.invokeInstanceMethod(scp, "iterator")
while (it.invokeInstanceMethod(this, "hasNext").toBool()) { val fn = scp.requiredArg<Statement>(0)
val x = it.invokeInstanceMethod(this, "next") while (it.invokeInstanceMethod(scp, "hasNext").toBool()) {
fn.execute(this.createChildScope(Arguments(listOf(x)))) val x = it.invokeInstanceMethod(scp, "next")
fn.execute(scp.createChildScope(Arguments(listOf(x))))
}
return ObjVoid
}
} }
ObjVoid )
}
addFnDoc( addFnDoc(
name = "map", name = "map",
@ -156,15 +178,18 @@ val ObjIterable by lazy {
params = listOf(ParamDoc("transform")), params = listOf(ParamDoc("transform")),
returns = type("lyng.List"), returns = type("lyng.List"),
isOpen = true, isOpen = true,
moduleName = "lyng.stdlib" moduleName = "lyng.stdlib",
) { code = object : ScopeCallable {
val fn = requiredArg<Statement>(0) override suspend fun call(scp: Scope): Obj {
val result = mutableListOf<Obj>() val fn = scp.requiredArg<Statement>(0)
thisObj.toFlow(this).collect { val result = mutableListOf<Obj>()
result.add(fn.call(this, it)) scp.thisObj.toFlow(scp).collect {
result.add(fn.invokeCallable(scp, scp.thisObj, it))
}
return ObjList(result)
}
} }
ObjList(result) )
}
addFnDoc( addFnDoc(
name = "mapNotNull", name = "mapNotNull",
@ -172,46 +197,56 @@ val ObjIterable by lazy {
params = listOf(ParamDoc("transform")), params = listOf(ParamDoc("transform")),
returns = type("lyng.List"), returns = type("lyng.List"),
isOpen = true, isOpen = true,
moduleName = "lyng.stdlib" moduleName = "lyng.stdlib",
) { code = object : ScopeCallable {
val fn = requiredArg<Statement>(0) override suspend fun call(scp: Scope): Obj {
val result = mutableListOf<Obj>() val fn = scp.requiredArg<Statement>(0)
thisObj.toFlow(this).collect { val result = mutableListOf<Obj>()
val transformed = fn.call(this, it) scp.thisObj.toFlow(scp).collect {
if( transformed != ObjNull) result.add(transformed) val transformed = fn.invokeCallable(scp, scp.thisObj, it)
if( transformed != ObjNull) result.add(transformed)
}
return ObjList(result)
}
} }
ObjList(result) )
}
addFnDoc( addFnDoc(
name = "take", name = "take",
doc = "Take the first N elements and return them as a list.", doc = "Take the first N elements and return them as a list.",
params = listOf(ParamDoc("n", type("lyng.Int"))), params = listOf(ParamDoc("n", type("lyng.Int"))),
returns = type("lyng.List"), returns = type("lyng.List"),
moduleName = "lyng.stdlib" moduleName = "lyng.stdlib",
) { code = object : ScopeCallable {
var n = requireOnlyArg<ObjInt>().value.toInt() override suspend fun call(scp: Scope): Obj {
val result = mutableListOf<Obj>() var n = scp.requireOnlyArg<ObjInt>().value.toInt()
if (n > 0) { val result = mutableListOf<Obj>()
thisObj.enumerate(this) { if (n > 0) {
result.add(it) scp.thisObj.enumerate(scp, object : EnumerateCallback {
--n > 0 override suspend fun call(element: Obj): Boolean {
result.add(element)
return --n > 0
}
})
}
return ObjList(result)
} }
} }
ObjList(result) )
}
addPropertyDoc( addPropertyDoc(
name = "isEmpty", name = "isEmpty",
doc = "Whether the iterable has no elements.", doc = "Whether the iterable has no elements.",
type = type("lyng.Bool"), type = type("lyng.Bool"),
moduleName = "lyng.stdlib", moduleName = "lyng.stdlib",
getter = { getter = object : ScopeCallable {
ObjBool( override suspend fun call(scp: Scope): Obj {
this.thisObj.invokeInstanceMethod(this, "iterator") return ObjBool(
.invokeInstanceMethod(this, "hasNext").toBool() scp.thisObj.invokeInstanceMethod(scp, "iterator")
.not() .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`.", doc = "Return a new list sorted using the provided comparator `(a, b) -> Int`.",
params = listOf(ParamDoc("comparator")), params = listOf(ParamDoc("comparator")),
returns = type("lyng.List"), returns = type("lyng.List"),
moduleName = "lyng.stdlib" moduleName = "lyng.stdlib",
) { code = object : ScopeCallable {
val list = thisObj.callMethod<ObjList>(this, "toList") override suspend fun call(scp: Scope): Obj {
val comparator = requireOnlyArg<Statement>() val list = scp.thisObj.callMethod<ObjList>(scp, "toList")
list.quicksort { a, b -> val comparator = scp.requireOnlyArg<Statement>()
comparator.call(this, a, b).toInt() list.quicksort { a, b ->
comparator.invokeCallable(scp, scp.thisObj, a, b).toInt()
}
return list
}
} }
list )
}
addFnDoc( addFnDoc(
name = "reversed", name = "reversed",
doc = "Return a new list with elements in reverse order.", doc = "Return a new list with elements in reverse order.",
returns = type("lyng.List"), returns = type("lyng.List"),
moduleName = "lyng.stdlib" moduleName = "lyng.stdlib",
) { code = object : ScopeCallable {
val list = thisObj.callMethod<ObjList>(this, "toList") override suspend fun call(scp: Scope): Obj {
list.list.reverse() val list = scp.thisObj.callMethod<ObjList>(scp, "toList")
list list.list.reverse()
} return list
}
}
)
} }
} }

View File

@ -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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -17,6 +17,8 @@
package net.sergeych.lyng.obj 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.TypeGenericDoc
import net.sergeych.lyng.miniast.addFnDoc import net.sergeych.lyng.miniast.addFnDoc
import net.sergeych.lyng.miniast.type import net.sergeych.lyng.miniast.type
@ -38,44 +40,50 @@ val ObjIterator by lazy {
doc = "Optional hint to stop iteration early and free resources.", doc = "Optional hint to stop iteration early and free resources.",
returns = type("lyng.Void"), returns = type("lyng.Void"),
isOpen = true, isOpen = true,
moduleName = "lyng.stdlib" moduleName = "lyng.stdlib",
) { code = object : ScopeCallable {
ObjVoid override suspend fun call(scp: Scope): Obj = ObjVoid
} }
)
addFnDoc( addFnDoc(
name = "hasNext", name = "hasNext",
doc = "Whether another element is available.", doc = "Whether another element is available.",
returns = type("lyng.Bool"), returns = type("lyng.Bool"),
isOpen = true, isOpen = true,
moduleName = "lyng.stdlib" moduleName = "lyng.stdlib",
) { code = object : ScopeCallable {
raiseNotImplemented("hasNext() is not implemented") override suspend fun call(scp: Scope): Obj = scp.raiseNotImplemented("hasNext() is not implemented")
} }
)
addFnDoc( addFnDoc(
name = "next", name = "next",
doc = "Return the next element.", doc = "Return the next element.",
returns = type("lyng.Any"), returns = type("lyng.Any"),
isOpen = true, isOpen = true,
moduleName = "lyng.stdlib" moduleName = "lyng.stdlib",
) { code = object : ScopeCallable {
raiseNotImplemented("next() is not implemented") override suspend fun call(scp: Scope): Obj = scp.raiseNotImplemented("next() is not implemented")
} }
)
// Helper to consume iterator into a list // Helper to consume iterator into a list
addFnDoc( addFnDoc(
name = "toList", name = "toList",
doc = "Consume this iterator and collect elements into a list.", doc = "Consume this iterator and collect elements into a list.",
returns = TypeGenericDoc(type("lyng.List"), listOf(type("lyng.Any"))), returns = TypeGenericDoc(type("lyng.List"), listOf(type("lyng.Any"))),
moduleName = "lyng.stdlib" moduleName = "lyng.stdlib",
) { code = object : ScopeCallable {
val out = mutableListOf<Obj>() override suspend fun call(scp: Scope): Obj {
while (true) { val out = mutableListOf<Obj>()
val has = thisObj.invokeInstanceMethod(this, "hasNext").toBool() while (true) {
if (!has) break val has = scp.thisObj.invokeInstanceMethod(scp, "hasNext").toBool()
val v = thisObj.invokeInstanceMethod(this, "next") if (!has) break
out += v val v = scp.thisObj.invokeInstanceMethod(scp, "next")
out += v
}
return ObjList(out.toMutableList())
}
} }
ObjList(out.toMutableList()) )
}
} }
} }

View File

@ -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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with 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 kotlinx.coroutines.flow.flow import kotlinx.coroutines.flow.flow
import net.sergeych.lyng.Scope import net.sergeych.lyng.Scope
import net.sergeych.lyng.ScopeCallable
/** /**
* Iterator wrapper to allow Kotlin collections to be returned from Lyng objects; * Iterator wrapper to allow Kotlin collections to be returned from Lyng objects;
@ -33,8 +34,12 @@ class ObjKotlinIterator(val iterator: Iterator<Any?>) : Obj() {
companion object { companion object {
val type = ObjClass("KotlinIterator", ObjIterator).apply { val type = ObjClass("KotlinIterator", ObjIterator).apply {
addFn("next") { thisAs<ObjKotlinIterator>().iterator.next().toObj() } addFn("next", code = object : ScopeCallable {
addFn("hasNext") { thisAs<ObjKotlinIterator>().iterator.hasNext().toObj() } override suspend fun call(scp: Scope): Obj = scp.thisAs<ObjKotlinIterator>().iterator.next().toObj()
})
addFn("hasNext", code = object : ScopeCallable {
override suspend fun call(scp: Scope): Obj = scp.thisAs<ObjKotlinIterator>().iterator.hasNext().toObj()
})
} }
} }
@ -50,12 +55,14 @@ class ObjKotlinObjIterator(val iterator: Iterator<Obj>) : Obj() {
companion object { companion object {
val type = ObjClass("KotlinIterator", ObjIterator).apply { val type = ObjClass("KotlinIterator", ObjIterator).apply {
addFn("next") { addFn("next", code = object : ScopeCallable {
thisAs<ObjKotlinObjIterator>().iterator.next() override suspend fun call(scp: Scope): Obj =
} scp.thisAs<ObjKotlinObjIterator>().iterator.next()
addFn("hasNext") { })
thisAs<ObjKotlinObjIterator>().iterator.hasNext().toObj() addFn("hasNext", code = object : ScopeCallable {
} override suspend fun call(scp: Scope): Obj =
scp.thisAs<ObjKotlinObjIterator>().iterator.hasNext().toObj()
})
} }
} }
@ -71,8 +78,8 @@ fun Obj.toFlow(scope: Scope): Flow<Obj> = flow {
val iterator = invokeInstanceMethod(scope, "iterator") val iterator = invokeInstanceMethod(scope, "iterator")
val hasNext = iterator.getInstanceMethod(scope, "hasNext") val hasNext = iterator.getInstanceMethod(scope, "hasNext")
val next = iterator.getInstanceMethod(scope, "next") val next = iterator.getInstanceMethod(scope, "next")
while (hasNext.invoke(scope, iterator).toBool()) { while (hasNext.invokeCallable(scope, iterator).toBool()) {
emit(next.invoke(scope, iterator)) emit(next.invokeCallable(scope, iterator))
} }
} }

View File

@ -20,6 +20,7 @@ package net.sergeych.lyng.obj
import kotlinx.serialization.json.JsonArray import kotlinx.serialization.json.JsonArray
import kotlinx.serialization.json.JsonElement import kotlinx.serialization.json.JsonElement
import net.sergeych.lyng.Scope import net.sergeych.lyng.Scope
import net.sergeych.lyng.ScopeCallable
import net.sergeych.lyng.Statement import net.sergeych.lyng.Statement
import net.sergeych.lyng.miniast.ParamDoc import net.sergeych.lyng.miniast.ParamDoc
import net.sergeych.lyng.miniast.addFnDoc import net.sergeych.lyng.miniast.addFnDoc
@ -111,13 +112,13 @@ class ObjList(val list: MutableList<Obj> = mutableListOf()) : Obj() {
val next2 = it2.getInstanceMethod(scope, "next") val next2 = it2.getInstanceMethod(scope, "next")
while (it1.hasNext()) { 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 v1 = it1.next()
val v2 = next2.invoke(scope, it2) val v2 = next2.invokeCallable(scope, it2)
val d = v1.compareTo(scope, v2) val d = v1.compareTo(scope, v2)
if (d != 0) return d 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 return -2
} }
@ -169,9 +170,9 @@ class ObjList(val list: MutableList<Obj> = mutableListOf()) : Obj() {
return list.contains(other) 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) { for (item in list) {
if (!callback(item)) break if (!callback.call(item)) break
} }
} }
@ -179,7 +180,9 @@ class ObjList(val list: MutableList<Obj> = mutableListOf()) : Obj() {
get() = type get() = type
override suspend fun toKotlin(scope: Scope): Any { override suspend fun toKotlin(scope: Scope): Any {
return list.map { it.toKotlin(scope) } val res = ArrayList<Any?>(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) suspend fun quicksort(compare: suspend (Obj, Obj) -> Int) = quicksort(compare, 0, list.size - 1)
@ -230,7 +233,9 @@ class ObjList(val list: MutableList<Obj> = mutableListOf()) : Obj() {
override suspend fun lynonType(): LynonType = LynonType.List override suspend fun lynonType(): LynonType = LynonType.List
override suspend fun toJson(scope: Scope): JsonElement { override suspend fun toJson(scope: Scope): JsonElement {
return JsonArray(list.map { it.toJson(scope) }) val res = ArrayList<JsonElement>(list.size)
for (i in list) res.add(i.toJson(scope))
return JsonArray(res)
} }
override suspend fun defaultToString(scope: Scope): ObjString { override suspend fun defaultToString(scope: Scope): ObjString {
@ -256,256 +261,290 @@ class ObjList(val list: MutableList<Obj> = mutableListOf()) : Obj() {
doc = "Number of elements in this list.", doc = "Number of elements in this list.",
type = type("lyng.Int"), type = type("lyng.Int"),
moduleName = "lyng.stdlib", moduleName = "lyng.stdlib",
getter = { getter = object : ScopeCallable {
val s = (this.thisObj as ObjList).list.size override suspend fun call(scp: Scope): Obj {
s.toObj() return (scp.thisObj as ObjList).list.size.toObj()
}
} }
) )
addFnDoc( addFnDoc(
name = "add", name = "add",
doc = "Append one or more elements to the end of this list.", doc = "Append one or more elements to the end of this list.",
moduleName = "lyng.stdlib" moduleName = "lyng.stdlib",
) { code = object : ScopeCallable {
val l = thisAs<ObjList>().list override suspend fun call(scp: Scope): Obj {
for (a in args) l.add(a) val l = scp.thisAs<ObjList>().list
ObjVoid for (a in scp.args) l.add(a)
} return ObjVoid
}
}
)
addFnDoc( addFnDoc(
name = "insertAt", name = "insertAt",
doc = "Insert elements starting at the given index.", doc = "Insert elements starting at the given index.",
params = listOf(ParamDoc("index", type("lyng.Int"))), params = listOf(ParamDoc("index", type("lyng.Int"))),
moduleName = "lyng.stdlib" moduleName = "lyng.stdlib",
) { code = object : ScopeCallable {
if (args.size < 2) raiseError("addAt takes 2+ arguments") override suspend fun call(scp: Scope): Obj {
val l = thisAs<ObjList>() if (scp.args.size < 2) scp.raiseError("addAt takes 2+ arguments")
var index = requiredArg<ObjInt>(0).value.toInt() val l = scp.thisAs<ObjList>()
for (i in 1..<args.size) l.list.add(index++, args[i]) var index = scp.requiredArg<ObjInt>(0).value.toInt()
ObjVoid for (i in 1..<scp.args.size) l.list.add(index++, scp.args[i])
} return ObjVoid
}
}
)
addFnDoc( addFnDoc(
name = "removeAt", name = "removeAt",
doc = "Remove element at index, or a range [start,end) if two indices are provided. Returns the list.", doc = "Remove element at index, or a range [start,end) if two indices are provided. Returns the list.",
params = listOf(ParamDoc("start", type("lyng.Int")), ParamDoc("end", type("lyng.Int"))), params = listOf(ParamDoc("start", type("lyng.Int")), ParamDoc("end", type("lyng.Int"))),
moduleName = "lyng.stdlib" moduleName = "lyng.stdlib",
) { code = object : ScopeCallable {
val self = thisAs<ObjList>() override suspend fun call(scp: Scope): Obj {
val start = requiredArg<ObjInt>(0).value.toInt() val self = scp.thisAs<ObjList>()
if (args.size == 2) { val start = scp.requiredArg<ObjInt>(0).value.toInt()
val end = requireOnlyArg<ObjInt>().value.toInt() if (scp.args.size == 2) {
self.list.subList(start, end).clear() val end = scp.requireOnlyArg<ObjInt>().value.toInt()
} else self.list.subList(start, end).clear()
self.list.removeAt(start) } else
self self.list.removeAt(start)
} return self
}
}
)
addFnDoc( addFnDoc(
name = "removeLast", name = "removeLast",
doc = "Remove the last element or the last N elements if a count is provided. Returns the list.", 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"))), params = listOf(ParamDoc("count", type("lyng.Int"))),
moduleName = "lyng.stdlib" moduleName = "lyng.stdlib",
) { code = object : ScopeCallable {
val self = thisAs<ObjList>() override suspend fun call(scp: Scope): Obj {
if (args.isNotEmpty()) { val self = scp.thisAs<ObjList>()
val count = requireOnlyArg<ObjInt>().value.toInt() if (scp.args.isNotEmpty()) {
val size = self.list.size val count = scp.requireOnlyArg<ObjInt>().value.toInt()
if (count >= size) self.list.clear() val size = self.list.size
else self.list.subList(size - count, size).clear() if (count >= size) self.list.clear()
} else self.list.removeLast() else self.list.subList(size - count, size).clear()
self } else self.list.removeLast()
} return self
}
}
)
addFnDoc( addFnDoc(
name = "removeRange", name = "removeRange",
doc = "Remove a range of elements. Accepts a Range or (start, endInclusive). Returns the list.", doc = "Remove a range of elements. Accepts a Range or (start, endInclusive). Returns the list.",
params = listOf(ParamDoc("range")), params = listOf(ParamDoc("range")),
moduleName = "lyng.stdlib" moduleName = "lyng.stdlib",
) { code = object : ScopeCallable {
val self = thisAs<ObjList>() override suspend fun call(scp: Scope): Obj {
val list = self.list val self = scp.thisAs<ObjList>()
val range = requiredArg<Obj>(0) val list = self.list
if (range is ObjRange) { val range = scp.requiredArg<Obj>(0)
val index = range if (range is ObjRange) {
when { val index = range
index.start is ObjInt && index.end is ObjInt -> { when {
if (index.isEndInclusive) index.start is ObjInt && index.end is ObjInt -> {
list.subList(index.start.toInt(), index.end.toInt() + 1) if (index.isEndInclusive)
else list.subList(index.start.toInt(), index.end.toInt() + 1)
list.subList(index.start.toInt(), index.end.toInt()) else
} list.subList(index.start.toInt(), index.end.toInt())
}
index.isOpenStart && !index.isOpenEnd -> { index.isOpenStart && !index.isOpenEnd -> {
if (index.isEndInclusive) if (index.isEndInclusive)
list.subList(0, index.end!!.toInt() + 1) list.subList(0, index.end!!.toInt() + 1)
else else
list.subList(0, index.end!!.toInt()) list.subList(0, index.end!!.toInt())
} }
index.isOpenEnd && !index.isOpenStart -> { index.isOpenEnd && !index.isOpenStart -> {
list.subList(index.start!!.toInt(), list.size) list.subList(index.start!!.toInt(), list.size)
} }
index.isOpenStart && index.isOpenEnd -> { index.isOpenStart && index.isOpenEnd -> {
list list
} }
else -> { else -> {
throw RuntimeException("Can't apply range for index: $index") throw RuntimeException("Can't apply range for index: $index")
}
}.clear()
} else {
val start = range.toInt()
val end = scp.requiredArg<ObjInt>(1).value.toInt() + 1
self.list.subList(start, end).clear()
} }
}.clear() return self
} else { }
val start = range.toInt()
val end = requiredArg<ObjInt>(1).value.toInt() + 1
self.list.subList(start, end).clear()
} }
self )
}
addFnDoc( addFnDoc(
name = "sortWith", name = "sortWith",
doc = "Sort this list in-place using a comparator function (a, b) -> Int.", doc = "Sort this list in-place using a comparator function (a, b) -> Int.",
params = listOf(ParamDoc("comparator")), params = listOf(ParamDoc("comparator")),
moduleName = "lyng.stdlib" moduleName = "lyng.stdlib",
) { code = object : ScopeCallable {
val comparator = requireOnlyArg<Statement>() override suspend fun call(scp: Scope): Obj {
thisAs<ObjList>().quicksort { a, b -> comparator.call(this, a, b).toInt() } val comparator = scp.requireOnlyArg<Statement>()
ObjVoid scp.thisAs<ObjList>().quicksort { a, b -> comparator.invokeCallable(scp, scp.thisObj, a, b).toInt() }
} return ObjVoid
}
}
)
addFnDoc( addFnDoc(
name = "shuffle", name = "shuffle",
doc = "Shuffle elements of this list in-place.", doc = "Shuffle elements of this list in-place.",
moduleName = "lyng.stdlib" moduleName = "lyng.stdlib",
) { code = object : ScopeCallable {
thisAs<ObjList>().list.shuffle() override suspend fun call(scp: Scope): Obj {
ObjVoid scp.thisAs<ObjList>().list.shuffle()
} return ObjVoid
}
}
)
addFnDoc( addFnDoc(
name = "sum", name = "sum",
doc = "Sum elements using dynamic '+' or optimized integer path. Returns null for empty lists.", doc = "Sum elements using dynamic '+' or optimized integer path. Returns null for empty lists.",
moduleName = "lyng.stdlib" moduleName = "lyng.stdlib",
) { code = object : ScopeCallable {
val self = thisAs<ObjList>() override suspend fun call(scp: Scope): Obj {
val l = self.list val self = scp.thisAs<ObjList>()
if (l.isEmpty()) return@addFnDoc ObjNull val l = self.list
if (net.sergeych.lyng.PerfFlags.PRIMITIVE_FASTOPS) { if (l.isEmpty()) return ObjNull
// Fast path: all ints → accumulate as long if (net.sergeych.lyng.PerfFlags.PRIMITIVE_FASTOPS) {
var i = 0 // Fast path: all ints → accumulate as long
var acc: Long = 0 var i = 0
while (i < l.size) { var acc: Long = 0
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) { while (i < l.size) {
res = res.plus(this, l[i]) val v = l[i]
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( addFnDoc(
name = "min", name = "min",
doc = "Minimum element by natural order. Returns null for empty lists.", doc = "Minimum element by natural order. Returns null for empty lists.",
moduleName = "lyng.stdlib" moduleName = "lyng.stdlib",
) { code = object : ScopeCallable {
val l = thisAs<ObjList>().list override suspend fun call(scp: Scope): Obj {
if (l.isEmpty()) return@addFnDoc ObjNull val l = scp.thisAs<ObjList>().list
if (net.sergeych.lyng.PerfFlags.PRIMITIVE_FASTOPS) { if (l.isEmpty()) return ObjNull
var i = 0 if (net.sergeych.lyng.PerfFlags.PRIMITIVE_FASTOPS) {
var hasOnlyInts = true var i = 0
var minVal: Long = Long.MAX_VALUE var hasOnlyInts = true
while (i < l.size) { var minVal: Long = Long.MAX_VALUE
val v = l[i] while (i < l.size) {
if (v is ObjInt) { val v = l[i]
if (v.value < minVal) minVal = v.value if (v is ObjInt) {
} else { if (v.value < minVal) minVal = v.value
hasOnlyInts = false } else {
break 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( addFnDoc(
name = "max", name = "max",
doc = "Maximum element by natural order. Returns null for empty lists.", doc = "Maximum element by natural order. Returns null for empty lists.",
moduleName = "lyng.stdlib" moduleName = "lyng.stdlib",
) { code = object : ScopeCallable {
val l = thisAs<ObjList>().list override suspend fun call(scp: Scope): Obj {
if (l.isEmpty()) return@addFnDoc ObjNull val l = scp.thisAs<ObjList>().list
if (net.sergeych.lyng.PerfFlags.PRIMITIVE_FASTOPS) { if (l.isEmpty()) return ObjNull
var i = 0 if (net.sergeych.lyng.PerfFlags.PRIMITIVE_FASTOPS) {
var hasOnlyInts = true var i = 0
var maxVal: Long = Long.MIN_VALUE var hasOnlyInts = true
while (i < l.size) { var maxVal: Long = Long.MIN_VALUE
val v = l[i] while (i < l.size) {
if (v is ObjInt) { val v = l[i]
if (v.value > maxVal) maxVal = v.value if (v is ObjInt) {
} else { if (v.value > maxVal) maxVal = v.value
hasOnlyInts = false } else {
break 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( addFnDoc(
name = "indexOf", name = "indexOf",
doc = "Index of the first occurrence of the given element, or -1 if not found.", doc = "Index of the first occurrence of the given element, or -1 if not found.",
params = listOf(ParamDoc("element")), params = listOf(ParamDoc("element")),
returns = type("lyng.Int"), returns = type("lyng.Int"),
moduleName = "lyng.stdlib" moduleName = "lyng.stdlib",
) { code = object : ScopeCallable {
val l = thisAs<ObjList>().list override suspend fun call(scp: Scope): Obj {
val needle = args.firstAndOnly() val l = scp.thisAs<ObjList>().list
if (net.sergeych.lyng.PerfFlags.PRIMITIVE_FASTOPS && needle is ObjInt) { val needle = scp.args.firstAndOnly()
var i = 0 if (net.sergeych.lyng.PerfFlags.PRIMITIVE_FASTOPS && needle is ObjInt) {
while (i < l.size) { var i = 0
val v = l[i] while (i < l.size) {
if (v is ObjInt && v.value == needle.value) return@addFnDoc ObjInt(i.toLong()) val v = l[i]
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())
}
} }
} }
} }

View File

@ -20,6 +20,7 @@ package net.sergeych.lyng.obj
import kotlinx.serialization.json.JsonElement import kotlinx.serialization.json.JsonElement
import kotlinx.serialization.json.JsonObject import kotlinx.serialization.json.JsonObject
import net.sergeych.lyng.Scope import net.sergeych.lyng.Scope
import net.sergeych.lyng.ScopeCallable
import net.sergeych.lyng.Statement import net.sergeych.lyng.Statement
import net.sergeych.lyng.miniast.* import net.sergeych.lyng.miniast.*
import net.sergeych.lynon.LynonDecoder 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.", doc = "Key component of this map entry.",
type = type("lyng.Any"), type = type("lyng.Any"),
moduleName = "lyng.stdlib", moduleName = "lyng.stdlib",
getter = { thisAs<ObjMapEntry>().key } getter = object : ScopeCallable {
override suspend fun call(scp: Scope): Obj = scp.thisAs<ObjMapEntry>().key
}
) )
addPropertyDoc( addPropertyDoc(
name = "value", name = "value",
doc = "Value component of this map entry.", doc = "Value component of this map entry.",
type = type("lyng.Any"), type = type("lyng.Any"),
moduleName = "lyng.stdlib", moduleName = "lyng.stdlib",
getter = { thisAs<ObjMapEntry>().value } getter = object : ScopeCallable {
override suspend fun call(scp: Scope): Obj = scp.thisAs<ObjMapEntry>().value
}
) )
addPropertyDoc( addPropertyDoc(
name = "size", name = "size",
doc = "Number of components in this entry (always 2).", doc = "Number of components in this entry (always 2).",
type = type("lyng.Int"), type = type("lyng.Int"),
moduleName = "lyng.stdlib", 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<Obj, Obj> = mutableMapOf()) : Obj() {
} }
override suspend fun toJson(scope: Scope): JsonElement { override suspend fun toJson(scope: Scope): JsonElement {
return JsonObject( val res = mutableMapOf<String, JsonElement>()
map.map { it.key.toString(scope).value to it.value.toJson(scope) }.toMap() for ((k, v) in map) {
) res[k.toString(scope).value] = v.toJson(scope)
}
return JsonObject(res)
} }
companion object { companion object {
@ -224,94 +233,119 @@ class ObjMap(val map: MutableMap<Obj, Obj> = mutableMapOf()) : Obj() {
} }
}.apply { }.apply {
implementingNames.add("Delegate") implementingNames.add("Delegate")
addFn("getValue") { addFn("getValue", code = object : ScopeCallable {
val self = thisAs<ObjMap>() override suspend fun call(scp: Scope): Obj {
val key = requiredArg<Obj>(1) val self = scp.thisAs<ObjMap>()
self.map[key] ?: ObjNull val key = scp.requiredArg<Obj>(1)
} return self.map[key] ?: ObjNull
addFn("setValue") { }
val self = thisAs<ObjMap>() })
val key = requiredArg<Obj>(1) addFn("setValue", code = object : ScopeCallable {
val value = requiredArg<Obj>(2) override suspend fun call(scp: Scope): Obj {
self.map[key] = value val self = scp.thisAs<ObjMap>()
self val key = scp.requiredArg<Obj>(1)
} val value = scp.requiredArg<Obj>(2)
addFn("bind") { self.map[key] = value
val mode = requiredArg<ObjEnumEntry>(1) return self
if( mode.ordinal.value > 1) }
raiseIllegalArgument("Map can be delegated only to val or var, got ${mode.name.value}") })
thisObj addFn("bind", code = object : ScopeCallable {
} override suspend fun call(scp: Scope): Obj {
val mode = scp.requiredArg<ObjEnumEntry>(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( addFnDoc(
name = "getOrNull", name = "getOrNull",
doc = "Get value by key or return null if the key is absent.", doc = "Get value by key or return null if the key is absent.",
params = listOf(ParamDoc("key")), params = listOf(ParamDoc("key")),
returns = type("lyng.Any", nullable = true), returns = type("lyng.Any", nullable = true),
moduleName = "lyng.stdlib" moduleName = "lyng.stdlib",
) { code = object : ScopeCallable {
val key = args.firstAndOnly(pos) override suspend fun call(scp: Scope): Obj {
thisAs<ObjMap>().map.getOrElse(key) { ObjNull } val key = scp.args.firstAndOnly(scp.pos)
} return scp.thisAs<ObjMap>().map.getOrElse(key) { ObjNull }
}
}
)
addFnDoc( addFnDoc(
name = "getOrPut", name = "getOrPut",
doc = "Get value by key or compute, store, and return the default from a lambda.", doc = "Get value by key or compute, store, and return the default from a lambda.",
params = listOf(ParamDoc("key"), ParamDoc("default")), params = listOf(ParamDoc("key"), ParamDoc("default")),
returns = type("lyng.Any"), returns = type("lyng.Any"),
moduleName = "lyng.stdlib" moduleName = "lyng.stdlib",
) { code = object : ScopeCallable {
val key = requiredArg<Obj>(0) override suspend fun call(scp: Scope): Obj {
thisAs<ObjMap>().map.getOrPut(key) { val key = scp.requiredArg<Obj>(0)
val lambda = requiredArg<Statement>(1) return scp.thisAs<ObjMap>().map.getOrPut(key) {
lambda.execute(this) val lambda = scp.requiredArg<Statement>(1)
lambda.execute(scp)
}
}
} }
} )
addPropertyDoc( addPropertyDoc(
name = "size", name = "size",
doc = "Number of entries in the map.", doc = "Number of entries in the map.",
type = type("lyng.Int"), type = type("lyng.Int"),
moduleName = "lyng.stdlib", 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( addFnDoc(
name = "remove", name = "remove",
doc = "Remove the entry by key and return the previous value or null if absent.", doc = "Remove the entry by key and return the previous value or null if absent.",
params = listOf(ParamDoc("key")), params = listOf(ParamDoc("key")),
returns = type("lyng.Any", nullable = true), returns = type("lyng.Any", nullable = true),
moduleName = "lyng.stdlib" moduleName = "lyng.stdlib",
) { code = object : ScopeCallable {
thisAs<ObjMap>().map.remove(requiredArg<Obj>(0))?.toObj() ?: ObjNull override suspend fun call(scp: Scope): Obj {
} return scp.thisAs<ObjMap>().map.remove(scp.requiredArg<Obj>(0))?.toObj() ?: ObjNull
}
}
)
addFnDoc( addFnDoc(
name = "clear", name = "clear",
doc = "Remove all entries from this map. Returns the map.", doc = "Remove all entries from this map. Returns the map.",
returns = type("lyng.Map"), returns = type("lyng.Map"),
moduleName = "lyng.stdlib" moduleName = "lyng.stdlib",
) { code = object : ScopeCallable {
thisAs<ObjMap>().map.clear() override suspend fun call(scp: Scope): Obj {
thisObj scp.thisAs<ObjMap>().map.clear()
} return scp.thisObj
}
}
)
addPropertyDoc( addPropertyDoc(
name = "keys", name = "keys",
doc = "List of keys in this map.", doc = "List of keys in this map.",
type = TypeGenericDoc(type("lyng.List"), listOf(type("lyng.Any"))), type = TypeGenericDoc(type("lyng.List"), listOf(type("lyng.Any"))),
moduleName = "lyng.stdlib", moduleName = "lyng.stdlib",
getter = { thisAs<ObjMap>().map.keys.toObj() } getter = object : ScopeCallable {
override suspend fun call(scp: Scope): Obj = scp.thisAs<ObjMap>().map.keys.toObj()
}
) )
addPropertyDoc( addPropertyDoc(
name = "values", name = "values",
doc = "List of values in this map.", doc = "List of values in this map.",
type = TypeGenericDoc(type("lyng.List"), listOf(type("lyng.Any"))), type = TypeGenericDoc(type("lyng.List"), listOf(type("lyng.Any"))),
moduleName = "lyng.stdlib", moduleName = "lyng.stdlib",
getter = { ObjList(thisAs<ObjMap>().map.values.toMutableList()) } getter = object : ScopeCallable {
override suspend fun call(scp: Scope): Obj = ObjList(scp.thisAs<ObjMap>().map.values.toMutableList())
}
) )
addFnDoc( addFnDoc(
name = "iterator", name = "iterator",
doc = "Iterator over map entries as MapEntry objects.", doc = "Iterator over map entries as MapEntry objects.",
returns = TypeGenericDoc(type("lyng.Iterator"), listOf(type("lyng.MapEntry"))), returns = TypeGenericDoc(type("lyng.Iterator"), listOf(type("lyng.MapEntry"))),
moduleName = "lyng.stdlib" moduleName = "lyng.stdlib",
) { code = object : ScopeCallable {
ObjKotlinIterator(thisAs<ObjMap>().map.entries.iterator()) override suspend fun call(scp: Scope): Obj = ObjKotlinIterator(scp.thisAs<ObjMap>().map.entries.iterator())
} }
)
} }
} }

View File

@ -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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with 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")), params = listOf(ParamDoc("action")),
returns = type("lyng.Any"), returns = type("lyng.Any"),
moduleName = "lyng.stdlib" moduleName = "lyng.stdlib"
) { ) { scp ->
val f = requiredArg<Statement>(0) val f = scp.requiredArg<Statement>(0)
// Execute user lambda directly in the current scope to preserve the active scope // Execute user lambda directly in the current scope to preserve the active scope
// ancestry across suspension points. The lambda still constructs a ClosureScope // ancestry across suspension points. The lambda still constructs a ClosureScope
// on top of this frame, and parseLambdaExpression sets skipScopeCreation for its body. // on top of this frame, and parseLambdaExpression sets skipScopeCreation for its body.
thisAs<ObjMutex>().mutex.withLock { f.execute(this) } scp.thisAs<ObjMutex>().mutex.withLock { f.execute(scp) }
} }
} }
} }

View File

@ -18,6 +18,7 @@
package net.sergeych.lyng.obj package net.sergeych.lyng.obj
import net.sergeych.lyng.Scope import net.sergeych.lyng.Scope
import net.sergeych.lyng.ScopeCallable
import net.sergeych.lyng.miniast.TypeGenericDoc import net.sergeych.lyng.miniast.TypeGenericDoc
import net.sergeych.lyng.miniast.addFnDoc import net.sergeych.lyng.miniast.addFnDoc
import net.sergeych.lyng.miniast.addPropertyDoc 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 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) { if (start is ObjInt && end is ObjInt) {
val s = start.value val s = start.value
val e = end.value val e = end.value
if (isEndInclusive) { if (isEndInclusive) {
for (i in s..e) { for (i in s..e) {
if (!callback(ObjInt.of(i))) break if (!callback.call(ObjInt.of(i))) break
} }
} else { } else {
for (i in s..<e) { for (i in s..<e) {
if (!callback(ObjInt.of(i))) break if (!callback.call(ObjInt.of(i))) break
} }
} }
} else if (start is ObjChar && end is ObjChar) { } else if (start is ObjChar && end is ObjChar) {
@ -133,11 +134,11 @@ class ObjRange(val start: Obj?, val end: Obj?, val isEndInclusive: Boolean) : Ob
val e = end.value val e = end.value
if (isEndInclusive) { if (isEndInclusive) {
for (c in s..e) { for (c in s..e) {
if (!callback(ObjChar(c))) break if (!callback.call(ObjChar(c))) break
} }
} else { } else {
for (c in s..<e) { for (c in s..<e) {
if (!callback(ObjChar(c))) break if (!callback.call(ObjChar(c))) break
} }
} }
} else { } else {
@ -180,64 +181,79 @@ class ObjRange(val start: Obj?, val end: Obj?, val isEndInclusive: Boolean) : Ob
doc = "Start bound of the range or null if open.", doc = "Start bound of the range or null if open.",
type = type("lyng.Any", nullable = true), type = type("lyng.Any", nullable = true),
moduleName = "lyng.stdlib", moduleName = "lyng.stdlib",
getter = { thisAs<ObjRange>().start ?: ObjNull } getter = object : ScopeCallable {
override suspend fun call(scp: Scope): Obj = scp.thisAs<ObjRange>().start ?: ObjNull
}
) )
addPropertyDoc( addPropertyDoc(
name = "end", name = "end",
doc = "End bound of the range or null if open.", doc = "End bound of the range or null if open.",
type = type("lyng.Any", nullable = true), type = type("lyng.Any", nullable = true),
moduleName = "lyng.stdlib", moduleName = "lyng.stdlib",
getter = { thisAs<ObjRange>().end ?: ObjNull } getter = object : ScopeCallable {
override suspend fun call(scp: Scope): Obj = scp.thisAs<ObjRange>().end ?: ObjNull
}
) )
addPropertyDoc( addPropertyDoc(
name = "isOpen", name = "isOpen",
doc = "Whether the range is open on either side (no start or no end).", doc = "Whether the range is open on either side (no start or no end).",
type = type("lyng.Bool"), type = type("lyng.Bool"),
moduleName = "lyng.stdlib", moduleName = "lyng.stdlib",
getter = { thisAs<ObjRange>().let { it.start == null || it.end == null }.toObj() } getter = object : ScopeCallable {
override suspend fun call(scp: Scope): Obj = scp.thisAs<ObjRange>().let { it.start == null || it.end == null }.toObj()
}
) )
addPropertyDoc( addPropertyDoc(
name = "isIntRange", name = "isIntRange",
doc = "True if both bounds are Int values.", doc = "True if both bounds are Int values.",
type = type("lyng.Bool"), type = type("lyng.Bool"),
moduleName = "lyng.stdlib", moduleName = "lyng.stdlib",
getter = { thisAs<ObjRange>().isIntRange.toObj() } getter = object : ScopeCallable {
override suspend fun call(scp: Scope): Obj = scp.thisAs<ObjRange>().isIntRange.toObj()
}
) )
addPropertyDoc( addPropertyDoc(
name = "isCharRange", name = "isCharRange",
doc = "True if both bounds are Char values.", doc = "True if both bounds are Char values.",
type = type("lyng.Bool"), type = type("lyng.Bool"),
moduleName = "lyng.stdlib", moduleName = "lyng.stdlib",
getter = { thisAs<ObjRange>().isCharRange.toObj() } getter = object : ScopeCallable {
override suspend fun call(scp: Scope): Obj = scp.thisAs<ObjRange>().isCharRange.toObj()
}
) )
addPropertyDoc( addPropertyDoc(
name = "isEndInclusive", name = "isEndInclusive",
doc = "Whether the end bound is inclusive.", doc = "Whether the end bound is inclusive.",
type = type("lyng.Bool"), type = type("lyng.Bool"),
moduleName = "lyng.stdlib", moduleName = "lyng.stdlib",
getter = { thisAs<ObjRange>().isEndInclusive.toObj() } getter = object : ScopeCallable {
override suspend fun call(scp: Scope): Obj = scp.thisAs<ObjRange>().isEndInclusive.toObj()
}
) )
addFnDoc( addFnDoc(
name = "iterator", name = "iterator",
doc = "Iterator over elements in this range (optimized for Int ranges).", doc = "Iterator over elements in this range (optimized for Int ranges).",
returns = TypeGenericDoc(type("lyng.Iterator"), listOf(type("lyng.Any"))), returns = TypeGenericDoc(type("lyng.Iterator"), listOf(type("lyng.Any"))),
moduleName = "lyng.stdlib" moduleName = "lyng.stdlib",
) { code = object : ScopeCallable {
val self = thisAs<ObjRange>() override suspend fun call(scp: Scope): Obj {
if (net.sergeych.lyng.PerfFlags.RANGE_FAST_ITER) { val self = scp.thisAs<ObjRange>()
val s = self.start if (net.sergeych.lyng.PerfFlags.RANGE_FAST_ITER) {
val e = self.end val s = self.start
if (s is ObjInt && e is ObjInt) { val e = self.end
val start = s.value.toInt() if (s is ObjInt && e is ObjInt) {
val endExclusive = (if (self.isEndInclusive) e.value.toInt() + 1 else e.value.toInt()) val start = s.value.toInt()
// Only for ascending simple ranges; fall back otherwise val endExclusive = (if (self.isEndInclusive) e.value.toInt() + 1 else e.value.toInt())
if (start <= endExclusive) { // Only for ascending simple ranges; fall back otherwise
return@addFnDoc ObjFastIntRangeIterator(start, endExclusive) if (start <= endExclusive) {
return ObjFastIntRangeIterator(start, endExclusive)
}
}
} }
return ObjRangeIterator(self).apply { init(scp) }
} }
} }
ObjRangeIterator(self).apply { init() } )
}
} }
} }
} }

View File

@ -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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with 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.PerfFlags
import net.sergeych.lyng.Scope import net.sergeych.lyng.Scope
import net.sergeych.lyng.ScopeCallable
class ObjRangeIterator(val self: ObjRange) : Obj() { class ObjRangeIterator(val self: ObjRange) : Obj() {
@ -28,7 +29,7 @@ class ObjRangeIterator(val self: ObjRange) : Obj() {
override val objClass: ObjClass get() = type override val objClass: ObjClass get() = type
fun Scope.init() { fun init(scope: Scope) {
val s = self.start val s = self.start
val e = self.end val e = self.end
if (s is ObjInt && e is ObjInt) { if (s is ObjInt && e is ObjInt) {
@ -43,7 +44,7 @@ class ObjRangeIterator(val self: ObjRange) : Obj() {
else else
(e.value.code - s.value.code) (e.value.code - s.value.code)
} else { } 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 { companion object {
val type = ObjClass("RangeIterator", ObjIterator).apply { val type = ObjClass("RangeIterator", ObjIterator).apply {
addFn("hasNext") { addFn("hasNext", code = object : ScopeCallable {
thisAs<ObjRangeIterator>().hasNext().toObj() override suspend fun call(scp: Scope): Obj =
} scp.thisAs<ObjRangeIterator>().hasNext().toObj()
addFn("next") { })
thisAs<ObjRangeIterator>().next(this) addFn("next", code = object : ScopeCallable {
} override suspend fun call(scp: Scope): Obj =
scp.thisAs<ObjRangeIterator>().next(scp)
})
} }
} }
} }
@ -94,8 +97,12 @@ class ObjFastIntRangeIterator(private val start: Int, private val endExclusive:
companion object { companion object {
val type = ObjClass("FastIntRangeIterator", ObjIterator).apply { val type = ObjClass("FastIntRangeIterator", ObjIterator).apply {
addFn("hasNext") { thisAs<ObjFastIntRangeIterator>().hasNext().toObj() } addFn("hasNext", code = object : ScopeCallable {
addFn("next") { thisAs<ObjFastIntRangeIterator>().next(this) } override suspend fun call(scp: Scope): Obj = scp.thisAs<ObjFastIntRangeIterator>().hasNext().toObj()
})
addFn("next", code = object : ScopeCallable {
override suspend fun call(scp: Scope): Obj = scp.thisAs<ObjFastIntRangeIterator>().next(scp)
})
} }
} }
} }

View File

@ -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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with 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 kotlinx.serialization.json.JsonPrimitive
import net.sergeych.lyng.Pos import net.sergeych.lyng.Pos
import net.sergeych.lyng.Scope import net.sergeych.lyng.Scope
import net.sergeych.lyng.ScopeCallable
import net.sergeych.lyng.miniast.addConstDoc import net.sergeych.lyng.miniast.addConstDoc
import net.sergeych.lyng.miniast.addFnDoc import net.sergeych.lyng.miniast.addFnDoc
import net.sergeych.lyng.miniast.type 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 // roundToInt: number rounded to the nearest integer
addConstDoc( addConstDoc(
name = "roundToInt", name = "roundToInt",
value = statement(Pos.builtIn) { value = statement(Pos.builtIn, f = object : ScopeCallable {
(it.thisObj as ObjReal).value.roundToLong().toObj() override suspend fun call(scp: Scope): Obj = (scp.thisObj as ObjReal).value.roundToLong().toObj()
}, }),
doc = "This real number rounded to the nearest integer.", doc = "This real number rounded to the nearest integer.",
type = type("lyng.Int"), type = type("lyng.Int"),
moduleName = "lyng.stdlib" moduleName = "lyng.stdlib"
@ -137,10 +138,11 @@ data class ObjReal(val value: Double) : Obj(), Numeric {
name = "toInt", name = "toInt",
doc = "Truncate this real number toward zero to an integer.", doc = "Truncate this real number toward zero to an integer.",
returns = type("lyng.Int"), returns = type("lyng.Int"),
moduleName = "lyng.stdlib" moduleName = "lyng.stdlib",
) { code = object : ScopeCallable {
ObjInt.of(thisAs<ObjReal>().value.toLong()) override suspend fun call(scp: Scope): Obj = ObjInt.of(scp.thisAs<ObjReal>().value.toLong())
} }
)
} }
} }
} }

View File

@ -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. */ /** Runtime-computed read-only reference backed by a lambda. */
class ValueFnRef(private val fn: suspend (Scope) -> ObjRecord) : ObjRef { class ValueFnRef(private val provider: RecordProvider) : ObjRef {
override suspend fun get(scope: Scope): ObjRecord = fn(scope)
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. */ /** Unary operations supported by ObjRef. */
@ -523,16 +554,16 @@ class FieldRef(
) : ObjRef { ) : ObjRef {
// 4-entry PIC for reads/writes (guarded by PerfFlags.FIELD_PIC) // 4-entry PIC for reads/writes (guarded by PerfFlags.FIELD_PIC)
// Reads // Reads
private var rKey1: Long = 0L; private var rVer1: Int = -1; private var rGetter1: (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: (suspend (Obj, Scope) -> ObjRecord)? = 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: (suspend (Obj, Scope) -> ObjRecord)? = 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: (suspend (Obj, Scope) -> ObjRecord)? = null private var rKey4: Long = 0L; private var rVer4: Int = -1; private var rGetter4: FieldGetter? = null
// Writes // Writes
private var wKey1: Long = 0L; private var wVer1: Int = -1; private var wSetter1: (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: (suspend (Obj, Scope, Obj) -> Unit)? = 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: (suspend (Obj, Scope, Obj) -> Unit)? = 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: (suspend (Obj, Scope, Obj) -> Unit)? = 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 // 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 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) { rGetter1?.let { g -> if (key == rKey1 && ver == rVer1) {
if (picCounters) PerfStats.fieldPicHit++ if (picCounters) PerfStats.fieldPicHit++
noteReadHit() noteReadHit()
val rec0 = g(base, scope) val rec0 = g.call(base, scope)
if (base is ObjClass) { if (base is ObjClass) {
val idx0 = base.classScope?.getSlotIndexOf(name) val idx0 = base.classScope?.getSlotIndexOf(name)
if (idx0 != null) { tKey = key; tVer = ver; tFrameId = scope.frameId; tRecord = rec0 } else { tRecord = null } 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 val tK = rKey2; val tV = rVer2; val tG = rGetter2
rKey2 = rKey1; rVer2 = rVer1; rGetter2 = rGetter1 rKey2 = rKey1; rVer2 = rVer1; rGetter2 = rGetter1
rKey1 = tK; rVer1 = tV; rGetter1 = tG rKey1 = tK; rVer1 = tV; rGetter1 = tG
val rec0 = g(base, scope) val rec0 = g.call(base, scope)
if (base is ObjClass) { if (base is ObjClass) {
val idx0 = base.classScope?.getSlotIndexOf(name) val idx0 = base.classScope?.getSlotIndexOf(name)
if (idx0 != null) { tKey = key; tVer = ver; tFrameId = scope.frameId; tRecord = rec0 } else { tRecord = null } 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 rKey3 = rKey2; rVer3 = rVer2; rGetter3 = rGetter2
rKey2 = rKey1; rVer2 = rVer1; rGetter2 = rGetter1 rKey2 = rKey1; rVer2 = rVer1; rGetter2 = rGetter1
rKey1 = tK; rVer1 = tV; rGetter1 = tG rKey1 = tK; rVer1 = tV; rGetter1 = tG
val rec0 = g(base, scope) val rec0 = g.call(base, scope)
if (base is ObjClass) { if (base is ObjClass) {
val idx0 = base.classScope?.getSlotIndexOf(name) val idx0 = base.classScope?.getSlotIndexOf(name)
if (idx0 != null) { tKey = key; tVer = ver; tFrameId = scope.frameId; tRecord = rec0 } else { tRecord = null } 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 rKey3 = rKey2; rVer3 = rVer2; rGetter3 = rGetter2
rKey2 = rKey1; rVer2 = rVer1; rGetter2 = rGetter1 rKey2 = rKey1; rVer2 = rVer1; rGetter2 = rGetter1
rKey1 = tK; rVer1 = tV; rGetter1 = tG rKey1 = tK; rVer1 = tV; rGetter1 = tG
val rec0 = g(base, scope) val rec0 = g.call(base, scope)
if (base is ObjClass) { if (base is ObjClass) {
val idx0 = base.classScope?.getSlotIndexOf(name) val idx0 = base.classScope?.getSlotIndexOf(name)
if (idx0 != null) { tKey = key; tVer = ver; tFrameId = scope.frameId; tRecord = rec0 } else { tRecord = null } 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 rKey4 = rKey3; rVer4 = rVer3; rGetter4 = rGetter3
rKey3 = rKey2; rVer3 = rVer2; rGetter3 = rGetter2 rKey3 = rKey2; rVer3 = rVer2; rGetter3 = rGetter2
rKey2 = rKey1; rVer2 = rVer1; rGetter2 = rGetter1 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 throw e
} }
// Install move-to-front with a handle-aware getter; honor PIC size flag // Install move-to-front with a handle-aware getter; honor PIC size flag
@ -674,7 +705,7 @@ class FieldRef(
val clsScope = base.classScope val clsScope = base.classScope
val capturedIdx = clsScope?.getSlotIndexOf(name) val capturedIdx = clsScope?.getSlotIndexOf(name)
if (clsScope != null && capturedIdx != null) { 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 scope0 = (obj as ObjClass).classScope!!
val r0 = scope0.getSlotRecord(capturedIdx) val r0 = scope0.getSlotRecord(capturedIdx)
if (!r0.visibility.isPublic) if (!r0.visibility.isPublic)
@ -682,14 +713,14 @@ class FieldRef(
r0 r0
} }
} else { } 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 -> { is ObjInstance -> {
val cls = base.objClass val cls = base.objClass
val effectiveKey = cls.publicMemberResolution[name] val effectiveKey = cls.publicMemberResolution[name]
if (effectiveKey != null) { 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) { if (obj is ObjInstance && obj.objClass === cls) {
val rec = obj.instanceScope.objects[effectiveKey] val rec = obj.instanceScope.objects[effectiveKey]
if (rec != null && rec.type != ObjRecord.Type.Delegated) rec if (rec != null && rec.type != ObjRecord.Type.Delegated) rec
@ -697,12 +728,12 @@ class FieldRef(
} else obj.readField(sc, name) } else obj.readField(sc, name)
} }
} else { } 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 -> { else -> {
// For instances and other types, fall back to name-based lookup per access (slot index may differ per instance) // 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 return rec
@ -745,7 +776,7 @@ class FieldRef(
wSetter1?.let { s -> if (key == wKey1 && ver == wVer1) { wSetter1?.let { s -> if (key == wKey1 && ver == wVer1) {
if (picCounters) PerfStats.fieldPicSetHit++ if (picCounters) PerfStats.fieldPicSetHit++
noteWriteHit() noteWriteHit()
return s(base, scope, newValue) return s.call(base, scope, newValue)
} } } }
wSetter2?.let { s -> if (key == wKey2 && ver == wVer2) { wSetter2?.let { s -> if (key == wKey2 && ver == wVer2) {
if (picCounters) PerfStats.fieldPicSetHit++ if (picCounters) PerfStats.fieldPicSetHit++
@ -754,7 +785,7 @@ class FieldRef(
val tK = wKey2; val tV = wVer2; val tS = wSetter2 val tK = wKey2; val tV = wVer2; val tS = wSetter2
wKey2 = wKey1; wVer2 = wVer1; wSetter2 = wSetter1 wKey2 = wKey1; wVer2 = wVer1; wSetter2 = wSetter1
wKey1 = tK; wVer1 = tV; wSetter1 = tS 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 (size4WritesEnabled()) wSetter3?.let { s -> if (key == wKey3 && ver == wVer3) {
if (picCounters) PerfStats.fieldPicSetHit++ if (picCounters) PerfStats.fieldPicSetHit++
@ -764,7 +795,7 @@ class FieldRef(
wKey3 = wKey2; wVer3 = wVer2; wSetter3 = wSetter2 wKey3 = wKey2; wVer3 = wVer2; wSetter3 = wSetter2
wKey2 = wKey1; wVer2 = wVer1; wSetter2 = wSetter1 wKey2 = wKey1; wVer2 = wVer1; wSetter2 = wSetter1
wKey1 = tK; wVer1 = tV; wSetter1 = tS 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 (size4WritesEnabled()) wSetter4?.let { s -> if (key == wKey4 && ver == wVer4) {
if (picCounters) PerfStats.fieldPicSetHit++ if (picCounters) PerfStats.fieldPicSetHit++
@ -775,7 +806,7 @@ class FieldRef(
wKey3 = wKey2; wVer3 = wVer2; wSetter3 = wSetter2 wKey3 = wKey2; wVer3 = wVer2; wSetter3 = wSetter2
wKey2 = wKey1; wVer2 = wVer1; wSetter2 = wSetter1 wKey2 = wKey1; wVer2 = wVer1; wSetter2 = wSetter1
wKey1 = tK; wVer1 = tV; wSetter1 = tS wKey1 = tK; wVer1 = tV; wSetter1 = tS
return s(base, scope, newValue) return s.call(base, scope, newValue)
} } } }
// Slow path // Slow path
if (picCounters) PerfStats.fieldPicSetMiss++ if (picCounters) PerfStats.fieldPicSetMiss++
@ -792,7 +823,7 @@ class FieldRef(
val clsScope = base.classScope val clsScope = base.classScope
val capturedIdx = clsScope?.getSlotIndexOf(name) val capturedIdx = clsScope?.getSlotIndexOf(name)
if (clsScope != null && capturedIdx != null) { 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 scope0 = (obj as ObjClass).classScope!!
val r0 = scope0.getSlotRecord(capturedIdx) val r0 = scope0.getSlotRecord(capturedIdx)
if (!r0.isMutable) if (!r0.isMutable)
@ -800,14 +831,14 @@ class FieldRef(
if (r0.value.assign(sc, v) == null) r0.value = v if (r0.value.assign(sc, v) == null) r0.value = v
} }
} else { } 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 -> { is ObjInstance -> {
val cls = base.objClass val cls = base.objClass
val effectiveKey = cls.publicMemberResolution[name] val effectiveKey = cls.publicMemberResolution[name]
if (effectiveKey != null) { 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) { if (obj is ObjInstance && obj.objClass === cls) {
val rec = obj.instanceScope.objects[effectiveKey] val rec = obj.instanceScope.objects[effectiveKey]
if (rec != null && rec.effectiveWriteVisibility == Visibility.Public && rec.isMutable && rec.type == ObjRecord.Type.Field) { 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 obj.writeField(sc, name, nv)
} }
} else { } 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 -> { else -> {
// For instances and other types, fall back to generic write (instance slot indices may differ per instance) // 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 return
@ -853,14 +884,14 @@ class FieldRef(
if (key != 0L) { if (key != 0L) {
rGetter1?.let { g -> if (key == rKey1 && ver == rVer1) { rGetter1?.let { g -> if (key == rKey1 && ver == rVer1) {
if (picCounters) PerfStats.fieldPicHit++ if (picCounters) PerfStats.fieldPicHit++
return g(base, scope).value return g.call(base, scope).value
} } } }
rGetter2?.let { g -> if (key == rKey2 && ver == rVer2) { rGetter2?.let { g -> if (key == rKey2 && ver == rVer2) {
if (picCounters) PerfStats.fieldPicHit++ if (picCounters) PerfStats.fieldPicHit++
val tK = rKey2; val tV = rVer2; val tG = rGetter2 val tK = rKey2; val tV = rVer2; val tG = rGetter2
rKey2 = rKey1; rVer2 = rVer1; rGetter2 = rGetter1 rKey2 = rKey1; rVer2 = rVer1; rGetter2 = rGetter1
rKey1 = tK; rVer1 = tV; rGetter1 = tG 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 (size4ReadsEnabled()) rGetter3?.let { g -> if (key == rKey3 && ver == rVer3) {
if (picCounters) PerfStats.fieldPicHit++ if (picCounters) PerfStats.fieldPicHit++
@ -868,7 +899,7 @@ class FieldRef(
rKey3 = rKey2; rVer3 = rVer2; rGetter3 = rGetter2 rKey3 = rKey2; rVer3 = rVer2; rGetter3 = rGetter2
rKey2 = rKey1; rVer2 = rVer1; rGetter2 = rGetter1 rKey2 = rKey1; rVer2 = rVer1; rGetter2 = rGetter1
rKey1 = tK; rVer1 = tV; rGetter1 = tG 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 (size4ReadsEnabled()) rGetter4?.let { g -> if (key == rKey4 && ver == rVer4) {
if (picCounters) PerfStats.fieldPicHit++ if (picCounters) PerfStats.fieldPicHit++
@ -877,12 +908,12 @@ class FieldRef(
rKey3 = rKey2; rVer3 = rVer2; rGetter3 = rGetter2 rKey3 = rKey2; rVer3 = rVer2; rGetter3 = rGetter2
rKey2 = rKey1; rVer2 = rVer1; rGetter2 = rGetter1 rKey2 = rKey1; rVer2 = rVer1; rGetter2 = rGetter1
rKey1 = tK; rVer1 = tV; rGetter1 = tG rKey1 = tK; rVer1 = tV; rGetter1 = tG
return g(base, scope).value return g.call(base, scope).value
} } } }
if (picCounters) PerfStats.fieldPicMiss++ if (picCounters) PerfStats.fieldPicMiss++
val rec = base.readField(scope, name) val rec = base.readField(scope, name)
// install primary generic getter for this shape // 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 return rec.value
} }
} }
@ -899,16 +930,16 @@ class IndexRef(
private val isOptional: Boolean, private val isOptional: Boolean,
) : ObjRef { ) : ObjRef {
// Tiny 4-entry PIC for index reads (guarded implicitly by RVAL_FASTPATH); move-to-front on hits // 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 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: (suspend (Obj, Scope, Obj) -> Obj)? = 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: (suspend (Obj, Scope, Obj) -> Obj)? = 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: (suspend (Obj, Scope, Obj) -> Obj)? = null private var rKey4: Long = 0L; private var rVer4: Int = -1; private var rGetter4: IndexGetter? = null
// Tiny 4-entry PIC for index writes // 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 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: (suspend (Obj, Scope, Obj, Obj) -> Unit)? = 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: (suspend (Obj, Scope, Obj, Obj) -> Unit)? = 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: (suspend (Obj, Scope, Obj, Obj) -> Unit)? = null private var wKey4: Long = 0L; private var wVer4: Int = -1; private var wSetter4: IndexSetter? = null
private fun receiverKeyAndVersion(obj: Obj): Pair<Long, Int> = when (obj) { private fun receiverKeyAndVersion(obj: Obj): Pair<Long, Int> = when (obj) {
is ObjInstance -> obj.objClass.classId to obj.objClass.layoutVersion is ObjInstance -> obj.objClass.classId to obj.objClass.layoutVersion
@ -949,14 +980,14 @@ class IndexRef(
if (key != 0L) { if (key != 0L) {
rGetter1?.let { g -> if (key == rKey1 && ver == rVer1) { rGetter1?.let { g -> if (key == rKey1 && ver == rVer1) {
if (picCounters) PerfStats.indexPicHit++ 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) { rGetter2?.let { g -> if (key == rKey2 && ver == rVer2) {
if (picCounters) PerfStats.indexPicHit++ if (picCounters) PerfStats.indexPicHit++
val tk = rKey2; val tv = rVer2; val tg = rGetter2 val tk = rKey2; val tv = rVer2; val tg = rGetter2
rKey2 = rKey1; rVer2 = rVer1; rGetter2 = rGetter1 rKey2 = rKey1; rVer2 = rVer1; rGetter2 = rGetter1
rKey1 = tk; rVer1 = tv; rGetter1 = tg 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 (PerfFlags.INDEX_PIC_SIZE_4) rGetter3?.let { g -> if (key == rKey3 && ver == rVer3) {
if (picCounters) PerfStats.indexPicHit++ if (picCounters) PerfStats.indexPicHit++
@ -964,7 +995,7 @@ class IndexRef(
rKey3 = rKey2; rVer3 = rVer2; rGetter3 = rGetter2 rKey3 = rKey2; rVer3 = rVer2; rGetter3 = rGetter2
rKey2 = rKey1; rVer2 = rVer1; rGetter2 = rGetter1 rKey2 = rKey1; rVer2 = rVer1; rGetter2 = rGetter1
rKey1 = tk; rVer1 = tv; rGetter1 = tg 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 (PerfFlags.INDEX_PIC_SIZE_4) rGetter4?.let { g -> if (key == rKey4 && ver == rVer4) {
if (picCounters) PerfStats.indexPicHit++ if (picCounters) PerfStats.indexPicHit++
@ -973,7 +1004,7 @@ class IndexRef(
rKey3 = rKey2; rVer3 = rVer2; rGetter3 = rGetter2 rKey3 = rKey2; rVer3 = rVer2; rGetter3 = rGetter2
rKey2 = rKey1; rVer2 = rVer1; rGetter2 = rGetter1 rKey2 = rKey1; rVer2 = rVer1; rGetter2 = rGetter1
rKey1 = tk; rVer1 = tv; rGetter1 = tg 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 // Miss: resolve and install generic handler
if (picCounters) PerfStats.indexPicMiss++ if (picCounters) PerfStats.indexPicMiss++
@ -983,7 +1014,7 @@ class IndexRef(
rKey3 = rKey2; rVer3 = rVer2; rGetter3 = rGetter2 rKey3 = rKey2; rVer3 = rVer2; rGetter3 = rGetter2
} }
rKey2 = rKey1; rVer2 = rVer1; rGetter2 = rGetter1 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 return v.asMutable
} }
} }
@ -1023,14 +1054,14 @@ class IndexRef(
if (key != 0L) { if (key != 0L) {
rGetter1?.let { g -> if (key == rKey1 && ver == rVer1) { rGetter1?.let { g -> if (key == rKey1 && ver == rVer1) {
if (picCounters) PerfStats.indexPicHit++ if (picCounters) PerfStats.indexPicHit++
return g(base, scope, idx) return g.call(base, scope, idx)
} } } }
rGetter2?.let { g -> if (key == rKey2 && ver == rVer2) { rGetter2?.let { g -> if (key == rKey2 && ver == rVer2) {
if (picCounters) PerfStats.indexPicHit++ if (picCounters) PerfStats.indexPicHit++
val tk = rKey2; val tv = rVer2; val tg = rGetter2 val tk = rKey2; val tv = rVer2; val tg = rGetter2
rKey2 = rKey1; rVer2 = rVer1; rGetter2 = rGetter1 rKey2 = rKey1; rVer2 = rVer1; rGetter2 = rGetter1
rKey1 = tk; rVer1 = tv; rGetter1 = tg 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 (PerfFlags.INDEX_PIC_SIZE_4) rGetter3?.let { g -> if (key == rKey3 && ver == rVer3) {
if (picCounters) PerfStats.indexPicHit++ if (picCounters) PerfStats.indexPicHit++
@ -1038,7 +1069,7 @@ class IndexRef(
rKey3 = rKey2; rVer3 = rVer2; rGetter3 = rGetter2 rKey3 = rKey2; rVer3 = rVer2; rGetter3 = rGetter2
rKey2 = rKey1; rVer2 = rVer1; rGetter2 = rGetter1 rKey2 = rKey1; rVer2 = rVer1; rGetter2 = rGetter1
rKey1 = tk; rVer1 = tv; rGetter1 = tg 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 (PerfFlags.INDEX_PIC_SIZE_4) rGetter4?.let { g -> if (key == rKey4 && ver == rVer4) {
if (picCounters) PerfStats.indexPicHit++ if (picCounters) PerfStats.indexPicHit++
@ -1047,7 +1078,7 @@ class IndexRef(
rKey3 = rKey2; rVer3 = rVer2; rGetter3 = rGetter2 rKey3 = rKey2; rVer3 = rVer2; rGetter3 = rGetter2
rKey2 = rKey1; rVer2 = rVer1; rGetter2 = rGetter1 rKey2 = rKey1; rVer2 = rVer1; rGetter2 = rGetter1
rKey1 = tk; rVer1 = tv; rGetter1 = tg rKey1 = tk; rVer1 = tv; rGetter1 = tg
return g(base, scope, idx) return g.call(base, scope, idx)
} } } }
if (picCounters) PerfStats.indexPicMiss++ if (picCounters) PerfStats.indexPicMiss++
val v = base.getAt(scope, idx) val v = base.getAt(scope, idx)
@ -1056,7 +1087,7 @@ class IndexRef(
rKey3 = rKey2; rVer3 = rVer2; rGetter3 = rGetter2 rKey3 = rKey2; rVer3 = rVer2; rGetter3 = rGetter2
} }
rKey2 = rKey1; rVer2 = rVer1; rGetter2 = rGetter1 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 return v
} }
} }
@ -1093,19 +1124,19 @@ class IndexRef(
else -> { key = 0L; ver = -1 } else -> { key = 0L; ver = -1 }
} }
if (key != 0L) { 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) { wSetter2?.let { s -> if (key == wKey2 && ver == wVer2) {
val tk = wKey2; val tv = wVer2; val ts = wSetter2 val tk = wKey2; val tv = wVer2; val ts = wSetter2
wKey2 = wKey1; wVer2 = wVer1; wSetter2 = wSetter1 wKey2 = wKey1; wVer2 = wVer1; wSetter2 = wSetter1
wKey1 = tk; wVer1 = tv; wSetter1 = ts 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) { if (PerfFlags.INDEX_PIC_SIZE_4) wSetter3?.let { s -> if (key == wKey3 && ver == wVer3) {
val tk = wKey3; val tv = wVer3; val ts = wSetter3 val tk = wKey3; val tv = wVer3; val ts = wSetter3
wKey3 = wKey2; wVer3 = wVer2; wSetter3 = wSetter2 wKey3 = wKey2; wVer3 = wVer2; wSetter3 = wSetter2
wKey2 = wKey1; wVer2 = wVer1; wSetter2 = wSetter1 wKey2 = wKey1; wVer2 = wVer1; wSetter2 = wSetter1
wKey1 = tk; wVer1 = tv; wSetter1 = ts 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) { if (PerfFlags.INDEX_PIC_SIZE_4) wSetter4?.let { s -> if (key == wKey4 && ver == wVer4) {
val tk = wKey4; val tv = wVer4; val ts = wSetter4 val tk = wKey4; val tv = wVer4; val ts = wSetter4
@ -1113,7 +1144,7 @@ class IndexRef(
wKey3 = wKey2; wVer3 = wVer2; wSetter3 = wSetter2 wKey3 = wKey2; wVer3 = wVer2; wSetter3 = wSetter2
wKey2 = wKey1; wVer2 = wVer1; wSetter2 = wSetter1 wKey2 = wKey1; wVer2 = wVer1; wSetter2 = wSetter1
wKey1 = tk; wVer1 = tv; wSetter1 = ts 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 // Miss: perform write and install generic handler
base.putAt(scope, idx, newValue) base.putAt(scope, idx, newValue)
@ -1122,7 +1153,7 @@ class IndexRef(
wKey3 = wKey2; wVer3 = wVer2; wSetter3 = wSetter2 wKey3 = wKey2; wVer3 = wVer2; wSetter3 = wSetter2
} }
wKey2 = wKey1; wVer2 = wVer1; wSetter2 = wSetter1 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 return
} }
} }
@ -1173,10 +1204,10 @@ class MethodCallRef(
private val isOptional: Boolean, private val isOptional: Boolean,
) : ObjRef { ) : ObjRef {
// 4-entry PIC for method invocations (guarded by PerfFlags.METHOD_PIC) // 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 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: (suspend (Obj, Scope, Arguments) -> Obj)? = 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: (suspend (Obj, Scope, Arguments) -> Obj)? = 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: (suspend (Obj, Scope, Arguments) -> Obj)? = null private var mKey4: Long = 0L; private var mVer4: Int = -1; private var mInvoker4: MethodInvoker? = null
// Adaptive PIC (2→4) for methods // Adaptive PIC (2→4) for methods
private var mAccesses: Int = 0; private var mMisses: Int = 0; private var mPromotedTo4: Boolean = false 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 (key == mKey1 && ver == mVer1) {
if (picCounters) PerfStats.methodPicHit++ if (picCounters) PerfStats.methodPicHit++
noteMethodHit() noteMethodHit()
return inv(base, scope, callArgs) return inv.call(base, scope, callArgs)
} }
} }
mInvoker2?.let { inv -> mInvoker2?.let { inv ->
@ -1285,7 +1316,7 @@ class MethodCallRef(
val tK = mKey2; val tV = mVer2; val tI = mInvoker2 val tK = mKey2; val tV = mVer2; val tI = mInvoker2
mKey2 = mKey1; mVer2 = mVer1; mInvoker2 = mInvoker1 mKey2 = mKey1; mVer2 = mVer1; mInvoker2 = mInvoker1
mKey1 = tK; mVer1 = tV; mInvoker1 = tI mKey1 = tK; mVer1 = tV; mInvoker1 = tI
return inv(base, scope, callArgs) return inv.call(base, scope, callArgs)
} }
} }
if (size4MethodsEnabled()) mInvoker3?.let { inv -> if (size4MethodsEnabled()) mInvoker3?.let { inv ->
@ -1297,7 +1328,7 @@ class MethodCallRef(
mKey3 = mKey2; mVer3 = mVer2; mInvoker3 = mInvoker2 mKey3 = mKey2; mVer3 = mVer2; mInvoker3 = mInvoker2
mKey2 = mKey1; mVer2 = mVer1; mInvoker2 = mInvoker1 mKey2 = mKey1; mVer2 = mVer1; mInvoker2 = mInvoker1
mKey1 = tK; mVer1 = tV; mInvoker1 = tI mKey1 = tK; mVer1 = tV; mInvoker1 = tI
return inv(base, scope, callArgs) return inv.call(base, scope, callArgs)
} }
} }
if (size4MethodsEnabled()) mInvoker4?.let { inv -> if (size4MethodsEnabled()) mInvoker4?.let { inv ->
@ -1310,7 +1341,7 @@ class MethodCallRef(
mKey3 = mKey2; mVer3 = mVer2; mInvoker3 = mInvoker2 mKey3 = mKey2; mVer3 = mVer2; mInvoker3 = mInvoker2
mKey2 = mKey1; mVer2 = mVer1; mInvoker2 = mInvoker1 mKey2 = mKey1; mVer2 = mVer1; mInvoker2 = mInvoker1
mKey1 = tK; mVer1 = tV; mInvoker1 = tI mKey1 = tK; mVer1 = tV; mInvoker1 = tI
return inv(base, scope, callArgs) return inv.call(base, scope, callArgs)
} }
} }
// Slow path // Slow path
@ -1323,7 +1354,7 @@ class MethodCallRef(
mKey4 = mKey3; mVer4 = mVer3; mInvoker4 = mInvoker3 mKey4 = mKey3; mVer4 = mVer3; mInvoker4 = mInvoker3
mKey3 = mKey2; mVer3 = mVer2; mInvoker3 = mInvoker2 mKey3 = mKey2; mVer3 = mVer2; mInvoker3 = mInvoker2
mKey2 = mKey1; mVer2 = mVer1; mInvoker2 = mInvoker1 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 throw e
} }
// Install move-to-front with a handle-aware invoker; honor PIC size flag // Install move-to-front with a handle-aware invoker; honor PIC size flag
@ -1361,15 +1392,15 @@ class MethodCallRef(
val visibility = hierarchyMember.visibility val visibility = hierarchyMember.visibility
val callable = hierarchyMember.value val callable = hierarchyMember.value
val decl = hierarchyMember.declaringClass ?: base.objClass 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 val inst = obj as ObjInstance
if (!visibility.isPublic && !canAccessMember(visibility, decl, sc.currentClassCtx, name)) if (!visibility.isPublic && !canAccessMember(visibility, decl, sc.currentClassCtx, name))
sc.raiseError(ObjIllegalAccessException(sc, "can't invoke non-public method $name")) sc.raiseError(ObjIllegalAccessException(sc, "can't invokeCallable non-public method $name"))
callable.invoke(inst.instanceScope, inst, a) callable.invokeCallable(inst.instanceScope, inst, a)
} }
} else { } else {
// Fallback to name-based lookup per call (handles extensions and root members) // 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 -> { is ObjClass -> {
@ -1377,13 +1408,13 @@ class MethodCallRef(
val rec = clsScope?.get(name) val rec = clsScope?.get(name)
if (rec != null) { if (rec != null) {
val callable = rec.value 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 { } 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 -> { 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 return result

View File

@ -20,6 +20,7 @@ package net.sergeych.lyng.obj
import net.sergeych.lyng.PerfFlags import net.sergeych.lyng.PerfFlags
import net.sergeych.lyng.RegexCache import net.sergeych.lyng.RegexCache
import net.sergeych.lyng.Scope import net.sergeych.lyng.Scope
import net.sergeych.lyng.ScopeCallable
import net.sergeych.lyng.miniast.* import net.sergeych.lyng.miniast.*
class ObjRegex(val regex: Regex) : Obj() { 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.", doc = "Whether the entire string matches this regular expression.",
params = listOf(ParamDoc("text", type("lyng.String"))), params = listOf(ParamDoc("text", type("lyng.String"))),
returns = type("lyng.Bool"), returns = type("lyng.Bool"),
moduleName = "lyng.stdlib" moduleName = "lyng.stdlib",
) { code = object : ScopeCallable {
ObjBool(args.firstAndOnly().toString().matches(thisAs<ObjRegex>().regex)) override suspend fun call(scp: Scope): Obj =
} ObjBool(scp.args.firstAndOnly().toString().matches(scp.thisAs<ObjRegex>().regex))
}
)
addFnDoc( addFnDoc(
name = "find", name = "find",
doc = "Find the first match in the given string.", doc = "Find the first match in the given string.",
params = listOf(ParamDoc("text", type("lyng.String"))), params = listOf(ParamDoc("text", type("lyng.String"))),
returns = type("lyng.RegexMatch", nullable = true), returns = type("lyng.RegexMatch", nullable = true),
moduleName = "lyng.stdlib" moduleName = "lyng.stdlib",
) { code = object : ScopeCallable {
thisAs<ObjRegex>().find(requireOnlyArg<ObjString>()) override suspend fun call(scp: Scope): Obj =
} scp.thisAs<ObjRegex>().find(scp.requireOnlyArg<ObjString>())
}
)
addFnDoc( addFnDoc(
name = "findAll", name = "findAll",
doc = "Find all matches in the given string.", doc = "Find all matches in the given string.",
params = listOf(ParamDoc("text", type("lyng.String"))), params = listOf(ParamDoc("text", type("lyng.String"))),
returns = TypeGenericDoc(type("lyng.List"), listOf(type("lyng.RegexMatch"))), returns = TypeGenericDoc(type("lyng.List"), listOf(type("lyng.RegexMatch"))),
moduleName = "lyng.stdlib" moduleName = "lyng.stdlib",
) { code = object : ScopeCallable {
val s = requireOnlyArg<ObjString>().value override suspend fun call(scp: Scope): Obj {
ObjList(thisAs<ObjRegex>().regex.findAll(s).map { ObjRegexMatch(it) }.toMutableList()) val s = scp.requireOnlyArg<ObjString>().value
} return ObjList(scp.thisAs<ObjRegex>().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.", doc = "List of captured groups with index 0 as the whole match.",
type = TypeGenericDoc(type("lyng.List"), listOf(type("lyng.String"))), type = TypeGenericDoc(type("lyng.List"), listOf(type("lyng.String"))),
moduleName = "lyng.stdlib", moduleName = "lyng.stdlib",
getter = { thisAs<ObjRegexMatch>().objGroups } getter = object : ScopeCallable {
override suspend fun call(scp: Scope): Obj = scp.thisAs<ObjRegexMatch>().objGroups
}
) )
addPropertyDoc( addPropertyDoc(
name = "value", name = "value",
doc = "The matched substring.", doc = "The matched substring.",
type = type("lyng.String"), type = type("lyng.String"),
moduleName = "lyng.stdlib", moduleName = "lyng.stdlib",
getter = { thisAs<ObjRegexMatch>().objValue } getter = object : ScopeCallable {
override suspend fun call(scp: Scope): Obj = scp.thisAs<ObjRegexMatch>().objValue
}
) )
addPropertyDoc( addPropertyDoc(
name = "range", name = "range",
doc = "Range of the match in the input (end-exclusive).", doc = "Range of the match in the input (end-exclusive).",
type = type("lyng.Range"), type = type("lyng.Range"),
moduleName = "lyng.stdlib", moduleName = "lyng.stdlib",
getter = { thisAs<ObjRegexMatch>().objRange } getter = object : ScopeCallable {
override suspend fun call(scp: Scope): Obj = scp.thisAs<ObjRegexMatch>().objRange
}
) )
} }
} }

View File

@ -18,6 +18,7 @@
package net.sergeych.lyng.obj package net.sergeych.lyng.obj
import net.sergeych.lyng.Scope import net.sergeych.lyng.Scope
import net.sergeych.lyng.ScopeCallable
import net.sergeych.lyng.miniast.* import net.sergeych.lyng.miniast.*
class RingBuffer<T>(val maxSize: Int) : Iterable<T> { class RingBuffer<T>(val maxSize: Int) : Iterable<T> {
@ -96,39 +97,55 @@ class ObjRingBuffer(val capacity: Int) : Obj() {
doc = "Maximum number of elements the buffer can hold.", doc = "Maximum number of elements the buffer can hold.",
type = type("lyng.Int"), type = type("lyng.Int"),
moduleName = "lyng.stdlib", moduleName = "lyng.stdlib",
getter = { thisAs<ObjRingBuffer>().capacity.toObj() } getter = object : ScopeCallable {
override suspend fun call(scp: Scope): Obj = scp.thisAs<ObjRingBuffer>().capacity.toObj()
}
) )
addPropertyDoc( addPropertyDoc(
name = "size", name = "size",
doc = "Current number of elements in the buffer.", doc = "Current number of elements in the buffer.",
type = type("lyng.Int"), type = type("lyng.Int"),
moduleName = "lyng.stdlib", moduleName = "lyng.stdlib",
getter = { thisAs<ObjRingBuffer>().buffer.size.toObj() } getter = object : ScopeCallable {
override suspend fun call(scp: Scope): Obj = scp.thisAs<ObjRingBuffer>().buffer.size.toObj()
}
) )
addFnDoc( addFnDoc(
name = "iterator", name = "iterator",
doc = "Iterator over elements in insertion order (oldest to newest).", doc = "Iterator over elements in insertion order (oldest to newest).",
returns = TypeGenericDoc(type("lyng.Iterator"), listOf(type("lyng.Any"))), returns = TypeGenericDoc(type("lyng.Iterator"), listOf(type("lyng.Any"))),
moduleName = "lyng.stdlib" moduleName = "lyng.stdlib",
) { code = object : ScopeCallable {
val buffer = thisAs<ObjRingBuffer>().buffer override suspend fun call(scp: Scope): Obj {
ObjKotlinObjIterator(buffer.iterator()) val buffer = scp.thisAs<ObjRingBuffer>().buffer
} return ObjKotlinObjIterator(buffer.iterator())
}
}
)
addFnDoc( addFnDoc(
name = "add", name = "add",
doc = "Append an element; if full, the oldest element is dropped.", doc = "Append an element; if full, the oldest element is dropped.",
params = listOf(ParamDoc("value", type("lyng.Any"))), params = listOf(ParamDoc("value", type("lyng.Any"))),
returns = type("lyng.Void"), returns = type("lyng.Void"),
moduleName = "lyng.stdlib" moduleName = "lyng.stdlib",
) { thisAs<ObjRingBuffer>().apply { buffer.add(requireOnlyArg<Obj>()) } } code = object : ScopeCallable {
override suspend fun call(scp: Scope): Obj {
val self = scp.thisAs<ObjRingBuffer>()
self.buffer.add(scp.requireOnlyArg<Obj>())
return ObjVoid
}
}
)
addPropertyDoc( addPropertyDoc(
name = "first", name = "first",
doc = "Return the oldest element in the buffer.", doc = "Return the oldest element in the buffer.",
type = type("lyng.Any"), type = type("lyng.Any"),
moduleName = "lyng.stdlib", moduleName = "lyng.stdlib",
getter = { getter = object : ScopeCallable {
val buffer = (this.thisObj as ObjRingBuffer).buffer override suspend fun call(scp: Scope): Obj {
if (buffer.size == 0) ObjNull else buffer.first() val buffer = (scp.thisObj as ObjRingBuffer).buffer
return if (buffer.size == 0) ObjNull else buffer.iterator().next()
}
} }
) )
} }

View File

@ -18,6 +18,7 @@
package net.sergeych.lyng.obj package net.sergeych.lyng.obj
import net.sergeych.lyng.Scope import net.sergeych.lyng.Scope
import net.sergeych.lyng.ScopeCallable
import net.sergeych.lyng.miniast.ParamDoc import net.sergeych.lyng.miniast.ParamDoc
import net.sergeych.lyng.miniast.TypeGenericDoc import net.sergeych.lyng.miniast.TypeGenericDoc
import net.sergeych.lyng.miniast.addFnDoc import net.sergeych.lyng.miniast.addFnDoc
@ -46,9 +47,9 @@ class ObjSet(val set: MutableSet<Obj> = mutableSetOf()) : Obj() {
return set.contains(other) 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) { for (item in set) {
if (!callback(item)) break if (!callback.call(item)) break
} }
} }
@ -164,56 +165,64 @@ class ObjSet(val set: MutableSet<Obj> = mutableSetOf()) : Obj() {
name = "size", name = "size",
doc = "Number of elements in this set.", doc = "Number of elements in this set.",
returns = type("lyng.Int"), returns = type("lyng.Int"),
moduleName = "lyng.stdlib" moduleName = "lyng.stdlib",
) { code = object : ScopeCallable {
thisAs<ObjSet>().set.size.toObj() override suspend fun call(scp: Scope): Obj = scp.thisAs<ObjSet>().set.size.toObj()
} }
)
addFnDoc( addFnDoc(
name = "intersect", name = "intersect",
doc = "Intersection with another set. Returns a new set.", doc = "Intersection with another set. Returns a new set.",
params = listOf(ParamDoc("other", type("lyng.Set"))), params = listOf(ParamDoc("other", type("lyng.Set"))),
returns = type("lyng.Set"), returns = type("lyng.Set"),
moduleName = "lyng.stdlib" moduleName = "lyng.stdlib",
) { code = object : ScopeCallable {
thisAs<ObjSet>().mul(this, args.firstAndOnly()) override suspend fun call(scp: Scope): Obj = scp.thisAs<ObjSet>().mul(scp, scp.args.firstAndOnly())
} }
)
addFnDoc( addFnDoc(
name = "iterator", name = "iterator",
doc = "Iterator over elements of this set.", doc = "Iterator over elements of this set.",
returns = TypeGenericDoc(type("lyng.Iterator"), listOf(type("lyng.Any"))), returns = TypeGenericDoc(type("lyng.Iterator"), listOf(type("lyng.Any"))),
moduleName = "lyng.stdlib" moduleName = "lyng.stdlib",
) { code = object : ScopeCallable {
thisAs<ObjSet>().set.iterator().toObj() override suspend fun call(scp: Scope): Obj = ObjKotlinIterator(scp.thisAs<ObjSet>().set.iterator())
} }
)
addFnDoc( addFnDoc(
name = "union", name = "union",
doc = "Union with another set or iterable. Returns a new set.", doc = "Union with another set or iterable. Returns a new set.",
params = listOf(ParamDoc("other")), params = listOf(ParamDoc("other")),
returns = type("lyng.Set"), returns = type("lyng.Set"),
moduleName = "lyng.stdlib" moduleName = "lyng.stdlib",
) { code = object : ScopeCallable {
thisAs<ObjSet>().plus(this, args.firstAndOnly()) override suspend fun call(scp: Scope): Obj = scp.thisAs<ObjSet>().plus(scp, scp.args.firstAndOnly())
} }
)
addFnDoc( addFnDoc(
name = "subtract", name = "subtract",
doc = "Subtract another set or iterable from this set. Returns a new set.", doc = "Subtract another set or iterable from this set. Returns a new set.",
params = listOf(ParamDoc("other")), params = listOf(ParamDoc("other")),
returns = type("lyng.Set"), returns = type("lyng.Set"),
moduleName = "lyng.stdlib" moduleName = "lyng.stdlib",
) { code = object : ScopeCallable {
thisAs<ObjSet>().minus(this, args.firstAndOnly()) override suspend fun call(scp: Scope): Obj = scp.thisAs<ObjSet>().minus(scp, scp.args.firstAndOnly())
} }
)
addFnDoc( addFnDoc(
name = "remove", name = "remove",
doc = "Remove one or more elements. Returns true if the set changed.", doc = "Remove one or more elements. Returns true if the set changed.",
returns = type("lyng.Bool"), returns = type("lyng.Bool"),
moduleName = "lyng.stdlib" moduleName = "lyng.stdlib",
) { code = object : ScopeCallable {
val set = thisAs<ObjSet>().set override suspend fun call(scp: Scope): Obj {
val n = set.size val set = scp.thisAs<ObjSet>().set
for( x in args.list ) set -= x val n = set.size
if( n == set.size ) ObjFalse else ObjTrue for( x in scp.args.list ) set -= x
} return if( n == set.size ) ObjFalse else ObjTrue
}
}
)
} }
} }
} }

View File

@ -24,6 +24,7 @@ import kotlinx.serialization.json.JsonPrimitive
import net.sergeych.lyng.PerfFlags import net.sergeych.lyng.PerfFlags
import net.sergeych.lyng.RegexCache import net.sergeych.lyng.RegexCache
import net.sergeych.lyng.Scope import net.sergeych.lyng.Scope
import net.sergeych.lyng.ScopeCallable
import net.sergeych.lyng.miniast.* import net.sergeych.lyng.miniast.*
import net.sergeych.lynon.LynonDecoder import net.sergeych.lynon.LynonDecoder
import net.sergeych.lynon.LynonEncoder import net.sergeych.lynon.LynonEncoder
@ -139,205 +140,274 @@ data class ObjString(val value: String) : Obj() {
name = "iterator", name = "iterator",
doc = "Iterator over characters of this string.", doc = "Iterator over characters of this string.",
returns = TypeGenericDoc(type("lyng.Iterator"), listOf(type("lyng.Char"))), returns = TypeGenericDoc(type("lyng.Iterator"), listOf(type("lyng.Char"))),
moduleName = "lyng.stdlib" moduleName = "lyng.stdlib",
) { ObjKotlinIterator(thisAs<ObjString>().value.iterator()) } code = object : ScopeCallable {
override suspend fun call(scp: Scope): Obj = ObjKotlinIterator(scp.thisAs<ObjString>().value.iterator())
}
)
addFnDoc( addFnDoc(
name = "toInt", name = "toInt",
doc = "Parse this string as an integer or throw if it is not a valid integer.", doc = "Parse this string as an integer or throw if it is not a valid integer.",
returns = type("lyng.Int"), returns = type("lyng.Int"),
moduleName = "lyng.stdlib" moduleName = "lyng.stdlib",
) { code = object : ScopeCallable {
ObjInt.of( override suspend fun call(scp: Scope): Obj {
thisAs<ObjString>().value.toLongOrNull() return ObjInt.of(
?: raiseIllegalArgument("can't convert to int: $thisObj") scp.thisAs<ObjString>().value.toLongOrNull()
) ?: scp.raiseIllegalArgument("can't convert to int: ${scp.thisObj}")
} )
}
}
)
addFnDoc( addFnDoc(
name = "startsWith", name = "startsWith",
doc = "Whether this string starts with the given prefix.", doc = "Whether this string starts with the given prefix.",
params = listOf(ParamDoc("prefix", type("lyng.String"))), params = listOf(ParamDoc("prefix", type("lyng.String"))),
returns = type("lyng.Bool"), returns = type("lyng.Bool"),
moduleName = "lyng.stdlib" moduleName = "lyng.stdlib",
) { code = object : ScopeCallable {
ObjBool(thisAs<ObjString>().value.startsWith(requiredArg<ObjString>(0).value)) override suspend fun call(scp: Scope): Obj {
} return ObjBool(scp.thisAs<ObjString>().value.startsWith(scp.requiredArg<ObjString>(0).value))
}
}
)
addFnDoc( addFnDoc(
name = "endsWith", name = "endsWith",
doc = "Whether this string ends with the given suffix.", doc = "Whether this string ends with the given suffix.",
params = listOf(ParamDoc("suffix", type("lyng.String"))), params = listOf(ParamDoc("suffix", type("lyng.String"))),
returns = type("lyng.Bool"), returns = type("lyng.Bool"),
moduleName = "lyng.stdlib" moduleName = "lyng.stdlib",
) { code = object : ScopeCallable {
ObjBool(thisAs<ObjString>().value.endsWith(requiredArg<ObjString>(0).value)) override suspend fun call(scp: Scope): Obj {
} return ObjBool(scp.thisAs<ObjString>().value.endsWith(scp.requiredArg<ObjString>(0).value))
}
}
)
addPropertyDoc( addPropertyDoc(
name = "length", name = "length",
doc = "Number of UTF-16 code units in this string.", doc = "Number of UTF-16 code units in this string.",
type = type("lyng.Int"), type = type("lyng.Int"),
moduleName = "lyng.stdlib", 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( addFnDoc(
name = "takeLast", name = "takeLast",
doc = "Return a string with the last N characters.", doc = "Return a string with the last N characters.",
params = listOf(ParamDoc("n", type("lyng.Int"))), params = listOf(ParamDoc("n", type("lyng.Int"))),
returns = type("lyng.String"), returns = type("lyng.String"),
moduleName = "lyng.stdlib" moduleName = "lyng.stdlib",
) { code = object : ScopeCallable {
thisAs<ObjString>().value.takeLast( override suspend fun call(scp: Scope): Obj {
requiredArg<ObjInt>(0).toInt() return ObjString(
).let(::ObjString) scp.thisAs<ObjString>().value.takeLast(
} scp.requiredArg<ObjInt>(0).toInt()
)
)
}
}
)
addFnDoc( addFnDoc(
name = "take", name = "take",
doc = "Return a string with the first N characters.", doc = "Return a string with the first N characters.",
params = listOf(ParamDoc("n", type("lyng.Int"))), params = listOf(ParamDoc("n", type("lyng.Int"))),
returns = type("lyng.String"), returns = type("lyng.String"),
moduleName = "lyng.stdlib" moduleName = "lyng.stdlib",
) { code = object : ScopeCallable {
thisAs<ObjString>().value.take( override suspend fun call(scp: Scope): Obj {
requiredArg<ObjInt>(0).toInt() return ObjString(
).let(::ObjString) scp.thisAs<ObjString>().value.take(
} scp.requiredArg<ObjInt>(0).toInt()
)
)
}
}
)
addFnDoc( addFnDoc(
name = "drop", name = "drop",
doc = "Drop the first N characters and return the remainder.", doc = "Drop the first N characters and return the remainder.",
params = listOf(ParamDoc("n", type("lyng.Int"))), params = listOf(ParamDoc("n", type("lyng.Int"))),
returns = type("lyng.String"), returns = type("lyng.String"),
moduleName = "lyng.stdlib" moduleName = "lyng.stdlib",
) { code = object : ScopeCallable {
thisAs<ObjString>().value.drop( override suspend fun call(scp: Scope): Obj {
requiredArg<ObjInt>(0).toInt() return ObjString(
).let(::ObjString) scp.thisAs<ObjString>().value.drop(
} scp.requiredArg<ObjInt>(0).toInt()
)
)
}
}
)
addFnDoc( addFnDoc(
name = "dropLast", name = "dropLast",
doc = "Drop the last N characters and return the remainder.", doc = "Drop the last N characters and return the remainder.",
params = listOf(ParamDoc("n", type("lyng.Int"))), params = listOf(ParamDoc("n", type("lyng.Int"))),
returns = type("lyng.String"), returns = type("lyng.String"),
moduleName = "lyng.stdlib" moduleName = "lyng.stdlib",
) { code = object : ScopeCallable {
thisAs<ObjString>().value.dropLast( override suspend fun call(scp: Scope): Obj {
requiredArg<ObjInt>(0).toInt() return ObjString(
).let(::ObjString) scp.thisAs<ObjString>().value.dropLast(
} scp.requiredArg<ObjInt>(0).toInt()
)
)
}
}
)
addFnDoc( addFnDoc(
name = "lower", name = "lower",
doc = "Lowercase version of this string (default locale).", doc = "Lowercase version of this string (default locale).",
returns = type("lyng.String"), returns = type("lyng.String"),
moduleName = "lyng.stdlib" moduleName = "lyng.stdlib",
) { code = object : ScopeCallable {
thisAs<ObjString>().value.lowercase().let(::ObjString) override suspend fun call(scp: Scope): Obj = ObjString(scp.thisAs<ObjString>().value.lowercase())
} }
)
addFnDoc( addFnDoc(
name = "lowercase", name = "lowercase",
doc = "Lowercase version of this string (default locale).", doc = "Lowercase version of this string (default locale).",
returns = type("lyng.String"), returns = type("lyng.String"),
moduleName = "lyng.stdlib" moduleName = "lyng.stdlib",
) { code = object : ScopeCallable {
thisAs<ObjString>().value.lowercase().let(::ObjString) override suspend fun call(scp: Scope): Obj = ObjString(scp.thisAs<ObjString>().value.lowercase())
} }
)
addFnDoc( addFnDoc(
name = "upper", name = "upper",
doc = "Uppercase version of this string (default locale).", doc = "Uppercase version of this string (default locale).",
returns = type("lyng.String"), returns = type("lyng.String"),
moduleName = "lyng.stdlib" moduleName = "lyng.stdlib",
) { code = object : ScopeCallable {
thisAs<ObjString>().value.uppercase().let(::ObjString) override suspend fun call(scp: Scope): Obj = ObjString(scp.thisAs<ObjString>().value.uppercase())
} }
)
addFnDoc( addFnDoc(
name = "uppercase", name = "uppercase",
doc = "Uppercase version of this string (default locale).", doc = "Uppercase version of this string (default locale).",
returns = type("lyng.String"), returns = type("lyng.String"),
moduleName = "lyng.stdlib" moduleName = "lyng.stdlib",
) { code = object : ScopeCallable {
thisAs<ObjString>().value.uppercase().let(::ObjString) override suspend fun call(scp: Scope): Obj = ObjString(scp.thisAs<ObjString>().value.uppercase())
} }
)
addPropertyDoc( addPropertyDoc(
name = "characters", name = "characters",
doc = "List of characters of this string.", doc = "List of characters of this string.",
type = TypeGenericDoc(type("lyng.List"), listOf(type("lyng.Char"))), type = TypeGenericDoc(type("lyng.List"), listOf(type("lyng.Char"))),
moduleName = "lyng.stdlib", moduleName = "lyng.stdlib",
getter = { getter = object : ScopeCallable {
ObjList( override suspend fun call(scp: Scope): Obj {
(this.thisObj as ObjString).value.map { ObjChar(it) }.toMutableList() return ObjList(
) (scp.thisObj as ObjString).value.map { ObjChar(it) }.toMutableList()
)
}
} }
) )
addFnDoc( addFnDoc(
name = "last", name = "last",
doc = "The last character of this string or throw if the string is empty.", doc = "The last character of this string or throw if the string is empty.",
returns = type("lyng.Char"), returns = type("lyng.Char"),
moduleName = "lyng.stdlib" moduleName = "lyng.stdlib",
) { code = object : ScopeCallable {
ObjChar(thisAs<ObjString>().value.lastOrNull() ?: raiseNoSuchElement("empty string")) override suspend fun call(scp: Scope): Obj {
} return ObjChar(scp.thisAs<ObjString>().value.lastOrNull() ?: scp.raiseNoSuchElement("empty string"))
}
}
)
addFnDoc( addFnDoc(
name = "encodeUtf8", name = "encodeUtf8",
doc = "Encode this string as UTF-8 bytes.", doc = "Encode this string as UTF-8 bytes.",
returns = type("lyng.Buffer"), returns = type("lyng.Buffer"),
moduleName = "lyng.stdlib" moduleName = "lyng.stdlib",
) { ObjBuffer(thisAs<ObjString>().value.encodeToByteArray().asUByteArray()) } code = object : ScopeCallable {
override suspend fun call(scp: Scope): Obj =
ObjBuffer(scp.thisAs<ObjString>().value.encodeToByteArray().asUByteArray())
}
)
addPropertyDoc( addPropertyDoc(
name = "size", name = "size",
doc = "Alias for length: the number of characters (code units) in this string.", doc = "Alias for length: the number of characters (code units) in this string.",
type = type("lyng.Int"), type = type("lyng.Int"),
moduleName = "lyng.stdlib", 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( addFnDoc(
name = "toReal", name = "toReal",
doc = "Parse this string as a real number (floating point).", doc = "Parse this string as a real number (floating point).",
returns = type("lyng.Real"), returns = type("lyng.Real"),
moduleName = "lyng.stdlib" moduleName = "lyng.stdlib",
) { code = object : ScopeCallable {
ObjReal.of(thisAs<ObjString>().value.toDouble()) override suspend fun call(scp: Scope): Obj = ObjReal.of(scp.thisAs<ObjString>().value.toDouble())
} }
)
addFnDoc( addFnDoc(
name = "trim", name = "trim",
doc = "Return a copy of this string with leading and trailing whitespace removed.", doc = "Return a copy of this string with leading and trailing whitespace removed.",
returns = type("lyng.String"), returns = type("lyng.String"),
moduleName = "lyng.stdlib" moduleName = "lyng.stdlib",
) { code = object : ScopeCallable {
thisAs<ObjString>().value.trim().let(::ObjString) override suspend fun call(scp: Scope): Obj = ObjString(scp.thisAs<ObjString>().value.trim())
} }
addFnDoc("isBlank", "Whether this string is empty or contains only whitespace characters.", )
returns = type("lyng.Bool"), moduleName = "lyng.stdlib") { addFnDoc(
ObjBool(thisAs<ObjString>().value.isBlank()) name = "isBlank",
} doc = "Whether this string is empty or contains only whitespace characters.",
addFnDoc("isEmpty", "Whether this string is empty.", returns = type("lyng.Bool"),
returns = type("lyng.Bool"), moduleName = "lyng.stdlib") { moduleName = "lyng.stdlib",
ObjBool(thisAs<ObjString>().value.isEmpty()) code = object : ScopeCallable {
} override suspend fun call(scp: Scope): Obj = ObjBool(scp.thisAs<ObjString>().value.isBlank())
addFnDoc("isNotEmpty", "Whether this string is not empty.", }
returns = type("lyng.Bool"), moduleName = "lyng.stdlib") { )
ObjBool(thisAs<ObjString>().value.isNotEmpty()) 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<ObjString>().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<ObjString>().value.isNotEmpty())
}
)
addFnDoc( addFnDoc(
name = "matches", name = "matches",
doc = "Whether this string matches the given regular expression or pattern string.", doc = "Whether this string matches the given regular expression or pattern string.",
params = listOf(ParamDoc("pattern")), params = listOf(ParamDoc("pattern")),
returns = type("lyng.Bool"), returns = type("lyng.Bool"),
moduleName = "lyng.stdlib" moduleName = "lyng.stdlib",
) { code = object : ScopeCallable {
val s = requireOnlyArg<Obj>() override suspend fun call(scp: Scope): Obj {
val self = thisAs<ObjString>().value val s = scp.requireOnlyArg<Obj>()
ObjBool( val self = scp.thisAs<ObjString>().value
when (s) { return ObjBool(
is ObjRegex -> self.matches(s.regex) when (s) {
is ObjString -> { is ObjRegex -> self.matches(s.regex)
if (s.value == ".*") true is ObjString -> {
else { if (s.value == ".*") true
val re = if (PerfFlags.REGEX_CACHE) RegexCache.get(s.value) else s.value.toRegex() else {
self.matches(re) val re = if (PerfFlags.REGEX_CACHE) RegexCache.get(s.value) else s.value.toRegex()
} self.matches(re)
} }
}
else -> else ->
raiseIllegalArgument("can't match ${s.objClass.className}: required Regex or String") scp.raiseIllegalArgument("can't match ${s.objClass.className}: required Regex or String")
}
)
} }
) }
} )
} }
} }
} }

View File

@ -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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with 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.ProtectedOp
import net.sergeych.synctools.withLock 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 * Import manager allow to register packages with builder lambdas and act as an
* [ImportProvider]. Note that packages _must be registered_ first with [addPackage], * [ImportProvider]. Note that packages _must be registered_ first with [addPackage],
@ -40,16 +44,16 @@ class ImportManager(
private inner class Entry( private inner class Entry(
val packageName: String, val packageName: String,
val builder: suspend (ModuleScope) -> Unit, val builder: ModuleBuilder,
var cachedScope: ModuleScope? = null var cachedScope: ModuleScope? = null
) { ) {
suspend fun getScope(pos: Pos): ModuleScope { suspend fun getScope(pos: Pos): ModuleScope {
cachedScope?.let { return it } cachedScope?.let { return it }
return ModuleScope(inner, pos, packageName).apply { val ms = ModuleScope(inner, pos, packageName)
cachedScope = this cachedScope = ms
builder(this) builder.build(ms)
} return ms
} }
} }
@ -90,7 +94,7 @@ class ImportManager(
* @param name package name * @param name package name
* @param builder lambda to create actual package using the given [ModuleScope] * @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 { op.withLock {
if (name in imports) if (name in imports)
throw IllegalArgumentException("Package $name already exists") throw IllegalArgumentException("Package $name already exists")
@ -102,7 +106,7 @@ class ImportManager(
* Bulk [addPackage] with slightly better performance * Bulk [addPackage] with slightly better performance
*/ */
@Suppress("unused") @Suppress("unused")
fun addPackages(registrationData: List<Pair<String, suspend (ModuleScope) -> Unit>>) { fun addPackages(registrationData: List<Pair<String, ModuleBuilder>>) {
op.withLock { op.withLock {
for (pp in registrationData) { for (pp in registrationData) {
if (pp.first in imports) if (pp.first in imports)
@ -129,9 +133,11 @@ class ImportManager(
*/ */
fun addSourcePackages(vararg sources: Source) { fun addSourcePackages(vararg sources: Source) {
for (s in sources) { for (s in sources) {
addPackage(s.extractPackageName()) { addPackage(s.extractPackageName(), object : ModuleBuilder {
it.eval(s) override suspend fun build(scope: ModuleScope) {
} scope.eval(s)
}
})
} }
} }
@ -144,7 +150,11 @@ class ImportManager(
var source = Source("tmp", s) var source = Source("tmp", s)
val packageName = source.extractPackageName() val packageName = source.extractPackageName()
source = Source(packageName, s) source = Source(packageName, s)
addPackage(packageName) { it.eval(source) } addPackage(packageName, object : ModuleBuilder {
override suspend fun build(scope: ModuleScope) {
scope.eval(source)
}
})
} }
} }

View File

@ -72,16 +72,16 @@ fun Statement.require(cond: Boolean, message: () -> String) {
if (!cond) raise(message()) 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) { object : Statement(isStaticConst, isConst) {
override val pos: Pos = pos 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) { object : Statement(isStaticConst, isConst) {
override val pos: Pos = Pos.builtIn 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) { object NopStatement: Statement(true, true, ObjType.Void) {

View File

@ -41,11 +41,11 @@ object ObjLynonClass : ObjClass("Lynon") {
init { init {
addClassConst("test", ObjString("test_const")) addClassConst("test", ObjString("test_const"))
addClassFn("encode") { addClassFn("encode") { scp ->
encodeAny(this, requireOnlyArg<Obj>()) encodeAny(scp, scp.requireOnlyArg<Obj>())
} }
addClassFn("decode") { addClassFn("decode") { scp ->
decodeAny(this, requireOnlyArg<Obj>()) decodeAny(scp, scp.requireOnlyArg<Obj>())
} }
} }
} }

View File

@ -171,7 +171,7 @@ class BindingHighlightTest {
// Find the specific usage inside string-literal invocation: "%s is directory"(name) // Find the specific usage inside string-literal invocation: "%s is directory"(name)
val pattern = "\"%s is directory\"(name)" val pattern = "\"%s is directory\"(name)"
val lineIdx = text.indexOf(pattern) 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 nameStart = lineIdx + pattern.indexOf("name")
val nameEnd = nameStart + "name".length val nameEnd = nameStart + "name".length

View File

@ -865,8 +865,8 @@ class OOTest {
callBar callBar
""".trimIndent()) as Statement """.trimIndent()) as Statement
val s2 = Script.newScope() val s2 = Script.newScope()
assertEquals(44L, fn.invoke(scope, fn).toKotlin(s2)) assertEquals(44L, fn.invokeCallable(scope, fn).toKotlin(s2))
assertEquals(45L, fn.invoke(s2, fn).toKotlin(s2)) assertEquals(45L, fn.invokeCallable(s2, fn).toKotlin(s2))
} }
@Test @Test

View File

@ -138,10 +138,10 @@ class ScriptTest {
companion object { companion object {
val type = ObjClass("TestIterable", ObjIterable).apply { val type = ObjClass("TestIterable", ObjIterable).apply {
addFn("iterator") { addFn("iterator") { scp ->
ObjTestIterator(thisAs<ObjTestIterable>()) ObjTestIterator(scp.thisAs<ObjTestIterable>())
} }
addFn("cancelCount") { thisAs<ObjTestIterable>().cancelCount.toObj() } addFn("cancelCount") { scp -> scp.thisAs<ObjTestIterable>().cancelCount.toObj() }
} }
} }
} }
@ -158,10 +158,10 @@ class ScriptTest {
companion object { companion object {
val type = ObjClass("TestIterator", ObjIterator).apply { val type = ObjClass("TestIterator", ObjIterator).apply {
addFn("hasNext") { thisAs<ObjTestIterator>().hasNext().toObj() } addFn("hasNext") { scp -> scp.thisAs<ObjTestIterator>().hasNext().toObj() }
addFn("next") { thisAs<ObjTestIterator>().next() } addFn("next") { scp -> scp.thisAs<ObjTestIterator>().next() }
addFn("cancelIteration") { addFn("cancelIteration") { scp ->
thisAs<ObjTestIterator>().cancelIteration() scp.thisAs<ObjTestIterator>().cancelIteration()
ObjVoid ObjVoid
} }
} }
@ -2229,8 +2229,8 @@ class ScriptTest {
@Test @Test
fun testThrowFromKotlin() = runTest { fun testThrowFromKotlin() = runTest {
val c = Script.newScope() val c = Script.newScope()
c.addFn("callThrow") { c.addFn("callThrow") { scp ->
raiseIllegalArgument("fromKotlin") scp.raiseIllegalArgument("fromKotlin")
} }
c.eval( c.eval(
""" """
@ -2738,8 +2738,8 @@ class ScriptTest {
companion object { companion object {
val klass = ObjClass("TestFoo").apply { val klass = ObjClass("TestFoo").apply {
addFn("test") { addFn("test") { scp ->
thisAs<ObjTestFoo>().value scp.thisAs<ObjTestFoo>().value
} }
} }
} }
@ -4480,7 +4480,7 @@ class ScriptTest {
val dummyThis = Obj() val dummyThis = Obj()
// but we should be able to call it directly // but we should be able to call it directly
val otherScope = baseScope.createChildScope() 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) println(r)
assertEquals(51, r.toInt()) assertEquals(51, r.toInt())
} }

View File

@ -90,7 +90,7 @@ class DelegationTest {
fun testFunDelegation() = runTest { fun testFunDelegation() = runTest {
eval(""" eval("""
class ActionDelegate() { class ActionDelegate() {
fun invoke(thisRef, name, args...) { fun invokeCallable(thisRef, name, args...) {
"Called %s with %d args: %s"(name, args.size, args.joinToString(",")) "Called %s with %d args: %s"(name, args.size, args.joinToString(","))
} }
} }

View File

@ -18,15 +18,15 @@
package net.sergeych.lyng package net.sergeych.lyng
actual object PerfDefaults { actual object PerfDefaults {
actual val LOCAL_SLOT_PIC: Boolean = true actual val LOCAL_SLOT_PIC: Boolean = false
actual val EMIT_FAST_LOCAL_REFS: Boolean = true actual val EMIT_FAST_LOCAL_REFS: Boolean = false
actual val ARG_BUILDER: Boolean = true actual val ARG_BUILDER: Boolean = false
actual val SKIP_ARGS_ON_NULL_RECEIVER: Boolean = true actual val SKIP_ARGS_ON_NULL_RECEIVER: Boolean = false
actual val SCOPE_POOL: Boolean = true actual val SCOPE_POOL: Boolean = false
actual val FIELD_PIC: Boolean = true actual val FIELD_PIC: Boolean = false
actual val METHOD_PIC: Boolean = true actual val METHOD_PIC: Boolean = false
actual val FIELD_PIC_SIZE_4: Boolean = false actual val FIELD_PIC_SIZE_4: Boolean = false
actual val METHOD_PIC_SIZE_4: Boolean = false actual val METHOD_PIC_SIZE_4: Boolean = false
actual val PIC_ADAPTIVE_2_TO_4: Boolean = false actual val PIC_ADAPTIVE_2_TO_4: Boolean = false