Compare commits

...

2 Commits

12 changed files with 287 additions and 98 deletions

View File

@ -14,6 +14,8 @@ Sources: `lynglib/src/commonMain/kotlin/net/sergeych/lyng/Script.kt`, `lynglib/s
- Assertions/tests: `assert`, `assertEquals`/`assertEqual`, `assertNotEquals`, `assertThrows`. - Assertions/tests: `assert`, `assertEquals`/`assertEqual`, `assertNotEquals`, `assertThrows`.
- Preconditions: `require`, `check`. - Preconditions: `require`, `check`.
- Async/concurrency: `launch`, `yield`, `flow`, `delay`. - Async/concurrency: `launch`, `yield`, `flow`, `delay`.
- `Deferred.cancel()` cancels an active task.
- `Deferred.await()` throws `CancellationException` if that task was cancelled.
- 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.
@ -26,13 +28,14 @@ Sources: `lynglib/src/commonMain/kotlin/net/sergeych/lyng/Script.kt`, `lynglib/s
- Collections/types: `Iterable`, `Iterator`, `Collection`, `Array`, `List`, `ImmutableList`, `Set`, `ImmutableSet`, `Map`, `ImmutableMap`, `MapEntry`, `Range`, `RingBuffer`. - Collections/types: `Iterable`, `Iterator`, `Collection`, `Array`, `List`, `ImmutableList`, `Set`, `ImmutableSet`, `Map`, `ImmutableMap`, `MapEntry`, `Range`, `RingBuffer`.
- Random: singleton `Random` and class `SeededRandom`. - Random: singleton `Random` and class `SeededRandom`.
- Async types: `Deferred`, `CompletableDeferred`, `Mutex`, `Flow`, `FlowBuilder`. - Async types: `Deferred`, `CompletableDeferred`, `Mutex`, `Flow`, `FlowBuilder`.
- Async exception: `CancellationException`.
- Delegation types: `Delegate`, `DelegateContext`. - Delegation types: `Delegate`, `DelegateContext`.
- Regex types: `Regex`, `RegexMatch`. - Regex types: `Regex`, `RegexMatch`.
- Also present: `Math.PI` namespace constant. - Also present: `Math.PI` namespace constant.
## 4. `lyng.stdlib` Module Surface (from `root.lyng`) ## 4. `lyng.stdlib` Module Surface (from `root.lyng`)
### 4.1 Extern class declarations ### 4.1 Extern class declarations
- Exceptions/delegation base: `Exception`, `IllegalArgumentException`, `NotImplementedException`, `Delegate`. - Exceptions/delegation base: `Exception`, `CancellationException`, `IllegalArgumentException`, `NotImplementedException`, `Delegate`.
- Collections and iterables: `Iterable<T>`, `Iterator<T>`, `Collection<T>`, `Array<T>`, `List<T>`, `ImmutableList<T>`, `Set<T>`, `ImmutableSet<T>`, `Map<K,V>`, `ImmutableMap<K,V>`, `MapEntry<K,V>`, `RingBuffer<T>`. - Collections and iterables: `Iterable<T>`, `Iterator<T>`, `Collection<T>`, `Array<T>`, `List<T>`, `ImmutableList<T>`, `Set<T>`, `ImmutableSet<T>`, `Map<K,V>`, `ImmutableMap<K,V>`, `MapEntry<K,V>`, `RingBuffer<T>`.
- Host iterator bridge: `KotlinIterator<T>`. - Host iterator bridge: `KotlinIterator<T>`.
- Random APIs: `extern object Random`, `extern class SeededRandom`. - Random APIs: `extern object Random`, `extern class SeededRandom`.

View File

@ -32,10 +32,25 @@ Depending on the platform, these coroutines may be executed on different CPU and
assert(xIsCalled) assert(xIsCalled)
>>> void >>> void
This example shows how to launch a coroutine with `launch` which returns [Deferred] instance, the latter have ways to await for the coroutine completion and retrieve possible result. This example shows how to launch a coroutine with `launch` which returns [Deferred] instance, the latter have ways to await for the coroutine completion, cancel it if it is no longer needed, and retrieve possible 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. 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.
If you no longer need the result, cancel the deferred. Awaiting a cancelled deferred throws `CancellationException`:
var reached = false
val work = launch {
delay(100)
reached = true
"ok"
}
work.cancel()
assertThrows(CancellationException) { work.await() }
assert(work.isCancelled)
assert(!work.isActive)
assert(!reached)
>>> void
## Synchronization: Mutex ## Synchronization: Mutex
Suppose we have a resource, that could be used concurrently, a counter in our case. If we won't protect it, concurrent usage cause RC, Race Condition, providing wrong result: Suppose we have a resource, that could be used concurrently, a counter in our case. If we won't protect it, concurrent usage cause RC, Race Condition, providing wrong result:

