plugin improvements; attempt to make AI life easier (incomplete and weak)

This commit is contained in:
Sergey Chernov 2026-01-12 15:39:30 +01:00
parent 474293cfe3
commit d2632cb99e
5 changed files with 148 additions and 2 deletions

11
.junie/guidelines.md Normal file
View File

@ -0,0 +1,11 @@
# Lyng Project Guidelines
This project uses the Lyng scripting language for multiplatform scripting.
## Coding in Lyng
When writing, refactoring, or analyzing Lyng code:
- **Reference**: Always use `LYNG_AI_SPEC.md` in the project root as the primary source of truth for syntax and idioms.
- **File Extensions**: Use `.lyng` for all script files.
- **Implicit Coroutines**: Remember that all Lyng functions are implicitly coroutines; do not look for `async/await`.
- **Everything is an Expression**: Leverage the fact that blocks, if-statements, and loops return values.
- **Maps vs Blocks**: Be careful: `{}` is a block/lambda, use `Map()` for an empty map.

89
LYNG_AI_SPEC.md Normal file
View File

@ -0,0 +1,89 @@
# Lyng Language AI Specification (V1.2)
High-density specification for LLMs. Reference this for all Lyng code generation.
## 1. Core Philosophy & Syntax
- **Everything is an Expression**: Blocks, `if`, `when`, `for`, `while` return their last expression (or `void`).
- **Implicit Coroutines**: All functions are coroutines. No `async/await`. Use `launch { ... }` (returns `Deferred`) or `flow { ... }`.
- **Variables**: `val` (read-only), `var` (mutable). Supports late-init `val` in classes (must be assigned in `init` or body).
- **Null Safety**: `?` (nullable type), `?.` (safe access), `?( )` (safe invoke), `?{ }` (safe block invoke), `?[ ]` (safe index), `?:` or `??` (elvis), `?=` (assign-if-null).
- **Equality**: `==` (equals), `!=` (not equals), `===` (ref identity), `!==` (ref not identity).
- **Comparison**: `<`, `>`, `<=`, `>=`, `<=>` (shuttle/spaceship, returns -1, 0, 1).
- **Destructuring**: `val [a, b, rest...] = list`. Supports nested `[a, [b, c]]` and splats.
## 2. Object-Oriented Programming (OOP)
- **Multiple Inheritance**: Supported with **C3 MRO** (Python-style). Diamond-safe.
- **Header Arguments**: `class Foo(a, b) : Base(a)` defines fields `a`, `b` and passes `a` to `Base`.
- **Members**: `fun name(args) { ... }`, `val`, `var`, `static val`, `static fun`.
- **Properties (Get/Set)**: Pure accessors, no auto-backing fields.
```lyng
var age
get() = _age
private set(v) { if(v >= 0) _age = v }
// Laconic syntax:
val area get = π * r * r
```
- **Mandatory `override`**: Required for all members existing in the ancestor chain.
- **Visibility**: `public` (default), `protected` (subclasses), `private` (this class instance only). `private set` / `protected set` allowed on properties.
- **Disambiguation**: `this@Base.member()` or `(obj as Base).member()`. `as` returns a qualified view.
- **Abstract/Interface**: `interface` is a synonym for `abstract class`. Both support state and constructors.
- **Extensions**: `fun Class.ext()` or `val Class.ext get = ...`. Scope-isolated.
## 3. Delegation (`by`)
Unified model for `val`, `var`, and `fun`.
```lyng
val x by MyDelegate()
var y by Map() // Uses "y" as key in map
fn f(a, b) by RemoteProxy() // Calls Proxy.invoke(thisRef, "f", a, b)
```
Delegate Methods:
- `getValue(thisRef, name)`: for `val`/`var`.
- `setValue(thisRef, name, val)`: for `var`.
- `invoke(thisRef, name, args...)`: for `fn` (called if `getValue` is absent).
- `bind(name, access, thisRef)`: optional hook called at declaration/binding time. `access` is `DelegateAccess.Val`, `Var`, or `Callable`.
## 4. Standard Library & Functional Built-ins
- **Scope Functions**:
- `obj.let { it... }`: result of block. `it` is `obj`.
- `obj.apply { this... }`: returns `obj`. `this` is `obj`.
- `obj.also { it... }`: returns `obj`. `it` is `obj`.
- `obj.run { this... }`: result of block. `this` is `obj`.
- `with(obj, { ... })`: result of block. `this` is `obj`.
- **Functional**: `forEach`, `map`, `filter`, `any`, `all`, `sum`, `count`, `sortedBy`, `flatten`, `flatMap`, `associateBy`.
- **Lazy**: `val x = cached { expensive() }` (call as `x()`) or `val x by lazy { ... }`.
- **Collections**: `List` ( `[a, b]` ), `Map` ( `Map(k => v)` ), `Set` ( `Set(a, b)` ). `MapEntry` ( `k => v` ).
## 5. Patterns & Shorthands
- **Map Literals**: `{ key: value, identifier: }` (identifier shorthand `x:` is `x: x`). Empty map is `Map()`.
- **Named Arguments**: `fun(y: 10, x: 5)`. Shorthand: `Point(x:, y:)`.
- **Varargs & Splats**: `fun f(args...)`, `f(...otherList)`.
- **Labels**: `loop@ for(x in list) { if(x == 0) break@loop }`.
- **Dynamic**: `val d = dynamic { get { name -> ... } }` allows `d.anyName`.
## 6. Operators & Methods to Overload
| Op | Method | Op | Method |
| :--- | :--- | :--- | :--- |
| `+` | `plus` | `==` | `equals` |
| `-` | `minus` | `<=>` | `compareTo` |
| `*` | `mul` | `[]` | `getAt` / `putAt` |
| `/` | `div` | `!` | `logicalNot` |
| `%` | `mod` | `-` | `negate` (unary) |
| `=~` | `operatorMatch` | `+=` | `plusAssign` |
## 7. Common Snippets
```lyng
// Multiple Inheritance and Properties
class Warrior(id, hp) : Character(id), HealthPool(hp) {
override fun toString() = "Warrior #%s (%s HP)"(id, hp)
}
// Map entry and merging
val m = Map("a" => 1) + ("b" => 2)
m += "c" => 3
// Destructuring with splat
val [first, middle..., last] = [1, 2, 3, 4, 5]
// Safe Navigation and Elvis
val companyName = person?.job?.company?.name ?? "Freelancer"
```

