diff --git a/.junie/guidelines.md b/.junie/guidelines.md new file mode 100644 index 0000000..3d058cb --- /dev/null +++ b/.junie/guidelines.md @@ -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. diff --git a/LYNG_AI_SPEC.md b/LYNG_AI_SPEC.md new file mode 100644 index 0000000..9fb5614 --- /dev/null +++ b/LYNG_AI_SPEC.md @@ -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" +``` diff --git a/README.md b/README.md index c0b11e9..3771229 100644 --- a/README.md +++ b/README.md @@ -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. - 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? Designed to add scripting to kotlin multiplatform application in easy and efficient way. This is attempt to achieve what Lua is for C/++. diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/miniast/CompletionEngineLight.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/miniast/CompletionEngineLight.kt index beacf25..91a7c9d 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/miniast/CompletionEngineLight.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/miniast/CompletionEngineLight.kt @@ -239,6 +239,9 @@ object CompletionEngineLight { fun addMembersOf(name: String, direct: Boolean) { val cls = classes[name] ?: return 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 (b in cls.bases) if (visited.add(b)) addMembersOf(b, false) } diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/miniast/DocLookupUtils.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/miniast/DocLookupUtils.kt index 7f253b2..8441aca 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/miniast/DocLookupUtils.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/miniast/DocLookupUtils.kt @@ -96,6 +96,19 @@ object DocLookupUtils { return null } + fun findEnclosingClass(mini: MiniScript, offset: Int): MiniClassDecl? { + val src = mini.range.start.source + return mini.declarations.filterIsInstance() + .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? = null): MiniTypeRef? { if (mini == null) return null val src = mini.range.start.source @@ -233,10 +246,14 @@ object DocLookupUtils { val rep = list.first() val bases = LinkedHashSet() val members = LinkedHashMap>() + val ctorFields = LinkedHashMap() + val classFields = LinkedHashMap() var doc: MiniDoc? = null for (c in list) { bases.addAll(c.bases) 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) { // Group by name to keep overloads together members.getOrPut(m.name) { mutableListOf() }.add(m) @@ -249,8 +266,8 @@ object DocLookupUtils { name = rep.name, bases = bases.toList(), bodyRange = rep.bodyRange, - ctorFields = rep.ctorFields, - classFields = rep.classFields, + ctorFields = ctorFields.values.toList(), + classFields = classFields.values.toList(), doc = doc, nameStart = rep.nameStart, members = mergedMembers @@ -268,6 +285,18 @@ object DocLookupUtils { 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, className: String, member: String, localMini: MiniScript? = null): Pair? { val classes = aggregateClasses(importedModules, localMini) fun dfs(name: String, visited: MutableSet): Pair? { @@ -275,6 +304,8 @@ object DocLookupUtils { val cls = classes[name] if (cls != null) { 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) { dfs(baseName, visited)?.let { return it } } @@ -656,6 +687,12 @@ object DocLookupUtils { val identRange = wordRangeAt(text, i + 1) if (identRange != null) { 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 var k = prevNonWs(text, identRange.first - 1) if (k >= 1 && text[k] == 's' && text[k - 1] == 'a' && (k - 1 == 0 || !text[k - 2].isLetterOrDigit())) {