v1.5.5 release
This commit is contained in:
parent
14214e91e1
commit
9735774efd
52
CHANGELOG.md
52
CHANGELOG.md
@ -9,24 +9,46 @@ History note:
|
|||||||
|
|
||||||
## Unreleased
|
## Unreleased
|
||||||
|
|
||||||
### Database access
|
- No unreleased entries yet.
|
||||||
- Added the portable `lyng.io.db` SQL contract and the first concrete provider, `lyng.io.db.sqlite`.
|
|
||||||
- Added SQLite support on JVM and Linux Native with:
|
|
||||||
- generic `openDatabase("sqlite:...")` dispatch
|
|
||||||
- typed `openSqlite(...)` helper
|
|
||||||
- real nested transactions via savepoints
|
|
||||||
- generated keys through `ExecutionResult.getGeneratedKeys()`
|
|
||||||
- strict schema-driven value conversion for `Bool`, `Decimal`, `Date`, `DateTime`, and `Instant`
|
|
||||||
- documented option handling for `readOnly`, `createIfMissing`, `foreignKeys`, and `busyTimeoutMillis`
|
|
||||||
- Added public docs for database usage and SQLite provider behavior.
|
|
||||||
|
|
||||||
### Time
|
## 1.5.5 (2026-04-23)
|
||||||
- Added `Date` to `lyng.time` and the core library as a first-class calendar-date type.
|
|
||||||
- Added `Instant.toDate(...)`, `DateTime.date`, `DateTime.toDate()`, `Date.toDateTime(...)`, and related date arithmetic.
|
### Concurrency and collections
|
||||||
- Added docs, stdlib reference updates, serialization support, and comprehensive tests for `Date`.
|
- Added coroutine coordination primitives and helpers for everyday parallel code:
|
||||||
|
- `Channel` for coroutine-to-coroutine communication
|
||||||
|
- `LaunchPool` for bounded-concurrency task execution
|
||||||
|
- `Iterable<Deferred>.joinAll()` to await a whole collection of deferreds in input order
|
||||||
|
- `CompletableDeferred.completeExceptionally(...)` and `Deferred.cancelAndJoin()`
|
||||||
|
- Added docs and examples for the new concurrency APIs, including `joinAll()` coverage in iterable and parallelism references.
|
||||||
|
|
||||||
|
### Database and time APIs
|
||||||
|
- Added the portable `lyng.io.db` SQL contract and the first concrete providers:
|
||||||
|
- `lyng.io.db.sqlite` on JVM and Linux Native
|
||||||
|
- `lyng.io.db.jdbc` on JVM
|
||||||
|
- Added SQLite/JDBC release hardening:
|
||||||
|
- nested transactions via savepoints
|
||||||
|
- detached materialized rows
|
||||||
|
- generated-key support through `ExecutionResult.getGeneratedKeys()`
|
||||||
|
- schema-driven value conversion for `Bool`, `Decimal`, `Date`, `DateTime`, and `Instant`
|
||||||
|
- portable SQLite linker/deployment fixes and documented runtime options
|
||||||
|
- Added `Date` to `lyng.time` and the core runtime as a first-class calendar-date type, plus conversions and arithmetic across `Instant`, `DateTime`, and `Date`.
|
||||||
|
|
||||||
|
### Language, stdlib, and tooling
|
||||||
|
- Added extensions on singleton `object` declarations, including object-scoped indexer overrides for bracket syntax.
|
||||||
|
- Added backtick string literals and formatter support.
|
||||||
|
- Added `lyng.legacy_digest` for SHA-1 compatibility work, `String.replace`, and `buffer.base64std`.
|
||||||
|
- Improved CLI/runtime behavior with `atExit` shutdown handlers, native release-binary work, and follow-up CLI packaging/import fixes.
|
||||||
|
- Expanded docs across the tutorial, stdlib references, database docs, networking docs, and release notes.
|
||||||
|
|
||||||
|
### Runtime/compiler stability and performance
|
||||||
|
- Extended exact-call and higher-order lambda inlining through the bytecode compiler, including compiled fast paths for simple lambdas, wrappers, captures, and common higher-order helpers.
|
||||||
|
- Fixed import caching and class/object bytecode dispatch on JVM.
|
||||||
|
- Fixed immutable `val` compound assignments so true mutating `*Assign` operations continue to work while fallback reassignments report the correct read-only error.
|
||||||
|
- Fixed closure/capture and import regressions across launched loops, singleton/object extensions, aliasing, transitive re-exports, and immutable capture escaping.
|
||||||
|
- Improved list-fill/list-append fast paths, nullable-let inference, Decimal/Complex interop, and related regression coverage.
|
||||||
|
|
||||||
### Release notes
|
### Release notes
|
||||||
- Full `:lyngio:jvmTest` and `:lyngio:linuxX64Test` pass on the release tree after SQLite hardening.
|
- Release metadata, homepage samples, docs, and README now point to `1.5.5`.
|
||||||
|
|
||||||
## 1.5.4 (2026-04-03)
|
## 1.5.4 (2026-04-03)
|
||||||
|
|
||||||
|
|||||||
@ -48,7 +48,7 @@ assertEquals(A.E.One, A.One)
|
|||||||
|
|
||||||
- [Language home](https://lynglang.com)
|
- [Language home](https://lynglang.com)
|
||||||
- [introduction and tutorial](docs/tutorial.md) - start here please
|
- [introduction and tutorial](docs/tutorial.md) - start here please
|
||||||
- [Latest release notes (1.5.4)](docs/whats_new.md)
|
- [Latest release notes (1.5.5)](docs/whats_new.md)
|
||||||
- [What's New in 1.5](docs/whats_new_1_5.md)
|
- [What's New in 1.5](docs/whats_new_1_5.md)
|
||||||
- [Testing and Assertions](docs/Testing.md)
|
- [Testing and Assertions](docs/Testing.md)
|
||||||
- [Filesystem and Processes (lyngio)](docs/lyngio.md)
|
- [Filesystem and Processes (lyngio)](docs/lyngio.md)
|
||||||
@ -66,7 +66,7 @@ assertEquals(A.E.One, A.One)
|
|||||||
### Add dependency to your project
|
### Add dependency to your project
|
||||||
|
|
||||||
```kotlin
|
```kotlin
|
||||||
val lyngVersion = "1.5.4"
|
val lyngVersion = "1.5.5"
|
||||||
|
|
||||||
repositories {
|
repositories {
|
||||||
// ...
|
// ...
|
||||||
@ -186,7 +186,7 @@ Designed to add scripting to kotlin multiplatform application in easy and effici
|
|||||||
|
|
||||||
# Language Roadmap
|
# Language Roadmap
|
||||||
|
|
||||||
The current stable release is **v1.5.4**: the 1.5 cycle is feature-complete, compiler/runtime stabilization work is in, and the language, tooling, and site are aligned around the current release.
|
The current stable release is **v1.5.5**: the 1.5 cycle now includes the database/date/concurrency additions as well as the latest compiler/runtime stabilization work, and the language, tooling, and site are aligned around this release.
|
||||||
|
|
||||||
Ready features:
|
Ready features:
|
||||||
|
|
||||||
|
|||||||
@ -55,6 +55,23 @@ Here is the sample:
|
|||||||
assertEquals( (1..3).joinToString { it * 10 }, "10 20 30")
|
assertEquals( (1..3).joinToString { it * 10 }, "10 20 30")
|
||||||
>>> void
|
>>> void
|
||||||
|
|
||||||
|
## joinAll
|
||||||
|
|
||||||
|
`joinAll()` is an `Iterable<Deferred>` helper that awaits every deferred in iteration order and returns a `List`
|
||||||
|
with the collected results.
|
||||||
|
|
||||||
|
val jobs = (1..4).map { n ->
|
||||||
|
launch { n * n }
|
||||||
|
}
|
||||||
|
assertEquals([1, 4, 9, 16], jobs.joinAll())
|
||||||
|
>>> void
|
||||||
|
|
||||||
|
Notes:
|
||||||
|
|
||||||
|
- it does not start any task by itself; it only awaits the deferreds already present in the iterable.
|
||||||
|
- awaiting happens in iteration order, so the result list keeps the same order as the input iterable.
|
||||||
|
- if any deferred fails or was cancelled, that `await()` error is propagated from `joinAll()`.
|
||||||
|
|
||||||
## `sum` and `sumOf`
|
## `sum` and `sumOf`
|
||||||
|
|
||||||
These, again, does the thing:
|
These, again, does the thing:
|
||||||
@ -184,6 +201,7 @@ Search for the first element that satisfies the given predicate:
|
|||||||
| sortedWith(comparator) | sort using a comparator that compares elements (1) |
|
| sortedWith(comparator) | sort using a comparator that compares elements (1) |
|
||||||
| sortedBy(predicate) | sort by comparing results of the predicate function |
|
| sortedBy(predicate) | sort by comparing results of the predicate function |
|
||||||
| joinToString(s,t) | convert iterable to string, see (2) |
|
| joinToString(s,t) | convert iterable to string, see (2) |
|
||||||
|
| joinAll() | for `Iterable<Deferred>`, await all items in order and collect results to [List] |
|
||||||
| reversed() | create a list containing items from this in reverse order |
|
| reversed() | create a list containing items from this in reverse order |
|
||||||
| shuffled() | create a list of shuffled elements |
|
| shuffled() | create a list of shuffled elements |
|
||||||
|
|
||||||
|
|||||||
@ -73,13 +73,13 @@ pool.closeAndJoin()
|
|||||||
|
|
||||||
## Collecting all results
|
## Collecting all results
|
||||||
|
|
||||||
`launch` returns a `Deferred`, so you can collect results via `map`:
|
`launch` returns a `Deferred`, so you can collect results with `joinAll()`:
|
||||||
|
|
||||||
```lyng
|
```lyng
|
||||||
val pool = LaunchPool(4)
|
val pool = LaunchPool(4)
|
||||||
val jobs = (1..10).map { n -> pool.launch { n * n } }
|
val jobs = (1..10).map { n -> pool.launch { n * n } }
|
||||||
pool.closeAndJoin()
|
pool.closeAndJoin()
|
||||||
val results = jobs.map { (it as Deferred).await() }
|
val results = jobs.joinAll()
|
||||||
// results == [1, 4, 9, 16, 25, 36, 49, 64, 81, 100]
|
// results == [1, 4, 9, 16, 25, 36, 49, 64, 81, 100]
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@ -18,6 +18,7 @@ Sources: `lynglib/src/commonMain/kotlin/net/sergeych/lyng/Script.kt`, `lynglib/s
|
|||||||
- Async/concurrency: `launch`, `yield`, `flow`, `delay`.
|
- Async/concurrency: `launch`, `yield`, `flow`, `delay`.
|
||||||
- `Deferred.cancel()` cancels an active task.
|
- `Deferred.cancel()` cancels an active task.
|
||||||
- `Deferred.await()` throws `CancellationException` if that task was cancelled.
|
- `Deferred.await()` throws `CancellationException` if that task was cancelled.
|
||||||
|
- `Iterable<Deferred>.joinAll()` awaits every deferred in iteration order and returns a `List` of results.
|
||||||
- Math: `floor`, `ceil`, `round`, `sin`, `cos`, `tan`, `asin`, `acos`, `atan`, `sinh`, `cosh`, `tanh`, `asinh`, `acosh`, `atanh`, `exp`, `ln`, `log10`, `log2`, `pow`, `sqrt`, `abs`, `clamp`.
|
- Math: `floor`, `ceil`, `round`, `sin`, `cos`, `tan`, `asin`, `acos`, `atan`, `sinh`, `cosh`, `tanh`, `asinh`, `acosh`, `atanh`, `exp`, `ln`, `log10`, `log2`, `pow`, `sqrt`, `abs`, `clamp`.
|
||||||
- These helpers also accept `lyng.decimal.Decimal`.
|
- These helpers also accept `lyng.decimal.Decimal`.
|
||||||
- Exact Decimal path today: `abs`, `floor`, `ceil`, `round`, and `pow` with integral exponent.
|
- Exact Decimal path today: `abs`, `floor`, `ceil`, `round`, and `pow` with integral exponent.
|
||||||
|
|||||||
@ -36,6 +36,17 @@ This example shows how to launch a coroutine with `launch` which returns [Deferr
|
|||||||
|
|
||||||
Launch has the only argument which should be a callable (lambda usually) that is run in parallel (or cooperatively in parallel), and return anything as the result.
|
Launch has the only argument which should be a callable (lambda usually) that is run in parallel (or cooperatively in parallel), and return anything as the result.
|
||||||
|
|
||||||
|
When you have an iterable of deferreds, use `joinAll()` to await all of them and collect results in input order:
|
||||||
|
|
||||||
|
val jobs = (1..4).map { n ->
|
||||||
|
launch {
|
||||||
|
delay(1)
|
||||||
|
n * 10
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assertEquals([10, 20, 30, 40], jobs.joinAll())
|
||||||
|
>>> void
|
||||||
|
|
||||||
If you no longer need the result, cancel the deferred. Awaiting a cancelled deferred throws `CancellationException`:
|
If you no longer need the result, cancel the deferred. Awaiting a cancelled deferred throws `CancellationException`:
|
||||||
|
|
||||||
var reached = false
|
var reached = false
|
||||||
@ -269,7 +280,7 @@ val jobs = (1..20).map { n ->
|
|||||||
}
|
}
|
||||||
pool.closeAndJoin() // wait for all tasks to complete
|
pool.closeAndJoin() // wait for all tasks to complete
|
||||||
|
|
||||||
val results = jobs.map { (it as Deferred).await() }
|
val results = jobs.joinAll()
|
||||||
```
|
```
|
||||||
|
|
||||||
Exceptions thrown inside a submitted lambda are captured in the returned `Deferred` and do not crash the pool, so other tasks continue running normally.
|
Exceptions thrown inside a submitted lambda are captured in the returned `Deferred` and do not crash the pool, so other tasks continue running normally.
|
||||||
|
|||||||
@ -1,26 +1,29 @@
|
|||||||
# What's New in Lyng
|
# What's New in Lyng
|
||||||
|
|
||||||
This document highlights the current Lyng release, **1.5.4**, and the broader additions from the 1.5 cycle.
|
This document highlights the current Lyng release, **1.5.5**, and the broader additions from the 1.5 cycle.
|
||||||
It is intentionally user-facing: new language features, new modules, new tools, and the practical things you can build with them.
|
It is intentionally user-facing: new language features, new modules, new tools, and the practical things you can build with them.
|
||||||
For a programmer-focused migration summary across 1.5.x, see `docs/whats_new_1_5.md`.
|
For a programmer-focused migration summary across 1.5.x, see `docs/whats_new_1_5.md`.
|
||||||
|
|
||||||
## Release 1.5.4 Highlights
|
## Release 1.5.5 Highlights
|
||||||
|
|
||||||
- `1.5.4` is the stabilization release for the 1.5 feature set.
|
- `1.5.5` extends the 1.5 line with practical database APIs, first-class calendar dates, and better coroutine building blocks.
|
||||||
- The 1.5 line now brings together richer ranges and loops, interpolation, math modules, immutable and observable collections, richer `lyngio`, and much better CLI/IDE support.
|
- The 1.5 line now brings together richer ranges and loops, interpolation, math modules, immutable and observable collections, richer `lyngio`, and much better CLI/IDE support.
|
||||||
- `1.5.4` specifically fixes user-visible issues around decimal arithmetic, mixed numeric flows, list behavior, and observable list hooks.
|
- `1.5.5` adds `Channel`, `LaunchPool`, and `joinAll()` so coroutine-heavy scripts can coordinate work more directly.
|
||||||
- `1.5.4` also fixes extension-member registration for named singleton `object` declarations, so `fun X.foo()` and `val X.bar` now work as expected.
|
- `1.5.5` adds `Date`, the portable `lyng.io.db` layer, SQLite/JDBC providers, and a compatibility `lyng.legacy_digest` module.
|
||||||
- `1.5.4` also lets named singleton `object` declarations use scoped indexer extensions with bracket syntax, so patterns like `Storage["name"]` can be implemented with `override fun Storage.getAt(...)` / `putAt(...)`.
|
- `1.5.5` also continues runtime/compiler hardening with better import dispatch, faster exact lambda calls, and correct `val +=`/`-=` behavior for mutating types versus real reassignment.
|
||||||
- The docs, homepage samples, and release metadata now point at the current stable version.
|
- The docs, homepage samples, and release metadata now point at the current stable version.
|
||||||
|
|
||||||
## User Highlights Across 1.5.x
|
## User Highlights Across 1.5.x
|
||||||
|
|
||||||
- Descending ranges and loops with `downTo` / `downUntil`
|
- Descending ranges and loops with `downTo` / `downUntil`
|
||||||
- String interpolation with `$name` and `${expr}`
|
- String interpolation with `$name` and `${expr}`
|
||||||
|
- Backtick string literals for raw-ish string text
|
||||||
- Decimal arithmetic, matrices/vectors, and complex numbers
|
- Decimal arithmetic, matrices/vectors, and complex numbers
|
||||||
- Calendar `Date` support in `lyng.time`
|
- Calendar `Date` support in `lyng.time`
|
||||||
|
- `Channel`, `LaunchPool`, and `joinAll()` for coroutine workflows
|
||||||
- Immutable collections and opt-in `ObservableList`
|
- Immutable collections and opt-in `ObservableList`
|
||||||
- Rich `lyngio` modules for SQLite databases, console, HTTP, WebSocket, TCP, and UDP
|
- Rich `lyngio` modules for SQLite/JDBC databases, console, HTTP, WebSocket, TCP, and UDP
|
||||||
|
- Legacy SHA-1 compatibility helpers in `lyng.legacy_digest`
|
||||||
- CLI improvements including the built-in formatter `lyng fmt`
|
- CLI improvements including the built-in formatter `lyng fmt`
|
||||||
- Better IDE support and stronger docs around the released feature set
|
- Better IDE support and stronger docs around the released feature set
|
||||||
|
|
||||||
@ -324,7 +327,7 @@ Singleton objects are declared using the `object` keyword. They provide a conven
|
|||||||
|
|
||||||
```lyng
|
```lyng
|
||||||
object Config {
|
object Config {
|
||||||
val version = "1.5.4"
|
val version = "1.5.5"
|
||||||
fun show() = println("Config version: " + version)
|
fun show() = println("Config version: " + version)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
0
examples/error2.lyng
Normal file
0
examples/error2.lyng
Normal file
@ -21,7 +21,7 @@ import org.jetbrains.kotlin.gradle.ExperimentalWasmDsl
|
|||||||
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
|
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
|
||||||
|
|
||||||
group = "net.sergeych"
|
group = "net.sergeych"
|
||||||
version = "1.5.5-SNAPSHOT"
|
version = "1.5.5"
|
||||||
|
|
||||||
// Removed legacy buildscript classpath declarations; plugins are applied via the plugins DSL below
|
// Removed legacy buildscript classpath declarations; plugins are applied via the plugins DSL below
|
||||||
|
|
||||||
|
|||||||
@ -2881,14 +2881,18 @@ class BytecodeCompiler(
|
|||||||
val slot = resolveCapturedOwnerScopeSlot(localTarget) ?: resolveSlot(localTarget) ?: return null
|
val slot = resolveCapturedOwnerScopeSlot(localTarget) ?: resolveSlot(localTarget) ?: return null
|
||||||
val targetType = slotTypes[slot] ?: SlotType.OBJ
|
val targetType = slotTypes[slot] ?: SlotType.OBJ
|
||||||
if (!localTarget.isMutable) {
|
if (!localTarget.isMutable) {
|
||||||
if (targetType != SlotType.OBJ && targetType != SlotType.UNKNOWN) return compileEvalRef(ref)
|
|
||||||
val rhs = compileRef(ref.value) ?: return compileEvalRef(ref)
|
val rhs = compileRef(ref.value) ?: return compileEvalRef(ref)
|
||||||
|
if (!shouldUseAssignOpForImmutableLocal(slot, targetType, ref.op)) {
|
||||||
|
emitImmutableLocalReassignError(localTarget.name, localTarget.pos())
|
||||||
|
return rhs
|
||||||
|
}
|
||||||
val rhsObj = ensureObjSlot(rhs)
|
val rhsObj = ensureObjSlot(rhs)
|
||||||
val nameId = builder.addConst(BytecodeConst.StringVal(localTarget.name))
|
val nameId = builder.addConst(BytecodeConst.StringVal(localTarget.name))
|
||||||
if (nameId > 0xFFFF) return compileEvalRef(ref)
|
if (nameId > 0xFFFF) return compileEvalRef(ref)
|
||||||
val dst = allocSlot()
|
val dst = allocSlot()
|
||||||
builder.emit(Opcode.ASSIGN_OP_OBJ, ref.op.ordinal, slot, rhsObj.slot, dst, nameId)
|
builder.emit(Opcode.ASSIGN_OP_OBJ, ref.op.ordinal, slot, rhsObj.slot, dst, nameId)
|
||||||
updateSlotType(dst, SlotType.OBJ)
|
updateSlotType(dst, SlotType.OBJ)
|
||||||
|
slotObjClass[slot]?.let { slotObjClass[dst] = it }
|
||||||
return CompiledValue(dst, SlotType.OBJ)
|
return CompiledValue(dst, SlotType.OBJ)
|
||||||
}
|
}
|
||||||
var rhs = compileRef(ref.value) ?: return compileEvalRef(ref)
|
var rhs = compileRef(ref.value) ?: return compileEvalRef(ref)
|
||||||
@ -8446,6 +8450,38 @@ class BytecodeCompiler(
|
|||||||
builder.emit(Opcode.THROW, posId, msgSlot)
|
builder.emit(Opcode.THROW, posId, msgSlot)
|
||||||
return msgSlot
|
return msgSlot
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun assignOpMethodName(op: BinOp): String? = when (op) {
|
||||||
|
BinOp.PLUS -> "plusAssign"
|
||||||
|
BinOp.MINUS -> "minusAssign"
|
||||||
|
BinOp.STAR -> "mulAssign"
|
||||||
|
BinOp.SLASH -> "divAssign"
|
||||||
|
BinOp.PERCENT -> "modAssign"
|
||||||
|
else -> null
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun knownBuiltinAssignOpSupport(cls: ObjClass, op: BinOp): Boolean? = when (cls) {
|
||||||
|
ObjList.type -> op == BinOp.PLUS || op == BinOp.MINUS
|
||||||
|
ObjObservableList.type -> op == BinOp.PLUS || op == BinOp.MINUS
|
||||||
|
ObjSet.type -> op == BinOp.PLUS
|
||||||
|
ObjMap.type -> op == BinOp.PLUS
|
||||||
|
ObjRingBuffer.type -> op == BinOp.PLUS
|
||||||
|
else -> null
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun shouldUseAssignOpForImmutableLocal(slot: Int, targetType: SlotType, op: BinOp): Boolean {
|
||||||
|
return when (targetType) {
|
||||||
|
SlotType.INT, SlotType.REAL, SlotType.BOOL -> false
|
||||||
|
SlotType.OBJ, SlotType.UNKNOWN -> {
|
||||||
|
val cls = slotObjClass[slot] ?: return true
|
||||||
|
val methodName = assignOpMethodName(op)
|
||||||
|
if (methodName != null && cls.getInstanceMemberOrNull(methodName, includeStatic = false) != null) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
knownBuiltinAssignOpSupport(cls, op) ?: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
private fun binaryLeft(ref: BinaryOpRef): ObjRef = ref.left
|
private fun binaryLeft(ref: BinaryOpRef): ObjRef = ref.left
|
||||||
private fun binaryRight(ref: BinaryOpRef): ObjRef = ref.right
|
private fun binaryRight(ref: BinaryOpRef): ObjRef = ref.right
|
||||||
private fun binaryOp(ref: BinaryOpRef): BinOp = ref.op
|
private fun binaryOp(ref: BinaryOpRef): BinOp = ref.op
|
||||||
|
|||||||
@ -104,14 +104,14 @@ class WebsiteSamplesTest {
|
|||||||
val name = "Lyng"
|
val name = "Lyng"
|
||||||
val base = { id:, name: } // Shorthand for id: id, name: name
|
val base = { id:, name: } // Shorthand for id: id, name: name
|
||||||
|
|
||||||
val full = { ...base, version: "1.5.4", status: "stable" }
|
val full = { ...base, version: "1.5.5", status: "stable" }
|
||||||
full
|
full
|
||||||
""".trimIndent())
|
""".trimIndent())
|
||||||
assertTrue(result is ObjMap)
|
assertTrue(result is ObjMap)
|
||||||
val m = result.map
|
val m = result.map
|
||||||
assertEquals(101L, (m[ObjString("id")] as ObjInt).value)
|
assertEquals(101L, (m[ObjString("id")] as ObjInt).value)
|
||||||
assertEquals("Lyng", (m[ObjString("name")] as ObjString).value)
|
assertEquals("Lyng", (m[ObjString("name")] as ObjString).value)
|
||||||
assertEquals("1.5.4", (m[ObjString("version")] as ObjString).value)
|
assertEquals("1.5.5", (m[ObjString("version")] as ObjString).value)
|
||||||
assertEquals("stable", (m[ObjString("status")] as ObjString).value)
|
assertEquals("stable", (m[ObjString("status")] as ObjString).value)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -19,6 +19,8 @@ package net.sergeych.lyng
|
|||||||
|
|
||||||
import kotlinx.coroutines.test.runTest
|
import kotlinx.coroutines.test.runTest
|
||||||
import kotlin.test.Test
|
import kotlin.test.Test
|
||||||
|
import kotlin.test.assertContains
|
||||||
|
import kotlin.test.assertFailsWith
|
||||||
|
|
||||||
class OperatorOverloadingTest {
|
class OperatorOverloadingTest {
|
||||||
@Test
|
@Test
|
||||||
@ -63,6 +65,24 @@ class OperatorOverloadingTest {
|
|||||||
""".trimIndent())
|
""".trimIndent())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testBuiltinListPlusAssignOnVal() = runTest {
|
||||||
|
eval("""
|
||||||
|
val list = [1, 2]
|
||||||
|
list += 3
|
||||||
|
assertEquals([1, 2, 3], list)
|
||||||
|
""".trimIndent())
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testBuiltinListMinusAssignOnVal() = runTest {
|
||||||
|
eval("""
|
||||||
|
val list = [1, 2, 3]
|
||||||
|
list -= 2
|
||||||
|
assertEquals([1, 3], list)
|
||||||
|
""".trimIndent())
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun testPlusAssignFallback() = runTest {
|
fun testPlusAssignFallback() = runTest {
|
||||||
eval("""
|
eval("""
|
||||||
@ -76,6 +96,130 @@ class OperatorOverloadingTest {
|
|||||||
""".trimIndent())
|
""".trimIndent())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testPlusAssignFallbackOnValReportsReadonlyError() = runTest {
|
||||||
|
val ex = assertFailsWith<ScriptError> {
|
||||||
|
eval("""
|
||||||
|
class Vector(var x: Int, var y: Int) {
|
||||||
|
fun plus(other: Vector) = Vector(this.x + other.x, this.y + other.y)
|
||||||
|
fun equals(other: Vector) = this.x == other.x && this.y == other.y
|
||||||
|
}
|
||||||
|
val v = Vector(1, 2)
|
||||||
|
v += Vector(3, 4)
|
||||||
|
""".trimIndent())
|
||||||
|
}
|
||||||
|
|
||||||
|
assertContains(ex.errorMessage, "can't reassign val v")
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testMinusAssignOverloadingOnVal() = runTest {
|
||||||
|
eval("""
|
||||||
|
class Counter(var n: Int) {
|
||||||
|
fun minusAssign(x: Int) { this.n = this.n - x }
|
||||||
|
}
|
||||||
|
val c = Counter(10)
|
||||||
|
c -= 3
|
||||||
|
assertEquals(7, c.n)
|
||||||
|
""".trimIndent())
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testMinusAssignFallbackOnValReportsReadonlyError() = runTest {
|
||||||
|
val ex = assertFailsWith<ScriptError> {
|
||||||
|
eval("""
|
||||||
|
class Counter(var n: Int) {
|
||||||
|
fun minus(x: Int) = Counter(this.n - x)
|
||||||
|
}
|
||||||
|
val c = Counter(10)
|
||||||
|
c -= 3
|
||||||
|
""".trimIndent())
|
||||||
|
}
|
||||||
|
|
||||||
|
assertContains(ex.errorMessage, "can't reassign val c")
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testMulAssignOverloadingOnVal() = runTest {
|
||||||
|
eval("""
|
||||||
|
class Counter(var n: Int) {
|
||||||
|
fun mulAssign(x: Int) { this.n = this.n * x }
|
||||||
|
}
|
||||||
|
val c = Counter(10)
|
||||||
|
c *= 3
|
||||||
|
assertEquals(30, c.n)
|
||||||
|
""".trimIndent())
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testMulAssignFallbackOnValReportsReadonlyError() = runTest {
|
||||||
|
val ex = assertFailsWith<ScriptError> {
|
||||||
|
eval("""
|
||||||
|
class Counter(var n: Int) {
|
||||||
|
fun times(x: Int) = Counter(this.n * x)
|
||||||
|
}
|
||||||
|
val c = Counter(10)
|
||||||
|
c *= 3
|
||||||
|
""".trimIndent())
|
||||||
|
}
|
||||||
|
|
||||||
|
assertContains(ex.errorMessage, "can't reassign val c")
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testDivAssignOverloadingOnVal() = runTest {
|
||||||
|
eval("""
|
||||||
|
class Counter(var n: Int) {
|
||||||
|
fun divAssign(x: Int) { this.n = this.n / x }
|
||||||
|
}
|
||||||
|
val c = Counter(21)
|
||||||
|
c /= 3
|
||||||
|
assertEquals(7, c.n)
|
||||||
|
""".trimIndent())
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testDivAssignFallbackOnValReportsReadonlyError() = runTest {
|
||||||
|
val ex = assertFailsWith<ScriptError> {
|
||||||
|
eval("""
|
||||||
|
class Counter(var n: Int) {
|
||||||
|
fun div(x: Int) = Counter(this.n / x)
|
||||||
|
}
|
||||||
|
val c = Counter(21)
|
||||||
|
c /= 3
|
||||||
|
""".trimIndent())
|
||||||
|
}
|
||||||
|
|
||||||
|
assertContains(ex.errorMessage, "can't reassign val c")
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testModAssignOverloadingOnVal() = runTest {
|
||||||
|
eval("""
|
||||||
|
class Counter(var n: Int) {
|
||||||
|
fun modAssign(x: Int) { this.n = this.n % x }
|
||||||
|
}
|
||||||
|
val c = Counter(23)
|
||||||
|
c %= 5
|
||||||
|
assertEquals(3, c.n)
|
||||||
|
""".trimIndent())
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testModAssignFallbackOnValReportsReadonlyError() = runTest {
|
||||||
|
val ex = assertFailsWith<ScriptError> {
|
||||||
|
eval("""
|
||||||
|
class Counter(var n: Int) {
|
||||||
|
fun mod(x: Int) = Counter(this.n % x)
|
||||||
|
}
|
||||||
|
val c = Counter(23)
|
||||||
|
c %= 5
|
||||||
|
""".trimIndent())
|
||||||
|
}
|
||||||
|
|
||||||
|
assertContains(ex.errorMessage, "can't reassign val c")
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun testCompareOverloading() = runTest {
|
fun testCompareOverloading() = runTest {
|
||||||
eval("""
|
eval("""
|
||||||
|
|||||||
@ -159,7 +159,7 @@ fun HomePage() {
|
|||||||
val id = 101
|
val id = 101
|
||||||
val name = "Lyng"
|
val name = "Lyng"
|
||||||
val base = { id:, name: }
|
val base = { id:, name: }
|
||||||
val full = { ...base, version: "1.5.4", status: "stable", tags: ["typed", "portable"] }
|
val full = { ...base, version: "1.5.5", status: "stable", tags: ["typed", "portable"] }
|
||||||
|
|
||||||
println(full)
|
println(full)
|
||||||
""".trimIndent()
|
""".trimIndent()
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user