fix #82 refactored and added builtin docs to all symbols

This commit is contained in:
Sergey Chernov 2025-12-06 21:10:40 +01:00
parent e25fc95cbf
commit 40f11b6f29
23 changed files with 1219 additions and 308 deletions

View File

@ -322,7 +322,7 @@ private suspend fun buildFsModule(module: ModuleScope, policy: FsAccessPolicy) {
// mkdirs(mustCreate: Bool=false)
addFnDoc(
name = "mkdirs",
doc = "Create directories (like `mkdir -p`). Optional `mustCreate` enforces error if target exists.",
doc = "Create directories (like `mkdir -p`). If `mustCreate` is true and the path already exists, the call fails. Otherwise it is a no‑op when the directory exists.",
params = listOf(ParamDoc("mustCreate", type("lyng.Bool"))),
moduleName = module.packageName
) {
@ -336,7 +336,7 @@ private suspend fun buildFsModule(module: ModuleScope, policy: FsAccessPolicy) {
// 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`. Use `overwrite` to replace existing target.",
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
@ -352,7 +352,7 @@ private suspend fun buildFsModule(module: ModuleScope, policy: FsAccessPolicy) {
// delete(mustExist: Bool=false, recursively: Bool=false)
addFnDoc(
name = "delete",
doc = "Delete this path. Optional flags: `mustExist` and `recursively`.",
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
) {
@ -367,7 +367,7 @@ private suspend fun buildFsModule(module: ModuleScope, policy: FsAccessPolicy) {
// 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`. Use `overwrite` to replace existing target.",
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
) {

View File

@ -20,6 +20,9 @@ package net.sergeych.lyng
import kotlinx.coroutines.delay
import kotlinx.coroutines.yield
import net.sergeych.lyng.Script.Companion.defaultImportManager
import net.sergeych.lyng.miniast.addConstDoc
import net.sergeych.lyng.miniast.addVoidFnDoc
import net.sergeych.lyng.miniast.type
import net.sergeych.lyng.obj.*
import net.sergeych.lyng.pacman.ImportManager
import net.sergeych.lyng.stdlib_included.rootLyng
@ -276,9 +279,19 @@ class Script(
}
val pi = ObjReal(PI)
addConst("π", pi)
addConstDoc(
name = "π",
value = pi,
doc = "The mathematical constant pi (π).",
type = type("lyng.Real")
)
getOrCreateNamespace("Math").apply {
addConst("PI", pi)
addConstDoc(
name = "PI",
value = pi,
doc = "The mathematical constant pi (π) in the Math namespace.",
type = type("lyng.Real")
)
}
}
@ -288,16 +301,44 @@ class Script(
rootLyng
)
addPackage("lyng.buffer") {
it.addConst("Buffer", ObjBuffer.type)
it.addConst("MutableBuffer", ObjMutableBuffer.type)
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.addConst("Lynon", ObjLynonClass)
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.addConst("Instant", ObjInstant.type)
it.addConst("Duration", ObjDuration.type)
it.addFn("delay") {
it.addConstDoc(
name = "Instant",
value = ObjInstant.type,
doc = "Point in time (epoch-based).",
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)
@ -305,7 +346,6 @@ class Script(
is ObjDuration -> delay(a.duration)
else -> raiseIllegalArgument("Expected Duration, Int or Real, got ${a.inspect(this)}")
}
ObjVoid
}
}
}

View File

@ -22,6 +22,7 @@
package net.sergeych.lyng.miniast
import net.sergeych.lyng.Pos
import net.sergeych.lyng.stdlib_included.rootLyng
// ---------------- Types DSL ----------------
@ -262,7 +263,125 @@ internal fun TypeDoc.toMiniTypeRef(): MiniTypeRef = when (this) {
// ---------------- Built-in module doc seeds ----------------
// Seed docs for lyng.stdlib lazily to avoid init-order coupling.
// ---------------- Inline docs support (.lyng source) ----------------
/**
* Lightweight, single-pass scanner that extracts inline doc comments from the stdlib .lyng source
* and associates them with declarations (top-level functions, classes, and class methods).
* It is intentionally conservative and only recognizes simple patterns present in stdlib sources.
*
* The scan is cached; performed at most once per process.
*/
private object StdlibInlineDocIndex {
private var built = false
// Keys for matching declaration docs
private sealed interface Key {
data class TopFun(val name: String) : Key
data class Clazz(val name: String) : Key
data class Method(val className: String, val name: String) : Key
}
private val docs: MutableMap<Key, String> = mutableMapOf()
private fun putIfAbsent(k: Key, doc: String) {
if (doc.isBlank()) return
if (!docs.containsKey(k)) docs[k] = doc.trim()
}
private fun buildOnce() {
if (built) return
built = true
val text = try { rootLyng } catch (_: Throwable) { null } ?: return
// Simple line-based scan. Collect a doc block immediately preceding a declaration.
val lines = text.lines()
val buf = mutableListOf<String>()
var inBlock = false
var prevWasComment = false
fun flushTo(key: Key) {
if (buf.isNotEmpty()) {
val raw = buf.joinToString("\n").trimEnd()
putIfAbsent(key, raw)
buf.clear()
}
}
for (rawLine in lines) {
val line = rawLine.trim()
when {
// Multiline block comment begin/end
line.startsWith("/*") && !inBlock -> {
inBlock = true
val inner = line.removePrefix("/*").let { l -> if (l.endsWith("*/")) l.removeSuffix("*/") else l }
buf += inner
prevWasComment = true
if (line.endsWith("*/")) inBlock = false
continue
}
inBlock -> {
val content = if (line.endsWith("*/")) {
inBlock = false
line.removeSuffix("*/")
} else line
// Trim leading '*' like Javadoc style
val t = content.trimStart()
buf += if (t.startsWith("*")) t.removePrefix("*").trimStart() else content
prevWasComment = true
continue
}
line.startsWith("//") -> {
buf += line.removePrefix("//")
prevWasComment = true
continue
}
line.isBlank() -> {
// Blank line breaks doc association unless it immediately follows a comment
if (!prevWasComment) buf.clear()
prevWasComment = false
continue
}
else -> {
// Non-comment, non-blank: try to match a declaration just after comments
if (buf.isNotEmpty()) {
// fun Class.name( ... )
val mExt = Regex("^fun\\s+([A-Za-z_][A-Za-z0-9_]*)\\.([A-Za-z_][A-Za-z0-9_]*)\\s*\\(").find(line)
if (mExt != null) {
val (cls, name) = mExt.destructured
flushTo(Key.Method(cls, name))
} else {
// fun name( ... )
val mTop = Regex("^fun\\s+([A-Za-z_][A-Za-z0-9_]*)\\s*\\(").find(line)
if (mTop != null) {
val (name) = mTop.destructured
flushTo(Key.TopFun(name))
} else {
// class Name
val mClass = Regex("^class\\s+([A-Za-z_][A-Za-z0-9_]*)\\b").find(line)
if (mClass != null) {
val (name) = mClass.destructured
flushTo(Key.Clazz(name))
} else {
// Unrecognized line – drop buffer to avoid leaking to unrelated code
buf.clear()
}
}
}
}
prevWasComment = false
}
}
}
}
// Public API: fetch doc text if present
fun topFunDoc(name: String): String? { buildOnce(); return docs[Key.TopFun(name)] }
fun classDoc(name: String): String? { buildOnce(); return docs[Key.Clazz(name)] }
fun methodDoc(className: String, name: String): String? { buildOnce(); return docs[Key.Method(className, name)] }
}
// Seed docs for lyng.stdlib lazily to avoid init-order coupling and prefer inline docs where present.
private fun buildStdlibDocs(): List<MiniDecl> {
val decls = mutableListOf<MiniDecl>()
// Use the same DSL builders to construct decls
@ -270,7 +389,7 @@ private fun buildStdlibDocs(): List<MiniDecl> {
// Printing
mod.funDoc(
name = "print",
doc = """
doc = StdlibInlineDocIndex.topFunDoc("print") ?: """
Print values to the standard output without a trailing newline.
Accepts any number of arguments and prints them separated by a space.
""".trimIndent(),
@ -279,7 +398,7 @@ private fun buildStdlibDocs(): List<MiniDecl> {
)
mod.funDoc(
name = "println",
doc = """
doc = StdlibInlineDocIndex.topFunDoc("println") ?: """
Print values to the standard output and append a newline.
Accepts any number of arguments and prints them separated by a space.
""".trimIndent(),
@ -288,7 +407,7 @@ private fun buildStdlibDocs(): List<MiniDecl> {
// Caching helper
mod.funDoc(
name = "cached",
doc = """
doc = StdlibInlineDocIndex.topFunDoc("cached") ?: """
Wrap a `builder` into a zero-argument thunk that computes once and caches the result.
The first call invokes `builder()` and stores the value; subsequent calls return the cached value.
""".trimIndent(),
@ -298,44 +417,44 @@ private fun buildStdlibDocs(): List<MiniDecl> {
// Math helpers (scalar versions)
fun math1(name: String) = mod.funDoc(
name = name,
doc = "Compute $name(x).",
doc = StdlibInlineDocIndex.topFunDoc(name) ?: "Compute $name(x).",
params = listOf(ParamDoc("x", type("lyng.Number")))
)
math1("sin"); math1("cos"); math1("tan"); math1("asin"); math1("acos"); math1("atan")
mod.funDoc(name = "floor", doc = "Round down the number to the nearest integer.", params = listOf(ParamDoc("x", type("lyng.Number"))))
mod.funDoc(name = "ceil", doc = "Round up the number to the nearest integer.", params = listOf(ParamDoc("x", type("lyng.Number"))))
mod.funDoc(name = "round", doc = "Round the number to the nearest integer.", params = listOf(ParamDoc("x", type("lyng.Number"))))
mod.funDoc(name = "floor", doc = StdlibInlineDocIndex.topFunDoc("floor") ?: "Round down the number to the nearest integer.", params = listOf(ParamDoc("x", type("lyng.Number"))))
mod.funDoc(name = "ceil", doc = StdlibInlineDocIndex.topFunDoc("ceil") ?: "Round up the number to the nearest integer.", params = listOf(ParamDoc("x", type("lyng.Number"))))
mod.funDoc(name = "round", doc = StdlibInlineDocIndex.topFunDoc("round") ?: "Round the number to the nearest integer.", params = listOf(ParamDoc("x", type("lyng.Number"))))
// Hyperbolic and inverse hyperbolic
math1("sinh"); math1("cosh"); math1("tanh"); math1("asinh"); math1("acosh"); math1("atanh")
// Exponentials and logarithms
mod.funDoc(name = "exp", doc = "Euler's exponential e^x.", params = listOf(ParamDoc("x", type("lyng.Number"))))
mod.funDoc(name = "ln", doc = "Natural logarithm (base e).", params = listOf(ParamDoc("x", type("lyng.Number"))))
mod.funDoc(name = "log10", doc = "Logarithm base 10.", params = listOf(ParamDoc("x", type("lyng.Number"))))
mod.funDoc(name = "log2", doc = "Logarithm base 2.", params = listOf(ParamDoc("x", type("lyng.Number"))))
mod.funDoc(name = "exp", doc = StdlibInlineDocIndex.topFunDoc("exp") ?: "Euler's exponential e^x.", params = listOf(ParamDoc("x", type("lyng.Number"))))
mod.funDoc(name = "ln", doc = StdlibInlineDocIndex.topFunDoc("ln") ?: "Natural logarithm (base e).", params = listOf(ParamDoc("x", type("lyng.Number"))))
mod.funDoc(name = "log10", doc = StdlibInlineDocIndex.topFunDoc("log10") ?: "Logarithm base 10.", params = listOf(ParamDoc("x", type("lyng.Number"))))
mod.funDoc(name = "log2", doc = StdlibInlineDocIndex.topFunDoc("log2") ?: "Logarithm base 2.", params = listOf(ParamDoc("x", type("lyng.Number"))))
// Power/roots and absolute value
mod.funDoc(
name = "pow",
doc = "Raise `x` to the power `y`.",
doc = StdlibInlineDocIndex.topFunDoc("pow") ?: "Raise `x` to the power `y`.",
params = listOf(ParamDoc("x", type("lyng.Number")), ParamDoc("y", type("lyng.Number")))
)
mod.funDoc(
name = "sqrt",
doc = "Square root of `x`.",
doc = StdlibInlineDocIndex.topFunDoc("sqrt") ?: "Square root of `x`.",
params = listOf(ParamDoc("x", type("lyng.Number")))
)
mod.funDoc(
name = "abs",
doc = "Absolute value of a number (works for Int and Real).",
doc = StdlibInlineDocIndex.topFunDoc("abs") ?: "Absolute value of a number (works for Int and Real).",
params = listOf(ParamDoc("x", type("lyng.Number")))
)
// Assertions and checks
mod.funDoc(
name = "assert",
doc = """
doc = StdlibInlineDocIndex.topFunDoc("assert") ?: """
Assert that `cond` is true, otherwise throw an `AssertionFailedException`.
Optionally provide a `message`.
""".trimIndent(),
@ -343,17 +462,17 @@ private fun buildStdlibDocs(): List<MiniDecl> {
)
mod.funDoc(
name = "assertEquals",
doc = "Assert that `a == b`, otherwise throw an assertion error.",
doc = StdlibInlineDocIndex.topFunDoc("assertEquals") ?: "Assert that `a == b`, otherwise throw an assertion error.",
params = listOf(ParamDoc("a"), ParamDoc("b"))
)
mod.funDoc(
name = "assertNotEquals",
doc = "Assert that `a != b`, otherwise throw an assertion error.",
doc = StdlibInlineDocIndex.topFunDoc("assertNotEquals") ?: "Assert that `a != b`, otherwise throw an assertion error.",
params = listOf(ParamDoc("a"), ParamDoc("b"))
)
mod.funDoc(
name = "assertThrows",
doc = """
doc = StdlibInlineDocIndex.topFunDoc("assertThrows") ?: """
Execute `code` and return the thrown `Exception` object.
If nothing is thrown, an assertion error is raised.
""".trimIndent(),
@ -364,119 +483,125 @@ private fun buildStdlibDocs(): List<MiniDecl> {
// Utilities
mod.funDoc(
name = "dynamic",
doc = "Wrap a value into a dynamic object that defers resolution to runtime.",
doc = StdlibInlineDocIndex.topFunDoc("dynamic") ?: "Wrap a value into a dynamic object that defers resolution to runtime.",
params = listOf(ParamDoc("value"))
)
mod.funDoc(
name = "require",
doc = "Require `cond` to be true, else throw `IllegalArgumentException` with optional `message`.",
doc = StdlibInlineDocIndex.topFunDoc("require") ?: "Require `cond` to be true, else throw `IllegalArgumentException` with optional `message`.",
params = listOf(ParamDoc("cond", type("lyng.Bool")), ParamDoc("message"))
)
mod.funDoc(
name = "check",
doc = "Check `cond` is true, else throw `IllegalStateException` with optional `message`.",
doc = StdlibInlineDocIndex.topFunDoc("check") ?: "Check `cond` is true, else throw `IllegalStateException` with optional `message`.",
params = listOf(ParamDoc("cond", type("lyng.Bool")), ParamDoc("message"))
)
mod.funDoc(
name = "traceScope",
doc = "Print a debug trace of the current scope chain with an optional label.",
doc = StdlibInlineDocIndex.topFunDoc("traceScope") ?: "Print a debug trace of the current scope chain with an optional label.",
params = listOf(ParamDoc("label", type("lyng.String")))
)
mod.funDoc(
name = "delay",
doc = "Suspend for the specified number of milliseconds.",
doc = StdlibInlineDocIndex.topFunDoc("delay") ?: "Suspend for the specified number of milliseconds.",
params = listOf(ParamDoc("ms", type("lyng.Number")))
)
// Concurrency helpers
mod.funDoc(
name = "launch",
doc = "Launch an asynchronous task and return a `Deferred`.",
doc = StdlibInlineDocIndex.topFunDoc("launch") ?: "Launch an asynchronous task and return a `Deferred`.",
params = listOf(ParamDoc("code")),
returns = type("lyng.Deferred")
)
mod.funDoc(
name = "yield",
doc = "Yield to the scheduler, allowing other tasks to run."
doc = StdlibInlineDocIndex.topFunDoc("yield") ?: "Yield to the scheduler, allowing other tasks to run."
)
mod.funDoc(
name = "flow",
doc = "Create a lazy iterable stream using the provided `builder`.",
doc = StdlibInlineDocIndex.topFunDoc("flow") ?: "Create a lazy iterable stream using the provided `builder`.",
params = listOf(ParamDoc("builder")),
returns = type("lyng.Iterable")
)
// Common Iterable helpers (document top-level extension-like APIs as class members)
mod.classDoc(name = "Iterable", doc = "Helper operations for iterable collections.") {
method(name = "filter", doc = "Filter elements by predicate.", params = listOf(ParamDoc("predicate")), returns = type("lyng.Iterable"))
method(name = "drop", doc = "Skip the first N elements.", params = listOf(ParamDoc("n", type("lyng.Int"))), returns = type("lyng.Iterable"))
method(name = "first", doc = "Return the first element or throw if empty.")
method(name = "last", doc = "Return the last element or throw if empty.")
method(name = "dropLast", doc = "Drop the last N elements.", params = listOf(ParamDoc("n", type("lyng.Int"))), returns = type("lyng.Iterable"))
method(name = "takeLast", doc = "Take the last N elements.", params = listOf(ParamDoc("n", type("lyng.Int"))), returns = type("lyng.List"))
method(name = "joinToString", doc = "Join elements into a string with an optional separator and transformer.", params = listOf(ParamDoc("prefix", type("lyng.String")), ParamDoc("transformer")), returns = type("lyng.String"))
method(name = "any", doc = "Return true if any element matches the predicate.", params = listOf(ParamDoc("predicate")), returns = type("lyng.Bool"))
method(name = "all", doc = "Return true if all elements match the predicate.", params = listOf(ParamDoc("predicate")), returns = type("lyng.Bool"))
method(name = "sum", doc = "Sum all elements; returns null for empty collections.", returns = type("lyng.Number", nullable = true))
method(name = "sumOf", doc = "Sum mapped values of elements; returns null for empty collections.", params = listOf(ParamDoc("f")))
method(name = "minOf", doc = "Minimum of mapped values.", params = listOf(ParamDoc("lambda")))
method(name = "maxOf", doc = "Maximum of mapped values.", params = listOf(ParamDoc("lambda")))
method(name = "sorted", doc = "Return elements sorted by natural order.", returns = type("lyng.Iterable"))
method(name = "sortedBy", doc = "Return elements sorted by the key selector.", params = listOf(ParamDoc("predicate")), returns = type("lyng.Iterable"))
method(name = "shuffled", doc = "Return a shuffled copy as a list.", returns = type("lyng.List"))
method(name = "map", doc = "Transform elements by applying `transform`.", params = listOf(ParamDoc("transform")), returns = type("lyng.Iterable"))
method(name = "toList", doc = "Collect elements of this iterable into a new list.", returns = type("lyng.List"))
mod.classDoc(name = "Iterable", doc = StdlibInlineDocIndex.classDoc("Iterable") ?: "Helper operations for iterable collections.") {
fun md(name: String, fallback: String) = StdlibInlineDocIndex.methodDoc("Iterable", name) ?: fallback
method(name = "filter", doc = md("filter", "Filter elements by predicate."), params = listOf(ParamDoc("predicate")), returns = type("lyng.Iterable"))
method(name = "drop", doc = md("drop", "Skip the first N elements."), params = listOf(ParamDoc("n", type("lyng.Int"))), returns = type("lyng.Iterable"))
method(name = "first", doc = md("first", "Return the first element or throw if empty."))
method(name = "last", doc = md("last", "Return the last element or throw if empty."))
method(name = "dropLast", doc = md("dropLast", "Drop the last N elements."), params = listOf(ParamDoc("n", type("lyng.Int"))), returns = type("lyng.Iterable"))
method(name = "takeLast", doc = md("takeLast", "Take the last N elements."), params = listOf(ParamDoc("n", type("lyng.Int"))), returns = type("lyng.List"))
method(name = "joinToString", doc = md("joinToString", "Join elements into a string with an optional separator and transformer."), params = listOf(ParamDoc("prefix", type("lyng.String")), ParamDoc("transformer")), returns = type("lyng.String"))
method(name = "any", doc = md("any", "Return true if any element matches the predicate."), params = listOf(ParamDoc("predicate")), returns = type("lyng.Bool"))
method(name = "all", doc = md("all", "Return true if all elements match the predicate."), params = listOf(ParamDoc("predicate")), returns = type("lyng.Bool"))
method(name = "sum", doc = md("sum", "Sum all elements; returns null for empty collections."), returns = type("lyng.Number", nullable = true))
method(name = "sumOf", doc = md("sumOf", "Sum mapped values of elements; returns null for empty collections."), params = listOf(ParamDoc("f")))
method(name = "minOf", doc = md("minOf", "Minimum of mapped values."), params = listOf(ParamDoc("lambda")))
method(name = "maxOf", doc = md("maxOf", "Maximum of mapped values."), params = listOf(ParamDoc("lambda")))
method(name = "sorted", doc = md("sorted", "Return elements sorted by natural order."), returns = type("lyng.Iterable"))
method(name = "sortedBy", doc = md("sortedBy", "Return elements sorted by the key selector."), params = listOf(ParamDoc("predicate")), returns = type("lyng.Iterable"))
method(name = "shuffled", doc = md("shuffled", "Return a shuffled copy as a list."), returns = type("lyng.List"))
method(name = "map", doc = md("map", "Transform elements by applying `transform`."), params = listOf(ParamDoc("transform")), returns = type("lyng.Iterable"))
method(name = "toList", doc = md("toList", "Collect elements of this iterable into a new list."), returns = type("lyng.List"))
}
// List helpers
mod.classDoc(name = "List", doc = "List-specific operations.", bases = listOf(type("Collection"), type("Iterable"))) {
method(name = "toString", doc = "Return string representation like [a,b,c].", returns = type("lyng.String"))
method(name = "sortBy", doc = "Sort list in-place by key selector.", params = listOf(ParamDoc("predicate")))
method(name = "sort", doc = "Sort list in-place by natural order.")
method(name = "toList", doc = "Return a shallow copy of this list (new list with the same elements).", returns = type("lyng.List"))
mod.classDoc(name = "List", doc = StdlibInlineDocIndex.classDoc("List") ?: "List-specific operations.", bases = listOf(type("Collection"), type("Iterable"))) {
fun md(name: String, fallback: String) = StdlibInlineDocIndex.methodDoc("List", name) ?: fallback
method(name = "toString", doc = md("toString", "Return string representation like [a,b,c]."), returns = type("lyng.String"))
method(name = "sortBy", doc = md("sortBy", "Sort list in-place by key selector."), params = listOf(ParamDoc("predicate")))
method(name = "sort", doc = md("sort", "Sort list in-place by natural order."))
method(name = "toList", doc = md("toList", "Return a shallow copy of this list (new list with the same elements)."), returns = type("lyng.List"))
}
// Collection helpers (supertype for sized collections)
mod.classDoc(name = "Collection", doc = "Collection operations common to sized collections.", bases = listOf(type("Iterable"))) {
method(name = "size", doc = "Number of elements in the collection.", returns = type("lyng.Int"))
method(name = "toList", doc = "Collect elements into a new list.", returns = type("lyng.List"))
mod.classDoc(name = "Collection", doc = StdlibInlineDocIndex.classDoc("Collection") ?: "Collection operations common to sized collections.", bases = listOf(type("Iterable"))) {
fun md(name: String, fallback: String) = StdlibInlineDocIndex.methodDoc("Collection", name) ?: fallback
method(name = "size", doc = md("size", "Number of elements in the collection."), returns = type("lyng.Int"))
method(name = "toList", doc = md("toList", "Collect elements into a new list."), returns = type("lyng.List"))
}
// Iterator helpers
mod.classDoc(name = "Iterator", doc = "Iterator protocol for sequential access.") {
method(name = "hasNext", doc = "Whether another element is available.", returns = type("lyng.Bool"))
method(name = "next", doc = "Return the next element.")
method(name = "cancelIteration", doc = "Stop the iteration early.")
method(name = "toList", doc = "Consume this iterator and collect elements into a list.", returns = type("lyng.List"))
mod.classDoc(name = "Iterator", doc = StdlibInlineDocIndex.classDoc("Iterator") ?: "Iterator protocol for sequential access.") {
fun md(name: String, fallback: String) = StdlibInlineDocIndex.methodDoc("Iterator", name) ?: fallback
method(name = "hasNext", doc = md("hasNext", "Whether another element is available."), returns = type("lyng.Bool"))
method(name = "next", doc = md("next", "Return the next element."))
method(name = "cancelIteration", doc = md("cancelIteration", "Stop the iteration early."))
method(name = "toList", doc = md("toList", "Consume this iterator and collect elements into a list."), returns = type("lyng.List"))
}
// Exceptions and utilities
mod.classDoc(name = "Exception", doc = "Exception helpers.") {
method(name = "printStackTrace", doc = "Print this exception and its stack trace to standard output.")
mod.classDoc(name = "Exception", doc = StdlibInlineDocIndex.classDoc("Exception") ?: "Exception helpers.") {
method(name = "printStackTrace", doc = StdlibInlineDocIndex.methodDoc("Exception", "printStackTrace") ?: "Print this exception and its stack trace to standard output.")
}
mod.classDoc(name = "String", doc = "String helpers.") {
method(name = "re", doc = "Compile this string into a regular expression.", returns = type("lyng.Regex"))
mod.classDoc(name = "String", doc = StdlibInlineDocIndex.classDoc("String") ?: "String helpers.") {
// Only include inline-source method here; Kotlin-embedded methods are now documented via DocHelpers near definitions.
method(name = "re", doc = StdlibInlineDocIndex.methodDoc("String", "re") ?: "Compile this string into a regular expression.", returns = type("lyng.Regex"))
}
// StackTraceEntry structure
mod.classDoc(name = "StackTraceEntry", doc = "Represents a single stack trace element.") {
mod.classDoc(name = "StackTraceEntry", doc = StdlibInlineDocIndex.classDoc("StackTraceEntry") ?: "Represents a single stack trace element.") {
// Fields are not present as declarations in root.lyng's class header docs. Keep seeded defaults.
field(name = "sourceName", doc = "Source (file) name.", type = type("lyng.String"))
field(name = "line", doc = "Line number (1-based).", type = type("lyng.Int"))
field(name = "column", doc = "Column number (0-based).", type = type("lyng.Int"))
field(name = "sourceString", doc = "The source line text.", type = type("lyng.String"))
method(name = "toString", doc = "Formatted representation: source:line:column: text.", returns = type("lyng.String"))
method(name = "toString", doc = StdlibInlineDocIndex.methodDoc("StackTraceEntry", "toString") ?: "Formatted representation: source:line:column: text.", returns = type("lyng.String"))
}
// Constants and namespaces
mod.valDoc(
name = "π",
doc = "The mathematical constant pi.",
doc = StdlibInlineDocIndex.topFunDoc("π") ?: "The mathematical constant pi.",
type = type("lyng.Real"),
mutable = false
)
mod.classDoc(name = "Math", doc = "Mathematical constants and helpers.") {
field(name = "PI", doc = "The mathematical constant pi.", type = type("lyng.Real"), isStatic = true)
field(name = "PI", doc = StdlibInlineDocIndex.methodDoc("Math", "PI") ?: "The mathematical constant pi.", type = type("lyng.Real"), isStatic = true)
}
decls += mod.build()

View File

@ -17,6 +17,11 @@
package net.sergeych.lyng.obj
import net.sergeych.lyng.miniast.ParamDoc
import net.sergeych.lyng.miniast.TypeGenericDoc
import net.sergeych.lyng.miniast.addFnDoc
import net.sergeych.lyng.miniast.type
val ObjArray by lazy {
/**
@ -25,19 +30,34 @@ val ObjArray by lazy {
ObjClass("Array", ObjCollection).apply {
// we can create iterators using size/getat:
addFn("iterator") {
ObjArrayIterator(thisObj).also { it.init(this) }
}
addFnDoc(
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) } }
addFn("contains", isOpen = true) {
addFnDoc(
name = "contains",
doc = "Whether the array contains the given element (by equality).",
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@addFn ObjTrue
if (thisObj.getAt(this, ObjInt(i.toLong())).compareTo(this, obj) == 0) return@addFnDoc ObjTrue
}
ObjFalse
}
addFn("last") {
addFnDoc(
name = "last",
doc = "The last element of this array.",
returns = type("lyng.Any"),
moduleName = "lyng.stdlib"
) {
thisObj.invokeInstanceMethod(
this,
"getAt",
@ -45,13 +65,27 @@ val ObjArray by lazy {
)
}
addFn("lastIndex") { (thisObj.invokeInstanceMethod(this, "size").toInt() - 1).toObj() }
addFnDoc(
name = "lastIndex",
doc = "Index of the last element (size - 1).",
returns = type("lyng.Int"),
moduleName = "lyng.stdlib"
) { (thisObj.invokeInstanceMethod(this, "size").toInt() - 1).toObj() }
addFn("indices") {
ObjRange(0.toObj(), thisObj.invokeInstanceMethod(this, "size"), false)
}
addFnDoc(
name = "indices",
doc = "Range of valid indices for this array.",
returns = type("lyng.Range"),
moduleName = "lyng.stdlib"
) { ObjRange(0.toObj(), thisObj.invokeInstanceMethod(this, "size"), false) }
addFn("binarySearch") {
addFnDoc(
name = "binarySearch",
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
@ -62,13 +96,12 @@ val ObjArray by lazy {
val cmp = midVal.compareTo(this, target)
when {
cmp == 0 -> return@addFn (mid).toObj()
cmp == 0 -> return@addFnDoc (mid).toObj()
cmp > 0 -> high = mid - 1
else -> low = mid + 1
}
}
// Элемент не найден, возвращаем -(точка вставки) - 1
(-low - 1).toObj()
}
}

View File

@ -18,6 +18,8 @@
package net.sergeych.lyng.obj
import net.sergeych.lyng.Scope
import net.sergeych.lyng.miniast.addFnDoc
import net.sergeych.lyng.miniast.type
class ObjChar(val value: Char): Obj() {
@ -45,7 +47,12 @@ class ObjChar(val value: Char): Obj() {
companion object {
val type = ObjClass("Char").apply {
addFn("code") { ObjInt(thisAs<ObjChar>().value.code.toLong()) }
addFnDoc(
name = "code",
doc = "Unicode code point (UTF-16 code unit) of this character.",
returns = type("lyng.Int"),
moduleName = "lyng.stdlib"
) { ObjInt(thisAs<ObjChar>().value.code.toLong()) }
}
}
}

View File

@ -18,13 +18,73 @@
package net.sergeych.lyng.obj
import net.sergeych.lyng.*
import net.sergeych.lyng.miniast.ParamDoc
import net.sergeych.lyng.miniast.TypeGenericDoc
import net.sergeych.lyng.miniast.addFnDoc
import net.sergeych.lyng.miniast.type
import net.sergeych.lynon.LynonDecoder
import net.sergeych.lynon.LynonType
// Simple id generator for class identities (not thread-safe; fine for scripts)
private object ClassIdGen { var c: Long = 1L; fun nextId(): Long = c++ }
val ObjClassType by lazy { ObjClass("Class") }
val ObjClassType by lazy {
ObjClass("Class").apply {
addFnDoc(
name = "name",
doc = "Simple name of this class (without package).",
returns = type("lyng.String"),
moduleName = "lyng.stdlib"
) { thisAs<ObjClass>().classNameObj }
addFnDoc(
name = "fields",
doc = "Declared instance fields of this class and its ancestors (C3 order), without duplicates.",
returns = TypeGenericDoc(type("lyng.List"), listOf(type("lyng.String"))),
moduleName = "lyng.stdlib"
) {
val cls = thisAs<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)
}
}
ObjList(names.toMutableList())
}
addFnDoc(
name = "methods",
doc = "Declared instance methods of this class and its ancestors (C3 order), without duplicates.",
returns = TypeGenericDoc(type("lyng.List"), listOf(type("lyng.String"))),
moduleName = "lyng.stdlib"
) {
val cls = thisAs<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)
}
}
ObjList(names.toMutableList())
}
addFnDoc(
name = "get",
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
}
}
}
open class ObjClass(
val className: String,

View File

@ -19,6 +19,9 @@ package net.sergeych.lyng.obj
import kotlinx.coroutines.CompletableDeferred
import net.sergeych.lyng.Scope
import net.sergeych.lyng.miniast.ParamDoc
import net.sergeych.lyng.miniast.addFnDoc
import net.sergeych.lyng.miniast.type
class ObjCompletableDeferred(val completableDeferred: CompletableDeferred<Obj>): ObjDeferred(completableDeferred) {
@ -30,7 +33,13 @@ class ObjCompletableDeferred(val completableDeferred: CompletableDeferred<Obj>):
return ObjCompletableDeferred(CompletableDeferred())
}
}.apply {
addFn("complete") {
addFnDoc(
name = "complete",
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
}

View File

@ -19,6 +19,8 @@ package net.sergeych.lyng.obj
import kotlinx.coroutines.Deferred
import net.sergeych.lyng.Scope
import net.sergeych.lyng.miniast.addFnDoc
import net.sergeych.lyng.miniast.type
open class ObjDeferred(val deferred: Deferred<Obj>): Obj() {
@ -30,20 +32,33 @@ open class ObjDeferred(val deferred: Deferred<Obj>): Obj() {
scope.raiseError("Deferred constructor is not directly callable")
}
}.apply {
addFn("await") {
thisAs<ObjDeferred>().deferred.await()
}
addFn("isCompleted") {
thisAs<ObjDeferred>().deferred.isCompleted.toObj()
}
addFn("isActive") {
addFnDoc(
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() }
addFnDoc(
name = "isCompleted",
doc = "Whether this deferred has completed (successfully or with an error).",
returns = type("lyng.Bool"),
moduleName = "lyng.stdlib"
) { thisAs<ObjDeferred>().deferred.isCompleted.toObj() }
addFnDoc(
name = "isActive",
doc = "Whether this deferred is currently active (not completed and not cancelled).",
returns = type("lyng.Bool"),
moduleName = "lyng.stdlib"
) {
val d = thisAs<ObjDeferred>().deferred
// Cross-engine tolerant: prefer Deferred.isActive; otherwise treat any not-yet-completed and not-cancelled as active
(d.isActive || (!d.isCompleted && !d.isCancelled)).toObj()
}
addFn("isCancelled") {
thisAs<ObjDeferred>().deferred.isCancelled.toObj()
}
addFnDoc(
name = "isCancelled",
doc = "Whether this deferred was cancelled.",
returns = type("lyng.Bool"),
moduleName = "lyng.stdlib"
) { thisAs<ObjDeferred>().deferred.isCancelled.toObj() }
}
}

View File

@ -18,6 +18,8 @@
package net.sergeych.lyng.obj
import net.sergeych.lyng.Scope
import net.sergeych.lyng.miniast.addFnDoc
import net.sergeych.lyng.miniast.type
import kotlin.time.Duration
import kotlin.time.Duration.Companion.days
import kotlin.time.Duration.Companion.hours
@ -72,91 +74,169 @@ class ObjDuration(val duration: Duration) : Obj() {
)
}
}.apply {
addFn("days") {
thisAs<ObjDuration>().duration.toDouble(DurationUnit.DAYS).toObj()
}
addFn("hours") {
thisAs<ObjDuration>().duration.toDouble(DurationUnit.HOURS).toObj()
}
addFn("minutes") {
thisAs<ObjDuration>().duration.toDouble(DurationUnit.MINUTES).toObj()
}
addFn("seconds") {
thisAs<ObjDuration>().duration.toDouble(DurationUnit.SECONDS).toObj()
}
addFn("milliseconds") {
thisAs<ObjDuration>().duration.toDouble(DurationUnit.MILLISECONDS).toObj()
}
addFn("microseconds") {
thisAs<ObjDuration>().duration.toDouble(DurationUnit.MICROSECONDS).toObj()
}
addFnDoc(
name = "days",
doc = "Return this duration as a real number of days.",
returns = type("lyng.Real"),
moduleName = "lyng.time"
) { thisAs<ObjDuration>().duration.toDouble(DurationUnit.DAYS).toObj() }
addFnDoc(
name = "hours",
doc = "Return this duration as a real number of hours.",
returns = type("lyng.Real"),
moduleName = "lyng.time"
) { thisAs<ObjDuration>().duration.toDouble(DurationUnit.HOURS).toObj() }
addFnDoc(
name = "minutes",
doc = "Return this duration as a real number of minutes.",
returns = type("lyng.Real"),
moduleName = "lyng.time"
) { thisAs<ObjDuration>().duration.toDouble(DurationUnit.MINUTES).toObj() }
addFnDoc(
name = "seconds",
doc = "Return this duration as a real number of seconds.",
returns = type("lyng.Real"),
moduleName = "lyng.time"
) { thisAs<ObjDuration>().duration.toDouble(DurationUnit.SECONDS).toObj() }
addFnDoc(
name = "milliseconds",
doc = "Return this duration as a real number of milliseconds.",
returns = type("lyng.Real"),
moduleName = "lyng.time"
) { thisAs<ObjDuration>().duration.toDouble(DurationUnit.MILLISECONDS).toObj() }
addFnDoc(
name = "microseconds",
doc = "Return this duration as a real number of microseconds.",
returns = type("lyng.Real"),
moduleName = "lyng.time"
) { thisAs<ObjDuration>().duration.toDouble(DurationUnit.MICROSECONDS).toObj() }
// extensions
ObjInt.type.addFn("seconds") {
ObjDuration(thisAs<ObjInt>().value.seconds)
}
ObjInt.type.addFnDoc(
name = "seconds",
doc = "Construct a `Duration` equal to this integer number of seconds.",
returns = type("lyng.Duration"),
moduleName = "lyng.time"
) { ObjDuration(thisAs<ObjInt>().value.seconds) }
ObjInt.type.addFn("second") {
ObjDuration(thisAs<ObjInt>().value.seconds)
}
ObjInt.type.addFn("milliseconds") {
ObjDuration(thisAs<ObjInt>().value.milliseconds)
}
ObjInt.type.addFnDoc(
name = "second",
doc = "Construct a `Duration` equal to this integer number of seconds.",
returns = type("lyng.Duration"),
moduleName = "lyng.time"
) { ObjDuration(thisAs<ObjInt>().value.seconds) }
ObjInt.type.addFnDoc(
name = "milliseconds",
doc = "Construct a `Duration` equal to this integer number of milliseconds.",
returns = type("lyng.Duration"),
moduleName = "lyng.time"
) { ObjDuration(thisAs<ObjInt>().value.milliseconds) }
ObjInt.type.addFn("millisecond") {
ObjDuration(thisAs<ObjInt>().value.milliseconds)
}
ObjReal.type.addFn("seconds") {
ObjDuration(thisAs<ObjReal>().value.seconds)
}
ObjInt.type.addFnDoc(
name = "millisecond",
doc = "Construct a `Duration` equal to this integer number of milliseconds.",
returns = type("lyng.Duration"),
moduleName = "lyng.time"
) { ObjDuration(thisAs<ObjInt>().value.milliseconds) }
ObjReal.type.addFnDoc(
name = "seconds",
doc = "Construct a `Duration` equal to this real number of seconds.",
returns = type("lyng.Duration"),
moduleName = "lyng.time"
) { ObjDuration(thisAs<ObjReal>().value.seconds) }
ObjReal.type.addFn("second") {
ObjDuration(thisAs<ObjReal>().value.seconds)
}
ObjReal.type.addFnDoc(
name = "second",
doc = "Construct a `Duration` equal to this real number of seconds.",
returns = type("lyng.Duration"),
moduleName = "lyng.time"
) { ObjDuration(thisAs<ObjReal>().value.seconds) }
ObjReal.type.addFn("milliseconds") {
ObjDuration(thisAs<ObjReal>().value.milliseconds)
}
ObjReal.type.addFn("millisecond") {
ObjDuration(thisAs<ObjReal>().value.milliseconds)
}
ObjReal.type.addFnDoc(
name = "milliseconds",
doc = "Construct a `Duration` equal to this real number of milliseconds.",
returns = type("lyng.Duration"),
moduleName = "lyng.time"
) { ObjDuration(thisAs<ObjReal>().value.milliseconds) }
ObjReal.type.addFnDoc(
name = "millisecond",
doc = "Construct a `Duration` equal to this real number of milliseconds.",
returns = type("lyng.Duration"),
moduleName = "lyng.time"
) { ObjDuration(thisAs<ObjReal>().value.milliseconds) }
ObjInt.type.addFn("minutes") {
ObjDuration(thisAs<ObjInt>().value.minutes)
}
ObjReal.type.addFn("minutes") {
ObjDuration(thisAs<ObjReal>().value.minutes)
}
ObjInt.type.addFn("minute") {
ObjDuration(thisAs<ObjInt>().value.minutes)
}
ObjReal.type.addFn("minute") {
ObjDuration(thisAs<ObjReal>().value.minutes)
}
ObjInt.type.addFn("hours") {
ObjDuration(thisAs<ObjInt>().value.hours)
}
ObjReal.type.addFn("hours") {
ObjDuration(thisAs<ObjReal>().value.hours)
}
ObjInt.type.addFn("hour") {
ObjDuration(thisAs<ObjInt>().value.hours)
}
ObjReal.type.addFn("hour") {
ObjDuration(thisAs<ObjReal>().value.hours)
}
ObjInt.type.addFn("days") {
ObjDuration(thisAs<ObjInt>().value.days)
}
ObjReal.type.addFn("days") {
ObjDuration(thisAs<ObjReal>().value.days)
}
ObjInt.type.addFn("day") {
ObjDuration(thisAs<ObjInt>().value.days)
}
ObjReal.type.addFn("day") {
ObjDuration(thisAs<ObjReal>().value.days)
}
ObjInt.type.addFnDoc(
name = "minutes",
doc = "Construct a `Duration` equal to this integer number of minutes.",
returns = type("lyng.Duration"),
moduleName = "lyng.time"
) { ObjDuration(thisAs<ObjInt>().value.minutes) }
ObjReal.type.addFnDoc(
name = "minutes",
doc = "Construct a `Duration` equal to this real number of minutes.",
returns = type("lyng.Duration"),
moduleName = "lyng.time"
) { ObjDuration(thisAs<ObjReal>().value.minutes) }
ObjInt.type.addFnDoc(
name = "minute",
doc = "Construct a `Duration` equal to this integer number of minutes.",
returns = type("lyng.Duration"),
moduleName = "lyng.time"
) { ObjDuration(thisAs<ObjInt>().value.minutes) }
ObjReal.type.addFnDoc(
name = "minute",
doc = "Construct a `Duration` equal to this real number of minutes.",
returns = type("lyng.Duration"),
moduleName = "lyng.time"
) { ObjDuration(thisAs<ObjReal>().value.minutes) }
ObjInt.type.addFnDoc(
name = "hours",
doc = "Construct a `Duration` equal to this integer number of hours.",
returns = type("lyng.Duration"),
moduleName = "lyng.time"
) { ObjDuration(thisAs<ObjInt>().value.hours) }
ObjReal.type.addFnDoc(
name = "hours",
doc = "Construct a `Duration` equal to this real number of hours.",
returns = type("lyng.Duration"),
moduleName = "lyng.time"
) { ObjDuration(thisAs<ObjReal>().value.hours) }
ObjInt.type.addFnDoc(
name = "hour",
doc = "Construct a `Duration` equal to this integer number of hours.",
returns = type("lyng.Duration"),
moduleName = "lyng.time"
) { ObjDuration(thisAs<ObjInt>().value.hours) }
ObjReal.type.addFnDoc(
name = "hour",
doc = "Construct a `Duration` equal to this real number of hours.",
returns = type("lyng.Duration"),
moduleName = "lyng.time"
) { ObjDuration(thisAs<ObjReal>().value.hours) }
ObjInt.type.addFnDoc(
name = "days",
doc = "Construct a `Duration` equal to this integer number of days.",
returns = type("lyng.Duration"),
moduleName = "lyng.time"
) { ObjDuration(thisAs<ObjInt>().value.days) }
ObjReal.type.addFnDoc(
name = "days",
doc = "Construct a `Duration` equal to this real number of days.",
returns = type("lyng.Duration"),
moduleName = "lyng.time"
) { ObjDuration(thisAs<ObjReal>().value.days) }
ObjInt.type.addFnDoc(
name = "day",
doc = "Construct a `Duration` equal to this integer number of days.",
returns = type("lyng.Duration"),
moduleName = "lyng.time"
) { ObjDuration(thisAs<ObjInt>().value.days) }
ObjReal.type.addFnDoc(
name = "day",
doc = "Construct a `Duration` equal to this real number of days.",
returns = type("lyng.Duration"),
moduleName = "lyng.time"
) { ObjDuration(thisAs<ObjReal>().value.days) }
// addFn("epochSeconds") {

View File

@ -19,6 +19,10 @@ package net.sergeych.lyng.obj
import net.sergeych.bintools.encodeToHex
import net.sergeych.lyng.*
import net.sergeych.lyng.miniast.TypeGenericDoc
import net.sergeych.lyng.miniast.addConstDoc
import net.sergeych.lyng.miniast.addFnDoc
import net.sergeych.lyng.miniast.type
import net.sergeych.lynon.LynonDecoder
import net.sergeych.lynon.LynonEncoder
import net.sergeych.lynon.LynonType
@ -133,10 +137,21 @@ open class ObjException(
}
val Root = ExceptionClass("Exception").apply {
addConst("message", statement {
(thisObj as ObjException).message.toObj()
})
addFn("stackTrace") {
addConstDoc(
name = "message",
value = statement {
(thisObj as ObjException).message.toObj()
},
doc = "Human‑readable error message.",
type = type("lyng.String"),
moduleName = "lyng.stdlib"
)
addFnDoc(
name = "stackTrace",
doc = "Stack trace captured at throw site as a list of `StackTraceEntry`.",
returns = TypeGenericDoc(type("lyng.List"), listOf(type("lyng.StackTraceEntry"))),
moduleName = "lyng.stdlib"
) {
(thisObj as ObjException).getStackTrace()
}
}

View File

@ -25,6 +25,10 @@ import kotlinx.coroutines.channels.SendChannel
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import net.sergeych.lyng.*
import net.sergeych.lyng.miniast.ParamDoc
import net.sergeych.lyng.miniast.TypeGenericDoc
import net.sergeych.lyng.miniast.addFnDoc
import net.sergeych.lyng.miniast.type
import net.sergeych.mp_tools.globalLaunch
import kotlin.coroutines.cancellation.CancellationException
@ -36,7 +40,13 @@ class ObjFlowBuilder(val output: SendChannel<Obj>) : Obj() {
companion object {
@OptIn(DelicateCoroutinesApi::class)
val type = object : ObjClass("FlowBuilder") {}.apply {
addFn("emit") {
addFnDoc(
name = "emit",
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
@ -47,7 +57,6 @@ class ObjFlowBuilder(val output: SendChannel<Obj>) : Obj() {
throw ScriptFlowIsNoMoreCollected()
} catch (x: Exception) {
// Any failure to send (including closed channel) should gracefully stop the producer.
// Do not print stack traces here to keep test output clean on JVM.
if (x is CancellationException) {
// Cancellation is a normal control-flow event
throw ScriptFlowIsNoMoreCollected()
@ -90,7 +99,12 @@ class ObjFlow(val producer: Statement, val scope: Scope) : Obj() {
scope.raiseError("Flow constructor is not available")
}
}.apply {
addFn("iterator") {
addFnDoc(
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(
@ -146,14 +160,27 @@ class ObjFlowIterator(val producer: Statement) : Obj() {
val type = object : ObjClass("FlowIterator", ObjIterator) {
}.apply {
addFn("hasNext") {
thisAs<ObjFlowIterator>().hasNext(this).toObj()
}
addFn("next") {
addFnDoc(
name = "hasNext",
doc = "Whether another element is available from the flow.",
returns = type("lyng.Bool"),
moduleName = "lyng.stdlib"
) { thisAs<ObjFlowIterator>().hasNext(this).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)
}
addFn("cancelIteration") {
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

View File

@ -19,6 +19,9 @@ package net.sergeych.lyng.obj
import net.sergeych.lyng.Arguments
import net.sergeych.lyng.Statement
import net.sergeych.lyng.miniast.ParamDoc
import net.sergeych.lyng.miniast.addFnDoc
import net.sergeych.lyng.miniast.type
/**
* Abstract class that must provide `iterator` method that returns [ObjIterator] instance.
@ -26,7 +29,12 @@ import net.sergeych.lyng.Statement
val ObjIterable by lazy {
ObjClass("Iterable").apply {
addFn("toList") {
addFnDoc(
name = "toList",
doc = "Collect elements of this iterable into a new list.",
returns = type("lyng.List"),
moduleName = "lyng.stdlib"
) {
val result = mutableListOf<Obj>()
val iterator = thisObj.invokeInstanceMethod(this, "iterator")
@ -36,29 +44,48 @@ val ObjIterable by lazy {
}
// it is not effective, but it is open:
addFn("contains", isOpen = true) {
addFnDoc(
name = "contains",
doc = "Whether the iterable contains the given element (by equality).",
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.compareTo(this, it.invokeInstanceMethod(this, "next")) == 0)
return@addFn ObjTrue
return@addFnDoc ObjTrue
}
ObjFalse
}
addFn("indexOf", isOpen = true) {
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"),
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.compareTo(this, it.invokeInstanceMethod(this, "next")) == 0)
return@addFn ObjInt(index.toLong())
return@addFnDoc ObjInt(index.toLong())
index++
}
ObjInt(-1L)
}
addFn("toSet") {
addFnDoc(
name = "toSet",
doc = "Collect elements of this iterable into a new set.",
returns = type("lyng.Set"),
moduleName = "lyng.stdlib"
) {
if( thisObj.isInstanceOf(ObjSet.type) )
thisObj
else {
@ -71,7 +98,12 @@ val ObjIterable by lazy {
}
}
addFn("toMap") {
addFnDoc(
name = "toMap",
doc = "Collect pairs into a map using [0] as key and [1] as value for each element.",
returns = type("lyng.Map"),
moduleName = "lyng.stdlib"
) {
val result = ObjMap()
thisObj.toFlow(this).collect { pair ->
result.map[pair.getAt(this, 0)] = pair.getAt(this, 1)
@ -79,7 +111,13 @@ val ObjIterable by lazy {
result
}
addFn("associateBy") {
addFnDoc(
name = "associateBy",
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 {
@ -88,7 +126,13 @@ val ObjIterable by lazy {
result
}
addFn("forEach", isOpen = true) {
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()) {
@ -98,7 +142,14 @@ val ObjIterable by lazy {
ObjVoid
}
addFn("map", isOpen = true) {
addFnDoc(
name = "map",
doc = "Transform elements by applying the given lambda.",
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 {
@ -107,7 +158,13 @@ val ObjIterable by lazy {
ObjList(result)
}
addFn("take") {
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) {
@ -119,7 +176,12 @@ val ObjIterable by lazy {
ObjList(result)
}
addFn("isEmpty") {
addFnDoc(
name = "isEmpty",
doc = "Whether the iterable has no elements.",
returns = type("lyng.Bool"),
moduleName = "lyng.stdlib"
) {
ObjBool(
thisObj.invokeInstanceMethod(this, "iterator")
.invokeInstanceMethod(this, "hasNext").toBool()
@ -127,7 +189,13 @@ val ObjIterable by lazy {
)
}
addFn("sortedWith") {
addFnDoc(
name = "sortedWith",
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 ->
@ -136,7 +204,12 @@ val ObjIterable by lazy {
list
}
addFn("reversed") {
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

View File

@ -17,6 +17,10 @@
package net.sergeych.lyng.obj
import net.sergeych.lyng.miniast.TypeGenericDoc
import net.sergeych.lyng.miniast.addFnDoc
import net.sergeych.lyng.miniast.type
/**
* Iterator should provide lyng-level iterator functions:
*
@ -28,15 +32,50 @@ package net.sergeych.lyng.obj
*/
val ObjIterator by lazy {
ObjClass("Iterator").apply {
addFn("cancelIteration", true) {
// Base protocol methods; actual iterators override these.
addFnDoc(
name = "cancelIteration",
doc = "Optional hint to stop iteration early and free resources.",
returns = type("lyng.Void"),
isOpen = true,
moduleName = "lyng.stdlib"
) {
ObjVoid
}
addFn("hasNext", true) {
addFnDoc(
name = "hasNext",
doc = "Whether another element is available.",
returns = type("lyng.Bool"),
isOpen = true,
moduleName = "lyng.stdlib"
) {
raiseNotImplemented("hasNext() is not implemented")
}
addFn("next", true) {
addFnDoc(
name = "next",
doc = "Return the next element.",
returns = type("lyng.Any"),
isOpen = true,
moduleName = "lyng.stdlib"
) {
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
}
ObjList(out.toMutableList())
}
}
}

View File

@ -21,6 +21,10 @@ import kotlinx.serialization.json.JsonArray
import kotlinx.serialization.json.JsonElement
import net.sergeych.lyng.Scope
import net.sergeych.lyng.Statement
import net.sergeych.lyng.miniast.ParamDoc
import net.sergeych.lyng.miniast.addConstDoc
import net.sergeych.lyng.miniast.addFnDoc
import net.sergeych.lyng.miniast.type
import net.sergeych.lyng.statement
import net.sergeych.lynon.LynonDecoder
import net.sergeych.lynon.LynonEncoder
@ -200,19 +204,32 @@ class ObjList(val list: MutableList<Obj> = mutableListOf()) : Obj() {
return ObjList(decoder.decodeAnyList(scope))
}
}.apply {
createField("size",
statement {
addConstDoc(
name = "size",
value = statement {
(thisObj as ObjList).list.size.toObj()
}
},
doc = "Number of elements in this list.",
type = type("lyng.Int"),
moduleName = "lyng.stdlib"
)
createField("add",
statement {
addConstDoc(
name = "add",
value = statement {
val l = thisAs<ObjList>().list
for (a in args) l.add(a)
ObjVoid
}
},
doc = "Append one or more elements to the end of this list.",
type = type("lyng.Callable"),
moduleName = "lyng.stdlib"
)
addFn("insertAt") {
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()
@ -220,7 +237,12 @@ class ObjList(val list: MutableList<Obj> = mutableListOf()) : Obj() {
ObjVoid
}
addFn("removeAt") {
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) {
@ -231,7 +253,12 @@ class ObjList(val list: MutableList<Obj> = mutableListOf()) : Obj() {
self
}
addFn("removeLast") {
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()
@ -242,7 +269,12 @@ class ObjList(val list: MutableList<Obj> = mutableListOf()) : Obj() {
self
}
addFn("removeRange") {
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)
@ -283,19 +315,32 @@ class ObjList(val list: MutableList<Obj> = mutableListOf()) : Obj() {
self
}
addFn("sortWith") {
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
}
addFn("shuffle") {
addFnDoc(
name = "shuffle",
doc = "Shuffle elements of this list in-place.",
moduleName = "lyng.stdlib"
) {
thisAs<ObjList>().list.shuffle()
ObjVoid
}
addFn("sum") {
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@addFn ObjNull
if (l.isEmpty()) return@addFnDoc ObjNull
if (net.sergeych.lyng.PerfFlags.PRIMITIVE_FASTOPS) {
// Fast path: all ints → accumulate as long
var i = 0
@ -312,10 +357,10 @@ class ObjList(val list: MutableList<Obj> = mutableListOf()) : Obj() {
res = res.plus(this, l[i])
i++
}
return@addFn res
return@addFnDoc res
}
}
return@addFn ObjInt(acc)
return@addFnDoc ObjInt(acc)
}
// Generic path: dynamic '+' starting from first element
var res: Obj = l[0]
@ -326,9 +371,13 @@ class ObjList(val list: MutableList<Obj> = mutableListOf()) : Obj() {
}
res
}
addFn("min") {
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@addFn ObjNull
if (l.isEmpty()) return@addFnDoc ObjNull
if (net.sergeych.lyng.PerfFlags.PRIMITIVE_FASTOPS) {
var i = 0
var hasOnlyInts = true
@ -343,7 +392,7 @@ class ObjList(val list: MutableList<Obj> = mutableListOf()) : Obj() {
}
i++
}
if (hasOnlyInts) return@addFn ObjInt(minVal)
if (hasOnlyInts) return@addFnDoc ObjInt(minVal)
}
var res: Obj = l[0]
var i = 1
@ -354,9 +403,13 @@ class ObjList(val list: MutableList<Obj> = mutableListOf()) : Obj() {
}
res
}
addFn("max") {
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@addFn ObjNull
if (l.isEmpty()) return@addFnDoc ObjNull
if (net.sergeych.lyng.PerfFlags.PRIMITIVE_FASTOPS) {
var i = 0
var hasOnlyInts = true
@ -371,7 +424,7 @@ class ObjList(val list: MutableList<Obj> = mutableListOf()) : Obj() {
}
i++
}
if (hasOnlyInts) return@addFn ObjInt(maxVal)
if (hasOnlyInts) return@addFnDoc ObjInt(maxVal)
}
var res: Obj = l[0]
var i = 1
@ -382,21 +435,27 @@ class ObjList(val list: MutableList<Obj> = mutableListOf()) : Obj() {
}
res
}
addFn("indexOf") {
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@addFn ObjInt(i.toLong())
if (v is ObjInt && v.value == needle.value) return@addFnDoc ObjInt(i.toLong())
i++
}
return@addFn ObjInt((-1).toLong())
return@addFnDoc ObjInt((-1).toLong())
}
var i = 0
while (i < l.size) {
if (l[i].compareTo(this, needle) == 0) return@addFn ObjInt(i.toLong())
if (l[i].compareTo(this, needle) == 0) return@addFnDoc ObjInt(i.toLong())
i++
}
ObjInt((-1).toLong())

View File

@ -21,6 +21,10 @@ import kotlinx.serialization.json.JsonElement
import kotlinx.serialization.json.JsonObject
import net.sergeych.lyng.Scope
import net.sergeych.lyng.Statement
import net.sergeych.lyng.miniast.ParamDoc
import net.sergeych.lyng.miniast.TypeGenericDoc
import net.sergeych.lyng.miniast.addFnDoc
import net.sergeych.lyng.miniast.type
import net.sergeych.lynon.LynonDecoder
import net.sergeych.lynon.LynonEncoder
import net.sergeych.lynon.LynonType
@ -72,9 +76,24 @@ class ObjMapEntry(val key: Obj, val value: Obj) : Obj() {
)
}
}.apply {
addFn("key") { thisAs<ObjMapEntry>().key }
addFn("value") { thisAs<ObjMapEntry>().value }
addFn("size") { 2.toObj() }
addFnDoc(
name = "key",
doc = "Key component of this map entry.",
returns = type("lyng.Any"),
moduleName = "lyng.stdlib"
) { thisAs<ObjMapEntry>().key }
addFnDoc(
name = "value",
doc = "Value component of this map entry.",
returns = type("lyng.Any"),
moduleName = "lyng.stdlib"
) { thisAs<ObjMapEntry>().value }
addFnDoc(
name = "size",
doc = "Number of components in this entry (always 2).",
returns = type("lyng.Int"),
moduleName = "lyng.stdlib"
) { 2.toObj() }
}
}
@ -184,34 +203,77 @@ class ObjMap(val map: MutableMap<Obj, Obj> = mutableMapOf()) : Obj() {
return ObjMap(keys.zip(values).toMap().toMutableMap())
}
}.apply {
addFn("getOrNull") {
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 }
}
addFn("getOrPut") {
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)
}
}
addFn("size") {
addFnDoc(
name = "size",
doc = "Number of entries in the map.",
returns = type("lyng.Int"),
moduleName = "lyng.stdlib"
) {
thisAs<ObjMap>().map.size.toObj()
}
addFn("remove") {
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
}
addFn("clear") {
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
}
addFn("keys") {
addFnDoc(
name = "keys",
doc = "List of keys in this map.",
returns = TypeGenericDoc(type("lyng.List"), listOf(type("lyng.Any"))),
moduleName = "lyng.stdlib"
) {
thisAs<ObjMap>().map.keys.toObj()
}
addFn("values") {
addFnDoc(
name = "values",
doc = "List of values in this map.",
returns = TypeGenericDoc(type("lyng.List"), listOf(type("lyng.Any"))),
moduleName = "lyng.stdlib"
) {
ObjList(thisAs<ObjMap>().map.values.toMutableList())
}
addFn("iterator") {
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())
}
}

View File

@ -21,6 +21,9 @@ import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import net.sergeych.lyng.Scope
import net.sergeych.lyng.Statement
import net.sergeych.lyng.miniast.ParamDoc
import net.sergeych.lyng.miniast.addFnDoc
import net.sergeych.lyng.miniast.type
class ObjMutex(val mutex: Mutex): Obj() {
override val objClass = type
@ -31,7 +34,13 @@ class ObjMutex(val mutex: Mutex): Obj() {
return ObjMutex(Mutex())
}
}.apply {
addFn("withLock") {
addFnDoc(
name = "withLock",
doc = "Run the given lambda while holding the mutex and return its result.",
params = listOf(ParamDoc("action")),
returns = type("lyng.Any"),
moduleName = "lyng.stdlib"
) {
val f = 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

View File

@ -18,6 +18,9 @@
package net.sergeych.lyng.obj
import net.sergeych.lyng.Scope
import net.sergeych.lyng.miniast.TypeGenericDoc
import net.sergeych.lyng.miniast.addFnDoc
import net.sergeych.lyng.miniast.type
class ObjRange(val start: Obj?, val end: Obj?, val isEndInclusive: Boolean) : Obj() {
@ -141,25 +144,60 @@ class ObjRange(val start: Obj?, val end: Obj?, val isEndInclusive: Boolean) : Ob
companion object {
val type = ObjClass("Range", ObjIterable).apply {
addFn("start") {
addFnDoc(
name = "start",
doc = "Start bound of the range or null if open.",
returns = type("lyng.Any", nullable = true),
moduleName = "lyng.stdlib"
) {
thisAs<ObjRange>().start ?: ObjNull
}
addFn("end") {
addFnDoc(
name = "end",
doc = "End bound of the range or null if open.",
returns = type("lyng.Any", nullable = true),
moduleName = "lyng.stdlib"
) {
thisAs<ObjRange>().end ?: ObjNull
}
addFn("isOpen") {
addFnDoc(
name = "isOpen",
doc = "Whether the range is open on either side (no start or no end).",
returns = type("lyng.Bool"),
moduleName = "lyng.stdlib"
) {
thisAs<ObjRange>().let { it.start == null || it.end == null }.toObj()
}
addFn("isIntRange") {
addFnDoc(
name = "isIntRange",
doc = "True if both bounds are Int values.",
returns = type("lyng.Bool"),
moduleName = "lyng.stdlib"
) {
thisAs<ObjRange>().isIntRange.toObj()
}
addFn("isCharRange") {
addFnDoc(
name = "isCharRange",
doc = "True if both bounds are Char values.",
returns = type("lyng.Bool"),
moduleName = "lyng.stdlib"
) {
thisAs<ObjRange>().isCharRange.toObj()
}
addFn("isEndInclusive") {
addFnDoc(
name = "isEndInclusive",
doc = "Whether the end bound is inclusive.",
returns = type("lyng.Bool"),
moduleName = "lyng.stdlib"
) {
thisAs<ObjRange>().isEndInclusive.toObj()
}
addFn("iterator") {
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
@ -169,7 +207,7 @@ class ObjRange(val start: Obj?, val end: Obj?, val isEndInclusive: Boolean) : Ob
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@addFn ObjFastIntRangeIterator(start, endExclusive)
return@addFnDoc ObjFastIntRangeIterator(start, endExclusive)
}
}
}

View File

@ -21,6 +21,9 @@ import kotlinx.serialization.json.JsonElement
import kotlinx.serialization.json.JsonPrimitive
import net.sergeych.lyng.Pos
import net.sergeych.lyng.Scope
import net.sergeych.lyng.miniast.addConstDoc
import net.sergeych.lyng.miniast.addFnDoc
import net.sergeych.lyng.miniast.type
import net.sergeych.lyng.statement
import net.sergeych.lynon.LynonDecoder
import net.sergeych.lynon.LynonEncoder
@ -111,13 +114,22 @@ data class ObjReal(val value: Double) : Obj(), Numeric {
override suspend fun deserialize(scope: Scope, decoder: LynonDecoder, lynonType: LynonType?): Obj =
ObjReal(decoder.unpackDouble())
}.apply {
createField(
"roundToInt",
statement(Pos.builtIn) {
// roundToInt: number rounded to the nearest integer
addConstDoc(
name = "roundToInt",
value = statement(Pos.builtIn) {
(it.thisObj as ObjReal).value.roundToLong().toObj()
},
doc = "This real number rounded to the nearest integer.",
type = type("lyng.Int"),
moduleName = "lyng.stdlib"
)
addFn("toInt") {
addFnDoc(
name = "toInt",
doc = "Truncate this real number toward zero to an integer.",
returns = type("lyng.Int"),
moduleName = "lyng.stdlib"
) {
ObjInt(thisAs<ObjReal>().value.toLong())
}
}

View File

@ -20,6 +20,10 @@ package net.sergeych.lyng.obj
import net.sergeych.lyng.PerfFlags
import net.sergeych.lyng.RegexCache
import net.sergeych.lyng.Scope
import net.sergeych.lyng.miniast.ParamDoc
import net.sergeych.lyng.miniast.TypeGenericDoc
import net.sergeych.lyng.miniast.addFnDoc
import net.sergeych.lyng.miniast.type
class ObjRegex(val regex: Regex) : Obj() {
override val objClass = type
@ -43,13 +47,31 @@ class ObjRegex(val regex: Regex) : Obj() {
return ObjRegex(re)
}
}.apply {
addFn("matches") {
addFnDoc(
name = "matches",
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))
}
addFn("find") {
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>())
}
addFn("findAll") {
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())
}
@ -101,13 +123,28 @@ class ObjRegexMatch(val match: MatchResult) : Obj() {
scope.raiseError("RegexMatch can't be constructed directly")
}
}.apply {
addFn("groups") {
addFnDoc(
name = "groups",
doc = "List of captured groups with index 0 as the whole match.",
returns = TypeGenericDoc(type("lyng.List"), listOf(type("lyng.String"))),
moduleName = "lyng.stdlib"
) {
thisAs<ObjRegexMatch>().objGroups
}
addFn("value") {
addFnDoc(
name = "value",
doc = "The matched substring.",
returns = type("lyng.String"),
moduleName = "lyng.stdlib"
) {
thisAs<ObjRegexMatch>().objValue
}
addFn("range") {
addFnDoc(
name = "range",
doc = "Range of the match in the input (end-exclusive).",
returns = type("lyng.Range"),
moduleName = "lyng.stdlib"
) {
thisAs<ObjRegexMatch>().objRange
}
}

View File

@ -18,6 +18,10 @@
package net.sergeych.lyng.obj
import net.sergeych.lyng.Scope
import net.sergeych.lyng.miniast.ParamDoc
import net.sergeych.lyng.miniast.TypeGenericDoc
import net.sergeych.lyng.miniast.addFnDoc
import net.sergeych.lyng.miniast.type
class RingBuffer<T>(val maxSize: Int) : Iterable<T> {
private val data = arrayOfNulls<Any>(maxSize)
@ -90,19 +94,34 @@ class ObjRingBuffer(val capacity: Int) : Obj() {
return ObjRingBuffer(scope.requireOnlyArg<ObjInt>().toInt())
}
}.apply {
addFn("capacity") {
thisAs<ObjRingBuffer>().capacity.toObj()
}
addFn("size") {
thisAs<ObjRingBuffer>().buffer.size.toObj()
}
addFn("iterator") {
addFnDoc(
name = "capacity",
doc = "Maximum number of elements the buffer can hold.",
returns = type("lyng.Int"),
moduleName = "lyng.stdlib"
) { thisAs<ObjRingBuffer>().capacity.toObj() }
addFnDoc(
name = "size",
doc = "Current number of elements in the buffer.",
returns = type("lyng.Int"),
moduleName = "lyng.stdlib"
) { 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())
}
addFn("add") {
thisAs<ObjRingBuffer>().apply { buffer.add(requireOnlyArg<Obj>()) }
}
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>()) } }
}
}
}

View File

@ -18,6 +18,10 @@
package net.sergeych.lyng.obj
import net.sergeych.lyng.Scope
import net.sergeych.lyng.miniast.ParamDoc
import net.sergeych.lyng.miniast.TypeGenericDoc
import net.sergeych.lyng.miniast.addFnDoc
import net.sergeych.lyng.miniast.type
import net.sergeych.lynon.LynonDecoder
import net.sergeych.lynon.LynonEncoder
import net.sergeych.lynon.LynonType
@ -140,22 +144,55 @@ class ObjSet(val set: MutableSet<Obj> = mutableSetOf()) : Obj() {
override suspend fun deserialize(scope: Scope, decoder: LynonDecoder, lynonType: LynonType?): Obj =
ObjSet(decoder.decodeAnyList(scope).toMutableSet())
}.apply {
addFn("size") {
addFnDoc(
name = "size",
doc = "Number of elements in this set.",
returns = type("lyng.Int"),
moduleName = "lyng.stdlib"
) {
thisAs<ObjSet>().set.size.toObj()
}
addFn("intersect") {
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())
}
addFn("iterator") {
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()
}
addFn("union") {
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())
}
addFn("subtract") {
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())
}
addFn("remove") {
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

View File

@ -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.miniast.*
import net.sergeych.lyng.statement
import net.sergeych.lynon.LynonDecoder
import net.sergeych.lynon.LynonEncoder
@ -127,64 +128,155 @@ data class ObjString(val value: String) : Obj() {
override suspend fun deserialize(scope: Scope, decoder: LynonDecoder, lynonType: LynonType?): Obj =
ObjString(decoder.unpackBinaryData().decodeToString())
}.apply {
addFn("toInt") {
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(
thisAs<ObjString>().value.toLongOrNull()
?: raiseIllegalArgument("can't convert to int: $thisObj")
)
}
addFn("startsWith") {
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))
}
addFn("endsWith") {
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))
}
addConst("length",
statement { ObjInt(thisAs<ObjString>().value.length.toLong()) }
addConstDoc(
name = "length",
value = statement { ObjInt(thisAs<ObjString>().value.length.toLong()) },
doc = "Number of UTF-16 code units in this string.",
type = type("lyng.Int"),
moduleName = "lyng.stdlib"
)
addFn("takeLast") {
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)
}
addFn("take") {
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)
}
addFn("drop") {
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)
}
addFn("dropLast") {
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)
}
addFn("lower") {
addFnDoc(
name = "lower",
doc = "Lowercase version of this string (default locale).",
returns = type("lyng.String"),
moduleName = "lyng.stdlib"
) {
thisAs<ObjString>().value.lowercase().let(::ObjString)
}
addFn("upper") {
addFnDoc(
name = "upper",
doc = "Uppercase version of this string (default locale).",
returns = type("lyng.String"),
moduleName = "lyng.stdlib"
) {
thisAs<ObjString>().value.uppercase().let(::ObjString)
}
addFn("characters") {
addFnDoc(
name = "characters",
doc = "List of characters of this string.",
returns = TypeGenericDoc(type("lyng.List"), listOf(type("lyng.Char"))),
moduleName = "lyng.stdlib"
) {
ObjList(
thisAs<ObjString>().value.map { ObjChar(it) }.toMutableList()
)
}
addFn("last") {
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"))
}
addFn("encodeUtf8") { ObjBuffer(thisAs<ObjString>().value.encodeToByteArray().asUByteArray()) }
addFn("size") { ObjInt(thisAs<ObjString>().value.length.toLong()) }
addFn("toReal") {
addFnDoc(
name = "encodeUtf8",
doc = "Encode this string as UTF-8 bytes.",
returns = type("lyng.Buffer"),
moduleName = "lyng.stdlib"
) { ObjBuffer(thisAs<ObjString>().value.encodeToByteArray().asUByteArray()) }
addFnDoc(
name = "size",
doc = "Alias for length: the number of characters (code units) in this string.",
returns = type("lyng.Int"),
moduleName = "lyng.stdlib"
) { ObjInt(thisAs<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(thisAs<ObjString>().value.toDouble())
}
addFn("trim") {
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)
}
addFn("matches") {
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(

View File

@ -1,5 +1,9 @@
package lyng.stdlib
/*
Wrap a builder into a zero-argument thunk that computes once and caches the result.
The first call invokes builder() and stores the value; subsequent calls return the cached value.
*/
fun cached(builder) {
var calculated = false
var value = null
@ -11,7 +15,7 @@ fun cached(builder) {
value
}
}
/* Filter elements of this iterable using the provided predicate. */
fun Iterable.filter(predicate) {
val list = this
flow {
@ -23,17 +27,20 @@ fun Iterable.filter(predicate) {
}
}
/* Skip the first N elements of this iterable. */
fun Iterable.drop(n) {
var cnt = 0
filter { cnt++ >= n }
}
/* Return the first element or throw if the iterable is empty. */
fun Iterable.first() {
val i = iterator()
if( !i.hasNext() ) throw NoSuchElementException()
i.next().also { i.cancelIteration() }
}
/* Return the last element or throw if the iterable is empty. */
fun Iterable.last() {
var found = false
var element = null
@ -45,6 +52,7 @@ fun Iterable.last() {
element
}
/* Emit all but the last N elements of this iterable. */
fun Iterable.dropLast(n) {
val list = this
val buffer = RingBuffer(n)
@ -57,12 +65,14 @@ fun Iterable.dropLast(n) {
}
}
/* Return the last N elements of this iterable as a buffer/list. */
fun Iterable.takeLast(n) {
val buffer = RingBuffer(n)
for( item in this ) buffer += item
buffer
}
/* Join elements into a string with a separator (prefix parameter) and optional transformer. */
fun Iterable.joinToString(prefix=" ", transformer=null) {
var result = null
for( part in this ) {
@ -73,6 +83,7 @@ fun Iterable.joinToString(prefix=" ", transformer=null) {
result ?: ""
}
/* Return true if any element matches the predicate. */
fun Iterable.any(predicate): Bool {
for( i in this ) {
if( predicate(i) )
@ -80,10 +91,12 @@ fun Iterable.any(predicate): Bool {
} else false
}
/* Return true if all elements match the predicate. */
fun Iterable.all(predicate): Bool {
!any { !predicate(it) }
}
/* Sum all elements; returns null for empty collections. */
fun Iterable.sum() {
val i = iterator()
if( i.hasNext() ) {
@ -94,6 +107,7 @@ fun Iterable.sum() {
else null
}
/* Sum mapped values of elements; returns null for empty collections. */
fun Iterable.sumOf(f) {
val i = iterator()
if( i.hasNext() ) {
@ -104,6 +118,7 @@ fun Iterable.sumOf(f) {
else null
}
/* Minimum value of the given function applied to elements of the collection. */
fun Iterable.minOf( lambda ) {
val i = iterator()
var minimum = lambda( i.next() )
@ -114,9 +129,7 @@ fun Iterable.minOf( lambda ) {
minimum
}
/*
Return maximum value of the given function applied to elements of the collection.
*/
/* Maximum value of the given function applied to elements of the collection. */
fun Iterable.maxOf( lambda ) {
val i = iterator()
var maximum = lambda( i.next() )
@ -127,41 +140,50 @@ fun Iterable.maxOf( lambda ) {
maximum
}
/* Return elements sorted by natural order. */
fun Iterable.sorted() {
sortedWith { a, b -> a <=> b }
}
/* Return elements sorted by the key selector. */
fun Iterable.sortedBy(predicate) {
sortedWith { a, b -> predicate(a) <=> predicate(b) }
}
/* Return a shuffled copy of the iterable as a list. */
fun Iterable.shuffled() {
toList().apply { shuffle() }
}
/* Return string representation like [a,b,c]. */
fun List.toString() {
"[" + joinToString(",") + "]"
}
/* Sort list in-place by key selector. */
fun List.sortBy(predicate) {
sortWith { a, b -> predicate(a) <=> predicate(b) }
}
/* Sort list in-place by natural order. */
fun List.sort() {
sortWith { a, b -> a <=> b }
}
/* Represents a single stack trace element. */
class StackTraceEntry(
val sourceName: String,
val line: Int,
val column: Int,
val sourceString: String
) {
/* Formatted representation: source:line:column: text. */
fun toString() {
"%s:%d:%d: %s"(sourceName, line, column, sourceString.trim())
}
}
/* Print this exception and its stack trace to standard output. */
fun Exception.printStackTrace() {
println(this)
for( entry in stackTrace() ) {
@ -169,6 +191,7 @@ fun Exception.printStackTrace() {
}
}
/* Compile this string into a regular expression. */
fun String.re() { Regex(this) }