Compare commits

..

No commits in common. "e346e7e56e1adea1517696d95138b6c0ed2e91fb" and "91624a30b82b43b7fe8e69183af66d5008e2a87a" have entirely different histories.

10 changed files with 104 additions and 433 deletions

View File

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

View File

@ -1,49 +0,0 @@
/*
* 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

@ -1,76 +0,0 @@
/*
* 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,7 +17,6 @@
package net.sergeych.lyng.bytecode package net.sergeych.lyng.bytecode
import net.sergeych.lyng.BlockStatement import net.sergeych.lyng.BlockStatement
import net.sergeych.lyng.DestructuringVarDeclStatement
import net.sergeych.lyng.ExpressionStatement import net.sergeych.lyng.ExpressionStatement
import net.sergeych.lyng.IfStatement import net.sergeych.lyng.IfStatement
import net.sergeych.lyng.ParsedArgument import net.sergeych.lyng.ParsedArgument
@ -25,11 +24,6 @@ import net.sergeych.lyng.Pos
import net.sergeych.lyng.Statement import net.sergeych.lyng.Statement
import net.sergeych.lyng.ToBoolStatement import net.sergeych.lyng.ToBoolStatement
import net.sergeych.lyng.VarDeclStatement 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.* import net.sergeych.lyng.obj.*
class BytecodeCompiler( class BytecodeCompiler(
@ -70,7 +64,6 @@ class BytecodeCompiler(
val continueLabel: CmdBuilder.Label, val continueLabel: CmdBuilder.Label,
val breakFlagSlot: Int, val breakFlagSlot: Int,
val resultSlot: Int?, val resultSlot: Int?,
val hasIterator: Boolean,
) )
fun compileStatement(name: String, stmt: net.sergeych.lyng.Statement): CmdFunction? { fun compileStatement(name: String, stmt: net.sergeych.lyng.Statement): CmdFunction? {
@ -1522,96 +1515,6 @@ class BytecodeCompiler(
return CompiledValue(resultSlot, SlotType.OBJ) 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 { private fun ensureObjSlot(value: CompiledValue): CompiledValue {
if (value.type == SlotType.OBJ) return value if (value.type == SlotType.OBJ) return value
val dst = allocSlot() val dst = allocSlot()
@ -1975,13 +1878,11 @@ class BytecodeCompiler(
} }
is BlockStatement -> emitBlock(target, true) is BlockStatement -> emitBlock(target, true)
is VarDeclStatement -> emitVarDecl(target) is VarDeclStatement -> emitVarDecl(target)
is DestructuringVarDeclStatement -> emitStatementEval(target)
is net.sergeych.lyng.ExtensionPropertyDeclStatement -> emitExtensionPropertyDecl(target) is net.sergeych.lyng.ExtensionPropertyDeclStatement -> emitExtensionPropertyDecl(target)
is net.sergeych.lyng.ClassDeclStatement -> emitStatementEval(target) is net.sergeych.lyng.ClassDeclStatement -> emitStatementEval(target)
is net.sergeych.lyng.FunctionDeclStatement -> emitStatementEval(target) is net.sergeych.lyng.FunctionDeclStatement -> emitStatementEval(target)
is net.sergeych.lyng.EnumDeclStatement -> emitStatementEval(target) is net.sergeych.lyng.EnumDeclStatement -> emitStatementEval(target)
is net.sergeych.lyng.TryStatement -> 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.BreakStatement -> compileBreak(target)
is net.sergeych.lyng.ContinueStatement -> compileContinue(target) is net.sergeych.lyng.ContinueStatement -> compileContinue(target)
is net.sergeych.lyng.ReturnStatement -> compileReturn(target) is net.sergeych.lyng.ReturnStatement -> compileReturn(target)
@ -2021,13 +1922,11 @@ class BytecodeCompiler(
} }
is BlockStatement -> emitBlock(target, false) is BlockStatement -> emitBlock(target, false)
is VarDeclStatement -> emitVarDecl(target) is VarDeclStatement -> emitVarDecl(target)
is DestructuringVarDeclStatement -> emitStatementEval(target)
is net.sergeych.lyng.ExtensionPropertyDeclStatement -> emitExtensionPropertyDecl(target) is net.sergeych.lyng.ExtensionPropertyDeclStatement -> emitExtensionPropertyDecl(target)
is net.sergeych.lyng.BreakStatement -> compileBreak(target) is net.sergeych.lyng.BreakStatement -> compileBreak(target)
is net.sergeych.lyng.ContinueStatement -> compileContinue(target) is net.sergeych.lyng.ContinueStatement -> compileContinue(target)
is net.sergeych.lyng.ReturnStatement -> compileReturn(target) is net.sergeych.lyng.ReturnStatement -> compileReturn(target)
is net.sergeych.lyng.ThrowStatement -> compileThrow(target) is net.sergeych.lyng.ThrowStatement -> compileThrow(target)
is net.sergeych.lyng.WhenStatement -> compileWhen(target, false)
else -> { else -> {
emitFallbackStatement(target) emitFallbackStatement(target)
} }
@ -2206,7 +2105,6 @@ class BytecodeCompiler(
val iterSlot = allocSlot() val iterSlot = allocSlot()
val iteratorId = builder.addConst(BytecodeConst.StringVal("iterator")) val iteratorId = builder.addConst(BytecodeConst.StringVal("iterator"))
builder.emit(Opcode.CALL_VIRTUAL, sourceObj.slot, iteratorId, 0, 0, iterSlot) builder.emit(Opcode.CALL_VIRTUAL, sourceObj.slot, iteratorId, 0, 0, iterSlot)
builder.emit(Opcode.ITER_PUSH, iterSlot)
val breakFlagSlot = allocSlot() val breakFlagSlot = allocSlot()
val falseId = builder.addConst(BytecodeConst.Bool(false)) val falseId = builder.addConst(BytecodeConst.Bool(false))
@ -2240,14 +2138,7 @@ class BytecodeCompiler(
updateSlotTypeByName(stmt.loopVarName, SlotType.OBJ) updateSlotTypeByName(stmt.loopVarName, SlotType.OBJ)
loopStack.addLast( loopStack.addLast(
LoopContext( LoopContext(stmt.label, endLabel, continueLabel, breakFlagSlot, if (wantResult) resultSlot else null)
stmt.label,
endLabel,
continueLabel,
breakFlagSlot,
if (wantResult) resultSlot else null,
hasIterator = true
)
) )
val bodyValue = compileLoopBody(stmt.body, wantResult) ?: return null val bodyValue = compileLoopBody(stmt.body, wantResult) ?: return null
loopStack.removeLast() loopStack.removeLast()
@ -2259,13 +2150,6 @@ class BytecodeCompiler(
builder.emit(Opcode.JMP, listOf(CmdBuilder.Operand.LabelRef(loopLabel))) builder.emit(Opcode.JMP, listOf(CmdBuilder.Operand.LabelRef(loopLabel)))
builder.mark(endLabel) 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) { if (stmt.elseStatement != null) {
val afterElse = builder.label() val afterElse = builder.label()
builder.emit( builder.emit(
@ -2335,14 +2219,7 @@ class BytecodeCompiler(
updateSlotType(loopSlotId, SlotType.INT) updateSlotType(loopSlotId, SlotType.INT)
updateSlotTypeByName(stmt.loopVarName, SlotType.INT) updateSlotTypeByName(stmt.loopVarName, SlotType.INT)
loopStack.addLast( loopStack.addLast(
LoopContext( LoopContext(stmt.label, endLabel, continueLabel, breakFlagSlot, if (wantResult) resultSlot else null)
stmt.label,
endLabel,
continueLabel,
breakFlagSlot,
if (wantResult) resultSlot else null,
hasIterator = false
)
) )
val bodyValue = compileLoopBody(stmt.body, wantResult) ?: return null val bodyValue = compileLoopBody(stmt.body, wantResult) ?: return null
loopStack.removeLast() loopStack.removeLast()
@ -2401,14 +2278,7 @@ class BytecodeCompiler(
updateSlotType(loopSlotId, SlotType.INT) updateSlotType(loopSlotId, SlotType.INT)
updateSlotTypeByName(stmt.loopVarName, SlotType.INT) updateSlotTypeByName(stmt.loopVarName, SlotType.INT)
loopStack.addLast( loopStack.addLast(
LoopContext( LoopContext(stmt.label, endLabel, continueLabel, breakFlagSlot, if (wantResult) resultSlot else null)
stmt.label,
endLabel,
continueLabel,
breakFlagSlot,
if (wantResult) resultSlot else null,
hasIterator = false
)
) )
val bodyValue = compileLoopBody(stmt.body, wantResult) ?: return null val bodyValue = compileLoopBody(stmt.body, wantResult) ?: return null
loopStack.removeLast() loopStack.removeLast()
@ -2462,14 +2332,7 @@ class BytecodeCompiler(
listOf(CmdBuilder.Operand.IntVal(condition.slot), CmdBuilder.Operand.LabelRef(endLabel)) listOf(CmdBuilder.Operand.IntVal(condition.slot), CmdBuilder.Operand.LabelRef(endLabel))
) )
loopStack.addLast( loopStack.addLast(
LoopContext( LoopContext(stmt.label, endLabel, continueLabel, breakFlagSlot, if (wantResult) resultSlot else null)
stmt.label,
endLabel,
continueLabel,
breakFlagSlot,
if (wantResult) resultSlot else null,
hasIterator = false
)
) )
val bodyValue = compileLoopBody(stmt.body, wantResult) ?: return null val bodyValue = compileLoopBody(stmt.body, wantResult) ?: return null
loopStack.removeLast() loopStack.removeLast()
@ -2511,14 +2374,7 @@ class BytecodeCompiler(
val endLabel = builder.label() val endLabel = builder.label()
builder.mark(loopLabel) builder.mark(loopLabel)
loopStack.addLast( loopStack.addLast(
LoopContext( LoopContext(stmt.label, endLabel, continueLabel, breakFlagSlot, if (wantResult) resultSlot else null)
stmt.label,
endLabel,
continueLabel,
breakFlagSlot,
if (wantResult) resultSlot else null,
hasIterator = false
)
) )
val bodyValue = compileStatementValueOrFallback(stmt.body, wantResult) ?: return null val bodyValue = compileStatementValueOrFallback(stmt.body, wantResult) ?: return null
loopStack.removeLast() loopStack.removeLast()
@ -2624,28 +2480,17 @@ class BytecodeCompiler(
} }
} }
private fun findLoopContextIndex(label: String?): Int? { private fun findLoopContext(label: String?): LoopContext? {
if (loopStack.isEmpty()) return null if (loopStack.isEmpty()) return null
val stack = loopStack.toList() if (label == null) return loopStack.last()
if (label == null) return stack.lastIndex for (ctx in loopStack.reversed()) {
for (i in stack.indices.reversed()) { if (ctx.label == label) return ctx
if (stack[i].label == label) return i
} }
return null 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? { private fun compileBreak(stmt: net.sergeych.lyng.BreakStatement): CompiledValue? {
val stack = loopStack.toList() val ctx = findLoopContext(stmt.label) ?: return null
val targetIndex = findLoopContextIndex(stmt.label) ?: return null
val ctx = stack[targetIndex]
val value = stmt.resultExpr?.let { compileStatementValueOrFallback(it) } val value = stmt.resultExpr?.let { compileStatementValueOrFallback(it) }
if (ctx.resultSlot != null) { if (ctx.resultSlot != null) {
val objValue = value?.let { ensureObjSlot(it) } ?: run { val objValue = value?.let { ensureObjSlot(it) } ?: run {
@ -2659,7 +2504,6 @@ class BytecodeCompiler(
} else if (value != null) { } else if (value != null) {
ensureObjSlot(value) ensureObjSlot(value)
} }
emitIteratorCancel(stack, targetIndex)
val trueId = builder.addConst(BytecodeConst.Bool(true)) val trueId = builder.addConst(BytecodeConst.Bool(true))
builder.emit(Opcode.CONST_BOOL, trueId, ctx.breakFlagSlot) builder.emit(Opcode.CONST_BOOL, trueId, ctx.breakFlagSlot)
builder.emit(Opcode.JMP, listOf(CmdBuilder.Operand.LabelRef(ctx.breakLabel))) builder.emit(Opcode.JMP, listOf(CmdBuilder.Operand.LabelRef(ctx.breakLabel)))
@ -2667,12 +2511,7 @@ class BytecodeCompiler(
} }
private fun compileContinue(stmt: net.sergeych.lyng.ContinueStatement): CompiledValue? { private fun compileContinue(stmt: net.sergeych.lyng.ContinueStatement): CompiledValue? {
val stack = loopStack.toList() val ctx = findLoopContext(stmt.label) ?: return null
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))) builder.emit(Opcode.JMP, listOf(CmdBuilder.Operand.LabelRef(ctx.continueLabel)))
return CompiledValue(ctx.breakFlagSlot, SlotType.BOOL) return CompiledValue(ctx.breakFlagSlot, SlotType.BOOL)
} }

View File

@ -19,13 +19,6 @@ package net.sergeych.lyng.bytecode
import net.sergeych.lyng.Pos import net.sergeych.lyng.Pos
import net.sergeych.lyng.Scope import net.sergeych.lyng.Scope
import net.sergeych.lyng.Statement 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.Obj
import net.sergeych.lyng.obj.RangeRef import net.sergeych.lyng.obj.RangeRef
@ -101,8 +94,6 @@ class BytecodeStatement private constructor(
target.statements().any { containsUnsupportedStatement(it) } target.statements().any { containsUnsupportedStatement(it) }
is net.sergeych.lyng.VarDeclStatement -> is net.sergeych.lyng.VarDeclStatement ->
target.initializer?.let { containsUnsupportedStatement(it) } ?: false target.initializer?.let { containsUnsupportedStatement(it) } ?: false
is net.sergeych.lyng.DestructuringVarDeclStatement ->
containsUnsupportedStatement(target.initializer)
is net.sergeych.lyng.BreakStatement -> is net.sergeych.lyng.BreakStatement ->
target.resultExpr?.let { containsUnsupportedStatement(it) } ?: false target.resultExpr?.let { containsUnsupportedStatement(it) } ?: false
is net.sergeych.lyng.ContinueStatement -> false is net.sergeych.lyng.ContinueStatement -> false
@ -115,14 +106,6 @@ class BytecodeStatement private constructor(
is net.sergeych.lyng.FunctionDeclStatement -> false is net.sergeych.lyng.FunctionDeclStatement -> false
is net.sergeych.lyng.EnumDeclStatement -> false is net.sergeych.lyng.EnumDeclStatement -> false
is net.sergeych.lyng.TryStatement -> 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 else -> true
} }
} }
@ -150,17 +133,6 @@ class BytecodeStatement private constructor(
stmt.pos 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 -> { is net.sergeych.lyng.IfStatement -> {
net.sergeych.lyng.IfStatement( net.sergeych.lyng.IfStatement(
unwrapDeep(stmt.condition), unwrapDeep(stmt.condition),
@ -215,29 +187,8 @@ class BytecodeStatement private constructor(
} }
is net.sergeych.lyng.ThrowStatement -> is net.sergeych.lyng.ThrowStatement ->
net.sergeych.lyng.ThrowStatement(unwrapDeep(stmt.throwExpr), stmt.pos) 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 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,10 +192,6 @@ class CmdBuilder {
listOf(OperandKind.ID, OperandKind.SLOT) listOf(OperandKind.ID, OperandKind.SLOT)
Opcode.EVAL_FALLBACK, Opcode.EVAL_REF, Opcode.EVAL_STMT, Opcode.EVAL_VALUE_FN -> Opcode.EVAL_FALLBACK, Opcode.EVAL_REF, Opcode.EVAL_STMT, Opcode.EVAL_VALUE_FN ->
listOf(OperandKind.ID, OperandKind.SLOT) listOf(OperandKind.ID, OperandKind.SLOT)
Opcode.ITER_PUSH ->
listOf(OperandKind.SLOT)
Opcode.ITER_POP, Opcode.ITER_CANCEL ->
emptyList()
} }
} }
@ -391,9 +387,6 @@ class CmdBuilder {
Opcode.EVAL_REF -> CmdEvalRef(operands[0], operands[1]) Opcode.EVAL_REF -> CmdEvalRef(operands[0], operands[1])
Opcode.EVAL_STMT -> CmdEvalStmt(operands[0], operands[1]) Opcode.EVAL_STMT -> CmdEvalStmt(operands[0], operands[1])
Opcode.EVAL_VALUE_FN -> CmdEvalValueFn(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,9 +191,6 @@ object CmdDisassembler {
is CmdEvalRef -> Opcode.EVAL_REF to intArrayOf(cmd.id, cmd.dst) is CmdEvalRef -> Opcode.EVAL_REF to intArrayOf(cmd.id, cmd.dst)
is CmdEvalStmt -> Opcode.EVAL_STMT 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 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()
} }
} }
@ -208,8 +205,7 @@ object CmdDisassembler {
private fun operandKinds(op: Opcode): List<OperandKind> { private fun operandKinds(op: Opcode): List<OperandKind> {
return when (op) { return when (op) {
Opcode.NOP, Opcode.RET_VOID, Opcode.POP_SCOPE, Opcode.POP_SLOT_PLAN, Opcode.NOP, Opcode.RET_VOID, Opcode.POP_SCOPE, Opcode.POP_SLOT_PLAN -> emptyList()
Opcode.ITER_POP, Opcode.ITER_CANCEL -> emptyList()
Opcode.MOVE_OBJ, Opcode.MOVE_INT, Opcode.MOVE_REAL, Opcode.MOVE_BOOL, Opcode.BOX_OBJ, 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.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 -> Opcode.NEG_INT, Opcode.NEG_REAL, Opcode.NOT_BOOL, Opcode.INV_INT ->
@ -252,7 +248,7 @@ object CmdDisassembler {
Opcode.ADD_OBJ, Opcode.SUB_OBJ, Opcode.MUL_OBJ, Opcode.DIV_OBJ, Opcode.MOD_OBJ, Opcode.CONTAINS_OBJ, Opcode.ADD_OBJ, Opcode.SUB_OBJ, Opcode.MUL_OBJ, Opcode.DIV_OBJ, Opcode.MOD_OBJ, Opcode.CONTAINS_OBJ,
Opcode.AND_BOOL, Opcode.OR_BOOL -> Opcode.AND_BOOL, Opcode.OR_BOOL ->
listOf(OperandKind.SLOT, OperandKind.SLOT, OperandKind.SLOT) listOf(OperandKind.SLOT, OperandKind.SLOT, OperandKind.SLOT)
Opcode.INC_INT, Opcode.DEC_INT, Opcode.RET, Opcode.ITER_PUSH -> Opcode.INC_INT, Opcode.DEC_INT, Opcode.RET ->
listOf(OperandKind.SLOT) listOf(OperandKind.SLOT)
Opcode.JMP -> Opcode.JMP ->
listOf(OperandKind.IP) listOf(OperandKind.IP)

View File

@ -32,17 +32,11 @@ class CmdVm {
result = null result = null
val frame = CmdFrame(this, fn, scope0, args) val frame = CmdFrame(this, fn, scope0, args)
val cmds = fn.cmds val cmds = fn.cmds
try {
while (result == null) { while (result == null) {
val cmd = cmds[frame.ip] val cmd = cmds[frame.ip]
frame.ip += 1 frame.ip += 1
cmd.perform(frame) cmd.perform(frame)
} }
} catch (e: Throwable) {
frame.cancelIterators()
throw e
}
frame.cancelIterators()
return result ?: ObjVoid return result ?: ObjVoid
} }
} }
@ -1425,27 +1419,6 @@ 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( class CmdFrame(
val vm: CmdVm, val vm: CmdVm,
val fn: CmdFunction, val fn: CmdFunction,
@ -1467,7 +1440,6 @@ class CmdFrame(
internal val slotPlanScopeStack = ArrayDeque<Boolean>() internal val slotPlanScopeStack = ArrayDeque<Boolean>()
private var scopeDepth = 0 private var scopeDepth = 0
private var virtualDepth = 0 private var virtualDepth = 0
private val iterStack = ArrayDeque<Obj>()
internal val frame = BytecodeFrame(fn.localCount, args.size) internal val frame = BytecodeFrame(fn.localCount, args.size)
private val addrScopes: Array<Scope?> = arrayOfNulls(fn.addrCount) private val addrScopes: Array<Scope?> = arrayOfNulls(fn.addrCount)
@ -1512,26 +1484,6 @@ class CmdFrame(
scopeDepth -= 1 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>) { fun pushSlotPlan(plan: Map<String, Int>) {
if (scope.hasSlotPlanConflict(plan)) { if (scope.hasSlotPlanConflict(plan)) {
scopeStack.addLast(scope) scopeStack.addLast(scope)

View File

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

View File

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