diff --git a/lyngio/src/commonMain/kotlin/net/sergeych/lyng/io/fs/LyngFsModule.kt b/lyngio/src/commonMain/kotlin/net/sergeych/lyng/io/fs/LyngFsModule.kt index fdc2a0a..9bc3ca1 100644 --- a/lyngio/src/commonMain/kotlin/net/sergeych/lyng/io/fs/LyngFsModule.kt +++ b/lyngio/src/commonMain/kotlin/net/sergeych/lyng/io/fs/LyngFsModule.kt @@ -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 ) { diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Script.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Script.kt index 48f100d..4858297 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Script.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Script.kt @@ -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 } } } diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/miniast/BuiltinDocRegistry.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/miniast/BuiltinDocRegistry.kt index ce465e7..c8c3f88 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/miniast/BuiltinDocRegistry.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/miniast/BuiltinDocRegistry.kt @@ -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 = 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() + 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 { val decls = mutableListOf() // Use the same DSL builders to construct decls @@ -270,7 +389,7 @@ private fun buildStdlibDocs(): List { // 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 { ) 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 { // 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 { // 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 { ) 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 { // 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() diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjArray.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjArray.kt index b06a828..3200b9d 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjArray.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjArray.kt @@ -17,6 +17,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.. return@addFn (mid).toObj() + cmp == 0 -> return@addFnDoc (mid).toObj() cmp > 0 -> high = mid - 1 else -> low = mid + 1 } } - // Элемент не найден, возвращаем -(точка вставки) - 1 (-low - 1).toObj() } } diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjChar.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjChar.kt index 1c90000..2e6b994 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjChar.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjChar.kt @@ -18,6 +18,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().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().value.code.toLong()) } } } } \ No newline at end of file diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjClass.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjClass.kt index ffe567e..1b5d7eb 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjClass.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjClass.kt @@ -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().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() + val seen = hashSetOf() + val names = mutableListOf() + for (c in cls.mro) { + for ((n, rec) in c.members) { + if (rec.value !is Statement && seen.add(n)) names += ObjString(n) + } + } + 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() + val seen = hashSetOf() + val names = mutableListOf() + for (c in cls.mro) { + for ((n, rec) in c.members) { + if (rec.value is Statement && seen.add(n)) names += ObjString(n) + } + } + 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() + val name = requiredArg(0).value + val rec = cls.getInstanceMemberOrNull(name) + rec?.value ?: ObjNull + } + } +} open class ObjClass( val className: String, diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjCompletableDeferred.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjCompletableDeferred.kt index c605170..69eced7 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjCompletableDeferred.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjCompletableDeferred.kt @@ -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): ObjDeferred(completableDeferred) { @@ -30,7 +33,13 @@ class ObjCompletableDeferred(val completableDeferred: CompletableDeferred): 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().completableDeferred.complete(args.firstAndOnly()) ObjVoid } diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjDeferred.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjDeferred.kt index f6bb23c..635e71f 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjDeferred.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjDeferred.kt @@ -19,6 +19,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() { @@ -30,20 +32,33 @@ open class ObjDeferred(val deferred: Deferred): Obj() { scope.raiseError("Deferred constructor is not directly callable") } }.apply { - addFn("await") { - thisAs().deferred.await() - } - addFn("isCompleted") { - thisAs().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().deferred.await() } + addFnDoc( + name = "isCompleted", + doc = "Whether this deferred has completed (successfully or with an error).", + returns = type("lyng.Bool"), + moduleName = "lyng.stdlib" + ) { thisAs().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().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().deferred.isCancelled.toObj() - } + addFnDoc( + name = "isCancelled", + doc = "Whether this deferred was cancelled.", + returns = type("lyng.Bool"), + moduleName = "lyng.stdlib" + ) { thisAs().deferred.isCancelled.toObj() } } } diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjDuration.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjDuration.kt index ec21bc3..174735d 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjDuration.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjDuration.kt @@ -18,6 +18,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().duration.toDouble(DurationUnit.DAYS).toObj() - } - addFn("hours") { - thisAs().duration.toDouble(DurationUnit.HOURS).toObj() - } - addFn("minutes") { - thisAs().duration.toDouble(DurationUnit.MINUTES).toObj() - } - addFn("seconds") { - thisAs().duration.toDouble(DurationUnit.SECONDS).toObj() - } - addFn("milliseconds") { - thisAs().duration.toDouble(DurationUnit.MILLISECONDS).toObj() - } - addFn("microseconds") { - thisAs().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().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().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().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().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().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().duration.toDouble(DurationUnit.MICROSECONDS).toObj() } // extensions - ObjInt.type.addFn("seconds") { - ObjDuration(thisAs().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().value.seconds) } - ObjInt.type.addFn("second") { - ObjDuration(thisAs().value.seconds) - } - ObjInt.type.addFn("milliseconds") { - ObjDuration(thisAs().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().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().value.milliseconds) } - ObjInt.type.addFn("millisecond") { - ObjDuration(thisAs().value.milliseconds) - } - ObjReal.type.addFn("seconds") { - ObjDuration(thisAs().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().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().value.seconds) } - ObjReal.type.addFn("second") { - ObjDuration(thisAs().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().value.seconds) } - ObjReal.type.addFn("milliseconds") { - ObjDuration(thisAs().value.milliseconds) - } - ObjReal.type.addFn("millisecond") { - ObjDuration(thisAs().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().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().value.milliseconds) } - ObjInt.type.addFn("minutes") { - ObjDuration(thisAs().value.minutes) - } - ObjReal.type.addFn("minutes") { - ObjDuration(thisAs().value.minutes) - } - ObjInt.type.addFn("minute") { - ObjDuration(thisAs().value.minutes) - } - ObjReal.type.addFn("minute") { - ObjDuration(thisAs().value.minutes) - } - ObjInt.type.addFn("hours") { - ObjDuration(thisAs().value.hours) - } - ObjReal.type.addFn("hours") { - ObjDuration(thisAs().value.hours) - } - ObjInt.type.addFn("hour") { - ObjDuration(thisAs().value.hours) - } - ObjReal.type.addFn("hour") { - ObjDuration(thisAs().value.hours) - } - ObjInt.type.addFn("days") { - ObjDuration(thisAs().value.days) - } - ObjReal.type.addFn("days") { - ObjDuration(thisAs().value.days) - } - ObjInt.type.addFn("day") { - ObjDuration(thisAs().value.days) - } - ObjReal.type.addFn("day") { - ObjDuration(thisAs().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().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().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().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().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().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().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().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().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().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().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().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().value.days) } // addFn("epochSeconds") { diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjException.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjException.kt index 5177939..d122119 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjException.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjException.kt @@ -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() } } diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjFlow.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjFlow.kt index 07dd8fc..b9a3540 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjFlow.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjFlow.kt @@ -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() { 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() try { val channel = thisAs().output @@ -47,7 +57,6 @@ class ObjFlowBuilder(val output: SendChannel) : 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() 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().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().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() 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() x.cancel() ObjVoid diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjIterable.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjIterable.kt index 9a84340..389a2c1 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjIterable.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjIterable.kt @@ -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() 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() 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(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(0) val result = mutableListOf() 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().value.toInt() val result = mutableListOf() 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(this, "toList") val comparator = requireOnlyArg() 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(this, "toList") list.list.reverse() list diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjIterator.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjIterator.kt index cbffc5f..319f7f0 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjIterator.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjIterator.kt @@ -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() + while (true) { + val has = thisObj.invokeInstanceMethod(this, "hasNext").toBool() + if (!has) break + val v = thisObj.invokeInstanceMethod(this, "next") + out += v + } + ObjList(out.toMutableList()) + } } } diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjList.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjList.kt index fba2f07..55e4a5c 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjList.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjList.kt @@ -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 = 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().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() var index = requiredArg(0).value.toInt() @@ -220,7 +237,12 @@ class ObjList(val list: MutableList = 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() val start = requiredArg(0).value.toInt() if (args.size == 2) { @@ -231,7 +253,12 @@ class ObjList(val list: MutableList = 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() if (args.isNotEmpty()) { val count = requireOnlyArg().value.toInt() @@ -242,7 +269,12 @@ class ObjList(val list: MutableList = 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() val list = self.list val range = requiredArg(0) @@ -283,19 +315,32 @@ class ObjList(val list: MutableList = 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() thisAs().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().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() 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 = 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 = 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().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 = 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 = 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().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 = 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 = 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().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()) diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjMap.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjMap.kt index 58b65ba..b1fa47e 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjMap.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjMap.kt @@ -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().key } - addFn("value") { thisAs().value } - addFn("size") { 2.toObj() } + addFnDoc( + name = "key", + doc = "Key component of this map entry.", + returns = type("lyng.Any"), + moduleName = "lyng.stdlib" + ) { thisAs().key } + addFnDoc( + name = "value", + doc = "Value component of this map entry.", + returns = type("lyng.Any"), + moduleName = "lyng.stdlib" + ) { thisAs().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 = 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().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(0) thisAs().map.getOrPut(key) { val lambda = requiredArg(1) lambda.execute(this) } } - addFn("size") { + addFnDoc( + name = "size", + doc = "Number of entries in the map.", + returns = type("lyng.Int"), + moduleName = "lyng.stdlib" + ) { thisAs().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().map.remove(requiredArg(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().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().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().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().map.entries.iterator()) } } diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjMutex.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjMutex.kt index faa164e..315153c 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjMutex.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjMutex.kt @@ -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(0) // Execute user lambda directly in the current scope to preserve the active scope // ancestry across suspension points. The lambda still constructs a ClosureScope diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjRange.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjRange.kt index 5ea4676..3f782c0 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjRange.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjRange.kt @@ -18,6 +18,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().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().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().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().isIntRange.toObj() } - addFn("isCharRange") { + addFnDoc( + name = "isCharRange", + doc = "True if both bounds are Char values.", + returns = type("lyng.Bool"), + moduleName = "lyng.stdlib" + ) { thisAs().isCharRange.toObj() } - addFn("isEndInclusive") { + addFnDoc( + name = "isEndInclusive", + doc = "Whether the end bound is inclusive.", + returns = type("lyng.Bool"), + moduleName = "lyng.stdlib" + ) { thisAs().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() 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) } } } diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjReal.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjReal.kt index 2dbc4ec..3c9d91a 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjReal.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjReal.kt @@ -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().value.toLong()) } } diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjRegex.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjRegex.kt index 853d3fe..b9b5033 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjRegex.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjRegex.kt @@ -20,6 +20,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().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().find(requireOnlyArg()) } - 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().value ObjList(thisAs().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().objGroups } - addFn("value") { + addFnDoc( + name = "value", + doc = "The matched substring.", + returns = type("lyng.String"), + moduleName = "lyng.stdlib" + ) { thisAs().objValue } - addFn("range") { + addFnDoc( + name = "range", + doc = "Range of the match in the input (end-exclusive).", + returns = type("lyng.Range"), + moduleName = "lyng.stdlib" + ) { thisAs().objRange } } diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjRingBuffer.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjRingBuffer.kt index 7a5b985..66693ef 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjRingBuffer.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjRingBuffer.kt @@ -18,6 +18,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(val maxSize: Int) : Iterable { private val data = arrayOfNulls(maxSize) @@ -90,19 +94,34 @@ class ObjRingBuffer(val capacity: Int) : Obj() { return ObjRingBuffer(scope.requireOnlyArg().toInt()) } }.apply { - addFn("capacity") { - thisAs().capacity.toObj() - } - addFn("size") { - thisAs().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().capacity.toObj() } + addFnDoc( + name = "size", + doc = "Current number of elements in the buffer.", + returns = type("lyng.Int"), + moduleName = "lyng.stdlib" + ) { thisAs().buffer.size.toObj() } + addFnDoc( + name = "iterator", + doc = "Iterator over elements in insertion order (oldest to newest).", + returns = TypeGenericDoc(type("lyng.Iterator"), listOf(type("lyng.Any"))), + moduleName = "lyng.stdlib" + ) { val buffer = thisAs().buffer ObjKotlinObjIterator(buffer.iterator()) } - addFn("add") { - thisAs().apply { buffer.add(requireOnlyArg()) } - } + addFnDoc( + name = "add", + doc = "Append an element; if full, the oldest element is dropped.", + params = listOf(ParamDoc("value", type("lyng.Any"))), + returns = type("lyng.Void"), + moduleName = "lyng.stdlib" + ) { thisAs().apply { buffer.add(requireOnlyArg()) } } } } } \ No newline at end of file diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjSet.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjSet.kt index 5b929ea..c735c15 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjSet.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjSet.kt @@ -18,6 +18,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 = 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().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().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().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().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().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().set val n = set.size for( x in args.list ) set -= x diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjString.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjString.kt index b99a594..803ede9 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjString.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjString.kt @@ -24,6 +24,7 @@ import kotlinx.serialization.json.JsonPrimitive import net.sergeych.lyng.PerfFlags import net.sergeych.lyng.RegexCache import net.sergeych.lyng.Scope +import net.sergeych.lyng.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().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().value.startsWith(requiredArg(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().value.endsWith(requiredArg(0).value)) } - addConst("length", - statement { ObjInt(thisAs().value.length.toLong()) } + addConstDoc( + name = "length", + value = statement { ObjInt(thisAs().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().value.takeLast( requiredArg(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().value.take( requiredArg(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().value.drop( requiredArg(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().value.dropLast( requiredArg(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().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().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().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().value.lastOrNull() ?: raiseNoSuchElement("empty string")) } - addFn("encodeUtf8") { ObjBuffer(thisAs().value.encodeToByteArray().asUByteArray()) } - addFn("size") { ObjInt(thisAs().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().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().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().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().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() val self = thisAs().value ObjBool( diff --git a/lynglib/stdlib/lyng/root.lyng b/lynglib/stdlib/lyng/root.lyng index 8f12266..0c6b5ef 100644 --- a/lynglib/stdlib/lyng/root.lyng +++ b/lynglib/stdlib/lyng/root.lyng @@ -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) } \ No newline at end of file