diff --git a/CHANGELOG.md b/CHANGELOG.md index d0977b8..2da2c2a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ### Unreleased +- Language: stdlib improvements + - Added `with(self, block)` function to `root.lyng` which executes a block with `this` set to the provided object. - Language: Abstract Classes and Interfaces - Support for `abstract` modifier on classes, methods, and variables. - Introduced `interface` as a synonym for `abstract class`, supporting full state (constructors, fields, `init` blocks) and implementation by parts via MI. diff --git a/docs/tutorial.md b/docs/tutorial.md index 0edfe26..fafff51 100644 --- a/docs/tutorial.md +++ b/docs/tutorial.md @@ -298,6 +298,16 @@ It works much like `also`, but is executed in the context of the source object: assertEquals(p, Point(2,3)) >>> void +## with + +Sets `this` to the first argument and executes the block. Returns the value returned by the block: + + class Point(x,y) + val p = Point(1,2) + val sum = with(p) { x + y } + assertEquals(3, sum) + >>> void + ## run Executes a block after it returning the value passed by the block. for example, can be used with elvis operator: @@ -1519,7 +1529,7 @@ See [math functions](math.md). Other general purpose functions are: | flow {} | create flow sequence, see [parallelism] | | delay, launch, yield | see [parallelism] | | cached(builder) | [Lazy evaluation with `cached`](#lazy-evaluation-with-cached) | -| let, also, apply, run | see above, flow controls | +| let, also, apply, run, with | see above, flow controls | (1) : `fn` is optional lambda returning string message to add to exception string. diff --git a/lynglib/build.gradle.kts b/lynglib/build.gradle.kts index dffe95f..162385d 100644 --- a/lynglib/build.gradle.kts +++ b/lynglib/build.gradle.kts @@ -17,6 +17,7 @@ import com.codingfeline.buildkonfig.compiler.FieldSpec.Type.STRING import org.jetbrains.kotlin.gradle.ExperimentalKotlinGradlePluginApi +import org.jetbrains.kotlin.gradle.ExperimentalWasmDsl import org.jetbrains.kotlin.gradle.dsl.JvmTarget group = "net.sergeych" @@ -65,11 +66,11 @@ kotlin { browser() nodejs() } -// @OptIn(ExperimentalWasmDsl::class) -// wasmJs() { -// browser() -// nodejs() -// } + @OptIn(ExperimentalWasmDsl::class) + wasmJs() { + browser() + nodejs() + } // Suppress Beta warning for expect/actual classes across all targets targets.configureEach { diff --git a/lynglib/src/commonTest/kotlin/StdlibTest.kt b/lynglib/src/commonTest/kotlin/StdlibTest.kt index 43523c2..e9319da 100644 --- a/lynglib/src/commonTest/kotlin/StdlibTest.kt +++ b/lynglib/src/commonTest/kotlin/StdlibTest.kt @@ -1,5 +1,5 @@ /* - * Copyright 2025 Sergey S. Chernov real.sergeych@gmail.com + * Copyright 2026 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. @@ -113,4 +113,22 @@ class StdlibTest { assertEquals(5, (1..10).toList().count { it % 2 == 1 } ) """) } + + @Test + fun testWith() = runTest { + eval(""" + class Person(val name, var age) + val p = Person("Alice", 30) + + val result = with(p) { + assertEquals("Alice", name) + assertEquals(30, age) + age = 31 + "done" + } + + assertEquals("done", result) + assertEquals(31, p.age) + """.trimIndent()) + } } \ No newline at end of file diff --git a/lynglib/src/commonTest/kotlin/net/sergeych/lyng/DelegationTest.kt b/lynglib/src/commonTest/kotlin/net/sergeych/lyng/DelegationTest.kt index 90b5a57..c114bc2 100644 --- a/lynglib/src/commonTest/kotlin/net/sergeych/lyng/DelegationTest.kt +++ b/lynglib/src/commonTest/kotlin/net/sergeych/lyng/DelegationTest.kt @@ -287,4 +287,40 @@ class DelegationTest { """.trimIndent()) } + @Test + fun testThisInLazy() = runTest { + eval(""" + class A { + val numbers = [1,2,3] + val tags by lazy { this.numbers } + } + class B { + val a by lazy { A() } + val test by lazy { a.tags + [4] } + } + assertEquals( [1,2,3], A().tags) + assertEquals( [1,2,3,4], B().test) + """) + } + + @Test + fun testScopeInLazy() = runTest { + val s1 = Script.newScope() + s1.eval(""" + class A { + val tags by lazy { GLOBAL_NUMBERS } + } + """.trimIndent()) + // on the same scope, it will see my GLOBAL_NUMBERS: + s1.eval(""" + val GLOBAL_NUMBERS = [1,2,3] + class B { + val a by lazy { A() } + val test by lazy { a.tags + [4] } + } + assertEquals( [1,2,3], A().tags) + assertEquals( [1,2,3,4], B().test) + """) + } + } diff --git a/lynglib/stdlib/lyng/root.lyng b/lynglib/stdlib/lyng/root.lyng index 5eb0e55..4645b58 100644 --- a/lynglib/stdlib/lyng/root.lyng +++ b/lynglib/stdlib/lyng/root.lyng @@ -310,6 +310,16 @@ interface Delegate { fun bind(name, access, thisRef) = this } +/* + Executes the block with `this` set to self and + returns what the block returns. +*/ +fun with(self, block) { + var result = Unset + self.apply { result = block() } + result +} + /* Standard implementation of a lazy-initialized property delegate. The provided creator lambda is called once on the first access to compute the value. @@ -325,7 +335,7 @@ class lazy(creatorParam) : Delegate { } override fun getValue(thisRef, name) { - if (value == Unset) value = creator() + if (value == Unset) value = with(thisRef,creator) value } }