Compare commits

..

4 Commits

10 changed files with 433 additions and 104 deletions

View File

@ -2031,8 +2031,6 @@ class Compiler(
}
}
data class WhenCase(val condition: Statement, val block: Statement)
private suspend fun parseWhenStatement(): Statement {
// has a value, when(value) ?
var t = cc.nextNonWhitespace()
@ -2044,7 +2042,6 @@ class Compiler(
if (t.type != Token.Type.LBRACE) throw ScriptError(t.pos, "when { ... } expected")
val cases = mutableListOf<WhenCase>()
var elseCase: Statement? = null
lateinit var whenValue: Obj
// there could be 0+ then clauses
// condition could be a value, in and is clauses:
@ -2053,9 +2050,8 @@ class Compiler(
// loop cases
outer@ while (true) {
var skipParseBody = false
val currentCondition = mutableListOf<Statement>()
val currentConditions = mutableListOf<WhenCondition>()
// loop conditions
while (true) {
@ -2064,31 +2060,16 @@ class Compiler(
when (t.type) {
Token.Type.IN,
Token.Type.NOTIN -> {
// we need a copy in the closure:
val isIn = t.type == Token.Type.IN
val negated = t.type == Token.Type.NOTIN
val container = parseExpression() ?: throw ScriptError(cc.currentPos(), "type expected")
val condPos = t.pos
currentCondition += object : Statement() {
override val pos: Pos = condPos
override suspend fun execute(scope: Scope): Obj {
val r = container.execute(scope).contains(scope, whenValue)
return ObjBool(if (isIn) r else !r)
}
}
currentConditions += WhenInCondition(container, negated, t.pos)
}
Token.Type.IS, Token.Type.NOTIS -> {
// we need a copy in the closure:
val isIn = t.type == Token.Type.IS
Token.Type.IS,
Token.Type.NOTIS -> {
val negated = t.type == Token.Type.NOTIS
val caseType = parseExpression() ?: throw ScriptError(cc.currentPos(), "type expected")
val condPos = t.pos
currentCondition += object : Statement() {
override val pos: Pos = condPos
override suspend fun execute(scope: Scope): Obj {
val r = whenValue.isInstanceOf(caseType.execute(scope))
return ObjBool(if (isIn) r else !r)
}
}
currentConditions += WhenIsCondition(caseType, negated, t.pos)
}
Token.Type.COMMA ->
@ -2117,13 +2098,7 @@ class Compiler(
cc.previous()
val x = parseExpression()
?: throw ScriptError(cc.currentPos(), "when case condition expected")
val condPos = t.pos
currentCondition += object : Statement() {
override val pos: Pos = condPos
override suspend fun execute(scope: Scope): Obj {
return ObjBool(x.execute(scope).compareTo(scope, whenValue) == 0)
}
}
currentConditions += WhenEqualsCondition(x, t.pos)
}
}
}
@ -2132,28 +2107,11 @@ class Compiler(
if (!skipParseBody) {
val block = parseStatement()?.let { unwrapBytecodeDeep(it) }
?: throw ScriptError(cc.currentPos(), "when case block expected")
for (c in currentCondition) cases += WhenCase(c, block)
cases += WhenCase(currentConditions, block)
}
}
val whenPos = t.pos
object : Statement() {
override val pos: Pos = whenPos
override suspend fun execute(scope: Scope): Obj {
var result: Obj = ObjVoid
// in / is and like uses whenValue from closure:
whenValue = value.execute(scope)
var found = false
for (c in cases) {
if (c.condition.execute(scope).toBool()) {
result = c.block.execute(scope)
found = true
break
}
}
if (!found && elseCase != null) result = elseCase.execute(scope)
return result
}
}
WhenStatement(value, cases, elseCase, whenPos)
} else {
// when { cond -> ... }
TODO("when without object is not yet implemented")
@ -3443,26 +3401,15 @@ class Compiler(
val names = mutableListOf<String>()
pattern.forEachVariable { names.add(it) }
return object : Statement() {
override val pos: Pos = start
override suspend fun execute(context: Scope): Obj {
val value = initialExpression.execute(context)
for (name in names) {
context.addItem(name, true, ObjVoid, visibility, isTransient = isTransient)
}
pattern.setAt(start, context, value)
if (!isMutable) {
for (name in names) {
val rec = context.objects[name]!!
val immutableRec = rec.copy(isMutable = false)
context.objects[name] = immutableRec
context.localBindings[name] = immutableRec
context.updateSlotFor(name, immutableRec)
}
}
return ObjVoid
}
}
return DestructuringVarDeclStatement(
pattern,
names,
initialExpression,
isMutable,
visibility,
isTransient,
start
)
}
if (nextToken.type != Token.Type.ID)

View File

@ -0,0 +1,49 @@
/*
* Copyright 2026 Sergey S. Chernov
*
* 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.
*/
package net.sergeych.lyng
import net.sergeych.lyng.obj.ListLiteralRef
import net.sergeych.lyng.obj.Obj
import net.sergeych.lyng.obj.ObjVoid
class DestructuringVarDeclStatement(
val pattern: ListLiteralRef,
val names: List<String>,
val initializer: Statement,
val isMutable: Boolean,
val visibility: Visibility,
val isTransient: Boolean,
override val pos: Pos,
) : Statement() {
override suspend fun execute(context: Scope): Obj {
val value = initializer.execute(context)
for (name in names) {
context.addItem(name, true, ObjVoid, visibility, isTransient = isTransient)
}
pattern.setAt(pos, context, value)
if (!isMutable) {
for (name in names) {
val rec = context.objects[name]!!
val immutableRec = rec.copy(isMutable = false)
context.objects[name] = immutableRec
context.localBindings[name] = immutableRec
context.updateSlotFor(name, immutableRec)
}
}
return ObjVoid
}
}

View File

@ -0,0 +1,76 @@
/*
* Copyright 2026 Sergey S. Chernov
*
* 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.
*/
package net.sergeych.lyng
import net.sergeych.lyng.obj.Obj
import net.sergeych.lyng.obj.ObjVoid
sealed class WhenCondition(open val expr: Statement, open val pos: Pos) {
abstract suspend fun matches(scope: Scope, value: Obj): Boolean
}
class WhenEqualsCondition(
override val expr: Statement,
override val pos: Pos,
) : WhenCondition(expr, pos) {
override suspend fun matches(scope: Scope, value: Obj): Boolean {
return expr.execute(scope).compareTo(scope, value) == 0
}
}
class WhenInCondition(
override val expr: Statement,
val negated: Boolean,
override val pos: Pos,
) : WhenCondition(expr, pos) {
override suspend fun matches(scope: Scope, value: Obj): Boolean {
val result = expr.execute(scope).contains(scope, value)
return if (negated) !result else result
}
}
class WhenIsCondition(
override val expr: Statement,
val negated: Boolean,
override val pos: Pos,
) : WhenCondition(expr, pos) {
override suspend fun matches(scope: Scope, value: Obj): Boolean {
val result = value.isInstanceOf(expr.execute(scope))
return if (negated) !result else result
}
}
data class WhenCase(val conditions: List<WhenCondition>, val block: Statement)
class WhenStatement(
val value: Statement,
val cases: List<WhenCase>,
val elseCase: Statement?,
override val pos: Pos,
) : Statement() {
override suspend fun execute(scope: Scope): Obj {
val whenValue = value.execute(scope)
for (case in cases) {
for (condition in case.conditions) {
if (condition.matches(scope, whenValue)) {
return case.block.execute(scope)
}
}
}
return elseCase?.execute(scope) ?: ObjVoid
}
}

View File

@ -17,6 +17,7 @@
package net.sergeych.lyng.bytecode
import net.sergeych.lyng.BlockStatement
import net.sergeych.lyng.DestructuringVarDeclStatement
import net.sergeych.lyng.ExpressionStatement
import net.sergeych.lyng.IfStatement
import net.sergeych.lyng.ParsedArgument
@ -24,6 +25,11 @@ import net.sergeych.lyng.Pos
import net.sergeych.lyng.Statement
import net.sergeych.lyng.ToBoolStatement
import net.sergeych.lyng.VarDeclStatement
import net.sergeych.lyng.WhenCondition
import net.sergeych.lyng.WhenEqualsCondition
import net.sergeych.lyng.WhenInCondition
import net.sergeych.lyng.WhenIsCondition
import net.sergeych.lyng.WhenStatement
import net.sergeych.lyng.obj.*
class BytecodeCompiler(
@ -64,6 +70,7 @@ class BytecodeCompiler(
val continueLabel: CmdBuilder.Label,
val breakFlagSlot: Int,
val resultSlot: Int?,
val hasIterator: Boolean,
)
fun compileStatement(name: String, stmt: net.sergeych.lyng.Statement): CmdFunction? {
@ -1515,6 +1522,96 @@ class BytecodeCompiler(
return CompiledValue(resultSlot, SlotType.OBJ)
}
private fun compileWhen(stmt: WhenStatement, wantResult: Boolean): CompiledValue? {
val subjectValue = compileStatementValueOrFallback(stmt.value) ?: return null
val subjectObj = ensureObjSlot(subjectValue)
val resultSlot = allocSlot()
if (wantResult) {
val voidId = builder.addConst(BytecodeConst.ObjRef(ObjVoid))
builder.emit(Opcode.CONST_OBJ, voidId, resultSlot)
updateSlotType(resultSlot, SlotType.OBJ)
}
val endLabel = builder.label()
for (case in stmt.cases) {
val caseLabel = builder.label()
val nextCaseLabel = builder.label()
for (cond in case.conditions) {
val condValue = compileWhenCondition(cond, subjectObj) ?: return null
builder.emit(
Opcode.JMP_IF_TRUE,
listOf(CmdBuilder.Operand.IntVal(condValue.slot), CmdBuilder.Operand.LabelRef(caseLabel))
)
}
builder.emit(Opcode.JMP, listOf(CmdBuilder.Operand.LabelRef(nextCaseLabel)))
builder.mark(caseLabel)
val bodyValue = compileStatementValueOrFallback(case.block, wantResult) ?: return null
if (wantResult) {
val bodyObj = ensureObjSlot(bodyValue)
builder.emit(Opcode.MOVE_OBJ, bodyObj.slot, resultSlot)
}
builder.emit(Opcode.JMP, listOf(CmdBuilder.Operand.LabelRef(endLabel)))
builder.mark(nextCaseLabel)
}
stmt.elseCase?.let {
val elseValue = compileStatementValueOrFallback(it, wantResult) ?: return null
if (wantResult) {
val elseObj = ensureObjSlot(elseValue)
builder.emit(Opcode.MOVE_OBJ, elseObj.slot, resultSlot)
}
}
builder.mark(endLabel)
return if (wantResult) {
updateSlotType(resultSlot, SlotType.OBJ)
CompiledValue(resultSlot, SlotType.OBJ)
} else {
subjectObj
}
}
private fun compileWhenCondition(cond: WhenCondition, subjectObj: CompiledValue): CompiledValue? {
val subject = ensureObjSlot(subjectObj)
return when (cond) {
is WhenEqualsCondition -> {
val expected = compileStatementValueOrFallback(cond.expr) ?: return null
val expectedObj = ensureObjSlot(expected)
val dst = allocSlot()
builder.emit(Opcode.CMP_EQ_OBJ, expectedObj.slot, subject.slot, dst)
updateSlotType(dst, SlotType.BOOL)
CompiledValue(dst, SlotType.BOOL)
}
is WhenInCondition -> {
val container = compileStatementValueOrFallback(cond.expr) ?: return null
val containerObj = ensureObjSlot(container)
val baseDst = allocSlot()
builder.emit(Opcode.CONTAINS_OBJ, containerObj.slot, subject.slot, baseDst)
updateSlotType(baseDst, SlotType.BOOL)
if (!cond.negated) {
CompiledValue(baseDst, SlotType.BOOL)
} else {
val neg = allocSlot()
builder.emit(Opcode.NOT_BOOL, baseDst, neg)
updateSlotType(neg, SlotType.BOOL)
CompiledValue(neg, SlotType.BOOL)
}
}
is WhenIsCondition -> {
val typeValue = compileStatementValueOrFallback(cond.expr) ?: return null
val typeObj = ensureObjSlot(typeValue)
val baseDst = allocSlot()
builder.emit(Opcode.CHECK_IS, subject.slot, typeObj.slot, baseDst)
updateSlotType(baseDst, SlotType.BOOL)
if (!cond.negated) {
CompiledValue(baseDst, SlotType.BOOL)
} else {
val neg = allocSlot()
builder.emit(Opcode.NOT_BOOL, baseDst, neg)
updateSlotType(neg, SlotType.BOOL)
CompiledValue(neg, SlotType.BOOL)
}
}
}
}
private fun ensureObjSlot(value: CompiledValue): CompiledValue {
if (value.type == SlotType.OBJ) return value
val dst = allocSlot()
@ -1878,11 +1975,13 @@ class BytecodeCompiler(
}
is BlockStatement -> emitBlock(target, true)
is VarDeclStatement -> emitVarDecl(target)
is DestructuringVarDeclStatement -> emitStatementEval(target)
is net.sergeych.lyng.ExtensionPropertyDeclStatement -> emitExtensionPropertyDecl(target)
is net.sergeych.lyng.ClassDeclStatement -> emitStatementEval(target)
is net.sergeych.lyng.FunctionDeclStatement -> emitStatementEval(target)
is net.sergeych.lyng.EnumDeclStatement -> emitStatementEval(target)
is net.sergeych.lyng.TryStatement -> emitStatementEval(target)
is net.sergeych.lyng.WhenStatement -> compileWhen(target, true)
is net.sergeych.lyng.BreakStatement -> compileBreak(target)
is net.sergeych.lyng.ContinueStatement -> compileContinue(target)
is net.sergeych.lyng.ReturnStatement -> compileReturn(target)
@ -1922,11 +2021,13 @@ class BytecodeCompiler(
}
is BlockStatement -> emitBlock(target, false)
is VarDeclStatement -> emitVarDecl(target)
is DestructuringVarDeclStatement -> emitStatementEval(target)
is net.sergeych.lyng.ExtensionPropertyDeclStatement -> emitExtensionPropertyDecl(target)
is net.sergeych.lyng.BreakStatement -> compileBreak(target)
is net.sergeych.lyng.ContinueStatement -> compileContinue(target)
is net.sergeych.lyng.ReturnStatement -> compileReturn(target)
is net.sergeych.lyng.ThrowStatement -> compileThrow(target)
is net.sergeych.lyng.WhenStatement -> compileWhen(target, false)
else -> {
emitFallbackStatement(target)
}
@ -2105,6 +2206,7 @@ class BytecodeCompiler(
val iterSlot = allocSlot()
val iteratorId = builder.addConst(BytecodeConst.StringVal("iterator"))
builder.emit(Opcode.CALL_VIRTUAL, sourceObj.slot, iteratorId, 0, 0, iterSlot)
builder.emit(Opcode.ITER_PUSH, iterSlot)
val breakFlagSlot = allocSlot()
val falseId = builder.addConst(BytecodeConst.Bool(false))
@ -2138,7 +2240,14 @@ class BytecodeCompiler(
updateSlotTypeByName(stmt.loopVarName, SlotType.OBJ)
loopStack.addLast(
LoopContext(stmt.label, endLabel, continueLabel, breakFlagSlot, if (wantResult) resultSlot else null)
LoopContext(
stmt.label,
endLabel,
continueLabel,
breakFlagSlot,
if (wantResult) resultSlot else null,
hasIterator = true
)
)
val bodyValue = compileLoopBody(stmt.body, wantResult) ?: return null
loopStack.removeLast()
@ -2150,6 +2259,13 @@ class BytecodeCompiler(
builder.emit(Opcode.JMP, listOf(CmdBuilder.Operand.LabelRef(loopLabel)))
builder.mark(endLabel)
val afterPop = builder.label()
builder.emit(
Opcode.JMP_IF_TRUE,
listOf(CmdBuilder.Operand.IntVal(breakFlagSlot), CmdBuilder.Operand.LabelRef(afterPop))
)
builder.emit(Opcode.ITER_POP)
builder.mark(afterPop)
if (stmt.elseStatement != null) {
val afterElse = builder.label()
builder.emit(
@ -2219,7 +2335,14 @@ class BytecodeCompiler(
updateSlotType(loopSlotId, SlotType.INT)
updateSlotTypeByName(stmt.loopVarName, SlotType.INT)
loopStack.addLast(
LoopContext(stmt.label, endLabel, continueLabel, breakFlagSlot, if (wantResult) resultSlot else null)
LoopContext(
stmt.label,
endLabel,
continueLabel,
breakFlagSlot,
if (wantResult) resultSlot else null,
hasIterator = false
)
)
val bodyValue = compileLoopBody(stmt.body, wantResult) ?: return null
loopStack.removeLast()
@ -2278,7 +2401,14 @@ class BytecodeCompiler(
updateSlotType(loopSlotId, SlotType.INT)
updateSlotTypeByName(stmt.loopVarName, SlotType.INT)
loopStack.addLast(
LoopContext(stmt.label, endLabel, continueLabel, breakFlagSlot, if (wantResult) resultSlot else null)
LoopContext(
stmt.label,
endLabel,
continueLabel,
breakFlagSlot,
if (wantResult) resultSlot else null,
hasIterator = false
)
)
val bodyValue = compileLoopBody(stmt.body, wantResult) ?: return null
loopStack.removeLast()
@ -2332,7 +2462,14 @@ class BytecodeCompiler(
listOf(CmdBuilder.Operand.IntVal(condition.slot), CmdBuilder.Operand.LabelRef(endLabel))
)
loopStack.addLast(
LoopContext(stmt.label, endLabel, continueLabel, breakFlagSlot, if (wantResult) resultSlot else null)
LoopContext(
stmt.label,
endLabel,
continueLabel,
breakFlagSlot,
if (wantResult) resultSlot else null,
hasIterator = false
)
)
val bodyValue = compileLoopBody(stmt.body, wantResult) ?: return null
loopStack.removeLast()
@ -2374,7 +2511,14 @@ class BytecodeCompiler(
val endLabel = builder.label()
builder.mark(loopLabel)
loopStack.addLast(
LoopContext(stmt.label, endLabel, continueLabel, breakFlagSlot, if (wantResult) resultSlot else null)
LoopContext(
stmt.label,
endLabel,
continueLabel,
breakFlagSlot,
if (wantResult) resultSlot else null,
hasIterator = false
)
)
val bodyValue = compileStatementValueOrFallback(stmt.body, wantResult) ?: return null
loopStack.removeLast()
@ -2480,17 +2624,28 @@ class BytecodeCompiler(
}
}
private fun findLoopContext(label: String?): LoopContext? {
private fun findLoopContextIndex(label: String?): Int? {
if (loopStack.isEmpty()) return null
if (label == null) return loopStack.last()
for (ctx in loopStack.reversed()) {
if (ctx.label == label) return ctx
val stack = loopStack.toList()
if (label == null) return stack.lastIndex
for (i in stack.indices.reversed()) {
if (stack[i].label == label) return i
}
return null
}
private fun emitIteratorCancel(stack: List<LoopContext>, startIndex: Int) {
for (i in stack.lastIndex downTo startIndex) {
if (stack[i].hasIterator) {
builder.emit(Opcode.ITER_CANCEL)
}
}
}
private fun compileBreak(stmt: net.sergeych.lyng.BreakStatement): CompiledValue? {
val ctx = findLoopContext(stmt.label) ?: return null
val stack = loopStack.toList()
val targetIndex = findLoopContextIndex(stmt.label) ?: return null
val ctx = stack[targetIndex]
val value = stmt.resultExpr?.let { compileStatementValueOrFallback(it) }
if (ctx.resultSlot != null) {
val objValue = value?.let { ensureObjSlot(it) } ?: run {
@ -2504,6 +2659,7 @@ class BytecodeCompiler(
} else if (value != null) {
ensureObjSlot(value)
}
emitIteratorCancel(stack, targetIndex)
val trueId = builder.addConst(BytecodeConst.Bool(true))
builder.emit(Opcode.CONST_BOOL, trueId, ctx.breakFlagSlot)
builder.emit(Opcode.JMP, listOf(CmdBuilder.Operand.LabelRef(ctx.breakLabel)))
@ -2511,7 +2667,12 @@ class BytecodeCompiler(
}
private fun compileContinue(stmt: net.sergeych.lyng.ContinueStatement): CompiledValue? {
val ctx = findLoopContext(stmt.label) ?: return null
val stack = loopStack.toList()
val targetIndex = findLoopContextIndex(stmt.label) ?: return null
val ctx = stack[targetIndex]
if (targetIndex < stack.lastIndex) {
emitIteratorCancel(stack, targetIndex + 1)
}
builder.emit(Opcode.JMP, listOf(CmdBuilder.Operand.LabelRef(ctx.continueLabel)))
return CompiledValue(ctx.breakFlagSlot, SlotType.BOOL)
}

View File

@ -19,6 +19,13 @@ package net.sergeych.lyng.bytecode
import net.sergeych.lyng.Pos
import net.sergeych.lyng.Scope
import net.sergeych.lyng.Statement
import net.sergeych.lyng.DestructuringVarDeclStatement
import net.sergeych.lyng.WhenCase
import net.sergeych.lyng.WhenCondition
import net.sergeych.lyng.WhenEqualsCondition
import net.sergeych.lyng.WhenInCondition
import net.sergeych.lyng.WhenIsCondition
import net.sergeych.lyng.WhenStatement
import net.sergeych.lyng.obj.Obj
import net.sergeych.lyng.obj.RangeRef
@ -94,6 +101,8 @@ class BytecodeStatement private constructor(
target.statements().any { containsUnsupportedStatement(it) }
is net.sergeych.lyng.VarDeclStatement ->
target.initializer?.let { containsUnsupportedStatement(it) } ?: false
is net.sergeych.lyng.DestructuringVarDeclStatement ->
containsUnsupportedStatement(target.initializer)
is net.sergeych.lyng.BreakStatement ->
target.resultExpr?.let { containsUnsupportedStatement(it) } ?: false
is net.sergeych.lyng.ContinueStatement -> false
@ -106,6 +115,14 @@ class BytecodeStatement private constructor(
is net.sergeych.lyng.FunctionDeclStatement -> false
is net.sergeych.lyng.EnumDeclStatement -> false
is net.sergeych.lyng.TryStatement -> false
is net.sergeych.lyng.WhenStatement -> {
containsUnsupportedStatement(target.value) ||
target.cases.any { case ->
case.conditions.any { cond -> containsUnsupportedStatement(cond.expr) } ||
containsUnsupportedStatement(case.block)
} ||
(target.elseCase?.let { containsUnsupportedStatement(it) } ?: false)
}
else -> true
}
}
@ -133,6 +150,17 @@ class BytecodeStatement private constructor(
stmt.pos
)
}
is net.sergeych.lyng.DestructuringVarDeclStatement -> {
net.sergeych.lyng.DestructuringVarDeclStatement(
stmt.pattern,
stmt.names,
unwrapDeep(stmt.initializer),
stmt.isMutable,
stmt.visibility,
stmt.isTransient,
stmt.pos
)
}
is net.sergeych.lyng.IfStatement -> {
net.sergeych.lyng.IfStatement(
unwrapDeep(stmt.condition),
@ -187,8 +215,29 @@ class BytecodeStatement private constructor(
}
is net.sergeych.lyng.ThrowStatement ->
net.sergeych.lyng.ThrowStatement(unwrapDeep(stmt.throwExpr), stmt.pos)
is net.sergeych.lyng.WhenStatement -> {
net.sergeych.lyng.WhenStatement(
unwrapDeep(stmt.value),
stmt.cases.map { case ->
net.sergeych.lyng.WhenCase(
case.conditions.map { unwrapWhenCondition(it) },
unwrapDeep(case.block)
)
},
stmt.elseCase?.let { unwrapDeep(it) },
stmt.pos
)
}
else -> stmt
}
}
private fun unwrapWhenCondition(cond: WhenCondition): WhenCondition {
return when (cond) {
is WhenEqualsCondition -> WhenEqualsCondition(unwrapDeep(cond.expr), cond.pos)
is WhenInCondition -> WhenInCondition(unwrapDeep(cond.expr), cond.negated, cond.pos)
is WhenIsCondition -> WhenIsCondition(unwrapDeep(cond.expr), cond.negated, cond.pos)
}
}
}
}

View File

@ -192,6 +192,10 @@ class CmdBuilder {
listOf(OperandKind.ID, OperandKind.SLOT)
Opcode.EVAL_FALLBACK, Opcode.EVAL_REF, Opcode.EVAL_STMT, Opcode.EVAL_VALUE_FN ->
listOf(OperandKind.ID, OperandKind.SLOT)
Opcode.ITER_PUSH ->
listOf(OperandKind.SLOT)
Opcode.ITER_POP, Opcode.ITER_CANCEL ->
emptyList()
}
}
@ -387,6 +391,9 @@ class CmdBuilder {
Opcode.EVAL_REF -> CmdEvalRef(operands[0], operands[1])
Opcode.EVAL_STMT -> CmdEvalStmt(operands[0], operands[1])
Opcode.EVAL_VALUE_FN -> CmdEvalValueFn(operands[0], operands[1])
Opcode.ITER_PUSH -> CmdIterPush(operands[0])
Opcode.ITER_POP -> CmdIterPop()
Opcode.ITER_CANCEL -> CmdIterCancel()
}
}
}

View File

@ -191,6 +191,9 @@ object CmdDisassembler {
is CmdEvalRef -> Opcode.EVAL_REF to intArrayOf(cmd.id, cmd.dst)
is CmdEvalStmt -> Opcode.EVAL_STMT to intArrayOf(cmd.id, cmd.dst)
is CmdEvalValueFn -> Opcode.EVAL_VALUE_FN to intArrayOf(cmd.id, cmd.dst)
is CmdIterPush -> Opcode.ITER_PUSH to intArrayOf(cmd.iterSlot)
is CmdIterPop -> Opcode.ITER_POP to intArrayOf()
is CmdIterCancel -> Opcode.ITER_CANCEL to intArrayOf()
}
}
@ -205,7 +208,8 @@ object CmdDisassembler {
private fun operandKinds(op: Opcode): List<OperandKind> {
return when (op) {
Opcode.NOP, Opcode.RET_VOID, Opcode.POP_SCOPE, Opcode.POP_SLOT_PLAN -> emptyList()
Opcode.NOP, Opcode.RET_VOID, Opcode.POP_SCOPE, Opcode.POP_SLOT_PLAN,
Opcode.ITER_POP, Opcode.ITER_CANCEL -> emptyList()
Opcode.MOVE_OBJ, Opcode.MOVE_INT, Opcode.MOVE_REAL, Opcode.MOVE_BOOL, Opcode.BOX_OBJ,
Opcode.INT_TO_REAL, Opcode.REAL_TO_INT, Opcode.BOOL_TO_INT, Opcode.INT_TO_BOOL,
Opcode.NEG_INT, Opcode.NEG_REAL, Opcode.NOT_BOOL, Opcode.INV_INT ->
@ -248,7 +252,7 @@ object CmdDisassembler {
Opcode.ADD_OBJ, Opcode.SUB_OBJ, Opcode.MUL_OBJ, Opcode.DIV_OBJ, Opcode.MOD_OBJ, Opcode.CONTAINS_OBJ,
Opcode.AND_BOOL, Opcode.OR_BOOL ->
listOf(OperandKind.SLOT, OperandKind.SLOT, OperandKind.SLOT)
Opcode.INC_INT, Opcode.DEC_INT, Opcode.RET ->
Opcode.INC_INT, Opcode.DEC_INT, Opcode.RET, Opcode.ITER_PUSH ->
listOf(OperandKind.SLOT)
Opcode.JMP ->
listOf(OperandKind.IP)

View File

@ -32,11 +32,17 @@ class CmdVm {
result = null
val frame = CmdFrame(this, fn, scope0, args)
val cmds = fn.cmds
try {
while (result == null) {
val cmd = cmds[frame.ip]
frame.ip += 1
cmd.perform(frame)
}
} catch (e: Throwable) {
frame.cancelIterators()
throw e
}
frame.cancelIterators()
return result ?: ObjVoid
}
}
@ -1419,6 +1425,27 @@ class CmdEvalValueFn(internal val id: Int, internal val dst: Int) : Cmd() {
}
}
class CmdIterPush(internal val iterSlot: Int) : Cmd() {
override suspend fun perform(frame: CmdFrame) {
frame.pushIterator(frame.slotToObj(iterSlot))
return
}
}
class CmdIterPop : Cmd() {
override suspend fun perform(frame: CmdFrame) {
frame.popIterator()
return
}
}
class CmdIterCancel : Cmd() {
override suspend fun perform(frame: CmdFrame) {
frame.cancelTopIterator()
return
}
}
class CmdFrame(
val vm: CmdVm,
val fn: CmdFunction,
@ -1440,6 +1467,7 @@ class CmdFrame(
internal val slotPlanScopeStack = ArrayDeque<Boolean>()
private var scopeDepth = 0
private var virtualDepth = 0
private val iterStack = ArrayDeque<Obj>()
internal val frame = BytecodeFrame(fn.localCount, args.size)
private val addrScopes: Array<Scope?> = arrayOfNulls(fn.addrCount)
@ -1484,6 +1512,26 @@ class CmdFrame(
scopeDepth -= 1
}
fun pushIterator(iter: Obj) {
iterStack.addLast(iter)
}
fun popIterator() {
iterStack.removeLastOrNull()
}
suspend fun cancelTopIterator() {
val iter = iterStack.removeLastOrNull() ?: return
iter.invokeInstanceMethod(scope, "cancelIteration") { ObjVoid }
}
suspend fun cancelIterators() {
while (iterStack.isNotEmpty()) {
val iter = iterStack.removeLast()
iter.invokeInstanceMethod(scope, "cancelIteration") { ObjVoid }
}
}
fun pushSlotPlan(plan: Map<String, Int>) {
if (scope.hasSlotPlanConflict(plan)) {
scopeStack.addLast(scope)

View File

@ -149,6 +149,9 @@ enum class Opcode(val code: Int) {
EVAL_REF(0xBC),
EVAL_STMT(0xBD),
EVAL_VALUE_FN(0xBE),
ITER_PUSH(0xBF),
ITER_POP(0xC0),
ITER_CANCEL(0xC1),
;
companion object {

View File

@ -186,7 +186,6 @@ class ScriptTest {
assertEquals(0, ti.cancelCount)
}
@Ignore("Bytecode: unsupported or incorrect behavior")
@Test
fun testForLoopCancelsOnBreak() = runTest {
val scope = Script.newScope()
@ -202,7 +201,6 @@ class ScriptTest {
assertEquals(1, ti.cancelCount)
}
@Ignore("Bytecode: unsupported or incorrect behavior")
@Test
fun testForLoopCancelsOnException() = runTest {
val scope = Script.newScope()
@ -2351,7 +2349,6 @@ class ScriptTest {
}
}
@Ignore("Bytecode: unsupported or incorrect behavior")
@Test
fun testSimpleWhen() = runTest {
eval(
@ -2376,7 +2373,6 @@ class ScriptTest {
)
}
@Ignore("Bytecode: unsupported or incorrect behavior")
@Test
fun testWhenIs() = runTest {
eval(
@ -2407,7 +2403,6 @@ class ScriptTest {
)
}
@Ignore("Bytecode: unsupported or incorrect behavior")
@Test
fun testWhenIn() = runTest {
eval(
@ -2502,7 +2497,6 @@ class ScriptTest {
// )
// }
@Ignore("Bytecode: unsupported or incorrect behavior")
@Test
fun testWhenSample1() = runTest {
eval(
@ -2522,7 +2516,6 @@ class ScriptTest {
)
}
@Ignore("Bytecode: unsupported or incorrect behavior")
@Test
fun testWhenSample2() = runTest {
eval(
@ -2794,7 +2787,6 @@ class ScriptTest {
}
}
@Ignore("Bytecode: unsupported or incorrect behavior")
@Test
fun testParallels2() = runTest {
withContext(Dispatchers.Default) {
@ -2842,7 +2834,6 @@ class ScriptTest {
}
}
@Ignore("Bytecode: unsupported or incorrect behavior")
@Test
fun testExtend() = runTest() {
eval(
@ -4574,7 +4565,6 @@ class ScriptTest {
}
@Ignore("Bytecode: unsupported or incorrect behavior")
@Test
fun testDestructuringAssignment() = runTest {
eval(
@ -4952,7 +4942,6 @@ class ScriptTest {
)
}
@Ignore("Bytecode: unsupported or incorrect behavior")
@Test
fun testLazyLocals() = runTest() {
eval(
@ -5119,7 +5108,6 @@ class ScriptTest {
)
}
@Ignore("Bytecode: unsupported or incorrect behavior")
@Test
fun testForInIterableDisasm() = runTest {
val scope = Script.newScope()
@ -5147,7 +5135,6 @@ class ScriptTest {
println("[DEBUG_LOG] type(\"153\")=${r2.inspect(scope)}")
}
@Ignore("Bytecode: unsupported or incorrect behavior")
@Test
fun testForInIterableBytecode() = runTest {
val result = eval(
@ -5163,7 +5150,6 @@ class ScriptTest {
assertEquals(ObjInt(12), result)
}
@Ignore("Bytecode: unsupported or incorrect behavior")
@Test
fun testForInIterableUnknownTypeDisasm() = runTest {
val scope = Script.newScope()
@ -5244,7 +5230,6 @@ class ScriptTest {
assertEquals(ObjFalse, scope.eval("isInt(\"42\")"))
}
@Ignore("Bytecode: unsupported or incorrect behavior")
@Test
fun testFilterBug() = runTest {
eval(