diff --git a/docs/fix-scope-parent-cycle.md b/archived/fix-scope-parent-cycle.md similarity index 100% rename from docs/fix-scope-parent-cycle.md rename to archived/fix-scope-parent-cycle.md diff --git a/docs/migrate_time_to_2_2.md b/archived/migrate_time_to_2_2.md similarity index 100% rename from docs/migrate_time_to_2_2.md rename to archived/migrate_time_to_2_2.md diff --git a/docs/wasm_generation_bug.md b/archived/wasm_generation_bug.md similarity index 100% rename from docs/wasm_generation_bug.md rename to archived/wasm_generation_bug.md diff --git a/docs/ai_language_reference.md b/docs/ai_language_reference.md index 79c77ed..d4c8e6f 100644 --- a/docs/ai_language_reference.md +++ b/docs/ai_language_reference.md @@ -1,5 +1,7 @@ # Lyng Language Reference for AI Agents (Current Compiler State) +[//]: # (excludeFromIndex) + Purpose: dense, implementation-first reference for generating valid Lyng code. Primary sources used: `lynglib/src/commonMain/kotlin/net/sergeych/lyng/{Parser,Token,Compiler,Script,TypeDecl}.kt`, `lynglib/stdlib/lyng/root.lyng`, tests in `lynglib/src/commonTest` and `lynglib/src/jvmTest`. diff --git a/docs/ai_notes_wasm_generation_bug.md b/docs/ai_notes_wasm_generation_bug.md index 7919636..a7f35b9 100644 --- a/docs/ai_notes_wasm_generation_bug.md +++ b/docs/ai_notes_wasm_generation_bug.md @@ -1,5 +1,7 @@ # AI notes: avoid Kotlin/Wasm invalid IR with suspend lambdas +[//]: # (excludeFromIndex) + ## Do - Prefer explicit `object : Statement()` with `override suspend fun execute(...)` when building compiler statements. - Keep `Statement` objects non-lambda, especially in compiler hot paths like parsing/var declarations. diff --git a/docs/ai_stdlib_reference.md b/docs/ai_stdlib_reference.md index d699fa3..8f450fd 100644 --- a/docs/ai_stdlib_reference.md +++ b/docs/ai_stdlib_reference.md @@ -1,5 +1,7 @@ # Lyng Stdlib Reference for AI Agents (Compact) +[//]: # (excludeFromIndex) + Purpose: fast overview of what is available by default and what must be imported. Sources: `lynglib/src/commonMain/kotlin/net/sergeych/lyng/Script.kt`, `lynglib/stdlib/lyng/root.lyng`, `lynglib/src/commonMain/kotlin/net/sergeych/lyng/stdlib_included/observable_lyng.kt`. diff --git a/docs/downloads.md b/docs/downloads.md new file mode 100644 index 0000000..0de0120 --- /dev/null +++ b/docs/downloads.md @@ -0,0 +1,11 @@ +# Some resources to download + +## Lync CLI tool + +- [lyng-linuxX64.zip](/distributables/lyng-linuxX64.zip) CLI tool for linuxX64: nodependencies, small monolith executable binary. + +## IDE plugins + +- [lyng-textmate.zip](../../lyng/distributables/lyng-textmate.zip) Texmate-compatible bundle with syntax coloring (could be outdated) + +- [lyng-idea-0.0.5-SNAPSHOT.zip](/distributables/lyng-idea-0.0.5-SNAPSHOT.zip) - plugin for IntelliJ-compatible IDE diff --git a/docs/embedding.md b/docs/embedding.md index 9d3c6fb..e14a926 100644 --- a/docs/embedding.md +++ b/docs/embedding.md @@ -1,5 +1,7 @@ # Embedding Lyng in your Kotlin project +[//]: # (topMenu) + Lyng is a tiny, embeddable, Kotlin‑first scripting language. This page shows, step by step, how to: - add Lyng to your build diff --git a/lyng/src/jvmTest/kotlin/net/sergeych/lyng_cli/FsIntegrationJvmTest.kt b/lyng/src/jvmTest/kotlin/net/sergeych/lyng_cli/FsIntegrationJvmTest.kt index 2484fa7..fb56515 100644 --- a/lyng/src/jvmTest/kotlin/net/sergeych/lyng_cli/FsIntegrationJvmTest.kt +++ b/lyng/src/jvmTest/kotlin/net/sergeych/lyng_cli/FsIntegrationJvmTest.kt @@ -31,11 +31,12 @@ class FsIntegrationJvmTest { val dir = createTempDirectory("lyng_cli_fs_test_") try { val file = dir.resolve("hello.txt") + val filePath = file.toString().replace("\\", "\\\\") // Drive the operation via Lyng code to validate bindings end-to-end scope.eval( """ import lyng.io.fs - val p = Path("${'$'}{file}") + val p = Path("${filePath}") p.writeUtf8("hello from cli test") assertEquals(true, p.exists()) assertEquals("hello from cli test", p.readUtf8()) diff --git a/lyngio/src/commonMain/kotlin/net/sergeych/lyng/io/http/LyngHttpModule.kt b/lyngio/src/commonMain/kotlin/net/sergeych/lyng/io/http/LyngHttpModule.kt index baa3765..af09106 100644 --- a/lyngio/src/commonMain/kotlin/net/sergeych/lyng/io/http/LyngHttpModule.kt +++ b/lyngio/src/commonMain/kotlin/net/sergeych/lyng/io/http/LyngHttpModule.kt @@ -332,7 +332,9 @@ private class ObjHttpResponse( fun from(response: LyngHttpResponse): ObjHttpResponse { val single = linkedMapOf() response.headers.forEach { (name, values) -> - if (values.isNotEmpty() && name !in single) single[name] = values.first() + if (values.isNotEmpty() && !single.containsKey(name)) { + single[name] = values.first() + } } return ObjHttpResponse( status = response.status.toLong(), diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Compiler.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Compiler.kt index 895afe3..abe3156 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Compiler.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Compiler.kt @@ -4532,6 +4532,15 @@ class Compiler( } } + private fun inferForLoopElementType(source: Statement, constRange: ConstIntRange?): TypeDecl? { + if (constRange != null) return TypeDecl.Simple("Int", false) + val sourceType = inferTypeDeclFromInitializer(source) ?: return null + return when { + isRangeType(sourceType) -> TypeDecl.Simple("Int", false) + else -> inferCollectionElementType(expandTypeAliases(sourceType, source.pos)) + } + } + private fun typeDeclSubtypeOf(arg: TypeDecl, param: TypeDecl): Boolean { if (param == TypeDecl.TypeAny || param == TypeDecl.TypeNullableAny) return true val (argBase, argNullable) = stripNullable(arg) @@ -4934,12 +4943,33 @@ class Compiler( private fun inferCallReturnClass(ref: CallRef): ObjClass? { return when (val target = ref.target) { - is LocalSlotRef -> callableReturnTypeByScopeId[target.scopeId]?.get(target.slot) - ?: resolveClassByName(target.name) - is LocalVarRef -> callableReturnTypeByName[target.name] - ?: resolveClassByName(target.name) - is FastLocalVarRef -> callableReturnTypeByName[target.name] - ?: resolveClassByName(target.name) + is LocalSlotRef -> when (target.name) { + "lazy" -> resolveClassByName("lazy") + "iterator" -> ObjIterator + "flow" -> ObjFlow.type + "launch" -> ObjDeferred.type + "dynamic" -> ObjDynamic.type + else -> callableReturnTypeByScopeId[target.scopeId]?.get(target.slot) + ?: resolveClassByName(target.name) + } + is LocalVarRef -> when (target.name) { + "lazy" -> resolveClassByName("lazy") + "iterator" -> ObjIterator + "flow" -> ObjFlow.type + "launch" -> ObjDeferred.type + "dynamic" -> ObjDynamic.type + else -> callableReturnTypeByName[target.name] + ?: resolveClassByName(target.name) + } + is FastLocalVarRef -> when (target.name) { + "lazy" -> resolveClassByName("lazy") + "iterator" -> ObjIterator + "flow" -> ObjFlow.type + "launch" -> ObjDeferred.type + "dynamic" -> ObjDynamic.type + else -> callableReturnTypeByName[target.name] + ?: resolveClassByName(target.name) + } is ConstRef -> when (val value = target.constValue) { is ObjClass -> value is ObjString -> ObjString.type @@ -7490,6 +7520,20 @@ class Compiler( val loopSlotPlan = SlotPlan(mutableMapOf(), 0, nextScopeId++) slotPlanStack.add(loopSlotPlan) declareSlotName(tVar.value, isMutable = true, isDelegated = false) + val loopSlotIndex = loopSlotPlan.slots[tVar.value]?.index + val loopVarTypeDecl = inferForLoopElementType(source, constRange) + val hadLoopNameType = nameTypeDecl.containsKey(tVar.value) + val prevLoopNameType = nameTypeDecl[tVar.value] + val hadLoopNameClass = nameObjClass.containsKey(tVar.value) + val prevLoopNameClass = nameObjClass[tVar.value] + if (loopSlotIndex != null && loopVarTypeDecl != null) { + slotTypeDeclByScopeId.getOrPut(loopSlotPlan.id) { mutableMapOf() }[loopSlotIndex] = loopVarTypeDecl + nameTypeDecl[tVar.value] = loopVarTypeDecl + resolveTypeDeclObjClass(loopVarTypeDecl)?.let { loopVarClass -> + slotTypeByScopeId.getOrPut(loopSlotPlan.id) { mutableMapOf() }[loopSlotIndex] = loopVarClass + nameObjClass[tVar.value] = loopVarClass + } + } val (canBreak, body, elseStatement) = try { resolutionSink?.enterScope(ScopeKind.BLOCK, tVar.pos, null) resolutionSink?.declareSymbol(tVar.value, SymbolKind.LOCAL, isMutable = true, pos = tVar.pos) @@ -7509,6 +7553,16 @@ class Compiler( Triple(loopParsed.first, loopParsed.second, elseStmt) } } finally { + if (hadLoopNameType) { + nameTypeDecl[tVar.value] = prevLoopNameType!! + } else { + nameTypeDecl.remove(tVar.value) + } + if (hadLoopNameClass) { + nameObjClass[tVar.value] = prevLoopNameClass!! + } else { + nameObjClass.remove(tVar.value) + } resolutionSink?.exitScope(cc.currentPos()) slotPlanStack.removeLast() } @@ -9164,7 +9218,6 @@ class Compiler( varTypeDecl = inferred } } - if (isDelegate && initialExpression != null) { ensureDelegateType(initialExpression) val lazyClass = resolveClassByName("lazy") diff --git a/lynglib/src/commonTest/kotlin/CoroutinesTest.kt b/lynglib/src/commonTest/kotlin/CoroutinesTest.kt index 19c5475..d7d485c 100644 --- a/lynglib/src/commonTest/kotlin/CoroutinesTest.kt +++ b/lynglib/src/commonTest/kotlin/CoroutinesTest.kt @@ -209,4 +209,26 @@ class TestCoroutines { // }.toList()) """.trimIndent()) } + + @Test + fun testInferenceList() = runTest { + eval(""" + import lyng.time + + val d1 = launch { + delay(1000.milliseconds) + "Task A finished" + } + val d2 = launch { + delay(500.milliseconds) + "Task B finished" + } + val foo = [d1, d2] + for (d in foo) { + d.await() + println(d) + } + + """.trimIndent()) + } } diff --git a/lynglib/src/commonTest/kotlin/ScriptTest.kt b/lynglib/src/commonTest/kotlin/ScriptTest.kt index 86aaf68..54a8c36 100644 --- a/lynglib/src/commonTest/kotlin/ScriptTest.kt +++ b/lynglib/src/commonTest/kotlin/ScriptTest.kt @@ -2050,9 +2050,9 @@ class ScriptTest { fun nationalCharsTest() = runTest { eval( """ - fun сумма_ряда(x, погрешность=0.0001, f) { + fun сумма_ряда(x, погрешность=0.001, f) { var сумма = 0 - for( n in 1..100000) { + for( n in 1..5000) { val следующая_сумма = сумма + f(x, n) if( n > 1 && abs(следующая_сумма - сумма) < погрешность ) break следующая_сумма diff --git a/lynglib/src/commonTest/kotlin/net/sergeych/lyng/ComplexModuleTest.kt b/lynglib/src/commonTest/kotlin/net/sergeych/lyng/ComplexModuleTest.kt index a60f749..c38461e 100644 --- a/lynglib/src/commonTest/kotlin/net/sergeych/lyng/ComplexModuleTest.kt +++ b/lynglib/src/commonTest/kotlin/net/sergeych/lyng/ComplexModuleTest.kt @@ -97,15 +97,15 @@ class ComplexModuleTest { assert( 5 + 1.d.i is Complex ) assert( 5.d + 1.i is Complex ) assert( 5.d + 2.d.i is Complex ) - assertEquals("0.0+1.0i", 1.d.i.toString()) - assertEquals("1.0+0.0i", 1.d.re.toString()) + assert(1.d.i.toString() in ["0.0+1.0i", "0+1i"]) + assert(1.d.re.toString() in ["1.0+0.0i", "1+0i"]) var c = 1 + 2.i assert(c is Complex) - assertEquals("1.0+2.0i", c.toString()) + assert(c.toString() in ["1.0+2.0i", "1+2i"]) c = 1.d + 2.i - assertEquals("1.0+2.0i", c.toString()) + assert(c.toString() in ["1.0+2.0i", "1+2i"]) """.trimIndent() ) } diff --git a/site/src/jsMain/resources/index.html b/site/src/jsMain/resources/index.html index bdda07d..df36bd8 100644 --- a/site/src/jsMain/resources/index.html +++ b/site/src/jsMain/resources/index.html @@ -413,6 +413,9 @@ + @@ -492,6 +495,8 @@ var activeLink = null; if (!hash || hash === '#' || hash === '#/') { activeLink = document.querySelector('#topbarNav .nav-link[data-route="home"]'); + } else if (hash.startsWith('#/docs/downloads.md')) { + activeLink = document.querySelector('#topbarNav .nav-link[data-route="downloads"]'); } else if (hash.startsWith('#/docs/') || hash.startsWith('#/authors')) { // Mark Docs menu root as active activeLink = document.querySelector('#topbarNav .nav-link.dropdown-toggle');