Compare commits
1 Commits
master
...
heroic_att
| Author | SHA1 | Date | |
|---|---|---|---|
| f9ae00b7f4 |
@ -1,5 +1,5 @@
|
||||
#
|
||||
# Copyright 2025 Sergey S. Chernov real.sergeych@gmail.com
|
||||
# Copyright 2026 Sergey S. Chernov real.sergeych@gmail.com
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
@ -16,7 +16,7 @@
|
||||
#
|
||||
|
||||
#Gradle
|
||||
org.gradle.jvmargs=-Xmx2048M -Dfile.encoding=UTF-8 -Dkotlin.daemon.jvm.options\="-Xmx2048M"
|
||||
org.gradle.jvmargs=-Xmx4096M -Dfile.encoding=UTF-8 -Dkotlin.daemon.jvm.options\="-Xmx2048M"
|
||||
org.gradle.caching=true
|
||||
org.gradle.configuration-cache=true
|
||||
#Kotlin
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2025 Sergey S. Chernov real.sergeych@gmail.com
|
||||
* Copyright 2026 Sergey S. Chernov real.sergeych@gmail.com
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@ -23,9 +23,11 @@ package net.sergeych.lyng.io.fs
|
||||
|
||||
import net.sergeych.lyng.ModuleScope
|
||||
import net.sergeych.lyng.Scope
|
||||
import net.sergeych.lyng.ScopeCallable
|
||||
import net.sergeych.lyng.miniast.*
|
||||
import net.sergeych.lyng.obj.*
|
||||
import net.sergeych.lyng.pacman.ImportManager
|
||||
import net.sergeych.lyng.pacman.ModuleBuilder
|
||||
import net.sergeych.lyngio.fs.LyngFS
|
||||
import net.sergeych.lyngio.fs.LyngFs
|
||||
import net.sergeych.lyngio.fs.LyngPath
|
||||
@ -50,9 +52,11 @@ fun createFsModule(policy: FsAccessPolicy, manager: ImportManager): Boolean {
|
||||
// Avoid re-registering in this ImportManager
|
||||
if (manager.packageNames.contains(name)) return false
|
||||
|
||||
manager.addPackage(name) { module ->
|
||||
buildFsModule(module, policy)
|
||||
}
|
||||
manager.addPackage(name, object : ModuleBuilder {
|
||||
override suspend fun build(module: ModuleScope) {
|
||||
buildFsModule(module, policy)
|
||||
}
|
||||
})
|
||||
return true
|
||||
}
|
||||
|
||||
@ -78,322 +82,394 @@ private suspend fun buildFsModule(module: ModuleScope, policy: FsAccessPolicy) {
|
||||
name = "name",
|
||||
doc = "Base name of the path (last segment).",
|
||||
returns = type("lyng.String"),
|
||||
moduleName = module.packageName
|
||||
) {
|
||||
val self = thisAs<ObjPath>()
|
||||
self.path.name.toObj()
|
||||
}
|
||||
moduleName = module.packageName,
|
||||
code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj {
|
||||
val self = scp.thisAs<ObjPath>()
|
||||
return self.path.name.toObj()
|
||||
}
|
||||
}
|
||||
)
|
||||
addFnDoc(
|
||||
name = "parent",
|
||||
doc = "Parent directory as a Path or null if none.",
|
||||
returns = type("Path", nullable = true),
|
||||
moduleName = module.packageName
|
||||
) {
|
||||
val self = thisAs<ObjPath>()
|
||||
self.path.parent?.let {
|
||||
ObjPath( this@apply, self.secured, it)
|
||||
} ?: ObjNull
|
||||
}
|
||||
moduleName = module.packageName,
|
||||
code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj {
|
||||
val self = scp.thisAs<ObjPath>()
|
||||
return self.path.parent?.let {
|
||||
ObjPath(this@apply, self.secured, it)
|
||||
} ?: ObjNull
|
||||
}
|
||||
}
|
||||
)
|
||||
addFnDoc(
|
||||
name = "segments",
|
||||
doc = "List of path segments.",
|
||||
// returns: List<String>
|
||||
returns = TypeGenericDoc(type("lyng.List"), listOf(type("lyng.String"))),
|
||||
moduleName = module.packageName
|
||||
) {
|
||||
val self = thisAs<ObjPath>()
|
||||
ObjList(self.path.segments.map { ObjString(it) }.toMutableList())
|
||||
}
|
||||
moduleName = module.packageName,
|
||||
code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj {
|
||||
val self = scp.thisAs<ObjPath>()
|
||||
return ObjList(self.path.segments.map { ObjString(it) }.toMutableList())
|
||||
}
|
||||
}
|
||||
)
|
||||
// exists(): Bool
|
||||
addFnDoc(
|
||||
name = "exists",
|
||||
doc = "Check whether this path exists.",
|
||||
returns = type("lyng.Bool"),
|
||||
moduleName = module.packageName
|
||||
) {
|
||||
fsGuard {
|
||||
val self = this.thisObj as ObjPath
|
||||
(self.secured.exists(self.path)).toObj()
|
||||
moduleName = module.packageName,
|
||||
code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj {
|
||||
return scp.fsGuard {
|
||||
val self = scp.thisObj as ObjPath
|
||||
(self.secured.exists(self.path)).toObj()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
// isFile(): Bool — cached metadata
|
||||
addFnDoc(
|
||||
name = "isFile",
|
||||
doc = "True if this path is a regular file (based on cached metadata).",
|
||||
returns = type("lyng.Bool"),
|
||||
moduleName = module.packageName
|
||||
) {
|
||||
fsGuard {
|
||||
val self = this.thisObj as ObjPath
|
||||
self.ensureMetadata().let { ObjBool(it.isRegularFile) }
|
||||
moduleName = module.packageName,
|
||||
code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj {
|
||||
return scp.fsGuard {
|
||||
val self = scp.thisObj as ObjPath
|
||||
self.ensureMetadata().let { ObjBool(it.isRegularFile) }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
// isDirectory(): Bool — cached metadata
|
||||
addFnDoc(
|
||||
name = "isDirectory",
|
||||
doc = "True if this path is a directory (based on cached metadata).",
|
||||
returns = type("lyng.Bool"),
|
||||
moduleName = module.packageName
|
||||
) {
|
||||
fsGuard {
|
||||
val self = this.thisObj as ObjPath
|
||||
self.ensureMetadata().let { ObjBool(it.isDirectory) }
|
||||
moduleName = module.packageName,
|
||||
code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj {
|
||||
return scp.fsGuard {
|
||||
val self = scp.thisObj as ObjPath
|
||||
self.ensureMetadata().let { ObjBool(it.isDirectory) }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
// size(): Int? — null when unavailable
|
||||
addFnDoc(
|
||||
name = "size",
|
||||
doc = "File size in bytes, or null when unavailable.",
|
||||
returns = type("lyng.Int", nullable = true),
|
||||
moduleName = module.packageName
|
||||
) {
|
||||
fsGuard {
|
||||
val self = this.thisObj as ObjPath
|
||||
val m = self.ensureMetadata()
|
||||
m.size?.let { ObjInt(it) } ?: ObjNull
|
||||
moduleName = module.packageName,
|
||||
code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj {
|
||||
return scp.fsGuard {
|
||||
val self = scp.thisObj as ObjPath
|
||||
val m = self.ensureMetadata()
|
||||
m.size?.let { ObjInt(it) } ?: ObjNull
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
// createdAt(): Instant? — Lyng Instant, null when unavailable
|
||||
addFnDoc(
|
||||
name = "createdAt",
|
||||
doc = "Creation time as `Instant`, or null when unavailable.",
|
||||
returns = type("lyng.Instant", nullable = true),
|
||||
moduleName = module.packageName
|
||||
) {
|
||||
fsGuard {
|
||||
val self = this.thisObj as ObjPath
|
||||
val m = self.ensureMetadata()
|
||||
m.createdAtMillis?.let { ObjInstant(kotlin.time.Instant.fromEpochMilliseconds(it)) } ?: ObjNull
|
||||
moduleName = module.packageName,
|
||||
code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj {
|
||||
return scp.fsGuard {
|
||||
val self = scp.thisObj as ObjPath
|
||||
val m = self.ensureMetadata()
|
||||
m.createdAtMillis?.let { ObjInstant(kotlin.time.Instant.fromEpochMilliseconds(it)) } ?: ObjNull
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
// createdAtMillis(): Int? — milliseconds since epoch or null
|
||||
addFnDoc(
|
||||
name = "createdAtMillis",
|
||||
doc = "Creation time in milliseconds since epoch, or null when unavailable.",
|
||||
returns = type("lyng.Int", nullable = true),
|
||||
moduleName = module.packageName
|
||||
) {
|
||||
fsGuard {
|
||||
val self = this.thisObj as ObjPath
|
||||
val m = self.ensureMetadata()
|
||||
m.createdAtMillis?.let { ObjInt(it) } ?: ObjNull
|
||||
moduleName = module.packageName,
|
||||
code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj {
|
||||
return scp.fsGuard {
|
||||
val self = scp.thisObj as ObjPath
|
||||
val m = self.ensureMetadata()
|
||||
m.createdAtMillis?.let { ObjInt(it) } ?: ObjNull
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
// modifiedAt(): Instant? — Lyng Instant, null when unavailable
|
||||
addFnDoc(
|
||||
name = "modifiedAt",
|
||||
doc = "Last modification time as `Instant`, or null when unavailable.",
|
||||
returns = type("lyng.Instant", nullable = true),
|
||||
moduleName = module.packageName
|
||||
) {
|
||||
fsGuard {
|
||||
val self = this.thisObj as ObjPath
|
||||
val m = self.ensureMetadata()
|
||||
m.modifiedAtMillis?.let { ObjInstant(kotlinx.datetime.Instant.fromEpochMilliseconds(it)) } ?: ObjNull
|
||||
moduleName = module.packageName,
|
||||
code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj {
|
||||
return scp.fsGuard {
|
||||
val self = scp.thisObj as ObjPath
|
||||
val m = self.ensureMetadata()
|
||||
m.modifiedAtMillis?.let { ObjInstant(kotlinx.datetime.Instant.fromEpochMilliseconds(it)) } ?: ObjNull
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
// modifiedAtMillis(): Int? — milliseconds since epoch or null
|
||||
addFnDoc(
|
||||
name = "modifiedAtMillis",
|
||||
doc = "Last modification time in milliseconds since epoch, or null when unavailable.",
|
||||
returns = type("lyng.Int", nullable = true),
|
||||
moduleName = module.packageName
|
||||
) {
|
||||
fsGuard {
|
||||
val self = this.thisObj as ObjPath
|
||||
val m = self.ensureMetadata()
|
||||
m.modifiedAtMillis?.let { ObjInt(it) } ?: ObjNull
|
||||
moduleName = module.packageName,
|
||||
code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj {
|
||||
return scp.fsGuard {
|
||||
val self = scp.thisObj as ObjPath
|
||||
val m = self.ensureMetadata()
|
||||
m.modifiedAtMillis?.let { ObjInt(it) } ?: ObjNull
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
// list(): List<Path>
|
||||
addFnDoc(
|
||||
name = "list",
|
||||
doc = "List directory entries as `Path` objects.",
|
||||
returns = TypeGenericDoc(type("lyng.List"), listOf(type("Path"))),
|
||||
moduleName = module.packageName
|
||||
) {
|
||||
fsGuard {
|
||||
val self = this.thisObj as ObjPath
|
||||
val items = self.secured.list(self.path).map { ObjPath(self.objClass, self.secured, it) }
|
||||
ObjList(items.toMutableList())
|
||||
moduleName = module.packageName,
|
||||
code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj {
|
||||
return scp.fsGuard {
|
||||
val self = scp.thisObj as ObjPath
|
||||
val items = self.secured.list(self.path).map { ObjPath(self.objClass, self.secured, it) }
|
||||
ObjList(items.toMutableList())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
// readBytes(): Buffer
|
||||
addFnDoc(
|
||||
name = "readBytes",
|
||||
doc = "Read the file into a binary buffer.",
|
||||
returns = type("lyng.Buffer"),
|
||||
moduleName = module.packageName
|
||||
) {
|
||||
fsGuard {
|
||||
val self = this.thisObj as ObjPath
|
||||
val bytes = self.secured.readBytes(self.path)
|
||||
ObjBuffer(bytes.asUByteArray())
|
||||
moduleName = module.packageName,
|
||||
code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj {
|
||||
return scp.fsGuard {
|
||||
val self = scp.thisObj as ObjPath
|
||||
val bytes = self.secured.readBytes(self.path)
|
||||
ObjBuffer(bytes.asUByteArray())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
// writeBytes(bytes: Buffer)
|
||||
addFnDoc(
|
||||
name = "writeBytes",
|
||||
doc = "Write a binary buffer to the file, replacing content.",
|
||||
params = listOf(ParamDoc("bytes", type("lyng.Buffer"))),
|
||||
moduleName = module.packageName
|
||||
) {
|
||||
fsGuard {
|
||||
val self = this.thisObj as ObjPath
|
||||
val buf = requiredArg<ObjBuffer>(0)
|
||||
self.secured.writeBytes(self.path, buf.byteArray.asByteArray(), append = false)
|
||||
ObjVoid
|
||||
moduleName = module.packageName,
|
||||
code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj {
|
||||
return scp.fsGuard {
|
||||
val self = scp.thisObj as ObjPath
|
||||
val buf = scp.requiredArg<ObjBuffer>(0)
|
||||
self.secured.writeBytes(self.path, buf.byteArray.asByteArray(), append = false)
|
||||
ObjVoid
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
// appendBytes(bytes: Buffer)
|
||||
addFnDoc(
|
||||
name = "appendBytes",
|
||||
doc = "Append a binary buffer to the end of the file.",
|
||||
params = listOf(ParamDoc("bytes", type("lyng.Buffer"))),
|
||||
moduleName = module.packageName
|
||||
) {
|
||||
fsGuard {
|
||||
val self = this.thisObj as ObjPath
|
||||
val buf = requiredArg<ObjBuffer>(0)
|
||||
self.secured.writeBytes(self.path, buf.byteArray.asByteArray(), append = true)
|
||||
ObjVoid
|
||||
moduleName = module.packageName,
|
||||
code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj {
|
||||
return scp.fsGuard {
|
||||
val self = scp.thisObj as ObjPath
|
||||
val buf = scp.requiredArg<ObjBuffer>(0)
|
||||
self.secured.writeBytes(self.path, buf.byteArray.asByteArray(), append = true)
|
||||
ObjVoid
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
// readUtf8(): String
|
||||
addFnDoc(
|
||||
name = "readUtf8",
|
||||
doc = "Read the file as a UTF-8 string.",
|
||||
returns = type("lyng.String"),
|
||||
moduleName = module.packageName
|
||||
) {
|
||||
fsGuard {
|
||||
val self = this.thisObj as ObjPath
|
||||
self.secured.readUtf8(self.path).toObj()
|
||||
moduleName = module.packageName,
|
||||
code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj {
|
||||
return scp.fsGuard {
|
||||
val self = scp.thisObj as ObjPath
|
||||
self.secured.readUtf8(self.path).toObj()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
// writeUtf8(text: String)
|
||||
addFnDoc(
|
||||
name = "writeUtf8",
|
||||
doc = "Write a UTF-8 string to the file, replacing content.",
|
||||
params = listOf(ParamDoc("text", type("lyng.String"))),
|
||||
moduleName = module.packageName
|
||||
) {
|
||||
fsGuard {
|
||||
val self = this.thisObj as ObjPath
|
||||
val text = requireOnlyArg<ObjString>().value
|
||||
self.secured.writeUtf8(self.path, text, append = false)
|
||||
ObjVoid
|
||||
moduleName = module.packageName,
|
||||
code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj {
|
||||
return scp.fsGuard {
|
||||
val self = scp.thisObj as ObjPath
|
||||
val text = scp.requireOnlyArg<ObjString>().value
|
||||
self.secured.writeUtf8(self.path, text, append = false)
|
||||
ObjVoid
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
// appendUtf8(text: String)
|
||||
addFnDoc(
|
||||
name = "appendUtf8",
|
||||
doc = "Append UTF-8 text to the end of the file.",
|
||||
params = listOf(ParamDoc("text", type("lyng.String"))),
|
||||
moduleName = module.packageName
|
||||
) {
|
||||
fsGuard {
|
||||
val self = this.thisObj as ObjPath
|
||||
val text = requireOnlyArg<ObjString>().value
|
||||
self.secured.writeUtf8(self.path, text, append = true)
|
||||
ObjVoid
|
||||
moduleName = module.packageName,
|
||||
code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj {
|
||||
return scp.fsGuard {
|
||||
val self = scp.thisObj as ObjPath
|
||||
val text = scp.requireOnlyArg<ObjString>().value
|
||||
self.secured.writeUtf8(self.path, text, append = true)
|
||||
ObjVoid
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
// metadata(): Map
|
||||
addFnDoc(
|
||||
name = "metadata",
|
||||
doc = "Fetch cached metadata as a map of fields: `isFile`, `isDirectory`, `size`, `createdAtMillis`, `modifiedAtMillis`, `isSymlink`.",
|
||||
returns = TypeGenericDoc(type("lyng.Map"), listOf(type("lyng.String"), type("lyng.Any"))),
|
||||
moduleName = module.packageName
|
||||
) {
|
||||
fsGuard {
|
||||
val self = this.thisObj as ObjPath
|
||||
val m = self.secured.metadata(self.path)
|
||||
ObjMap(mutableMapOf(
|
||||
ObjString("isFile") to ObjBool(m.isRegularFile),
|
||||
ObjString("isDirectory") to ObjBool(m.isDirectory),
|
||||
ObjString("size") to (m.size?.toLong() ?: 0L).toObj(),
|
||||
ObjString("createdAtMillis") to ((m.createdAtMillis ?: 0L)).toObj(),
|
||||
ObjString("modifiedAtMillis") to ((m.modifiedAtMillis ?: 0L)).toObj(),
|
||||
ObjString("isSymlink") to ObjBool(m.isSymlink),
|
||||
))
|
||||
moduleName = module.packageName,
|
||||
code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj {
|
||||
return scp.fsGuard {
|
||||
val self = scp.thisObj as ObjPath
|
||||
val m = self.secured.metadata(self.path)
|
||||
ObjMap(mutableMapOf(
|
||||
ObjString("isFile") to ObjBool(m.isRegularFile),
|
||||
ObjString("isDirectory") to ObjBool(m.isDirectory),
|
||||
ObjString("size") to (m.size?.toLong() ?: 0L).toObj(),
|
||||
ObjString("createdAtMillis") to ((m.createdAtMillis ?: 0L)).toObj(),
|
||||
ObjString("modifiedAtMillis") to ((m.modifiedAtMillis ?: 0L)).toObj(),
|
||||
ObjString("isSymlink") to ObjBool(m.isSymlink),
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
// mkdirs(mustCreate: Bool=false)
|
||||
addFnDoc(
|
||||
name = "mkdirs",
|
||||
doc = "Create directories (like `mkdir -p`). If `mustCreate` is true and the path already exists, the call fails. Otherwise it is a no‑op when the directory exists.",
|
||||
params = listOf(ParamDoc("mustCreate", type("lyng.Bool"))),
|
||||
moduleName = module.packageName
|
||||
) {
|
||||
fsGuard {
|
||||
val self = this.thisObj as ObjPath
|
||||
val mustCreate = args.list.getOrNull(0)?.toBool() ?: false
|
||||
self.secured.createDirectories(self.path, mustCreate)
|
||||
ObjVoid
|
||||
moduleName = module.packageName,
|
||||
code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj {
|
||||
return scp.fsGuard {
|
||||
val self = scp.thisObj as ObjPath
|
||||
val mustCreate = scp.args.list.getOrNull(0)?.toBool() ?: false
|
||||
self.secured.createDirectories(self.path, mustCreate)
|
||||
ObjVoid
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
// move(to: Path|String, overwrite: Bool=false)
|
||||
addFnDoc(
|
||||
name = "move",
|
||||
doc = "Move this path to a new location. `to` may be a `Path` or `String`. When `overwrite` is false and the target exists, the operation fails (provider may throw `AccessDeniedException`).",
|
||||
// types vary; keep generic description in doc
|
||||
params = listOf(ParamDoc("to"), ParamDoc("overwrite", type("lyng.Bool"))),
|
||||
moduleName = module.packageName
|
||||
) {
|
||||
fsGuard {
|
||||
val self = this.thisObj as ObjPath
|
||||
val toPath = parsePathArg(this, self, requiredArg<Obj>(0))
|
||||
val overwrite = args.list.getOrNull(1)?.toBool() ?: false
|
||||
self.secured.move(self.path, toPath, overwrite)
|
||||
ObjVoid
|
||||
moduleName = module.packageName,
|
||||
code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj {
|
||||
return scp.fsGuard {
|
||||
val self = scp.thisObj as ObjPath
|
||||
val toPath = parsePathArg(scp, self, scp.requiredArg<Obj>(0))
|
||||
val overwrite = scp.args.list.getOrNull(1)?.toBool() ?: false
|
||||
self.secured.move(self.path, toPath, overwrite)
|
||||
ObjVoid
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
// delete(mustExist: Bool=false, recursively: Bool=false)
|
||||
addFnDoc(
|
||||
name = "delete",
|
||||
doc = "Delete this path. `mustExist=true` causes failure if the path does not exist. `recursively=true` removes directories with their contents. Providers can throw `AccessDeniedException` on policy violations.",
|
||||
params = listOf(ParamDoc("mustExist", type("lyng.Bool")), ParamDoc("recursively", type("lyng.Bool"))),
|
||||
moduleName = module.packageName
|
||||
) {
|
||||
fsGuard {
|
||||
val self = this.thisObj as ObjPath
|
||||
val mustExist = args.list.getOrNull(0)?.toBool() ?: false
|
||||
val recursively = args.list.getOrNull(1)?.toBool() ?: false
|
||||
self.secured.delete(self.path, mustExist, recursively)
|
||||
ObjVoid
|
||||
moduleName = module.packageName,
|
||||
code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj {
|
||||
return scp.fsGuard {
|
||||
val self = scp.thisObj as ObjPath
|
||||
val mustExist = scp.args.list.getOrNull(0)?.toBool() ?: false
|
||||
val recursively = scp.args.list.getOrNull(1)?.toBool() ?: false
|
||||
self.secured.delete(self.path, mustExist, recursively)
|
||||
ObjVoid
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
// copy(to: Path|String, overwrite: Bool=false)
|
||||
addFnDoc(
|
||||
name = "copy",
|
||||
doc = "Copy this path to a new location. `to` may be a `Path` or `String`. When `overwrite` is false and the target exists, the operation fails (provider may throw `AccessDeniedException`).",
|
||||
params = listOf(ParamDoc("to"), ParamDoc("overwrite", type("lyng.Bool"))),
|
||||
moduleName = module.packageName
|
||||
) {
|
||||
fsGuard {
|
||||
val self = this.thisObj as ObjPath
|
||||
val toPath = parsePathArg(this, self, requiredArg<Obj>(0))
|
||||
val overwrite = args.list.getOrNull(1)?.toBool() ?: false
|
||||
self.secured.copy(self.path, toPath, overwrite)
|
||||
ObjVoid
|
||||
moduleName = module.packageName,
|
||||
code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj {
|
||||
return scp.fsGuard {
|
||||
val self = scp.thisObj as ObjPath
|
||||
val toPath = parsePathArg(scp, self, scp.requiredArg<Obj>(0))
|
||||
val overwrite = scp.args.list.getOrNull(1)?.toBool() ?: false
|
||||
self.secured.copy(self.path, toPath, overwrite)
|
||||
ObjVoid
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
// glob(pattern: String): List<Path>
|
||||
addFnDoc(
|
||||
name = "glob",
|
||||
doc = "List entries matching a glob pattern (no recursion).",
|
||||
params = listOf(ParamDoc("pattern", type("lyng.String"))),
|
||||
returns = TypeGenericDoc(type("lyng.List"), listOf(type("Path"))),
|
||||
moduleName = module.packageName
|
||||
) {
|
||||
fsGuard {
|
||||
val self = this.thisObj as ObjPath
|
||||
val pattern = requireOnlyArg<ObjString>().value
|
||||
val matches = self.secured.glob(self.path, pattern)
|
||||
ObjList(matches.map { ObjPath(self.objClass, self.secured, it) }.toMutableList())
|
||||
moduleName = module.packageName,
|
||||
code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj {
|
||||
return scp.fsGuard {
|
||||
val self = scp.thisObj as ObjPath
|
||||
val pattern = scp.requireOnlyArg<ObjString>().value
|
||||
val matches = self.secured.glob(self.path, pattern)
|
||||
ObjList(matches.map { ObjPath(self.objClass, self.secured, it) }.toMutableList())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
// --- streaming readers (initial version: chunk from whole content, API stable) ---
|
||||
|
||||
@ -403,15 +479,18 @@ private suspend fun buildFsModule(module: ModuleScope, policy: FsAccessPolicy) {
|
||||
doc = "Read file in fixed-size chunks as an iterator of `Buffer`.",
|
||||
params = listOf(ParamDoc("size", type("lyng.Int"))),
|
||||
returns = TypeGenericDoc(type("lyng.Iterator"), listOf(type("lyng.Buffer"))),
|
||||
moduleName = module.packageName
|
||||
) {
|
||||
fsGuard {
|
||||
val self = this.thisObj as ObjPath
|
||||
val size = args.list.getOrNull(0)?.toInt() ?: 65536
|
||||
val bytes = self.secured.readBytes(self.path)
|
||||
ObjFsBytesIterator(bytes, size)
|
||||
moduleName = module.packageName,
|
||||
code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj {
|
||||
return scp.fsGuard {
|
||||
val self = scp.thisObj as ObjPath
|
||||
val size = scp.args.list.getOrNull(0)?.toInt() ?: 65536
|
||||
val bytes = self.secured.readBytes(self.path)
|
||||
ObjFsBytesIterator(bytes, size)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
// readUtf8Chunks(size: Int = 65536) -> Iterator<String>
|
||||
addFnDoc(
|
||||
@ -419,28 +498,34 @@ private suspend fun buildFsModule(module: ModuleScope, policy: FsAccessPolicy) {
|
||||
doc = "Read UTF-8 text in fixed-size chunks as an iterator of `String`.",
|
||||
params = listOf(ParamDoc("size", type("lyng.Int"))),
|
||||
returns = TypeGenericDoc(type("lyng.Iterator"), listOf(type("lyng.String"))),
|
||||
moduleName = module.packageName
|
||||
) {
|
||||
fsGuard {
|
||||
val self = this.thisObj as ObjPath
|
||||
val size = args.list.getOrNull(0)?.toInt() ?: 65536
|
||||
val text = self.secured.readUtf8(self.path)
|
||||
ObjFsStringChunksIterator(text, size)
|
||||
moduleName = module.packageName,
|
||||
code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj {
|
||||
return scp.fsGuard {
|
||||
val self = scp.thisObj as ObjPath
|
||||
val size = scp.args.list.getOrNull(0)?.toInt() ?: 65536
|
||||
val text = self.secured.readUtf8(self.path)
|
||||
ObjFsStringChunksIterator(text, size)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
// lines() -> Iterator<String>, implemented via readUtf8Chunks
|
||||
addFnDoc(
|
||||
name = "lines",
|
||||
doc = "Iterate lines of the file as `String` values.",
|
||||
returns = TypeGenericDoc(type("lyng.Iterator"), listOf(type("lyng.String"))),
|
||||
moduleName = module.packageName
|
||||
) {
|
||||
fsGuard {
|
||||
val chunkIt = thisObj.invokeInstanceMethod(this, "readUtf8Chunks")
|
||||
ObjFsLinesIterator(chunkIt)
|
||||
moduleName = module.packageName,
|
||||
code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj {
|
||||
return scp.fsGuard {
|
||||
val chunkIt = scp.thisObj.invokeInstanceMethod(scp, "readUtf8Chunks")
|
||||
ObjFsLinesIterator(chunkIt)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
// Export into the module scope with docs
|
||||
@ -518,39 +603,51 @@ class ObjFsBytesIterator(
|
||||
name = "iterator",
|
||||
doc = "Return this iterator instance (enables `for` loops).",
|
||||
returns = type("BytesIterator"),
|
||||
moduleName = "lyng.io.fs"
|
||||
) { thisObj }
|
||||
moduleName = "lyng.io.fs",
|
||||
code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj = scp.thisObj
|
||||
}
|
||||
)
|
||||
addFnDoc(
|
||||
name = "hasNext",
|
||||
doc = "Whether there is another chunk available.",
|
||||
returns = type("lyng.Bool"),
|
||||
moduleName = "lyng.io.fs"
|
||||
) {
|
||||
val self = thisAs<ObjFsBytesIterator>()
|
||||
(self.pos < self.data.size).toObj()
|
||||
}
|
||||
moduleName = "lyng.io.fs",
|
||||
code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj {
|
||||
val self = scp.thisAs<ObjFsBytesIterator>()
|
||||
return (self.pos < self.data.size).toObj()
|
||||
}
|
||||
}
|
||||
)
|
||||
addFnDoc(
|
||||
name = "next",
|
||||
doc = "Return the next chunk as a `Buffer`.",
|
||||
returns = type("lyng.Buffer"),
|
||||
moduleName = "lyng.io.fs"
|
||||
) {
|
||||
val self = thisAs<ObjFsBytesIterator>()
|
||||
if (self.pos >= self.data.size) raiseIllegalState("iterator exhausted")
|
||||
val end = minOf(self.pos + self.chunkSize, self.data.size)
|
||||
val chunk = self.data.copyOfRange(self.pos, end)
|
||||
self.pos = end
|
||||
ObjBuffer(chunk.asUByteArray())
|
||||
}
|
||||
moduleName = "lyng.io.fs",
|
||||
code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj {
|
||||
val self = scp.thisAs<ObjFsBytesIterator>()
|
||||
if (self.pos >= self.data.size) scp.raiseIllegalState("iterator exhausted")
|
||||
val end = minOf(self.pos + self.chunkSize, self.data.size)
|
||||
val chunk = self.data.copyOfRange(self.pos, end)
|
||||
self.pos = end
|
||||
return ObjBuffer(chunk.asUByteArray())
|
||||
}
|
||||
}
|
||||
)
|
||||
addFnDoc(
|
||||
name = "cancelIteration",
|
||||
doc = "Stop the iteration early; subsequent `hasNext` returns false.",
|
||||
moduleName = "lyng.io.fs"
|
||||
) {
|
||||
val self = thisAs<ObjFsBytesIterator>()
|
||||
self.pos = self.data.size
|
||||
ObjVoid
|
||||
}
|
||||
moduleName = "lyng.io.fs",
|
||||
code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj {
|
||||
val self = scp.thisAs<ObjFsBytesIterator>()
|
||||
self.pos = self.data.size
|
||||
return ObjVoid
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -573,35 +670,47 @@ class ObjFsStringChunksIterator(
|
||||
name = "iterator",
|
||||
doc = "Return this iterator instance (enables `for` loops).",
|
||||
returns = type("StringChunksIterator"),
|
||||
moduleName = "lyng.io.fs"
|
||||
) { thisObj }
|
||||
moduleName = "lyng.io.fs",
|
||||
code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj = scp.thisObj
|
||||
}
|
||||
)
|
||||
addFnDoc(
|
||||
name = "hasNext",
|
||||
doc = "Whether there is another chunk available.",
|
||||
returns = type("lyng.Bool"),
|
||||
moduleName = "lyng.io.fs"
|
||||
) {
|
||||
val self = thisAs<ObjFsStringChunksIterator>()
|
||||
(self.pos < self.text.length).toObj()
|
||||
}
|
||||
moduleName = "lyng.io.fs",
|
||||
code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj {
|
||||
val self = scp.thisAs<ObjFsStringChunksIterator>()
|
||||
return (self.pos < self.text.length).toObj()
|
||||
}
|
||||
}
|
||||
)
|
||||
addFnDoc(
|
||||
name = "next",
|
||||
doc = "Return the next UTF-8 chunk as a `String`.",
|
||||
returns = type("lyng.String"),
|
||||
moduleName = "lyng.io.fs"
|
||||
) {
|
||||
val self = thisAs<ObjFsStringChunksIterator>()
|
||||
if (self.pos >= self.text.length) raiseIllegalState("iterator exhausted")
|
||||
val end = minOf(self.pos + self.chunkChars, self.text.length)
|
||||
val chunk = self.text.substring(self.pos, end)
|
||||
self.pos = end
|
||||
ObjString(chunk)
|
||||
}
|
||||
moduleName = "lyng.io.fs",
|
||||
code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj {
|
||||
val self = scp.thisAs<ObjFsStringChunksIterator>()
|
||||
if (self.pos >= self.text.length) scp.raiseIllegalState("iterator exhausted")
|
||||
val end = minOf(self.pos + self.chunkChars, self.text.length)
|
||||
val chunk = self.text.substring(self.pos, end)
|
||||
self.pos = end
|
||||
return ObjString(chunk)
|
||||
}
|
||||
}
|
||||
)
|
||||
addFnDoc(
|
||||
name = "cancelIteration",
|
||||
doc = "Stop the iteration early; subsequent `hasNext` returns false.",
|
||||
moduleName = "lyng.io.fs"
|
||||
) { ObjVoid }
|
||||
moduleName = "lyng.io.fs",
|
||||
code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj = ObjVoid
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -624,46 +733,58 @@ class ObjFsLinesIterator(
|
||||
name = "iterator",
|
||||
doc = "Return this iterator instance (enables `for` loops).",
|
||||
returns = type("LinesIterator"),
|
||||
moduleName = "lyng.io.fs"
|
||||
) { thisObj }
|
||||
moduleName = "lyng.io.fs",
|
||||
code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj = scp.thisObj
|
||||
}
|
||||
)
|
||||
addFnDoc(
|
||||
name = "hasNext",
|
||||
doc = "Whether another line is available.",
|
||||
returns = type("lyng.Bool"),
|
||||
moduleName = "lyng.io.fs"
|
||||
) {
|
||||
val self = thisAs<ObjFsLinesIterator>()
|
||||
self.ensureBufferFilled(this)
|
||||
(self.buffer.isNotEmpty() || !self.exhausted).toObj()
|
||||
}
|
||||
moduleName = "lyng.io.fs",
|
||||
code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj {
|
||||
val self = scp.thisAs<ObjFsLinesIterator>()
|
||||
self.ensureBufferFilled(scp)
|
||||
return (self.buffer.isNotEmpty() || !self.exhausted).toObj()
|
||||
}
|
||||
}
|
||||
)
|
||||
addFnDoc(
|
||||
name = "next",
|
||||
doc = "Return the next line as `String`.",
|
||||
returns = type("lyng.String"),
|
||||
moduleName = "lyng.io.fs"
|
||||
) {
|
||||
val self = thisAs<ObjFsLinesIterator>()
|
||||
self.ensureBufferFilled(this)
|
||||
if (self.buffer.isEmpty() && self.exhausted) raiseIllegalState("iterator exhausted")
|
||||
val idx = self.buffer.indexOf('\n')
|
||||
val line = if (idx >= 0) {
|
||||
val l = self.buffer.substring(0, idx)
|
||||
self.buffer = self.buffer.substring(idx + 1)
|
||||
l
|
||||
} else {
|
||||
// last line without trailing newline
|
||||
val l = self.buffer
|
||||
self.buffer = ""
|
||||
self.exhausted = true
|
||||
l
|
||||
moduleName = "lyng.io.fs",
|
||||
code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj {
|
||||
val self = scp.thisAs<ObjFsLinesIterator>()
|
||||
self.ensureBufferFilled(scp)
|
||||
if (self.buffer.isEmpty() && self.exhausted) scp.raiseIllegalState("iterator exhausted")
|
||||
val idx = self.buffer.indexOf('\n')
|
||||
val line = if (idx >= 0) {
|
||||
val l = self.buffer.substring(0, idx)
|
||||
self.buffer = self.buffer.substring(idx + 1)
|
||||
l
|
||||
} else {
|
||||
// last line without trailing newline
|
||||
val l = self.buffer
|
||||
self.buffer = ""
|
||||
self.exhausted = true
|
||||
l
|
||||
}
|
||||
return ObjString(line)
|
||||
}
|
||||
}
|
||||
ObjString(line)
|
||||
}
|
||||
)
|
||||
addFnDoc(
|
||||
name = "cancelIteration",
|
||||
doc = "Stop the iteration early; subsequent `hasNext` returns false.",
|
||||
moduleName = "lyng.io.fs"
|
||||
) { ObjVoid }
|
||||
moduleName = "lyng.io.fs",
|
||||
code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj = ObjVoid
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -20,9 +20,11 @@ package net.sergeych.lyng.io.process
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import net.sergeych.lyng.ModuleScope
|
||||
import net.sergeych.lyng.Scope
|
||||
import net.sergeych.lyng.ScopeCallable
|
||||
import net.sergeych.lyng.miniast.*
|
||||
import net.sergeych.lyng.obj.*
|
||||
import net.sergeych.lyng.pacman.ImportManager
|
||||
import net.sergeych.lyng.pacman.ModuleBuilder
|
||||
import net.sergeych.lyng.statement
|
||||
import net.sergeych.lyngio.process.*
|
||||
import net.sergeych.lyngio.process.security.ProcessAccessDeniedException
|
||||
@ -39,9 +41,11 @@ fun createProcessModule(policy: ProcessAccessPolicy, manager: ImportManager): Bo
|
||||
val name = "lyng.io.process"
|
||||
if (manager.packageNames.contains(name)) return false
|
||||
|
||||
manager.addPackage(name) { module ->
|
||||
buildProcessModule(module, policy)
|
||||
}
|
||||
manager.addPackage(name, object : ModuleBuilder {
|
||||
override suspend fun build(module: ModuleScope) {
|
||||
buildProcessModule(module, policy)
|
||||
}
|
||||
})
|
||||
return true
|
||||
}
|
||||
|
||||
@ -59,59 +63,74 @@ private suspend fun buildProcessModule(module: ModuleScope, policy: ProcessAcces
|
||||
name = "stdout",
|
||||
doc = "Get standard output stream as a Flow of lines.",
|
||||
returns = type("lyng.Flow"),
|
||||
moduleName = module.packageName
|
||||
) {
|
||||
val self = thisAs<ObjRunningProcess>()
|
||||
self.process.stdout.toLyngFlow(this)
|
||||
}
|
||||
moduleName = module.packageName,
|
||||
code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj {
|
||||
val self = scp.thisAs<ObjRunningProcess>()
|
||||
return self.process.stdout.toLyngFlow(scp)
|
||||
}
|
||||
}
|
||||
)
|
||||
addFnDoc(
|
||||
name = "stderr",
|
||||
doc = "Get standard error stream as a Flow of lines.",
|
||||
returns = type("lyng.Flow"),
|
||||
moduleName = module.packageName
|
||||
) {
|
||||
val self = thisAs<ObjRunningProcess>()
|
||||
self.process.stderr.toLyngFlow(this)
|
||||
}
|
||||
moduleName = module.packageName,
|
||||
code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj {
|
||||
val self = scp.thisAs<ObjRunningProcess>()
|
||||
return self.process.stderr.toLyngFlow(scp)
|
||||
}
|
||||
}
|
||||
)
|
||||
addFnDoc(
|
||||
name = "signal",
|
||||
doc = "Send a signal to the process (e.g. 'SIGINT', 'SIGTERM', 'SIGKILL').",
|
||||
params = listOf(ParamDoc("signal", type("lyng.String"))),
|
||||
moduleName = module.packageName
|
||||
) {
|
||||
processGuard {
|
||||
val sigStr = requireOnlyArg<ObjString>().value.uppercase()
|
||||
val sig = try {
|
||||
ProcessSignal.valueOf(sigStr)
|
||||
} catch (e: Exception) {
|
||||
try {
|
||||
ProcessSignal.valueOf("SIG$sigStr")
|
||||
} catch (e2: Exception) {
|
||||
raiseIllegalArgument("Unknown signal: $sigStr")
|
||||
moduleName = module.packageName,
|
||||
code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj {
|
||||
return scp.processGuard {
|
||||
val sigStr = scp.requireOnlyArg<ObjString>().value.uppercase()
|
||||
val sig = try {
|
||||
ProcessSignal.valueOf(sigStr)
|
||||
} catch (e: Exception) {
|
||||
try {
|
||||
ProcessSignal.valueOf("SIG$sigStr")
|
||||
} catch (e2: Exception) {
|
||||
scp.raiseIllegalArgument("Unknown signal: $sigStr")
|
||||
}
|
||||
}
|
||||
scp.thisAs<ObjRunningProcess>().process.sendSignal(sig)
|
||||
ObjVoid
|
||||
}
|
||||
}
|
||||
thisAs<ObjRunningProcess>().process.sendSignal(sig)
|
||||
ObjVoid
|
||||
}
|
||||
}
|
||||
)
|
||||
addFnDoc(
|
||||
name = "waitFor",
|
||||
doc = "Wait for the process to exit and return its exit code.",
|
||||
returns = type("lyng.Int"),
|
||||
moduleName = module.packageName
|
||||
) {
|
||||
processGuard {
|
||||
thisAs<ObjRunningProcess>().process.waitFor().toObj()
|
||||
moduleName = module.packageName,
|
||||
code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj {
|
||||
return scp.processGuard {
|
||||
scp.thisAs<ObjRunningProcess>().process.waitFor().toObj()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
addFnDoc(
|
||||
name = "destroy",
|
||||
doc = "Forcefully terminate the process.",
|
||||
moduleName = module.packageName
|
||||
) {
|
||||
thisAs<ObjRunningProcess>().process.destroy()
|
||||
ObjVoid
|
||||
}
|
||||
moduleName = module.packageName,
|
||||
code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj {
|
||||
scp.thisAs<ObjRunningProcess>().process.destroy()
|
||||
return ObjVoid
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
val processType = object : ObjClass("Process") {}
|
||||
@ -122,30 +141,36 @@ private suspend fun buildProcessModule(module: ModuleScope, policy: ProcessAcces
|
||||
doc = "Execute a process with arguments.",
|
||||
params = listOf(ParamDoc("executable", type("lyng.String")), ParamDoc("args", type("lyng.List"))),
|
||||
returns = type("RunningProcess"),
|
||||
moduleName = module.packageName
|
||||
) {
|
||||
if (runner == null) raiseError("Processes are not supported on this platform")
|
||||
processGuard {
|
||||
val executable = requiredArg<ObjString>(0).value
|
||||
val args = requiredArg<ObjList>(1).list.map { it.toString() }
|
||||
val lp = runner.execute(executable, args)
|
||||
ObjRunningProcess(runningProcessType, lp)
|
||||
moduleName = module.packageName,
|
||||
code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj {
|
||||
if (runner == null) scp.raiseError("Processes are not supported on this platform")
|
||||
return scp.processGuard {
|
||||
val executable = scp.requiredArg<ObjString>(0).value
|
||||
val args = scp.requiredArg<ObjList>(1).list.map { it.toString() }
|
||||
val lp = runner.execute(executable, args)
|
||||
ObjRunningProcess(runningProcessType, lp)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
addClassFnDoc(
|
||||
name = "shell",
|
||||
doc = "Execute a command via system shell.",
|
||||
params = listOf(ParamDoc("command", type("lyng.String"))),
|
||||
returns = type("RunningProcess"),
|
||||
moduleName = module.packageName
|
||||
) {
|
||||
if (runner == null) raiseError("Processes are not supported on this platform")
|
||||
processGuard {
|
||||
val command = requireOnlyArg<ObjString>().value
|
||||
val lp = runner.shell(command)
|
||||
ObjRunningProcess(runningProcessType, lp)
|
||||
moduleName = module.packageName,
|
||||
code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj {
|
||||
if (runner == null) scp.raiseError("Processes are not supported on this platform")
|
||||
return scp.processGuard {
|
||||
val command = scp.requireOnlyArg<ObjString>().value
|
||||
val lp = runner.shell(command)
|
||||
ObjRunningProcess(runningProcessType, lp)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
val platformType = object : ObjClass("Platform") {}
|
||||
@ -155,24 +180,28 @@ private suspend fun buildProcessModule(module: ModuleScope, policy: ProcessAcces
|
||||
name = "details",
|
||||
doc = "Get platform core details.",
|
||||
returns = type("lyng.Map"),
|
||||
moduleName = module.packageName
|
||||
) {
|
||||
val d = getPlatformDetails()
|
||||
ObjMap(mutableMapOf(
|
||||
ObjString("name") to ObjString(d.name),
|
||||
ObjString("version") to ObjString(d.version),
|
||||
ObjString("arch") to ObjString(d.arch),
|
||||
ObjString("kernelVersion") to (d.kernelVersion?.toObj() ?: ObjNull)
|
||||
))
|
||||
}
|
||||
moduleName = module.packageName,
|
||||
code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj {
|
||||
val d = getPlatformDetails()
|
||||
return ObjMap(mutableMapOf(
|
||||
ObjString("name") to ObjString(d.name),
|
||||
ObjString("version") to ObjString(d.version),
|
||||
ObjString("arch") to ObjString(d.arch),
|
||||
ObjString("kernelVersion") to (d.kernelVersion?.toObj() ?: ObjNull)
|
||||
))
|
||||
}
|
||||
}
|
||||
)
|
||||
addClassFnDoc(
|
||||
name = "isSupported",
|
||||
doc = "Check if processes are supported on this platform.",
|
||||
returns = type("lyng.Bool"),
|
||||
moduleName = module.packageName
|
||||
) {
|
||||
isProcessSupported().toObj()
|
||||
}
|
||||
moduleName = module.packageName,
|
||||
code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj = isProcessSupported().toObj()
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
module.addConstDoc(
|
||||
@ -216,19 +245,21 @@ private suspend inline fun Scope.processGuard(crossinline block: suspend () -> O
|
||||
}
|
||||
|
||||
private fun Flow<String>.toLyngFlow(flowScope: Scope): ObjFlow {
|
||||
val producer = statement {
|
||||
val builder = (this as? net.sergeych.lyng.ClosureScope)?.callScope?.thisObj as? ObjFlowBuilder
|
||||
?: this.thisObj as? ObjFlowBuilder
|
||||
val producer = statement(f = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj {
|
||||
val builder = (scp as? net.sergeych.lyng.ClosureScope)?.callScope?.thisObj as? ObjFlowBuilder
|
||||
?: scp.thisObj as? ObjFlowBuilder
|
||||
|
||||
this@toLyngFlow.collect {
|
||||
try {
|
||||
builder?.output?.send(ObjString(it))
|
||||
} catch (e: Exception) {
|
||||
// Channel closed or other error, stop collecting
|
||||
return@collect
|
||||
this@toLyngFlow.collect {
|
||||
try {
|
||||
builder?.output?.send(ObjString(it))
|
||||
} catch (e: Exception) {
|
||||
// Channel closed or other error, stop collecting
|
||||
return@collect
|
||||
}
|
||||
}
|
||||
return ObjVoid
|
||||
}
|
||||
ObjVoid
|
||||
}
|
||||
})
|
||||
return ObjFlow(producer, flowScope)
|
||||
}
|
||||
|
||||
@ -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
|
||||
suspend fun processHead(index: Int, headPos: Int): Pair<Int, Int> {
|
||||
var i = index
|
||||
var hp = headPos
|
||||
// Locate ellipsis index within considered parameters
|
||||
val ellipsisIndex = params.subList(0, paramsSize).indexOfFirst { it.isEllipsis }
|
||||
|
||||
var headPos = 0
|
||||
var tailPos = callArgs.size - 1
|
||||
|
||||
if (ellipsisIndex >= 0) {
|
||||
// Assign head first to know how many positionals are consumed from the start
|
||||
var i = 0
|
||||
while (i < paramsSize) {
|
||||
val a = params[i]
|
||||
if (a.isEllipsis) break
|
||||
if (assignedByName[i]) {
|
||||
assign(a, namedValues[i]!!)
|
||||
} else {
|
||||
val value = if (hp < callArgs.size) callArgs[hp++]
|
||||
val value = if (headPos < callArgs.size) callArgs[headPos++]
|
||||
else a.defaultValue?.execute(scope)
|
||||
?: scope.raiseIllegalArgument("too few arguments for the call (missing ${a.name})")
|
||||
assign(a, value)
|
||||
}
|
||||
i++
|
||||
}
|
||||
return i to hp
|
||||
}
|
||||
val afterHead = i
|
||||
val headConsumedTo = headPos
|
||||
|
||||
// Helper: assign tail part from the end, consuming from tailPos; stop before ellipsis index
|
||||
// Do not consume elements below headPosBound to avoid overlap with head consumption
|
||||
suspend fun processTail(startExclusive: Int, tailStart: Int, headPosBound: Int): Int {
|
||||
var i = paramsSize - 1
|
||||
var tp = tailStart
|
||||
while (i > startExclusive) {
|
||||
// Then assign tail consuming from the end down to headConsumedTo boundary
|
||||
i = paramsSize - 1
|
||||
var tp = tailPos
|
||||
while (i > ellipsisIndex) {
|
||||
val a = params[i]
|
||||
if (a.isEllipsis) break
|
||||
if (i < assignedByName.size && assignedByName[i]) {
|
||||
assign(a, namedValues[i]!!)
|
||||
} else {
|
||||
val value = if (tp >= headPosBound) callArgs[tp--]
|
||||
val value = if (tp >= headConsumedTo) callArgs[tp--]
|
||||
else a.defaultValue?.execute(scope)
|
||||
?: scope.raiseIllegalArgument("too few arguments for the call")
|
||||
assign(a, value)
|
||||
}
|
||||
i--
|
||||
}
|
||||
return tp
|
||||
}
|
||||
val tailConsumedFrom = tp
|
||||
|
||||
fun processEllipsis(index: Int, headPos: Int, tailPos: Int) {
|
||||
val a = params[index]
|
||||
val from = headPos
|
||||
val to = tailPos
|
||||
// Assign ellipsis list from remaining positionals between headConsumedTo..tailConsumedFrom
|
||||
val a = params[ellipsisIndex]
|
||||
val from = headConsumedTo
|
||||
val to = tailConsumedFrom
|
||||
val l = if (from > to) ObjList()
|
||||
else ObjList(callArgs.subList(from, to + 1).toMutableList())
|
||||
assign(a, l)
|
||||
}
|
||||
|
||||
// Locate ellipsis index within considered parameters
|
||||
val ellipsisIndex = params.subList(0, paramsSize).indexOfFirst { it.isEllipsis }
|
||||
|
||||
if (ellipsisIndex >= 0) {
|
||||
// Assign head first to know how many positionals are consumed from the start
|
||||
val (afterHead, headConsumedTo) = processHead(0, 0)
|
||||
// Then assign tail consuming from the end down to headConsumedTo boundary
|
||||
val tailConsumedFrom = processTail(ellipsisIndex, callArgs.size - 1, headConsumedTo)
|
||||
// Assign ellipsis list from remaining positionals between headConsumedTo..tailConsumedFrom
|
||||
processEllipsis(ellipsisIndex, headConsumedTo, tailConsumedFrom)
|
||||
} else {
|
||||
// No ellipsis: assign head only; any leftover positionals → error
|
||||
val (_, headConsumedTo) = processHead(0, 0)
|
||||
var i = 0
|
||||
while (i < paramsSize) {
|
||||
val a = params[i]
|
||||
if (a.isEllipsis) break
|
||||
if (assignedByName[i]) {
|
||||
assign(a, namedValues[i]!!)
|
||||
} else {
|
||||
val value = if (headPos < callArgs.size) callArgs[headPos++]
|
||||
else a.defaultValue?.execute(scope)
|
||||
?: scope.raiseIllegalArgument("too few arguments for the call (missing ${a.name})")
|
||||
assign(a, value)
|
||||
}
|
||||
i++
|
||||
}
|
||||
val headConsumedTo = headPos
|
||||
if (headConsumedTo != callArgs.size)
|
||||
scope.raiseIllegalArgument("too many arguments for the call")
|
||||
}
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2025 Sergey S. Chernov real.sergeych@gmail.com
|
||||
* Copyright 2026 Sergey S. Chernov real.sergeych@gmail.com
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@ -244,10 +244,16 @@ data class ParsedArgument(
|
||||
* Convert to list of kotlin objects, see [Obj.toKotlin].
|
||||
*/
|
||||
suspend fun toKotlinList(scope: Scope): List<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 {
|
||||
val EMPTY = Arguments(emptyList())
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -21,6 +21,23 @@ import net.sergeych.lyng.obj.*
|
||||
import net.sergeych.lyng.pacman.ImportManager
|
||||
import net.sergeych.lyng.pacman.ImportProvider
|
||||
|
||||
interface ScopeCallable {
|
||||
suspend fun call(scope: Scope): Obj
|
||||
}
|
||||
|
||||
interface VoidScopeCallable {
|
||||
suspend fun call(scope: Scope)
|
||||
}
|
||||
|
||||
interface ScopeBlock<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)
|
||||
object FrameIdGen { var c: Long = 1L; fun nextId(): Long = c++ }
|
||||
fun nextFrameId(): Long = FrameIdGen.nextId()
|
||||
@ -447,17 +464,17 @@ open class Scope(
|
||||
* Execute a block inside a child frame. Guarded for future pooling via [PerfFlags.SCOPE_POOL].
|
||||
* Currently always creates a fresh child scope to preserve unique frameId semantics.
|
||||
*/
|
||||
inline suspend fun <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) {
|
||||
val child = ScopePool.borrow(this, args, pos, newThisObj ?: thisObj)
|
||||
try {
|
||||
return block(child)
|
||||
return block.call(child)
|
||||
} finally {
|
||||
ScopePool.release(child)
|
||||
}
|
||||
} else {
|
||||
val child = createChildScope(args, newThisObj)
|
||||
return block(child)
|
||||
return block.call(child)
|
||||
}
|
||||
}
|
||||
|
||||
@ -563,20 +580,17 @@ open class Scope(
|
||||
return ns.objClass
|
||||
}
|
||||
|
||||
inline fun addVoidFn(vararg names: String, crossinline fn: suspend Scope.() -> Unit) {
|
||||
addFn(*names) {
|
||||
fn(this)
|
||||
ObjVoid
|
||||
}
|
||||
fun addVoidFn(vararg names: String, fn: VoidScopeCallable) {
|
||||
addFn(*names, fn = object : ScopeCallable {
|
||||
override suspend fun call(scope: Scope): Obj {
|
||||
fn.call(scope)
|
||||
return ObjVoid
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fun addFn(vararg names: String, fn: suspend Scope.() -> Obj) {
|
||||
val newFn = object : Statement() {
|
||||
override val pos: Pos = Pos.builtIn
|
||||
|
||||
override suspend fun execute(scope: Scope): Obj = scope.fn()
|
||||
|
||||
}
|
||||
fun addFn(vararg names: String, fn: ScopeCallable) {
|
||||
val newFn = FnStatement(fn)
|
||||
for (name in names) {
|
||||
addItem(
|
||||
name,
|
||||
|
||||
@ -18,14 +18,13 @@
|
||||
package net.sergeych.lyng
|
||||
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.yield
|
||||
import net.sergeych.lyng.Script.Companion.defaultImportManager
|
||||
import net.sergeych.lyng.miniast.*
|
||||
import net.sergeych.lyng.obj.*
|
||||
import net.sergeych.lyng.pacman.ImportManager
|
||||
import net.sergeych.lyng.pacman.ModuleBuilder
|
||||
import net.sergeych.lyng.stdlib_included.rootLyng
|
||||
import net.sergeych.lynon.ObjLynonClass
|
||||
import net.sergeych.mp_tools.globalDefer
|
||||
import kotlin.math.*
|
||||
|
||||
@Suppress("TYPE_INTERSECTION_AS_REIFIED_WARNING")
|
||||
@ -59,186 +58,173 @@ class Script(
|
||||
internal val rootScope: Scope = Scope(null).apply {
|
||||
ObjException.addExceptionsToContext(this)
|
||||
addConst("Unset", ObjUnset)
|
||||
addFn("print") {
|
||||
for ((i, a) in args.withIndex()) {
|
||||
if (i > 0) print(' ' + a.toString(this).value)
|
||||
else print(a.toString(this).value)
|
||||
addFn("print", fn = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj {
|
||||
for ((i, a) in scp.args.withIndex()) {
|
||||
if (i > 0) print(' ' + a.toString(scp).value)
|
||||
else print(a.toString(scp).value)
|
||||
}
|
||||
return ObjVoid
|
||||
}
|
||||
ObjVoid
|
||||
}
|
||||
addFn("println") {
|
||||
for ((i, a) in args.withIndex()) {
|
||||
if (i > 0) print(' ' + a.toString(this).value)
|
||||
else print(a.toString(this).value)
|
||||
})
|
||||
addFn("println", fn = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj {
|
||||
for ((i, a) in scp.args.withIndex()) {
|
||||
if (i > 0) print(' ' + a.toString(scp).value)
|
||||
else print(a.toString(scp).value)
|
||||
}
|
||||
println()
|
||||
return ObjVoid
|
||||
}
|
||||
println()
|
||||
ObjVoid
|
||||
}
|
||||
addFn("floor") {
|
||||
val x = args.firstAndOnly()
|
||||
(if (x is ObjInt) x
|
||||
else ObjReal(floor(x.toDouble())))
|
||||
}
|
||||
addFn("ceil") {
|
||||
val x = args.firstAndOnly()
|
||||
(if (x is ObjInt) x
|
||||
else ObjReal(ceil(x.toDouble())))
|
||||
}
|
||||
addFn("round") {
|
||||
val x = args.firstAndOnly()
|
||||
(if (x is ObjInt) x
|
||||
else ObjReal(round(x.toDouble())))
|
||||
}
|
||||
})
|
||||
addFn("floor", fn = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj {
|
||||
val x = scp.args.firstAndOnly()
|
||||
return (if (x is ObjInt) x
|
||||
else ObjReal(floor(x.toDouble())))
|
||||
}
|
||||
})
|
||||
addFn("ceil", fn = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj {
|
||||
val x = scp.args.firstAndOnly()
|
||||
return (if (x is ObjInt) x
|
||||
else ObjReal(ceil(x.toDouble())))
|
||||
}
|
||||
})
|
||||
addFn("round", fn = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj {
|
||||
val x = scp.args.firstAndOnly()
|
||||
return (if (x is ObjInt) x
|
||||
else ObjReal(round(x.toDouble())))
|
||||
}
|
||||
})
|
||||
|
||||
addFn("sin") {
|
||||
ObjReal(sin(args.firstAndOnly().toDouble()))
|
||||
}
|
||||
addFn("cos") {
|
||||
ObjReal(cos(args.firstAndOnly().toDouble()))
|
||||
}
|
||||
addFn("tan") {
|
||||
ObjReal(tan(args.firstAndOnly().toDouble()))
|
||||
}
|
||||
addFn("asin") {
|
||||
ObjReal(asin(args.firstAndOnly().toDouble()))
|
||||
}
|
||||
addFn("acos") {
|
||||
ObjReal(acos(args.firstAndOnly().toDouble()))
|
||||
}
|
||||
addFn("atan") {
|
||||
ObjReal(atan(args.firstAndOnly().toDouble()))
|
||||
}
|
||||
addFn("sin", fn = object : ScopeCallable { override suspend fun call(scp: Scope): Obj = ObjReal(sin(scp.args.firstAndOnly().toDouble())) })
|
||||
addFn("cos", fn = object : ScopeCallable { override suspend fun call(scp: Scope): Obj = ObjReal(cos(scp.args.firstAndOnly().toDouble())) })
|
||||
addFn("tan", fn = object : ScopeCallable { override suspend fun call(scp: Scope): Obj = ObjReal(tan(scp.args.firstAndOnly().toDouble())) })
|
||||
addFn("asin", fn = object : ScopeCallable { override suspend fun call(scp: Scope): Obj = ObjReal(asin(scp.args.firstAndOnly().toDouble())) })
|
||||
addFn("acos", fn = object : ScopeCallable { override suspend fun call(scp: Scope): Obj = ObjReal(acos(scp.args.firstAndOnly().toDouble())) })
|
||||
addFn("atan", fn = object : ScopeCallable { override suspend fun call(scp: Scope): Obj = ObjReal(atan(scp.args.firstAndOnly().toDouble())) })
|
||||
|
||||
addFn("sinh") {
|
||||
ObjReal(sinh(args.firstAndOnly().toDouble()))
|
||||
}
|
||||
addFn("cosh") {
|
||||
ObjReal(cosh(args.firstAndOnly().toDouble()))
|
||||
}
|
||||
addFn("tanh") {
|
||||
ObjReal(tanh(args.firstAndOnly().toDouble()))
|
||||
}
|
||||
addFn("asinh") {
|
||||
ObjReal(asinh(args.firstAndOnly().toDouble()))
|
||||
}
|
||||
addFn("acosh") {
|
||||
ObjReal(acosh(args.firstAndOnly().toDouble()))
|
||||
}
|
||||
addFn("atanh") {
|
||||
ObjReal(atanh(args.firstAndOnly().toDouble()))
|
||||
}
|
||||
addFn("sinh", fn = object : ScopeCallable { override suspend fun call(scp: Scope): Obj = ObjReal(sinh(scp.args.firstAndOnly().toDouble())) })
|
||||
addFn("cosh", fn = object : ScopeCallable { override suspend fun call(scp: Scope): Obj = ObjReal(cosh(scp.args.firstAndOnly().toDouble())) })
|
||||
addFn("tanh", fn = object : ScopeCallable { override suspend fun call(scp: Scope): Obj = ObjReal(tanh(scp.args.firstAndOnly().toDouble())) })
|
||||
addFn("asinh", fn = object : ScopeCallable { override suspend fun call(scp: Scope): Obj = ObjReal(asinh(scp.args.firstAndOnly().toDouble())) })
|
||||
addFn("acosh", fn = object : ScopeCallable { override suspend fun call(scp: Scope): Obj = ObjReal(acosh(scp.args.firstAndOnly().toDouble())) })
|
||||
addFn("atanh", fn = object : ScopeCallable { override suspend fun call(scp: Scope): Obj = ObjReal(atanh(scp.args.firstAndOnly().toDouble())) })
|
||||
|
||||
addFn("exp") {
|
||||
ObjReal(exp(args.firstAndOnly().toDouble()))
|
||||
}
|
||||
addFn("ln") {
|
||||
ObjReal(ln(args.firstAndOnly().toDouble()))
|
||||
}
|
||||
addFn("exp", fn = object : ScopeCallable { override suspend fun call(scp: Scope): Obj = ObjReal(exp(scp.args.firstAndOnly().toDouble())) })
|
||||
addFn("ln", fn = object : ScopeCallable { override suspend fun call(scp: Scope): Obj = ObjReal(ln(scp.args.firstAndOnly().toDouble())) })
|
||||
|
||||
addFn("log10") {
|
||||
ObjReal(log10(args.firstAndOnly().toDouble()))
|
||||
}
|
||||
addFn("log10", fn = object : ScopeCallable { override suspend fun call(scp: Scope): Obj = ObjReal(log10(scp.args.firstAndOnly().toDouble())) })
|
||||
|
||||
addFn("log2") {
|
||||
ObjReal(log2(args.firstAndOnly().toDouble()))
|
||||
}
|
||||
addFn("log2", fn = object : ScopeCallable { override suspend fun call(scp: Scope): Obj = ObjReal(log2(scp.args.firstAndOnly().toDouble())) })
|
||||
|
||||
addFn("pow") {
|
||||
requireExactCount(2)
|
||||
ObjReal(
|
||||
(args[0].toDouble()).pow(args[1].toDouble())
|
||||
)
|
||||
}
|
||||
addFn("sqrt") {
|
||||
ObjReal(
|
||||
sqrt(args.firstAndOnly().toDouble())
|
||||
)
|
||||
}
|
||||
addFn("abs") {
|
||||
val x = args.firstAndOnly()
|
||||
if (x is ObjInt) ObjInt(x.value.absoluteValue) else ObjReal(x.toDouble().absoluteValue)
|
||||
}
|
||||
addFn("pow", fn = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj {
|
||||
scp.requireExactCount(2)
|
||||
return ObjReal((scp.args[0].toDouble()).pow(scp.args[1].toDouble()))
|
||||
}
|
||||
})
|
||||
addFn("sqrt", fn = object : ScopeCallable { override suspend fun call(scp: Scope): Obj = ObjReal(sqrt(scp.args.firstAndOnly().toDouble())) })
|
||||
addFn("abs", fn = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj {
|
||||
val x = scp.args.firstAndOnly()
|
||||
return if (x is ObjInt) ObjInt(x.value.absoluteValue) else ObjReal(x.toDouble().absoluteValue)
|
||||
}
|
||||
})
|
||||
|
||||
addFnDoc(
|
||||
addFnDoc<Obj>(
|
||||
"clamp",
|
||||
doc = "Clamps the value within the specified range. If the value is outside the range, it is set to the nearest boundary. Respects inclusive/exclusive range ends.",
|
||||
params = listOf(ParamDoc("value"), ParamDoc("range")),
|
||||
moduleName = "lyng.stdlib"
|
||||
) {
|
||||
val value = requiredArg<Obj>(0)
|
||||
val range = requiredArg<ObjRange>(1)
|
||||
|
||||
var result = value
|
||||
if (range.start != null && !range.start.isNull) {
|
||||
if (result.compareTo(this, range.start) < 0) {
|
||||
result = range.start
|
||||
}
|
||||
}
|
||||
if (range.end != null && !range.end.isNull) {
|
||||
val cmp = range.end.compareTo(this, result)
|
||||
if (range.isEndInclusive) {
|
||||
if (cmp < 0) result = range.end
|
||||
} else {
|
||||
if (cmp <= 0) {
|
||||
if (range.end is ObjInt) {
|
||||
result = ObjInt.of(range.end.value - 1)
|
||||
} else if (range.end is ObjChar) {
|
||||
result = ObjChar((range.end.value.code - 1).toChar())
|
||||
} else {
|
||||
// For types where we can't easily find "previous" value (like Real),
|
||||
// we just return the exclusive boundary as a fallback.
|
||||
result = range.end
|
||||
moduleName = "lyng.stdlib",
|
||||
fn = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj {
|
||||
val value = scp.requiredArg<Obj>(0)
|
||||
val range = scp.requiredArg<ObjRange>(1)
|
||||
|
||||
var result = value
|
||||
if (range.start != null && !range.start.isNull) {
|
||||
if (result.compareTo(scp, range.start) < 0) {
|
||||
result = range.start
|
||||
}
|
||||
}
|
||||
if (range.end != null && !range.end.isNull) {
|
||||
val cmp = range.end.compareTo(scp, result)
|
||||
if (range.isEndInclusive) {
|
||||
if (cmp < 0) result = range.end
|
||||
} else {
|
||||
if (cmp <= 0) {
|
||||
if (range.end is ObjInt) {
|
||||
result = ObjInt.of(range.end.value - 1)
|
||||
} else if (range.end is ObjChar) {
|
||||
result = ObjChar((range.end.value.code - 1).toChar())
|
||||
} else {
|
||||
// For types where we can't easily find "previous" value (like Real),
|
||||
// we just return the exclusive boundary as a fallback.
|
||||
result = range.end
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
}
|
||||
result
|
||||
}
|
||||
)
|
||||
|
||||
addVoidFn("assert") {
|
||||
val cond = requiredArg<ObjBool>(0)
|
||||
val message = if (args.size > 1)
|
||||
": " + (args[1] as Statement).execute(this).toString(this).value
|
||||
else ""
|
||||
if (!cond.value == true)
|
||||
raiseError(ObjAssertionFailedException(this, "Assertion failed$message"))
|
||||
}
|
||||
addVoidFn("assert", fn = object : VoidScopeCallable {
|
||||
override suspend fun call(scp: Scope) {
|
||||
val cond = scp.requiredArg<ObjBool>(0)
|
||||
val message = if (scp.args.size > 1)
|
||||
": " + (scp.args[1] as Statement).execute(scp).toString(scp).value
|
||||
else ""
|
||||
if (!cond.value == true)
|
||||
scp.raiseError(ObjAssertionFailedException(scp, "Assertion failed$message"))
|
||||
}
|
||||
})
|
||||
|
||||
addVoidFn("assertEquals") {
|
||||
val a = requiredArg<Obj>(0)
|
||||
val b = requiredArg<Obj>(1)
|
||||
if (a.compareTo(this, b) != 0)
|
||||
raiseError(
|
||||
ObjAssertionFailedException(
|
||||
this,
|
||||
"Assertion failed: ${a.inspect(this)} == ${b.inspect(this)}"
|
||||
addVoidFn("assertEquals", fn = object : VoidScopeCallable {
|
||||
override suspend fun call(scp: Scope) {
|
||||
val a = scp.requiredArg<Obj>(0)
|
||||
val b = scp.requiredArg<Obj>(1)
|
||||
if (a.compareTo(scp, b) != 0)
|
||||
scp.raiseError(
|
||||
ObjAssertionFailedException(
|
||||
scp,
|
||||
"Assertion failed: ${a.inspect(scp)} == ${b.inspect(scp)}"
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
})
|
||||
// alias used in tests
|
||||
addVoidFn("assertEqual") {
|
||||
val a = requiredArg<Obj>(0)
|
||||
val b = requiredArg<Obj>(1)
|
||||
if (a.compareTo(this, b) != 0)
|
||||
raiseError(
|
||||
ObjAssertionFailedException(
|
||||
this,
|
||||
"Assertion failed: ${a.inspect(this)} == ${b.inspect(this)}"
|
||||
addVoidFn("assertEqual", fn = object : VoidScopeCallable {
|
||||
override suspend fun call(scp: Scope) {
|
||||
val a = scp.requiredArg<Obj>(0)
|
||||
val b = scp.requiredArg<Obj>(1)
|
||||
if (a.compareTo(scp, b) != 0)
|
||||
scp.raiseError(
|
||||
ObjAssertionFailedException(
|
||||
scp,
|
||||
"Assertion failed: ${a.inspect(scp)} == ${b.inspect(scp)}"
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
addVoidFn("assertNotEquals") {
|
||||
val a = requiredArg<Obj>(0)
|
||||
val b = requiredArg<Obj>(1)
|
||||
if (a.compareTo(this, b) == 0)
|
||||
raiseError(
|
||||
ObjAssertionFailedException(
|
||||
this,
|
||||
"Assertion failed: ${a.inspect(this)} != ${b.inspect(this)}"
|
||||
}
|
||||
})
|
||||
addVoidFn("assertNotEquals", fn = object : VoidScopeCallable {
|
||||
override suspend fun call(scp: Scope) {
|
||||
val a = scp.requiredArg<Obj>(0)
|
||||
val b = scp.requiredArg<Obj>(1)
|
||||
if (a.compareTo(scp, b) == 0)
|
||||
scp.raiseError(
|
||||
ObjAssertionFailedException(
|
||||
scp,
|
||||
"Assertion failed: ${a.inspect(scp)} != ${b.inspect(scp)}"
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
addFnDoc(
|
||||
}
|
||||
})
|
||||
addFnDoc<Obj>(
|
||||
"assertThrows",
|
||||
doc = """
|
||||
Asserts that the provided code block throws an exception, with or without exception:
|
||||
@ -249,83 +235,97 @@ class Script(
|
||||
If an expected exception class is provided,
|
||||
it checks that the thrown exception is of that class. If no expected class is provided, any exception
|
||||
will be accepted.
|
||||
""".trimIndent()
|
||||
) {
|
||||
val code: Statement
|
||||
val expectedClass: ObjClass?
|
||||
when (args.size) {
|
||||
1 -> {
|
||||
code = requiredArg<Statement>(0)
|
||||
expectedClass = null
|
||||
}
|
||||
""".trimIndent(),
|
||||
fn = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj {
|
||||
val code: Statement
|
||||
val expectedClass: ObjClass?
|
||||
when (scp.args.size) {
|
||||
1 -> {
|
||||
code = scp.requiredArg<Statement>(0)
|
||||
expectedClass = null
|
||||
}
|
||||
|
||||
2 -> {
|
||||
code = requiredArg<Statement>(1)
|
||||
expectedClass = requiredArg<ObjClass>(0)
|
||||
}
|
||||
2 -> {
|
||||
code = scp.requiredArg<Statement>(1)
|
||||
expectedClass = scp.requiredArg<ObjClass>(0)
|
||||
}
|
||||
|
||||
else -> raiseIllegalArgument("Expected 1 or 2 arguments, got ${args.size}")
|
||||
}
|
||||
val result = try {
|
||||
code.execute(this)
|
||||
null
|
||||
} catch (e: ExecutionError) {
|
||||
e.errorObject
|
||||
} catch (_: ScriptError) {
|
||||
ObjNull
|
||||
}
|
||||
if (result == null) raiseError(
|
||||
ObjAssertionFailedException(
|
||||
this,
|
||||
"Expected exception but nothing was thrown"
|
||||
)
|
||||
)
|
||||
expectedClass?.let {
|
||||
if (!result.isInstanceOf(it)) {
|
||||
val actual = if (result is ObjException) result.exceptionClass else result.objClass
|
||||
raiseError("Expected $it, got $actual")
|
||||
else -> scp.raiseIllegalArgument("Expected 1 or 2 arguments, got ${scp.args.size}")
|
||||
}
|
||||
val result = try {
|
||||
code.execute(scp)
|
||||
null
|
||||
} catch (e: ExecutionError) {
|
||||
e.errorObject
|
||||
} catch (_: ScriptError) {
|
||||
ObjNull
|
||||
}
|
||||
if (result == null) scp.raiseError(
|
||||
ObjAssertionFailedException(
|
||||
scp,
|
||||
"Expected exception but nothing was thrown"
|
||||
)
|
||||
)
|
||||
expectedClass?.let {
|
||||
if (!result.isInstanceOf(it)) {
|
||||
val actual = if (result is ObjException) result.exceptionClass else result.objClass
|
||||
scp.raiseError("Expected $it, got $actual")
|
||||
}
|
||||
}
|
||||
return result ?: ObjNull
|
||||
}
|
||||
}
|
||||
result
|
||||
}
|
||||
)
|
||||
|
||||
addFn("dynamic") {
|
||||
ObjDynamic.create(this, requireOnlyArg())
|
||||
}
|
||||
addFn("dynamic", fn = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj {
|
||||
return ObjDynamic.create(scp, scp.requireOnlyArg())
|
||||
}
|
||||
})
|
||||
|
||||
val root = this
|
||||
val mathClass = ObjClass("Math").apply {
|
||||
addFn("sqrt") {
|
||||
ObjReal(sqrt(args.firstAndOnly().toDouble()))
|
||||
}
|
||||
addFn("sqrt", fn = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj {
|
||||
return ObjReal(sqrt(scp.args.firstAndOnly().toDouble()))
|
||||
}
|
||||
})
|
||||
}
|
||||
addItem("Math", false, ObjInstance(mathClass).apply {
|
||||
instanceScope = Scope(root, thisObj = this)
|
||||
})
|
||||
|
||||
addFn("require") {
|
||||
val condition = requiredArg<ObjBool>(0)
|
||||
if (!condition.value) {
|
||||
var message = args.list.getOrNull(1)
|
||||
if (message is Statement) message = message.execute(this)
|
||||
raiseIllegalArgument(message?.toString() ?: "requirement not met")
|
||||
addFn("require", fn = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj {
|
||||
val condition = scp.requiredArg<ObjBool>(0)
|
||||
if (!condition.value) {
|
||||
var message = scp.args.list.getOrNull(1)
|
||||
if (message is Statement) message = message.execute(scp)
|
||||
scp.raiseIllegalArgument(message?.toString() ?: "requirement not met")
|
||||
}
|
||||
return ObjVoid
|
||||
}
|
||||
ObjVoid
|
||||
}
|
||||
addFn("check") {
|
||||
val condition = requiredArg<ObjBool>(0)
|
||||
if (!condition.value) {
|
||||
var message = args.list.getOrNull(1)
|
||||
if (message is Statement) message = message.execute(this)
|
||||
raiseIllegalState(message?.toString() ?: "check failed")
|
||||
})
|
||||
addFn("check", fn = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj {
|
||||
val condition = scp.requiredArg<ObjBool>(0)
|
||||
if (!condition.value) {
|
||||
var message = scp.args.list.getOrNull(1)
|
||||
if (message is Statement) message = message.execute(scp)
|
||||
scp.raiseIllegalState(message?.toString() ?: "check failed")
|
||||
}
|
||||
return ObjVoid
|
||||
}
|
||||
ObjVoid
|
||||
}
|
||||
addFn("traceScope") {
|
||||
this.trace(args.getOrNull(0)?.toString() ?: "")
|
||||
ObjVoid
|
||||
}
|
||||
})
|
||||
addFn("traceScope", fn = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj {
|
||||
scp.trace(scp.args.getOrNull(0)?.toString() ?: "")
|
||||
return ObjVoid
|
||||
}
|
||||
})
|
||||
|
||||
/*
|
||||
addVoidFn("delay") {
|
||||
val a = args.firstAndOnly()
|
||||
when (a) {
|
||||
@ -335,7 +335,7 @@ class Script(
|
||||
else -> raiseIllegalArgument("Expected Int, Real or Duration, got ${a.inspect(this)}")
|
||||
}
|
||||
}
|
||||
|
||||
*/
|
||||
addConst("Object", rootObjectType)
|
||||
addConst("Real", ObjReal.type)
|
||||
addConst("String", ObjString.type)
|
||||
@ -362,7 +362,7 @@ class Script(
|
||||
addConst("Flow", ObjFlow.type)
|
||||
|
||||
addConst("Regex", ObjRegex.type)
|
||||
|
||||
/*
|
||||
addFn("launch") {
|
||||
val callable = requireOnlyArg<Statement>()
|
||||
ObjDeferred(globalDefer {
|
||||
@ -380,7 +380,7 @@ class Script(
|
||||
// we'll need it for the producer
|
||||
ObjFlow(requireOnlyArg<Statement>(), this)
|
||||
}
|
||||
|
||||
*/
|
||||
val pi = ObjReal(PI)
|
||||
addConstDoc(
|
||||
name = "π",
|
||||
@ -403,60 +403,69 @@ class Script(
|
||||
addTextPackages(
|
||||
rootLyng
|
||||
)
|
||||
addPackage("lyng.buffer") {
|
||||
it.addConstDoc(
|
||||
name = "Buffer",
|
||||
value = ObjBuffer.type,
|
||||
doc = "Immutable sequence of bytes. Use for binary data and IO.",
|
||||
type = type("lyng.Class")
|
||||
)
|
||||
it.addConstDoc(
|
||||
name = "MutableBuffer",
|
||||
value = ObjMutableBuffer.type,
|
||||
doc = "Mutable byte buffer. Supports in-place modifications.",
|
||||
type = type("lyng.Class")
|
||||
)
|
||||
}
|
||||
addPackage("lyng.serialization") {
|
||||
it.addConstDoc(
|
||||
name = "Lynon",
|
||||
value = ObjLynonClass,
|
||||
doc = "Lynon serialization utilities: encode/decode data structures to a portable binary/text form.",
|
||||
type = type("lyng.Class")
|
||||
)
|
||||
}
|
||||
addPackage("lyng.time") {
|
||||
it.addConstDoc(
|
||||
name = "Instant",
|
||||
value = ObjInstant.type,
|
||||
doc = "Point in time (epoch-based).",
|
||||
type = type("lyng.Class")
|
||||
)
|
||||
it.addConstDoc(
|
||||
name = "DateTime",
|
||||
value = ObjDateTime.type,
|
||||
doc = "Point in time in a specific time zone.",
|
||||
type = type("lyng.Class")
|
||||
)
|
||||
it.addConstDoc(
|
||||
name = "Duration",
|
||||
value = ObjDuration.type,
|
||||
doc = "Time duration with millisecond precision.",
|
||||
type = type("lyng.Class")
|
||||
)
|
||||
it.addVoidFnDoc(
|
||||
"delay",
|
||||
doc = "Suspend for the given time. Accepts Duration, Int seconds, or Real seconds."
|
||||
) {
|
||||
val a = args.firstAndOnly()
|
||||
when (a) {
|
||||
is ObjInt -> delay(a.value * 1000)
|
||||
is ObjReal -> delay((a.value * 1000).roundToLong())
|
||||
is ObjDuration -> delay(a.duration)
|
||||
else -> raiseIllegalArgument("Expected Duration, Int or Real, got ${a.inspect(this)}")
|
||||
}
|
||||
addPackage("lyng.buffer", object : ModuleBuilder {
|
||||
override suspend fun build(ms: ModuleScope) {
|
||||
ms.addConstDoc(
|
||||
name = "Buffer",
|
||||
value = ObjBuffer.type,
|
||||
doc = "Immutable sequence of bytes. Use for binary data and IO.",
|
||||
type = type("lyng.Class")
|
||||
)
|
||||
ms.addConstDoc(
|
||||
name = "MutableBuffer",
|
||||
value = ObjMutableBuffer.type,
|
||||
doc = "Mutable byte buffer. Supports in-place modifications.",
|
||||
type = type("lyng.Class")
|
||||
)
|
||||
}
|
||||
}
|
||||
})
|
||||
addPackage("lyng.serialization", object : ModuleBuilder {
|
||||
override suspend fun build(ms: ModuleScope) {
|
||||
ms.addConstDoc(
|
||||
name = "Lynon",
|
||||
value = ObjLynonClass,
|
||||
doc = "Lynon serialization utilities: encode/decode data structures to a portable binary/text form.",
|
||||
type = type("lyng.Class")
|
||||
)
|
||||
}
|
||||
})
|
||||
addPackage("lyng.time", object : ModuleBuilder {
|
||||
override suspend fun build(ms: ModuleScope) {
|
||||
ms.addConstDoc(
|
||||
name = "Instant",
|
||||
value = ObjInstant.type,
|
||||
doc = "Point in time (epoch-based).",
|
||||
type = type("lyng.Class")
|
||||
)
|
||||
ms.addConstDoc(
|
||||
name = "DateTime",
|
||||
value = ObjDateTime.type,
|
||||
doc = "Point in time in a specific time zone.",
|
||||
type = type("lyng.Class")
|
||||
)
|
||||
ms.addConstDoc(
|
||||
name = "Duration",
|
||||
value = ObjDuration.type,
|
||||
doc = "Time duration with millisecond precision.",
|
||||
type = type("lyng.Class")
|
||||
)
|
||||
ms.addVoidFnDoc(
|
||||
"delay",
|
||||
doc = "Suspend for the given time. Accepts Duration, Int seconds, or Real seconds.",
|
||||
fn = object : VoidScopeCallable {
|
||||
override suspend fun call(scp: Scope) {
|
||||
val a = scp.args.firstAndOnly()
|
||||
when (a) {
|
||||
is ObjInt -> delay(a.value * 1000)
|
||||
is ObjReal -> delay((a.value * 1000).roundToLong())
|
||||
is ObjDuration -> delay(a.duration)
|
||||
else -> scp.raiseIllegalArgument("Expected Duration, Int or Real, got ${a.inspect(scp)}")
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -17,9 +17,7 @@
|
||||
|
||||
package net.sergeych.lyng.miniast
|
||||
|
||||
import net.sergeych.lyng.ModuleScope
|
||||
import net.sergeych.lyng.Scope
|
||||
import net.sergeych.lyng.Visibility
|
||||
import net.sergeych.lyng.*
|
||||
import net.sergeych.lyng.obj.Obj
|
||||
import net.sergeych.lyng.obj.ObjClass
|
||||
import net.sergeych.lyng.obj.ObjVoid
|
||||
@ -39,10 +37,10 @@ inline fun <reified T : Obj> Scope.addFnDoc(
|
||||
returns: TypeDoc? = null,
|
||||
tags: Map<String, List<String>> = emptyMap(),
|
||||
moduleName: String? = null,
|
||||
crossinline fn: suspend Scope.() -> T
|
||||
fn: ScopeCallable
|
||||
) {
|
||||
// Register runtime function(s)
|
||||
addFn(*names) { fn() }
|
||||
addFn(*names, fn = fn)
|
||||
// Determine module
|
||||
val mod = moduleName ?: findModuleNameOrUnknown()
|
||||
// Register docs once per name
|
||||
@ -56,7 +54,7 @@ inline fun Scope.addVoidFnDoc(
|
||||
doc: String,
|
||||
tags: Map<String, List<String>> = emptyMap(),
|
||||
moduleName: String? = null,
|
||||
crossinline fn: suspend Scope.() -> Unit
|
||||
fn: VoidScopeCallable
|
||||
) {
|
||||
addFnDoc<ObjVoid>(
|
||||
*names,
|
||||
@ -64,11 +62,14 @@ inline fun Scope.addVoidFnDoc(
|
||||
params = emptyList(),
|
||||
returns = null,
|
||||
tags = tags,
|
||||
moduleName = moduleName
|
||||
) {
|
||||
fn(this)
|
||||
ObjVoid
|
||||
}
|
||||
moduleName = moduleName,
|
||||
fn = object : ScopeCallable {
|
||||
override suspend fun call(sc: Scope): Obj {
|
||||
fn.call(sc)
|
||||
return ObjVoid
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
fun Scope.addConstDoc(
|
||||
@ -97,7 +98,7 @@ fun ObjClass.addFnDoc(
|
||||
visibility: Visibility = Visibility.Public,
|
||||
tags: Map<String, List<String>> = emptyMap(),
|
||||
moduleName: String? = null,
|
||||
code: suspend Scope.() -> Obj
|
||||
code: ScopeCallable
|
||||
) {
|
||||
// Register runtime method
|
||||
addFn(name, isOpen, visibility, code = code)
|
||||
@ -135,7 +136,7 @@ fun ObjClass.addClassFnDoc(
|
||||
isOpen: Boolean = false,
|
||||
tags: Map<String, List<String>> = emptyMap(),
|
||||
moduleName: String? = null,
|
||||
code: suspend Scope.() -> Obj
|
||||
code: ScopeCallable
|
||||
) {
|
||||
addClassFn(name, isOpen, code)
|
||||
BuiltinDocRegistry.module(moduleName ?: ownerModuleNameFromClassOrUnknown()) {
|
||||
@ -151,8 +152,8 @@ fun ObjClass.addPropertyDoc(
|
||||
type: TypeDoc? = null,
|
||||
visibility: Visibility = Visibility.Public,
|
||||
moduleName: String? = null,
|
||||
getter: (suspend Scope.() -> Obj)? = null,
|
||||
setter: (suspend Scope.(Obj) -> Unit)? = null
|
||||
getter: ScopeCallable? = null,
|
||||
setter: ScopeCallable? = null
|
||||
) {
|
||||
addProperty(name, getter, setter, visibility)
|
||||
BuiltinDocRegistry.module(moduleName ?: ownerModuleNameFromClassOrUnknown()) {
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2025 Sergey S. Chernov real.sergeych@gmail.com
|
||||
* Copyright 2026 Sergey S. Chernov real.sergeych@gmail.com
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@ -32,29 +32,41 @@ typealias DocCompiler = Compiler
|
||||
*/
|
||||
typealias Accessor = ObjRef
|
||||
|
||||
interface AccessorGetter {
|
||||
suspend fun call(scope: Scope): ObjRecord
|
||||
}
|
||||
|
||||
interface AccessorSetter {
|
||||
suspend fun call(scope: Scope, value: Obj)
|
||||
}
|
||||
|
||||
/** Lambda-based reference for edge cases that still construct access via lambdas. */
|
||||
private class LambdaRef(
|
||||
private val getterFn: suspend (Scope) -> ObjRecord,
|
||||
private val setterFn: (suspend (Pos, Scope, Obj) -> Unit)? = null
|
||||
private val getterFn: AccessorGetter,
|
||||
private val setterFn: AccessorSetter? = null
|
||||
) : ObjRef {
|
||||
override suspend fun get(scope: Scope): ObjRecord = getterFn(scope)
|
||||
override suspend fun get(scope: Scope): ObjRecord = getterFn.call(scope)
|
||||
override suspend fun setAt(pos: Pos, scope: Scope, newValue: Obj) {
|
||||
val s = setterFn ?: throw ScriptError(pos, "can't assign value")
|
||||
s(pos, scope, newValue)
|
||||
s.call(scope, newValue)
|
||||
}
|
||||
}
|
||||
|
||||
// Factory functions to preserve current call sites like `Accessor { ... }`
|
||||
fun Accessor(getter: suspend (Scope) -> ObjRecord): Accessor = LambdaRef(getter)
|
||||
fun Accessor(getter: AccessorGetter): Accessor = LambdaRef(getter)
|
||||
fun Accessor(
|
||||
getter: suspend (Scope) -> ObjRecord,
|
||||
setter: suspend (Scope, Obj) -> Unit
|
||||
): Accessor = LambdaRef(getter) { _, scope, value -> setter(scope, value) }
|
||||
getter: AccessorGetter,
|
||||
setter: AccessorSetter
|
||||
): Accessor = LambdaRef(getter, setter)
|
||||
|
||||
// Compatibility shims used throughout Compiler: `.getter(...)` and `.setter(pos)`
|
||||
val Accessor.getter: suspend (Scope) -> ObjRecord
|
||||
get() = { scope -> this.get(scope) }
|
||||
val Accessor.getter: AccessorGetter
|
||||
get() = object : AccessorGetter {
|
||||
override suspend fun call(scope: Scope): ObjRecord = this@getter.get(scope)
|
||||
}
|
||||
|
||||
fun Accessor.setter(pos: Pos): suspend (Scope, Obj) -> Unit = { scope, newValue ->
|
||||
this.setAt(pos, scope, newValue)
|
||||
fun Accessor.setter(pos: Pos): AccessorSetter = object : AccessorSetter {
|
||||
override suspend fun call(scope: Scope, value: Obj) {
|
||||
this@setter.setAt(pos, scope, value)
|
||||
}
|
||||
}
|
||||
@ -32,6 +32,14 @@ import net.sergeych.lynon.LynonDecoder
|
||||
import net.sergeych.lynon.LynonEncoder
|
||||
import net.sergeych.lynon.LynonType
|
||||
|
||||
fun interface OnNotFound {
|
||||
suspend fun call(): Obj?
|
||||
}
|
||||
|
||||
fun interface EnumerateCallback {
|
||||
suspend fun call(element: Obj): Boolean
|
||||
}
|
||||
|
||||
open class Obj {
|
||||
|
||||
open val isConst: Boolean = false
|
||||
@ -91,7 +99,7 @@ open class Obj {
|
||||
scope: Scope,
|
||||
name: String,
|
||||
args: Arguments = Arguments.EMPTY,
|
||||
onNotFoundResult: (suspend () -> Obj?)? = null
|
||||
onNotFoundResult: OnNotFound? = null
|
||||
): Obj {
|
||||
// 0. Prefer private member of current class context
|
||||
scope.currentClassCtx?.let { caller ->
|
||||
@ -100,7 +108,7 @@ open class Obj {
|
||||
if (rec.type == ObjRecord.Type.Property) {
|
||||
if (args.isEmpty()) return (rec.value as ObjProperty).callGetter(scope, this, caller)
|
||||
} else if (rec.type != ObjRecord.Type.Delegated) {
|
||||
return rec.value.invoke(scope, this, args, caller)
|
||||
return rec.value.invokeCallable(scope, this, args, caller)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -114,12 +122,12 @@ open class Obj {
|
||||
val decl = rec.declaringClass ?: cls
|
||||
val caller = scope.currentClassCtx
|
||||
if (!canAccessMember(rec.visibility, decl, caller, name))
|
||||
scope.raiseError(ObjIllegalAccessException(scope, "can't invoke ${name}: not visible (declared in ${decl.className}, caller ${caller?.className ?: "?"})"))
|
||||
scope.raiseError(ObjIllegalAccessException(scope, "can't invokeCallable ${name}: not visible (declared in ${decl.className}, caller ${caller?.className ?: "?"})"))
|
||||
|
||||
if (rec.type == ObjRecord.Type.Property) {
|
||||
if (args.isEmpty()) return (rec.value as ObjProperty).callGetter(scope, this, decl)
|
||||
} else if (rec.type != ObjRecord.Type.Delegated) {
|
||||
return rec.value.invoke(scope, this, args, decl)
|
||||
return rec.value.invokeCallable(scope, this, args, decl)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -130,7 +138,7 @@ open class Obj {
|
||||
if (extension.type == ObjRecord.Type.Property) {
|
||||
if (args.isEmpty()) return (extension.value as ObjProperty).callGetter(scope, this, extension.declaringClass)
|
||||
} else if (extension.type != ObjRecord.Type.Delegated) {
|
||||
return extension.value.invoke(scope, this, args)
|
||||
return extension.value.invokeCallable(scope, this, args)
|
||||
}
|
||||
}
|
||||
|
||||
@ -141,18 +149,18 @@ open class Obj {
|
||||
val decl = rec.declaringClass ?: cls
|
||||
val caller = scope.currentClassCtx
|
||||
if (!canAccessMember(rec.visibility, decl, caller, name))
|
||||
scope.raiseError(ObjIllegalAccessException(scope, "can't invoke ${name}: not visible (declared in ${decl.className}, caller ${caller?.className ?: "?"})"))
|
||||
scope.raiseError(ObjIllegalAccessException(scope, "can't invokeCallable ${name}: not visible (declared in ${decl.className}, caller ${caller?.className ?: "?"})"))
|
||||
|
||||
if (rec.type == ObjRecord.Type.Property) {
|
||||
if (args.isEmpty()) return (rec.value as ObjProperty).callGetter(scope, this, decl)
|
||||
} else if (rec.type != ObjRecord.Type.Delegated) {
|
||||
return rec.value.invoke(scope, this, args, decl)
|
||||
return rec.value.invokeCallable(scope, this, args, decl)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return onNotFoundResult?.invoke()
|
||||
return onNotFoundResult?.call()
|
||||
?: scope.raiseError(
|
||||
"no such member: $name on ${objClass.className}. Considered order: ${objClass.renderLinearization(true)}. " +
|
||||
"Tip: try this@Base.$name(...) or (obj as Base).$name(...) if ambiguous"
|
||||
@ -174,9 +182,11 @@ open class Obj {
|
||||
open suspend fun compareTo(scope: Scope, other: Obj): Int {
|
||||
if (other === this) return 0
|
||||
if (other === ObjNull || other === ObjUnset || other === ObjVoid) return 2
|
||||
return invokeInstanceMethod(scope, "compareTo", Arguments(other)) {
|
||||
scope.raiseNotImplemented("compareTo for ${objClass.className}")
|
||||
}.cast<ObjInt>(scope).toInt()
|
||||
return invokeInstanceMethod(scope, "compareTo", Arguments(other), onNotFoundResult = object : OnNotFound {
|
||||
override suspend fun call(): Obj? {
|
||||
scope.raiseNotImplemented("compareTo for ${objClass.className}")
|
||||
}
|
||||
}).toInt()
|
||||
}
|
||||
|
||||
open suspend fun equals(scope: Scope, other: Obj): Boolean {
|
||||
@ -202,16 +212,16 @@ open class Obj {
|
||||
*
|
||||
* IF callback returns false, iteration is stopped.
|
||||
*/
|
||||
open suspend fun enumerate(scope: Scope, callback: suspend (Obj) -> Boolean) {
|
||||
open suspend fun enumerate(scope: Scope, callback: EnumerateCallback) {
|
||||
val iterator = invokeInstanceMethod(scope, "iterator")
|
||||
val hasNext = iterator.getInstanceMethod(scope, "hasNext")
|
||||
val next = iterator.getInstanceMethod(scope, "next")
|
||||
var closeIt = false
|
||||
try {
|
||||
while (hasNext.invoke(scope, iterator).toBool()) {
|
||||
val nextValue = next.invoke(scope, iterator)
|
||||
while (hasNext.invokeCallable(scope, iterator).toBool()) {
|
||||
val nextValue = next.invokeCallable(scope, iterator)
|
||||
val shouldContinue = try {
|
||||
callback(nextValue)
|
||||
callback.call(nextValue)
|
||||
} catch (e: Exception) {
|
||||
// iteration aborted due to exception in callback
|
||||
closeIt = true
|
||||
@ -448,7 +458,7 @@ open class Obj {
|
||||
if (rec.visibility == Visibility.Private && !rec.isAbstract) {
|
||||
val resolved = resolveRecord(scope, rec, name, caller)
|
||||
if (resolved.type == ObjRecord.Type.Fun && resolved.value is Statement)
|
||||
return resolved.copy(value = resolved.value.invoke(scope, this, Arguments.EMPTY, caller))
|
||||
return resolved.copy(value = resolved.value.invokeCallable(scope, this, Arguments.EMPTY, caller))
|
||||
return resolved
|
||||
}
|
||||
}
|
||||
@ -462,7 +472,7 @@ open class Obj {
|
||||
val decl = rec.declaringClass ?: cls
|
||||
val resolved = resolveRecord(scope, rec, name, decl)
|
||||
if (resolved.type == ObjRecord.Type.Fun && resolved.value is Statement)
|
||||
return resolved.copy(value = resolved.value.invoke(scope, this, Arguments.EMPTY, decl))
|
||||
return resolved.copy(value = resolved.value.invokeCallable(scope, this, Arguments.EMPTY, decl))
|
||||
return resolved
|
||||
}
|
||||
}
|
||||
@ -472,7 +482,7 @@ open class Obj {
|
||||
if (extension != null) {
|
||||
val resolved = resolveRecord(scope, extension, name, extension.declaringClass)
|
||||
if (resolved.type == ObjRecord.Type.Fun && resolved.value is Statement)
|
||||
return resolved.copy(value = resolved.value.invoke(scope, this, Arguments.EMPTY, extension.declaringClass))
|
||||
return resolved.copy(value = resolved.value.invokeCallable(scope, this, Arguments.EMPTY, extension.declaringClass))
|
||||
return resolved
|
||||
}
|
||||
|
||||
@ -486,7 +496,7 @@ open class Obj {
|
||||
scope.raiseError(ObjIllegalAccessException(scope, "can't access field ${name}: not visible (declared in ${decl.className}, caller ${caller?.className ?: "?"})"))
|
||||
val resolved = resolveRecord(scope, rec, name, decl)
|
||||
if (resolved.type == ObjRecord.Type.Fun && resolved.value is Statement)
|
||||
return resolved.copy(value = resolved.value.invoke(scope, this, Arguments.EMPTY, decl))
|
||||
return resolved.copy(value = resolved.value.invokeCallable(scope, this, Arguments.EMPTY, decl))
|
||||
return resolved
|
||||
}
|
||||
}
|
||||
@ -502,13 +512,13 @@ open class Obj {
|
||||
val del = obj.delegate ?: scope.raiseError("Internal error: delegated property $name has no delegate")
|
||||
val th = if (this === ObjVoid) ObjNull else this
|
||||
val res = del.invokeInstanceMethod(scope, "getValue", Arguments(th, ObjString(name)), onNotFoundResult = {
|
||||
// If getValue not found, return a wrapper that calls invoke
|
||||
// If getValue not found, return a wrapper that calls invokeCallable
|
||||
object : Statement() {
|
||||
override val pos: Pos = Pos.builtIn
|
||||
override suspend fun execute(s: Scope): Obj {
|
||||
val th2 = if (s.thisObj === ObjVoid) ObjNull else s.thisObj
|
||||
val allArgs = (listOf(th2, ObjString(name)) + s.args.list).toTypedArray()
|
||||
return del.invokeInstanceMethod(s, "invoke", Arguments(*allArgs))
|
||||
return del.invokeInstanceMethod(s, "invokeCallable", Arguments(*allArgs))
|
||||
}
|
||||
}
|
||||
})
|
||||
@ -605,18 +615,20 @@ open class Obj {
|
||||
scope.raiseNotImplemented()
|
||||
}
|
||||
|
||||
suspend fun invoke(scope: Scope, thisObj: Obj, args: Arguments, declaringClass: ObjClass? = null): Obj =
|
||||
suspend fun invokeCallable(scope: Scope, thisObj: Obj, args: Arguments, declaringClass: ObjClass? = null): Obj =
|
||||
if (PerfFlags.SCOPE_POOL)
|
||||
scope.withChildFrame(args, newThisObj = thisObj) { child ->
|
||||
if (declaringClass != null) child.currentClassCtx = declaringClass
|
||||
callOn(child)
|
||||
}
|
||||
scope.withChildFrame(args, newThisObj = thisObj, block = object : ScopeBlock<Obj> {
|
||||
override suspend fun call(child: Scope): Obj {
|
||||
if (declaringClass != null) child.currentClassCtx = declaringClass
|
||||
return callOn(child)
|
||||
}
|
||||
})
|
||||
else
|
||||
callOn(scope.createChildScope(scope.pos, args = args, newThisObj = thisObj).also {
|
||||
if (declaringClass != null) it.currentClassCtx = declaringClass
|
||||
})
|
||||
|
||||
suspend fun invoke(scope: Scope, thisObj: Obj, vararg args: Obj): Obj =
|
||||
suspend fun invokeCallable(scope: Scope, thisObj: Obj, vararg args: Obj): Obj =
|
||||
callOn(
|
||||
scope.createChildScope(
|
||||
scope.pos,
|
||||
@ -625,7 +637,7 @@ open class Obj {
|
||||
)
|
||||
)
|
||||
|
||||
suspend fun invoke(scope: Scope, thisObj: Obj): Obj =
|
||||
suspend fun invokeCallable(scope: Scope, thisObj: Obj): Obj =
|
||||
callOn(
|
||||
scope.createChildScope(
|
||||
scope.pos,
|
||||
@ -634,7 +646,7 @@ open class Obj {
|
||||
)
|
||||
)
|
||||
|
||||
suspend fun invoke(scope: Scope, atPos: Pos, thisObj: Obj, args: Arguments): Obj =
|
||||
suspend fun invokeCallable(scope: Scope, atPos: Pos, thisObj: Obj, args: Arguments): Obj =
|
||||
callOn(scope.createChildScope(atPos, args = args, newThisObj = thisObj))
|
||||
|
||||
|
||||
@ -681,117 +693,138 @@ open class Obj {
|
||||
name = "toString",
|
||||
doc = "Returns a string representation of the object.",
|
||||
returns = type("lyng.String"),
|
||||
moduleName = "lyng.stdlib"
|
||||
) {
|
||||
thisObj.toString(this, true)
|
||||
}
|
||||
moduleName = "lyng.stdlib",
|
||||
code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj = scp.thisObj.toString(scp, true)
|
||||
}
|
||||
)
|
||||
addFnDoc(
|
||||
name = "inspect",
|
||||
doc = "Returns a detailed string representation for debugging.",
|
||||
returns = type("lyng.String"),
|
||||
moduleName = "lyng.stdlib"
|
||||
) {
|
||||
thisObj.inspect(this).toObj()
|
||||
}
|
||||
moduleName = "lyng.stdlib",
|
||||
code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj = scp.thisObj.inspect(scp).toObj()
|
||||
}
|
||||
)
|
||||
addFnDoc(
|
||||
name = "contains",
|
||||
doc = "Returns true if the object contains the given element.",
|
||||
params = listOf(ParamDoc("element")),
|
||||
returns = type("lyng.Bool"),
|
||||
moduleName = "lyng.stdlib"
|
||||
) {
|
||||
ObjBool(thisObj.contains(this, args.firstAndOnly()))
|
||||
}
|
||||
moduleName = "lyng.stdlib",
|
||||
code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj = ObjBool(scp.thisObj.contains(scp, scp.args.firstAndOnly()))
|
||||
}
|
||||
)
|
||||
// utilities
|
||||
addFnDoc(
|
||||
name = "let",
|
||||
doc = "Calls the specified function block with `this` value as its argument and returns its result.",
|
||||
params = listOf(ParamDoc("block")),
|
||||
moduleName = "lyng.stdlib"
|
||||
) {
|
||||
args.firstAndOnly().callOn(createChildScope(Arguments(thisObj)))
|
||||
}
|
||||
moduleName = "lyng.stdlib",
|
||||
code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj =
|
||||
scp.args.firstAndOnly().callOn(scp.createChildScope(Arguments(scp.thisObj)))
|
||||
}
|
||||
)
|
||||
addFnDoc(
|
||||
name = "apply",
|
||||
doc = "Calls the specified function block with `this` value as its receiver and returns `this` value.",
|
||||
params = listOf(ParamDoc("block")),
|
||||
moduleName = "lyng.stdlib"
|
||||
) {
|
||||
val body = args.firstAndOnly()
|
||||
(thisObj as? ObjInstance)?.let {
|
||||
body.callOn(ApplyScope(this, it.instanceScope))
|
||||
} ?: run {
|
||||
body.callOn(this)
|
||||
moduleName = "lyng.stdlib",
|
||||
code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj {
|
||||
val body = scp.args.firstAndOnly()
|
||||
(scp.thisObj as? ObjInstance)?.let {
|
||||
body.callOn(ApplyScope(scp, it.instanceScope))
|
||||
} ?: run {
|
||||
body.callOn(scp)
|
||||
}
|
||||
return scp.thisObj
|
||||
}
|
||||
}
|
||||
thisObj
|
||||
}
|
||||
)
|
||||
addFnDoc(
|
||||
name = "also",
|
||||
doc = "Calls the specified function block with `this` value as its argument and returns `this` value.",
|
||||
params = listOf(ParamDoc("block")),
|
||||
moduleName = "lyng.stdlib"
|
||||
) {
|
||||
args.firstAndOnly().callOn(createChildScope(Arguments(thisObj)))
|
||||
thisObj
|
||||
}
|
||||
moduleName = "lyng.stdlib",
|
||||
code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj {
|
||||
scp.args.firstAndOnly().callOn(scp.createChildScope(Arguments(scp.thisObj)))
|
||||
return scp.thisObj
|
||||
}
|
||||
}
|
||||
)
|
||||
addFnDoc(
|
||||
name = "run",
|
||||
doc = "Calls the specified function block with `this` value as its receiver and returns its result.",
|
||||
params = listOf(ParamDoc("block")),
|
||||
moduleName = "lyng.stdlib"
|
||||
) {
|
||||
args.firstAndOnly().callOn(this)
|
||||
}
|
||||
addFn("getAt") {
|
||||
requireExactCount(1)
|
||||
thisObj.getAt(this, requiredArg<Obj>(0))
|
||||
}
|
||||
addFn("putAt") {
|
||||
requireExactCount(2)
|
||||
val newValue = args[1]
|
||||
thisObj.putAt(this, requiredArg<Obj>(0), newValue)
|
||||
newValue
|
||||
}
|
||||
moduleName = "lyng.stdlib",
|
||||
code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj = scp.args.firstAndOnly().callOn(scp)
|
||||
}
|
||||
)
|
||||
addFn("getAt", code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj {
|
||||
scp.requireExactCount(1)
|
||||
return scp.thisObj.getAt(scp, scp.requiredArg<Obj>(0))
|
||||
}
|
||||
})
|
||||
addFn("putAt", code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj {
|
||||
scp.requireExactCount(2)
|
||||
val newValue = scp.args[1]
|
||||
scp.thisObj.putAt(scp, scp.requiredArg<Obj>(0), newValue)
|
||||
return newValue
|
||||
}
|
||||
})
|
||||
addFnDoc(
|
||||
name = "toJsonString",
|
||||
doc = "Encodes this object to a JSON string.",
|
||||
returns = type("lyng.String"),
|
||||
moduleName = "lyng.stdlib"
|
||||
) {
|
||||
thisObj.toJson(this).toString().toObj()
|
||||
}
|
||||
moduleName = "lyng.stdlib",
|
||||
code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj =
|
||||
scp.thisObj.toJson(scp).toString().toObj()
|
||||
}
|
||||
)
|
||||
addFnDoc(
|
||||
name = "clamp",
|
||||
doc = "Clamps this value within the specified range. If the value is outside the range, it is set to the nearest boundary. Respects inclusive/exclusive range ends.",
|
||||
params = listOf(ParamDoc("range")),
|
||||
moduleName = "lyng.stdlib"
|
||||
) {
|
||||
val range = requiredArg<ObjRange>(0)
|
||||
moduleName = "lyng.stdlib",
|
||||
code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj {
|
||||
val range = scp.requiredArg<ObjRange>(0)
|
||||
|
||||
var result = thisObj
|
||||
if (range.start != null && !range.start.isNull) {
|
||||
if (result.compareTo(this, range.start) < 0) {
|
||||
result = range.start
|
||||
}
|
||||
}
|
||||
if (range.end != null && !range.end.isNull) {
|
||||
val cmp = range.end.compareTo(this, result)
|
||||
if (range.isEndInclusive) {
|
||||
if (cmp < 0) result = range.end
|
||||
} else {
|
||||
if (cmp <= 0) {
|
||||
if (range.end is ObjInt) {
|
||||
result = ObjInt.of(range.end.value - 1)
|
||||
} else if (range.end is ObjChar) {
|
||||
result = ObjChar((range.end.value.code - 1).toChar())
|
||||
} else {
|
||||
result = range.end
|
||||
var result = scp.thisObj
|
||||
if (range.start != null && !range.start.isNull) {
|
||||
if (result.compareTo(scp, range.start) < 0) {
|
||||
result = range.start
|
||||
}
|
||||
}
|
||||
if (range.end != null && !range.end.isNull) {
|
||||
val cmp = range.end.compareTo(scp, result)
|
||||
if (range.isEndInclusive) {
|
||||
if (cmp < 0) result = range.end
|
||||
} else {
|
||||
if (cmp <= 0) {
|
||||
if (range.end is ObjInt) {
|
||||
result = ObjInt.of(range.end.value - 1)
|
||||
} else if (range.end is ObjChar) {
|
||||
result = ObjChar((range.end.value.code - 1).toChar())
|
||||
} else {
|
||||
result = range.end
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
}
|
||||
result
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@ -919,7 +952,7 @@ object ObjUnset : Obj() {
|
||||
scope: Scope,
|
||||
name: String,
|
||||
args: Arguments,
|
||||
onNotFoundResult: (suspend () -> Obj?)?
|
||||
onNotFoundResult: OnNotFound?
|
||||
): Obj = scope.raiseUnset()
|
||||
|
||||
override suspend fun getAt(scope: Scope, index: Obj): Obj = scope.raiseUnset()
|
||||
|
||||
@ -17,6 +17,8 @@
|
||||
|
||||
package net.sergeych.lyng.obj
|
||||
|
||||
import net.sergeych.lyng.Scope
|
||||
import net.sergeych.lyng.ScopeCallable
|
||||
import net.sergeych.lyng.miniast.*
|
||||
|
||||
val ObjArray by lazy {
|
||||
@ -31,8 +33,11 @@ val ObjArray by lazy {
|
||||
name = "iterator",
|
||||
doc = "Iterator over elements of this array using its indexer.",
|
||||
returns = TypeGenericDoc(type("lyng.Iterator"), listOf(type("lyng.Any"))),
|
||||
moduleName = "lyng.stdlib"
|
||||
) { ObjArrayIterator(thisObj).also { it.init(this) } }
|
||||
moduleName = "lyng.stdlib",
|
||||
code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj = ObjArrayIterator(scp.thisObj).also { it.init(scp) }
|
||||
}
|
||||
)
|
||||
|
||||
addFnDoc(
|
||||
name = "contains",
|
||||
@ -40,26 +45,31 @@ val ObjArray by lazy {
|
||||
params = listOf(ParamDoc("element")),
|
||||
returns = type("lyng.Bool"),
|
||||
isOpen = true,
|
||||
moduleName = "lyng.stdlib"
|
||||
) {
|
||||
val obj = args.firstAndOnly()
|
||||
for (i in 0..<thisObj.invokeInstanceMethod(this, "size").toInt()) {
|
||||
if (thisObj.getAt(this, ObjInt(i.toLong())).compareTo(this, obj) == 0) return@addFnDoc ObjTrue
|
||||
moduleName = "lyng.stdlib",
|
||||
code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj {
|
||||
val obj = scp.args.firstAndOnly()
|
||||
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(
|
||||
name = "last",
|
||||
doc = "The last element of this array.",
|
||||
type = type("lyng.Any"),
|
||||
moduleName = "lyng.stdlib",
|
||||
getter = {
|
||||
this.thisObj.invokeInstanceMethod(
|
||||
this,
|
||||
"getAt",
|
||||
(this.thisObj.invokeInstanceMethod(this, "size").toInt() - 1).toObj()
|
||||
)
|
||||
getter = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj {
|
||||
return scp.thisObj.invokeInstanceMethod(
|
||||
scp,
|
||||
"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).",
|
||||
type = type("lyng.Int"),
|
||||
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(
|
||||
@ -76,7 +88,9 @@ val ObjArray by lazy {
|
||||
doc = "Range of valid indices for this array.",
|
||||
type = type("lyng.Range"),
|
||||
moduleName = "lyng.stdlib",
|
||||
getter = { ObjRange(0.toObj(), this.thisObj.invokeInstanceMethod(this, "size"), false) }
|
||||
getter = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj = ObjRange(0.toObj(), scp.thisObj.invokeInstanceMethod(scp, "size"), false)
|
||||
}
|
||||
)
|
||||
|
||||
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.",
|
||||
params = listOf(ParamDoc("target")),
|
||||
returns = type("lyng.Int"),
|
||||
moduleName = "lyng.stdlib"
|
||||
) {
|
||||
val target = args.firstAndOnly()
|
||||
var low = 0
|
||||
var high = thisObj.invokeInstanceMethod(this, "size").toInt() - 1
|
||||
moduleName = "lyng.stdlib",
|
||||
code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj {
|
||||
val target = scp.args.firstAndOnly()
|
||||
var low = 0
|
||||
var high = scp.thisObj.invokeInstanceMethod(scp, "size").toInt() - 1
|
||||
|
||||
while (low <= high) {
|
||||
val mid = (low + high) / 2
|
||||
val midVal = thisObj.getAt(this, ObjInt(mid.toLong()))
|
||||
while (low <= high) {
|
||||
val mid = (low + high) / 2
|
||||
val midVal = scp.thisObj.getAt(scp, ObjInt(mid.toLong()))
|
||||
|
||||
val cmp = midVal.compareTo(this, target)
|
||||
when {
|
||||
cmp == 0 -> return@addFnDoc (mid).toObj()
|
||||
cmp > 0 -> high = mid - 1
|
||||
else -> low = mid + 1
|
||||
val cmp = midVal.compareTo(scp, target)
|
||||
when {
|
||||
cmp == 0 -> return (mid).toObj()
|
||||
cmp > 0 -> high = mid - 1
|
||||
else -> low = mid + 1
|
||||
}
|
||||
}
|
||||
|
||||
return (-low - 1).toObj()
|
||||
}
|
||||
}
|
||||
|
||||
(-low - 1).toObj()
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2025 Sergey S. Chernov real.sergeych@gmail.com
|
||||
* Copyright 2026 Sergey S. Chernov real.sergeych@gmail.com
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@ -18,6 +18,7 @@
|
||||
package net.sergeych.lyng.obj
|
||||
|
||||
import net.sergeych.lyng.Scope
|
||||
import net.sergeych.lyng.ScopeCallable
|
||||
|
||||
class ObjArrayIterator(val array: Obj) : Obj() {
|
||||
|
||||
@ -35,16 +36,20 @@ class ObjArrayIterator(val array: Obj) : Obj() {
|
||||
companion object {
|
||||
val type by lazy {
|
||||
ObjClass("ArrayIterator", ObjIterator).apply {
|
||||
addFn("next") {
|
||||
val self = thisAs<ObjArrayIterator>()
|
||||
if (self.nextIndex < self.lastIndex) {
|
||||
self.array.invokeInstanceMethod(this, "getAt", (self.nextIndex++).toObj())
|
||||
} else raiseError(ObjIterationFinishedException(this))
|
||||
}
|
||||
addFn("hasNext") {
|
||||
val self = thisAs<ObjArrayIterator>()
|
||||
if (self.nextIndex < self.lastIndex) ObjTrue else ObjFalse
|
||||
}
|
||||
addFn("next", code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj {
|
||||
val self = scp.thisAs<ObjArrayIterator>()
|
||||
return if (self.nextIndex < self.lastIndex) {
|
||||
self.array.invokeInstanceMethod(scp, "getAt", (self.nextIndex++).toObj())
|
||||
} else scp.raiseError(ObjIterationFinishedException(scp))
|
||||
}
|
||||
})
|
||||
addFn("hasNext", code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj {
|
||||
val self = scp.thisAs<ObjArrayIterator>()
|
||||
return if (self.nextIndex < self.lastIndex) ObjTrue else ObjFalse
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -19,6 +19,7 @@ package net.sergeych.lyng.obj
|
||||
|
||||
import net.sergeych.bintools.toDump
|
||||
import net.sergeych.lyng.Scope
|
||||
import net.sergeych.lyng.ScopeCallable
|
||||
import net.sergeych.lyng.miniast.addPropertyDoc
|
||||
import net.sergeych.lyng.miniast.type
|
||||
import net.sergeych.lynon.BitArray
|
||||
@ -35,29 +36,37 @@ class ObjBitBuffer(val bitArray: BitArray) : Obj() {
|
||||
val type = object: ObjClass("BitBuffer", ObjArray) {
|
||||
|
||||
}.apply {
|
||||
addFn("toBuffer") {
|
||||
requireNoArgs()
|
||||
ObjBuffer(thisAs<ObjBitBuffer>().bitArray.asUByteArray())
|
||||
}
|
||||
addFn("toDump") {
|
||||
requireNoArgs()
|
||||
ObjString(
|
||||
thisAs<ObjBitBuffer>().bitArray.asUByteArray().toDump()
|
||||
)
|
||||
}
|
||||
addFn("toBuffer", code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj {
|
||||
scp.requireNoArgs()
|
||||
return ObjBuffer(scp.thisAs<ObjBitBuffer>().bitArray.asUByteArray())
|
||||
}
|
||||
})
|
||||
addFn("toDump", code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj {
|
||||
scp.requireNoArgs()
|
||||
return ObjString(
|
||||
scp.thisAs<ObjBitBuffer>().bitArray.asUByteArray().toByteArray().toDump()
|
||||
)
|
||||
}
|
||||
})
|
||||
addPropertyDoc(
|
||||
name = "size",
|
||||
doc = "Size of the bit buffer in bits.",
|
||||
type = type("lyng.Int"),
|
||||
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(
|
||||
name = "sizeInBytes",
|
||||
doc = "Size of the bit buffer in full bytes (rounded up).",
|
||||
type = type("lyng.Int"),
|
||||
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())
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@ -23,6 +23,8 @@ import net.sergeych.bintools.decodeHex
|
||||
import net.sergeych.bintools.encodeToHex
|
||||
import net.sergeych.bintools.toDump
|
||||
import net.sergeych.lyng.Scope
|
||||
import net.sergeych.lyng.ScopeCallable
|
||||
import net.sergeych.lyng.miniast.addFnDoc
|
||||
import net.sergeych.lyng.miniast.addPropertyDoc
|
||||
import net.sergeych.lyng.miniast.type
|
||||
import net.sergeych.lynon.BitArray
|
||||
@ -169,51 +171,85 @@ open class ObjBuffer(val byteArray: UByteArray) : Obj() {
|
||||
})
|
||||
|
||||
}.apply {
|
||||
addClassFn("decodeBase64") {
|
||||
ObjBuffer(requireOnlyArg<Obj>().toString().decodeBase64Url().asUByteArray())
|
||||
}
|
||||
addClassFn("decodeHex") {
|
||||
ObjBuffer(requireOnlyArg<Obj>().toString().decodeHex().asUByteArray())
|
||||
}
|
||||
addClassFn("decodeBase64", code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj =
|
||||
ObjBuffer(scp.requireOnlyArg<Obj>().toString().decodeBase64Url().asUByteArray())
|
||||
})
|
||||
addClassFn("decodeHex", code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj =
|
||||
ObjBuffer(scp.requireOnlyArg<Obj>().toString().decodeHex().asUByteArray())
|
||||
})
|
||||
addPropertyDoc(
|
||||
name = "size",
|
||||
doc = "Number of bytes in this buffer.",
|
||||
type = type("lyng.Int"),
|
||||
moduleName = "lyng.stdlib",
|
||||
getter = { (this.thisObj as ObjBuffer).byteArray.size.toObj() }
|
||||
getter = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj = (scp.thisObj as ObjBuffer).byteArray.size.toObj()
|
||||
}
|
||||
)
|
||||
addPropertyDoc(
|
||||
name = "hex",
|
||||
doc = "Hexadecimal string representation of the buffer.",
|
||||
type = type("lyng.String"),
|
||||
moduleName = "lyng.stdlib",
|
||||
getter = { thisAs<ObjBuffer>().hex.toObj() }
|
||||
getter = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj = scp.thisAs<ObjBuffer>().hex.toObj()
|
||||
}
|
||||
)
|
||||
addPropertyDoc(
|
||||
name = "base64",
|
||||
doc = "Base64 (URL-safe) string representation of the buffer.",
|
||||
type = type("lyng.String"),
|
||||
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))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -18,6 +18,7 @@
|
||||
package net.sergeych.lyng.obj
|
||||
|
||||
import net.sergeych.lyng.Scope
|
||||
import net.sergeych.lyng.ScopeCallable
|
||||
import net.sergeych.lyng.miniast.addPropertyDoc
|
||||
import net.sergeych.lyng.miniast.type
|
||||
|
||||
@ -52,14 +53,16 @@ class ObjChar(val value: Char): Obj() {
|
||||
doc = "Unicode code point (UTF-16 code unit) of this character.",
|
||||
type = type("lyng.Int"),
|
||||
moduleName = "lyng.stdlib",
|
||||
getter = { ObjInt((this.thisObj as ObjChar).value.code.toLong()) }
|
||||
getter = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj = ObjInt((scp.thisObj as ObjChar).value.code.toLong())
|
||||
}
|
||||
)
|
||||
addFn("isDigit") {
|
||||
thisAs<ObjChar>().value.isDigit().toObj()
|
||||
}
|
||||
addFn("isSpace") {
|
||||
thisAs<ObjChar>().value.isWhitespace().toObj()
|
||||
}
|
||||
addFn("isDigit", code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj = scp.thisAs<ObjChar>().value.isDigit().toObj()
|
||||
})
|
||||
addFn("isSpace", code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj = scp.thisAs<ObjChar>().value.isWhitespace().toObj()
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -40,14 +40,18 @@ val ObjClassType by lazy {
|
||||
doc = "Full name of this class including package if available.",
|
||||
type = type("lyng.String"),
|
||||
moduleName = "lyng.stdlib",
|
||||
getter = { (this.thisObj as ObjClass).classNameObj }
|
||||
getter = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj = (scp.thisObj as ObjClass).classNameObj
|
||||
}
|
||||
)
|
||||
addPropertyDoc(
|
||||
name = "name",
|
||||
doc = "Simple name of this class (without package).",
|
||||
type = type("lyng.String"),
|
||||
moduleName = "lyng.stdlib",
|
||||
getter = { (this.thisObj as ObjClass).classNameObj }
|
||||
getter = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj = (scp.thisObj as ObjClass).classNameObj
|
||||
}
|
||||
)
|
||||
|
||||
addPropertyDoc(
|
||||
@ -55,16 +59,18 @@ val ObjClassType by lazy {
|
||||
doc = "Declared instance fields of this class and its ancestors (C3 order), without duplicates.",
|
||||
type = TypeGenericDoc(type("lyng.List"), listOf(type("lyng.String"))),
|
||||
moduleName = "lyng.stdlib",
|
||||
getter = {
|
||||
val cls = this.thisObj as ObjClass
|
||||
val seen = hashSetOf<String>()
|
||||
val names = mutableListOf<Obj>()
|
||||
for (c in cls.mro) {
|
||||
for ((n, rec) in c.members) {
|
||||
if (rec.value !is Statement && seen.add(n)) names += ObjString(n)
|
||||
getter = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj {
|
||||
val cls = scp.thisObj as ObjClass
|
||||
val seen = hashSetOf<String>()
|
||||
val names = mutableListOf<Obj>()
|
||||
for (c in cls.mro) {
|
||||
for ((n, rec) in c.members) {
|
||||
if (rec.value !is Statement && seen.add(n)) names += ObjString(n)
|
||||
}
|
||||
}
|
||||
return ObjList(names.toMutableList())
|
||||
}
|
||||
ObjList(names.toMutableList())
|
||||
}
|
||||
)
|
||||
|
||||
@ -73,16 +79,18 @@ val ObjClassType by lazy {
|
||||
doc = "Declared instance methods of this class and its ancestors (C3 order), without duplicates.",
|
||||
type = TypeGenericDoc(type("lyng.List"), listOf(type("lyng.String"))),
|
||||
moduleName = "lyng.stdlib",
|
||||
getter = {
|
||||
val cls = this.thisObj as ObjClass
|
||||
val seen = hashSetOf<String>()
|
||||
val names = mutableListOf<Obj>()
|
||||
for (c in cls.mro) {
|
||||
for ((n, rec) in c.members) {
|
||||
if (rec.value is Statement && seen.add(n)) names += ObjString(n)
|
||||
getter = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj {
|
||||
val cls = scp.thisObj as ObjClass
|
||||
val seen = hashSetOf<String>()
|
||||
val names = mutableListOf<Obj>()
|
||||
for (c in cls.mro) {
|
||||
for ((n, rec) in c.members) {
|
||||
if (rec.value is Statement && seen.add(n)) names += ObjString(n)
|
||||
}
|
||||
}
|
||||
return ObjList(names.toMutableList())
|
||||
}
|
||||
ObjList(names.toMutableList())
|
||||
}
|
||||
)
|
||||
|
||||
@ -91,13 +99,16 @@ val ObjClassType by lazy {
|
||||
doc = "Lookup a member by name in this class (including ancestors) and return it, or null if absent.",
|
||||
params = listOf(ParamDoc("name", type("lyng.String"))),
|
||||
returns = type("lyng.Any", nullable = true),
|
||||
moduleName = "lyng.stdlib"
|
||||
) {
|
||||
val cls = thisAs<ObjClass>()
|
||||
val name = requiredArg<ObjString>(0).value
|
||||
val rec = cls.getInstanceMemberOrNull(name)
|
||||
rec?.value ?: ObjNull
|
||||
}
|
||||
moduleName = "lyng.stdlib",
|
||||
code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj {
|
||||
val cls = scp.thisAs<ObjClass>()
|
||||
val name = scp.requiredArg<ObjString>(0).value
|
||||
val rec = cls.getInstanceMemberOrNull(name)
|
||||
return rec?.value ?: ObjNull
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@ -547,9 +558,9 @@ open class ObjClass(
|
||||
isClosed: Boolean = false,
|
||||
isOverride: Boolean = false,
|
||||
pos: Pos = Pos.builtIn,
|
||||
code: (suspend Scope.() -> Obj)? = null
|
||||
code: ScopeCallable? = null
|
||||
) {
|
||||
val stmt = code?.let { statement { it() } } ?: ObjNull
|
||||
val stmt = code?.let { statement(pos, f = it) } ?: ObjNull
|
||||
createField(
|
||||
name, stmt, isMutable, visibility, writeVisibility, pos, declaringClass,
|
||||
isAbstract = isAbstract, isClosed = isClosed, isOverride = isOverride,
|
||||
@ -561,8 +572,8 @@ open class ObjClass(
|
||||
|
||||
fun addProperty(
|
||||
name: String,
|
||||
getter: (suspend Scope.() -> Obj)? = null,
|
||||
setter: (suspend Scope.(Obj) -> Unit)? = null,
|
||||
getter: ScopeCallable? = null,
|
||||
setter: ScopeCallable? = null,
|
||||
visibility: Visibility = Visibility.Public,
|
||||
writeVisibility: Visibility? = null,
|
||||
declaringClass: ObjClass? = this,
|
||||
@ -572,8 +583,8 @@ open class ObjClass(
|
||||
pos: Pos = Pos.builtIn,
|
||||
prop: ObjProperty? = null
|
||||
) {
|
||||
val g = getter?.let { statement { it() } }
|
||||
val s = setter?.let { statement { it(requiredArg(0)); ObjVoid } }
|
||||
val g = getter?.let { statement(pos, f = it) }
|
||||
val s = setter?.let { statement(pos, f = it) }
|
||||
val finalProp = prop ?: if (isAbstract) ObjNull else ObjProperty(name, g, s)
|
||||
createField(
|
||||
name, finalProp, false, visibility, writeVisibility, pos, declaringClass,
|
||||
@ -583,8 +594,8 @@ open class ObjClass(
|
||||
}
|
||||
|
||||
fun addClassConst(name: String, value: Obj) = createClassField(name, value)
|
||||
fun addClassFn(name: String, isOpen: Boolean = false, code: suspend Scope.() -> Obj) {
|
||||
createClassField(name, statement { code() }, isOpen, type = ObjRecord.Type.Fun)
|
||||
fun addClassFn(name: String, isOpen: Boolean = false, code: ScopeCallable) {
|
||||
createClassField(name, statement(f = code), isOpen, type = ObjRecord.Type.Fun)
|
||||
}
|
||||
|
||||
|
||||
@ -694,25 +705,25 @@ open class ObjClass(
|
||||
|
||||
override suspend fun invokeInstanceMethod(
|
||||
scope: Scope, name: String, args: Arguments,
|
||||
onNotFoundResult: (suspend () -> Obj?)?
|
||||
onNotFoundResult: OnNotFound?
|
||||
): Obj {
|
||||
getInstanceMemberOrNull(name)?.let { rec ->
|
||||
val decl = rec.declaringClass ?: findDeclaringClassOf(name) ?: this
|
||||
if (rec.type == ObjRecord.Type.Delegated) {
|
||||
val del = rec.delegate ?: scope.raiseError("Internal error: delegated member $name has no delegate")
|
||||
val allArgs = (listOf(this, ObjString(name)) + args.list).toTypedArray()
|
||||
return del.invokeInstanceMethod(scope, "invoke", Arguments(*allArgs), onNotFoundResult = {
|
||||
return del.invokeInstanceMethod(scope, "invokeCallable", Arguments(*allArgs), onNotFoundResult = {
|
||||
// Fallback: property delegation
|
||||
val propVal = del.invokeInstanceMethod(scope, "getValue", Arguments(this, ObjString(name)))
|
||||
propVal.invoke(scope, this, args, decl)
|
||||
propVal.invokeCallable(scope, this, args, decl)
|
||||
})
|
||||
}
|
||||
if (rec.type == ObjRecord.Type.Fun) {
|
||||
return rec.value.invoke(scope, this, args, decl)
|
||||
return rec.value.invokeCallable(scope, this, args, decl)
|
||||
} else {
|
||||
// Resolved field or property value
|
||||
val resolved = readField(scope, name)
|
||||
return resolved.value.invoke(scope, this, args, decl)
|
||||
return resolved.value.invokeCallable(scope, this, args, decl)
|
||||
}
|
||||
}
|
||||
return super.invokeInstanceMethod(scope, name, args, onNotFoundResult)
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2025 Sergey S. Chernov real.sergeych@gmail.com
|
||||
* Copyright 2026 Sergey S. Chernov real.sergeych@gmail.com
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@ -19,6 +19,7 @@ package net.sergeych.lyng.obj
|
||||
|
||||
import kotlinx.coroutines.CompletableDeferred
|
||||
import net.sergeych.lyng.Scope
|
||||
import net.sergeych.lyng.ScopeCallable
|
||||
import net.sergeych.lyng.miniast.ParamDoc
|
||||
import net.sergeych.lyng.miniast.addFnDoc
|
||||
import net.sergeych.lyng.miniast.type
|
||||
@ -38,11 +39,14 @@ class ObjCompletableDeferred(val completableDeferred: CompletableDeferred<Obj>):
|
||||
doc = "Complete this deferred with the given value. Subsequent calls have no effect.",
|
||||
params = listOf(ParamDoc("value")),
|
||||
returns = type("lyng.Void"),
|
||||
moduleName = "lyng.stdlib"
|
||||
) {
|
||||
thisAs<ObjCompletableDeferred>().completableDeferred.complete(args.firstAndOnly())
|
||||
ObjVoid
|
||||
}
|
||||
moduleName = "lyng.stdlib",
|
||||
code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj {
|
||||
scp.thisAs<ObjCompletableDeferred>().completableDeferred.complete(scp.args.firstAndOnly())
|
||||
return ObjVoid
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -21,6 +21,7 @@ import kotlinx.datetime.*
|
||||
import kotlinx.serialization.json.JsonElement
|
||||
import kotlinx.serialization.json.JsonPrimitive
|
||||
import net.sergeych.lyng.Scope
|
||||
import net.sergeych.lyng.ScopeCallable
|
||||
import net.sergeych.lyng.Statement
|
||||
import net.sergeych.lyng.miniast.addClassFnDoc
|
||||
import net.sergeych.lyng.miniast.addFnDoc
|
||||
@ -54,7 +55,9 @@ class ObjDateTime(val instant: Instant, val timeZone: TimeZone) : Obj() {
|
||||
}
|
||||
if (rec.type == ObjRecord.Type.Fun || rec.value is Statement) {
|
||||
val s = rec.value as Statement
|
||||
return ObjRecord(net.sergeych.lyng.statement { s.execute(this.createChildScope(newThisObj = this@ObjDateTime)) }, rec.isMutable)
|
||||
return ObjRecord(net.sergeych.lyng.statement(f = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj = s.execute(scp.createChildScope(newThisObj = this@ObjDateTime))
|
||||
}), rec.isMutable)
|
||||
}
|
||||
return resolveRecord(scope, rec, name, rec.declaringClass ?: cls)
|
||||
}
|
||||
@ -172,122 +175,129 @@ class ObjDateTime(val instant: Instant, val timeZone: TimeZone) : Obj() {
|
||||
}
|
||||
}.apply {
|
||||
addPropertyDoc("year", "The year component.", type("lyng.Int"), moduleName = "lyng.time",
|
||||
getter = { thisAs<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",
|
||||
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",
|
||||
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",
|
||||
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",
|
||||
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",
|
||||
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",
|
||||
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",
|
||||
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",
|
||||
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") {
|
||||
ObjInstant(thisAs<ObjDateTime>().instant)
|
||||
}
|
||||
addFnDoc("toEpochSeconds", "Return the number of full seconds since the Unix epoch (UTC).", returns = type("lyng.Int"), moduleName = "lyng.time") {
|
||||
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") {
|
||||
thisAs<ObjDateTime>().toRFC3339().toObj()
|
||||
}
|
||||
addFnDoc("toSortableString", "Alias to toRFC3339.", returns = type("lyng.String"), moduleName = "lyng.time") {
|
||||
thisAs<ObjDateTime>().toRFC3339().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("toInstant", "Convert this localized date time back to an absolute Instant.", returns = type("lyng.Instant"), moduleName = "lyng.time",
|
||||
code = object : ScopeCallable { override suspend fun call(scp: Scope): Obj = ObjInstant(scp.thisAs<ObjDateTime>().instant) })
|
||||
addFnDoc("toEpochSeconds", "Return the number of full seconds since the Unix epoch (UTC).", returns = type("lyng.Int"), moduleName = "lyng.time",
|
||||
code = object : ScopeCallable { override suspend fun call(scp: Scope): Obj = scp.thisAs<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("toSortableString", "Alias to toRFC3339.", returns = type("lyng.String"), moduleName = "lyng.time",
|
||||
code = object : ScopeCallable { override suspend fun call(scp: Scope): Obj = scp.thisAs<ObjDateTime>().toRFC3339().toObj() })
|
||||
|
||||
addFnDoc("toEpochMilliseconds", "Return the number of milliseconds since the Unix epoch (UTC).", returns = type("lyng.Int"), moduleName = "lyng.time",
|
||||
code = object : ScopeCallable { override suspend fun call(scp: Scope): Obj = scp.thisAs<ObjDateTime>().instant.toEpochMilliseconds().toObj() })
|
||||
addFnDoc("toTimeZone", "Return a new DateTime representing the same instant but in a different time zone. " +
|
||||
"Accepts a timezone ID string (e.g., 'UTC', '+02:00') or an integer offset in seconds.",
|
||||
params = listOf(net.sergeych.lyng.miniast.ParamDoc("tz", type = type("lyng.Any"))),
|
||||
returns = type("lyng.DateTime"), moduleName = "lyng.time") {
|
||||
val tz = when (val a = args.list.getOrNull(0)) {
|
||||
is ObjString -> TimeZone.of(a.value)
|
||||
is ObjInt -> UtcOffset(seconds = a.value.toInt()).asTimeZone()
|
||||
else -> raiseIllegalArgument("invalid timezone: $a")
|
||||
}
|
||||
ObjDateTime(thisAs<ObjDateTime>().instant, tz)
|
||||
}
|
||||
addFnDoc("toUTC", "Shortcut to convert this date time to the UTC time zone.", returns = type("lyng.DateTime"), moduleName = "lyng.time") {
|
||||
ObjDateTime(thisAs<ObjDateTime>().instant, TimeZone.UTC)
|
||||
}
|
||||
returns = type("lyng.DateTime"), moduleName = "lyng.time",
|
||||
code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj {
|
||||
val tz = when (val a = scp.args.list.getOrNull(0)) {
|
||||
is ObjString -> TimeZone.of(a.value)
|
||||
is ObjInt -> UtcOffset(seconds = a.value.toInt()).asTimeZone()
|
||||
else -> scp.raiseIllegalArgument("invalid timezone: $a")
|
||||
}
|
||||
return ObjDateTime(scp.thisAs<ObjDateTime>().instant, tz)
|
||||
}
|
||||
})
|
||||
addFnDoc("toUTC", "Shortcut to convert this date time to the UTC time zone.", returns = type("lyng.DateTime"), moduleName = "lyng.time",
|
||||
code = object : ScopeCallable { override suspend fun call(scp: Scope): Obj = ObjDateTime(scp.thisAs<ObjDateTime>().instant, TimeZone.UTC) })
|
||||
|
||||
addFnDoc("addMonths", "Return a new DateTime with the specified number of months added (or subtracted if negative). " +
|
||||
"Normalizes the day of month if necessary (e.g., Jan 31 + 1 month = Feb 28/29).",
|
||||
params = listOf(net.sergeych.lyng.miniast.ParamDoc("months", type = type("lyng.Int"))),
|
||||
returns = type("lyng.DateTime"), moduleName = "lyng.time") {
|
||||
val n = args.list.getOrNull(0)?.toInt() ?: 0
|
||||
val res = thisAs<ObjDateTime>().instant.plus(n, DateTimeUnit.MONTH, thisAs<ObjDateTime>().timeZone)
|
||||
ObjDateTime(res, thisAs<ObjDateTime>().timeZone)
|
||||
}
|
||||
returns = type("lyng.DateTime"), moduleName = "lyng.time",
|
||||
code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj {
|
||||
val n = scp.args.list.getOrNull(0)?.toInt() ?: 0
|
||||
val res = scp.thisAs<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).",
|
||||
params = listOf(net.sergeych.lyng.miniast.ParamDoc("years", type = type("lyng.Int"))),
|
||||
returns = type("lyng.DateTime"), moduleName = "lyng.time") {
|
||||
val n = args.list.getOrNull(0)?.toInt() ?: 0
|
||||
val res = thisAs<ObjDateTime>().instant.plus(n, DateTimeUnit.YEAR, thisAs<ObjDateTime>().timeZone)
|
||||
ObjDateTime(res, thisAs<ObjDateTime>().timeZone)
|
||||
}
|
||||
returns = type("lyng.DateTime"), moduleName = "lyng.time",
|
||||
code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj {
|
||||
val n = scp.args.list.getOrNull(0)?.toInt() ?: 0
|
||||
val res = scp.thisAs<ObjDateTime>().instant.plus(n, DateTimeUnit.YEAR, scp.thisAs<ObjDateTime>().timeZone)
|
||||
return ObjDateTime(res, scp.thisAs<ObjDateTime>().timeZone)
|
||||
}
|
||||
})
|
||||
|
||||
addClassFn("now") {
|
||||
val tz = when (val a = args.list.getOrNull(0)) {
|
||||
null -> TimeZone.currentSystemDefault()
|
||||
is ObjString -> TimeZone.of(a.value)
|
||||
is ObjInt -> UtcOffset(seconds = a.value.toInt()).asTimeZone()
|
||||
else -> raiseIllegalArgument("invalid timezone: $a")
|
||||
addClassFn("now", code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj {
|
||||
val tz = when (val a = scp.args.list.getOrNull(0)) {
|
||||
null -> TimeZone.currentSystemDefault()
|
||||
is ObjString -> TimeZone.of(a.value)
|
||||
is ObjInt -> UtcOffset(seconds = a.value.toInt()).asTimeZone()
|
||||
else -> scp.raiseIllegalArgument("invalid timezone: $a")
|
||||
}
|
||||
return ObjDateTime(kotlin.time.Clock.System.now(), tz)
|
||||
}
|
||||
ObjDateTime(kotlin.time.Clock.System.now(), tz)
|
||||
}
|
||||
})
|
||||
|
||||
addClassFnDoc("parseRFC3339",
|
||||
"Parse an RFC3339 string into a DateTime object. " +
|
||||
"Note: if the string does not specify a timezone, UTC is assumed.",
|
||||
params = listOf(net.sergeych.lyng.miniast.ParamDoc("string", type = type("lyng.String"))),
|
||||
returns = type("lyng.DateTime"),
|
||||
moduleName = "lyng.time") {
|
||||
val s = (args.firstAndOnly() as ObjString).value
|
||||
// kotlinx-datetime's Instant.parse handles RFC3339
|
||||
// But we want to preserve the offset if present for DateTime.
|
||||
// However, Instant.parse("...") always gives an Instant.
|
||||
// If we want the specific offset from the string, we might need a more complex parse.
|
||||
// For now, let's stick to parsing it as Instant and converting to UTC or specified TZ.
|
||||
// Actually, if the string has an offset, Instant.parse handles it but returns UTC instant.
|
||||
|
||||
// Let's try to detect if there is an offset in the string.
|
||||
// If not, use UTC.
|
||||
val instant = Instant.parse(s)
|
||||
|
||||
// RFC3339 can have Z or +/-HH:mm or +/-HHmm or +/-HH
|
||||
val tz = try {
|
||||
if (s.endsWith("Z", ignoreCase = true)) {
|
||||
TimeZone.of("Z")
|
||||
} else {
|
||||
// Look for the last + or - which is likely the start of the offset
|
||||
val lastPlus = s.lastIndexOf('+')
|
||||
val lastMinus = s.lastIndexOf('-')
|
||||
val offsetStart = if (lastPlus > lastMinus) lastPlus else lastMinus
|
||||
if (offsetStart > s.lastIndexOf('T')) {
|
||||
// Likely an offset
|
||||
val offsetStr = s.substring(offsetStart)
|
||||
TimeZone.of(offsetStr)
|
||||
} else {
|
||||
moduleName = "lyng.time",
|
||||
code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj {
|
||||
val s = (scp.args.firstAndOnly() as ObjString).value
|
||||
// kotlinx-datetime's Instant.parse handles RFC3339
|
||||
// But we want to preserve the offset if present for DateTime.
|
||||
// However, Instant.parse("...") always gives an Instant.
|
||||
// If we want the specific offset from the string, we might need a more complex parse.
|
||||
// For now, let's stick to parsing it as Instant and converting to UTC or specified TZ.
|
||||
// Actually, if the string has an offset, Instant.parse handles it but returns UTC instant.
|
||||
|
||||
// Let's try to detect if there is an offset in the string.
|
||||
// If not, use UTC.
|
||||
val instant = Instant.parse(s)
|
||||
|
||||
// RFC3339 can have Z or +/-HH:mm or +/-HHmm or +/-HH
|
||||
val tz = try {
|
||||
if (s.endsWith("Z", ignoreCase = true)) {
|
||||
TimeZone.of("Z")
|
||||
} else {
|
||||
// Look for the last + or - which is likely the start of the offset
|
||||
val lastPlus = s.lastIndexOf('+')
|
||||
val lastMinus = s.lastIndexOf('-')
|
||||
val offsetStart = if (lastPlus > lastMinus) lastPlus else lastMinus
|
||||
if (offsetStart > s.lastIndexOf('T')) {
|
||||
// Likely an offset
|
||||
val offsetStr = s.substring(offsetStart)
|
||||
TimeZone.of(offsetStr)
|
||||
} else {
|
||||
TimeZone.UTC
|
||||
}
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
TimeZone.UTC
|
||||
}
|
||||
return ObjDateTime(instant, tz)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
TimeZone.UTC
|
||||
}
|
||||
|
||||
ObjDateTime(instant, tz)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -19,6 +19,7 @@ package net.sergeych.lyng.obj
|
||||
|
||||
import kotlinx.coroutines.Deferred
|
||||
import net.sergeych.lyng.Scope
|
||||
import net.sergeych.lyng.ScopeCallable
|
||||
import net.sergeych.lyng.miniast.addFnDoc
|
||||
import net.sergeych.lyng.miniast.addPropertyDoc
|
||||
import net.sergeych.lyng.miniast.type
|
||||
@ -37,23 +38,30 @@ open class ObjDeferred(val deferred: Deferred<Obj>): Obj() {
|
||||
name = "await",
|
||||
doc = "Suspend until completion and return the result value (or throw if failed).",
|
||||
returns = type("lyng.Any"),
|
||||
moduleName = "lyng.stdlib"
|
||||
) { thisAs<ObjDeferred>().deferred.await() }
|
||||
moduleName = "lyng.stdlib",
|
||||
code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj = scp.thisAs<ObjDeferred>().deferred.await()
|
||||
}
|
||||
)
|
||||
addPropertyDoc(
|
||||
name = "isCompleted",
|
||||
doc = "Whether this deferred has completed (successfully or with an error).",
|
||||
type = type("lyng.Bool"),
|
||||
moduleName = "lyng.stdlib",
|
||||
getter = { thisAs<ObjDeferred>().deferred.isCompleted.toObj() }
|
||||
getter = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj = scp.thisAs<ObjDeferred>().deferred.isCompleted.toObj()
|
||||
}
|
||||
)
|
||||
addPropertyDoc(
|
||||
name = "isActive",
|
||||
doc = "Whether this deferred is currently active (not completed and not cancelled).",
|
||||
type = type("lyng.Bool"),
|
||||
moduleName = "lyng.stdlib",
|
||||
getter = {
|
||||
val d = thisAs<ObjDeferred>().deferred
|
||||
(d.isActive || (!d.isCompleted && !d.isCancelled)).toObj()
|
||||
getter = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj {
|
||||
val d = scp.thisAs<ObjDeferred>().deferred
|
||||
return (d.isActive || (!d.isCompleted && !d.isCancelled)).toObj()
|
||||
}
|
||||
}
|
||||
)
|
||||
addPropertyDoc(
|
||||
@ -61,7 +69,9 @@ open class ObjDeferred(val deferred: Deferred<Obj>): Obj() {
|
||||
doc = "Whether this deferred was cancelled.",
|
||||
type = type("lyng.Bool"),
|
||||
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()
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@ -18,6 +18,7 @@
|
||||
package net.sergeych.lyng.obj
|
||||
|
||||
import net.sergeych.lyng.Scope
|
||||
import net.sergeych.lyng.ScopeCallable
|
||||
import net.sergeych.lyng.miniast.addPropertyDoc
|
||||
import net.sergeych.lyng.miniast.type
|
||||
import kotlin.time.Duration
|
||||
@ -79,42 +80,54 @@ class ObjDuration(val duration: Duration) : Obj() {
|
||||
doc = "Return this duration as a real number of days.",
|
||||
type = type("lyng.Real"),
|
||||
moduleName = "lyng.time",
|
||||
getter = { thisAs<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(
|
||||
name = "hours",
|
||||
doc = "Return this duration as a real number of hours.",
|
||||
type = type("lyng.Real"),
|
||||
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(
|
||||
name = "minutes",
|
||||
doc = "Return this duration as a real number of minutes.",
|
||||
type = type("lyng.Real"),
|
||||
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(
|
||||
name = "seconds",
|
||||
doc = "Return this duration as a real number of seconds.",
|
||||
type = type("lyng.Real"),
|
||||
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(
|
||||
name = "milliseconds",
|
||||
doc = "Return this duration as a real number of milliseconds.",
|
||||
type = type("lyng.Real"),
|
||||
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(
|
||||
name = "microseconds",
|
||||
doc = "Return this duration as a real number of microseconds.",
|
||||
type = type("lyng.Real"),
|
||||
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
|
||||
|
||||
@ -123,7 +136,9 @@ class ObjDuration(val duration: Duration) : Obj() {
|
||||
doc = "Construct a `Duration` equal to this integer number of seconds.",
|
||||
type = type("lyng.Duration"),
|
||||
moduleName = "lyng.time",
|
||||
getter = { ObjDuration(thisAs<ObjInt>().value.seconds) }
|
||||
getter = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj = ObjDuration(scp.thisAs<ObjInt>().value.seconds)
|
||||
}
|
||||
)
|
||||
|
||||
ObjInt.type.addPropertyDoc(
|
||||
@ -131,14 +146,18 @@ class ObjDuration(val duration: Duration) : Obj() {
|
||||
doc = "Construct a `Duration` equal to this integer number of seconds.",
|
||||
type = type("lyng.Duration"),
|
||||
moduleName = "lyng.time",
|
||||
getter = { ObjDuration(thisAs<ObjInt>().value.seconds) }
|
||||
getter = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj = ObjDuration(scp.thisAs<ObjInt>().value.seconds)
|
||||
}
|
||||
)
|
||||
ObjInt.type.addPropertyDoc(
|
||||
name = "milliseconds",
|
||||
doc = "Construct a `Duration` equal to this integer number of milliseconds.",
|
||||
type = type("lyng.Duration"),
|
||||
moduleName = "lyng.time",
|
||||
getter = { ObjDuration(thisAs<ObjInt>().value.milliseconds) }
|
||||
getter = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj = ObjDuration(scp.thisAs<ObjInt>().value.milliseconds)
|
||||
}
|
||||
)
|
||||
|
||||
ObjInt.type.addPropertyDoc(
|
||||
@ -146,14 +165,18 @@ class ObjDuration(val duration: Duration) : Obj() {
|
||||
doc = "Construct a `Duration` equal to this integer number of milliseconds.",
|
||||
type = type("lyng.Duration"),
|
||||
moduleName = "lyng.time",
|
||||
getter = { ObjDuration(thisAs<ObjInt>().value.milliseconds) }
|
||||
getter = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj = ObjDuration(scp.thisAs<ObjInt>().value.milliseconds)
|
||||
}
|
||||
)
|
||||
ObjReal.type.addPropertyDoc(
|
||||
name = "seconds",
|
||||
doc = "Construct a `Duration` equal to this real number of seconds.",
|
||||
type = type("lyng.Duration"),
|
||||
moduleName = "lyng.time",
|
||||
getter = { ObjDuration(thisAs<ObjReal>().value.seconds) }
|
||||
getter = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj = ObjDuration(scp.thisAs<ObjReal>().value.seconds)
|
||||
}
|
||||
)
|
||||
|
||||
ObjReal.type.addPropertyDoc(
|
||||
@ -161,7 +184,9 @@ class ObjDuration(val duration: Duration) : Obj() {
|
||||
doc = "Construct a `Duration` equal to this real number of seconds.",
|
||||
type = type("lyng.Duration"),
|
||||
moduleName = "lyng.time",
|
||||
getter = { ObjDuration(thisAs<ObjReal>().value.seconds) }
|
||||
getter = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj = ObjDuration(scp.thisAs<ObjReal>().value.seconds)
|
||||
}
|
||||
)
|
||||
|
||||
ObjReal.type.addPropertyDoc(
|
||||
@ -169,14 +194,18 @@ class ObjDuration(val duration: Duration) : Obj() {
|
||||
doc = "Construct a `Duration` equal to this real number of milliseconds.",
|
||||
type = type("lyng.Duration"),
|
||||
moduleName = "lyng.time",
|
||||
getter = { ObjDuration(thisAs<ObjReal>().value.milliseconds) }
|
||||
getter = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj = ObjDuration(scp.thisAs<ObjReal>().value.milliseconds)
|
||||
}
|
||||
)
|
||||
ObjReal.type.addPropertyDoc(
|
||||
name = "millisecond",
|
||||
doc = "Construct a `Duration` equal to this real number of milliseconds.",
|
||||
type = type("lyng.Duration"),
|
||||
moduleName = "lyng.time",
|
||||
getter = { ObjDuration(thisAs<ObjReal>().value.milliseconds) }
|
||||
getter = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj = ObjDuration(scp.thisAs<ObjReal>().value.milliseconds)
|
||||
}
|
||||
)
|
||||
|
||||
ObjInt.type.addPropertyDoc(
|
||||
@ -184,84 +213,108 @@ class ObjDuration(val duration: Duration) : Obj() {
|
||||
doc = "Construct a `Duration` equal to this integer number of minutes.",
|
||||
type = type("lyng.Duration"),
|
||||
moduleName = "lyng.time",
|
||||
getter = { ObjDuration(thisAs<ObjInt>().value.minutes) }
|
||||
getter = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj = ObjDuration(scp.thisAs<ObjInt>().value.minutes)
|
||||
}
|
||||
)
|
||||
ObjReal.type.addPropertyDoc(
|
||||
name = "minutes",
|
||||
doc = "Construct a `Duration` equal to this real number of minutes.",
|
||||
type = type("lyng.Duration"),
|
||||
moduleName = "lyng.time",
|
||||
getter = { ObjDuration(thisAs<ObjReal>().value.minutes) }
|
||||
getter = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj = ObjDuration(scp.thisAs<ObjReal>().value.minutes)
|
||||
}
|
||||
)
|
||||
ObjInt.type.addPropertyDoc(
|
||||
name = "minute",
|
||||
doc = "Construct a `Duration` equal to this integer number of minutes.",
|
||||
type = type("lyng.Duration"),
|
||||
moduleName = "lyng.time",
|
||||
getter = { ObjDuration(thisAs<ObjInt>().value.minutes) }
|
||||
getter = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj = ObjDuration(scp.thisAs<ObjInt>().value.minutes)
|
||||
}
|
||||
)
|
||||
ObjReal.type.addPropertyDoc(
|
||||
name = "minute",
|
||||
doc = "Construct a `Duration` equal to this real number of minutes.",
|
||||
type = type("lyng.Duration"),
|
||||
moduleName = "lyng.time",
|
||||
getter = { ObjDuration(thisAs<ObjReal>().value.minutes) }
|
||||
getter = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj = ObjDuration(scp.thisAs<ObjReal>().value.minutes)
|
||||
}
|
||||
)
|
||||
ObjInt.type.addPropertyDoc(
|
||||
name = "hours",
|
||||
doc = "Construct a `Duration` equal to this integer number of hours.",
|
||||
type = type("lyng.Duration"),
|
||||
moduleName = "lyng.time",
|
||||
getter = { ObjDuration(thisAs<ObjInt>().value.hours) }
|
||||
getter = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj = ObjDuration(scp.thisAs<ObjInt>().value.hours)
|
||||
}
|
||||
)
|
||||
ObjReal.type.addPropertyDoc(
|
||||
name = "hours",
|
||||
doc = "Construct a `Duration` equal to this real number of hours.",
|
||||
type = type("lyng.Duration"),
|
||||
moduleName = "lyng.time",
|
||||
getter = { ObjDuration(thisAs<ObjReal>().value.hours) }
|
||||
getter = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj = ObjDuration(scp.thisAs<ObjReal>().value.hours)
|
||||
}
|
||||
)
|
||||
ObjInt.type.addPropertyDoc(
|
||||
name = "hour",
|
||||
doc = "Construct a `Duration` equal to this integer number of hours.",
|
||||
type = type("lyng.Duration"),
|
||||
moduleName = "lyng.time",
|
||||
getter = { ObjDuration(thisAs<ObjInt>().value.hours) }
|
||||
getter = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj = ObjDuration(scp.thisAs<ObjInt>().value.hours)
|
||||
}
|
||||
)
|
||||
ObjReal.type.addPropertyDoc(
|
||||
name = "hour",
|
||||
doc = "Construct a `Duration` equal to this real number of hours.",
|
||||
type = type("lyng.Duration"),
|
||||
moduleName = "lyng.time",
|
||||
getter = { ObjDuration(thisAs<ObjReal>().value.hours) }
|
||||
getter = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj = ObjDuration(scp.thisAs<ObjReal>().value.hours)
|
||||
}
|
||||
)
|
||||
ObjInt.type.addPropertyDoc(
|
||||
name = "days",
|
||||
doc = "Construct a `Duration` equal to this integer number of days.",
|
||||
type = type("lyng.Duration"),
|
||||
moduleName = "lyng.time",
|
||||
getter = { ObjDuration(thisAs<ObjInt>().value.days) }
|
||||
getter = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj = ObjDuration(scp.thisAs<ObjInt>().value.days)
|
||||
}
|
||||
)
|
||||
ObjReal.type.addPropertyDoc(
|
||||
name = "days",
|
||||
doc = "Construct a `Duration` equal to this real number of days.",
|
||||
type = type("lyng.Duration"),
|
||||
moduleName = "lyng.time",
|
||||
getter = { ObjDuration(thisAs<ObjReal>().value.days) }
|
||||
getter = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj = ObjDuration(scp.thisAs<ObjReal>().value.days)
|
||||
}
|
||||
)
|
||||
ObjInt.type.addPropertyDoc(
|
||||
name = "day",
|
||||
doc = "Construct a `Duration` equal to this integer number of days.",
|
||||
type = type("lyng.Duration"),
|
||||
moduleName = "lyng.time",
|
||||
getter = { ObjDuration(thisAs<ObjInt>().value.days) }
|
||||
getter = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj = ObjDuration(scp.thisAs<ObjInt>().value.days)
|
||||
}
|
||||
)
|
||||
ObjReal.type.addPropertyDoc(
|
||||
name = "day",
|
||||
doc = "Construct a `Duration` equal to this real number of days.",
|
||||
type = type("lyng.Duration"),
|
||||
moduleName = "lyng.time",
|
||||
getter = { ObjDuration(thisAs<ObjReal>().value.days) }
|
||||
getter = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj = ObjDuration(scp.thisAs<ObjReal>().value.days)
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2025 Sergey S. Chernov real.sergeych@gmail.com
|
||||
* Copyright 2026 Sergey S. Chernov real.sergeych@gmail.com
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@ -17,31 +17,32 @@
|
||||
|
||||
package net.sergeych.lyng.obj
|
||||
|
||||
import net.sergeych.lyng.Arguments
|
||||
import net.sergeych.lyng.ClosureScope
|
||||
import net.sergeych.lyng.Scope
|
||||
import net.sergeych.lyng.Statement
|
||||
import net.sergeych.lyng.*
|
||||
|
||||
class ObjDynamicContext(val delegate: ObjDynamic) : Obj() {
|
||||
override val objClass: ObjClass get() = type
|
||||
|
||||
companion object {
|
||||
val type = ObjClass("DelegateContext").apply {
|
||||
addFn("get") {
|
||||
val d = thisAs<ObjDynamicContext>().delegate
|
||||
if (d.readCallback != null)
|
||||
raiseIllegalState("get already defined")
|
||||
d.readCallback = requireOnlyArg()
|
||||
ObjVoid
|
||||
}
|
||||
addFn("get", code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj {
|
||||
val d = scp.thisAs<ObjDynamicContext>().delegate
|
||||
if (d.readCallback != null)
|
||||
scp.raiseIllegalState("get already defined")
|
||||
d.readCallback = scp.requireOnlyArg()
|
||||
return ObjVoid
|
||||
}
|
||||
})
|
||||
|
||||
addFn("set") {
|
||||
val d = thisAs<ObjDynamicContext>().delegate
|
||||
if (d.writeCallback != null)
|
||||
raiseIllegalState("set already defined")
|
||||
d.writeCallback = requireOnlyArg()
|
||||
ObjVoid
|
||||
}
|
||||
addFn("set", code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj {
|
||||
val d = scp.thisAs<ObjDynamicContext>().delegate
|
||||
if (d.writeCallback != null)
|
||||
scp.raiseIllegalState("set already defined")
|
||||
d.writeCallback = scp.requireOnlyArg()
|
||||
return ObjVoid
|
||||
}
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
@ -81,11 +82,11 @@ open class ObjDynamic(var readCallback: Statement? = null, var writeCallback: St
|
||||
scope: Scope,
|
||||
name: String,
|
||||
args: Arguments,
|
||||
onNotFoundResult: (suspend () -> Obj?)?
|
||||
onNotFoundResult: OnNotFound?
|
||||
): Obj {
|
||||
val execBase = builderScope?.let { ClosureScope(scope, it) } ?: scope
|
||||
val over = readCallback?.execute(execBase.createChildScope(Arguments(ObjString(name))))
|
||||
return over?.invoke(scope, scope.thisObj, args)
|
||||
return over?.invokeCallable(scope, scope.thisObj, args)
|
||||
?: super.invokeInstanceMethod(scope, name, args, onNotFoundResult)
|
||||
}
|
||||
|
||||
|
||||
@ -35,6 +35,7 @@ package net.sergeych.lyng.obj/*
|
||||
import kotlinx.serialization.json.JsonElement
|
||||
import kotlinx.serialization.json.JsonPrimitive
|
||||
import net.sergeych.lyng.Scope
|
||||
import net.sergeych.lyng.ScopeCallable
|
||||
import net.sergeych.lyng.miniast.addPropertyDoc
|
||||
import net.sergeych.lyng.miniast.type
|
||||
import net.sergeych.lynon.LynonDecoder
|
||||
@ -74,12 +75,18 @@ class ObjEnumClass(val name: String) : ObjClass(name, EnumBase) {
|
||||
|
||||
init {
|
||||
addClassConst("entries", objEntries )
|
||||
addClassFn("valueOf") {
|
||||
val name = requireOnlyArg<ObjString>()
|
||||
byName[name] ?: 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 })
|
||||
addClassFn("valueOf", code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj {
|
||||
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 = 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
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
|
||||
@ -125,7 +125,9 @@ open class ObjException(
|
||||
class ExceptionClass(val name: String, vararg parents: ObjClass) : ObjClass(name, *parents) {
|
||||
init {
|
||||
constructorMeta = ArgsDeclaration(
|
||||
listOf(ArgsDeclaration.Item("message", defaultValue = statement { ObjString(name) })),
|
||||
listOf(ArgsDeclaration.Item("message", defaultValue = statement(f = object : ScopeCallable {
|
||||
override suspend fun call(scope: Scope): Obj = ObjString(name)
|
||||
}))),
|
||||
Token.Type.RPAREN
|
||||
)
|
||||
}
|
||||
@ -163,27 +165,33 @@ open class ObjException(
|
||||
}
|
||||
|
||||
val Root = ExceptionClass("Exception").apply {
|
||||
instanceInitializers.add(statement {
|
||||
if (thisObj is ObjInstance) {
|
||||
val msg = get("message")?.value ?: ObjString("Exception")
|
||||
(thisObj as ObjInstance).instanceScope.addItem("Exception::message", false, msg)
|
||||
instanceInitializers.add(statement(f = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj {
|
||||
if (scp.thisObj is ObjInstance) {
|
||||
val msg = scp.get("message")?.value ?: ObjString("Exception")
|
||||
(scp.thisObj as ObjInstance).instanceScope.addItem("Exception::message", false, msg)
|
||||
|
||||
val stack = captureStackTrace(this)
|
||||
(thisObj as ObjInstance).instanceScope.addItem("Exception::stackTrace", false, stack)
|
||||
val stack = captureStackTrace(scp)
|
||||
(scp.thisObj as ObjInstance).instanceScope.addItem("Exception::stackTrace", false, stack)
|
||||
}
|
||||
return ObjVoid
|
||||
}
|
||||
ObjVoid
|
||||
}))
|
||||
instanceConstructor = statement(f = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj = ObjVoid
|
||||
})
|
||||
instanceConstructor = statement { ObjVoid }
|
||||
addPropertyDoc(
|
||||
name = "message",
|
||||
doc = "Human‑readable error message.",
|
||||
type = type("lyng.String"),
|
||||
moduleName = "lyng.stdlib",
|
||||
getter = {
|
||||
when (val t = this.thisObj) {
|
||||
is ObjException -> t.message
|
||||
is ObjInstance -> t.instanceScope.get("Exception::message")?.value ?: ObjNull
|
||||
else -> ObjNull
|
||||
getter = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj {
|
||||
return when (val t = scp.thisObj) {
|
||||
is ObjException -> t.message
|
||||
is ObjInstance -> t.instanceScope.get("Exception::message")?.value ?: ObjNull
|
||||
else -> ObjNull
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
@ -192,10 +200,12 @@ open class ObjException(
|
||||
doc = "Extra data associated with the exception.",
|
||||
type = type("lyng.Any", nullable = true),
|
||||
moduleName = "lyng.stdlib",
|
||||
getter = {
|
||||
when (val t = this.thisObj) {
|
||||
is ObjException -> t.extraData
|
||||
else -> ObjNull
|
||||
getter = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj {
|
||||
return when (val t = scp.thisObj) {
|
||||
is ObjException -> t.extraData
|
||||
else -> ObjNull
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
@ -204,11 +214,13 @@ open class ObjException(
|
||||
doc = "Stack trace captured at throw site as a list of `StackTraceEntry`.",
|
||||
type = TypeGenericDoc(type("lyng.List"), listOf(type("lyng.StackTraceEntry"))),
|
||||
moduleName = "lyng.stdlib",
|
||||
getter = {
|
||||
when (val t = this.thisObj) {
|
||||
is ObjException -> t.getStackTrace()
|
||||
is ObjInstance -> t.instanceScope.get("Exception::stackTrace")?.value as? ObjList ?: ObjList()
|
||||
else -> ObjList()
|
||||
getter = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj {
|
||||
return when (val t = scp.thisObj) {
|
||||
is ObjException -> t.getStackTrace()
|
||||
is ObjInstance -> t.instanceScope.get("Exception::stackTrace")?.value as? ObjList ?: ObjList()
|
||||
else -> ObjList()
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
@ -216,23 +228,26 @@ open class ObjException(
|
||||
name = "toString",
|
||||
doc = "Human‑readable string representation of the error.",
|
||||
returns = type("lyng.String"),
|
||||
moduleName = "lyng.stdlib"
|
||||
) {
|
||||
val msg = when (val t = thisObj) {
|
||||
is ObjException -> t.message.value
|
||||
is ObjInstance -> (t.instanceScope.get("Exception::message")?.value as? ObjString)?.value
|
||||
?: t.objClass.className
|
||||
moduleName = "lyng.stdlib",
|
||||
code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj {
|
||||
val msg = when (val t = scp.thisObj) {
|
||||
is ObjException -> t.message.value
|
||||
is ObjInstance -> (t.instanceScope.get("Exception::message")?.value as? ObjString)?.value
|
||||
?: t.objClass.className
|
||||
|
||||
else -> t.objClass.className
|
||||
else -> t.objClass.className
|
||||
}
|
||||
val stack = when (val t = scp.thisObj) {
|
||||
is ObjException -> t.getStackTrace()
|
||||
is ObjInstance -> t.instanceScope.get("Exception::stackTrace")?.value as? ObjList ?: ObjList()
|
||||
else -> ObjList()
|
||||
}
|
||||
val at = stack.list.firstOrNull()?.toString(scp) ?: ObjString("(unknown)")
|
||||
return ObjString("${scp.thisObj.objClass.className}: $msg at $at")
|
||||
}
|
||||
}
|
||||
val stack = when (val t = thisObj) {
|
||||
is ObjException -> t.getStackTrace()
|
||||
is ObjInstance -> t.instanceScope.get("Exception::stackTrace")?.value as? ObjList ?: ObjList()
|
||||
else -> ObjList()
|
||||
}
|
||||
val at = stack.list.firstOrNull()?.toString(this) ?: ObjString("(unknown)")
|
||||
ObjString("${thisObj.objClass.className}: $msg at $at")
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
private val op = ProtectedOp()
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2025 Sergey S. Chernov real.sergeych@gmail.com
|
||||
* Copyright 2026 Sergey S. Chernov real.sergeych@gmail.com
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@ -45,28 +45,31 @@ class ObjFlowBuilder(val output: SendChannel<Obj>) : Obj() {
|
||||
doc = "Send a value to the flow consumer. Suspends if back‑pressured; no‑ops after consumer stops.",
|
||||
params = listOf(ParamDoc("value", type("lyng.Any"))),
|
||||
returns = type("lyng.Void"),
|
||||
moduleName = "lyng.stdlib"
|
||||
) {
|
||||
val data = requireOnlyArg<Obj>()
|
||||
try {
|
||||
val channel = thisAs<ObjFlowBuilder>().output
|
||||
if (!channel.isClosedForSend)
|
||||
channel.send(data)
|
||||
else
|
||||
// Flow consumer is no longer collecting; signal producer to stop
|
||||
throw ScriptFlowIsNoMoreCollected()
|
||||
} catch (x: Exception) {
|
||||
// Any failure to send (including closed channel) should gracefully stop the producer.
|
||||
if (x is CancellationException) {
|
||||
// Cancellation is a normal control-flow event
|
||||
throw ScriptFlowIsNoMoreCollected()
|
||||
} else {
|
||||
// Treat other send failures as normal flow termination as well
|
||||
throw ScriptFlowIsNoMoreCollected()
|
||||
moduleName = "lyng.stdlib",
|
||||
code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj {
|
||||
val data = scp.requireOnlyArg<Obj>()
|
||||
try {
|
||||
val channel = scp.thisAs<ObjFlowBuilder>().output
|
||||
if (!channel.isClosedForSend)
|
||||
channel.send(data)
|
||||
else
|
||||
// Flow consumer is no longer collecting; signal producer to stop
|
||||
throw ScriptFlowIsNoMoreCollected()
|
||||
} catch (x: Exception) {
|
||||
// Any failure to send (including closed channel) should gracefully stop the producer.
|
||||
if (x is CancellationException) {
|
||||
// Cancellation is a normal control-flow event
|
||||
throw ScriptFlowIsNoMoreCollected()
|
||||
} else {
|
||||
// Treat other send failures as normal flow termination as well
|
||||
throw ScriptFlowIsNoMoreCollected()
|
||||
}
|
||||
}
|
||||
return ObjVoid
|
||||
}
|
||||
}
|
||||
ObjVoid
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -103,15 +106,21 @@ class ObjFlow(val producer: Statement, val scope: Scope) : Obj() {
|
||||
name = "iterator",
|
||||
doc = "Create a pull‑based iterator over this flow. Each step resumes the producer as needed.",
|
||||
returns = TypeGenericDoc(type("lyng.Iterator"), listOf(type("lyng.Any"))),
|
||||
moduleName = "lyng.stdlib"
|
||||
) {
|
||||
val objFlow = thisAs<ObjFlow>()
|
||||
ObjFlowIterator(statement {
|
||||
objFlow.producer.execute(
|
||||
ClosureScope(this, objFlow.scope)
|
||||
)
|
||||
})
|
||||
}
|
||||
moduleName = "lyng.stdlib",
|
||||
code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj {
|
||||
val objFlow = scp.thisAs<ObjFlow>()
|
||||
return ObjFlowIterator(statement(f = object : ScopeCallable {
|
||||
override suspend fun call(scp2: Scope): Obj {
|
||||
objFlow.producer.execute(
|
||||
ClosureScope(scp2, objFlow.scope)
|
||||
)
|
||||
return ObjVoid
|
||||
}
|
||||
}))
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -164,27 +173,36 @@ class ObjFlowIterator(val producer: Statement) : Obj() {
|
||||
name = "hasNext",
|
||||
doc = "Whether another element is available from the flow.",
|
||||
returns = type("lyng.Bool"),
|
||||
moduleName = "lyng.stdlib"
|
||||
) { thisAs<ObjFlowIterator>().hasNext(this).toObj() }
|
||||
moduleName = "lyng.stdlib",
|
||||
code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj = scp.thisAs<ObjFlowIterator>().hasNext(scp).toObj()
|
||||
}
|
||||
)
|
||||
addFnDoc(
|
||||
name = "next",
|
||||
doc = "Receive the next element from the flow or throw if completed.",
|
||||
returns = type("lyng.Any"),
|
||||
moduleName = "lyng.stdlib"
|
||||
) {
|
||||
val x = thisAs<ObjFlowIterator>()
|
||||
x.next(this)
|
||||
}
|
||||
moduleName = "lyng.stdlib",
|
||||
code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj {
|
||||
val x = scp.thisAs<ObjFlowIterator>()
|
||||
return x.next(scp)
|
||||
}
|
||||
}
|
||||
)
|
||||
addFnDoc(
|
||||
name = "cancelIteration",
|
||||
doc = "Stop iteration and cancel the underlying flow producer.",
|
||||
returns = type("lyng.Void"),
|
||||
moduleName = "lyng.stdlib"
|
||||
) {
|
||||
val x = thisAs<ObjFlowIterator>()
|
||||
x.cancel()
|
||||
ObjVoid
|
||||
}
|
||||
moduleName = "lyng.stdlib",
|
||||
code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj {
|
||||
val x = scp.thisAs<ObjFlowIterator>()
|
||||
x.cancel()
|
||||
return ObjVoid
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -218,7 +218,7 @@ class ObjInstance(override val objClass: ObjClass) : Obj() {
|
||||
|
||||
override suspend fun invokeInstanceMethod(
|
||||
scope: Scope, name: String, args: Arguments,
|
||||
onNotFoundResult: (suspend () -> Obj?)?
|
||||
onNotFoundResult: OnNotFound?
|
||||
): Obj {
|
||||
val caller = scope.currentClassCtx
|
||||
|
||||
@ -231,7 +231,7 @@ class ObjInstance(override val objClass: ObjClass) : Obj() {
|
||||
if (rec.type == ObjRecord.Type.Property) {
|
||||
if (args.isEmpty()) return (rec.value as ObjProperty).callGetter(scope, this, decl)
|
||||
} else if (rec.type == ObjRecord.Type.Fun) {
|
||||
return rec.value.invoke(instanceScope, this, args, decl)
|
||||
return rec.value.invokeCallable(instanceScope, this, args, decl)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -246,7 +246,7 @@ class ObjInstance(override val objClass: ObjClass) : Obj() {
|
||||
if (rec.type == ObjRecord.Type.Property) {
|
||||
if (args.isEmpty()) return (rec.value as ObjProperty).callGetter(scope, this, c)
|
||||
} else if (rec.type == ObjRecord.Type.Fun) {
|
||||
return rec.value.invoke(instanceScope, this, args, c)
|
||||
return rec.value.invokeCallable(instanceScope, this, args, c)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -255,7 +255,7 @@ class ObjInstance(override val objClass: ObjClass) : Obj() {
|
||||
if (rec.type == ObjRecord.Type.Property) {
|
||||
if (args.isEmpty()) return (rec.value as ObjProperty).callGetter(scope, this, c)
|
||||
} else if (rec.type == ObjRecord.Type.Fun) {
|
||||
return rec.value.invoke(instanceScope, this, args, c)
|
||||
return rec.value.invokeCallable(instanceScope, this, args, c)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -271,12 +271,12 @@ class ObjInstance(override val objClass: ObjClass) : Obj() {
|
||||
val del = instanceScope[storageName]?.delegate ?: rec.delegate
|
||||
?: scope.raiseError("Internal error: delegated member $name has no delegate (tried $storageName)")
|
||||
|
||||
// For delegated member, try 'invoke' first if it's a function-like call
|
||||
// For delegated member, try 'invokeCallable' first if it's a function-like call
|
||||
val allArgs = (listOf(this, ObjString(name)) + args.list).toTypedArray()
|
||||
return del.invokeInstanceMethod(scope, "invoke", Arguments(*allArgs), onNotFoundResult = {
|
||||
return del.invokeInstanceMethod(scope, "invokeCallable", Arguments(*allArgs), onNotFoundResult = {
|
||||
// Fallback: property delegation (getValue then call result)
|
||||
val propVal = del.invokeInstanceMethod(scope, "getValue", Arguments(this, ObjString(name)))
|
||||
propVal.invoke(scope, this, args, rec.declaringClass ?: cls)
|
||||
propVal.invokeCallable(scope, this, args, rec.declaringClass ?: cls)
|
||||
})
|
||||
}
|
||||
val decl = rec.declaringClass ?: cls
|
||||
@ -285,14 +285,14 @@ class ObjInstance(override val objClass: ObjClass) : Obj() {
|
||||
scope.raiseError(
|
||||
ObjIllegalAccessException(
|
||||
scope,
|
||||
"can't invoke method $name (declared in ${decl.className})"
|
||||
"can't invokeCallable method $name (declared in ${decl.className})"
|
||||
)
|
||||
)
|
||||
|
||||
if (rec.type == ObjRecord.Type.Property) {
|
||||
if (args.isEmpty()) return (rec.value as ObjProperty).callGetter(scope, this, decl)
|
||||
} else if (rec.type == ObjRecord.Type.Fun) {
|
||||
return rec.value.invoke(
|
||||
return rec.value.invokeCallable(
|
||||
instanceScope,
|
||||
this,
|
||||
args,
|
||||
@ -300,7 +300,7 @@ class ObjInstance(override val objClass: ObjClass) : Obj() {
|
||||
)
|
||||
} else if (rec.type == ObjRecord.Type.Field || rec.type == ObjRecord.Type.ConstructorField || rec.type == ObjRecord.Type.Argument) {
|
||||
val resolved = readField(scope, name)
|
||||
return resolved.value.invoke(scope, this, args, resolved.declaringClass)
|
||||
return resolved.value.invokeCallable(scope, this, args, resolved.declaringClass)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -507,18 +507,18 @@ class ObjQualifiedView(val instance: ObjInstance, private val startClass: ObjCla
|
||||
scope: Scope,
|
||||
name: String,
|
||||
args: Arguments,
|
||||
onNotFoundResult: (suspend () -> Obj?)?
|
||||
onNotFoundResult: OnNotFound?
|
||||
): Obj {
|
||||
// Qualified method dispatch must start from the specified ancestor, not from the instance scope.
|
||||
memberFromAncestor(name)?.let { rec ->
|
||||
val decl = rec.declaringClass ?: startClass
|
||||
val caller = scope.currentClassCtx
|
||||
if (!canAccessMember(rec.visibility, decl, caller, name))
|
||||
scope.raiseError(ObjIllegalAccessException(scope, "can't invoke method $name (declared in ${decl.className})"))
|
||||
scope.raiseError(ObjIllegalAccessException(scope, "can't invokeCallable method $name (declared in ${decl.className})"))
|
||||
val saved = instance.instanceScope.currentClassCtx
|
||||
instance.instanceScope.currentClassCtx = decl
|
||||
try {
|
||||
return rec.value.invoke(instance.instanceScope, instance, args)
|
||||
return rec.value.invokeCallable(instance.instanceScope, instance, args)
|
||||
} finally {
|
||||
instance.instanceScope.currentClassCtx = saved
|
||||
}
|
||||
@ -532,19 +532,19 @@ class ObjQualifiedView(val instance: ObjInstance, private val startClass: ObjCla
|
||||
scope.raiseError(
|
||||
ObjIllegalAccessException(
|
||||
scope,
|
||||
"can't invoke method $name (declared in ${decl?.className ?: "?"})"
|
||||
"can't invokeCallable method $name (declared in ${decl?.className ?: "?"})"
|
||||
)
|
||||
)
|
||||
val saved = instance.instanceScope.currentClassCtx
|
||||
instance.instanceScope.currentClassCtx = decl
|
||||
try {
|
||||
return rec.value.invoke(instance.instanceScope, instance, args)
|
||||
return rec.value.invokeCallable(instance.instanceScope, instance, args)
|
||||
} finally {
|
||||
instance.instanceScope.currentClassCtx = saved
|
||||
}
|
||||
}
|
||||
}
|
||||
return onNotFoundResult?.invoke() ?: scope.raiseSymbolNotFound(name)
|
||||
return onNotFoundResult?.call() ?: scope.raiseSymbolNotFound(name)
|
||||
}
|
||||
|
||||
override fun toString(): String = instance.toString()
|
||||
|
||||
@ -18,17 +18,16 @@
|
||||
package net.sergeych.lyng.obj
|
||||
|
||||
import kotlinx.datetime.*
|
||||
import kotlinx.datetime.Instant
|
||||
import kotlinx.serialization.json.JsonElement
|
||||
import kotlinx.serialization.json.JsonPrimitive
|
||||
import net.sergeych.lyng.Scope
|
||||
import net.sergeych.lyng.miniast.addFnDoc
|
||||
import net.sergeych.lyng.miniast.addPropertyDoc
|
||||
import net.sergeych.lyng.miniast.type
|
||||
import net.sergeych.lyng.ScopeCallable
|
||||
import net.sergeych.lyng.miniast.*
|
||||
import net.sergeych.lynon.LynonDecoder
|
||||
import net.sergeych.lynon.LynonEncoder
|
||||
import net.sergeych.lynon.LynonSettings
|
||||
import net.sergeych.lynon.LynonType
|
||||
import kotlin.time.Clock
|
||||
import kotlin.time.isDistantFuture
|
||||
import kotlin.time.isDistantPast
|
||||
|
||||
@ -113,7 +112,7 @@ class ObjInstant(val instant: Instant,val truncateMode: LynonSettings.InstantTru
|
||||
return ObjInstant(
|
||||
when (a0) {
|
||||
null -> {
|
||||
val t = Clock.System.now()
|
||||
val t = kotlin.time.Clock.System.now()
|
||||
Instant.fromEpochSeconds(t.epochSeconds, t.nanosecondsOfSecond)
|
||||
}
|
||||
is ObjInt -> Instant.fromEpochSeconds(a0.value)
|
||||
@ -157,9 +156,11 @@ class ObjInstant(val instant: Instant,val truncateMode: LynonSettings.InstantTru
|
||||
doc = "Return the number of seconds since the Unix epoch as a real number (including fractions).",
|
||||
type = type("lyng.Real"),
|
||||
moduleName = "lyng.time",
|
||||
getter = {
|
||||
val instant = thisAs<ObjInstant>().instant
|
||||
ObjReal(instant.epochSeconds + instant.nanosecondsOfSecond * 1e-9)
|
||||
getter = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj {
|
||||
val instant = scp.thisAs<ObjInstant>().instant
|
||||
return ObjReal(instant.epochSeconds + instant.nanosecondsOfSecond * 1e-9)
|
||||
}
|
||||
}
|
||||
)
|
||||
addPropertyDoc(
|
||||
@ -167,92 +168,114 @@ class ObjInstant(val instant: Instant,val truncateMode: LynonSettings.InstantTru
|
||||
doc = "Whether this instant represents the distant future.",
|
||||
type = type("lyng.Bool"),
|
||||
moduleName = "lyng.time",
|
||||
getter = { thisAs<ObjInstant>().instant.isDistantFuture.toObj() }
|
||||
getter = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj = scp.thisAs<ObjInstant>().instant.isDistantFuture.toObj()
|
||||
}
|
||||
)
|
||||
addPropertyDoc(
|
||||
name = "isDistantPast",
|
||||
doc = "Whether this instant represents the distant past.",
|
||||
type = type("lyng.Bool"),
|
||||
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(
|
||||
name = "epochWholeSeconds",
|
||||
doc = "Return the number of full seconds since the Unix epoch.",
|
||||
type = type("lyng.Int"),
|
||||
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(
|
||||
name = "nanosecondsOfSecond",
|
||||
doc = "The number of nanoseconds within the current second.",
|
||||
type = type("lyng.Int"),
|
||||
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(
|
||||
name = "truncateToMinute",
|
||||
doc = "Truncate this instant to the nearest minute.",
|
||||
returns = type("lyng.Instant"),
|
||||
moduleName = "lyng.time"
|
||||
) {
|
||||
val t = thisAs<ObjInstant>().instant
|
||||
val tz = TimeZone.UTC
|
||||
val dt = t.toLocalDateTime(tz)
|
||||
val truncated = LocalDateTime(dt.year, dt.month, dt.dayOfMonth, dt.hour, dt.minute, 0, 0)
|
||||
ObjInstant(truncated.toInstant(tz), LynonSettings.InstantTruncateMode.Second)
|
||||
}
|
||||
moduleName = "lyng.time",
|
||||
code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj {
|
||||
val t = scp.thisAs<ObjInstant>().instant
|
||||
val tz = TimeZone.UTC
|
||||
val dt = t.toLocalDateTime(tz)
|
||||
val truncated = LocalDateTime(dt.year, dt.month, dt.dayOfMonth, dt.hour, dt.minute, 0, 0)
|
||||
return ObjInstant(truncated.toInstant(tz), LynonSettings.InstantTruncateMode.Second)
|
||||
}
|
||||
}
|
||||
)
|
||||
addFnDoc(
|
||||
name = "truncateToSecond",
|
||||
doc = "Truncate this instant to the nearest second.",
|
||||
returns = type("lyng.Instant"),
|
||||
moduleName = "lyng.time"
|
||||
) {
|
||||
val t = thisAs<ObjInstant>().instant
|
||||
ObjInstant(Instant.fromEpochSeconds(t.epochSeconds), LynonSettings.InstantTruncateMode.Second)
|
||||
}
|
||||
moduleName = "lyng.time",
|
||||
code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj {
|
||||
val t = scp.thisAs<ObjInstant>().instant
|
||||
return ObjInstant(Instant.fromEpochSeconds(t.epochSeconds), LynonSettings.InstantTruncateMode.Second)
|
||||
}
|
||||
}
|
||||
)
|
||||
addFnDoc(
|
||||
name = "truncateToMillisecond",
|
||||
doc = "Truncate this instant to the nearest millisecond.",
|
||||
returns = type("lyng.Instant"),
|
||||
moduleName = "lyng.time"
|
||||
) {
|
||||
val t = thisAs<ObjInstant>().instant
|
||||
ObjInstant(
|
||||
Instant.fromEpochSeconds(t.epochSeconds, t.nanosecondsOfSecond / 1_000_000 * 1_000_000),
|
||||
LynonSettings.InstantTruncateMode.Millisecond
|
||||
)
|
||||
}
|
||||
moduleName = "lyng.time",
|
||||
code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj {
|
||||
val t = scp.thisAs<ObjInstant>().instant
|
||||
return ObjInstant(
|
||||
Instant.fromEpochSeconds(t.epochSeconds, t.nanosecondsOfSecond / 1_000_000 * 1_000_000),
|
||||
LynonSettings.InstantTruncateMode.Millisecond
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
||||
addFnDoc(
|
||||
name = "truncateToMicrosecond",
|
||||
doc = "Truncate this instant to the nearest microsecond.",
|
||||
returns = type("lyng.Instant"),
|
||||
moduleName = "lyng.time"
|
||||
) {
|
||||
val t = thisAs<ObjInstant>().instant
|
||||
ObjInstant(
|
||||
Instant.fromEpochSeconds(t.epochSeconds, t.nanosecondsOfSecond / 1_000 * 1_000),
|
||||
LynonSettings.InstantTruncateMode.Microsecond
|
||||
)
|
||||
}
|
||||
moduleName = "lyng.time",
|
||||
code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj {
|
||||
val t = scp.thisAs<ObjInstant>().instant
|
||||
return ObjInstant(
|
||||
Instant.fromEpochSeconds(t.epochSeconds, t.nanosecondsOfSecond / 1_000 * 1_000),
|
||||
LynonSettings.InstantTruncateMode.Microsecond
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
addFnDoc(
|
||||
name = "toRFC3339",
|
||||
doc = "Return the RFC3339 string representation of this instant in UTC (e.g., '1970-01-01T00:00:00Z').",
|
||||
returns = type("lyng.String"),
|
||||
moduleName = "lyng.time"
|
||||
) {
|
||||
thisAs<ObjInstant>().instant.toString().toObj()
|
||||
}
|
||||
moduleName = "lyng.time",
|
||||
code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj = scp.thisAs<ObjInstant>().instant.toString().toObj()
|
||||
}
|
||||
)
|
||||
|
||||
addFnDoc(
|
||||
name = "toSortableString",
|
||||
doc = "Alias to toRFC3339.",
|
||||
returns = type("lyng.String"),
|
||||
moduleName = "lyng.time"
|
||||
) {
|
||||
thisAs<ObjInstant>().instant.toString().toObj()
|
||||
}
|
||||
moduleName = "lyng.time",
|
||||
code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj = scp.thisAs<ObjInstant>().instant.toString().toObj()
|
||||
}
|
||||
)
|
||||
|
||||
addFnDoc(
|
||||
name = "toDateTime",
|
||||
@ -261,24 +284,27 @@ class ObjInstant(val instant: Instant,val truncateMode: LynonSettings.InstantTru
|
||||
"If no argument is provided, the system's current default time zone is used.",
|
||||
params = listOf(net.sergeych.lyng.miniast.ParamDoc("tz", type = type("lyng.Any", true))),
|
||||
returns = type("lyng.DateTime"),
|
||||
moduleName = "lyng.time"
|
||||
) {
|
||||
val tz = when (val a = args.list.getOrNull(0)) {
|
||||
null -> TimeZone.currentSystemDefault()
|
||||
is ObjString -> TimeZone.of(a.value)
|
||||
is ObjInt -> UtcOffset(seconds = a.value.toInt()).asTimeZone()
|
||||
else -> raiseIllegalArgument("invalid timezone: $a")
|
||||
moduleName = "lyng.time",
|
||||
code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj {
|
||||
val tz = when (val a = scp.args.list.getOrNull(0)) {
|
||||
null -> TimeZone.currentSystemDefault()
|
||||
is ObjString -> TimeZone.of(a.value)
|
||||
is ObjInt -> UtcOffset(seconds = a.value.toInt()).asTimeZone()
|
||||
else -> scp.raiseIllegalArgument("invalid timezone: $a")
|
||||
}
|
||||
return ObjDateTime(scp.thisAs<ObjInstant>().instant, tz)
|
||||
}
|
||||
}
|
||||
ObjDateTime(thisAs<ObjInstant>().instant, tz)
|
||||
}
|
||||
)
|
||||
|
||||
// class members
|
||||
|
||||
addClassConst("distantFuture", distantFuture)
|
||||
addClassConst("distantPast", distantPast)
|
||||
addClassFn("now") {
|
||||
ObjInstant(Clock.System.now())
|
||||
}
|
||||
addClassFn("now", code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj = ObjInstant(kotlin.time.Clock.System.now())
|
||||
})
|
||||
// addFn("epochMilliseconds") {
|
||||
// ObjInt(instant.toEpochMilliseconds())
|
||||
// }
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2025 Sergey S. Chernov real.sergeych@gmail.com
|
||||
* Copyright 2026 Sergey S. Chernov real.sergeych@gmail.com
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@ -20,6 +20,7 @@ package net.sergeych.lyng.obj
|
||||
import kotlinx.serialization.json.JsonElement
|
||||
import kotlinx.serialization.json.JsonPrimitive
|
||||
import net.sergeych.lyng.Scope
|
||||
import net.sergeych.lyng.ScopeCallable
|
||||
import net.sergeych.lyng.miniast.addFnDoc
|
||||
import net.sergeych.lynon.LynonDecoder
|
||||
import net.sergeych.lynon.LynonEncoder
|
||||
@ -183,10 +184,11 @@ class ObjInt(val value: Long, override val isConst: Boolean = false) : Obj(), Nu
|
||||
name = "toInt",
|
||||
doc = "Returns this integer (identity operation).",
|
||||
returns = net.sergeych.lyng.miniast.type("lyng.Int"),
|
||||
moduleName = "lyng.stdlib"
|
||||
) {
|
||||
thisObj
|
||||
}
|
||||
moduleName = "lyng.stdlib",
|
||||
code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj = scp.thisObj
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -18,6 +18,8 @@
|
||||
package net.sergeych.lyng.obj
|
||||
|
||||
import net.sergeych.lyng.Arguments
|
||||
import net.sergeych.lyng.Scope
|
||||
import net.sergeych.lyng.ScopeCallable
|
||||
import net.sergeych.lyng.Statement
|
||||
import net.sergeych.lyng.miniast.ParamDoc
|
||||
import net.sergeych.lyng.miniast.addFnDoc
|
||||
@ -35,13 +37,15 @@ val ObjIterable by lazy {
|
||||
doc = "Collect elements of this iterable into a new list.",
|
||||
type = type("lyng.List"),
|
||||
moduleName = "lyng.stdlib",
|
||||
getter = {
|
||||
val result = mutableListOf<Obj>()
|
||||
val it = this.thisObj.invokeInstanceMethod(this, "iterator")
|
||||
while (it.invokeInstanceMethod(this, "hasNext").toBool()) {
|
||||
result.add(it.invokeInstanceMethod(this, "next"))
|
||||
getter = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj {
|
||||
val result = mutableListOf<Obj>()
|
||||
val it = scp.thisObj.invokeInstanceMethod(scp, "iterator")
|
||||
while (it.invokeInstanceMethod(scp, "hasNext").toBool()) {
|
||||
result.add(it.invokeInstanceMethod(scp, "next"))
|
||||
}
|
||||
return ObjList(result)
|
||||
}
|
||||
ObjList(result)
|
||||
}
|
||||
)
|
||||
|
||||
@ -52,16 +56,19 @@ val ObjIterable by lazy {
|
||||
params = listOf(ParamDoc("element")),
|
||||
returns = type("lyng.Bool"),
|
||||
isOpen = true,
|
||||
moduleName = "lyng.stdlib"
|
||||
) {
|
||||
val obj = args.firstAndOnly()
|
||||
val it = thisObj.invokeInstanceMethod(this, "iterator")
|
||||
while (it.invokeInstanceMethod(this, "hasNext").toBool()) {
|
||||
if (obj.equals(this, it.invokeInstanceMethod(this, "next")))
|
||||
return@addFnDoc ObjTrue
|
||||
moduleName = "lyng.stdlib",
|
||||
code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj {
|
||||
val obj = scp.args.firstAndOnly()
|
||||
val it = scp.thisObj.invokeInstanceMethod(scp, "iterator")
|
||||
while (it.invokeInstanceMethod(scp, "hasNext").toBool()) {
|
||||
if (obj.equals(scp, it.invokeInstanceMethod(scp, "next")))
|
||||
return ObjTrue
|
||||
}
|
||||
return ObjFalse
|
||||
}
|
||||
}
|
||||
ObjFalse
|
||||
}
|
||||
)
|
||||
|
||||
addFnDoc(
|
||||
name = "indexOf",
|
||||
@ -69,34 +76,39 @@ val ObjIterable by lazy {
|
||||
params = listOf(ParamDoc("element")),
|
||||
returns = type("lyng.Int"),
|
||||
isOpen = true,
|
||||
moduleName = "lyng.stdlib"
|
||||
) {
|
||||
val obj = args.firstAndOnly()
|
||||
var index = 0
|
||||
val it = thisObj.invokeInstanceMethod(this, "iterator")
|
||||
while (it.invokeInstanceMethod(this, "hasNext").toBool()) {
|
||||
if (obj.equals(this, it.invokeInstanceMethod(this, "next")))
|
||||
return@addFnDoc ObjInt(index.toLong())
|
||||
index++
|
||||
moduleName = "lyng.stdlib",
|
||||
code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj {
|
||||
val obj = scp.args.firstAndOnly()
|
||||
var index = 0
|
||||
val it = scp.thisObj.invokeInstanceMethod(scp, "iterator")
|
||||
while (it.invokeInstanceMethod(scp, "hasNext").toBool()) {
|
||||
if (obj.equals(scp, it.invokeInstanceMethod(scp, "next")))
|
||||
return ObjInt(index.toLong())
|
||||
index++
|
||||
}
|
||||
return ObjInt(-1L)
|
||||
}
|
||||
}
|
||||
ObjInt(-1L)
|
||||
}
|
||||
)
|
||||
|
||||
addPropertyDoc(
|
||||
name = "toSet",
|
||||
doc = "Collect elements of this iterable into a new set.",
|
||||
type = type("lyng.Set"),
|
||||
moduleName = "lyng.stdlib",
|
||||
getter = {
|
||||
if( this.thisObj.isInstanceOf(ObjSet.type) )
|
||||
this.thisObj
|
||||
else {
|
||||
val result = mutableSetOf<Obj>()
|
||||
val it = this.thisObj.invokeInstanceMethod(this, "iterator")
|
||||
while (it.invokeInstanceMethod(this, "hasNext").toBool()) {
|
||||
result.add(it.invokeInstanceMethod(this, "next"))
|
||||
getter = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj {
|
||||
return if( scp.thisObj.isInstanceOf(ObjSet.type) )
|
||||
scp.thisObj
|
||||
else {
|
||||
val result = mutableSetOf<Obj>()
|
||||
val it = scp.thisObj.invokeInstanceMethod(scp, "iterator")
|
||||
while (it.invokeInstanceMethod(scp, "hasNext").toBool()) {
|
||||
result.add(it.invokeInstanceMethod(scp, "next"))
|
||||
}
|
||||
ObjSet(result)
|
||||
}
|
||||
ObjSet(result)
|
||||
}
|
||||
}
|
||||
)
|
||||
@ -106,16 +118,20 @@ val ObjIterable by lazy {
|
||||
doc = "Collect pairs into a map using [0] as key and [1] as value for each element.",
|
||||
type = type("lyng.Map"),
|
||||
moduleName = "lyng.stdlib",
|
||||
getter = {
|
||||
val result = mutableMapOf<Obj, Obj>()
|
||||
this.thisObj.enumerate(this) { pair ->
|
||||
when (pair) {
|
||||
is ObjMapEntry -> result[pair.key] = pair.value
|
||||
else -> result[pair.getAt(this, 0)] = pair.getAt(this, 1)
|
||||
}
|
||||
true
|
||||
getter = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj {
|
||||
val result = mutableMapOf<Obj, Obj>()
|
||||
scp.thisObj.enumerate(scp, object : EnumerateCallback {
|
||||
override suspend fun call(pair: Obj): Boolean {
|
||||
when (pair) {
|
||||
is ObjMapEntry -> result[pair.key] = pair.value
|
||||
else -> result[pair.getAt(scp, ObjInt(0))] = pair.getAt(scp, ObjInt(1))
|
||||
}
|
||||
return true
|
||||
}
|
||||
})
|
||||
return ObjMap(result)
|
||||
}
|
||||
ObjMap(result)
|
||||
}
|
||||
)
|
||||
|
||||
@ -124,31 +140,37 @@ val ObjIterable by lazy {
|
||||
doc = "Build a map from elements using the lambda result as key.",
|
||||
params = listOf(ParamDoc("keySelector")),
|
||||
returns = type("lyng.Map"),
|
||||
moduleName = "lyng.stdlib"
|
||||
) {
|
||||
val association = requireOnlyArg<Statement>()
|
||||
val result = ObjMap()
|
||||
thisObj.toFlow(this).collect {
|
||||
result.map[association.call(this, it)] = it
|
||||
moduleName = "lyng.stdlib",
|
||||
code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj {
|
||||
val association = scp.requireOnlyArg<Statement>()
|
||||
val result = ObjMap()
|
||||
scp.thisObj.toFlow(scp).collect {
|
||||
result.map[association.invokeCallable(scp, scp.thisObj, it)] = it
|
||||
}
|
||||
return result
|
||||
}
|
||||
}
|
||||
result
|
||||
}
|
||||
)
|
||||
|
||||
addFnDoc(
|
||||
name = "forEach",
|
||||
doc = "Apply the lambda to each element in iteration order.",
|
||||
params = listOf(ParamDoc("action")),
|
||||
isOpen = true,
|
||||
moduleName = "lyng.stdlib"
|
||||
) {
|
||||
val it = thisObj.invokeInstanceMethod(this, "iterator")
|
||||
val fn = requiredArg<Statement>(0)
|
||||
while (it.invokeInstanceMethod(this, "hasNext").toBool()) {
|
||||
val x = it.invokeInstanceMethod(this, "next")
|
||||
fn.execute(this.createChildScope(Arguments(listOf(x))))
|
||||
moduleName = "lyng.stdlib",
|
||||
code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj {
|
||||
val it = scp.thisObj.invokeInstanceMethod(scp, "iterator")
|
||||
val fn = scp.requiredArg<Statement>(0)
|
||||
while (it.invokeInstanceMethod(scp, "hasNext").toBool()) {
|
||||
val x = it.invokeInstanceMethod(scp, "next")
|
||||
fn.execute(scp.createChildScope(Arguments(listOf(x))))
|
||||
}
|
||||
return ObjVoid
|
||||
}
|
||||
}
|
||||
ObjVoid
|
||||
}
|
||||
)
|
||||
|
||||
addFnDoc(
|
||||
name = "map",
|
||||
@ -156,15 +178,18 @@ val ObjIterable by lazy {
|
||||
params = listOf(ParamDoc("transform")),
|
||||
returns = type("lyng.List"),
|
||||
isOpen = true,
|
||||
moduleName = "lyng.stdlib"
|
||||
) {
|
||||
val fn = requiredArg<Statement>(0)
|
||||
val result = mutableListOf<Obj>()
|
||||
thisObj.toFlow(this).collect {
|
||||
result.add(fn.call(this, it))
|
||||
moduleName = "lyng.stdlib",
|
||||
code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj {
|
||||
val fn = scp.requiredArg<Statement>(0)
|
||||
val result = mutableListOf<Obj>()
|
||||
scp.thisObj.toFlow(scp).collect {
|
||||
result.add(fn.invokeCallable(scp, scp.thisObj, it))
|
||||
}
|
||||
return ObjList(result)
|
||||
}
|
||||
}
|
||||
ObjList(result)
|
||||
}
|
||||
)
|
||||
|
||||
addFnDoc(
|
||||
name = "mapNotNull",
|
||||
@ -172,46 +197,56 @@ val ObjIterable by lazy {
|
||||
params = listOf(ParamDoc("transform")),
|
||||
returns = type("lyng.List"),
|
||||
isOpen = true,
|
||||
moduleName = "lyng.stdlib"
|
||||
) {
|
||||
val fn = requiredArg<Statement>(0)
|
||||
val result = mutableListOf<Obj>()
|
||||
thisObj.toFlow(this).collect {
|
||||
val transformed = fn.call(this, it)
|
||||
if( transformed != ObjNull) result.add(transformed)
|
||||
moduleName = "lyng.stdlib",
|
||||
code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj {
|
||||
val fn = scp.requiredArg<Statement>(0)
|
||||
val result = mutableListOf<Obj>()
|
||||
scp.thisObj.toFlow(scp).collect {
|
||||
val transformed = fn.invokeCallable(scp, scp.thisObj, it)
|
||||
if( transformed != ObjNull) result.add(transformed)
|
||||
}
|
||||
return ObjList(result)
|
||||
}
|
||||
}
|
||||
ObjList(result)
|
||||
}
|
||||
)
|
||||
|
||||
addFnDoc(
|
||||
name = "take",
|
||||
doc = "Take the first N elements and return them as a list.",
|
||||
params = listOf(ParamDoc("n", type("lyng.Int"))),
|
||||
returns = type("lyng.List"),
|
||||
moduleName = "lyng.stdlib"
|
||||
) {
|
||||
var n = requireOnlyArg<ObjInt>().value.toInt()
|
||||
val result = mutableListOf<Obj>()
|
||||
if (n > 0) {
|
||||
thisObj.enumerate(this) {
|
||||
result.add(it)
|
||||
--n > 0
|
||||
moduleName = "lyng.stdlib",
|
||||
code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj {
|
||||
var n = scp.requireOnlyArg<ObjInt>().value.toInt()
|
||||
val result = mutableListOf<Obj>()
|
||||
if (n > 0) {
|
||||
scp.thisObj.enumerate(scp, object : EnumerateCallback {
|
||||
override suspend fun call(element: Obj): Boolean {
|
||||
result.add(element)
|
||||
return --n > 0
|
||||
}
|
||||
})
|
||||
}
|
||||
return ObjList(result)
|
||||
}
|
||||
}
|
||||
ObjList(result)
|
||||
}
|
||||
)
|
||||
|
||||
addPropertyDoc(
|
||||
name = "isEmpty",
|
||||
doc = "Whether the iterable has no elements.",
|
||||
type = type("lyng.Bool"),
|
||||
moduleName = "lyng.stdlib",
|
||||
getter = {
|
||||
ObjBool(
|
||||
this.thisObj.invokeInstanceMethod(this, "iterator")
|
||||
.invokeInstanceMethod(this, "hasNext").toBool()
|
||||
.not()
|
||||
)
|
||||
getter = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj {
|
||||
return ObjBool(
|
||||
scp.thisObj.invokeInstanceMethod(scp, "iterator")
|
||||
.invokeInstanceMethod(scp, "hasNext").toBool()
|
||||
.not()
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
@ -220,25 +255,31 @@ val ObjIterable by lazy {
|
||||
doc = "Return a new list sorted using the provided comparator `(a, b) -> Int`.",
|
||||
params = listOf(ParamDoc("comparator")),
|
||||
returns = type("lyng.List"),
|
||||
moduleName = "lyng.stdlib"
|
||||
) {
|
||||
val list = thisObj.callMethod<ObjList>(this, "toList")
|
||||
val comparator = requireOnlyArg<Statement>()
|
||||
list.quicksort { a, b ->
|
||||
comparator.call(this, a, b).toInt()
|
||||
moduleName = "lyng.stdlib",
|
||||
code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj {
|
||||
val list = scp.thisObj.callMethod<ObjList>(scp, "toList")
|
||||
val comparator = scp.requireOnlyArg<Statement>()
|
||||
list.quicksort { a, b ->
|
||||
comparator.invokeCallable(scp, scp.thisObj, a, b).toInt()
|
||||
}
|
||||
return list
|
||||
}
|
||||
}
|
||||
list
|
||||
}
|
||||
)
|
||||
|
||||
addFnDoc(
|
||||
name = "reversed",
|
||||
doc = "Return a new list with elements in reverse order.",
|
||||
returns = type("lyng.List"),
|
||||
moduleName = "lyng.stdlib"
|
||||
) {
|
||||
val list = thisObj.callMethod<ObjList>(this, "toList")
|
||||
list.list.reverse()
|
||||
list
|
||||
}
|
||||
moduleName = "lyng.stdlib",
|
||||
code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj {
|
||||
val list = scp.thisObj.callMethod<ObjList>(scp, "toList")
|
||||
list.list.reverse()
|
||||
return list
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2025 Sergey S. Chernov real.sergeych@gmail.com
|
||||
* Copyright 2026 Sergey S. Chernov real.sergeych@gmail.com
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@ -17,6 +17,8 @@
|
||||
|
||||
package net.sergeych.lyng.obj
|
||||
|
||||
import net.sergeych.lyng.Scope
|
||||
import net.sergeych.lyng.ScopeCallable
|
||||
import net.sergeych.lyng.miniast.TypeGenericDoc
|
||||
import net.sergeych.lyng.miniast.addFnDoc
|
||||
import net.sergeych.lyng.miniast.type
|
||||
@ -38,44 +40,50 @@ val ObjIterator by lazy {
|
||||
doc = "Optional hint to stop iteration early and free resources.",
|
||||
returns = type("lyng.Void"),
|
||||
isOpen = true,
|
||||
moduleName = "lyng.stdlib"
|
||||
) {
|
||||
ObjVoid
|
||||
}
|
||||
moduleName = "lyng.stdlib",
|
||||
code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj = ObjVoid
|
||||
}
|
||||
)
|
||||
addFnDoc(
|
||||
name = "hasNext",
|
||||
doc = "Whether another element is available.",
|
||||
returns = type("lyng.Bool"),
|
||||
isOpen = true,
|
||||
moduleName = "lyng.stdlib"
|
||||
) {
|
||||
raiseNotImplemented("hasNext() is not implemented")
|
||||
}
|
||||
moduleName = "lyng.stdlib",
|
||||
code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj = scp.raiseNotImplemented("hasNext() is not implemented")
|
||||
}
|
||||
)
|
||||
addFnDoc(
|
||||
name = "next",
|
||||
doc = "Return the next element.",
|
||||
returns = type("lyng.Any"),
|
||||
isOpen = true,
|
||||
moduleName = "lyng.stdlib"
|
||||
) {
|
||||
raiseNotImplemented("next() is not implemented")
|
||||
}
|
||||
moduleName = "lyng.stdlib",
|
||||
code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj = scp.raiseNotImplemented("next() is not implemented")
|
||||
}
|
||||
)
|
||||
// Helper to consume iterator into a list
|
||||
addFnDoc(
|
||||
name = "toList",
|
||||
doc = "Consume this iterator and collect elements into a list.",
|
||||
returns = TypeGenericDoc(type("lyng.List"), listOf(type("lyng.Any"))),
|
||||
moduleName = "lyng.stdlib"
|
||||
) {
|
||||
val out = mutableListOf<Obj>()
|
||||
while (true) {
|
||||
val has = thisObj.invokeInstanceMethod(this, "hasNext").toBool()
|
||||
if (!has) break
|
||||
val v = thisObj.invokeInstanceMethod(this, "next")
|
||||
out += v
|
||||
moduleName = "lyng.stdlib",
|
||||
code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj {
|
||||
val out = mutableListOf<Obj>()
|
||||
while (true) {
|
||||
val has = scp.thisObj.invokeInstanceMethod(scp, "hasNext").toBool()
|
||||
if (!has) break
|
||||
val v = scp.thisObj.invokeInstanceMethod(scp, "next")
|
||||
out += v
|
||||
}
|
||||
return ObjList(out.toMutableList())
|
||||
}
|
||||
}
|
||||
ObjList(out.toMutableList())
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2025 Sergey S. Chernov real.sergeych@gmail.com
|
||||
* Copyright 2026 Sergey S. Chernov real.sergeych@gmail.com
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@ -22,6 +22,7 @@ package net.sergeych.lyng.obj
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.flow
|
||||
import net.sergeych.lyng.Scope
|
||||
import net.sergeych.lyng.ScopeCallable
|
||||
|
||||
/**
|
||||
* Iterator wrapper to allow Kotlin collections to be returned from Lyng objects;
|
||||
@ -33,8 +34,12 @@ class ObjKotlinIterator(val iterator: Iterator<Any?>) : Obj() {
|
||||
|
||||
companion object {
|
||||
val type = ObjClass("KotlinIterator", ObjIterator).apply {
|
||||
addFn("next") { thisAs<ObjKotlinIterator>().iterator.next().toObj() }
|
||||
addFn("hasNext") { thisAs<ObjKotlinIterator>().iterator.hasNext().toObj() }
|
||||
addFn("next", code = object : ScopeCallable {
|
||||
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 {
|
||||
val type = ObjClass("KotlinIterator", ObjIterator).apply {
|
||||
addFn("next") {
|
||||
thisAs<ObjKotlinObjIterator>().iterator.next()
|
||||
}
|
||||
addFn("hasNext") {
|
||||
thisAs<ObjKotlinObjIterator>().iterator.hasNext().toObj()
|
||||
}
|
||||
addFn("next", code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj =
|
||||
scp.thisAs<ObjKotlinObjIterator>().iterator.next()
|
||||
})
|
||||
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 hasNext = iterator.getInstanceMethod(scope, "hasNext")
|
||||
val next = iterator.getInstanceMethod(scope, "next")
|
||||
while (hasNext.invoke(scope, iterator).toBool()) {
|
||||
emit(next.invoke(scope, iterator))
|
||||
while (hasNext.invokeCallable(scope, iterator).toBool()) {
|
||||
emit(next.invokeCallable(scope, iterator))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -20,6 +20,7 @@ package net.sergeych.lyng.obj
|
||||
import kotlinx.serialization.json.JsonArray
|
||||
import kotlinx.serialization.json.JsonElement
|
||||
import net.sergeych.lyng.Scope
|
||||
import net.sergeych.lyng.ScopeCallable
|
||||
import net.sergeych.lyng.Statement
|
||||
import net.sergeych.lyng.miniast.ParamDoc
|
||||
import net.sergeych.lyng.miniast.addFnDoc
|
||||
@ -111,13 +112,13 @@ class ObjList(val list: MutableList<Obj> = mutableListOf()) : Obj() {
|
||||
val next2 = it2.getInstanceMethod(scope, "next")
|
||||
|
||||
while (it1.hasNext()) {
|
||||
if (!hasNext2.invoke(scope, it2).toBool()) return 1 // I'm longer
|
||||
if (!hasNext2.invokeCallable(scope, it2).toBool()) return 1 // I'm longer
|
||||
val v1 = it1.next()
|
||||
val v2 = next2.invoke(scope, it2)
|
||||
val v2 = next2.invokeCallable(scope, it2)
|
||||
val d = v1.compareTo(scope, v2)
|
||||
if (d != 0) return d
|
||||
}
|
||||
return if (hasNext2.invoke(scope, it2).toBool()) -1 else 0
|
||||
return if (hasNext2.invokeCallable(scope, it2).toBool()) -1 else 0
|
||||
}
|
||||
return -2
|
||||
}
|
||||
@ -169,9 +170,9 @@ class ObjList(val list: MutableList<Obj> = mutableListOf()) : Obj() {
|
||||
return list.contains(other)
|
||||
}
|
||||
|
||||
override suspend fun enumerate(scope: Scope, callback: suspend (Obj) -> Boolean) {
|
||||
override suspend fun enumerate(scope: Scope, callback: EnumerateCallback) {
|
||||
for (item in list) {
|
||||
if (!callback(item)) break
|
||||
if (!callback.call(item)) break
|
||||
}
|
||||
}
|
||||
|
||||
@ -179,7 +180,9 @@ class ObjList(val list: MutableList<Obj> = mutableListOf()) : Obj() {
|
||||
get() = type
|
||||
|
||||
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)
|
||||
@ -230,7 +233,9 @@ class ObjList(val list: MutableList<Obj> = mutableListOf()) : Obj() {
|
||||
override suspend fun lynonType(): LynonType = LynonType.List
|
||||
|
||||
override suspend fun toJson(scope: Scope): JsonElement {
|
||||
return JsonArray(list.map { it.toJson(scope) })
|
||||
val res = ArrayList<JsonElement>(list.size)
|
||||
for (i in list) res.add(i.toJson(scope))
|
||||
return JsonArray(res)
|
||||
}
|
||||
|
||||
override suspend fun defaultToString(scope: Scope): ObjString {
|
||||
@ -256,256 +261,290 @@ class ObjList(val list: MutableList<Obj> = mutableListOf()) : Obj() {
|
||||
doc = "Number of elements in this list.",
|
||||
type = type("lyng.Int"),
|
||||
moduleName = "lyng.stdlib",
|
||||
getter = {
|
||||
val s = (this.thisObj as ObjList).list.size
|
||||
s.toObj()
|
||||
getter = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj {
|
||||
return (scp.thisObj as ObjList).list.size.toObj()
|
||||
}
|
||||
}
|
||||
)
|
||||
addFnDoc(
|
||||
name = "add",
|
||||
doc = "Append one or more elements to the end of this list.",
|
||||
moduleName = "lyng.stdlib"
|
||||
) {
|
||||
val l = thisAs<ObjList>().list
|
||||
for (a in args) l.add(a)
|
||||
ObjVoid
|
||||
}
|
||||
moduleName = "lyng.stdlib",
|
||||
code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj {
|
||||
val l = scp.thisAs<ObjList>().list
|
||||
for (a in scp.args) l.add(a)
|
||||
return ObjVoid
|
||||
}
|
||||
}
|
||||
)
|
||||
addFnDoc(
|
||||
name = "insertAt",
|
||||
doc = "Insert elements starting at the given index.",
|
||||
params = listOf(ParamDoc("index", type("lyng.Int"))),
|
||||
moduleName = "lyng.stdlib"
|
||||
) {
|
||||
if (args.size < 2) raiseError("addAt takes 2+ arguments")
|
||||
val l = thisAs<ObjList>()
|
||||
var index = requiredArg<ObjInt>(0).value.toInt()
|
||||
for (i in 1..<args.size) l.list.add(index++, args[i])
|
||||
ObjVoid
|
||||
}
|
||||
moduleName = "lyng.stdlib",
|
||||
code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj {
|
||||
if (scp.args.size < 2) scp.raiseError("addAt takes 2+ arguments")
|
||||
val l = scp.thisAs<ObjList>()
|
||||
var index = scp.requiredArg<ObjInt>(0).value.toInt()
|
||||
for (i in 1..<scp.args.size) l.list.add(index++, scp.args[i])
|
||||
return ObjVoid
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
addFnDoc(
|
||||
name = "removeAt",
|
||||
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"))),
|
||||
moduleName = "lyng.stdlib"
|
||||
) {
|
||||
val self = thisAs<ObjList>()
|
||||
val start = requiredArg<ObjInt>(0).value.toInt()
|
||||
if (args.size == 2) {
|
||||
val end = requireOnlyArg<ObjInt>().value.toInt()
|
||||
self.list.subList(start, end).clear()
|
||||
} else
|
||||
self.list.removeAt(start)
|
||||
self
|
||||
}
|
||||
moduleName = "lyng.stdlib",
|
||||
code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj {
|
||||
val self = scp.thisAs<ObjList>()
|
||||
val start = scp.requiredArg<ObjInt>(0).value.toInt()
|
||||
if (scp.args.size == 2) {
|
||||
val end = scp.requireOnlyArg<ObjInt>().value.toInt()
|
||||
self.list.subList(start, end).clear()
|
||||
} else
|
||||
self.list.removeAt(start)
|
||||
return self
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
addFnDoc(
|
||||
name = "removeLast",
|
||||
doc = "Remove the last element or the last N elements if a count is provided. Returns the list.",
|
||||
params = listOf(ParamDoc("count", type("lyng.Int"))),
|
||||
moduleName = "lyng.stdlib"
|
||||
) {
|
||||
val self = thisAs<ObjList>()
|
||||
if (args.isNotEmpty()) {
|
||||
val count = requireOnlyArg<ObjInt>().value.toInt()
|
||||
val size = self.list.size
|
||||
if (count >= size) self.list.clear()
|
||||
else self.list.subList(size - count, size).clear()
|
||||
} else self.list.removeLast()
|
||||
self
|
||||
}
|
||||
moduleName = "lyng.stdlib",
|
||||
code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj {
|
||||
val self = scp.thisAs<ObjList>()
|
||||
if (scp.args.isNotEmpty()) {
|
||||
val count = scp.requireOnlyArg<ObjInt>().value.toInt()
|
||||
val size = self.list.size
|
||||
if (count >= size) self.list.clear()
|
||||
else self.list.subList(size - count, size).clear()
|
||||
} else self.list.removeLast()
|
||||
return self
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
addFnDoc(
|
||||
name = "removeRange",
|
||||
doc = "Remove a range of elements. Accepts a Range or (start, endInclusive). Returns the list.",
|
||||
params = listOf(ParamDoc("range")),
|
||||
moduleName = "lyng.stdlib"
|
||||
) {
|
||||
val self = thisAs<ObjList>()
|
||||
val list = self.list
|
||||
val range = requiredArg<Obj>(0)
|
||||
if (range is ObjRange) {
|
||||
val index = range
|
||||
when {
|
||||
index.start is ObjInt && index.end is ObjInt -> {
|
||||
if (index.isEndInclusive)
|
||||
list.subList(index.start.toInt(), index.end.toInt() + 1)
|
||||
else
|
||||
list.subList(index.start.toInt(), index.end.toInt())
|
||||
}
|
||||
moduleName = "lyng.stdlib",
|
||||
code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj {
|
||||
val self = scp.thisAs<ObjList>()
|
||||
val list = self.list
|
||||
val range = scp.requiredArg<Obj>(0)
|
||||
if (range is ObjRange) {
|
||||
val index = range
|
||||
when {
|
||||
index.start is ObjInt && index.end is ObjInt -> {
|
||||
if (index.isEndInclusive)
|
||||
list.subList(index.start.toInt(), index.end.toInt() + 1)
|
||||
else
|
||||
list.subList(index.start.toInt(), index.end.toInt())
|
||||
}
|
||||
|
||||
index.isOpenStart && !index.isOpenEnd -> {
|
||||
if (index.isEndInclusive)
|
||||
list.subList(0, index.end!!.toInt() + 1)
|
||||
else
|
||||
list.subList(0, index.end!!.toInt())
|
||||
}
|
||||
index.isOpenStart && !index.isOpenEnd -> {
|
||||
if (index.isEndInclusive)
|
||||
list.subList(0, index.end!!.toInt() + 1)
|
||||
else
|
||||
list.subList(0, index.end!!.toInt())
|
||||
}
|
||||
|
||||
index.isOpenEnd && !index.isOpenStart -> {
|
||||
list.subList(index.start!!.toInt(), list.size)
|
||||
}
|
||||
index.isOpenEnd && !index.isOpenStart -> {
|
||||
list.subList(index.start!!.toInt(), list.size)
|
||||
}
|
||||
|
||||
index.isOpenStart && index.isOpenEnd -> {
|
||||
list
|
||||
}
|
||||
index.isOpenStart && index.isOpenEnd -> {
|
||||
list
|
||||
}
|
||||
|
||||
else -> {
|
||||
throw RuntimeException("Can't apply range for index: $index")
|
||||
else -> {
|
||||
throw RuntimeException("Can't apply range for index: $index")
|
||||
}
|
||||
}.clear()
|
||||
} else {
|
||||
val start = range.toInt()
|
||||
val end = scp.requiredArg<ObjInt>(1).value.toInt() + 1
|
||||
self.list.subList(start, end).clear()
|
||||
}
|
||||
}.clear()
|
||||
} else {
|
||||
val start = range.toInt()
|
||||
val end = requiredArg<ObjInt>(1).value.toInt() + 1
|
||||
self.list.subList(start, end).clear()
|
||||
return self
|
||||
}
|
||||
}
|
||||
self
|
||||
}
|
||||
)
|
||||
|
||||
addFnDoc(
|
||||
name = "sortWith",
|
||||
doc = "Sort this list in-place using a comparator function (a, b) -> Int.",
|
||||
params = listOf(ParamDoc("comparator")),
|
||||
moduleName = "lyng.stdlib"
|
||||
) {
|
||||
val comparator = requireOnlyArg<Statement>()
|
||||
thisAs<ObjList>().quicksort { a, b -> comparator.call(this, a, b).toInt() }
|
||||
ObjVoid
|
||||
}
|
||||
moduleName = "lyng.stdlib",
|
||||
code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj {
|
||||
val comparator = scp.requireOnlyArg<Statement>()
|
||||
scp.thisAs<ObjList>().quicksort { a, b -> comparator.invokeCallable(scp, scp.thisObj, a, b).toInt() }
|
||||
return ObjVoid
|
||||
}
|
||||
}
|
||||
)
|
||||
addFnDoc(
|
||||
name = "shuffle",
|
||||
doc = "Shuffle elements of this list in-place.",
|
||||
moduleName = "lyng.stdlib"
|
||||
) {
|
||||
thisAs<ObjList>().list.shuffle()
|
||||
ObjVoid
|
||||
}
|
||||
moduleName = "lyng.stdlib",
|
||||
code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj {
|
||||
scp.thisAs<ObjList>().list.shuffle()
|
||||
return ObjVoid
|
||||
}
|
||||
}
|
||||
)
|
||||
addFnDoc(
|
||||
name = "sum",
|
||||
doc = "Sum elements using dynamic '+' or optimized integer path. Returns null for empty lists.",
|
||||
moduleName = "lyng.stdlib"
|
||||
) {
|
||||
val self = thisAs<ObjList>()
|
||||
val l = self.list
|
||||
if (l.isEmpty()) return@addFnDoc ObjNull
|
||||
if (net.sergeych.lyng.PerfFlags.PRIMITIVE_FASTOPS) {
|
||||
// Fast path: all ints → accumulate as long
|
||||
var i = 0
|
||||
var acc: Long = 0
|
||||
while (i < l.size) {
|
||||
val v = l[i]
|
||||
if (v is ObjInt) {
|
||||
acc += v.value
|
||||
i++
|
||||
} else {
|
||||
// Fallback to generic dynamic '+' accumulation starting from current acc
|
||||
var res: Obj = ObjInt(acc)
|
||||
moduleName = "lyng.stdlib",
|
||||
code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj {
|
||||
val self = scp.thisAs<ObjList>()
|
||||
val l = self.list
|
||||
if (l.isEmpty()) return ObjNull
|
||||
if (net.sergeych.lyng.PerfFlags.PRIMITIVE_FASTOPS) {
|
||||
// Fast path: all ints → accumulate as long
|
||||
var i = 0
|
||||
var acc: Long = 0
|
||||
while (i < l.size) {
|
||||
res = res.plus(this, l[i])
|
||||
i++
|
||||
val v = l[i]
|
||||
if (v is ObjInt) {
|
||||
acc += v.value
|
||||
i++
|
||||
} else {
|
||||
// Fallback to generic dynamic '+' accumulation starting from current acc
|
||||
var res: Obj = ObjInt(acc)
|
||||
while (i < l.size) {
|
||||
res = res.plus(scp, l[i])
|
||||
i++
|
||||
}
|
||||
return res
|
||||
}
|
||||
}
|
||||
return@addFnDoc res
|
||||
return ObjInt(acc)
|
||||
}
|
||||
// Generic path: dynamic '+' starting from first element
|
||||
var res: Obj = l[0]
|
||||
var k = 1
|
||||
while (k < l.size) {
|
||||
res = res.plus(scp, l[k])
|
||||
k++
|
||||
}
|
||||
return res
|
||||
}
|
||||
return@addFnDoc ObjInt(acc)
|
||||
}
|
||||
// Generic path: dynamic '+' starting from first element
|
||||
var res: Obj = l[0]
|
||||
var k = 1
|
||||
while (k < l.size) {
|
||||
res = res.plus(this, l[k])
|
||||
k++
|
||||
}
|
||||
res
|
||||
}
|
||||
)
|
||||
addFnDoc(
|
||||
name = "min",
|
||||
doc = "Minimum element by natural order. Returns null for empty lists.",
|
||||
moduleName = "lyng.stdlib"
|
||||
) {
|
||||
val l = thisAs<ObjList>().list
|
||||
if (l.isEmpty()) return@addFnDoc ObjNull
|
||||
if (net.sergeych.lyng.PerfFlags.PRIMITIVE_FASTOPS) {
|
||||
var i = 0
|
||||
var hasOnlyInts = true
|
||||
var minVal: Long = Long.MAX_VALUE
|
||||
while (i < l.size) {
|
||||
val v = l[i]
|
||||
if (v is ObjInt) {
|
||||
if (v.value < minVal) minVal = v.value
|
||||
} else {
|
||||
hasOnlyInts = false
|
||||
break
|
||||
moduleName = "lyng.stdlib",
|
||||
code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj {
|
||||
val l = scp.thisAs<ObjList>().list
|
||||
if (l.isEmpty()) return ObjNull
|
||||
if (net.sergeych.lyng.PerfFlags.PRIMITIVE_FASTOPS) {
|
||||
var i = 0
|
||||
var hasOnlyInts = true
|
||||
var minVal: Long = Long.MAX_VALUE
|
||||
while (i < l.size) {
|
||||
val v = l[i]
|
||||
if (v is ObjInt) {
|
||||
if (v.value < minVal) minVal = v.value
|
||||
} else {
|
||||
hasOnlyInts = false
|
||||
break
|
||||
}
|
||||
i++
|
||||
}
|
||||
if (hasOnlyInts) return ObjInt(minVal)
|
||||
}
|
||||
i++
|
||||
var res: Obj = l[0]
|
||||
var i = 1
|
||||
while (i < l.size) {
|
||||
val v = l[i]
|
||||
if (v.compareTo(scp, res) < 0) res = v
|
||||
i++
|
||||
}
|
||||
return res
|
||||
}
|
||||
if (hasOnlyInts) return@addFnDoc ObjInt(minVal)
|
||||
}
|
||||
var res: Obj = l[0]
|
||||
var i = 1
|
||||
while (i < l.size) {
|
||||
val v = l[i]
|
||||
if (v.compareTo(this, res) < 0) res = v
|
||||
i++
|
||||
}
|
||||
res
|
||||
}
|
||||
)
|
||||
addFnDoc(
|
||||
name = "max",
|
||||
doc = "Maximum element by natural order. Returns null for empty lists.",
|
||||
moduleName = "lyng.stdlib"
|
||||
) {
|
||||
val l = thisAs<ObjList>().list
|
||||
if (l.isEmpty()) return@addFnDoc ObjNull
|
||||
if (net.sergeych.lyng.PerfFlags.PRIMITIVE_FASTOPS) {
|
||||
var i = 0
|
||||
var hasOnlyInts = true
|
||||
var maxVal: Long = Long.MIN_VALUE
|
||||
while (i < l.size) {
|
||||
val v = l[i]
|
||||
if (v is ObjInt) {
|
||||
if (v.value > maxVal) maxVal = v.value
|
||||
} else {
|
||||
hasOnlyInts = false
|
||||
break
|
||||
moduleName = "lyng.stdlib",
|
||||
code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj {
|
||||
val l = scp.thisAs<ObjList>().list
|
||||
if (l.isEmpty()) return ObjNull
|
||||
if (net.sergeych.lyng.PerfFlags.PRIMITIVE_FASTOPS) {
|
||||
var i = 0
|
||||
var hasOnlyInts = true
|
||||
var maxVal: Long = Long.MIN_VALUE
|
||||
while (i < l.size) {
|
||||
val v = l[i]
|
||||
if (v is ObjInt) {
|
||||
if (v.value > maxVal) maxVal = v.value
|
||||
} else {
|
||||
hasOnlyInts = false
|
||||
break
|
||||
}
|
||||
i++
|
||||
}
|
||||
if (hasOnlyInts) return ObjInt(maxVal)
|
||||
}
|
||||
i++
|
||||
var res: Obj = l[0]
|
||||
var i = 1
|
||||
while (i < l.size) {
|
||||
val v = l[i]
|
||||
if (v.compareTo(scp, res) > 0) res = v
|
||||
i++
|
||||
}
|
||||
return res
|
||||
}
|
||||
if (hasOnlyInts) return@addFnDoc ObjInt(maxVal)
|
||||
}
|
||||
var res: Obj = l[0]
|
||||
var i = 1
|
||||
while (i < l.size) {
|
||||
val v = l[i]
|
||||
if (v.compareTo(this, res) > 0) res = v
|
||||
i++
|
||||
}
|
||||
res
|
||||
}
|
||||
)
|
||||
addFnDoc(
|
||||
name = "indexOf",
|
||||
doc = "Index of the first occurrence of the given element, or -1 if not found.",
|
||||
params = listOf(ParamDoc("element")),
|
||||
returns = type("lyng.Int"),
|
||||
moduleName = "lyng.stdlib"
|
||||
) {
|
||||
val l = thisAs<ObjList>().list
|
||||
val needle = args.firstAndOnly()
|
||||
if (net.sergeych.lyng.PerfFlags.PRIMITIVE_FASTOPS && needle is ObjInt) {
|
||||
var i = 0
|
||||
while (i < l.size) {
|
||||
val v = l[i]
|
||||
if (v is ObjInt && v.value == needle.value) return@addFnDoc ObjInt(i.toLong())
|
||||
i++
|
||||
moduleName = "lyng.stdlib",
|
||||
code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj {
|
||||
val l = scp.thisAs<ObjList>().list
|
||||
val needle = scp.args.firstAndOnly()
|
||||
if (net.sergeych.lyng.PerfFlags.PRIMITIVE_FASTOPS && needle is ObjInt) {
|
||||
var i = 0
|
||||
while (i < l.size) {
|
||||
val v = l[i]
|
||||
if (v is ObjInt && v.value == needle.value) return ObjInt(i.toLong())
|
||||
i++
|
||||
}
|
||||
return ObjInt((-1).toLong())
|
||||
}
|
||||
var i = 0
|
||||
while (i < l.size) {
|
||||
if (l[i].compareTo(scp, needle) == 0) return ObjInt(i.toLong())
|
||||
i++
|
||||
}
|
||||
return ObjInt((-1).toLong())
|
||||
}
|
||||
return@addFnDoc ObjInt((-1).toLong())
|
||||
}
|
||||
var i = 0
|
||||
while (i < l.size) {
|
||||
if (l[i].compareTo(this, needle) == 0) return@addFnDoc ObjInt(i.toLong())
|
||||
i++
|
||||
}
|
||||
ObjInt((-1).toLong())
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -20,6 +20,7 @@ package net.sergeych.lyng.obj
|
||||
import kotlinx.serialization.json.JsonElement
|
||||
import kotlinx.serialization.json.JsonObject
|
||||
import net.sergeych.lyng.Scope
|
||||
import net.sergeych.lyng.ScopeCallable
|
||||
import net.sergeych.lyng.Statement
|
||||
import net.sergeych.lyng.miniast.*
|
||||
import net.sergeych.lynon.LynonDecoder
|
||||
@ -78,21 +79,27 @@ class ObjMapEntry(val key: Obj, val value: Obj) : Obj() {
|
||||
doc = "Key component of this map entry.",
|
||||
type = type("lyng.Any"),
|
||||
moduleName = "lyng.stdlib",
|
||||
getter = { thisAs<ObjMapEntry>().key }
|
||||
getter = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj = scp.thisAs<ObjMapEntry>().key
|
||||
}
|
||||
)
|
||||
addPropertyDoc(
|
||||
name = "value",
|
||||
doc = "Value component of this map entry.",
|
||||
type = type("lyng.Any"),
|
||||
moduleName = "lyng.stdlib",
|
||||
getter = { thisAs<ObjMapEntry>().value }
|
||||
getter = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj = scp.thisAs<ObjMapEntry>().value
|
||||
}
|
||||
)
|
||||
addPropertyDoc(
|
||||
name = "size",
|
||||
doc = "Number of components in this entry (always 2).",
|
||||
type = type("lyng.Int"),
|
||||
moduleName = "lyng.stdlib",
|
||||
getter = { 2.toObj() }
|
||||
getter = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj = 2.toObj()
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
@ -181,9 +188,11 @@ class ObjMap(val map: MutableMap<Obj, Obj> = mutableMapOf()) : Obj() {
|
||||
}
|
||||
|
||||
override suspend fun toJson(scope: Scope): JsonElement {
|
||||
return JsonObject(
|
||||
map.map { it.key.toString(scope).value to it.value.toJson(scope) }.toMap()
|
||||
)
|
||||
val res = mutableMapOf<String, JsonElement>()
|
||||
for ((k, v) in map) {
|
||||
res[k.toString(scope).value] = v.toJson(scope)
|
||||
}
|
||||
return JsonObject(res)
|
||||
}
|
||||
|
||||
companion object {
|
||||
@ -224,94 +233,119 @@ class ObjMap(val map: MutableMap<Obj, Obj> = mutableMapOf()) : Obj() {
|
||||
}
|
||||
}.apply {
|
||||
implementingNames.add("Delegate")
|
||||
addFn("getValue") {
|
||||
val self = thisAs<ObjMap>()
|
||||
val key = requiredArg<Obj>(1)
|
||||
self.map[key] ?: ObjNull
|
||||
}
|
||||
addFn("setValue") {
|
||||
val self = thisAs<ObjMap>()
|
||||
val key = requiredArg<Obj>(1)
|
||||
val value = requiredArg<Obj>(2)
|
||||
self.map[key] = value
|
||||
self
|
||||
}
|
||||
addFn("bind") {
|
||||
val mode = requiredArg<ObjEnumEntry>(1)
|
||||
if( mode.ordinal.value > 1)
|
||||
raiseIllegalArgument("Map can be delegated only to val or var, got ${mode.name.value}")
|
||||
thisObj
|
||||
}
|
||||
addFn("getValue", code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj {
|
||||
val self = scp.thisAs<ObjMap>()
|
||||
val key = scp.requiredArg<Obj>(1)
|
||||
return self.map[key] ?: ObjNull
|
||||
}
|
||||
})
|
||||
addFn("setValue", code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj {
|
||||
val self = scp.thisAs<ObjMap>()
|
||||
val key = scp.requiredArg<Obj>(1)
|
||||
val value = scp.requiredArg<Obj>(2)
|
||||
self.map[key] = value
|
||||
return self
|
||||
}
|
||||
})
|
||||
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(
|
||||
name = "getOrNull",
|
||||
doc = "Get value by key or return null if the key is absent.",
|
||||
params = listOf(ParamDoc("key")),
|
||||
returns = type("lyng.Any", nullable = true),
|
||||
moduleName = "lyng.stdlib"
|
||||
) {
|
||||
val key = args.firstAndOnly(pos)
|
||||
thisAs<ObjMap>().map.getOrElse(key) { ObjNull }
|
||||
}
|
||||
moduleName = "lyng.stdlib",
|
||||
code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj {
|
||||
val key = scp.args.firstAndOnly(scp.pos)
|
||||
return scp.thisAs<ObjMap>().map.getOrElse(key) { ObjNull }
|
||||
}
|
||||
}
|
||||
)
|
||||
addFnDoc(
|
||||
name = "getOrPut",
|
||||
doc = "Get value by key or compute, store, and return the default from a lambda.",
|
||||
params = listOf(ParamDoc("key"), ParamDoc("default")),
|
||||
returns = type("lyng.Any"),
|
||||
moduleName = "lyng.stdlib"
|
||||
) {
|
||||
val key = requiredArg<Obj>(0)
|
||||
thisAs<ObjMap>().map.getOrPut(key) {
|
||||
val lambda = requiredArg<Statement>(1)
|
||||
lambda.execute(this)
|
||||
moduleName = "lyng.stdlib",
|
||||
code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj {
|
||||
val key = scp.requiredArg<Obj>(0)
|
||||
return scp.thisAs<ObjMap>().map.getOrPut(key) {
|
||||
val lambda = scp.requiredArg<Statement>(1)
|
||||
lambda.execute(scp)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
addPropertyDoc(
|
||||
name = "size",
|
||||
doc = "Number of entries in the map.",
|
||||
type = type("lyng.Int"),
|
||||
moduleName = "lyng.stdlib",
|
||||
getter = { (this.thisObj as ObjMap).map.size.toObj() }
|
||||
getter = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj = (scp.thisObj as ObjMap).map.size.toObj()
|
||||
}
|
||||
)
|
||||
addFnDoc(
|
||||
name = "remove",
|
||||
doc = "Remove the entry by key and return the previous value or null if absent.",
|
||||
params = listOf(ParamDoc("key")),
|
||||
returns = type("lyng.Any", nullable = true),
|
||||
moduleName = "lyng.stdlib"
|
||||
) {
|
||||
thisAs<ObjMap>().map.remove(requiredArg<Obj>(0))?.toObj() ?: ObjNull
|
||||
}
|
||||
moduleName = "lyng.stdlib",
|
||||
code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj {
|
||||
return scp.thisAs<ObjMap>().map.remove(scp.requiredArg<Obj>(0))?.toObj() ?: ObjNull
|
||||
}
|
||||
}
|
||||
)
|
||||
addFnDoc(
|
||||
name = "clear",
|
||||
doc = "Remove all entries from this map. Returns the map.",
|
||||
returns = type("lyng.Map"),
|
||||
moduleName = "lyng.stdlib"
|
||||
) {
|
||||
thisAs<ObjMap>().map.clear()
|
||||
thisObj
|
||||
}
|
||||
moduleName = "lyng.stdlib",
|
||||
code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj {
|
||||
scp.thisAs<ObjMap>().map.clear()
|
||||
return scp.thisObj
|
||||
}
|
||||
}
|
||||
)
|
||||
addPropertyDoc(
|
||||
name = "keys",
|
||||
doc = "List of keys in this map.",
|
||||
type = TypeGenericDoc(type("lyng.List"), listOf(type("lyng.Any"))),
|
||||
moduleName = "lyng.stdlib",
|
||||
getter = { thisAs<ObjMap>().map.keys.toObj() }
|
||||
getter = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj = scp.thisAs<ObjMap>().map.keys.toObj()
|
||||
}
|
||||
)
|
||||
addPropertyDoc(
|
||||
name = "values",
|
||||
doc = "List of values in this map.",
|
||||
type = TypeGenericDoc(type("lyng.List"), listOf(type("lyng.Any"))),
|
||||
moduleName = "lyng.stdlib",
|
||||
getter = { ObjList(thisAs<ObjMap>().map.values.toMutableList()) }
|
||||
getter = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj = ObjList(scp.thisAs<ObjMap>().map.values.toMutableList())
|
||||
}
|
||||
)
|
||||
addFnDoc(
|
||||
name = "iterator",
|
||||
doc = "Iterator over map entries as MapEntry objects.",
|
||||
returns = TypeGenericDoc(type("lyng.Iterator"), listOf(type("lyng.MapEntry"))),
|
||||
moduleName = "lyng.stdlib"
|
||||
) {
|
||||
ObjKotlinIterator(thisAs<ObjMap>().map.entries.iterator())
|
||||
}
|
||||
moduleName = "lyng.stdlib",
|
||||
code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj = ObjKotlinIterator(scp.thisAs<ObjMap>().map.entries.iterator())
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2025 Sergey S. Chernov real.sergeych@gmail.com
|
||||
* Copyright 2026 Sergey S. Chernov real.sergeych@gmail.com
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@ -40,12 +40,12 @@ class ObjMutex(val mutex: Mutex): Obj() {
|
||||
params = listOf(ParamDoc("action")),
|
||||
returns = type("lyng.Any"),
|
||||
moduleName = "lyng.stdlib"
|
||||
) {
|
||||
val f = requiredArg<Statement>(0)
|
||||
) { scp ->
|
||||
val f = scp.requiredArg<Statement>(0)
|
||||
// Execute user lambda directly in the current scope to preserve the active scope
|
||||
// ancestry across suspension points. The lambda still constructs a ClosureScope
|
||||
// on top of this frame, and parseLambdaExpression sets skipScopeCreation for its body.
|
||||
thisAs<ObjMutex>().mutex.withLock { f.execute(this) }
|
||||
scp.thisAs<ObjMutex>().mutex.withLock { f.execute(scp) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -18,6 +18,7 @@
|
||||
package net.sergeych.lyng.obj
|
||||
|
||||
import net.sergeych.lyng.Scope
|
||||
import net.sergeych.lyng.ScopeCallable
|
||||
import net.sergeych.lyng.miniast.TypeGenericDoc
|
||||
import net.sergeych.lyng.miniast.addFnDoc
|
||||
import net.sergeych.lyng.miniast.addPropertyDoc
|
||||
@ -115,17 +116,17 @@ class ObjRange(val start: Obj?, val end: Obj?, val isEndInclusive: Boolean) : Ob
|
||||
start is ObjChar && end is ObjChar
|
||||
}
|
||||
|
||||
override suspend fun enumerate(scope: Scope, callback: suspend (Obj) -> Boolean) {
|
||||
override suspend fun enumerate(scope: Scope, callback: EnumerateCallback) {
|
||||
if (start is ObjInt && end is ObjInt) {
|
||||
val s = start.value
|
||||
val e = end.value
|
||||
if (isEndInclusive) {
|
||||
for (i in s..e) {
|
||||
if (!callback(ObjInt.of(i))) break
|
||||
if (!callback.call(ObjInt.of(i))) break
|
||||
}
|
||||
} else {
|
||||
for (i in s..<e) {
|
||||
if (!callback(ObjInt.of(i))) break
|
||||
if (!callback.call(ObjInt.of(i))) break
|
||||
}
|
||||
}
|
||||
} 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
|
||||
if (isEndInclusive) {
|
||||
for (c in s..e) {
|
||||
if (!callback(ObjChar(c))) break
|
||||
if (!callback.call(ObjChar(c))) break
|
||||
}
|
||||
} else {
|
||||
for (c in s..<e) {
|
||||
if (!callback(ObjChar(c))) break
|
||||
if (!callback.call(ObjChar(c))) break
|
||||
}
|
||||
}
|
||||
} 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.",
|
||||
type = type("lyng.Any", nullable = true),
|
||||
moduleName = "lyng.stdlib",
|
||||
getter = { thisAs<ObjRange>().start ?: ObjNull }
|
||||
getter = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj = scp.thisAs<ObjRange>().start ?: ObjNull
|
||||
}
|
||||
)
|
||||
addPropertyDoc(
|
||||
name = "end",
|
||||
doc = "End bound of the range or null if open.",
|
||||
type = type("lyng.Any", nullable = true),
|
||||
moduleName = "lyng.stdlib",
|
||||
getter = { thisAs<ObjRange>().end ?: ObjNull }
|
||||
getter = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj = scp.thisAs<ObjRange>().end ?: ObjNull
|
||||
}
|
||||
)
|
||||
addPropertyDoc(
|
||||
name = "isOpen",
|
||||
doc = "Whether the range is open on either side (no start or no end).",
|
||||
type = type("lyng.Bool"),
|
||||
moduleName = "lyng.stdlib",
|
||||
getter = { thisAs<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(
|
||||
name = "isIntRange",
|
||||
doc = "True if both bounds are Int values.",
|
||||
type = type("lyng.Bool"),
|
||||
moduleName = "lyng.stdlib",
|
||||
getter = { thisAs<ObjRange>().isIntRange.toObj() }
|
||||
getter = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj = scp.thisAs<ObjRange>().isIntRange.toObj()
|
||||
}
|
||||
)
|
||||
addPropertyDoc(
|
||||
name = "isCharRange",
|
||||
doc = "True if both bounds are Char values.",
|
||||
type = type("lyng.Bool"),
|
||||
moduleName = "lyng.stdlib",
|
||||
getter = { thisAs<ObjRange>().isCharRange.toObj() }
|
||||
getter = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj = scp.thisAs<ObjRange>().isCharRange.toObj()
|
||||
}
|
||||
)
|
||||
addPropertyDoc(
|
||||
name = "isEndInclusive",
|
||||
doc = "Whether the end bound is inclusive.",
|
||||
type = type("lyng.Bool"),
|
||||
moduleName = "lyng.stdlib",
|
||||
getter = { thisAs<ObjRange>().isEndInclusive.toObj() }
|
||||
getter = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj = scp.thisAs<ObjRange>().isEndInclusive.toObj()
|
||||
}
|
||||
)
|
||||
addFnDoc(
|
||||
name = "iterator",
|
||||
doc = "Iterator over elements in this range (optimized for Int ranges).",
|
||||
returns = TypeGenericDoc(type("lyng.Iterator"), listOf(type("lyng.Any"))),
|
||||
moduleName = "lyng.stdlib"
|
||||
) {
|
||||
val self = thisAs<ObjRange>()
|
||||
if (net.sergeych.lyng.PerfFlags.RANGE_FAST_ITER) {
|
||||
val s = self.start
|
||||
val e = self.end
|
||||
if (s is ObjInt && e is ObjInt) {
|
||||
val start = s.value.toInt()
|
||||
val endExclusive = (if (self.isEndInclusive) e.value.toInt() + 1 else e.value.toInt())
|
||||
// Only for ascending simple ranges; fall back otherwise
|
||||
if (start <= endExclusive) {
|
||||
return@addFnDoc ObjFastIntRangeIterator(start, endExclusive)
|
||||
moduleName = "lyng.stdlib",
|
||||
code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj {
|
||||
val self = scp.thisAs<ObjRange>()
|
||||
if (net.sergeych.lyng.PerfFlags.RANGE_FAST_ITER) {
|
||||
val s = self.start
|
||||
val e = self.end
|
||||
if (s is ObjInt && e is ObjInt) {
|
||||
val start = s.value.toInt()
|
||||
val endExclusive = (if (self.isEndInclusive) e.value.toInt() + 1 else e.value.toInt())
|
||||
// Only for ascending simple ranges; fall back otherwise
|
||||
if (start <= endExclusive) {
|
||||
return ObjFastIntRangeIterator(start, endExclusive)
|
||||
}
|
||||
}
|
||||
}
|
||||
return ObjRangeIterator(self).apply { init(scp) }
|
||||
}
|
||||
}
|
||||
ObjRangeIterator(self).apply { init() }
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2025 Sergey S. Chernov real.sergeych@gmail.com
|
||||
* Copyright 2026 Sergey S. Chernov real.sergeych@gmail.com
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@ -19,6 +19,7 @@ package net.sergeych.lyng.obj
|
||||
|
||||
import net.sergeych.lyng.PerfFlags
|
||||
import net.sergeych.lyng.Scope
|
||||
import net.sergeych.lyng.ScopeCallable
|
||||
|
||||
class ObjRangeIterator(val self: ObjRange) : Obj() {
|
||||
|
||||
@ -28,7 +29,7 @@ class ObjRangeIterator(val self: ObjRange) : Obj() {
|
||||
|
||||
override val objClass: ObjClass get() = type
|
||||
|
||||
fun Scope.init() {
|
||||
fun init(scope: Scope) {
|
||||
val s = self.start
|
||||
val e = self.end
|
||||
if (s is ObjInt && e is ObjInt) {
|
||||
@ -43,7 +44,7 @@ class ObjRangeIterator(val self: ObjRange) : Obj() {
|
||||
else
|
||||
(e.value.code - s.value.code)
|
||||
} else {
|
||||
raiseError("not implemented iterator for range of $this")
|
||||
scope.raiseError("not implemented iterator for range of $this")
|
||||
}
|
||||
}
|
||||
|
||||
@ -66,12 +67,14 @@ class ObjRangeIterator(val self: ObjRange) : Obj() {
|
||||
|
||||
companion object {
|
||||
val type = ObjClass("RangeIterator", ObjIterator).apply {
|
||||
addFn("hasNext") {
|
||||
thisAs<ObjRangeIterator>().hasNext().toObj()
|
||||
}
|
||||
addFn("next") {
|
||||
thisAs<ObjRangeIterator>().next(this)
|
||||
}
|
||||
addFn("hasNext", code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj =
|
||||
scp.thisAs<ObjRangeIterator>().hasNext().toObj()
|
||||
})
|
||||
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 {
|
||||
val type = ObjClass("FastIntRangeIterator", ObjIterator).apply {
|
||||
addFn("hasNext") { thisAs<ObjFastIntRangeIterator>().hasNext().toObj() }
|
||||
addFn("next") { thisAs<ObjFastIntRangeIterator>().next(this) }
|
||||
addFn("hasNext", code = object : ScopeCallable {
|
||||
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)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2025 Sergey S. Chernov real.sergeych@gmail.com
|
||||
* Copyright 2026 Sergey S. Chernov real.sergeych@gmail.com
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@ -21,6 +21,7 @@ import kotlinx.serialization.json.JsonElement
|
||||
import kotlinx.serialization.json.JsonPrimitive
|
||||
import net.sergeych.lyng.Pos
|
||||
import net.sergeych.lyng.Scope
|
||||
import net.sergeych.lyng.ScopeCallable
|
||||
import net.sergeych.lyng.miniast.addConstDoc
|
||||
import net.sergeych.lyng.miniast.addFnDoc
|
||||
import net.sergeych.lyng.miniast.type
|
||||
@ -126,9 +127,9 @@ data class ObjReal(val value: Double) : Obj(), Numeric {
|
||||
// roundToInt: number rounded to the nearest integer
|
||||
addConstDoc(
|
||||
name = "roundToInt",
|
||||
value = statement(Pos.builtIn) {
|
||||
(it.thisObj as ObjReal).value.roundToLong().toObj()
|
||||
},
|
||||
value = statement(Pos.builtIn, f = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj = (scp.thisObj as ObjReal).value.roundToLong().toObj()
|
||||
}),
|
||||
doc = "This real number rounded to the nearest integer.",
|
||||
type = type("lyng.Int"),
|
||||
moduleName = "lyng.stdlib"
|
||||
@ -137,10 +138,11 @@ data class ObjReal(val value: Double) : Obj(), Numeric {
|
||||
name = "toInt",
|
||||
doc = "Truncate this real number toward zero to an integer.",
|
||||
returns = type("lyng.Int"),
|
||||
moduleName = "lyng.stdlib"
|
||||
) {
|
||||
ObjInt.of(thisAs<ObjReal>().value.toLong())
|
||||
}
|
||||
moduleName = "lyng.stdlib",
|
||||
code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj = ObjInt.of(scp.thisAs<ObjReal>().value.toLong())
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -64,9 +64,40 @@ sealed interface ObjRef {
|
||||
}
|
||||
}
|
||||
|
||||
interface RecordProvider {
|
||||
suspend fun getRecord(scope: Scope): ObjRecord
|
||||
}
|
||||
|
||||
fun interface FieldGetter {
|
||||
suspend fun call(obj: Obj, scope: Scope): ObjRecord
|
||||
}
|
||||
|
||||
fun interface FieldSetter {
|
||||
suspend fun call(obj: Obj, scope: Scope, value: Obj)
|
||||
}
|
||||
|
||||
fun interface IndexGetter {
|
||||
suspend fun call(obj: Obj, scope: Scope, index: Obj): Obj
|
||||
}
|
||||
|
||||
fun interface IndexSetter {
|
||||
suspend fun call(obj: Obj, scope: Scope, index: Obj, value: Obj)
|
||||
}
|
||||
|
||||
fun interface MethodInvoker {
|
||||
suspend fun call(obj: Obj, scope: Scope, args: Arguments): Obj
|
||||
}
|
||||
|
||||
/** Runtime-computed read-only reference backed by a lambda. */
|
||||
class ValueFnRef(private val fn: suspend (Scope) -> ObjRecord) : ObjRef {
|
||||
override suspend fun get(scope: Scope): ObjRecord = fn(scope)
|
||||
class ValueFnRef(private val provider: RecordProvider) : ObjRef {
|
||||
|
||||
constructor(fn: suspend (Scope) -> ObjRecord) : this(object : RecordProvider {
|
||||
override suspend fun getRecord(scope: Scope): ObjRecord = fn(scope)
|
||||
})
|
||||
|
||||
override suspend fun get(scope: Scope): ObjRecord {
|
||||
return provider.getRecord(scope)
|
||||
}
|
||||
}
|
||||
|
||||
/** Unary operations supported by ObjRef. */
|
||||
@ -523,16 +554,16 @@ class FieldRef(
|
||||
) : ObjRef {
|
||||
// 4-entry PIC for reads/writes (guarded by PerfFlags.FIELD_PIC)
|
||||
// Reads
|
||||
private var rKey1: Long = 0L; private var rVer1: Int = -1; private var rGetter1: (suspend (Obj, Scope) -> ObjRecord)? = null
|
||||
private var rKey2: Long = 0L; private var rVer2: Int = -1; private var rGetter2: (suspend (Obj, Scope) -> ObjRecord)? = null
|
||||
private var rKey3: Long = 0L; private var rVer3: Int = -1; private var rGetter3: (suspend (Obj, Scope) -> ObjRecord)? = null
|
||||
private var rKey4: Long = 0L; private var rVer4: Int = -1; private var rGetter4: (suspend (Obj, Scope) -> ObjRecord)? = null
|
||||
private var rKey1: Long = 0L; private var rVer1: Int = -1; private var rGetter1: FieldGetter? = null
|
||||
private var rKey2: Long = 0L; private var rVer2: Int = -1; private var rGetter2: FieldGetter? = null
|
||||
private var rKey3: Long = 0L; private var rVer3: Int = -1; private var rGetter3: FieldGetter? = null
|
||||
private var rKey4: Long = 0L; private var rVer4: Int = -1; private var rGetter4: FieldGetter? = null
|
||||
|
||||
// Writes
|
||||
private var wKey1: Long = 0L; private var wVer1: Int = -1; private var wSetter1: (suspend (Obj, Scope, Obj) -> Unit)? = null
|
||||
private var wKey2: Long = 0L; private var wVer2: Int = -1; private var wSetter2: (suspend (Obj, Scope, Obj) -> Unit)? = null
|
||||
private var wKey3: Long = 0L; private var wVer3: Int = -1; private var wSetter3: (suspend (Obj, Scope, Obj) -> Unit)? = null
|
||||
private var wKey4: Long = 0L; private var wVer4: Int = -1; private var wSetter4: (suspend (Obj, Scope, Obj) -> Unit)? = null
|
||||
private var wKey1: Long = 0L; private var wVer1: Int = -1; private var wSetter1: FieldSetter? = null
|
||||
private var wKey2: Long = 0L; private var wVer2: Int = -1; private var wSetter2: FieldSetter? = null
|
||||
private var wKey3: Long = 0L; private var wVer3: Int = -1; private var wSetter3: FieldSetter? = null
|
||||
private var wKey4: Long = 0L; private var wVer4: Int = -1; private var wSetter4: FieldSetter? = null
|
||||
|
||||
// Transient per-step cache to optimize read-then-write sequences within the same frame
|
||||
private var tKey: Long = 0L; private var tVer: Int = -1; private var tFrameId: Long = -1L; private var tRecord: ObjRecord? = null
|
||||
@ -598,7 +629,7 @@ class FieldRef(
|
||||
rGetter1?.let { g -> if (key == rKey1 && ver == rVer1) {
|
||||
if (picCounters) PerfStats.fieldPicHit++
|
||||
noteReadHit()
|
||||
val rec0 = g(base, scope)
|
||||
val rec0 = g.call(base, scope)
|
||||
if (base is ObjClass) {
|
||||
val idx0 = base.classScope?.getSlotIndexOf(name)
|
||||
if (idx0 != null) { tKey = key; tVer = ver; tFrameId = scope.frameId; tRecord = rec0 } else { tRecord = null }
|
||||
@ -612,7 +643,7 @@ class FieldRef(
|
||||
val tK = rKey2; val tV = rVer2; val tG = rGetter2
|
||||
rKey2 = rKey1; rVer2 = rVer1; rGetter2 = rGetter1
|
||||
rKey1 = tK; rVer1 = tV; rGetter1 = tG
|
||||
val rec0 = g(base, scope)
|
||||
val rec0 = g.call(base, scope)
|
||||
if (base is ObjClass) {
|
||||
val idx0 = base.classScope?.getSlotIndexOf(name)
|
||||
if (idx0 != null) { tKey = key; tVer = ver; tFrameId = scope.frameId; tRecord = rec0 } else { tRecord = null }
|
||||
@ -627,7 +658,7 @@ class FieldRef(
|
||||
rKey3 = rKey2; rVer3 = rVer2; rGetter3 = rGetter2
|
||||
rKey2 = rKey1; rVer2 = rVer1; rGetter2 = rGetter1
|
||||
rKey1 = tK; rVer1 = tV; rGetter1 = tG
|
||||
val rec0 = g(base, scope)
|
||||
val rec0 = g.call(base, scope)
|
||||
if (base is ObjClass) {
|
||||
val idx0 = base.classScope?.getSlotIndexOf(name)
|
||||
if (idx0 != null) { tKey = key; tVer = ver; tFrameId = scope.frameId; tRecord = rec0 } else { tRecord = null }
|
||||
@ -643,7 +674,7 @@ class FieldRef(
|
||||
rKey3 = rKey2; rVer3 = rVer2; rGetter3 = rGetter2
|
||||
rKey2 = rKey1; rVer2 = rVer1; rGetter2 = rGetter1
|
||||
rKey1 = tK; rVer1 = tV; rGetter1 = tG
|
||||
val rec0 = g(base, scope)
|
||||
val rec0 = g.call(base, scope)
|
||||
if (base is ObjClass) {
|
||||
val idx0 = base.classScope?.getSlotIndexOf(name)
|
||||
if (idx0 != null) { tKey = key; tVer = ver; tFrameId = scope.frameId; tRecord = rec0 } else { tRecord = null }
|
||||
@ -660,7 +691,7 @@ class FieldRef(
|
||||
rKey4 = rKey3; rVer4 = rVer3; rGetter4 = rGetter3
|
||||
rKey3 = rKey2; rVer3 = rVer2; rGetter3 = rGetter2
|
||||
rKey2 = rKey1; rVer2 = rVer1; rGetter2 = rGetter1
|
||||
rKey1 = key; rVer1 = ver; rGetter1 = { _, sc -> sc.raiseError(e.message ?: "no such field: $name") }
|
||||
rKey1 = key; rVer1 = ver; rGetter1 = FieldGetter { _, sc -> sc.raiseError(e.message ?: "no such field: $name") }
|
||||
throw e
|
||||
}
|
||||
// Install move-to-front with a handle-aware getter; honor PIC size flag
|
||||
@ -674,7 +705,7 @@ class FieldRef(
|
||||
val clsScope = base.classScope
|
||||
val capturedIdx = clsScope?.getSlotIndexOf(name)
|
||||
if (clsScope != null && capturedIdx != null) {
|
||||
rKey1 = key; rVer1 = ver; rGetter1 = { obj, sc ->
|
||||
rKey1 = key; rVer1 = ver; rGetter1 = FieldGetter { obj, sc ->
|
||||
val scope0 = (obj as ObjClass).classScope!!
|
||||
val r0 = scope0.getSlotRecord(capturedIdx)
|
||||
if (!r0.visibility.isPublic)
|
||||
@ -682,14 +713,14 @@ class FieldRef(
|
||||
r0
|
||||
}
|
||||
} else {
|
||||
rKey1 = key; rVer1 = ver; rGetter1 = { obj, sc -> obj.readField(sc, name) }
|
||||
rKey1 = key; rVer1 = ver; rGetter1 = FieldGetter { obj, sc -> obj.readField(sc, name) }
|
||||
}
|
||||
}
|
||||
is ObjInstance -> {
|
||||
val cls = base.objClass
|
||||
val effectiveKey = cls.publicMemberResolution[name]
|
||||
if (effectiveKey != null) {
|
||||
rKey1 = key; rVer1 = ver; rGetter1 = { obj, sc ->
|
||||
rKey1 = key; rVer1 = ver; rGetter1 = FieldGetter { obj, sc ->
|
||||
if (obj is ObjInstance && obj.objClass === cls) {
|
||||
val rec = obj.instanceScope.objects[effectiveKey]
|
||||
if (rec != null && rec.type != ObjRecord.Type.Delegated) rec
|
||||
@ -697,12 +728,12 @@ class FieldRef(
|
||||
} else obj.readField(sc, name)
|
||||
}
|
||||
} else {
|
||||
rKey1 = key; rVer1 = ver; rGetter1 = { obj, sc -> obj.readField(sc, name) }
|
||||
rKey1 = key; rVer1 = ver; rGetter1 = FieldGetter { obj, sc -> obj.readField(sc, name) }
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
// For instances and other types, fall back to name-based lookup per access (slot index may differ per instance)
|
||||
rKey1 = key; rVer1 = ver; rGetter1 = { obj, sc -> obj.readField(sc, name) }
|
||||
rKey1 = key; rVer1 = ver; rGetter1 = FieldGetter { obj, sc -> obj.readField(sc, name) }
|
||||
}
|
||||
}
|
||||
return rec
|
||||
@ -745,7 +776,7 @@ class FieldRef(
|
||||
wSetter1?.let { s -> if (key == wKey1 && ver == wVer1) {
|
||||
if (picCounters) PerfStats.fieldPicSetHit++
|
||||
noteWriteHit()
|
||||
return s(base, scope, newValue)
|
||||
return s.call(base, scope, newValue)
|
||||
} }
|
||||
wSetter2?.let { s -> if (key == wKey2 && ver == wVer2) {
|
||||
if (picCounters) PerfStats.fieldPicSetHit++
|
||||
@ -754,7 +785,7 @@ class FieldRef(
|
||||
val tK = wKey2; val tV = wVer2; val tS = wSetter2
|
||||
wKey2 = wKey1; wVer2 = wVer1; wSetter2 = wSetter1
|
||||
wKey1 = tK; wVer1 = tV; wSetter1 = tS
|
||||
return s(base, scope, newValue)
|
||||
return s.call(base, scope, newValue)
|
||||
} }
|
||||
if (size4WritesEnabled()) wSetter3?.let { s -> if (key == wKey3 && ver == wVer3) {
|
||||
if (picCounters) PerfStats.fieldPicSetHit++
|
||||
@ -764,7 +795,7 @@ class FieldRef(
|
||||
wKey3 = wKey2; wVer3 = wVer2; wSetter3 = wSetter2
|
||||
wKey2 = wKey1; wVer2 = wVer1; wSetter2 = wSetter1
|
||||
wKey1 = tK; wVer1 = tV; wSetter1 = tS
|
||||
return s(base, scope, newValue)
|
||||
return s.call(base, scope, newValue)
|
||||
} }
|
||||
if (size4WritesEnabled()) wSetter4?.let { s -> if (key == wKey4 && ver == wVer4) {
|
||||
if (picCounters) PerfStats.fieldPicSetHit++
|
||||
@ -775,7 +806,7 @@ class FieldRef(
|
||||
wKey3 = wKey2; wVer3 = wVer2; wSetter3 = wSetter2
|
||||
wKey2 = wKey1; wVer2 = wVer1; wSetter2 = wSetter1
|
||||
wKey1 = tK; wVer1 = tV; wSetter1 = tS
|
||||
return s(base, scope, newValue)
|
||||
return s.call(base, scope, newValue)
|
||||
} }
|
||||
// Slow path
|
||||
if (picCounters) PerfStats.fieldPicSetMiss++
|
||||
@ -792,7 +823,7 @@ class FieldRef(
|
||||
val clsScope = base.classScope
|
||||
val capturedIdx = clsScope?.getSlotIndexOf(name)
|
||||
if (clsScope != null && capturedIdx != null) {
|
||||
wKey1 = key; wVer1 = ver; wSetter1 = { obj, sc, v ->
|
||||
wKey1 = key; wVer1 = ver; wSetter1 = FieldSetter { obj, sc, v ->
|
||||
val scope0 = (obj as ObjClass).classScope!!
|
||||
val r0 = scope0.getSlotRecord(capturedIdx)
|
||||
if (!r0.isMutable)
|
||||
@ -800,14 +831,14 @@ class FieldRef(
|
||||
if (r0.value.assign(sc, v) == null) r0.value = v
|
||||
}
|
||||
} else {
|
||||
wKey1 = key; wVer1 = ver; wSetter1 = { obj, sc, v -> obj.writeField(sc, name, v) }
|
||||
wKey1 = key; wVer1 = ver; wSetter1 = FieldSetter { obj, sc, v -> obj.writeField(sc, name, v) }
|
||||
}
|
||||
}
|
||||
is ObjInstance -> {
|
||||
val cls = base.objClass
|
||||
val effectiveKey = cls.publicMemberResolution[name]
|
||||
if (effectiveKey != null) {
|
||||
wKey1 = key; wVer1 = ver; wSetter1 = { obj, sc, nv ->
|
||||
wKey1 = key; wVer1 = ver; wSetter1 = FieldSetter { obj, sc, nv ->
|
||||
if (obj is ObjInstance && obj.objClass === cls) {
|
||||
val rec = obj.instanceScope.objects[effectiveKey]
|
||||
if (rec != null && rec.effectiveWriteVisibility == Visibility.Public && rec.isMutable && rec.type == ObjRecord.Type.Field) {
|
||||
@ -816,12 +847,12 @@ class FieldRef(
|
||||
} else obj.writeField(sc, name, nv)
|
||||
}
|
||||
} else {
|
||||
wKey1 = key; wVer1 = ver; wSetter1 = { obj, sc, v -> obj.writeField(sc, name, v) }
|
||||
wKey1 = key; wVer1 = ver; wSetter1 = FieldSetter { obj, sc, v -> obj.writeField(sc, name, v) }
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
// For instances and other types, fall back to generic write (instance slot indices may differ per instance)
|
||||
wKey1 = key; wVer1 = ver; wSetter1 = { obj, sc, v -> obj.writeField(sc, name, v) }
|
||||
wKey1 = key; wVer1 = ver; wSetter1 = FieldSetter { obj, sc, v -> obj.writeField(sc, name, v) }
|
||||
}
|
||||
}
|
||||
return
|
||||
@ -853,14 +884,14 @@ class FieldRef(
|
||||
if (key != 0L) {
|
||||
rGetter1?.let { g -> if (key == rKey1 && ver == rVer1) {
|
||||
if (picCounters) PerfStats.fieldPicHit++
|
||||
return g(base, scope).value
|
||||
return g.call(base, scope).value
|
||||
} }
|
||||
rGetter2?.let { g -> if (key == rKey2 && ver == rVer2) {
|
||||
if (picCounters) PerfStats.fieldPicHit++
|
||||
val tK = rKey2; val tV = rVer2; val tG = rGetter2
|
||||
rKey2 = rKey1; rVer2 = rVer1; rGetter2 = rGetter1
|
||||
rKey1 = tK; rVer1 = tV; rGetter1 = tG
|
||||
return g(base, scope).value
|
||||
return g.call(base, scope).value
|
||||
} }
|
||||
if (size4ReadsEnabled()) rGetter3?.let { g -> if (key == rKey3 && ver == rVer3) {
|
||||
if (picCounters) PerfStats.fieldPicHit++
|
||||
@ -868,7 +899,7 @@ class FieldRef(
|
||||
rKey3 = rKey2; rVer3 = rVer2; rGetter3 = rGetter2
|
||||
rKey2 = rKey1; rVer2 = rVer1; rGetter2 = rGetter1
|
||||
rKey1 = tK; rVer1 = tV; rGetter1 = tG
|
||||
return g(base, scope).value
|
||||
return g.call(base, scope).value
|
||||
} }
|
||||
if (size4ReadsEnabled()) rGetter4?.let { g -> if (key == rKey4 && ver == rVer4) {
|
||||
if (picCounters) PerfStats.fieldPicHit++
|
||||
@ -877,12 +908,12 @@ class FieldRef(
|
||||
rKey3 = rKey2; rVer3 = rVer2; rGetter3 = rGetter2
|
||||
rKey2 = rKey1; rVer2 = rVer1; rGetter2 = rGetter1
|
||||
rKey1 = tK; rVer1 = tV; rGetter1 = tG
|
||||
return g(base, scope).value
|
||||
return g.call(base, scope).value
|
||||
} }
|
||||
if (picCounters) PerfStats.fieldPicMiss++
|
||||
val rec = base.readField(scope, name)
|
||||
// install primary generic getter for this shape
|
||||
rKey1 = key; rVer1 = ver; rGetter1 = { obj, sc -> obj.readField(sc, name) }
|
||||
rKey1 = key; rVer1 = ver; rGetter1 = FieldGetter { obj, sc -> obj.readField(sc, name) }
|
||||
return rec.value
|
||||
}
|
||||
}
|
||||
@ -899,16 +930,16 @@ class IndexRef(
|
||||
private val isOptional: Boolean,
|
||||
) : ObjRef {
|
||||
// Tiny 4-entry PIC for index reads (guarded implicitly by RVAL_FASTPATH); move-to-front on hits
|
||||
private var rKey1: Long = 0L; private var rVer1: Int = -1; private var rGetter1: (suspend (Obj, Scope, Obj) -> Obj)? = null
|
||||
private var rKey2: Long = 0L; private var rVer2: Int = -1; private var rGetter2: (suspend (Obj, Scope, Obj) -> Obj)? = null
|
||||
private var rKey3: Long = 0L; private var rVer3: Int = -1; private var rGetter3: (suspend (Obj, Scope, Obj) -> Obj)? = null
|
||||
private var rKey4: Long = 0L; private var rVer4: Int = -1; private var rGetter4: (suspend (Obj, Scope, Obj) -> Obj)? = null
|
||||
private var rKey1: Long = 0L; private var rVer1: Int = -1; private var rGetter1: IndexGetter? = null
|
||||
private var rKey2: Long = 0L; private var rVer2: Int = -1; private var rGetter2: IndexGetter? = null
|
||||
private var rKey3: Long = 0L; private var rVer3: Int = -1; private var rGetter3: IndexGetter? = null
|
||||
private var rKey4: Long = 0L; private var rVer4: Int = -1; private var rGetter4: IndexGetter? = null
|
||||
|
||||
// Tiny 4-entry PIC for index writes
|
||||
private var wKey1: Long = 0L; private var wVer1: Int = -1; private var wSetter1: (suspend (Obj, Scope, Obj, Obj) -> Unit)? = null
|
||||
private var wKey2: Long = 0L; private var wVer2: Int = -1; private var wSetter2: (suspend (Obj, Scope, Obj, Obj) -> Unit)? = null
|
||||
private var wKey3: Long = 0L; private var wVer3: Int = -1; private var wSetter3: (suspend (Obj, Scope, Obj, Obj) -> Unit)? = null
|
||||
private var wKey4: Long = 0L; private var wVer4: Int = -1; private var wSetter4: (suspend (Obj, Scope, Obj, Obj) -> Unit)? = null
|
||||
private var wKey1: Long = 0L; private var wVer1: Int = -1; private var wSetter1: IndexSetter? = null
|
||||
private var wKey2: Long = 0L; private var wVer2: Int = -1; private var wSetter2: IndexSetter? = null
|
||||
private var wKey3: Long = 0L; private var wVer3: Int = -1; private var wSetter3: IndexSetter? = null
|
||||
private var wKey4: Long = 0L; private var wVer4: Int = -1; private var wSetter4: IndexSetter? = null
|
||||
|
||||
private fun receiverKeyAndVersion(obj: Obj): Pair<Long, Int> = when (obj) {
|
||||
is ObjInstance -> obj.objClass.classId to obj.objClass.layoutVersion
|
||||
@ -949,14 +980,14 @@ class IndexRef(
|
||||
if (key != 0L) {
|
||||
rGetter1?.let { g -> if (key == rKey1 && ver == rVer1) {
|
||||
if (picCounters) PerfStats.indexPicHit++
|
||||
return g(base, scope, idx).asMutable
|
||||
return g.call(base, scope, idx).asMutable
|
||||
} }
|
||||
rGetter2?.let { g -> if (key == rKey2 && ver == rVer2) {
|
||||
if (picCounters) PerfStats.indexPicHit++
|
||||
val tk = rKey2; val tv = rVer2; val tg = rGetter2
|
||||
rKey2 = rKey1; rVer2 = rVer1; rGetter2 = rGetter1
|
||||
rKey1 = tk; rVer1 = tv; rGetter1 = tg
|
||||
return g(base, scope, idx).asMutable
|
||||
return g.call(base, scope, idx).asMutable
|
||||
} }
|
||||
if (PerfFlags.INDEX_PIC_SIZE_4) rGetter3?.let { g -> if (key == rKey3 && ver == rVer3) {
|
||||
if (picCounters) PerfStats.indexPicHit++
|
||||
@ -964,7 +995,7 @@ class IndexRef(
|
||||
rKey3 = rKey2; rVer3 = rVer2; rGetter3 = rGetter2
|
||||
rKey2 = rKey1; rVer2 = rVer1; rGetter2 = rGetter1
|
||||
rKey1 = tk; rVer1 = tv; rGetter1 = tg
|
||||
return g(base, scope, idx).asMutable
|
||||
return g.call(base, scope, idx).asMutable
|
||||
} }
|
||||
if (PerfFlags.INDEX_PIC_SIZE_4) rGetter4?.let { g -> if (key == rKey4 && ver == rVer4) {
|
||||
if (picCounters) PerfStats.indexPicHit++
|
||||
@ -973,7 +1004,7 @@ class IndexRef(
|
||||
rKey3 = rKey2; rVer3 = rVer2; rGetter3 = rGetter2
|
||||
rKey2 = rKey1; rVer2 = rVer1; rGetter2 = rGetter1
|
||||
rKey1 = tk; rVer1 = tv; rGetter1 = tg
|
||||
return g(base, scope, idx).asMutable
|
||||
return g.call(base, scope, idx).asMutable
|
||||
} }
|
||||
// Miss: resolve and install generic handler
|
||||
if (picCounters) PerfStats.indexPicMiss++
|
||||
@ -983,7 +1014,7 @@ class IndexRef(
|
||||
rKey3 = rKey2; rVer3 = rVer2; rGetter3 = rGetter2
|
||||
}
|
||||
rKey2 = rKey1; rVer2 = rVer1; rGetter2 = rGetter1
|
||||
rKey1 = key; rVer1 = ver; rGetter1 = { obj, sc, ix -> obj.getAt(sc, ix) }
|
||||
rKey1 = key; rVer1 = ver; rGetter1 = IndexGetter { obj, sc, ix -> obj.getAt(sc, ix) }
|
||||
return v.asMutable
|
||||
}
|
||||
}
|
||||
@ -1023,14 +1054,14 @@ class IndexRef(
|
||||
if (key != 0L) {
|
||||
rGetter1?.let { g -> if (key == rKey1 && ver == rVer1) {
|
||||
if (picCounters) PerfStats.indexPicHit++
|
||||
return g(base, scope, idx)
|
||||
return g.call(base, scope, idx)
|
||||
} }
|
||||
rGetter2?.let { g -> if (key == rKey2 && ver == rVer2) {
|
||||
if (picCounters) PerfStats.indexPicHit++
|
||||
val tk = rKey2; val tv = rVer2; val tg = rGetter2
|
||||
rKey2 = rKey1; rVer2 = rVer1; rGetter2 = rGetter1
|
||||
rKey1 = tk; rVer1 = tv; rGetter1 = tg
|
||||
return g(base, scope, idx)
|
||||
return g.call(base, scope, idx)
|
||||
} }
|
||||
if (PerfFlags.INDEX_PIC_SIZE_4) rGetter3?.let { g -> if (key == rKey3 && ver == rVer3) {
|
||||
if (picCounters) PerfStats.indexPicHit++
|
||||
@ -1038,7 +1069,7 @@ class IndexRef(
|
||||
rKey3 = rKey2; rVer3 = rVer2; rGetter3 = rGetter2
|
||||
rKey2 = rKey1; rVer2 = rVer1; rGetter2 = rGetter1
|
||||
rKey1 = tk; rVer1 = tv; rGetter1 = tg
|
||||
return g(base, scope, idx)
|
||||
return g.call(base, scope, idx)
|
||||
} }
|
||||
if (PerfFlags.INDEX_PIC_SIZE_4) rGetter4?.let { g -> if (key == rKey4 && ver == rVer4) {
|
||||
if (picCounters) PerfStats.indexPicHit++
|
||||
@ -1047,7 +1078,7 @@ class IndexRef(
|
||||
rKey3 = rKey2; rVer3 = rVer2; rGetter3 = rGetter2
|
||||
rKey2 = rKey1; rVer2 = rVer1; rGetter2 = rGetter1
|
||||
rKey1 = tk; rVer1 = tv; rGetter1 = tg
|
||||
return g(base, scope, idx)
|
||||
return g.call(base, scope, idx)
|
||||
} }
|
||||
if (picCounters) PerfStats.indexPicMiss++
|
||||
val v = base.getAt(scope, idx)
|
||||
@ -1056,7 +1087,7 @@ class IndexRef(
|
||||
rKey3 = rKey2; rVer3 = rVer2; rGetter3 = rGetter2
|
||||
}
|
||||
rKey2 = rKey1; rVer2 = rVer1; rGetter2 = rGetter1
|
||||
rKey1 = key; rVer1 = ver; rGetter1 = { obj, sc, ix -> obj.getAt(sc, ix) }
|
||||
rKey1 = key; rVer1 = ver; rGetter1 = IndexGetter { obj, sc, ix -> obj.getAt(sc, ix) }
|
||||
return v
|
||||
}
|
||||
}
|
||||
@ -1093,19 +1124,19 @@ class IndexRef(
|
||||
else -> { key = 0L; ver = -1 }
|
||||
}
|
||||
if (key != 0L) {
|
||||
wSetter1?.let { s -> if (key == wKey1 && ver == wVer1) { s(base, scope, idx, newValue); return } }
|
||||
wSetter1?.let { s -> if (key == wKey1 && ver == wVer1) { s.call(base, scope, idx, newValue); return } }
|
||||
wSetter2?.let { s -> if (key == wKey2 && ver == wVer2) {
|
||||
val tk = wKey2; val tv = wVer2; val ts = wSetter2
|
||||
wKey2 = wKey1; wVer2 = wVer1; wSetter2 = wSetter1
|
||||
wKey1 = tk; wVer1 = tv; wSetter1 = ts
|
||||
s(base, scope, idx, newValue); return
|
||||
s.call(base, scope, idx, newValue); return
|
||||
} }
|
||||
if (PerfFlags.INDEX_PIC_SIZE_4) wSetter3?.let { s -> if (key == wKey3 && ver == wVer3) {
|
||||
val tk = wKey3; val tv = wVer3; val ts = wSetter3
|
||||
wKey3 = wKey2; wVer3 = wVer2; wSetter3 = wSetter2
|
||||
wKey2 = wKey1; wVer2 = wVer1; wSetter2 = wSetter1
|
||||
wKey1 = tk; wVer1 = tv; wSetter1 = ts
|
||||
s(base, scope, idx, newValue); return
|
||||
s.call(base, scope, idx, newValue); return
|
||||
} }
|
||||
if (PerfFlags.INDEX_PIC_SIZE_4) wSetter4?.let { s -> if (key == wKey4 && ver == wVer4) {
|
||||
val tk = wKey4; val tv = wVer4; val ts = wSetter4
|
||||
@ -1113,7 +1144,7 @@ class IndexRef(
|
||||
wKey3 = wKey2; wVer3 = wVer2; wSetter3 = wSetter2
|
||||
wKey2 = wKey1; wVer2 = wVer1; wSetter2 = wSetter1
|
||||
wKey1 = tk; wVer1 = tv; wSetter1 = ts
|
||||
s(base, scope, idx, newValue); return
|
||||
s.call(base, scope, idx, newValue); return
|
||||
} }
|
||||
// Miss: perform write and install generic handler
|
||||
base.putAt(scope, idx, newValue)
|
||||
@ -1122,7 +1153,7 @@ class IndexRef(
|
||||
wKey3 = wKey2; wVer3 = wVer2; wSetter3 = wSetter2
|
||||
}
|
||||
wKey2 = wKey1; wVer2 = wVer1; wSetter2 = wSetter1
|
||||
wKey1 = key; wVer1 = ver; wSetter1 = { obj, sc, ix, v -> obj.putAt(sc, ix, v) }
|
||||
wKey1 = key; wVer1 = ver; wSetter1 = IndexSetter { obj, sc, ix, v -> obj.putAt(sc, ix, v) }
|
||||
return
|
||||
}
|
||||
}
|
||||
@ -1173,10 +1204,10 @@ class MethodCallRef(
|
||||
private val isOptional: Boolean,
|
||||
) : ObjRef {
|
||||
// 4-entry PIC for method invocations (guarded by PerfFlags.METHOD_PIC)
|
||||
private var mKey1: Long = 0L; private var mVer1: Int = -1; private var mInvoker1: (suspend (Obj, Scope, Arguments) -> Obj)? = null
|
||||
private var mKey2: Long = 0L; private var mVer2: Int = -1; private var mInvoker2: (suspend (Obj, Scope, Arguments) -> Obj)? = null
|
||||
private var mKey3: Long = 0L; private var mVer3: Int = -1; private var mInvoker3: (suspend (Obj, Scope, Arguments) -> Obj)? = null
|
||||
private var mKey4: Long = 0L; private var mVer4: Int = -1; private var mInvoker4: (suspend (Obj, Scope, Arguments) -> Obj)? = null
|
||||
private var mKey1: Long = 0L; private var mVer1: Int = -1; private var mInvoker1: MethodInvoker? = null
|
||||
private var mKey2: Long = 0L; private var mVer2: Int = -1; private var mInvoker2: MethodInvoker? = null
|
||||
private var mKey3: Long = 0L; private var mVer3: Int = -1; private var mInvoker3: MethodInvoker? = null
|
||||
private var mKey4: Long = 0L; private var mVer4: Int = -1; private var mInvoker4: MethodInvoker? = null
|
||||
|
||||
// Adaptive PIC (2→4) for methods
|
||||
private var mAccesses: Int = 0; private var mMisses: Int = 0; private var mPromotedTo4: Boolean = false
|
||||
@ -1274,7 +1305,7 @@ class MethodCallRef(
|
||||
if (key == mKey1 && ver == mVer1) {
|
||||
if (picCounters) PerfStats.methodPicHit++
|
||||
noteMethodHit()
|
||||
return inv(base, scope, callArgs)
|
||||
return inv.call(base, scope, callArgs)
|
||||
}
|
||||
}
|
||||
mInvoker2?.let { inv ->
|
||||
@ -1285,7 +1316,7 @@ class MethodCallRef(
|
||||
val tK = mKey2; val tV = mVer2; val tI = mInvoker2
|
||||
mKey2 = mKey1; mVer2 = mVer1; mInvoker2 = mInvoker1
|
||||
mKey1 = tK; mVer1 = tV; mInvoker1 = tI
|
||||
return inv(base, scope, callArgs)
|
||||
return inv.call(base, scope, callArgs)
|
||||
}
|
||||
}
|
||||
if (size4MethodsEnabled()) mInvoker3?.let { inv ->
|
||||
@ -1297,7 +1328,7 @@ class MethodCallRef(
|
||||
mKey3 = mKey2; mVer3 = mVer2; mInvoker3 = mInvoker2
|
||||
mKey2 = mKey1; mVer2 = mVer1; mInvoker2 = mInvoker1
|
||||
mKey1 = tK; mVer1 = tV; mInvoker1 = tI
|
||||
return inv(base, scope, callArgs)
|
||||
return inv.call(base, scope, callArgs)
|
||||
}
|
||||
}
|
||||
if (size4MethodsEnabled()) mInvoker4?.let { inv ->
|
||||
@ -1310,7 +1341,7 @@ class MethodCallRef(
|
||||
mKey3 = mKey2; mVer3 = mVer2; mInvoker3 = mInvoker2
|
||||
mKey2 = mKey1; mVer2 = mVer1; mInvoker2 = mInvoker1
|
||||
mKey1 = tK; mVer1 = tV; mInvoker1 = tI
|
||||
return inv(base, scope, callArgs)
|
||||
return inv.call(base, scope, callArgs)
|
||||
}
|
||||
}
|
||||
// Slow path
|
||||
@ -1323,7 +1354,7 @@ class MethodCallRef(
|
||||
mKey4 = mKey3; mVer4 = mVer3; mInvoker4 = mInvoker3
|
||||
mKey3 = mKey2; mVer3 = mVer2; mInvoker3 = mInvoker2
|
||||
mKey2 = mKey1; mVer2 = mVer1; mInvoker2 = mInvoker1
|
||||
mKey1 = key; mVer1 = ver; mInvoker1 = { _, sc, _ -> sc.raiseError(e.message ?: "method not found: $name") }
|
||||
mKey1 = key; mVer1 = ver; mInvoker1 = MethodInvoker { _, sc, _ -> sc.raiseError(e.message ?: "method not found: $name") }
|
||||
throw e
|
||||
}
|
||||
// Install move-to-front with a handle-aware invoker; honor PIC size flag
|
||||
@ -1361,15 +1392,15 @@ class MethodCallRef(
|
||||
val visibility = hierarchyMember.visibility
|
||||
val callable = hierarchyMember.value
|
||||
val decl = hierarchyMember.declaringClass ?: base.objClass
|
||||
mKey1 = key; mVer1 = ver; mInvoker1 = { obj, sc, a ->
|
||||
mKey1 = key; mVer1 = ver; mInvoker1 = MethodInvoker { obj, sc, a ->
|
||||
val inst = obj as ObjInstance
|
||||
if (!visibility.isPublic && !canAccessMember(visibility, decl, sc.currentClassCtx, name))
|
||||
sc.raiseError(ObjIllegalAccessException(sc, "can't invoke non-public method $name"))
|
||||
callable.invoke(inst.instanceScope, inst, a)
|
||||
sc.raiseError(ObjIllegalAccessException(sc, "can't invokeCallable non-public method $name"))
|
||||
callable.invokeCallable(inst.instanceScope, inst, a)
|
||||
}
|
||||
} else {
|
||||
// Fallback to name-based lookup per call (handles extensions and root members)
|
||||
mKey1 = key; mVer1 = ver; mInvoker1 = { obj, sc, a -> obj.invokeInstanceMethod(sc, name, a) }
|
||||
mKey1 = key; mVer1 = ver; mInvoker1 = MethodInvoker { obj, sc, a -> obj.invokeInstanceMethod(sc, name, a) }
|
||||
}
|
||||
}
|
||||
is ObjClass -> {
|
||||
@ -1377,13 +1408,13 @@ class MethodCallRef(
|
||||
val rec = clsScope?.get(name)
|
||||
if (rec != null) {
|
||||
val callable = rec.value
|
||||
mKey1 = key; mVer1 = ver; mInvoker1 = { obj, sc, a -> callable.invoke(sc, obj, a) }
|
||||
mKey1 = key; mVer1 = ver; mInvoker1 = MethodInvoker { obj, sc, a -> callable.invokeCallable(sc, obj, a) }
|
||||
} else {
|
||||
mKey1 = key; mVer1 = ver; mInvoker1 = { obj, sc, a -> obj.invokeInstanceMethod(sc, name, a) }
|
||||
mKey1 = key; mVer1 = ver; mInvoker1 = MethodInvoker { obj, sc, a -> obj.invokeInstanceMethod(sc, name, a) }
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
mKey1 = key; mVer1 = ver; mInvoker1 = { obj, sc, a -> obj.invokeInstanceMethod(sc, name, a) }
|
||||
mKey1 = key; mVer1 = ver; mInvoker1 = MethodInvoker { obj, sc, a -> obj.invokeInstanceMethod(sc, name, a) }
|
||||
}
|
||||
}
|
||||
return result
|
||||
|
||||
@ -20,6 +20,7 @@ package net.sergeych.lyng.obj
|
||||
import net.sergeych.lyng.PerfFlags
|
||||
import net.sergeych.lyng.RegexCache
|
||||
import net.sergeych.lyng.Scope
|
||||
import net.sergeych.lyng.ScopeCallable
|
||||
import net.sergeych.lyng.miniast.*
|
||||
|
||||
class ObjRegex(val regex: Regex) : Obj() {
|
||||
@ -49,29 +50,36 @@ class ObjRegex(val regex: Regex) : Obj() {
|
||||
doc = "Whether the entire string matches this regular expression.",
|
||||
params = listOf(ParamDoc("text", type("lyng.String"))),
|
||||
returns = type("lyng.Bool"),
|
||||
moduleName = "lyng.stdlib"
|
||||
) {
|
||||
ObjBool(args.firstAndOnly().toString().matches(thisAs<ObjRegex>().regex))
|
||||
}
|
||||
moduleName = "lyng.stdlib",
|
||||
code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj =
|
||||
ObjBool(scp.args.firstAndOnly().toString().matches(scp.thisAs<ObjRegex>().regex))
|
||||
}
|
||||
)
|
||||
addFnDoc(
|
||||
name = "find",
|
||||
doc = "Find the first match in the given string.",
|
||||
params = listOf(ParamDoc("text", type("lyng.String"))),
|
||||
returns = type("lyng.RegexMatch", nullable = true),
|
||||
moduleName = "lyng.stdlib"
|
||||
) {
|
||||
thisAs<ObjRegex>().find(requireOnlyArg<ObjString>())
|
||||
}
|
||||
moduleName = "lyng.stdlib",
|
||||
code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj =
|
||||
scp.thisAs<ObjRegex>().find(scp.requireOnlyArg<ObjString>())
|
||||
}
|
||||
)
|
||||
addFnDoc(
|
||||
name = "findAll",
|
||||
doc = "Find all matches in the given string.",
|
||||
params = listOf(ParamDoc("text", type("lyng.String"))),
|
||||
returns = TypeGenericDoc(type("lyng.List"), listOf(type("lyng.RegexMatch"))),
|
||||
moduleName = "lyng.stdlib"
|
||||
) {
|
||||
val s = requireOnlyArg<ObjString>().value
|
||||
ObjList(thisAs<ObjRegex>().regex.findAll(s).map { ObjRegexMatch(it) }.toMutableList())
|
||||
}
|
||||
moduleName = "lyng.stdlib",
|
||||
code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj {
|
||||
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.",
|
||||
type = TypeGenericDoc(type("lyng.List"), listOf(type("lyng.String"))),
|
||||
moduleName = "lyng.stdlib",
|
||||
getter = { thisAs<ObjRegexMatch>().objGroups }
|
||||
getter = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj = scp.thisAs<ObjRegexMatch>().objGroups
|
||||
}
|
||||
)
|
||||
addPropertyDoc(
|
||||
name = "value",
|
||||
doc = "The matched substring.",
|
||||
type = type("lyng.String"),
|
||||
moduleName = "lyng.stdlib",
|
||||
getter = { thisAs<ObjRegexMatch>().objValue }
|
||||
getter = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj = scp.thisAs<ObjRegexMatch>().objValue
|
||||
}
|
||||
)
|
||||
addPropertyDoc(
|
||||
name = "range",
|
||||
doc = "Range of the match in the input (end-exclusive).",
|
||||
type = type("lyng.Range"),
|
||||
moduleName = "lyng.stdlib",
|
||||
getter = { thisAs<ObjRegexMatch>().objRange }
|
||||
getter = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj = scp.thisAs<ObjRegexMatch>().objRange
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@ -18,6 +18,7 @@
|
||||
package net.sergeych.lyng.obj
|
||||
|
||||
import net.sergeych.lyng.Scope
|
||||
import net.sergeych.lyng.ScopeCallable
|
||||
import net.sergeych.lyng.miniast.*
|
||||
|
||||
class RingBuffer<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.",
|
||||
type = type("lyng.Int"),
|
||||
moduleName = "lyng.stdlib",
|
||||
getter = { thisAs<ObjRingBuffer>().capacity.toObj() }
|
||||
getter = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj = scp.thisAs<ObjRingBuffer>().capacity.toObj()
|
||||
}
|
||||
)
|
||||
addPropertyDoc(
|
||||
name = "size",
|
||||
doc = "Current number of elements in the buffer.",
|
||||
type = type("lyng.Int"),
|
||||
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(
|
||||
name = "iterator",
|
||||
doc = "Iterator over elements in insertion order (oldest to newest).",
|
||||
returns = TypeGenericDoc(type("lyng.Iterator"), listOf(type("lyng.Any"))),
|
||||
moduleName = "lyng.stdlib"
|
||||
) {
|
||||
val buffer = thisAs<ObjRingBuffer>().buffer
|
||||
ObjKotlinObjIterator(buffer.iterator())
|
||||
}
|
||||
moduleName = "lyng.stdlib",
|
||||
code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj {
|
||||
val buffer = scp.thisAs<ObjRingBuffer>().buffer
|
||||
return ObjKotlinObjIterator(buffer.iterator())
|
||||
}
|
||||
}
|
||||
)
|
||||
addFnDoc(
|
||||
name = "add",
|
||||
doc = "Append an element; if full, the oldest element is dropped.",
|
||||
params = listOf(ParamDoc("value", type("lyng.Any"))),
|
||||
returns = type("lyng.Void"),
|
||||
moduleName = "lyng.stdlib"
|
||||
) { thisAs<ObjRingBuffer>().apply { buffer.add(requireOnlyArg<Obj>()) } }
|
||||
moduleName = "lyng.stdlib",
|
||||
code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj {
|
||||
val self = scp.thisAs<ObjRingBuffer>()
|
||||
self.buffer.add(scp.requireOnlyArg<Obj>())
|
||||
return ObjVoid
|
||||
}
|
||||
}
|
||||
)
|
||||
addPropertyDoc(
|
||||
name = "first",
|
||||
doc = "Return the oldest element in the buffer.",
|
||||
type = type("lyng.Any"),
|
||||
moduleName = "lyng.stdlib",
|
||||
getter = {
|
||||
val buffer = (this.thisObj as ObjRingBuffer).buffer
|
||||
if (buffer.size == 0) ObjNull else buffer.first()
|
||||
getter = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj {
|
||||
val buffer = (scp.thisObj as ObjRingBuffer).buffer
|
||||
return if (buffer.size == 0) ObjNull else buffer.iterator().next()
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
@ -18,6 +18,7 @@
|
||||
package net.sergeych.lyng.obj
|
||||
|
||||
import net.sergeych.lyng.Scope
|
||||
import net.sergeych.lyng.ScopeCallable
|
||||
import net.sergeych.lyng.miniast.ParamDoc
|
||||
import net.sergeych.lyng.miniast.TypeGenericDoc
|
||||
import net.sergeych.lyng.miniast.addFnDoc
|
||||
@ -46,9 +47,9 @@ class ObjSet(val set: MutableSet<Obj> = mutableSetOf()) : Obj() {
|
||||
return set.contains(other)
|
||||
}
|
||||
|
||||
override suspend fun enumerate(scope: Scope, callback: suspend (Obj) -> Boolean) {
|
||||
override suspend fun enumerate(scope: Scope, callback: EnumerateCallback) {
|
||||
for (item in set) {
|
||||
if (!callback(item)) break
|
||||
if (!callback.call(item)) break
|
||||
}
|
||||
}
|
||||
|
||||
@ -164,56 +165,64 @@ class ObjSet(val set: MutableSet<Obj> = mutableSetOf()) : Obj() {
|
||||
name = "size",
|
||||
doc = "Number of elements in this set.",
|
||||
returns = type("lyng.Int"),
|
||||
moduleName = "lyng.stdlib"
|
||||
) {
|
||||
thisAs<ObjSet>().set.size.toObj()
|
||||
}
|
||||
moduleName = "lyng.stdlib",
|
||||
code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj = scp.thisAs<ObjSet>().set.size.toObj()
|
||||
}
|
||||
)
|
||||
addFnDoc(
|
||||
name = "intersect",
|
||||
doc = "Intersection with another set. Returns a new set.",
|
||||
params = listOf(ParamDoc("other", type("lyng.Set"))),
|
||||
returns = type("lyng.Set"),
|
||||
moduleName = "lyng.stdlib"
|
||||
) {
|
||||
thisAs<ObjSet>().mul(this, args.firstAndOnly())
|
||||
}
|
||||
moduleName = "lyng.stdlib",
|
||||
code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj = scp.thisAs<ObjSet>().mul(scp, scp.args.firstAndOnly())
|
||||
}
|
||||
)
|
||||
addFnDoc(
|
||||
name = "iterator",
|
||||
doc = "Iterator over elements of this set.",
|
||||
returns = TypeGenericDoc(type("lyng.Iterator"), listOf(type("lyng.Any"))),
|
||||
moduleName = "lyng.stdlib"
|
||||
) {
|
||||
thisAs<ObjSet>().set.iterator().toObj()
|
||||
}
|
||||
moduleName = "lyng.stdlib",
|
||||
code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj = ObjKotlinIterator(scp.thisAs<ObjSet>().set.iterator())
|
||||
}
|
||||
)
|
||||
addFnDoc(
|
||||
name = "union",
|
||||
doc = "Union with another set or iterable. Returns a new set.",
|
||||
params = listOf(ParamDoc("other")),
|
||||
returns = type("lyng.Set"),
|
||||
moduleName = "lyng.stdlib"
|
||||
) {
|
||||
thisAs<ObjSet>().plus(this, args.firstAndOnly())
|
||||
}
|
||||
moduleName = "lyng.stdlib",
|
||||
code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj = scp.thisAs<ObjSet>().plus(scp, scp.args.firstAndOnly())
|
||||
}
|
||||
)
|
||||
addFnDoc(
|
||||
name = "subtract",
|
||||
doc = "Subtract another set or iterable from this set. Returns a new set.",
|
||||
params = listOf(ParamDoc("other")),
|
||||
returns = type("lyng.Set"),
|
||||
moduleName = "lyng.stdlib"
|
||||
) {
|
||||
thisAs<ObjSet>().minus(this, args.firstAndOnly())
|
||||
}
|
||||
moduleName = "lyng.stdlib",
|
||||
code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj = scp.thisAs<ObjSet>().minus(scp, scp.args.firstAndOnly())
|
||||
}
|
||||
)
|
||||
addFnDoc(
|
||||
name = "remove",
|
||||
doc = "Remove one or more elements. Returns true if the set changed.",
|
||||
returns = type("lyng.Bool"),
|
||||
moduleName = "lyng.stdlib"
|
||||
) {
|
||||
val set = thisAs<ObjSet>().set
|
||||
val n = set.size
|
||||
for( x in args.list ) set -= x
|
||||
if( n == set.size ) ObjFalse else ObjTrue
|
||||
}
|
||||
moduleName = "lyng.stdlib",
|
||||
code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj {
|
||||
val set = scp.thisAs<ObjSet>().set
|
||||
val n = set.size
|
||||
for( x in scp.args.list ) set -= x
|
||||
return if( n == set.size ) ObjFalse else ObjTrue
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -24,6 +24,7 @@ import kotlinx.serialization.json.JsonPrimitive
|
||||
import net.sergeych.lyng.PerfFlags
|
||||
import net.sergeych.lyng.RegexCache
|
||||
import net.sergeych.lyng.Scope
|
||||
import net.sergeych.lyng.ScopeCallable
|
||||
import net.sergeych.lyng.miniast.*
|
||||
import net.sergeych.lynon.LynonDecoder
|
||||
import net.sergeych.lynon.LynonEncoder
|
||||
@ -139,205 +140,274 @@ data class ObjString(val value: String) : Obj() {
|
||||
name = "iterator",
|
||||
doc = "Iterator over characters of this string.",
|
||||
returns = TypeGenericDoc(type("lyng.Iterator"), listOf(type("lyng.Char"))),
|
||||
moduleName = "lyng.stdlib"
|
||||
) { ObjKotlinIterator(thisAs<ObjString>().value.iterator()) }
|
||||
moduleName = "lyng.stdlib",
|
||||
code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj = ObjKotlinIterator(scp.thisAs<ObjString>().value.iterator())
|
||||
}
|
||||
)
|
||||
addFnDoc(
|
||||
name = "toInt",
|
||||
doc = "Parse this string as an integer or throw if it is not a valid integer.",
|
||||
returns = type("lyng.Int"),
|
||||
moduleName = "lyng.stdlib"
|
||||
) {
|
||||
ObjInt.of(
|
||||
thisAs<ObjString>().value.toLongOrNull()
|
||||
?: raiseIllegalArgument("can't convert to int: $thisObj")
|
||||
)
|
||||
}
|
||||
moduleName = "lyng.stdlib",
|
||||
code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj {
|
||||
return ObjInt.of(
|
||||
scp.thisAs<ObjString>().value.toLongOrNull()
|
||||
?: scp.raiseIllegalArgument("can't convert to int: ${scp.thisObj}")
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
||||
addFnDoc(
|
||||
name = "startsWith",
|
||||
doc = "Whether this string starts with the given prefix.",
|
||||
params = listOf(ParamDoc("prefix", type("lyng.String"))),
|
||||
returns = type("lyng.Bool"),
|
||||
moduleName = "lyng.stdlib"
|
||||
) {
|
||||
ObjBool(thisAs<ObjString>().value.startsWith(requiredArg<ObjString>(0).value))
|
||||
}
|
||||
moduleName = "lyng.stdlib",
|
||||
code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj {
|
||||
return ObjBool(scp.thisAs<ObjString>().value.startsWith(scp.requiredArg<ObjString>(0).value))
|
||||
}
|
||||
}
|
||||
)
|
||||
addFnDoc(
|
||||
name = "endsWith",
|
||||
doc = "Whether this string ends with the given suffix.",
|
||||
params = listOf(ParamDoc("suffix", type("lyng.String"))),
|
||||
returns = type("lyng.Bool"),
|
||||
moduleName = "lyng.stdlib"
|
||||
) {
|
||||
ObjBool(thisAs<ObjString>().value.endsWith(requiredArg<ObjString>(0).value))
|
||||
}
|
||||
moduleName = "lyng.stdlib",
|
||||
code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj {
|
||||
return ObjBool(scp.thisAs<ObjString>().value.endsWith(scp.requiredArg<ObjString>(0).value))
|
||||
}
|
||||
}
|
||||
)
|
||||
addPropertyDoc(
|
||||
name = "length",
|
||||
doc = "Number of UTF-16 code units in this string.",
|
||||
type = type("lyng.Int"),
|
||||
moduleName = "lyng.stdlib",
|
||||
getter = { ObjInt.of((this.thisObj as ObjString).value.length.toLong()) }
|
||||
getter = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj = ObjInt.of((scp.thisObj as ObjString).value.length.toLong())
|
||||
}
|
||||
)
|
||||
addFnDoc(
|
||||
name = "takeLast",
|
||||
doc = "Return a string with the last N characters.",
|
||||
params = listOf(ParamDoc("n", type("lyng.Int"))),
|
||||
returns = type("lyng.String"),
|
||||
moduleName = "lyng.stdlib"
|
||||
) {
|
||||
thisAs<ObjString>().value.takeLast(
|
||||
requiredArg<ObjInt>(0).toInt()
|
||||
).let(::ObjString)
|
||||
}
|
||||
moduleName = "lyng.stdlib",
|
||||
code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj {
|
||||
return ObjString(
|
||||
scp.thisAs<ObjString>().value.takeLast(
|
||||
scp.requiredArg<ObjInt>(0).toInt()
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
||||
addFnDoc(
|
||||
name = "take",
|
||||
doc = "Return a string with the first N characters.",
|
||||
params = listOf(ParamDoc("n", type("lyng.Int"))),
|
||||
returns = type("lyng.String"),
|
||||
moduleName = "lyng.stdlib"
|
||||
) {
|
||||
thisAs<ObjString>().value.take(
|
||||
requiredArg<ObjInt>(0).toInt()
|
||||
).let(::ObjString)
|
||||
}
|
||||
moduleName = "lyng.stdlib",
|
||||
code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj {
|
||||
return ObjString(
|
||||
scp.thisAs<ObjString>().value.take(
|
||||
scp.requiredArg<ObjInt>(0).toInt()
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
||||
addFnDoc(
|
||||
name = "drop",
|
||||
doc = "Drop the first N characters and return the remainder.",
|
||||
params = listOf(ParamDoc("n", type("lyng.Int"))),
|
||||
returns = type("lyng.String"),
|
||||
moduleName = "lyng.stdlib"
|
||||
) {
|
||||
thisAs<ObjString>().value.drop(
|
||||
requiredArg<ObjInt>(0).toInt()
|
||||
).let(::ObjString)
|
||||
}
|
||||
moduleName = "lyng.stdlib",
|
||||
code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj {
|
||||
return ObjString(
|
||||
scp.thisAs<ObjString>().value.drop(
|
||||
scp.requiredArg<ObjInt>(0).toInt()
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
||||
addFnDoc(
|
||||
name = "dropLast",
|
||||
doc = "Drop the last N characters and return the remainder.",
|
||||
params = listOf(ParamDoc("n", type("lyng.Int"))),
|
||||
returns = type("lyng.String"),
|
||||
moduleName = "lyng.stdlib"
|
||||
) {
|
||||
thisAs<ObjString>().value.dropLast(
|
||||
requiredArg<ObjInt>(0).toInt()
|
||||
).let(::ObjString)
|
||||
}
|
||||
moduleName = "lyng.stdlib",
|
||||
code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj {
|
||||
return ObjString(
|
||||
scp.thisAs<ObjString>().value.dropLast(
|
||||
scp.requiredArg<ObjInt>(0).toInt()
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
||||
addFnDoc(
|
||||
name = "lower",
|
||||
doc = "Lowercase version of this string (default locale).",
|
||||
returns = type("lyng.String"),
|
||||
moduleName = "lyng.stdlib"
|
||||
) {
|
||||
thisAs<ObjString>().value.lowercase().let(::ObjString)
|
||||
}
|
||||
moduleName = "lyng.stdlib",
|
||||
code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj = ObjString(scp.thisAs<ObjString>().value.lowercase())
|
||||
}
|
||||
)
|
||||
addFnDoc(
|
||||
name = "lowercase",
|
||||
doc = "Lowercase version of this string (default locale).",
|
||||
returns = type("lyng.String"),
|
||||
moduleName = "lyng.stdlib"
|
||||
) {
|
||||
thisAs<ObjString>().value.lowercase().let(::ObjString)
|
||||
}
|
||||
moduleName = "lyng.stdlib",
|
||||
code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj = ObjString(scp.thisAs<ObjString>().value.lowercase())
|
||||
}
|
||||
)
|
||||
addFnDoc(
|
||||
name = "upper",
|
||||
doc = "Uppercase version of this string (default locale).",
|
||||
returns = type("lyng.String"),
|
||||
moduleName = "lyng.stdlib"
|
||||
) {
|
||||
thisAs<ObjString>().value.uppercase().let(::ObjString)
|
||||
}
|
||||
moduleName = "lyng.stdlib",
|
||||
code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj = ObjString(scp.thisAs<ObjString>().value.uppercase())
|
||||
}
|
||||
)
|
||||
addFnDoc(
|
||||
name = "uppercase",
|
||||
doc = "Uppercase version of this string (default locale).",
|
||||
returns = type("lyng.String"),
|
||||
moduleName = "lyng.stdlib"
|
||||
) {
|
||||
thisAs<ObjString>().value.uppercase().let(::ObjString)
|
||||
}
|
||||
moduleName = "lyng.stdlib",
|
||||
code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj = ObjString(scp.thisAs<ObjString>().value.uppercase())
|
||||
}
|
||||
)
|
||||
addPropertyDoc(
|
||||
name = "characters",
|
||||
doc = "List of characters of this string.",
|
||||
type = TypeGenericDoc(type("lyng.List"), listOf(type("lyng.Char"))),
|
||||
moduleName = "lyng.stdlib",
|
||||
getter = {
|
||||
ObjList(
|
||||
(this.thisObj as ObjString).value.map { ObjChar(it) }.toMutableList()
|
||||
)
|
||||
getter = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj {
|
||||
return ObjList(
|
||||
(scp.thisObj as ObjString).value.map { ObjChar(it) }.toMutableList()
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
||||
addFnDoc(
|
||||
name = "last",
|
||||
doc = "The last character of this string or throw if the string is empty.",
|
||||
returns = type("lyng.Char"),
|
||||
moduleName = "lyng.stdlib"
|
||||
) {
|
||||
ObjChar(thisAs<ObjString>().value.lastOrNull() ?: raiseNoSuchElement("empty string"))
|
||||
}
|
||||
moduleName = "lyng.stdlib",
|
||||
code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj {
|
||||
return ObjChar(scp.thisAs<ObjString>().value.lastOrNull() ?: scp.raiseNoSuchElement("empty string"))
|
||||
}
|
||||
}
|
||||
)
|
||||
addFnDoc(
|
||||
name = "encodeUtf8",
|
||||
doc = "Encode this string as UTF-8 bytes.",
|
||||
returns = type("lyng.Buffer"),
|
||||
moduleName = "lyng.stdlib"
|
||||
) { ObjBuffer(thisAs<ObjString>().value.encodeToByteArray().asUByteArray()) }
|
||||
moduleName = "lyng.stdlib",
|
||||
code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj =
|
||||
ObjBuffer(scp.thisAs<ObjString>().value.encodeToByteArray().asUByteArray())
|
||||
}
|
||||
)
|
||||
addPropertyDoc(
|
||||
name = "size",
|
||||
doc = "Alias for length: the number of characters (code units) in this string.",
|
||||
type = type("lyng.Int"),
|
||||
moduleName = "lyng.stdlib",
|
||||
getter = { ObjInt.of((this.thisObj as ObjString).value.length.toLong()) }
|
||||
getter = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj = ObjInt.of((scp.thisObj as ObjString).value.length.toLong())
|
||||
}
|
||||
)
|
||||
addFnDoc(
|
||||
name = "toReal",
|
||||
doc = "Parse this string as a real number (floating point).",
|
||||
returns = type("lyng.Real"),
|
||||
moduleName = "lyng.stdlib"
|
||||
) {
|
||||
ObjReal.of(thisAs<ObjString>().value.toDouble())
|
||||
}
|
||||
moduleName = "lyng.stdlib",
|
||||
code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj = ObjReal.of(scp.thisAs<ObjString>().value.toDouble())
|
||||
}
|
||||
)
|
||||
addFnDoc(
|
||||
name = "trim",
|
||||
doc = "Return a copy of this string with leading and trailing whitespace removed.",
|
||||
returns = type("lyng.String"),
|
||||
moduleName = "lyng.stdlib"
|
||||
) {
|
||||
thisAs<ObjString>().value.trim().let(::ObjString)
|
||||
}
|
||||
addFnDoc("isBlank", "Whether this string is empty or contains only whitespace characters.",
|
||||
returns = type("lyng.Bool"), moduleName = "lyng.stdlib") {
|
||||
ObjBool(thisAs<ObjString>().value.isBlank())
|
||||
}
|
||||
addFnDoc("isEmpty", "Whether this string is empty.",
|
||||
returns = type("lyng.Bool"), moduleName = "lyng.stdlib") {
|
||||
ObjBool(thisAs<ObjString>().value.isEmpty())
|
||||
}
|
||||
addFnDoc("isNotEmpty", "Whether this string is not empty.",
|
||||
returns = type("lyng.Bool"), moduleName = "lyng.stdlib") {
|
||||
ObjBool(thisAs<ObjString>().value.isNotEmpty())
|
||||
}
|
||||
moduleName = "lyng.stdlib",
|
||||
code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj = ObjString(scp.thisAs<ObjString>().value.trim())
|
||||
}
|
||||
)
|
||||
addFnDoc(
|
||||
name = "isBlank",
|
||||
doc = "Whether this string is empty or contains only whitespace characters.",
|
||||
returns = type("lyng.Bool"),
|
||||
moduleName = "lyng.stdlib",
|
||||
code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj = ObjBool(scp.thisAs<ObjString>().value.isBlank())
|
||||
}
|
||||
)
|
||||
addFnDoc(
|
||||
name = "isEmpty",
|
||||
doc = "Whether this string is empty.",
|
||||
returns = type("lyng.Bool"),
|
||||
moduleName = "lyng.stdlib",
|
||||
code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj = ObjBool(scp.thisAs<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(
|
||||
name = "matches",
|
||||
doc = "Whether this string matches the given regular expression or pattern string.",
|
||||
params = listOf(ParamDoc("pattern")),
|
||||
returns = type("lyng.Bool"),
|
||||
moduleName = "lyng.stdlib"
|
||||
) {
|
||||
val s = requireOnlyArg<Obj>()
|
||||
val self = thisAs<ObjString>().value
|
||||
ObjBool(
|
||||
when (s) {
|
||||
is ObjRegex -> self.matches(s.regex)
|
||||
is ObjString -> {
|
||||
if (s.value == ".*") true
|
||||
else {
|
||||
val re = if (PerfFlags.REGEX_CACHE) RegexCache.get(s.value) else s.value.toRegex()
|
||||
self.matches(re)
|
||||
}
|
||||
}
|
||||
moduleName = "lyng.stdlib",
|
||||
code = object : ScopeCallable {
|
||||
override suspend fun call(scp: Scope): Obj {
|
||||
val s = scp.requireOnlyArg<Obj>()
|
||||
val self = scp.thisAs<ObjString>().value
|
||||
return ObjBool(
|
||||
when (s) {
|
||||
is ObjRegex -> self.matches(s.regex)
|
||||
is ObjString -> {
|
||||
if (s.value == ".*") true
|
||||
else {
|
||||
val re = if (PerfFlags.REGEX_CACHE) RegexCache.get(s.value) else s.value.toRegex()
|
||||
self.matches(re)
|
||||
}
|
||||
}
|
||||
|
||||
else ->
|
||||
raiseIllegalArgument("can't match ${s.objClass.className}: required Regex or String")
|
||||
else ->
|
||||
scp.raiseIllegalArgument("can't match ${s.objClass.className}: required Regex or String")
|
||||
}
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2025 Sergey S. Chernov real.sergeych@gmail.com
|
||||
* Copyright 2026 Sergey S. Chernov real.sergeych@gmail.com
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@ -21,6 +21,10 @@ import net.sergeych.lyng.*
|
||||
import net.sergeych.synctools.ProtectedOp
|
||||
import net.sergeych.synctools.withLock
|
||||
|
||||
interface ModuleBuilder {
|
||||
suspend fun build(scope: ModuleScope)
|
||||
}
|
||||
|
||||
/**
|
||||
* Import manager allow to register packages with builder lambdas and act as an
|
||||
* [ImportProvider]. Note that packages _must be registered_ first with [addPackage],
|
||||
@ -40,16 +44,16 @@ class ImportManager(
|
||||
|
||||
private inner class Entry(
|
||||
val packageName: String,
|
||||
val builder: suspend (ModuleScope) -> Unit,
|
||||
val builder: ModuleBuilder,
|
||||
var cachedScope: ModuleScope? = null
|
||||
) {
|
||||
|
||||
suspend fun getScope(pos: Pos): ModuleScope {
|
||||
cachedScope?.let { return it }
|
||||
return ModuleScope(inner, pos, packageName).apply {
|
||||
cachedScope = this
|
||||
builder(this)
|
||||
}
|
||||
val ms = ModuleScope(inner, pos, packageName)
|
||||
cachedScope = ms
|
||||
builder.build(ms)
|
||||
return ms
|
||||
}
|
||||
}
|
||||
|
||||
@ -90,7 +94,7 @@ class ImportManager(
|
||||
* @param name package name
|
||||
* @param builder lambda to create actual package using the given [ModuleScope]
|
||||
*/
|
||||
fun addPackage(name: String, builder: suspend (ModuleScope) -> Unit) {
|
||||
fun addPackage(name: String, builder: ModuleBuilder) {
|
||||
op.withLock {
|
||||
if (name in imports)
|
||||
throw IllegalArgumentException("Package $name already exists")
|
||||
@ -102,7 +106,7 @@ class ImportManager(
|
||||
* Bulk [addPackage] with slightly better performance
|
||||
*/
|
||||
@Suppress("unused")
|
||||
fun addPackages(registrationData: List<Pair<String, suspend (ModuleScope) -> Unit>>) {
|
||||
fun addPackages(registrationData: List<Pair<String, ModuleBuilder>>) {
|
||||
op.withLock {
|
||||
for (pp in registrationData) {
|
||||
if (pp.first in imports)
|
||||
@ -129,9 +133,11 @@ class ImportManager(
|
||||
*/
|
||||
fun addSourcePackages(vararg sources: Source) {
|
||||
for (s in sources) {
|
||||
addPackage(s.extractPackageName()) {
|
||||
it.eval(s)
|
||||
}
|
||||
addPackage(s.extractPackageName(), object : ModuleBuilder {
|
||||
override suspend fun build(scope: ModuleScope) {
|
||||
scope.eval(s)
|
||||
}
|
||||
})
|
||||
|
||||
}
|
||||
}
|
||||
@ -144,7 +150,11 @@ class ImportManager(
|
||||
var source = Source("tmp", s)
|
||||
val packageName = source.extractPackageName()
|
||||
source = Source(packageName, s)
|
||||
addPackage(packageName) { it.eval(source) }
|
||||
addPackage(packageName, object : ModuleBuilder {
|
||||
override suspend fun build(scope: ModuleScope) {
|
||||
scope.eval(source)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -72,16 +72,16 @@ fun Statement.require(cond: Boolean, message: () -> String) {
|
||||
if (!cond) raise(message())
|
||||
}
|
||||
|
||||
fun statement(pos: Pos, isStaticConst: Boolean = false, isConst: Boolean = false, f: suspend (Scope) -> Obj): Statement =
|
||||
fun statement(pos: Pos, isStaticConst: Boolean = false, isConst: Boolean = false, f: ScopeCallable): Statement =
|
||||
object : Statement(isStaticConst, isConst) {
|
||||
override val pos: Pos = pos
|
||||
override suspend fun execute(scope: Scope): Obj = f(scope)
|
||||
override suspend fun execute(scope: Scope): Obj = f.call(scope)
|
||||
}
|
||||
|
||||
fun statement(isStaticConst: Boolean = false, isConst: Boolean = false, f: suspend Scope.() -> Obj): Statement =
|
||||
fun statement(isStaticConst: Boolean = false, isConst: Boolean = false, f: ScopeCallable): Statement =
|
||||
object : Statement(isStaticConst, isConst) {
|
||||
override val pos: Pos = Pos.builtIn
|
||||
override suspend fun execute(scope: Scope): Obj = f(scope)
|
||||
override suspend fun execute(scope: Scope): Obj = f.call(scope)
|
||||
}
|
||||
|
||||
object NopStatement: Statement(true, true, ObjType.Void) {
|
||||
|
||||
@ -41,11 +41,11 @@ object ObjLynonClass : ObjClass("Lynon") {
|
||||
|
||||
init {
|
||||
addClassConst("test", ObjString("test_const"))
|
||||
addClassFn("encode") {
|
||||
encodeAny(this, requireOnlyArg<Obj>())
|
||||
addClassFn("encode") { scp ->
|
||||
encodeAny(scp, scp.requireOnlyArg<Obj>())
|
||||
}
|
||||
addClassFn("decode") {
|
||||
decodeAny(this, requireOnlyArg<Obj>())
|
||||
addClassFn("decode") { scp ->
|
||||
decodeAny(scp, scp.requireOnlyArg<Obj>())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -171,7 +171,7 @@ class BindingHighlightTest {
|
||||
// Find the specific usage inside string-literal invocation: "%s is directory"(name)
|
||||
val pattern = "\"%s is directory\"(name)"
|
||||
val lineIdx = text.indexOf(pattern)
|
||||
assertTrue(lineIdx >= 0, "Pattern with string invoke should be present in the snippet")
|
||||
assertTrue(lineIdx >= 0, "Pattern with string invokeCallable should be present in the snippet")
|
||||
val nameStart = lineIdx + pattern.indexOf("name")
|
||||
val nameEnd = nameStart + "name".length
|
||||
|
||||
|
||||
@ -865,8 +865,8 @@ class OOTest {
|
||||
callBar
|
||||
""".trimIndent()) as Statement
|
||||
val s2 = Script.newScope()
|
||||
assertEquals(44L, fn.invoke(scope, fn).toKotlin(s2))
|
||||
assertEquals(45L, fn.invoke(s2, fn).toKotlin(s2))
|
||||
assertEquals(44L, fn.invokeCallable(scope, fn).toKotlin(s2))
|
||||
assertEquals(45L, fn.invokeCallable(s2, fn).toKotlin(s2))
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@ -138,10 +138,10 @@ class ScriptTest {
|
||||
|
||||
companion object {
|
||||
val type = ObjClass("TestIterable", ObjIterable).apply {
|
||||
addFn("iterator") {
|
||||
ObjTestIterator(thisAs<ObjTestIterable>())
|
||||
addFn("iterator") { scp ->
|
||||
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 {
|
||||
val type = ObjClass("TestIterator", ObjIterator).apply {
|
||||
addFn("hasNext") { thisAs<ObjTestIterator>().hasNext().toObj() }
|
||||
addFn("next") { thisAs<ObjTestIterator>().next() }
|
||||
addFn("cancelIteration") {
|
||||
thisAs<ObjTestIterator>().cancelIteration()
|
||||
addFn("hasNext") { scp -> scp.thisAs<ObjTestIterator>().hasNext().toObj() }
|
||||
addFn("next") { scp -> scp.thisAs<ObjTestIterator>().next() }
|
||||
addFn("cancelIteration") { scp ->
|
||||
scp.thisAs<ObjTestIterator>().cancelIteration()
|
||||
ObjVoid
|
||||
}
|
||||
}
|
||||
@ -2229,8 +2229,8 @@ class ScriptTest {
|
||||
@Test
|
||||
fun testThrowFromKotlin() = runTest {
|
||||
val c = Script.newScope()
|
||||
c.addFn("callThrow") {
|
||||
raiseIllegalArgument("fromKotlin")
|
||||
c.addFn("callThrow") { scp ->
|
||||
scp.raiseIllegalArgument("fromKotlin")
|
||||
}
|
||||
c.eval(
|
||||
"""
|
||||
@ -2738,8 +2738,8 @@ class ScriptTest {
|
||||
companion object {
|
||||
|
||||
val klass = ObjClass("TestFoo").apply {
|
||||
addFn("test") {
|
||||
thisAs<ObjTestFoo>().value
|
||||
addFn("test") { scp ->
|
||||
scp.thisAs<ObjTestFoo>().value
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -4480,7 +4480,7 @@ class ScriptTest {
|
||||
val dummyThis = Obj()
|
||||
// but we should be able to call it directly
|
||||
val otherScope = baseScope.createChildScope()
|
||||
val r = (exports["exportedFunction".toObj()] as Statement).invoke(otherScope, dummyThis, ObjInt(50))
|
||||
val r = (exports["exportedFunction".toObj()] as Statement).invokeCallable(otherScope, dummyThis, ObjInt(50))
|
||||
println(r)
|
||||
assertEquals(51, r.toInt())
|
||||
}
|
||||
|
||||
@ -90,7 +90,7 @@ class DelegationTest {
|
||||
fun testFunDelegation() = runTest {
|
||||
eval("""
|
||||
class ActionDelegate() {
|
||||
fun invoke(thisRef, name, args...) {
|
||||
fun invokeCallable(thisRef, name, args...) {
|
||||
"Called %s with %d args: %s"(name, args.size, args.joinToString(","))
|
||||
}
|
||||
}
|
||||
|
||||
@ -18,15 +18,15 @@
|
||||
package net.sergeych.lyng
|
||||
|
||||
actual object PerfDefaults {
|
||||
actual val LOCAL_SLOT_PIC: Boolean = true
|
||||
actual val EMIT_FAST_LOCAL_REFS: Boolean = true
|
||||
actual val LOCAL_SLOT_PIC: Boolean = false
|
||||
actual val EMIT_FAST_LOCAL_REFS: Boolean = false
|
||||
|
||||
actual val ARG_BUILDER: Boolean = true
|
||||
actual val SKIP_ARGS_ON_NULL_RECEIVER: Boolean = true
|
||||
actual val SCOPE_POOL: Boolean = true
|
||||
actual val ARG_BUILDER: Boolean = false
|
||||
actual val SKIP_ARGS_ON_NULL_RECEIVER: Boolean = false
|
||||
actual val SCOPE_POOL: Boolean = false
|
||||
|
||||
actual val FIELD_PIC: Boolean = true
|
||||
actual val METHOD_PIC: Boolean = true
|
||||
actual val FIELD_PIC: Boolean = false
|
||||
actual val METHOD_PIC: Boolean = false
|
||||
actual val FIELD_PIC_SIZE_4: Boolean = false
|
||||
actual val METHOD_PIC_SIZE_4: Boolean = false
|
||||
actual val PIC_ADAPTIVE_2_TO_4: Boolean = false
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user