View File

@ -84,6 +84,7 @@ fun calculateDepth(
h = hNew h = hNew
iter++ iter++
println("iter: $iter: $h")
} }
// Не сошлось за maxIter // Не сошлось за maxIter
@ -97,7 +98,7 @@ val d = 0.1 // м (10 см)
val depth = calculateDepth(T, m, d) val depth = calculateDepth(T, m, d)
if (depth != null) { if (depth != null) {
println("Глубина: %.2f м".format(depth)) println("Глубина: %.2f м"(depth))
} else { } else {
println("Расчёт не сошёлся") println("Расчёт не сошёлся")
} }

View File

@ -343,6 +343,73 @@ class Compiler(
} }
return t return t
} }
fun handleTopLevelKeyword(keyword: Token): Boolean {
when (keyword.value) {
"fun", "fn" -> {
val nameToken = nextNonWs()
if (nameToken.type != Token.Type.ID) return true
val afterName = cc.peekNextNonWhitespace()
if (afterName.type == Token.Type.DOT) {
cc.nextNonWhitespace()
val actual = cc.nextNonWhitespace()
if (actual.type == Token.Type.ID) {
extensionNames.add(actual.value)
registerExtensionName(nameToken.value, actual.value)
declareSlotNameIn(plan, extensionCallableName(nameToken.value, actual.value), isMutable = false, isDelegated = false)
moduleDeclaredNames.add(extensionCallableName(nameToken.value, actual.value))
}
return true
}
declareSlotNameIn(plan, nameToken.value, isMutable = false, isDelegated = false)
moduleDeclaredNames.add(nameToken.value)
return true
}
"val", "var" -> {
val nameToken = nextNonWs()
if (nameToken.type != Token.Type.ID) return true
val afterName = cc.peekNextNonWhitespace()
if (afterName.type == Token.Type.DOT) {
cc.nextNonWhitespace()
val actual = cc.nextNonWhitespace()
if (actual.type == Token.Type.ID) {
extensionNames.add(actual.value)
registerExtensionName(nameToken.value, actual.value)
declareSlotNameIn(plan, extensionPropertyGetterName(nameToken.value, actual.value), isMutable = false, isDelegated = false)
moduleDeclaredNames.add(extensionPropertyGetterName(nameToken.value, actual.value))
if (keyword.value == "var") {
declareSlotNameIn(plan, extensionPropertySetterName(nameToken.value, actual.value), isMutable = false, isDelegated = false)
moduleDeclaredNames.add(extensionPropertySetterName(nameToken.value, actual.value))
}
}
return true
}
declareSlotNameIn(plan, nameToken.value, isMutable = keyword.value == "var", isDelegated = false)
moduleDeclaredNames.add(nameToken.value)
predeclaredTopLevelValueNames.add(nameToken.value)
return true
}
"class", "object" -> {
val nameToken = nextNonWs()
if (nameToken.type == Token.Type.ID) {
declareSlotNameIn(plan, nameToken.value, isMutable = false, isDelegated = false)
scopeSeedNames.add(nameToken.value)
moduleDeclaredNames.add(nameToken.value)
}
return true
}
"enum" -> {
val next = nextNonWs()
val nameToken = if (next.type == Token.Type.ID && next.value == "class") nextNonWs() else next
if (nameToken.type == Token.Type.ID) {
declareSlotNameIn(plan, nameToken.value, isMutable = false, isDelegated = false)
scopeSeedNames.add(nameToken.value)
moduleDeclaredNames.add(nameToken.value)
}
return true
}
}
return false
}
try { try {
while (cc.hasNext()) { while (cc.hasNext()) {
val t = cc.next() val t = cc.next()
@ -355,66 +422,11 @@ class Compiler(
Token.Type.RBRACKET -> if (bracketDepth > 0) bracketDepth-- Token.Type.RBRACKET -> if (bracketDepth > 0) bracketDepth--
Token.Type.ID -> if (depth == 0) { Token.Type.ID -> if (depth == 0) {
if (parenDepth > 0 || bracketDepth > 0) continue if (parenDepth > 0 || bracketDepth > 0) continue
when (t.value) { if (t.value == "extern") {
"fun", "fn" -> { handleTopLevelKeyword(nextNonWs())
val nameToken = nextNonWs() continue
if (nameToken.type != Token.Type.ID) continue
val afterName = cc.peekNextNonWhitespace()
if (afterName.type == Token.Type.DOT) {
cc.nextNonWhitespace()
val actual = cc.nextNonWhitespace()
if (actual.type == Token.Type.ID) {
extensionNames.add(actual.value)
registerExtensionName(nameToken.value, actual.value)
declareSlotNameIn(plan, extensionCallableName(nameToken.value, actual.value), isMutable = false, isDelegated = false)
moduleDeclaredNames.add(extensionCallableName(nameToken.value, actual.value))
}
continue
}
declareSlotNameIn(plan, nameToken.value, isMutable = false, isDelegated = false)
moduleDeclaredNames.add(nameToken.value)
}
"val", "var" -> {
val nameToken = nextNonWs()
if (nameToken.type != Token.Type.ID) continue
val afterName = cc.peekNextNonWhitespace()
if (afterName.type == Token.Type.DOT) {
cc.nextNonWhitespace()
val actual = cc.nextNonWhitespace()
if (actual.type == Token.Type.ID) {
extensionNames.add(actual.value)
registerExtensionName(nameToken.value, actual.value)
declareSlotNameIn(plan, extensionPropertyGetterName(nameToken.value, actual.value), isMutable = false, isDelegated = false)
moduleDeclaredNames.add(extensionPropertyGetterName(nameToken.value, actual.value))
if (t.value == "var") {
declareSlotNameIn(plan, extensionPropertySetterName(nameToken.value, actual.value), isMutable = false, isDelegated = false)
moduleDeclaredNames.add(extensionPropertySetterName(nameToken.value, actual.value))
}
}
continue
}
declareSlotNameIn(plan, nameToken.value, isMutable = t.value == "var", isDelegated = false)
moduleDeclaredNames.add(nameToken.value)
predeclaredTopLevelValueNames.add(nameToken.value)
}
"class", "object" -> {
val nameToken = nextNonWs()
if (nameToken.type == Token.Type.ID) {
declareSlotNameIn(plan, nameToken.value, isMutable = false, isDelegated = false)
scopeSeedNames.add(nameToken.value)
moduleDeclaredNames.add(nameToken.value)
}
}
"enum" -> {
val next = nextNonWs()
val nameToken = if (next.type == Token.Type.ID && next.value == "class") nextNonWs() else next
if (nameToken.type == Token.Type.ID) {
declareSlotNameIn(plan, nameToken.value, isMutable = false, isDelegated = false)
scopeSeedNames.add(nameToken.value)
moduleDeclaredNames.add(nameToken.value)
}
}
} }
handleTopLevelKeyword(t)
} }
else -> {} else -> {}
} }
@ -1296,23 +1308,11 @@ class Compiler(
} }
} }
if (seedRecord != null) { if (seedRecord != null) {
val value = seedRecord.value seedImportTypeMetadata(name, seedRecord)
if (!nameObjClass.containsKey(name)) {
when (value) {
is ObjClass -> nameObjClass[name] = value
is ObjInstance -> nameObjClass[name] = value.objClass
}
}
return ImportBindingResolution(ImportBinding(name, ImportBindingSource.Seed), seedRecord) return ImportBindingResolution(ImportBinding(name, ImportBindingSource.Seed), seedRecord)
} }
if (rootRecord != null) { if (rootRecord != null) {
val value = rootRecord.value seedImportTypeMetadata(name, rootRecord)
if (!nameObjClass.containsKey(name)) {
when (value) {
is ObjClass -> nameObjClass[name] = value
is ObjInstance -> nameObjClass[name] = value.objClass
}
}
return ImportBindingResolution(ImportBinding(name, ImportBindingSource.Root), rootRecord) return ImportBindingResolution(ImportBinding(name, ImportBindingSource.Root), rootRecord)
} }
if (moduleMatches.isEmpty()) return null if (moduleMatches.isEmpty()) return null
@ -1327,13 +1327,7 @@ class Compiler(
val candidates = byOrigin[origin] ?: mutableListOf() val candidates = byOrigin[origin] ?: mutableListOf()
val preferred = candidates.firstOrNull { it.first.scope.packageName == origin } ?: candidates.first() val preferred = candidates.firstOrNull { it.first.scope.packageName == origin } ?: candidates.first()
val binding = ImportBinding(name, ImportBindingSource.Module(origin, preferred.first.pos)) val binding = ImportBinding(name, ImportBindingSource.Module(origin, preferred.first.pos))
val value = preferred.second.value seedImportTypeMetadata(name, preferred.second)
if (!nameObjClass.containsKey(name)) {
when (value) {
is ObjClass -> nameObjClass[name] = value
is ObjInstance -> nameObjClass[name] = value.objClass
}
}
return ImportBindingResolution(binding, preferred.second) return ImportBindingResolution(binding, preferred.second)
} }
val moduleNames = moduleMatches.keys.toList() val moduleNames = moduleMatches.keys.toList()
@ -1341,14 +1335,24 @@ class Compiler(
} }
val (module, record) = moduleMatches.values.first() val (module, record) = moduleMatches.values.first()
val binding = ImportBinding(name, ImportBindingSource.Module(module.scope.packageName, module.pos)) val binding = ImportBinding(name, ImportBindingSource.Module(module.scope.packageName, module.pos))
val value = record.value seedImportTypeMetadata(name, record)
return ImportBindingResolution(binding, record)
}
private fun seedImportTypeMetadata(name: String, record: ObjRecord) {
if (record.typeDecl != null && nameTypeDecl[name] == null) {
nameTypeDecl[name] = record.typeDecl
}
if (!nameObjClass.containsKey(name)) { if (!nameObjClass.containsKey(name)) {
when (value) { record.typeDecl?.let { resolveTypeDeclObjClass(it) }?.let {
nameObjClass[name] = it
return
}
when (val value = record.value) {
is ObjClass -> nameObjClass[name] = value is ObjClass -> nameObjClass[name] = value
is ObjInstance -> nameObjClass[name] = value.objClass is ObjInstance -> nameObjClass[name] = value.objClass
} }
} }
return ImportBindingResolution(binding, record)
} }
private fun collectModuleRecordMatches( private fun collectModuleRecordMatches(
@ -4336,14 +4340,19 @@ class Compiler(
is MapLiteralRef -> inferMapLiteralTypeDecl(ref) is MapLiteralRef -> inferMapLiteralTypeDecl(ref)
is ConstRef -> inferTypeDeclFromConst(ref.constValue) is ConstRef -> inferTypeDeclFromConst(ref.constValue)
is CallRef -> { is CallRef -> {
val targetDecl = resolveReceiverTypeDecl(ref.target)
val targetName = when (val target = ref.target) { val targetName = when (val target = ref.target) {
is LocalVarRef -> target.name is LocalVarRef -> target.name
is FastLocalVarRef -> target.name is FastLocalVarRef -> target.name
is LocalSlotRef -> target.name is LocalSlotRef -> target.name
else -> null else -> null
} }
if (targetDecl is TypeDecl.Function) {
return targetDecl.returnType
}
if (targetName != null) { if (targetName != null) {
callableReturnTypeDeclByName[targetName]?.let { return it } callableReturnTypeDeclByName[targetName]?.let { return it }
(seedTypeDeclByName(targetName) as? TypeDecl.Function)?.let { return it.returnType }
} }
inferCallReturnClass(ref)?.let { TypeDecl.Simple(it.className, false) } inferCallReturnClass(ref)?.let { TypeDecl.Simple(it.className, false) }
?: run { ?: run {
@ -4677,6 +4686,17 @@ class Compiler(
return null return null
} }
private fun lookupLocalTypeDeclByName(name: String): TypeDecl? {
val slotLoc = lookupSlotLocation(name, includeModule = true) ?: return null
return slotTypeDeclByScopeId[slotLoc.scopeId]?.get(slotLoc.slot)
}
private fun lookupLocalObjClassByName(name: String): ObjClass? {
val slotLoc = lookupSlotLocation(name, includeModule = true) ?: return null
return slotTypeByScopeId[slotLoc.scopeId]?.get(slotLoc.slot)
?: slotTypeDeclByScopeId[slotLoc.scopeId]?.get(slotLoc.slot)?.let { resolveTypeDeclObjClass(it) }
}
private fun resolveReceiverTypeDecl(ref: ObjRef): TypeDecl? { private fun resolveReceiverTypeDecl(ref: ObjRef): TypeDecl? {
return when (ref) { return when (ref) {
is LocalSlotRef -> { is LocalSlotRef -> {
@ -4686,8 +4706,12 @@ class Compiler(
?: nameTypeDecl[ref.name] ?: nameTypeDecl[ref.name]
?: seedTypeDeclByName(ref.name) ?: seedTypeDeclByName(ref.name)
} }
is LocalVarRef -> nameTypeDecl[ref.name] ?: seedTypeDeclByName(ref.name) is LocalVarRef -> nameTypeDecl[ref.name]
is FastLocalVarRef -> nameTypeDecl[ref.name] ?: seedTypeDeclByName(ref.name) ?: lookupLocalTypeDeclByName(ref.name)
?: seedTypeDeclByName(ref.name)
is FastLocalVarRef -> nameTypeDecl[ref.name]
?: lookupLocalTypeDeclByName(ref.name)
?: seedTypeDeclByName(ref.name)
is FieldRef -> { is FieldRef -> {
val targetDecl = resolveReceiverTypeDecl(ref.target) ?: return null val targetDecl = resolveReceiverTypeDecl(ref.target) ?: return null
val targetClass = resolveTypeDeclObjClass(targetDecl) ?: resolveReceiverClassForMember(ref.target) val targetClass = resolveTypeDeclObjClass(targetDecl) ?: resolveReceiverClassForMember(ref.target)
@ -4733,11 +4757,13 @@ class Compiler(
is LocalVarRef -> nameObjClass[ref.name] is LocalVarRef -> nameObjClass[ref.name]
?.takeIf { it == ObjDynamic.type } ?.takeIf { it == ObjDynamic.type }
?: nameObjClass[ref.name] ?: nameObjClass[ref.name]
?: lookupLocalObjClassByName(ref.name)
?: nameTypeDecl[ref.name]?.let { resolveTypeDeclObjClass(it) } ?: nameTypeDecl[ref.name]?.let { resolveTypeDeclObjClass(it) }
?: resolveClassByName(ref.name) ?: resolveClassByName(ref.name)
is FastLocalVarRef -> nameObjClass[ref.name] is FastLocalVarRef -> nameObjClass[ref.name]
?.takeIf { it == ObjDynamic.type } ?.takeIf { it == ObjDynamic.type }
?: nameObjClass[ref.name] ?: nameObjClass[ref.name]
?: lookupLocalObjClassByName(ref.name)
?: nameTypeDecl[ref.name]?.let { resolveTypeDeclObjClass(it) } ?: nameTypeDecl[ref.name]?.let { resolveTypeDeclObjClass(it) }
?: resolveClassByName(ref.name) ?: resolveClassByName(ref.name)
is ClassScopeMemberRef -> { is ClassScopeMemberRef -> {
@ -9056,7 +9082,10 @@ class Compiler(
val effectiveEqToken = if (isProperty) null else eqToken val effectiveEqToken = if (isProperty) null else eqToken
// Register the local name at compile time so that subsequent identifiers can be emitted as fast locals // Register the local name at compile time so that subsequent identifiers can be emitted as fast locals
if (!isStatic && declaringClassNameCaptured == null && !actualExtern) declareLocalName(name, isMutable) val isTopLevelExtern = actualExtern && declaringClassNameCaptured == null && slotPlanStack.size == 1
if (!isStatic && declaringClassNameCaptured == null && (!actualExtern || isTopLevelExtern)) {
declareLocalName(name, isMutable)
}
val declKind = if (codeContexts.lastOrNull() is CodeContext.ClassBody) { val declKind = if (codeContexts.lastOrNull() is CodeContext.ClassBody) {
SymbolKind.MEMBER SymbolKind.MEMBER
} else { } else {

View File

@ -395,9 +395,20 @@ class Script(
requireExactCount(2) requireExactCount(2)
decimalAwarePow(args[0], args[1]) decimalAwarePow(args[0], args[1])
} }
addFn("sqrt") { addItem(
decimalAwareUnaryMath(args.firstAndOnly(), fallback = ::sqrt) "sqrt",
} false,
ObjExternCallable.fromBridge {
decimalAwareUnaryMath(args.firstAndOnly(), fallback = ::sqrt)
},
recordType = ObjRecord.Type.Fun,
typeDecl = TypeDecl.Function(
receiver = null,
params = listOf(TypeDecl.TypeAny),
returnType = TypeDecl.Simple("Real", false),
nullable = false
)
)
addFn("abs") { addFn("abs") {
val x = args.firstAndOnly() val x = args.firstAndOnly()
if (x is ObjInt) ObjInt(x.value.absoluteValue) if (x is ObjInt) ObjInt(x.value.absoluteValue)

View File

@ -540,7 +540,8 @@ private fun buildStdlibDocs(): List<MiniDecl> {
// Concurrency helpers // Concurrency helpers
mod.classDoc(name = "Deferred", doc = "Represents a value that will be available in the future.", bases = listOf(type("Obj"))) { mod.classDoc(name = "Deferred", doc = "Represents a value that will be available in the future.", bases = listOf(type("Obj"))) {
method(name = "await", doc = "Suspend until the value is available and return it.") method(name = "cancel", doc = "Cancel the deferred if it is still active.")
method(name = "await", doc = "Suspend until the value is available and return it. Throws `CancellationException` if cancelled.")
} }
mod.funDoc( mod.funDoc(
name = "launch", name = "launch",

View File

@ -18,6 +18,7 @@
package net.sergeych.lyng.obj package net.sergeych.lyng.obj
import kotlinx.coroutines.Deferred import kotlinx.coroutines.Deferred
import kotlin.coroutines.cancellation.CancellationException as KotlinCancellationException
import net.sergeych.lyng.Scope import net.sergeych.lyng.Scope
import net.sergeych.lyng.miniast.addFnDoc import net.sergeych.lyng.miniast.addFnDoc
import net.sergeych.lyng.miniast.addPropertyDoc import net.sergeych.lyng.miniast.addPropertyDoc
@ -33,12 +34,27 @@ open class ObjDeferred(val deferred: Deferred<Obj>): Obj() {
scope.raiseError("Deferred constructor is not directly callable") scope.raiseError("Deferred constructor is not directly callable")
} }
}.apply { }.apply {
addFnDoc(
name = "cancel",
doc = "Cancel this deferred if it is still active. Safe to call multiple times.",
returns = type("lyng.Void"),
moduleName = "lyng.stdlib"
) {
thisAs<ObjDeferred>().deferred.cancel()
ObjVoid
}
addFnDoc( addFnDoc(
name = "await", name = "await",
doc = "Suspend until completion and return the result value (or throw if failed).", doc = "Suspend until completion and return the result value. Throws `CancellationException` if cancelled.",
returns = type("lyng.Any"), returns = type("lyng.Any"),
moduleName = "lyng.stdlib" moduleName = "lyng.stdlib"
) { thisAs<ObjDeferred>().deferred.await() } ) {
try {
thisAs<ObjDeferred>().deferred.await()
} catch (e: KotlinCancellationException) {
requireScope().raiseError(ObjCancellationException(requireScope(), e.message ?: "Deferred was cancelled"))
}
}
addPropertyDoc( addPropertyDoc(
name = "isCompleted", name = "isCompleted",
doc = "Whether this deferred has completed (successfully or with an error).", doc = "Whether this deferred has completed (successfully or with an error).",
@ -66,4 +82,3 @@ open class ObjDeferred(val deferred: Deferred<Obj>): Obj() {
} }
} }
} }

View File

@ -278,6 +278,7 @@ open class ObjException(
"AssertionFailedException", "AssertionFailedException",
"ClassCastException", "ClassCastException",
"IndexOutOfBoundsException", "IndexOutOfBoundsException",
"CancellationException",
"IllegalArgumentException", "IllegalArgumentException",
"IllegalStateException", "IllegalStateException",
"NoSuchElementException", "NoSuchElementException",
@ -311,6 +312,9 @@ class ObjClassCastException(scope: Scope, message: String) : ObjException("Class
class ObjIndexOutOfBoundsException(scope: Scope, message: String = "index out of bounds") : class ObjIndexOutOfBoundsException(scope: Scope, message: String = "index out of bounds") :
ObjException("IndexOutOfBoundsException", scope, message) ObjException("IndexOutOfBoundsException", scope, message)
class ObjCancellationException(scope: Scope, message: String = "cancelled") :
ObjException("CancellationException", scope, message)
class ObjIllegalArgumentException(scope: Scope, message: String = "illegal argument") : class ObjIllegalArgumentException(scope: Scope, message: String = "illegal argument") :
ObjException("IllegalArgumentException", scope, message) ObjException("IllegalArgumentException", scope, message)

View File

@ -58,6 +58,30 @@ class TestCoroutines {
) )
} }
@Test
fun testDeferredCancel() = runTest {
eval(
"""
var reached = false
val d = launch {
delay(100)
reached = true
"ok"
}
d.cancel()
d.cancel()
assertThrows(CancellationException) { d.await() }
delay(150)
assert(d.isCancelled)
assert(!d.isActive)
assert(!reached)
""".trimIndent()
)
}
@Test @Test
fun testMutex() = runTest { fun testMutex() = runTest {
eval( eval(

View File

@ -0,0 +1,49 @@
/*
* 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.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
import kotlinx.coroutines.test.runTest
import net.sergeych.lyng.eval
import net.sergeych.lyng.obj.ObjBool
import kotlin.test.Test
import kotlin.test.assertEquals
class LocalRealMemberInferenceRegressionTest {
@Test
fun sqrtInitializedLocalVarKeepsRealReceiverTypeForMembers() = runTest {
val result = eval(
"""
fun probe(T: Real, c: Real, g: Real): Bool {
fun passthrough(h: Real): Real = h / c
val term = 1.0 + g * T / c
val sqrtTerm = sqrt(1.0 + 2.0 * g * T / c)
assert(sqrtTerm is Real)
assert(!sqrtTerm.isNaN())
var h = (c * c / g) * (term - sqrtTerm)
assert(passthrough(h) >= 0.0)
assert(h is Real)
!h.isNaN()
}
probe(6.0, 340.0, 9.81)
""".trimIndent()
)
assertEquals(ObjBool(true), result)
}
}

View File

@ -5576,7 +5576,7 @@ class ScriptTest {
val depth = calculateDepth(T, m, d) val depth = calculateDepth(T, m, d)
if (depth != null) { if (depth != null) {
println("Глубина: %.2f м".format(depth)) println("Глубина: %.2f м"(depth))
} else { } else {
println("Расчёт не сошёлся") println("Расчёт не сошёлся")
} }

View File

@ -1,11 +1,48 @@
package lyng.stdlib package lyng.stdlib
/* Launch `code` asynchronously and return its result handle. */
extern fun launch(code): Deferred
/* Yield execution so other scheduled coroutines can run. */
extern fun yield(): void
/* Build a lazy asynchronous sequence. */
extern fun flow(builder: FlowBuilder.()->void): Flow extern fun flow(builder: FlowBuilder.()->void): Flow
/* Built-in exception type. */ /* Built-in exception type. */
extern class Exception extern class Exception
extern class IllegalArgumentException extern class IllegalArgumentException
extern class NotImplementedException extern class NotImplementedException
/* Raised when an awaited asynchronous task was cancelled before producing a value. */
extern class CancellationException : Exception
/* A handle to a running asynchronous task. */
extern class Deferred {
/* Cancel the task if it is still active. Safe to call multiple times. */
fun cancel(): void
/* Suspend until the task finishes and return its value.
Throws `CancellationException` if the task was cancelled. */
fun await(): Object
/* True when the task has finished, failed, or otherwise reached a terminal state. */
val isCompleted: Bool
/* True while the task is still running and has not been cancelled. */
val isActive: Bool
/* True when the task was cancelled. */
val isCancelled: Bool
}
/* A deferred result that can be completed manually. */
extern class CompletableDeferred : Deferred {
fun complete(value: Object): void
}
/* Receiver passed into `flow { ... }` builders. */
extern class FlowBuilder {
fun emit(value: Object): void
}
/* A cold asynchronous iterable sequence. */
extern class Flow<T> : Iterable<T> {
}
extern class Delegate extern class Delegate
extern class Iterable<T> { extern class Iterable<T> {
fun iterator(): Iterator<T> fun iterator(): Iterator<T>
@ -87,7 +124,7 @@ extern class MapEntry<K,V> : Array<Object> {
extern fun abs(x: Object): Object extern fun abs(x: Object): Object
extern fun ln(x: Object): Object extern fun ln(x: Object): Object
extern fun pow(x: Object, y: Object): Object extern fun pow(x: Object, y: Object): Object
extern fun sqrt(x: Object): Object extern fun sqrt(x: Object): Real
extern fun clamp<T>(value: T, range: Range<T>): T extern fun clamp<T>(value: T, range: Range<T>): T
class SeededRandom { class SeededRandom {