Add MiniAst documentation system and Markdown rendering for built-in and IDE documentation.
This commit is contained in:
1 parent
c52e132dcc
commit
067970b80c
12 files changed
+1411
-53
No files matched your search
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
* Copyright 2025 Sergey S. Chernov real.sergeych@gmail.com
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
package net.sergeych.lyng
|
||||
|
||||
/**
|
||||
* Lightweight documentation payload for symbols defined from Kotlin code (built-ins, stdlib, host bindings).
|
||||
*
|
||||
* The [summary] is optional; if not provided, it will be derived as the first non-empty line of [raw].
|
||||
* Simple tag lines like "@since 1.0" can be stored in [tags] when needed.
|
||||
*/
|
||||
data class DocString(
|
||||
val raw: String,
|
||||
val summary: String? = null,
|
||||
val tags: Map<String, List<String>> = emptyMap()
|
||||
) {
|
||||
val effectiveSummary: String? by lazy {
|
||||
summary ?: raw.lineSequence().map { it.trim() }.firstOrNull { it.isNotEmpty() }
|
||||
}
|
||||
|
||||
companion object {
|
||||
/** Convenience to create a [DocString] taking the first non-empty line as a summary. */
|
||||
fun of(text: String, tags: Map<String, List<String>> = emptyMap()): DocString = DocString(text, null, tags)
|
||||
}
|
||||
}
|
||||
@@ -340,8 +340,11 @@ open class Scope(
|
||||
}
|
||||
}
|
||||
|
||||
// --- removed doc-aware overloads to keep runtime lean ---
|
||||
|
||||
fun addConst(name: String, value: Obj) = addItem(name, false, value)
|
||||
|
||||
|
||||
suspend fun eval(code: String): Obj =
|
||||
eval(code.toSource())
|
||||
|
||||
|
||||
@@ -0,0 +1,486 @@
|
||||
/*
|
||||
* Copyright 2025 Sergey S. Chernov real.sergeych@gmail.com
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
/*
|
||||
* Built-in documentation registry for Kotlin-defined APIs.
|
||||
* Stores MiniAst declarations with MiniDoc, no runtime coupling.
|
||||
*/
|
||||
package net.sergeych.lyng.miniast
|
||||
|
||||
import net.sergeych.lyng.Pos
|
||||
|
||||
// ---------------- Types DSL ----------------
|
||||
|
||||
/** Simple param descriptor for docs builder. */
|
||||
data class ParamDoc(val name: String, val type: TypeDoc? = null)
|
||||
|
||||
/** Type documentation model mapped later to MiniTypeRef. */
|
||||
sealed interface TypeDoc { val nullable: Boolean }
|
||||
|
||||
data class TypeNameDoc(val segments: List<String>, override val nullable: Boolean = false) : TypeDoc
|
||||
data class TypeGenericDoc(val base: TypeNameDoc, val args: List<TypeDoc>, override val nullable: Boolean = false) : TypeDoc
|
||||
data class TypeFunctionDoc(
|
||||
val receiver: TypeDoc? = null,
|
||||
val params: List<TypeDoc>,
|
||||
val returns: TypeDoc,
|
||||
override val nullable: Boolean = false
|
||||
) : TypeDoc
|
||||
data class TypeVarDoc(val name: String, override val nullable: Boolean = false) : TypeDoc
|
||||
|
||||
// Convenience builders
|
||||
fun type(name: String, nullable: Boolean = false) = TypeNameDoc(name.split('.'), nullable)
|
||||
fun typeVar(name: String, nullable: Boolean = false) = TypeVarDoc(name, nullable)
|
||||
fun funType(params: List<TypeDoc>, returns: TypeDoc, receiver: TypeDoc? = null, nullable: Boolean = false) =
|
||||
TypeFunctionDoc(receiver, params, returns, nullable)
|
||||
|
||||
// ---------------- Registry ----------------
|
||||
|
||||
interface BuiltinDocSource {
|
||||
fun docsForModule(moduleName: String): List<MiniDecl>
|
||||
}
|
||||
|
||||
object BuiltinDocRegistry : BuiltinDocSource {
|
||||
// Simple storage; populated at init time; reads dominate afterwards.
|
||||
private val modules: MutableMap<String, MutableList<MiniDecl>> = mutableMapOf()
|
||||
// Optional lazy suppliers to avoid hard init order coupling (e.g., stdlib docs)
|
||||
private val lazySuppliers: MutableMap<String, () -> List<MiniDecl>> = mutableMapOf()
|
||||
|
||||
fun module(name: String, init: ModuleDocsBuilder.() -> Unit) {
|
||||
val builder = ModuleDocsBuilder(name)
|
||||
builder.init()
|
||||
val list = modules.getOrPut(name) { mutableListOf() }
|
||||
list += builder.build()
|
||||
}
|
||||
|
||||
override fun docsForModule(moduleName: String): List<MiniDecl> {
|
||||
modules[moduleName]?.let { return it }
|
||||
// Try lazy supplier once
|
||||
val built = lazySuppliers.remove(moduleName)?.invoke()
|
||||
if (built != null) {
|
||||
val list = modules.getOrPut(moduleName) { mutableListOf() }
|
||||
list += built
|
||||
return list
|
||||
}
|
||||
return emptyList()
|
||||
}
|
||||
|
||||
fun clearModule(moduleName: String) { modules.remove(moduleName) }
|
||||
fun allModules(): Set<String> = (modules.keys + lazySuppliers.keys).toSet()
|
||||
|
||||
/** Atomically replace a module's docs with freshly built ones. */
|
||||
fun moduleReplace(name: String, init: ModuleDocsBuilder.() -> Unit) {
|
||||
modules.remove(name)
|
||||
module(name, init)
|
||||
}
|
||||
|
||||
/** Register a lazy supplier that will be invoked on the first lookup for [name]. */
|
||||
fun registerLazy(name: String, supplier: () -> List<MiniDecl>) {
|
||||
// do not overwrite if module already present
|
||||
if (!modules.containsKey(name)) lazySuppliers[name] = supplier
|
||||
}
|
||||
// Register built-in lazy seeds
|
||||
init {
|
||||
registerLazy("lyng.stdlib") { buildStdlibDocs() }
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------- Builders ----------------
|
||||
|
||||
class ModuleDocsBuilder internal constructor(private val moduleName: String) {
|
||||
private val decls = mutableListOf<MiniDecl>()
|
||||
|
||||
fun funDoc(
|
||||
name: String,
|
||||
doc: String,
|
||||
params: List<ParamDoc> = emptyList(),
|
||||
returns: TypeDoc? = null,
|
||||
tags: Map<String, List<String>> = emptyMap(),
|
||||
) {
|
||||
val md = miniDoc(doc, tags)
|
||||
val mp = params.map { MiniParam(it.name, it.type?.toMiniTypeRef(), Pos.builtIn) }
|
||||
val ret = returns?.toMiniTypeRef()
|
||||
decls += MiniFunDecl(
|
||||
range = builtinRange(),
|
||||
name = name,
|
||||
params = mp,
|
||||
returnType = ret,
|
||||
body = null,
|
||||
doc = md,
|
||||
nameStart = Pos.builtIn,
|
||||
)
|
||||
}
|
||||
|
||||
fun valDoc(
|
||||
name: String,
|
||||
doc: String,
|
||||
type: TypeDoc? = null,
|
||||
mutable: Boolean = false,
|
||||
tags: Map<String, List<String>> = emptyMap(),
|
||||
) {
|
||||
val md = miniDoc(doc, tags)
|
||||
decls += MiniValDecl(
|
||||
range = builtinRange(),
|
||||
name = name,
|
||||
mutable = mutable,
|
||||
type = type?.toMiniTypeRef(),
|
||||
initRange = null,
|
||||
doc = md,
|
||||
nameStart = Pos.builtIn,
|
||||
)
|
||||
}
|
||||
|
||||
fun classDoc(
|
||||
name: String,
|
||||
doc: String,
|
||||
bases: List<TypeDoc> = emptyList(),
|
||||
tags: Map<String, List<String>> = emptyMap(),
|
||||
init: ClassDocsBuilder.() -> Unit = {},
|
||||
) {
|
||||
val md = miniDoc(doc, tags)
|
||||
val cb = ClassDocsBuilder(name)
|
||||
cb.init()
|
||||
val baseNames = bases.map { it.toDisplayName() }
|
||||
decls += MiniClassDecl(
|
||||
range = builtinRange(),
|
||||
name = name,
|
||||
bases = baseNames,
|
||||
bodyRange = null,
|
||||
ctorFields = emptyList(),
|
||||
classFields = emptyList(),
|
||||
doc = md,
|
||||
nameStart = Pos.builtIn,
|
||||
members = cb.build()
|
||||
)
|
||||
}
|
||||
|
||||
internal fun build(): List<MiniDecl> = decls.toList()
|
||||
}
|
||||
|
||||
class ClassDocsBuilder internal constructor(private val className: String) {
|
||||
private val members = mutableListOf<MiniMemberDecl>()
|
||||
|
||||
fun method(
|
||||
name: String,
|
||||
doc: String,
|
||||
params: List<ParamDoc> = emptyList(),
|
||||
returns: TypeDoc? = null,
|
||||
isStatic: Boolean = false,
|
||||
tags: Map<String, List<String>> = emptyMap(),
|
||||
) {
|
||||
val md = miniDoc(doc, tags)
|
||||
val mp = params.map { MiniParam(it.name, it.type?.toMiniTypeRef(), Pos.builtIn) }
|
||||
val ret = returns?.toMiniTypeRef()
|
||||
members += MiniMemberFunDecl(
|
||||
range = builtinRange(),
|
||||
name = name,
|
||||
params = mp,
|
||||
returnType = ret,
|
||||
doc = md,
|
||||
nameStart = Pos.builtIn,
|
||||
isStatic = isStatic,
|
||||
)
|
||||
}
|
||||
|
||||
fun field(
|
||||
name: String,
|
||||
doc: String,
|
||||
type: TypeDoc? = null,
|
||||
mutable: Boolean = false,
|
||||
isStatic: Boolean = false,
|
||||
tags: Map<String, List<String>> = emptyMap(),
|
||||
) {
|
||||
val md = miniDoc(doc, tags)
|
||||
members += MiniMemberValDecl(
|
||||
range = builtinRange(),
|
||||
name = name,
|
||||
mutable = mutable,
|
||||
type = type?.toMiniTypeRef(),
|
||||
doc = md,
|
||||
nameStart = Pos.builtIn,
|
||||
isStatic = isStatic,
|
||||
)
|
||||
}
|
||||
|
||||
internal fun build(): List<MiniMemberDecl> = members.toList()
|
||||
}
|
||||
|
||||
// ---------------- Helpers ----------------
|
||||
|
||||
private fun builtinRange() = MiniRange(Pos.builtIn, Pos.builtIn)
|
||||
|
||||
private fun miniDoc(text: String, tags: Map<String, List<String>>): MiniDoc {
|
||||
val summary = text.lineSequence().map { it.trim() }.firstOrNull { it.isNotEmpty() }
|
||||
return MiniDoc(range = builtinRange(), raw = text, summary = summary, tags = tags)
|
||||
}
|
||||
|
||||
private fun TypeDoc.toDisplayName(): String = when (this) {
|
||||
is TypeNameDoc -> segments.joinToString(".")
|
||||
is TypeGenericDoc -> base.segments.joinToString(".")
|
||||
is TypeFunctionDoc -> "(function)"
|
||||
is TypeVarDoc -> name
|
||||
}
|
||||
|
||||
internal fun TypeDoc.toMiniTypeRef(): MiniTypeRef = when (this) {
|
||||
is TypeNameDoc -> MiniTypeName(
|
||||
range = builtinRange(),
|
||||
segments = this.segments.map { seg -> MiniTypeName.Segment(seg, builtinRange()) },
|
||||
nullable = this.nullable
|
||||
)
|
||||
is TypeGenericDoc -> MiniGenericType(
|
||||
range = builtinRange(),
|
||||
base = this.base.toMiniTypeRef(),
|
||||
args = this.args.map { it.toMiniTypeRef() },
|
||||
nullable = this.nullable
|
||||
)
|
||||
is TypeFunctionDoc -> MiniFunctionType(
|
||||
range = builtinRange(),
|
||||
receiver = this.receiver?.toMiniTypeRef(),
|
||||
params = this.params.map { it.toMiniTypeRef() },
|
||||
returnType = this.returns.toMiniTypeRef(),
|
||||
nullable = this.nullable
|
||||
)
|
||||
is TypeVarDoc -> MiniTypeVar(
|
||||
range = builtinRange(),
|
||||
name = this.name,
|
||||
nullable = this.nullable
|
||||
)
|
||||
}
|
||||
|
||||
// ---------------- Built-in module doc seeds ----------------
|
||||
|
||||
// Seed docs for lyng.stdlib lazily to avoid init-order coupling.
|
||||
private fun buildStdlibDocs(): List<MiniDecl> {
|
||||
val decls = mutableListOf<MiniDecl>()
|
||||
// Use the same DSL builders to construct decls
|
||||
val mod = ModuleDocsBuilder("lyng.stdlib")
|
||||
// Printing
|
||||
mod.funDoc(
|
||||
name = "print",
|
||||
doc = """
|
||||
Print values to the standard output without a trailing newline.
|
||||
Accepts any number of arguments and prints them separated by a space.
|
||||
""".trimIndent(),
|
||||
// We keep signature minimal; variadic in Lyng is not modeled in MiniAst yet
|
||||
params = listOf(ParamDoc("values"))
|
||||
)
|
||||
mod.funDoc(
|
||||
name = "println",
|
||||
doc = """
|
||||
Print values to the standard output and append a newline.
|
||||
Accepts any number of arguments and prints them separated by a space.
|
||||
""".trimIndent(),
|
||||
params = listOf(ParamDoc("values"))
|
||||
)
|
||||
// Caching helper
|
||||
mod.funDoc(
|
||||
name = "cached",
|
||||
doc = """
|
||||
Wrap a `builder` into a zero-argument thunk that computes once and caches the result.
|
||||
The first call invokes `builder()` and stores the value; subsequent calls return the cached value.
|
||||
""".trimIndent(),
|
||||
params = listOf(ParamDoc("builder")),
|
||||
returns = funType(params = emptyList(), returns = type("lyng.Any"))
|
||||
)
|
||||
// Math helpers (scalar versions)
|
||||
fun math1(name: String) = mod.funDoc(
|
||||
name = name,
|
||||
doc = "Compute $name(x).",
|
||||
params = listOf(ParamDoc("x", type("lyng.Number")))
|
||||
)
|
||||
math1("sin"); math1("cos"); math1("tan"); math1("asin"); math1("acos"); math1("atan")
|
||||
mod.funDoc(name = "floor", doc = "Round down the number to the nearest integer.", params = listOf(ParamDoc("x", type("lyng.Number"))))
|
||||
mod.funDoc(name = "ceil", doc = "Round up the number to the nearest integer.", params = listOf(ParamDoc("x", type("lyng.Number"))))
|
||||
mod.funDoc(name = "round", doc = "Round the number to the nearest integer.", params = listOf(ParamDoc("x", type("lyng.Number"))))
|
||||
|
||||
// Hyperbolic and inverse hyperbolic
|
||||
math1("sinh"); math1("cosh"); math1("tanh"); math1("asinh"); math1("acosh"); math1("atanh")
|
||||
|
||||
// Exponentials and logarithms
|
||||
mod.funDoc(name = "exp", doc = "Euler's exponential e^x.", params = listOf(ParamDoc("x", type("lyng.Number"))))
|
||||
mod.funDoc(name = "ln", doc = "Natural logarithm (base e).", params = listOf(ParamDoc("x", type("lyng.Number"))))
|
||||
mod.funDoc(name = "log10", doc = "Logarithm base 10.", params = listOf(ParamDoc("x", type("lyng.Number"))))
|
||||
mod.funDoc(name = "log2", doc = "Logarithm base 2.", params = listOf(ParamDoc("x", type("lyng.Number"))))
|
||||
|
||||
// Power/roots and absolute value
|
||||
mod.funDoc(
|
||||
name = "pow",
|
||||
doc = "Raise `x` to the power `y`.",
|
||||
params = listOf(ParamDoc("x", type("lyng.Number")), ParamDoc("y", type("lyng.Number")))
|
||||
)
|
||||
mod.funDoc(
|
||||
name = "sqrt",
|
||||
doc = "Square root of `x`.",
|
||||
params = listOf(ParamDoc("x", type("lyng.Number")))
|
||||
)
|
||||
mod.funDoc(
|
||||
name = "abs",
|
||||
doc = "Absolute value of a number (works for Int and Real).",
|
||||
params = listOf(ParamDoc("x", type("lyng.Number")))
|
||||
)
|
||||
|
||||
// Assertions and checks
|
||||
mod.funDoc(
|
||||
name = "assert",
|
||||
doc = """
|
||||
Assert that `cond` is true, otherwise throw an `AssertionFailedException`.
|
||||
Optionally provide a `message`.
|
||||
""".trimIndent(),
|
||||
params = listOf(ParamDoc("cond", type("lyng.Bool")), ParamDoc("message"))
|
||||
)
|
||||
mod.funDoc(
|
||||
name = "assertEquals",
|
||||
doc = "Assert that `a == b`, otherwise throw an assertion error.",
|
||||
params = listOf(ParamDoc("a"), ParamDoc("b"))
|
||||
)
|
||||
mod.funDoc(
|
||||
name = "assertNotEquals",
|
||||
doc = "Assert that `a != b`, otherwise throw an assertion error.",
|
||||
params = listOf(ParamDoc("a"), ParamDoc("b"))
|
||||
)
|
||||
mod.funDoc(
|
||||
name = "assertThrows",
|
||||
doc = """
|
||||
Execute `code` and return the thrown `Exception` object.
|
||||
If nothing is thrown, an assertion error is raised.
|
||||
""".trimIndent(),
|
||||
params = listOf(ParamDoc("code")),
|
||||
returns = type("lyng.Exception", nullable = true)
|
||||
)
|
||||
|
||||
// Utilities
|
||||
mod.funDoc(
|
||||
name = "dynamic",
|
||||
doc = "Wrap a value into a dynamic object that defers resolution to runtime.",
|
||||
params = listOf(ParamDoc("value"))
|
||||
)
|
||||
mod.funDoc(
|
||||
name = "require",
|
||||
doc = "Require `cond` to be true, else throw `IllegalArgumentException` with optional `message`.",
|
||||
params = listOf(ParamDoc("cond", type("lyng.Bool")), ParamDoc("message"))
|
||||
)
|
||||
mod.funDoc(
|
||||
name = "check",
|
||||
doc = "Check `cond` is true, else throw `IllegalStateException` with optional `message`.",
|
||||
params = listOf(ParamDoc("cond", type("lyng.Bool")), ParamDoc("message"))
|
||||
)
|
||||
mod.funDoc(
|
||||
name = "traceScope",
|
||||
doc = "Print a debug trace of the current scope chain with an optional label.",
|
||||
params = listOf(ParamDoc("label", type("lyng.String")))
|
||||
)
|
||||
mod.funDoc(
|
||||
name = "delay",
|
||||
doc = "Suspend for the specified number of milliseconds.",
|
||||
params = listOf(ParamDoc("ms", type("lyng.Number")))
|
||||
)
|
||||
|
||||
// Concurrency helpers
|
||||
mod.funDoc(
|
||||
name = "launch",
|
||||
doc = "Launch an asynchronous task and return a `Deferred`.",
|
||||
params = listOf(ParamDoc("code")),
|
||||
returns = type("lyng.Deferred")
|
||||
)
|
||||
mod.funDoc(
|
||||
name = "yield",
|
||||
doc = "Yield to the scheduler, allowing other tasks to run."
|
||||
)
|
||||
mod.funDoc(
|
||||
name = "flow",
|
||||
doc = "Create a lazy iterable stream using the provided `builder`.",
|
||||
params = listOf(ParamDoc("builder")),
|
||||
returns = type("lyng.Iterable")
|
||||
)
|
||||
|
||||
// Common Iterable helpers (document top-level extension-like APIs as class members)
|
||||
mod.classDoc(name = "Iterable", doc = "Helper operations for iterable collections.") {
|
||||
method(name = "filter", doc = "Filter elements by predicate.", params = listOf(ParamDoc("predicate")), returns = type("lyng.Iterable"))
|
||||
method(name = "drop", doc = "Skip the first N elements.", params = listOf(ParamDoc("n", type("lyng.Int"))), returns = type("lyng.Iterable"))
|
||||
method(name = "first", doc = "Return the first element or throw if empty.")
|
||||
method(name = "last", doc = "Return the last element or throw if empty.")
|
||||
method(name = "dropLast", doc = "Drop the last N elements.", params = listOf(ParamDoc("n", type("lyng.Int"))), returns = type("lyng.Iterable"))
|
||||
method(name = "takeLast", doc = "Take the last N elements.", params = listOf(ParamDoc("n", type("lyng.Int"))), returns = type("lyng.List"))
|
||||
method(name = "joinToString", doc = "Join elements into a string with an optional separator and transformer.", params = listOf(ParamDoc("prefix", type("lyng.String")), ParamDoc("transformer")), returns = type("lyng.String"))
|
||||
method(name = "any", doc = "Return true if any element matches the predicate.", params = listOf(ParamDoc("predicate")), returns = type("lyng.Bool"))
|
||||
method(name = "all", doc = "Return true if all elements match the predicate.", params = listOf(ParamDoc("predicate")), returns = type("lyng.Bool"))
|
||||
method(name = "sum", doc = "Sum all elements; returns null for empty collections.", returns = type("lyng.Number", nullable = true))
|
||||
method(name = "sumOf", doc = "Sum mapped values of elements; returns null for empty collections.", params = listOf(ParamDoc("f")))
|
||||
method(name = "minOf", doc = "Minimum of mapped values.", params = listOf(ParamDoc("lambda")))
|
||||
method(name = "maxOf", doc = "Maximum of mapped values.", params = listOf(ParamDoc("lambda")))
|
||||
method(name = "sorted", doc = "Return elements sorted by natural order.", returns = type("lyng.Iterable"))
|
||||
method(name = "sortedBy", doc = "Return elements sorted by the key selector.", params = listOf(ParamDoc("predicate")), returns = type("lyng.Iterable"))
|
||||
method(name = "shuffled", doc = "Return a shuffled copy as a list.", returns = type("lyng.List"))
|
||||
method(name = "map", doc = "Transform elements by applying `transform`.", params = listOf(ParamDoc("transform")), returns = type("lyng.Iterable"))
|
||||
method(name = "toList", doc = "Collect elements of this iterable into a new list.", returns = type("lyng.List"))
|
||||
}
|
||||
|
||||
// List helpers
|
||||
mod.classDoc(name = "List", doc = "List-specific operations.", bases = listOf(type("Collection"), type("Iterable"))) {
|
||||
method(name = "toString", doc = "Return string representation like [a,b,c].", returns = type("lyng.String"))
|
||||
method(name = "sortBy", doc = "Sort list in-place by key selector.", params = listOf(ParamDoc("predicate")))
|
||||
method(name = "sort", doc = "Sort list in-place by natural order.")
|
||||
method(name = "toList", doc = "Return a shallow copy of this list (new list with the same elements).", returns = type("lyng.List"))
|
||||
}
|
||||
|
||||
// Collection helpers (supertype for sized collections)
|
||||
mod.classDoc(name = "Collection", doc = "Collection operations common to sized collections.", bases = listOf(type("Iterable"))) {
|
||||
method(name = "size", doc = "Number of elements in the collection.", returns = type("lyng.Int"))
|
||||
method(name = "toList", doc = "Collect elements into a new list.", returns = type("lyng.List"))
|
||||
}
|
||||
|
||||
// Iterator helpers
|
||||
mod.classDoc(name = "Iterator", doc = "Iterator protocol for sequential access.") {
|
||||
method(name = "hasNext", doc = "Whether another element is available.", returns = type("lyng.Bool"))
|
||||
method(name = "next", doc = "Return the next element.")
|
||||
method(name = "cancelIteration", doc = "Stop the iteration early.")
|
||||
method(name = "toList", doc = "Consume this iterator and collect elements into a list.", returns = type("lyng.List"))
|
||||
}
|
||||
|
||||
// Exceptions and utilities
|
||||
mod.classDoc(name = "Exception", doc = "Exception helpers.") {
|
||||
method(name = "printStackTrace", doc = "Print this exception and its stack trace to standard output.")
|
||||
}
|
||||
|
||||
mod.classDoc(name = "String", doc = "String helpers.") {
|
||||
method(name = "re", doc = "Compile this string into a regular expression.", returns = type("lyng.Regex"))
|
||||
}
|
||||
|
||||
// StackTraceEntry structure
|
||||
mod.classDoc(name = "StackTraceEntry", doc = "Represents a single stack trace element.") {
|
||||
field(name = "sourceName", doc = "Source (file) name.", type = type("lyng.String"))
|
||||
field(name = "line", doc = "Line number (1-based).", type = type("lyng.Int"))
|
||||
field(name = "column", doc = "Column number (0-based).", type = type("lyng.Int"))
|
||||
field(name = "sourceString", doc = "The source line text.", type = type("lyng.String"))
|
||||
method(name = "toString", doc = "Formatted representation: source:line:column: text.", returns = type("lyng.String"))
|
||||
}
|
||||
|
||||
// Constants and namespaces
|
||||
mod.valDoc(
|
||||
name = "π",
|
||||
doc = "The mathematical constant pi.",
|
||||
type = type("lyng.Real"),
|
||||
mutable = false
|
||||
)
|
||||
mod.classDoc(name = "Math", doc = "Mathematical constants and helpers.") {
|
||||
field(name = "PI", doc = "The mathematical constant pi.", type = type("lyng.Real"), isStatic = true)
|
||||
}
|
||||
|
||||
decls += mod.build()
|
||||
return decls
|
||||
}
|
||||
|
||||
// (Registration is triggered from BuiltinDocRegistry.init)
|
||||
@@ -0,0 +1,178 @@
|
||||
/*
|
||||
* Copyright 2025 Sergey S. Chernov real.sergeych@gmail.com
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
package net.sergeych.lyng.miniast
|
||||
|
||||
import net.sergeych.lyng.ModuleScope
|
||||
import net.sergeych.lyng.Scope
|
||||
import net.sergeych.lyng.Visibility
|
||||
import net.sergeych.lyng.obj.Obj
|
||||
import net.sergeych.lyng.obj.ObjClass
|
||||
import net.sergeych.lyng.obj.ObjVoid
|
||||
|
||||
/**
|
||||
* Helper extensions that mirror `addFn`/`addConst` APIs but also register Markdown docs
|
||||
* into the BuiltinDocRegistry (MiniAst-based). This keeps docs co-located with code
|
||||
* definitions and avoids any runtime overhead.
|
||||
*/
|
||||
|
||||
// --------- Module-level (Scope) ---------
|
||||
|
||||
inline fun <reified T : Obj> Scope.addFnDoc(
|
||||
vararg names: String,
|
||||
doc: String,
|
||||
params: List<ParamDoc> = emptyList(),
|
||||
returns: TypeDoc? = null,
|
||||
tags: Map<String, List<String>> = emptyMap(),
|
||||
moduleName: String? = null,
|
||||
crossinline fn: suspend Scope.() -> T
|
||||
) {
|
||||
// Register runtime function(s)
|
||||
addFn(*names) { fn() }
|
||||
// Determine module
|
||||
val mod = moduleName ?: findModuleNameOrUnknown()
|
||||
// Register docs once per name
|
||||
if (names.isNotEmpty()) BuiltinDocRegistry.module(mod) {
|
||||
for (n in names) funDoc(name = n, doc = doc, params = params, returns = returns, tags = tags)
|
||||
}
|
||||
}
|
||||
|
||||
inline fun Scope.addVoidFnDoc(
|
||||
vararg names: String,
|
||||
doc: String,
|
||||
tags: Map<String, List<String>> = emptyMap(),
|
||||
moduleName: String? = null,
|
||||
crossinline fn: suspend Scope.() -> Unit
|
||||
) {
|
||||
addFnDoc<ObjVoid>(
|
||||
*names,
|
||||
doc = doc,
|
||||
params = emptyList(),
|
||||
returns = null,
|
||||
tags = tags,
|
||||
moduleName = moduleName
|
||||
) {
|
||||
fn(this)
|
||||
ObjVoid
|
||||
}
|
||||
}
|
||||
|
||||
fun Scope.addConstDoc(
|
||||
name: String,
|
||||
value: Obj,
|
||||
doc: String,
|
||||
type: TypeDoc? = null,
|
||||
mutable: Boolean = false,
|
||||
tags: Map<String, List<String>> = emptyMap(),
|
||||
moduleName: String? = null
|
||||
) {
|
||||
if (mutable) addItem(name, true, value) else addConst(name, value)
|
||||
BuiltinDocRegistry.module(moduleName ?: findModuleNameOrUnknown()) {
|
||||
valDoc(name = name, doc = doc, type = type, mutable = mutable, tags = tags)
|
||||
}
|
||||
}
|
||||
|
||||
// --------- Class-level (ObjClass) ---------
|
||||
|
||||
fun ObjClass.addFnDoc(
|
||||
name: String,
|
||||
doc: String,
|
||||
params: List<ParamDoc> = emptyList(),
|
||||
returns: TypeDoc? = null,
|
||||
isOpen: Boolean = false,
|
||||
visibility: Visibility = Visibility.Public,
|
||||
tags: Map<String, List<String>> = emptyMap(),
|
||||
moduleName: String? = null,
|
||||
code: suspend Scope.() -> Obj
|
||||
) {
|
||||
// Register runtime method
|
||||
addFn(name, isOpen, visibility, code)
|
||||
// Register docs for the member under this class
|
||||
BuiltinDocRegistry.module(moduleName ?: ownerModuleNameFromClassOrUnknown()) {
|
||||
classDoc(this@addFnDoc.className, doc = "") {
|
||||
method(name = name, doc = doc, params = params, returns = returns, tags = tags)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun ObjClass.addConstDoc(
|
||||
name: String,
|
||||
value: Obj,
|
||||
doc: String,
|
||||
type: TypeDoc? = null,
|
||||
isMutable: Boolean = false,
|
||||
visibility: Visibility = Visibility.Public,
|
||||
tags: Map<String, List<String>> = emptyMap(),
|
||||
moduleName: String? = null
|
||||
) {
|
||||
createField(name, value, isMutable, visibility)
|
||||
BuiltinDocRegistry.module(moduleName ?: ownerModuleNameFromClassOrUnknown()) {
|
||||
classDoc(this@addConstDoc.className, doc = "") {
|
||||
field(name = name, doc = doc, type = type, mutable = isMutable, tags = tags)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun ObjClass.addClassFnDoc(
|
||||
name: String,
|
||||
doc: String,
|
||||
params: List<ParamDoc> = emptyList(),
|
||||
returns: TypeDoc? = null,
|
||||
isOpen: Boolean = false,
|
||||
tags: Map<String, List<String>> = emptyMap(),
|
||||
moduleName: String? = null,
|
||||
code: suspend Scope.() -> Obj
|
||||
) {
|
||||
addClassFn(name, isOpen, code)
|
||||
BuiltinDocRegistry.module(moduleName ?: ownerModuleNameFromClassOrUnknown()) {
|
||||
classDoc(this@addClassFnDoc.className, doc = "") {
|
||||
method(name = name, doc = doc, params = params, returns = returns, isStatic = true, tags = tags)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun ObjClass.addClassConstDoc(
|
||||
name: String,
|
||||
value: Obj,
|
||||
doc: String,
|
||||
type: TypeDoc? = null,
|
||||
isMutable: Boolean = false,
|
||||
tags: Map<String, List<String>> = emptyMap(),
|
||||
moduleName: String? = null
|
||||
) {
|
||||
createClassField(name, value, isMutable)
|
||||
BuiltinDocRegistry.module(moduleName ?: ownerModuleNameFromClassOrUnknown()) {
|
||||
classDoc(this@addClassConstDoc.className, doc = "") {
|
||||
field(name = name, doc = doc, type = type, mutable = isMutable, isStatic = true, tags = tags)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ------------- utils -------------
|
||||
@PublishedApi
|
||||
internal tailrec fun Scope.findModuleNameOrNull(): String? = when (this) {
|
||||
is ModuleScope -> this.packageName
|
||||
else -> this.parent?.findModuleNameOrNull()
|
||||
}
|
||||
|
||||
@PublishedApi
|
||||
internal fun Scope.findModuleNameOrUnknown(): String = findModuleNameOrNull() ?: "unknown"
|
||||
|
||||
@PublishedApi
|
||||
internal fun ObjClass.ownerModuleNameFromClassOrUnknown(): String =
|
||||
// Try to find a ModuleScope in classScope parent chain if available, else unknown
|
||||
(classScope?.parent?.findModuleNameOrNull()) ?: "unknown"
|
||||
@@ -130,7 +130,9 @@ data class MiniClassDecl(
|
||||
val ctorFields: List<MiniCtorField> = emptyList(),
|
||||
val classFields: List<MiniCtorField> = emptyList(),
|
||||
override val doc: MiniDoc?,
|
||||
override val nameStart: Pos
|
||||
override val nameStart: Pos,
|
||||
// Built-in extension: list of member declarations (functions and fields)
|
||||
val members: List<MiniMemberDecl> = emptyList()
|
||||
) : MiniDecl
|
||||
|
||||
data class MiniCtorField(
|
||||
@@ -153,6 +155,34 @@ data class MiniIdentifier(
|
||||
val role: IdRole
|
||||
) : MiniNode
|
||||
|
||||
// --- Class member declarations (for built-in/registry docs) ---
|
||||
sealed interface MiniMemberDecl : MiniNode {
|
||||
val name: String
|
||||
val doc: MiniDoc?
|
||||
val nameStart: Pos
|
||||
val isStatic: Boolean
|
||||
}
|
||||
|
||||
data class MiniMemberFunDecl(
|
||||
override val range: MiniRange,
|
||||
override val name: String,
|
||||
val params: List<MiniParam>,
|
||||
val returnType: MiniTypeRef?,
|
||||
override val doc: MiniDoc?,
|
||||
override val nameStart: Pos,
|
||||
override val isStatic: Boolean = false,
|
||||
) : MiniMemberDecl
|
||||
|
||||
data class MiniMemberValDecl(
|
||||
override val range: MiniRange,
|
||||
override val name: String,
|
||||
val mutable: Boolean,
|
||||
val type: MiniTypeRef?,
|
||||
override val doc: MiniDoc?,
|
||||
override val nameStart: Pos,
|
||||
override val isStatic: Boolean = false,
|
||||
) : MiniMemberDecl
|
||||
|
||||
// Streaming sink to collect mini-AST during parsing. Implementations may assemble a tree or process events.
|
||||
interface MiniAstSink {
|
||||
fun onScriptStart(start: Pos) {}
|
||||
|
||||
@@ -16,7 +16,6 @@
|
||||
*/
|
||||
|
||||
package net.sergeych.lyng.obj
|
||||
|
||||
import net.sergeych.lyng.Scope
|
||||
import net.sergeych.lyng.Visibility
|
||||
|
||||
@@ -31,7 +30,7 @@ data class ObjRecord(
|
||||
val declaringClass: ObjClass? = null,
|
||||
var importedFrom: Scope? = null,
|
||||
val isTransient: Boolean = false,
|
||||
val type: Type = Type.Other
|
||||
val type: Type = Type.Other,
|
||||
) {
|
||||
enum class Type(val comparable: Boolean = false,val serializable: Boolean = false) {
|
||||
Field(true, true),
|
||||
|
||||
Reference in new issue
Block a user