Add Lyng deferred cancellation support
This commit is contained in:
1 parent
3e338f3d53
commit
311cf6ee44
8 files changed
+180
-66
No files matched your search
@@ -343,6 +343,73 @@ class Compiler(
|
||||
}
|
||||
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 {
|
||||
while (cc.hasNext()) {
|
||||
val t = cc.next()
|
||||
@@ -355,66 +422,11 @@ class Compiler(
|
||||
Token.Type.RBRACKET -> if (bracketDepth > 0) bracketDepth--
|
||||
Token.Type.ID -> if (depth == 0) {
|
||||
if (parenDepth > 0 || bracketDepth > 0) continue
|
||||
when (t.value) {
|
||||
"fun", "fn" -> {
|
||||
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, 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)
|
||||
}
|
||||
}
|
||||
if (t.value == "extern") {
|
||||
handleTopLevelKeyword(nextNonWs())
|
||||
continue
|
||||
}
|
||||
handleTopLevelKeyword(t)
|
||||
}
|
||||
else -> {}
|
||||
}
|
||||
@@ -9070,7 +9082,10 @@ class Compiler(
|
||||
val effectiveEqToken = if (isProperty) null else eqToken
|
||||
|
||||
// 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) {
|
||||
SymbolKind.MEMBER
|
||||
} else {
|
||||
|
||||
@@ -540,7 +540,8 @@ private fun buildStdlibDocs(): List<MiniDecl> {
|
||||
|
||||
// Concurrency helpers
|
||||
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(
|
||||
name = "launch",
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
package net.sergeych.lyng.obj
|
||||
|
||||
import kotlinx.coroutines.Deferred
|
||||
import kotlin.coroutines.cancellation.CancellationException as KotlinCancellationException
|
||||
import net.sergeych.lyng.Scope
|
||||
import net.sergeych.lyng.miniast.addFnDoc
|
||||
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")
|
||||
}
|
||||
}.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(
|
||||
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"),
|
||||
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(
|
||||
name = "isCompleted",
|
||||
doc = "Whether this deferred has completed (successfully or with an error).",
|
||||
@@ -66,4 +82,3 @@ open class ObjDeferred(val deferred: Deferred<Obj>): Obj() {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -278,6 +278,7 @@ open class ObjException(
|
||||
"AssertionFailedException",
|
||||
"ClassCastException",
|
||||
"IndexOutOfBoundsException",
|
||||
"CancellationException",
|
||||
"IllegalArgumentException",
|
||||
"IllegalStateException",
|
||||
"NoSuchElementException",
|
||||
@@ -311,6 +312,9 @@ class ObjClassCastException(scope: Scope, message: String) : ObjException("Class
|
||||
class ObjIndexOutOfBoundsException(scope: Scope, message: String = "index out of bounds") :
|
||||
ObjException("IndexOutOfBoundsException", scope, message)
|
||||
|
||||
class ObjCancellationException(scope: Scope, message: String = "cancelled") :
|
||||
ObjException("CancellationException", scope, message)
|
||||
|
||||
class ObjIllegalArgumentException(scope: Scope, message: String = "illegal argument") :
|
||||
ObjException("IllegalArgumentException", scope, message)
|
||||
|
||||
|
||||
@@ -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
|
||||
fun testMutex() = runTest {
|
||||
eval(
|
||||
|
||||
Reference in new issue
Block a user