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) // mkdirs(mustCreate: Bool=false)
addFnDoc( addFnDoc(
name = "mkdirs", 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"))), params = listOf(ParamDoc("mustCreate", type("lyng.Bool"))),
moduleName = module.packageName moduleName = module.packageName
) { ) {
@ -336,7 +336,7 @@ private suspend fun buildFsModule(module: ModuleScope, policy: FsAccessPolicy) {
// move(to: Path|String, overwrite: Bool=false) // move(to: Path|String, overwrite: Bool=false)
addFnDoc( addFnDoc(
name = "move", name = "move",
doc = "Move this path to a new location. `to` may be a `Path` or `String`. 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 // types vary; keep generic description in doc
params = listOf(ParamDoc("to"), ParamDoc("overwrite", type("lyng.Bool"))), params = listOf(ParamDoc("to"), ParamDoc("overwrite", type("lyng.Bool"))),
moduleName = module.packageName moduleName = module.packageName
@ -352,7 +352,7 @@ private suspend fun buildFsModule(module: ModuleScope, policy: FsAccessPolicy) {
// delete(mustExist: Bool=false, recursively: Bool=false) // delete(mustExist: Bool=false, recursively: Bool=false)
addFnDoc( addFnDoc(
name = "delete", 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"))), params = listOf(ParamDoc("mustExist", type("lyng.Bool")), ParamDoc("recursively", type("lyng.Bool"))),
moduleName = module.packageName moduleName = module.packageName
) { ) {
@ -367,7 +367,7 @@ private suspend fun buildFsModule(module: ModuleScope, policy: FsAccessPolicy) {
// copy(to: Path|String, overwrite: Bool=false) // copy(to: Path|String, overwrite: Bool=false)
addFnDoc( addFnDoc(
name = "copy", name = "copy",
doc = "Copy this path to a new location. `to` may be a `Path` or `String`. 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"))), params = listOf(ParamDoc("to"), ParamDoc("overwrite", type("lyng.Bool"))),
moduleName = module.packageName moduleName = module.packageName
) { ) {

View File

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

View File

@ -22,6 +22,7 @@
package net.sergeych.lyng.miniast package net.sergeych.lyng.miniast
import net.sergeych.lyng.Pos import net.sergeych.lyng.Pos
import net.sergeych.lyng.stdlib_included.rootLyng
// ---------------- Types DSL ---------------- // ---------------- Types DSL ----------------
@ -262,7 +263,125 @@ internal fun TypeDoc.toMiniTypeRef(): MiniTypeRef = when (this) {
// ---------------- Built-in module doc seeds ---------------- // ---------------- 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> { private fun buildStdlibDocs(): List<MiniDecl> {
val decls = mutableListOf<MiniDecl>() val decls = mutableListOf<MiniDecl>()
// Use the same DSL builders to construct decls // Use the same DSL builders to construct decls
@ -270,7 +389,7 @@ private fun buildStdlibDocs(): List<MiniDecl> {
// Printing // Printing
mod.funDoc( mod.funDoc(
name = "print", name = "print",
doc = """ doc = StdlibInlineDocIndex.topFunDoc("print") ?: """
Print values to the standard output without a trailing newline. Print values to the standard output without a trailing newline.
Accepts any number of arguments and prints them separated by a space. Accepts any number of arguments and prints them separated by a space.
""".trimIndent(), """.trimIndent(),
@ -279,7 +398,7 @@ private fun buildStdlibDocs(): List<MiniDecl> {
) )
mod.funDoc( mod.funDoc(
name = "println", name = "println",
doc = """ doc = StdlibInlineDocIndex.topFunDoc("println") ?: """
Print values to the standard output and append a newline. Print values to the standard output and append a newline.
Accepts any number of arguments and prints them separated by a space. Accepts any number of arguments and prints them separated by a space.
""".trimIndent(), """.trimIndent(),
@ -288,7 +407,7 @@ private fun buildStdlibDocs(): List<MiniDecl> {
// Caching helper // Caching helper
mod.funDoc( mod.funDoc(
name = "cached", name = "cached",
doc = """ doc = StdlibInlineDocIndex.topFunDoc("cached") ?: """
Wrap a `builder` into a zero-argument thunk that computes once and caches the result. 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. The first call invokes `builder()` and stores the value; subsequent calls return the cached value.
""".trimIndent(), """.trimIndent(),
@ -298,44 +417,44 @@ private fun buildStdlibDocs(): List<MiniDecl> {
// Math helpers (scalar versions) // Math helpers (scalar versions)
fun math1(name: String) = mod.funDoc( fun math1(name: String) = mod.funDoc(
name = name, name = name,
doc = "Compute $name(x).", doc = StdlibInlineDocIndex.topFunDoc(name) ?: "Compute $name(x).",
params = listOf(ParamDoc("x", type("lyng.Number"))) params = listOf(ParamDoc("x", type("lyng.Number")))
) )
math1("sin"); math1("cos"); math1("tan"); math1("asin"); math1("acos"); math1("atan") 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 = "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 = "Round up 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 = "Round 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 // Hyperbolic and inverse hyperbolic
math1("sinh"); math1("cosh"); math1("tanh"); math1("asinh"); math1("acosh"); math1("atanh") math1("sinh"); math1("cosh"); math1("tanh"); math1("asinh"); math1("acosh"); math1("atanh")
// Exponentials and logarithms // Exponentials and logarithms
mod.funDoc(name = "exp", doc = "Euler's exponential e^x.", 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 = "Natural logarithm (base e).", 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 = "Logarithm base 10.", 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 = "Logarithm base 2.", 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 // Power/roots and absolute value
mod.funDoc( mod.funDoc(
name = "pow", 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"))) params = listOf(ParamDoc("x", type("lyng.Number")), ParamDoc("y", type("lyng.Number")))
) )
mod.funDoc( mod.funDoc(
name = "sqrt", name = "sqrt",
doc = "Square root of `x`.", doc = StdlibInlineDocIndex.topFunDoc("sqrt") ?: "Square root of `x`.",
params = listOf(ParamDoc("x", type("lyng.Number"))) params = listOf(ParamDoc("x", type("lyng.Number")))
) )
mod.funDoc( mod.funDoc(
name = "abs", 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"))) params = listOf(ParamDoc("x", type("lyng.Number")))
) )
// Assertions and checks // Assertions and checks
mod.funDoc( mod.funDoc(
name = "assert", name = "assert",
doc = """ doc = StdlibInlineDocIndex.topFunDoc("assert") ?: """
Assert that `cond` is true, otherwise throw an `AssertionFailedException`. Assert that `cond` is true, otherwise throw an `AssertionFailedException`.
Optionally provide a `message`. Optionally provide a `message`.
""".trimIndent(), """.trimIndent(),
@ -343,17 +462,17 @@ private fun buildStdlibDocs(): List<MiniDecl> {
) )
mod.funDoc( mod.funDoc(
name = "assertEquals", 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")) params = listOf(ParamDoc("a"), ParamDoc("b"))
) )
mod.funDoc( mod.funDoc(
name = "assertNotEquals", 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")) params = listOf(ParamDoc("a"), ParamDoc("b"))
) )
mod.funDoc( mod.funDoc(
name = "assertThrows", name = "assertThrows",
doc = """ doc = StdlibInlineDocIndex.topFunDoc("assertThrows") ?: """
Execute `code` and return the thrown `Exception` object. Execute `code` and return the thrown `Exception` object.
If nothing is thrown, an assertion error is raised. If nothing is thrown, an assertion error is raised.
""".trimIndent(), """.trimIndent(),
@ -364,119 +483,125 @@ private fun buildStdlibDocs(): List<MiniDecl> {
// Utilities // Utilities
mod.funDoc( mod.funDoc(
name = "dynamic", 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")) params = listOf(ParamDoc("value"))
) )
mod.funDoc( mod.funDoc(
name = "require", 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")) params = listOf(ParamDoc("cond", type("lyng.Bool")), ParamDoc("message"))
) )
mod.funDoc( mod.funDoc(
name = "check", 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")) params = listOf(ParamDoc("cond", type("lyng.Bool")), ParamDoc("message"))
) )
mod.funDoc( mod.funDoc(
name = "traceScope", 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"))) params = listOf(ParamDoc("label", type("lyng.String")))
) )
mod.funDoc( mod.funDoc(
name = "delay", 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"))) params = listOf(ParamDoc("ms", type("lyng.Number")))
) )
// Concurrency helpers // Concurrency helpers
mod.funDoc( mod.funDoc(
name = "launch", 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")), params = listOf(ParamDoc("code")),
returns = type("lyng.Deferred") returns = type("lyng.Deferred")
) )
mod.funDoc( mod.funDoc(
name = "yield", 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( mod.funDoc(
name = "flow", 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")), params = listOf(ParamDoc("builder")),
returns = type("lyng.Iterable") returns = type("lyng.Iterable")
) )
// Common Iterable helpers (document top-level extension-like APIs as class members) // Common Iterable helpers (document top-level extension-like APIs as class members)
mod.classDoc(name = "Iterable", doc = "Helper operations for iterable collections.") { mod.classDoc(name = "Iterable", doc = StdlibInlineDocIndex.classDoc("Iterable") ?: "Helper operations for iterable collections.") {
method(name = "filter", doc = "Filter elements by predicate.", params = listOf(ParamDoc("predicate")), returns = type("lyng.Iterable")) fun md(name: String, fallback: String) = StdlibInlineDocIndex.methodDoc("Iterable", name) ?: fallback
method(name = "drop", doc = "Skip the first N elements.", params = listOf(ParamDoc("n", type("lyng.Int"))), returns = type("lyng.Iterable")) method(name = "filter", doc = md("filter", "Filter elements by predicate."), params = listOf(ParamDoc("predicate")), returns = type("lyng.Iterable"))
method(name = "first", doc = "Return the first element or throw if empty.") method(name = "drop", doc = md("drop", "Skip the first N elements."), params = listOf(ParamDoc("n", type("lyng.Int"))), returns = type("lyng.Iterable"))
method(name = "last", doc = "Return the last element or throw if empty.") method(name = "first", doc = md("first", "Return the first 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 = "last", doc = md("last", "Return the last element or throw if empty."))
method(name = "takeLast", doc = "Take the last N elements.", params = listOf(ParamDoc("n", type("lyng.Int"))), returns = type("lyng.List")) method(name = "dropLast", doc = md("dropLast", "Drop the last N elements."), params = listOf(ParamDoc("n", type("lyng.Int"))), returns = type("lyng.Iterable"))
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 = "takeLast", doc = md("takeLast", "Take the last N elements."), params = listOf(ParamDoc("n", type("lyng.Int"))), returns = type("lyng.List"))
method(name = "any", doc = "Return true if any element matches the predicate.", params = listOf(ParamDoc("predicate")), returns = type("lyng.Bool")) 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 = "all", doc = "Return true if all elements match the predicate.", params = listOf(ParamDoc("predicate")), returns = type("lyng.Bool")) method(name = "any", doc = md("any", "Return true if any element matches 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 = "all", doc = md("all", "Return true if all elements match the predicate."), params = listOf(ParamDoc("predicate")), returns = type("lyng.Bool"))
method(name = "sumOf", doc = "Sum mapped values of elements; returns null for empty collections.", params = listOf(ParamDoc("f"))) method(name = "sum", doc = md("sum", "Sum all elements; returns null for empty collections."), returns = type("lyng.Number", nullable = true))
method(name = "minOf", doc = "Minimum of mapped values.", params = listOf(ParamDoc("lambda"))) method(name = "sumOf", doc = md("sumOf", "Sum mapped values of elements; returns null for empty collections."), params = listOf(ParamDoc("f")))
method(name = "maxOf", doc = "Maximum of mapped values.", params = listOf(ParamDoc("lambda"))) method(name = "minOf", doc = md("minOf", "Minimum of mapped values."), params = listOf(ParamDoc("lambda")))
method(name = "sorted", doc = "Return elements sorted by natural order.", returns = type("lyng.Iterable")) method(name = "maxOf", doc = md("maxOf", "Maximum of mapped values."), params = listOf(ParamDoc("lambda")))
method(name = "sortedBy", doc = "Return elements sorted by the key selector.", params = listOf(ParamDoc("predicate")), returns = type("lyng.Iterable")) method(name = "sorted", doc = md("sorted", "Return elements sorted by natural order."), returns = type("lyng.Iterable"))
method(name = "shuffled", doc = "Return a shuffled copy as a list.", returns = type("lyng.List")) method(name = "sortedBy", doc = md("sortedBy", "Return elements sorted by the key selector."), params = listOf(ParamDoc("predicate")), returns = type("lyng.Iterable"))
method(name = "map", doc = "Transform elements by applying `transform`.", params = listOf(ParamDoc("transform")), returns = type("lyng.Iterable")) method(name = "shuffled", doc = md("shuffled", "Return a shuffled copy as a list."), returns = type("lyng.List"))
method(name = "toList", doc = "Collect elements of this iterable into a new 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 // List helpers
mod.classDoc(name = "List", doc = "List-specific operations.", bases = listOf(type("Collection"), type("Iterable"))) { mod.classDoc(name = "List", doc = StdlibInlineDocIndex.classDoc("List") ?: "List-specific operations.", bases = listOf(type("Collection"), type("Iterable"))) {
method(name = "toString", doc = "Return string representation like [a,b,c].", returns = type("lyng.String")) fun md(name: String, fallback: String) = StdlibInlineDocIndex.methodDoc("List", name) ?: fallback
method(name = "sortBy", doc = "Sort list in-place by key selector.", params = listOf(ParamDoc("predicate"))) method(name = "toString", doc = md("toString", "Return string representation like [a,b,c]."), returns = type("lyng.String"))
method(name = "sort", doc = "Sort list in-place by natural order.") method(name = "sortBy", doc = md("sortBy", "Sort list in-place by key selector."), params = listOf(ParamDoc("predicate")))
method(name = "toList", doc = "Return a shallow copy of this list (new list with the same elements).", returns = type("lyng.List")) 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) // Collection helpers (supertype for sized collections)
mod.classDoc(name = "Collection", doc = "Collection operations common to sized collections.", bases = listOf(type("Iterable"))) { mod.classDoc(name = "Collection", doc = StdlibInlineDocIndex.classDoc("Collection") ?: "Collection operations common to sized collections.", bases = listOf(type("Iterable"))) {
method(name = "size", doc = "Number of elements in the collection.", returns = type("lyng.Int")) fun md(name: String, fallback: String) = StdlibInlineDocIndex.methodDoc("Collection", name) ?: fallback
method(name = "toList", doc = "Collect elements into a new list.", returns = type("lyng.List")) 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 // Iterator helpers
mod.classDoc(name = "Iterator", doc = "Iterator protocol for sequential access.") { mod.classDoc(name = "Iterator", doc = StdlibInlineDocIndex.classDoc("Iterator") ?: "Iterator protocol for sequential access.") {
method(name = "hasNext", doc = "Whether another element is available.", returns = type("lyng.Bool")) fun md(name: String, fallback: String) = StdlibInlineDocIndex.methodDoc("Iterator", name) ?: fallback
method(name = "next", doc = "Return the next element.") method(name = "hasNext", doc = md("hasNext", "Whether another element is available."), returns = type("lyng.Bool"))
method(name = "cancelIteration", doc = "Stop the iteration early.") method(name = "next", doc = md("next", "Return the next element."))
method(name = "toList", doc = "Consume this iterator and collect elements into a list.", returns = type("lyng.List")) 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 // Exceptions and utilities
mod.classDoc(name = "Exception", doc = "Exception helpers.") { mod.classDoc(name = "Exception", doc = StdlibInlineDocIndex.classDoc("Exception") ?: "Exception helpers.") {
method(name = "printStackTrace", doc = "Print this exception and its stack trace to standard output.") 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.") { mod.classDoc(name = "String", doc = StdlibInlineDocIndex.classDoc("String") ?: "String helpers.") {
method(name = "re", doc = "Compile this string into a regular expression.", returns = type("lyng.Regex")) // 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 // 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 = "sourceName", doc = "Source (file) name.", type = type("lyng.String"))
field(name = "line", doc = "Line number (1-based).", type = type("lyng.Int")) 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 = "column", doc = "Column number (0-based).", type = type("lyng.Int"))
field(name = "sourceString", doc = "The source line text.", type = type("lyng.String")) 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 // Constants and namespaces
mod.valDoc( mod.valDoc(
name = "π", name = "π",
doc = "The mathematical constant pi.", doc = StdlibInlineDocIndex.topFunDoc("π") ?: "The mathematical constant pi.",
type = type("lyng.Real"), type = type("lyng.Real"),
mutable = false mutable = false
) )
mod.classDoc(name = "Math", doc = "Mathematical constants and helpers.") { 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() decls += mod.build()

View File

@ -17,6 +17,11 @@
package net.sergeych.lyng.obj 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 { val ObjArray by lazy {
/** /**
@ -25,19 +30,34 @@ val ObjArray by lazy {
ObjClass("Array", ObjCollection).apply { ObjClass("Array", ObjCollection).apply {
// we can create iterators using size/getat: // we can create iterators using size/getat:
addFn("iterator") { addFnDoc(
ObjArrayIterator(thisObj).also { it.init(this) } 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() val obj = args.firstAndOnly()
for (i in 0..<thisObj.invokeInstanceMethod(this, "size").toInt()) { 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 ObjFalse
} }
addFn("last") { addFnDoc(
name = "last",
doc = "The last element of this array.",
returns = type("lyng.Any"),
moduleName = "lyng.stdlib"
) {
thisObj.invokeInstanceMethod( thisObj.invokeInstanceMethod(
this, this,
"getAt", "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") { addFnDoc(
ObjRange(0.toObj(), thisObj.invokeInstanceMethod(this, "size"), false) 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() val target = args.firstAndOnly()
var low = 0 var low = 0
var high = thisObj.invokeInstanceMethod(this, "size").toInt() - 1 var high = thisObj.invokeInstanceMethod(this, "size").toInt() - 1
@ -62,13 +96,12 @@ val ObjArray by lazy {
val cmp = midVal.compareTo(this, target) val cmp = midVal.compareTo(this, target)
when { when {
cmp == 0 -> return@addFn (mid).toObj() cmp == 0 -> return@addFnDoc (mid).toObj()
cmp > 0 -> high = mid - 1 cmp > 0 -> high = mid - 1
else -> low = mid + 1 else -> low = mid + 1
} }
} }
// Элемент не найден, возвращаем -(точка вставки) - 1
(-low - 1).toObj() (-low - 1).toObj()
} }
} }

View File

@ -18,6 +18,8 @@
package net.sergeych.lyng.obj package net.sergeych.lyng.obj
import net.sergeych.lyng.Scope import net.sergeych.lyng.Scope
import net.sergeych.lyng.miniast.addFnDoc
import net.sergeych.lyng.miniast.type
class ObjChar(val value: Char): Obj() { class ObjChar(val value: Char): Obj() {
@ -45,7 +47,12 @@ class ObjChar(val value: Char): Obj() {
companion object { companion object {
val type = ObjClass("Char").apply { 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 package net.sergeych.lyng.obj
import net.sergeych.lyng.* 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.LynonDecoder
import net.sergeych.lynon.LynonType import net.sergeych.lynon.LynonType
// Simple id generator for class identities (not thread-safe; fine for scripts) // Simple id generator for class identities (not thread-safe; fine for scripts)
private object ClassIdGen { var c: Long = 1L; fun nextId(): Long = c++ } 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( open class ObjClass(
val className: String, val className: String,

View File

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

View File

@ -19,6 +19,8 @@ package net.sergeych.lyng.obj
import kotlinx.coroutines.Deferred import kotlinx.coroutines.Deferred
import net.sergeych.lyng.Scope import net.sergeych.lyng.Scope
import net.sergeych.lyng.miniast.addFnDoc
import net.sergeych.lyng.miniast.type
open class ObjDeferred(val deferred: Deferred<Obj>): Obj() { 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") scope.raiseError("Deferred constructor is not directly callable")
} }
}.apply { }.apply {
addFn("await") { addFnDoc(
thisAs<ObjDeferred>().deferred.await() name = "await",
} doc = "Suspend until completion and return the result value (or throw if failed).",
addFn("isCompleted") { returns = type("lyng.Any"),
thisAs<ObjDeferred>().deferred.isCompleted.toObj() moduleName = "lyng.stdlib"
} ) { thisAs<ObjDeferred>().deferred.await() }
addFn("isActive") { 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 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() (d.isActive || (!d.isCompleted && !d.isCancelled)).toObj()
} }
addFn("isCancelled") { addFnDoc(
thisAs<ObjDeferred>().deferred.isCancelled.toObj() 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 package net.sergeych.lyng.obj
import net.sergeych.lyng.Scope 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
import kotlin.time.Duration.Companion.days import kotlin.time.Duration.Companion.days
import kotlin.time.Duration.Companion.hours import kotlin.time.Duration.Companion.hours
@ -72,91 +74,169 @@ class ObjDuration(val duration: Duration) : Obj() {
) )
} }
}.apply { }.apply {
addFn("days") { addFnDoc(
thisAs<ObjDuration>().duration.toDouble(DurationUnit.DAYS).toObj() name = "days",
} doc = "Return this duration as a real number of days.",
addFn("hours") { returns = type("lyng.Real"),
thisAs<ObjDuration>().duration.toDouble(DurationUnit.HOURS).toObj() moduleName = "lyng.time"
} ) { thisAs<ObjDuration>().duration.toDouble(DurationUnit.DAYS).toObj() }
addFn("minutes") { addFnDoc(
thisAs<ObjDuration>().duration.toDouble(DurationUnit.MINUTES).toObj() name = "hours",
} doc = "Return this duration as a real number of hours.",
addFn("seconds") { returns = type("lyng.Real"),
thisAs<ObjDuration>().duration.toDouble(DurationUnit.SECONDS).toObj() moduleName = "lyng.time"
} ) { thisAs<ObjDuration>().duration.toDouble(DurationUnit.HOURS).toObj() }
addFn("milliseconds") { addFnDoc(
thisAs<ObjDuration>().duration.toDouble(DurationUnit.MILLISECONDS).toObj() name = "minutes",
} doc = "Return this duration as a real number of minutes.",
addFn("microseconds") { returns = type("lyng.Real"),
thisAs<ObjDuration>().duration.toDouble(DurationUnit.MICROSECONDS).toObj() 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 // extensions
ObjInt.type.addFn("seconds") { ObjInt.type.addFnDoc(
ObjDuration(thisAs<ObjInt>().value.seconds) 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") { ObjInt.type.addFnDoc(
ObjDuration(thisAs<ObjInt>().value.seconds) name = "second",
} doc = "Construct a `Duration` equal to this integer number of seconds.",
ObjInt.type.addFn("milliseconds") { returns = type("lyng.Duration"),
ObjDuration(thisAs<ObjInt>().value.milliseconds) 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") { ObjInt.type.addFnDoc(
ObjDuration(thisAs<ObjInt>().value.milliseconds) name = "millisecond",
} doc = "Construct a `Duration` equal to this integer number of milliseconds.",
ObjReal.type.addFn("seconds") { returns = type("lyng.Duration"),
ObjDuration(thisAs<ObjReal>().value.seconds) 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") { ObjReal.type.addFnDoc(
ObjDuration(thisAs<ObjReal>().value.seconds) 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") { ObjReal.type.addFnDoc(
ObjDuration(thisAs<ObjReal>().value.milliseconds) name = "milliseconds",
} doc = "Construct a `Duration` equal to this real number of milliseconds.",
ObjReal.type.addFn("millisecond") { returns = type("lyng.Duration"),
ObjDuration(thisAs<ObjReal>().value.milliseconds) 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") { ObjInt.type.addFnDoc(
ObjDuration(thisAs<ObjInt>().value.minutes) name = "minutes",
} doc = "Construct a `Duration` equal to this integer number of minutes.",
ObjReal.type.addFn("minutes") { returns = type("lyng.Duration"),
ObjDuration(thisAs<ObjReal>().value.minutes) moduleName = "lyng.time"
} ) { ObjDuration(thisAs<ObjInt>().value.minutes) }
ObjInt.type.addFn("minute") { ObjReal.type.addFnDoc(
ObjDuration(thisAs<ObjInt>().value.minutes) name = "minutes",
} doc = "Construct a `Duration` equal to this real number of minutes.",
ObjReal.type.addFn("minute") { returns = type("lyng.Duration"),
ObjDuration(thisAs<ObjReal>().value.minutes) moduleName = "lyng.time"
} ) { ObjDuration(thisAs<ObjReal>().value.minutes) }
ObjInt.type.addFn("hours") { ObjInt.type.addFnDoc(
ObjDuration(thisAs<ObjInt>().value.hours) name = "minute",
} doc = "Construct a `Duration` equal to this integer number of minutes.",
ObjReal.type.addFn("hours") { returns = type("lyng.Duration"),
ObjDuration(thisAs<ObjReal>().value.hours) moduleName = "lyng.time"
} ) { ObjDuration(thisAs<ObjInt>().value.minutes) }
ObjInt.type.addFn("hour") { ObjReal.type.addFnDoc(
ObjDuration(thisAs<ObjInt>().value.hours) name = "minute",
} doc = "Construct a `Duration` equal to this real number of minutes.",
ObjReal.type.addFn("hour") { returns = type("lyng.Duration"),
ObjDuration(thisAs<ObjReal>().value.hours) moduleName = "lyng.time"
} ) { ObjDuration(thisAs<ObjReal>().value.minutes) }
ObjInt.type.addFn("days") { ObjInt.type.addFnDoc(
ObjDuration(thisAs<ObjInt>().value.days) name = "hours",
} doc = "Construct a `Duration` equal to this integer number of hours.",
ObjReal.type.addFn("days") { returns = type("lyng.Duration"),
ObjDuration(thisAs<ObjReal>().value.days) moduleName = "lyng.time"
} ) { ObjDuration(thisAs<ObjInt>().value.hours) }
ObjInt.type.addFn("day") { ObjReal.type.addFnDoc(
ObjDuration(thisAs<ObjInt>().value.days) name = "hours",
} doc = "Construct a `Duration` equal to this real number of hours.",
ObjReal.type.addFn("day") { returns = type("lyng.Duration"),
ObjDuration(thisAs<ObjReal>().value.days) 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") { // addFn("epochSeconds") {

View File

@ -19,6 +19,10 @@ package net.sergeych.lyng.obj
import net.sergeych.bintools.encodeToHex import net.sergeych.bintools.encodeToHex
import net.sergeych.lyng.* 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.LynonDecoder
import net.sergeych.lynon.LynonEncoder import net.sergeych.lynon.LynonEncoder
import net.sergeych.lynon.LynonType import net.sergeych.lynon.LynonType
@ -133,10 +137,21 @@ open class ObjException(
} }
val Root = ExceptionClass("Exception").apply { val Root = ExceptionClass("Exception").apply {
addConst("message", statement { addConstDoc(
name = "message",
value = statement {
(thisObj as ObjException).message.toObj() (thisObj as ObjException).message.toObj()
}) },
addFn("stackTrace") { 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() (thisObj as ObjException).getStackTrace()
} }
} }

View File

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

View File

@ -19,6 +19,9 @@ package net.sergeych.lyng.obj
import net.sergeych.lyng.Arguments import net.sergeych.lyng.Arguments
import net.sergeych.lyng.Statement 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. * Abstract class that must provide `iterator` method that returns [ObjIterator] instance.
@ -26,7 +29,12 @@ import net.sergeych.lyng.Statement
val ObjIterable by lazy { val ObjIterable by lazy {
ObjClass("Iterable").apply { 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 result = mutableListOf<Obj>()
val iterator = thisObj.invokeInstanceMethod(this, "iterator") val iterator = thisObj.invokeInstanceMethod(this, "iterator")
@ -36,29 +44,48 @@ val ObjIterable by lazy {
} }
// it is not effective, but it is open: // 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 obj = args.firstAndOnly()
val it = thisObj.invokeInstanceMethod(this, "iterator") val it = thisObj.invokeInstanceMethod(this, "iterator")
while (it.invokeInstanceMethod(this, "hasNext").toBool()) { while (it.invokeInstanceMethod(this, "hasNext").toBool()) {
if (obj.compareTo(this, it.invokeInstanceMethod(this, "next")) == 0) if (obj.compareTo(this, it.invokeInstanceMethod(this, "next")) == 0)
return@addFn ObjTrue return@addFnDoc ObjTrue
} }
ObjFalse 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() val obj = args.firstAndOnly()
var index = 0 var index = 0
val it = thisObj.invokeInstanceMethod(this, "iterator") val it = thisObj.invokeInstanceMethod(this, "iterator")
while (it.invokeInstanceMethod(this, "hasNext").toBool()) { while (it.invokeInstanceMethod(this, "hasNext").toBool()) {
if (obj.compareTo(this, it.invokeInstanceMethod(this, "next")) == 0) if (obj.compareTo(this, it.invokeInstanceMethod(this, "next")) == 0)
return@addFn ObjInt(index.toLong()) return@addFnDoc ObjInt(index.toLong())
index++ index++
} }
ObjInt(-1L) 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) ) if( thisObj.isInstanceOf(ObjSet.type) )
thisObj thisObj
else { 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() val result = ObjMap()
thisObj.toFlow(this).collect { pair -> thisObj.toFlow(this).collect { pair ->
result.map[pair.getAt(this, 0)] = pair.getAt(this, 1) result.map[pair.getAt(this, 0)] = pair.getAt(this, 1)
@ -79,7 +111,13 @@ val ObjIterable by lazy {
result 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 association = requireOnlyArg<Statement>()
val result = ObjMap() val result = ObjMap()
thisObj.toFlow(this).collect { thisObj.toFlow(this).collect {
@ -88,7 +126,13 @@ val ObjIterable by lazy {
result 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 it = thisObj.invokeInstanceMethod(this, "iterator")
val fn = requiredArg<Statement>(0) val fn = requiredArg<Statement>(0)
while (it.invokeInstanceMethod(this, "hasNext").toBool()) { while (it.invokeInstanceMethod(this, "hasNext").toBool()) {
@ -98,7 +142,14 @@ val ObjIterable by lazy {
ObjVoid 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 fn = requiredArg<Statement>(0)
val result = mutableListOf<Obj>() val result = mutableListOf<Obj>()
thisObj.toFlow(this).collect { thisObj.toFlow(this).collect {
@ -107,7 +158,13 @@ val ObjIterable by lazy {
ObjList(result) 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() var n = requireOnlyArg<ObjInt>().value.toInt()
val result = mutableListOf<Obj>() val result = mutableListOf<Obj>()
if (n > 0) { if (n > 0) {
@ -119,7 +176,12 @@ val ObjIterable by lazy {
ObjList(result) ObjList(result)
} }
addFn("isEmpty") { addFnDoc(
name = "isEmpty",
doc = "Whether the iterable has no elements.",
returns = type("lyng.Bool"),
moduleName = "lyng.stdlib"
) {
ObjBool( ObjBool(
thisObj.invokeInstanceMethod(this, "iterator") thisObj.invokeInstanceMethod(this, "iterator")
.invokeInstanceMethod(this, "hasNext").toBool() .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 list = thisObj.callMethod<ObjList>(this, "toList")
val comparator = requireOnlyArg<Statement>() val comparator = requireOnlyArg<Statement>()
list.quicksort { a, b -> list.quicksort { a, b ->
@ -136,7 +204,12 @@ val ObjIterable by lazy {
list 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") val list = thisObj.callMethod<ObjList>(this, "toList")
list.list.reverse() list.list.reverse()
list list

View File

@ -17,6 +17,10 @@
package net.sergeych.lyng.obj 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: * Iterator should provide lyng-level iterator functions:
* *
@ -28,15 +32,50 @@ package net.sergeych.lyng.obj
*/ */
val ObjIterator by lazy { val ObjIterator by lazy {
ObjClass("Iterator").apply { 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 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") 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") 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 kotlinx.serialization.json.JsonElement
import net.sergeych.lyng.Scope import net.sergeych.lyng.Scope
import net.sergeych.lyng.Statement 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.lyng.statement
import net.sergeych.lynon.LynonDecoder import net.sergeych.lynon.LynonDecoder
import net.sergeych.lynon.LynonEncoder import net.sergeych.lynon.LynonEncoder
@ -200,19 +204,32 @@ class ObjList(val list: MutableList<Obj> = mutableListOf()) : Obj() {
return ObjList(decoder.decodeAnyList(scope)) return ObjList(decoder.decodeAnyList(scope))
} }
}.apply { }.apply {
createField("size", addConstDoc(
statement { name = "size",
value = statement {
(thisObj as ObjList).list.size.toObj() (thisObj as ObjList).list.size.toObj()
} },
doc = "Number of elements in this list.",
type = type("lyng.Int"),
moduleName = "lyng.stdlib"
) )
createField("add", addConstDoc(
statement { name = "add",
value = statement {
val l = thisAs<ObjList>().list val l = thisAs<ObjList>().list
for (a in args) l.add(a) for (a in args) l.add(a)
ObjVoid 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") if (args.size < 2) raiseError("addAt takes 2+ arguments")
val l = thisAs<ObjList>() val l = thisAs<ObjList>()
var index = requiredArg<ObjInt>(0).value.toInt() var index = requiredArg<ObjInt>(0).value.toInt()
@ -220,7 +237,12 @@ class ObjList(val list: MutableList<Obj> = mutableListOf()) : Obj() {
ObjVoid 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 self = thisAs<ObjList>()
val start = requiredArg<ObjInt>(0).value.toInt() val start = requiredArg<ObjInt>(0).value.toInt()
if (args.size == 2) { if (args.size == 2) {
@ -231,7 +253,12 @@ class ObjList(val list: MutableList<Obj> = mutableListOf()) : Obj() {
self 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>() val self = thisAs<ObjList>()
if (args.isNotEmpty()) { if (args.isNotEmpty()) {
val count = requireOnlyArg<ObjInt>().value.toInt() val count = requireOnlyArg<ObjInt>().value.toInt()
@ -242,7 +269,12 @@ class ObjList(val list: MutableList<Obj> = mutableListOf()) : Obj() {
self 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 self = thisAs<ObjList>()
val list = self.list val list = self.list
val range = requiredArg<Obj>(0) val range = requiredArg<Obj>(0)
@ -283,19 +315,32 @@ class ObjList(val list: MutableList<Obj> = mutableListOf()) : Obj() {
self 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>() val comparator = requireOnlyArg<Statement>()
thisAs<ObjList>().quicksort { a, b -> comparator.call(this, a, b).toInt() } thisAs<ObjList>().quicksort { a, b -> comparator.call(this, a, b).toInt() }
ObjVoid ObjVoid
} }
addFn("shuffle") { addFnDoc(
name = "shuffle",
doc = "Shuffle elements of this list in-place.",
moduleName = "lyng.stdlib"
) {
thisAs<ObjList>().list.shuffle() thisAs<ObjList>().list.shuffle()
ObjVoid 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 self = thisAs<ObjList>()
val l = self.list val l = self.list
if (l.isEmpty()) return@addFn ObjNull if (l.isEmpty()) return@addFnDoc ObjNull
if (net.sergeych.lyng.PerfFlags.PRIMITIVE_FASTOPS) { if (net.sergeych.lyng.PerfFlags.PRIMITIVE_FASTOPS) {
// Fast path: all ints → accumulate as long // Fast path: all ints → accumulate as long
var i = 0 var i = 0
@ -312,10 +357,10 @@ class ObjList(val list: MutableList<Obj> = mutableListOf()) : Obj() {
res = res.plus(this, l[i]) res = res.plus(this, l[i])
i++ i++
} }
return@addFn res return@addFnDoc res
} }
} }
return@addFn ObjInt(acc) return@addFnDoc ObjInt(acc)
} }
// Generic path: dynamic '+' starting from first element // Generic path: dynamic '+' starting from first element
var res: Obj = l[0] var res: Obj = l[0]
@ -326,9 +371,13 @@ class ObjList(val list: MutableList<Obj> = mutableListOf()) : Obj() {
} }
res 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 val l = thisAs<ObjList>().list
if (l.isEmpty()) return@addFn ObjNull if (l.isEmpty()) return@addFnDoc ObjNull
if (net.sergeych.lyng.PerfFlags.PRIMITIVE_FASTOPS) { if (net.sergeych.lyng.PerfFlags.PRIMITIVE_FASTOPS) {
var i = 0 var i = 0
var hasOnlyInts = true var hasOnlyInts = true
@ -343,7 +392,7 @@ class ObjList(val list: MutableList<Obj> = mutableListOf()) : Obj() {
} }
i++ i++
} }
if (hasOnlyInts) return@addFn ObjInt(minVal) if (hasOnlyInts) return@addFnDoc ObjInt(minVal)
} }
var res: Obj = l[0] var res: Obj = l[0]
var i = 1 var i = 1
@ -354,9 +403,13 @@ class ObjList(val list: MutableList<Obj> = mutableListOf()) : Obj() {
} }
res 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 val l = thisAs<ObjList>().list
if (l.isEmpty()) return@addFn ObjNull if (l.isEmpty()) return@addFnDoc ObjNull
if (net.sergeych.lyng.PerfFlags.PRIMITIVE_FASTOPS) { if (net.sergeych.lyng.PerfFlags.PRIMITIVE_FASTOPS) {
var i = 0 var i = 0
var hasOnlyInts = true var hasOnlyInts = true
@ -371,7 +424,7 @@ class ObjList(val list: MutableList<Obj> = mutableListOf()) : Obj() {
} }
i++ i++
} }
if (hasOnlyInts) return@addFn ObjInt(maxVal) if (hasOnlyInts) return@addFnDoc ObjInt(maxVal)
} }
var res: Obj = l[0] var res: Obj = l[0]
var i = 1 var i = 1
@ -382,21 +435,27 @@ class ObjList(val list: MutableList<Obj> = mutableListOf()) : Obj() {
} }
res 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 l = thisAs<ObjList>().list
val needle = args.firstAndOnly() val needle = args.firstAndOnly()
if (net.sergeych.lyng.PerfFlags.PRIMITIVE_FASTOPS && needle is ObjInt) { if (net.sergeych.lyng.PerfFlags.PRIMITIVE_FASTOPS && needle is ObjInt) {
var i = 0 var i = 0
while (i < l.size) { while (i < l.size) {
val v = l[i] 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++ i++
} }
return@addFn ObjInt((-1).toLong()) return@addFnDoc ObjInt((-1).toLong())
} }
var i = 0 var i = 0
while (i < l.size) { 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++ i++
} }
ObjInt((-1).toLong()) ObjInt((-1).toLong())

View File

@ -21,6 +21,10 @@ import kotlinx.serialization.json.JsonElement
import kotlinx.serialization.json.JsonObject import kotlinx.serialization.json.JsonObject
import net.sergeych.lyng.Scope import net.sergeych.lyng.Scope
import net.sergeych.lyng.Statement 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.LynonDecoder
import net.sergeych.lynon.LynonEncoder import net.sergeych.lynon.LynonEncoder
import net.sergeych.lynon.LynonType import net.sergeych.lynon.LynonType
@ -72,9 +76,24 @@ class ObjMapEntry(val key: Obj, val value: Obj) : Obj() {
) )
} }
}.apply { }.apply {
addFn("key") { thisAs<ObjMapEntry>().key } addFnDoc(
addFn("value") { thisAs<ObjMapEntry>().value } name = "key",
addFn("size") { 2.toObj() } 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()) return ObjMap(keys.zip(values).toMap().toMutableMap())
} }
}.apply { }.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) val key = args.firstAndOnly(pos)
thisAs<ObjMap>().map.getOrElse(key) { ObjNull } 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) val key = requiredArg<Obj>(0)
thisAs<ObjMap>().map.getOrPut(key) { thisAs<ObjMap>().map.getOrPut(key) {
val lambda = requiredArg<Statement>(1) val lambda = requiredArg<Statement>(1)
lambda.execute(this) 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() 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 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() thisAs<ObjMap>().map.clear()
thisObj 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() 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()) 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()) ObjKotlinIterator(thisAs<ObjMap>().map.entries.iterator())
} }
} }

View File

@ -21,6 +21,9 @@ import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock import kotlinx.coroutines.sync.withLock
import net.sergeych.lyng.Scope import net.sergeych.lyng.Scope
import net.sergeych.lyng.Statement 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() { class ObjMutex(val mutex: Mutex): Obj() {
override val objClass = type override val objClass = type
@ -31,7 +34,13 @@ class ObjMutex(val mutex: Mutex): Obj() {
return ObjMutex(Mutex()) return ObjMutex(Mutex())
} }
}.apply { }.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) val f = requiredArg<Statement>(0)
// Execute user lambda directly in the current scope to preserve the active scope // Execute user lambda directly in the current scope to preserve the active scope
// ancestry across suspension points. The lambda still constructs a ClosureScope // ancestry across suspension points. The lambda still constructs a ClosureScope

View File

@ -18,6 +18,9 @@
package net.sergeych.lyng.obj package net.sergeych.lyng.obj
import net.sergeych.lyng.Scope 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() { 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 { companion object {
val type = ObjClass("Range", ObjIterable).apply { 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 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 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() 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() 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() 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() 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>() val self = thisAs<ObjRange>()
if (net.sergeych.lyng.PerfFlags.RANGE_FAST_ITER) { if (net.sergeych.lyng.PerfFlags.RANGE_FAST_ITER) {
val s = self.start 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()) val endExclusive = (if (self.isEndInclusive) e.value.toInt() + 1 else e.value.toInt())
// Only for ascending simple ranges; fall back otherwise // Only for ascending simple ranges; fall back otherwise
if (start <= endExclusive) { 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 kotlinx.serialization.json.JsonPrimitive
import net.sergeych.lyng.Pos import net.sergeych.lyng.Pos
import net.sergeych.lyng.Scope import net.sergeych.lyng.Scope
import net.sergeych.lyng.miniast.addConstDoc
import net.sergeych.lyng.miniast.addFnDoc
import net.sergeych.lyng.miniast.type
import net.sergeych.lyng.statement import net.sergeych.lyng.statement
import net.sergeych.lynon.LynonDecoder import net.sergeych.lynon.LynonDecoder
import net.sergeych.lynon.LynonEncoder 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 = override suspend fun deserialize(scope: Scope, decoder: LynonDecoder, lynonType: LynonType?): Obj =
ObjReal(decoder.unpackDouble()) ObjReal(decoder.unpackDouble())
}.apply { }.apply {
createField( // roundToInt: number rounded to the nearest integer
"roundToInt", addConstDoc(
statement(Pos.builtIn) { name = "roundToInt",
value = statement(Pos.builtIn) {
(it.thisObj as ObjReal).value.roundToLong().toObj() (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()) 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.PerfFlags
import net.sergeych.lyng.RegexCache import net.sergeych.lyng.RegexCache
import net.sergeych.lyng.Scope import net.sergeych.lyng.Scope
import net.sergeych.lyng.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() { class ObjRegex(val regex: Regex) : Obj() {
override val objClass = type override val objClass = type
@ -43,13 +47,31 @@ class ObjRegex(val regex: Regex) : Obj() {
return ObjRegex(re) return ObjRegex(re)
} }
}.apply { }.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)) 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>()) 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 val s = requireOnlyArg<ObjString>().value
ObjList(thisAs<ObjRegex>().regex.findAll(s).map { ObjRegexMatch(it) }.toMutableList()) 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") scope.raiseError("RegexMatch can't be constructed directly")
} }
}.apply { }.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 thisAs<ObjRegexMatch>().objGroups
} }
addFn("value") { addFnDoc(
name = "value",
doc = "The matched substring.",
returns = type("lyng.String"),
moduleName = "lyng.stdlib"
) {
thisAs<ObjRegexMatch>().objValue 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 thisAs<ObjRegexMatch>().objRange
} }
} }

View File

@ -18,6 +18,10 @@
package net.sergeych.lyng.obj package net.sergeych.lyng.obj
import net.sergeych.lyng.Scope 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> { class RingBuffer<T>(val maxSize: Int) : Iterable<T> {
private val data = arrayOfNulls<Any>(maxSize) private val data = arrayOfNulls<Any>(maxSize)
@ -90,19 +94,34 @@ class ObjRingBuffer(val capacity: Int) : Obj() {
return ObjRingBuffer(scope.requireOnlyArg<ObjInt>().toInt()) return ObjRingBuffer(scope.requireOnlyArg<ObjInt>().toInt())
} }
}.apply { }.apply {
addFn("capacity") { addFnDoc(
thisAs<ObjRingBuffer>().capacity.toObj() name = "capacity",
} doc = "Maximum number of elements the buffer can hold.",
addFn("size") { returns = type("lyng.Int"),
thisAs<ObjRingBuffer>().buffer.size.toObj() moduleName = "lyng.stdlib"
} ) { thisAs<ObjRingBuffer>().capacity.toObj() }
addFn("iterator") { 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 val buffer = thisAs<ObjRingBuffer>().buffer
ObjKotlinObjIterator(buffer.iterator()) ObjKotlinObjIterator(buffer.iterator())
} }
addFn("add") { addFnDoc(
thisAs<ObjRingBuffer>().apply { buffer.add(requireOnlyArg<Obj>()) } 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 package net.sergeych.lyng.obj
import net.sergeych.lyng.Scope 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.LynonDecoder
import net.sergeych.lynon.LynonEncoder import net.sergeych.lynon.LynonEncoder
import net.sergeych.lynon.LynonType 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 = override suspend fun deserialize(scope: Scope, decoder: LynonDecoder, lynonType: LynonType?): Obj =
ObjSet(decoder.decodeAnyList(scope).toMutableSet()) ObjSet(decoder.decodeAnyList(scope).toMutableSet())
}.apply { }.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() 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()) 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() 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()) 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()) 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 set = thisAs<ObjSet>().set
val n = set.size val n = set.size
for( x in args.list ) set -= x 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.PerfFlags
import net.sergeych.lyng.RegexCache import net.sergeych.lyng.RegexCache
import net.sergeych.lyng.Scope import net.sergeych.lyng.Scope
import net.sergeych.lyng.miniast.*
import net.sergeych.lyng.statement import net.sergeych.lyng.statement
import net.sergeych.lynon.LynonDecoder import net.sergeych.lynon.LynonDecoder
import net.sergeych.lynon.LynonEncoder 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 = override suspend fun deserialize(scope: Scope, decoder: LynonDecoder, lynonType: LynonType?): Obj =
ObjString(decoder.unpackBinaryData().decodeToString()) ObjString(decoder.unpackBinaryData().decodeToString())
}.apply { }.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( ObjInt(
thisAs<ObjString>().value.toLongOrNull() thisAs<ObjString>().value.toLongOrNull()
?: raiseIllegalArgument("can't convert to int: $thisObj") ?: 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)) 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)) ObjBool(thisAs<ObjString>().value.endsWith(requiredArg<ObjString>(0).value))
} }
addConst("length", addConstDoc(
statement { ObjInt(thisAs<ObjString>().value.length.toLong()) } 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( thisAs<ObjString>().value.takeLast(
requiredArg<ObjInt>(0).toInt() requiredArg<ObjInt>(0).toInt()
).let(::ObjString) ).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( thisAs<ObjString>().value.take(
requiredArg<ObjInt>(0).toInt() requiredArg<ObjInt>(0).toInt()
).let(::ObjString) ).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( thisAs<ObjString>().value.drop(
requiredArg<ObjInt>(0).toInt() requiredArg<ObjInt>(0).toInt()
).let(::ObjString) ).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( thisAs<ObjString>().value.dropLast(
requiredArg<ObjInt>(0).toInt() requiredArg<ObjInt>(0).toInt()
).let(::ObjString) ).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) 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) 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( ObjList(
thisAs<ObjString>().value.map { ObjChar(it) }.toMutableList() 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")) ObjChar(thisAs<ObjString>().value.lastOrNull() ?: raiseNoSuchElement("empty string"))
} }
addFn("encodeUtf8") { ObjBuffer(thisAs<ObjString>().value.encodeToByteArray().asUByteArray()) } addFnDoc(
addFn("size") { ObjInt(thisAs<ObjString>().value.length.toLong()) } name = "encodeUtf8",
addFn("toReal") { 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()) 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) 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 s = requireOnlyArg<Obj>()
val self = thisAs<ObjString>().value val self = thisAs<ObjString>().value
ObjBool( ObjBool(

View File

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