View File

@ -140,6 +140,12 @@ Tips:
- After a dot, globals are intentionally suppressed (e.g., `lines().Path` is not valid), only the receiver’s members are suggested. - After a dot, globals are intentionally suppressed (e.g., `lines().Path` is not valid), only the receiver’s members are suggested.
- If completion seems sparse, make sure related modules are imported (e.g., `import lyng.io.fs` so that `Path` and its methods are known). - If completion seems sparse, make sure related modules are imported (e.g., `import lyng.io.fs` so that `Path` and its methods are known).
## AI Assistant Support
To help AI assistants (like Cursor, Windsurf, or GitHub Copilot) understand Lyng with minimal effort, we provide a high-density language specification:
- **[LYNG_AI_SPEC.md](LYNG_AI_SPEC.md)**: A concise guide for AI models to learn Lyng syntax, idioms, and core philosophy. We recommend pointing your AI tool to this file or including it in your project's custom instructions.
## Why? ## Why?
Designed to add scripting to kotlin multiplatform application in easy and efficient way. This is attempt to achieve what Lua is for C/++. Designed to add scripting to kotlin multiplatform application in easy and efficient way. This is attempt to achieve what Lua is for C/++.

View File

@ -239,6 +239,9 @@ object CompletionEngineLight {
fun addMembersOf(name: String, direct: Boolean) { fun addMembersOf(name: String, direct: Boolean) {
val cls = classes[name] ?: return val cls = classes[name] ?: return
val target = if (direct) directMap else inheritedMap val target = if (direct) directMap else inheritedMap
for (cf in cls.ctorFields + cls.classFields) {
target.getOrPut(cf.name) { mutableListOf() }.add(DocLookupUtils.toMemberVal(cf))
}
for (m in cls.members) target.getOrPut(m.name) { mutableListOf() }.add(m) for (m in cls.members) target.getOrPut(m.name) { mutableListOf() }.add(m)
for (b in cls.bases) if (visited.add(b)) addMembersOf(b, false) for (b in cls.bases) if (visited.add(b)) addMembersOf(b, false)
} }

View File

@ -96,6 +96,19 @@ object DocLookupUtils {
return null return null
} }
fun findEnclosingClass(mini: MiniScript, offset: Int): MiniClassDecl? {
val src = mini.range.start.source
return mini.declarations.filterIsInstance<MiniClassDecl>()
.filter {
val start = src.offsetOf(it.range.start)
val end = src.offsetOf(it.range.end)
offset in start..end
}
.minByOrNull {
src.offsetOf(it.range.end) - src.offsetOf(it.range.start)
}
}
fun findTypeByRange(mini: MiniScript?, name: String, startOffset: Int, text: String? = null, imported: List<String>? = null): MiniTypeRef? { fun findTypeByRange(mini: MiniScript?, name: String, startOffset: Int, text: String? = null, imported: List<String>? = null): MiniTypeRef? {
if (mini == null) return null if (mini == null) return null
val src = mini.range.start.source val src = mini.range.start.source
@ -233,10 +246,14 @@ object DocLookupUtils {
val rep = list.first() val rep = list.first()
val bases = LinkedHashSet<String>() val bases = LinkedHashSet<String>()
val members = LinkedHashMap<String, MutableList<MiniMemberDecl>>() val members = LinkedHashMap<String, MutableList<MiniMemberDecl>>()
val ctorFields = LinkedHashMap<String, MiniCtorField>()
val classFields = LinkedHashMap<String, MiniCtorField>()
var doc: MiniDoc? = null var doc: MiniDoc? = null
for (c in list) { for (c in list) {
bases.addAll(c.bases) bases.addAll(c.bases)
if (doc == null && c.doc != null && c.doc.raw.isNotBlank()) doc = c.doc if (doc == null && c.doc != null && c.doc.raw.isNotBlank()) doc = c.doc
for (cf in c.ctorFields) ctorFields[cf.name] = cf
for (cf in c.classFields) classFields[cf.name] = cf
for (m in c.members) { for (m in c.members) {
// Group by name to keep overloads together // Group by name to keep overloads together
members.getOrPut(m.name) { mutableListOf() }.add(m) members.getOrPut(m.name) { mutableListOf() }.add(m)
@ -249,8 +266,8 @@ object DocLookupUtils {
name = rep.name, name = rep.name,
bases = bases.toList(), bases = bases.toList(),
bodyRange = rep.bodyRange, bodyRange = rep.bodyRange,
ctorFields = rep.ctorFields, ctorFields = ctorFields.values.toList(),
classFields = rep.classFields, classFields = classFields.values.toList(),
doc = doc, doc = doc,
nameStart = rep.nameStart, nameStart = rep.nameStart,
members = mergedMembers members = mergedMembers
@ -268,6 +285,18 @@ object DocLookupUtils {
return result return result
} }
internal fun toMemberVal(cf: MiniCtorField): MiniMemberValDecl = MiniMemberValDecl(
range = MiniRange(cf.nameStart, cf.nameStart),
name = cf.name,
mutable = cf.mutable,
type = cf.type,
initRange = null,
doc = null,
nameStart = cf.nameStart,
isStatic = false,
isExtern = false
)
fun resolveMemberWithInheritance(importedModules: List<String>, className: String, member: String, localMini: MiniScript? = null): Pair<String, MiniNamedDecl>? { fun resolveMemberWithInheritance(importedModules: List<String>, className: String, member: String, localMini: MiniScript? = null): Pair<String, MiniNamedDecl>? {
val classes = aggregateClasses(importedModules, localMini) val classes = aggregateClasses(importedModules, localMini)
fun dfs(name: String, visited: MutableSet<String>): Pair<String, MiniNamedDecl>? { fun dfs(name: String, visited: MutableSet<String>): Pair<String, MiniNamedDecl>? {
@ -275,6 +304,8 @@ object DocLookupUtils {
val cls = classes[name] val cls = classes[name]
if (cls != null) { if (cls != null) {
cls.members.firstOrNull { it.name == member }?.let { return name to it } cls.members.firstOrNull { it.name == member }?.let { return name to it }
cls.ctorFields.firstOrNull { it.name == member }?.let { return name to toMemberVal(it) }
cls.classFields.firstOrNull { it.name == member }?.let { return name to toMemberVal(it) }
for (baseName in cls.bases) { for (baseName in cls.bases) {
dfs(baseName, visited)?.let { return it } dfs(baseName, visited)?.let { return it }
} }
@ -656,6 +687,12 @@ object DocLookupUtils {
val identRange = wordRangeAt(text, i + 1) val identRange = wordRangeAt(text, i + 1)
if (identRange != null) { if (identRange != null) {
val ident = text.substring(identRange.first, identRange.second) val ident = text.substring(identRange.first, identRange.second)
// 3a) Handle plain "this"
if (ident == "this" && mini != null) {
findEnclosingClass(mini, identRange.first)?.let { return it.name }
}
// if it's "as Type", we want Type // if it's "as Type", we want Type
var k = prevNonWs(text, identRange.first - 1) var k = prevNonWs(text, identRange.first - 1)
if (k >= 1 && text[k] == 's' && text[k - 1] == 'a' && (k - 1 == 0 || !text[k - 2].isLetterOrDigit())) { if (k >= 1 && text[k] == 's' && text[k - 1] == 'a' && (k - 1 == 0 || !text[k - 2].isLetterOrDigit())) {