diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Compiler.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Compiler.kt index bd5bec5..4408757 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Compiler.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Compiler.kt @@ -370,16 +370,29 @@ class Compiler( private var isTransientFlag: Boolean = false private var lastLabel: String? = null private val useBytecodeStatements: Boolean = true + private val returnLabelStack = ArrayDeque>() private fun wrapBytecode(stmt: Statement): Statement { if (!useBytecodeStatements) return stmt val allowLocals = codeContexts.lastOrNull() is CodeContext.Function - return BytecodeStatement.wrap(stmt, "stmt@${stmt.pos}", allowLocalSlots = allowLocals) + val returnLabels = returnLabelStack.lastOrNull() ?: emptySet() + return BytecodeStatement.wrap( + stmt, + "stmt@${stmt.pos}", + allowLocalSlots = allowLocals, + returnLabels = returnLabels + ) } private fun wrapFunctionBytecode(stmt: Statement, name: String): Statement { if (!useBytecodeStatements) return stmt - return BytecodeStatement.wrap(stmt, "fn@$name", allowLocalSlots = true) + val returnLabels = returnLabelStack.lastOrNull() ?: emptySet() + return BytecodeStatement.wrap( + stmt, + "fn@$name", + allowLocalSlots = true, + returnLabels = returnLabels + ) } private fun containsUnsupportedForBytecode(stmt: Statement): Boolean { @@ -392,13 +405,27 @@ class Compiler( (target.elseBody?.let { containsUnsupportedForBytecode(it) } ?: false) } is ForInStatement -> { - target.constRange == null || target.canBreak || + target.constRange == null || containsUnsupportedForBytecode(target.source) || containsUnsupportedForBytecode(target.body) || (target.elseStatement?.let { containsUnsupportedForBytecode(it) } ?: false) } + is WhileStatement -> { + containsUnsupportedForBytecode(target.condition) || + containsUnsupportedForBytecode(target.body) || + (target.elseStatement?.let { containsUnsupportedForBytecode(it) } ?: false) + } + is DoWhileStatement -> { + containsUnsupportedForBytecode(target.body) || + containsUnsupportedForBytecode(target.condition) || + (target.elseStatement?.let { containsUnsupportedForBytecode(it) } ?: false) + } is BlockStatement -> target.statements().any { containsUnsupportedForBytecode(it) } is VarDeclStatement -> target.initializer?.let { containsUnsupportedForBytecode(it) } ?: false + is BreakStatement -> target.resultExpr?.let { containsUnsupportedForBytecode(it) } ?: false + is ContinueStatement -> false + is ReturnStatement -> target.resultExpr?.let { containsUnsupportedForBytecode(it) } ?: false + is ThrowStatement -> containsUnsupportedForBytecode(target.throwExpr) else -> true } } @@ -430,6 +457,59 @@ class Compiler( val elseBody = stmt.elseBody?.let { unwrapBytecodeDeep(it) } IfStatement(cond, ifBody, elseBody, stmt.pos) } + is ForInStatement -> { + val source = unwrapBytecodeDeep(stmt.source) + val body = unwrapBytecodeDeep(stmt.body) + val elseBody = stmt.elseStatement?.let { unwrapBytecodeDeep(it) } + ForInStatement( + stmt.loopVarName, + source, + stmt.constRange, + body, + elseBody, + stmt.label, + stmt.canBreak, + stmt.loopSlotPlan, + stmt.pos + ) + } + is WhileStatement -> { + val condition = unwrapBytecodeDeep(stmt.condition) + val body = unwrapBytecodeDeep(stmt.body) + val elseBody = stmt.elseStatement?.let { unwrapBytecodeDeep(it) } + WhileStatement( + condition, + body, + elseBody, + stmt.label, + stmt.canBreak, + stmt.loopSlotPlan, + stmt.pos + ) + } + is DoWhileStatement -> { + val body = unwrapBytecodeDeep(stmt.body) + val condition = unwrapBytecodeDeep(stmt.condition) + val elseBody = stmt.elseStatement?.let { unwrapBytecodeDeep(it) } + DoWhileStatement( + body, + condition, + elseBody, + stmt.label, + stmt.loopSlotPlan, + stmt.pos + ) + } + is BreakStatement -> { + val resultExpr = stmt.resultExpr?.let { unwrapBytecodeDeep(it) } + BreakStatement(stmt.label, resultExpr, stmt.pos) + } + is ContinueStatement -> ContinueStatement(stmt.label, stmt.pos) + is ReturnStatement -> { + val resultExpr = stmt.resultExpr?.let { unwrapBytecodeDeep(it) } + ReturnStatement(stmt.label, resultExpr, stmt.pos) + } + is ThrowStatement -> ThrowStatement(unwrapBytecodeDeep(stmt.throwExpr), stmt.pos) else -> stmt } } @@ -885,8 +965,14 @@ class Compiler( slotPlanStack.add(paramSlotPlan) val parsedBody = try { inCodeContext(CodeContext.Function("")) { - withLocalNames(slotParamNames.toSet()) { - parseBlock(skipLeadingBrace = true) + val returnLabels = label?.let { setOf(it) } ?: emptySet() + returnLabelStack.addLast(returnLabels) + try { + withLocalNames(slotParamNames.toSet()) { + parseBlock(skipLeadingBrace = true) + } + } finally { + returnLabelStack.removeLast() } } } finally { @@ -2031,37 +2117,7 @@ class Compiler( private suspend fun parseThrowStatement(start: Pos): Statement { val throwStatement = parseStatement() ?: throw ScriptError(cc.currentPos(), "throw object expected") - // Important: bind the created statement to the position of the `throw` keyword so that - // any raised error reports the correct source location. - val stmt = object : Statement() { - override val pos: Pos = start - override suspend fun execute(scope: Scope): Obj { - var errorObject = throwStatement.execute(scope) - // Rebind error scope to the throw-site position so ScriptError.pos is accurate - val throwScope = scope.createChildScope(pos = start) - if (errorObject is ObjString) { - errorObject = ObjException(throwScope, errorObject.value).apply { getStackTrace() } - } - if (!errorObject.isInstanceOf(ObjException.Root)) { - throwScope.raiseError("this is not an exception object: $errorObject") - } - if (errorObject is ObjException) { - errorObject = ObjException( - errorObject.exceptionClass, - throwScope, - errorObject.message, - errorObject.extraData, - errorObject.useStackTrace - ).apply { getStackTrace() } - throwScope.raiseError(errorObject) - } else { - val msg = errorObject.invokeInstanceMethod(scope, "message").toString(scope).value - throwScope.raiseError(errorObject, start, msg) - } - return ObjVoid - } - } - return wrapBytecode(stmt) + return wrapBytecode(ThrowStatement(throwStatement, start)) } private data class CatchBlockData( @@ -2692,7 +2748,11 @@ class Compiler( slotPlanStack.add(loopSlotPlan) val (canBreak, parsedBody) = try { cc.parseLoop { - parseStatement() ?: throw ScriptError(cc.currentPos(), "Bad do-while statement: expected body statement") + if (cc.current().type == Token.Type.LBRACE) { + parseLoopBlock() + } else { + parseStatement() ?: throw ScriptError(cc.currentPos(), "Bad do-while statement: expected body statement") + } } } finally { slotPlanStack.removeLast() @@ -2722,36 +2782,8 @@ class Compiler( cc.previous() null } - - return object : Statement() { - override val pos: Pos = body.pos - override suspend fun execute(scope: Scope): Obj { - var wasBroken = false - var result: Obj = ObjVoid - while (true) { - val doScope = scope.createChildScope().apply { skipScopeCreation = true } - try { - result = body.execute(doScope) - } catch (e: LoopBreakContinueException) { - if (e.label == label || e.label == null) { - if (!e.doContinue) { - result = e.result - wasBroken = true - break - } - // for continue: just fall through to condition check below - } else { - throw e - } - } - if (!condition.execute(doScope).toBool()) { - break - } - } - if (!wasBroken) elseStatement?.let { s -> result = s.execute(scope) } - return result - } - } + val loopPlanSnapshot = slotPlanIndices(loopSlotPlan) + return DoWhileStatement(body, condition, elseStatement, label, loopPlanSnapshot, body.pos) } private suspend fun parseWhileStatement(): Statement { @@ -2765,7 +2797,7 @@ class Compiler( slotPlanStack.add(loopSlotPlan) val (canBreak, parsedBody) = try { cc.parseLoop { - if (cc.current().type == Token.Type.LBRACE) parseBlock() + if (cc.current().type == Token.Type.LBRACE) parseLoopBlock() else parseStatement() ?: throw ScriptError(start, "Bad while statement: expected statement") } } finally { @@ -2782,34 +2814,8 @@ class Compiler( cc.previous() null } - return object : Statement() { - override val pos: Pos = body.pos - override suspend fun execute(scope: Scope): Obj { - var result: Obj = ObjVoid - var wasBroken = false - while (condition.execute(scope).toBool()) { - val loopScope = scope.createChildScope().apply { skipScopeCreation = true } - if (canBreak) { - try { - result = body.execute(loopScope) - } catch (lbe: LoopBreakContinueException) { - if (lbe.label == label || lbe.label == null) { - if (lbe.doContinue) continue - else { - result = lbe.result - wasBroken = true - break - } - } else - throw lbe - } - } else - result = body.execute(loopScope) - } - if (!wasBroken) elseStatement?.let { s -> result = s.execute(scope) } - return result - } - } + val loopPlanSnapshot = slotPlanIndices(loopSlotPlan) + return WhileStatement(condition, body, elseStatement, label, canBreak, loopPlanSnapshot, body.pos) } private suspend fun parseBreakStatement(start: Pos): Statement { @@ -2843,17 +2849,7 @@ class Compiler( cc.addBreak() - return object : Statement() { - override val pos: Pos = start - override suspend fun execute(scope: Scope): Obj { - val returnValue = resultExpr?.execute(scope)// ?: ObjVoid - throw LoopBreakContinueException( - doContinue = false, - label = label, - result = returnValue ?: ObjVoid - ) - } - } + return BreakStatement(label, resultExpr, start) } private fun parseContinueStatement(start: Pos): Statement { @@ -2870,15 +2866,7 @@ class Compiler( } cc.addBreak() - return object : Statement() { - override val pos: Pos = start - override suspend fun execute(scope: Scope): Obj { - throw LoopBreakContinueException( - doContinue = true, - label = label, - ) - } - } + return ContinueStatement(label, start) } private suspend fun parseReturnStatement(start: Pos): Statement { @@ -2907,13 +2895,7 @@ class Compiler( parseExpression() } else null - return object : Statement() { - override val pos: Pos = start - override suspend fun execute(scope: Scope): Obj { - val returnValue = resultExpr?.execute(scope) ?: ObjVoid - throw ReturnException(returnValue, label) - } - } + return ReturnStatement(label, resultExpr, start) } private fun ensureRparen(): Pos { @@ -3065,32 +3047,41 @@ class Compiler( localDeclCountStack.add(0) slotPlanStack.add(paramSlotPlan) val parsedFnStatements = try { - if (actualExtern) - object : Statement() { - override val pos: Pos = start - override suspend fun execute(scope: Scope): Obj { - scope.raiseError("extern function not provided: $name") - } - } - else if (isAbstract || isDelegated) { - null - } else - withLocalNames(paramNames) { - val next = cc.peekNextNonWhitespace() - if (next.type == Token.Type.ASSIGN) { - cc.nextNonWhitespace() // consume '=' - if (cc.peekNextNonWhitespace().value == "return") - throw ScriptError(cc.currentPos(), "return is not allowed in shorthand function") - val expr = parseExpression() ?: throw ScriptError(cc.currentPos(), "Expected function body expression") - // Shorthand function returns the expression value - object : Statement() { - override val pos: Pos = expr.pos - override suspend fun execute(scope: Scope): Obj = expr.execute(scope) + val returnLabels = buildSet { + add(name) + outerLabel?.let { add(it) } + } + returnLabelStack.addLast(returnLabels) + try { + if (actualExtern) + object : Statement() { + override val pos: Pos = start + override suspend fun execute(scope: Scope): Obj { + scope.raiseError("extern function not provided: $name") } - } else { - parseBlock() } - } + else if (isAbstract || isDelegated) { + null + } else + withLocalNames(paramNames) { + val next = cc.peekNextNonWhitespace() + if (next.type == Token.Type.ASSIGN) { + cc.nextNonWhitespace() // consume '=' + if (cc.peekNextNonWhitespace().value == "return") + throw ScriptError(cc.currentPos(), "return is not allowed in shorthand function") + val expr = parseExpression() ?: throw ScriptError(cc.currentPos(), "Expected function body expression") + // Shorthand function returns the expression value + object : Statement() { + override val pos: Pos = expr.pos + override suspend fun execute(scope: Scope): Obj = expr.execute(scope) + } + } else { + parseBlock() + } + } + } finally { + returnLabelStack.removeLast() + } } finally { slotPlanStack.removeLast() } @@ -3326,6 +3317,24 @@ class Compiler( } } + private suspend fun parseLoopBlock(): Statement { + val startPos = cc.currentPos() + val t = cc.next() + if (t.type != Token.Type.LBRACE) + throw ScriptError(t.pos, "Expected block body start: {") + val block = parseScript() + val stmt = BlockStatement(block, emptyMap(), startPos) + val wrapped = wrapBytecode(stmt) + return wrapped.also { + val t1 = cc.next() + if (t1.type != Token.Type.RBRACE) + throw ScriptError(t1.pos, "unbalanced braces: expected block body end: }") + val range = MiniRange(startPos, t1.pos) + lastParsedBlockRange = range + miniSink?.onBlock(MiniBlock(range)) + } + } + private suspend fun parseVarDeclaration( isMutable: Boolean, visibility: Visibility, diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/BytecodeCompiler.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/BytecodeCompiler.kt index 8c48023..05cbbe9 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/BytecodeCompiler.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/BytecodeCompiler.kt @@ -28,6 +28,7 @@ import net.sergeych.lyng.obj.* class BytecodeCompiler( private val allowLocalSlots: Boolean = true, + private val returnLabels: Set = emptySet(), ) { private var builder = CmdBuilder() private var nextSlot = 0 @@ -49,6 +50,16 @@ class BytecodeCompiler( private val declaredLocalKeys = LinkedHashSet() private val slotTypes = mutableMapOf() private val intLoopVarNames = LinkedHashSet() + private val loopStack = ArrayDeque() + private val virtualScopeDepths = LinkedHashSet() + + private data class LoopContext( + val label: String?, + val breakLabel: CmdBuilder.Label, + val continueLabel: CmdBuilder.Label, + val breakFlagSlot: Int, + val resultSlot: Int?, + ) fun compileStatement(name: String, stmt: net.sergeych.lyng.Statement): CmdFunction? { prepareCompilation(stmt) @@ -56,6 +67,7 @@ class BytecodeCompiler( is ExpressionStatement -> compileExpression(name, stmt) is net.sergeych.lyng.IfStatement -> compileIf(name, stmt) is net.sergeych.lyng.ForInStatement -> compileForIn(name, stmt) + is net.sergeych.lyng.DoWhileStatement -> compileDoWhile(name, stmt) is BlockStatement -> compileBlock(name, stmt) is VarDeclStatement -> compileVarDecl(name, stmt) else -> null @@ -71,6 +83,7 @@ class BytecodeCompiler( name, localCount, addrCount = nextAddrSlot, + returnLabels = returnLabels, scopeSlotDepths, scopeSlotIndices, scopeSlotNames, @@ -115,6 +128,8 @@ class BytecodeCompiler( is ElvisRef -> compileElvis(ref) is CallRef -> compileCall(ref) is MethodCallRef -> compileMethodCall(ref) + is FieldRef -> compileFieldRef(ref) + is IndexRef -> compileIndexRef(ref) else -> null } } @@ -668,63 +683,256 @@ class BytecodeCompiler( } private fun compileAssign(ref: AssignRef): CompiledValue? { - val target = assignTarget(ref) ?: return null - if (!allowLocalSlots) return null - if (!target.isMutable || target.isDelegated) return null - val value = compileRef(assignValue(ref)) ?: return null - val slot = resolveSlot(target) ?: return null - if (slot < scopeSlotCount && value.type != SlotType.UNKNOWN) { - val addrSlot = ensureScopeAddr(slot) - emitStoreToAddr(value.slot, addrSlot, value.type) - } else if (slot < scopeSlotCount) { - val addrSlot = ensureScopeAddr(slot) - emitStoreToAddr(value.slot, addrSlot, SlotType.OBJ) - } else { - when (value.type) { - SlotType.INT -> builder.emit(Opcode.MOVE_INT, value.slot, slot) - SlotType.REAL -> builder.emit(Opcode.MOVE_REAL, value.slot, slot) - SlotType.BOOL -> builder.emit(Opcode.MOVE_BOOL, value.slot, slot) - else -> builder.emit(Opcode.MOVE_OBJ, value.slot, slot) + val localTarget = assignTarget(ref) + if (localTarget != null) { + if (!allowLocalSlots) return null + if (!localTarget.isMutable || localTarget.isDelegated) return null + val value = compileRef(assignValue(ref)) ?: return null + val slot = resolveSlot(localTarget) ?: return null + if (slot < scopeSlotCount && value.type != SlotType.UNKNOWN) { + val addrSlot = ensureScopeAddr(slot) + emitStoreToAddr(value.slot, addrSlot, value.type) + } else if (slot < scopeSlotCount) { + val addrSlot = ensureScopeAddr(slot) + emitStoreToAddr(value.slot, addrSlot, SlotType.OBJ) + } else { + when (value.type) { + SlotType.INT -> builder.emit(Opcode.MOVE_INT, value.slot, slot) + SlotType.REAL -> builder.emit(Opcode.MOVE_REAL, value.slot, slot) + SlotType.BOOL -> builder.emit(Opcode.MOVE_BOOL, value.slot, slot) + else -> builder.emit(Opcode.MOVE_OBJ, value.slot, slot) + } } + updateSlotType(slot, value.type) + return value } - updateSlotType(slot, value.type) - return value + val value = compileRef(assignValue(ref)) ?: return null + val target = ref.target + if (target is FieldRef) { + val receiver = compileRefWithFallback(target.target, null, Pos.builtIn) ?: return null + val nameId = builder.addConst(BytecodeConst.StringVal(target.name)) + if (nameId > 0xFFFF) return null + if (!target.isOptional) { + builder.emit(Opcode.SET_FIELD, receiver.slot, nameId, value.slot) + } else { + val nullSlot = allocSlot() + builder.emit(Opcode.CONST_NULL, nullSlot) + val cmpSlot = allocSlot() + builder.emit(Opcode.CMP_REF_EQ_OBJ, receiver.slot, nullSlot, cmpSlot) + val endLabel = builder.label() + builder.emit( + Opcode.JMP_IF_TRUE, + listOf(CmdBuilder.Operand.IntVal(cmpSlot), CmdBuilder.Operand.LabelRef(endLabel)) + ) + builder.emit(Opcode.SET_FIELD, receiver.slot, nameId, value.slot) + builder.mark(endLabel) + } + return value + } + if (target is IndexRef) { + val receiver = compileRefWithFallback(target.targetRef, null, Pos.builtIn) ?: return null + if (!target.optionalRef) { + val index = compileRefWithFallback(target.indexRef, null, Pos.builtIn) ?: return null + builder.emit(Opcode.SET_INDEX, receiver.slot, index.slot, value.slot) + } else { + val nullSlot = allocSlot() + builder.emit(Opcode.CONST_NULL, nullSlot) + val cmpSlot = allocSlot() + builder.emit(Opcode.CMP_REF_EQ_OBJ, receiver.slot, nullSlot, cmpSlot) + val endLabel = builder.label() + builder.emit( + Opcode.JMP_IF_TRUE, + listOf(CmdBuilder.Operand.IntVal(cmpSlot), CmdBuilder.Operand.LabelRef(endLabel)) + ) + val index = compileRefWithFallback(target.indexRef, null, Pos.builtIn) ?: return null + builder.emit(Opcode.SET_INDEX, receiver.slot, index.slot, value.slot) + builder.mark(endLabel) + } + return value + } + return null } private fun compileAssignOp(ref: AssignOpRef): CompiledValue? { - val target = ref.target as? LocalSlotRef ?: return null - if (!allowLocalSlots) return null - if (!target.isMutable || target.isDelegated) return null - val slot = resolveSlot(target) ?: return null - val targetType = slotTypes[slot] ?: return null - val rhs = compileRef(ref.value) ?: return null - if (slot < scopeSlotCount) { - val addrSlot = ensureScopeAddr(slot) - val current = allocSlot() - emitLoadFromAddr(addrSlot, current, targetType) + val localTarget = ref.target as? LocalSlotRef + if (localTarget != null) { + if (!allowLocalSlots) return null + if (!localTarget.isMutable || localTarget.isDelegated) return null + val slot = resolveSlot(localTarget) ?: return null + val targetType = slotTypes[slot] ?: SlotType.OBJ + var rhs = compileRef(ref.value) ?: return null + if (targetType == SlotType.OBJ && rhs.type != SlotType.OBJ) { + rhs = ensureObjSlot(rhs) + } + if (slot < scopeSlotCount) { + val addrSlot = ensureScopeAddr(slot) + val current = allocSlot() + emitLoadFromAddr(addrSlot, current, targetType) + val result = when (ref.op) { + BinOp.PLUS -> compileAssignOpBinary(targetType, rhs, current, Opcode.ADD_INT, Opcode.ADD_REAL, Opcode.ADD_OBJ) + BinOp.MINUS -> compileAssignOpBinary(targetType, rhs, current, Opcode.SUB_INT, Opcode.SUB_REAL, Opcode.SUB_OBJ) + BinOp.STAR -> compileAssignOpBinary(targetType, rhs, current, Opcode.MUL_INT, Opcode.MUL_REAL, Opcode.MUL_OBJ) + BinOp.SLASH -> compileAssignOpBinary(targetType, rhs, current, Opcode.DIV_INT, Opcode.DIV_REAL, Opcode.DIV_OBJ) + BinOp.PERCENT -> compileAssignOpBinary(targetType, rhs, current, Opcode.MOD_INT, null, Opcode.MOD_OBJ) + else -> null + } ?: return null + emitStoreToAddr(current, addrSlot, result.type) + updateSlotType(slot, result.type) + return CompiledValue(current, result.type) + } + val out = slot val result = when (ref.op) { - BinOp.PLUS -> compileAssignOpBinary(targetType, rhs, current, Opcode.ADD_INT, Opcode.ADD_REAL, Opcode.ADD_OBJ) - BinOp.MINUS -> compileAssignOpBinary(targetType, rhs, current, Opcode.SUB_INT, Opcode.SUB_REAL, Opcode.SUB_OBJ) - BinOp.STAR -> compileAssignOpBinary(targetType, rhs, current, Opcode.MUL_INT, Opcode.MUL_REAL, Opcode.MUL_OBJ) - BinOp.SLASH -> compileAssignOpBinary(targetType, rhs, current, Opcode.DIV_INT, Opcode.DIV_REAL, Opcode.DIV_OBJ) - BinOp.PERCENT -> compileAssignOpBinary(targetType, rhs, current, Opcode.MOD_INT, null, Opcode.MOD_OBJ) + BinOp.PLUS -> compileAssignOpBinary(targetType, rhs, out, Opcode.ADD_INT, Opcode.ADD_REAL, Opcode.ADD_OBJ) + BinOp.MINUS -> compileAssignOpBinary(targetType, rhs, out, Opcode.SUB_INT, Opcode.SUB_REAL, Opcode.SUB_OBJ) + BinOp.STAR -> compileAssignOpBinary(targetType, rhs, out, Opcode.MUL_INT, Opcode.MUL_REAL, Opcode.MUL_OBJ) + BinOp.SLASH -> compileAssignOpBinary(targetType, rhs, out, Opcode.DIV_INT, Opcode.DIV_REAL, Opcode.DIV_OBJ) + BinOp.PERCENT -> compileAssignOpBinary(targetType, rhs, out, Opcode.MOD_INT, null, Opcode.MOD_OBJ) else -> null } ?: return null - emitStoreToAddr(current, addrSlot, result.type) - updateSlotType(slot, result.type) - return CompiledValue(current, result.type) + updateSlotType(out, result.type) + return CompiledValue(out, result.type) } - val out = slot - val result = when (ref.op) { - BinOp.PLUS -> compileAssignOpBinary(targetType, rhs, out, Opcode.ADD_INT, Opcode.ADD_REAL, Opcode.ADD_OBJ) - BinOp.MINUS -> compileAssignOpBinary(targetType, rhs, out, Opcode.SUB_INT, Opcode.SUB_REAL, Opcode.SUB_OBJ) - BinOp.STAR -> compileAssignOpBinary(targetType, rhs, out, Opcode.MUL_INT, Opcode.MUL_REAL, Opcode.MUL_OBJ) - BinOp.SLASH -> compileAssignOpBinary(targetType, rhs, out, Opcode.DIV_INT, Opcode.DIV_REAL, Opcode.DIV_OBJ) - BinOp.PERCENT -> compileAssignOpBinary(targetType, rhs, out, Opcode.MOD_INT, null, Opcode.MOD_OBJ) + val objOp = when (ref.op) { + BinOp.PLUS -> Opcode.ADD_OBJ + BinOp.MINUS -> Opcode.SUB_OBJ + BinOp.STAR -> Opcode.MUL_OBJ + BinOp.SLASH -> Opcode.DIV_OBJ + BinOp.PERCENT -> Opcode.MOD_OBJ else -> null } ?: return null - updateSlotType(out, result.type) - return CompiledValue(out, result.type) + val fieldTarget = ref.target as? FieldRef + if (fieldTarget != null) { + val receiver = compileRefWithFallback(fieldTarget.target, null, Pos.builtIn) ?: return null + val nameId = builder.addConst(BytecodeConst.StringVal(fieldTarget.name)) + if (nameId > 0xFFFF) return null + val current = allocSlot() + val result = allocSlot() + if (!fieldTarget.isOptional) { + val rhs = compileRef(ref.value) ?: return null + builder.emit(Opcode.GET_FIELD, receiver.slot, nameId, current) + builder.emit(objOp, current, rhs.slot, result) + builder.emit(Opcode.SET_FIELD, receiver.slot, nameId, result) + updateSlotType(result, SlotType.OBJ) + return CompiledValue(result, SlotType.OBJ) + } + val nullSlot = allocSlot() + builder.emit(Opcode.CONST_NULL, nullSlot) + val cmpSlot = allocSlot() + builder.emit(Opcode.CMP_REF_EQ_OBJ, receiver.slot, nullSlot, cmpSlot) + val nullLabel = builder.label() + val endLabel = builder.label() + builder.emit( + Opcode.JMP_IF_TRUE, + listOf(CmdBuilder.Operand.IntVal(cmpSlot), CmdBuilder.Operand.LabelRef(nullLabel)) + ) + val rhs = compileRef(ref.value) ?: return null + builder.emit(Opcode.GET_FIELD, receiver.slot, nameId, current) + builder.emit(objOp, current, rhs.slot, result) + builder.emit(Opcode.SET_FIELD, receiver.slot, nameId, result) + builder.emit(Opcode.JMP, listOf(CmdBuilder.Operand.LabelRef(endLabel))) + builder.mark(nullLabel) + val rhsNull = compileRef(ref.value) ?: return null + builder.emit(Opcode.CONST_NULL, current) + builder.emit(objOp, current, rhsNull.slot, result) + builder.mark(endLabel) + updateSlotType(result, SlotType.OBJ) + return CompiledValue(result, SlotType.OBJ) + } + val indexTarget = ref.target as? IndexRef + if (indexTarget != null) { + val receiver = compileRefWithFallback(indexTarget.targetRef, null, Pos.builtIn) ?: return null + val current = allocSlot() + val result = allocSlot() + if (!indexTarget.optionalRef) { + val index = compileRefWithFallback(indexTarget.indexRef, null, Pos.builtIn) ?: return null + val rhs = compileRef(ref.value) ?: return null + builder.emit(Opcode.GET_INDEX, receiver.slot, index.slot, current) + builder.emit(objOp, current, rhs.slot, result) + builder.emit(Opcode.SET_INDEX, receiver.slot, index.slot, result) + updateSlotType(result, SlotType.OBJ) + return CompiledValue(result, SlotType.OBJ) + } + val nullSlot = allocSlot() + builder.emit(Opcode.CONST_NULL, nullSlot) + val cmpSlot = allocSlot() + builder.emit(Opcode.CMP_REF_EQ_OBJ, receiver.slot, nullSlot, cmpSlot) + val nullLabel = builder.label() + val endLabel = builder.label() + builder.emit( + Opcode.JMP_IF_TRUE, + listOf(CmdBuilder.Operand.IntVal(cmpSlot), CmdBuilder.Operand.LabelRef(nullLabel)) + ) + val index = compileRefWithFallback(indexTarget.indexRef, null, Pos.builtIn) ?: return null + val rhs = compileRef(ref.value) ?: return null + builder.emit(Opcode.GET_INDEX, receiver.slot, index.slot, current) + builder.emit(objOp, current, rhs.slot, result) + builder.emit(Opcode.SET_INDEX, receiver.slot, index.slot, result) + builder.emit(Opcode.JMP, listOf(CmdBuilder.Operand.LabelRef(endLabel))) + builder.mark(nullLabel) + val rhsNull = compileRef(ref.value) ?: return null + builder.emit(Opcode.CONST_NULL, current) + builder.emit(objOp, current, rhsNull.slot, result) + builder.mark(endLabel) + updateSlotType(result, SlotType.OBJ) + return CompiledValue(result, SlotType.OBJ) + } + return null + } + + private fun compileFieldRef(ref: FieldRef): CompiledValue? { + val receiver = compileRefWithFallback(ref.target, null, Pos.builtIn) ?: return null + val dst = allocSlot() + val nameId = builder.addConst(BytecodeConst.StringVal(ref.name)) + if (nameId > 0xFFFF) return null + if (!ref.isOptional) { + builder.emit(Opcode.GET_FIELD, receiver.slot, nameId, dst) + } else { + val nullSlot = allocSlot() + builder.emit(Opcode.CONST_NULL, nullSlot) + val cmpSlot = allocSlot() + builder.emit(Opcode.CMP_REF_EQ_OBJ, receiver.slot, nullSlot, cmpSlot) + val nullLabel = builder.label() + val endLabel = builder.label() + builder.emit( + Opcode.JMP_IF_TRUE, + listOf(CmdBuilder.Operand.IntVal(cmpSlot), CmdBuilder.Operand.LabelRef(nullLabel)) + ) + builder.emit(Opcode.GET_FIELD, receiver.slot, nameId, dst) + builder.emit(Opcode.JMP, listOf(CmdBuilder.Operand.LabelRef(endLabel))) + builder.mark(nullLabel) + builder.emit(Opcode.CONST_NULL, dst) + builder.mark(endLabel) + } + updateSlotType(dst, SlotType.OBJ) + return CompiledValue(dst, SlotType.OBJ) + } + + private fun compileIndexRef(ref: IndexRef): CompiledValue? { + val receiver = compileRefWithFallback(ref.targetRef, null, Pos.builtIn) ?: return null + val dst = allocSlot() + if (!ref.optionalRef) { + val index = compileRefWithFallback(ref.indexRef, null, Pos.builtIn) ?: return null + builder.emit(Opcode.GET_INDEX, receiver.slot, index.slot, dst) + } else { + val nullSlot = allocSlot() + builder.emit(Opcode.CONST_NULL, nullSlot) + val cmpSlot = allocSlot() + builder.emit(Opcode.CMP_REF_EQ_OBJ, receiver.slot, nullSlot, cmpSlot) + val nullLabel = builder.label() + val endLabel = builder.label() + builder.emit( + Opcode.JMP_IF_TRUE, + listOf(CmdBuilder.Operand.IntVal(cmpSlot), CmdBuilder.Operand.LabelRef(nullLabel)) + ) + val index = compileRefWithFallback(ref.indexRef, null, Pos.builtIn) ?: return null + builder.emit(Opcode.GET_INDEX, receiver.slot, index.slot, dst) + builder.emit(Opcode.JMP, listOf(CmdBuilder.Operand.LabelRef(endLabel))) + builder.mark(nullLabel) + builder.emit(Opcode.CONST_NULL, dst) + builder.mark(endLabel) + } + updateSlotType(dst, SlotType.OBJ) + return CompiledValue(dst, SlotType.OBJ) } private fun compileAssignOpBinary( @@ -972,39 +1180,97 @@ class BytecodeCompiler( } private fun compileCall(ref: CallRef): CompiledValue? { - if (ref.isOptionalInvoke) return null if (ref.target is LocalVarRef || ref.target is FastLocalVarRef || ref.target is BoundLocalVarRef) { return null } val fieldTarget = ref.target as? FieldRef - if (fieldTarget != null && !fieldTarget.isOptional) { + if (fieldTarget != null) { val receiver = compileRefWithFallback(fieldTarget.target, null, Pos.builtIn) ?: return null - val args = compileCallArgs(ref.args, ref.tailBlock) ?: return null - val encodedCount = encodeCallArgCount(args) ?: return null val methodId = builder.addConst(BytecodeConst.StringVal(fieldTarget.name)) if (methodId > 0xFFFF) return null val dst = allocSlot() + if (!fieldTarget.isOptional && !ref.isOptionalInvoke) { + val args = compileCallArgs(ref.args, ref.tailBlock) ?: return null + val encodedCount = encodeCallArgCount(args) ?: return null + builder.emit(Opcode.CALL_VIRTUAL, receiver.slot, methodId, args.base, encodedCount, dst) + return CompiledValue(dst, SlotType.UNKNOWN) + } + val nullSlot = allocSlot() + builder.emit(Opcode.CONST_NULL, nullSlot) + val cmpSlot = allocSlot() + builder.emit(Opcode.CMP_REF_EQ_OBJ, receiver.slot, nullSlot, cmpSlot) + val nullLabel = builder.label() + val endLabel = builder.label() + builder.emit( + Opcode.JMP_IF_TRUE, + listOf(CmdBuilder.Operand.IntVal(cmpSlot), CmdBuilder.Operand.LabelRef(nullLabel)) + ) + val args = compileCallArgs(ref.args, ref.tailBlock) ?: return null + val encodedCount = encodeCallArgCount(args) ?: return null builder.emit(Opcode.CALL_VIRTUAL, receiver.slot, methodId, args.base, encodedCount, dst) - return CompiledValue(dst, SlotType.UNKNOWN) + builder.emit(Opcode.JMP, listOf(CmdBuilder.Operand.LabelRef(endLabel))) + builder.mark(nullLabel) + builder.emit(Opcode.CONST_NULL, dst) + builder.mark(endLabel) + return CompiledValue(dst, SlotType.OBJ) } val callee = compileRefWithFallback(ref.target, null, Pos.builtIn) ?: return null + val dst = allocSlot() + if (!ref.isOptionalInvoke) { + val args = compileCallArgs(ref.args, ref.tailBlock) ?: return null + val encodedCount = encodeCallArgCount(args) ?: return null + builder.emit(Opcode.CALL_SLOT, callee.slot, args.base, encodedCount, dst) + return CompiledValue(dst, SlotType.UNKNOWN) + } + val nullSlot = allocSlot() + builder.emit(Opcode.CONST_NULL, nullSlot) + val cmpSlot = allocSlot() + builder.emit(Opcode.CMP_REF_EQ_OBJ, callee.slot, nullSlot, cmpSlot) + val nullLabel = builder.label() + val endLabel = builder.label() + builder.emit( + Opcode.JMP_IF_TRUE, + listOf(CmdBuilder.Operand.IntVal(cmpSlot), CmdBuilder.Operand.LabelRef(nullLabel)) + ) val args = compileCallArgs(ref.args, ref.tailBlock) ?: return null val encodedCount = encodeCallArgCount(args) ?: return null - val dst = allocSlot() builder.emit(Opcode.CALL_SLOT, callee.slot, args.base, encodedCount, dst) - return CompiledValue(dst, SlotType.UNKNOWN) + builder.emit(Opcode.JMP, listOf(CmdBuilder.Operand.LabelRef(endLabel))) + builder.mark(nullLabel) + builder.emit(Opcode.CONST_NULL, dst) + builder.mark(endLabel) + return CompiledValue(dst, SlotType.OBJ) } private fun compileMethodCall(ref: MethodCallRef): CompiledValue? { - if (ref.isOptional) return null val receiver = compileRefWithFallback(ref.receiver, null, Pos.builtIn) ?: return null - val args = compileCallArgs(ref.args, ref.tailBlock) ?: return null - val encodedCount = encodeCallArgCount(args) ?: return null val methodId = builder.addConst(BytecodeConst.StringVal(ref.name)) if (methodId > 0xFFFF) return null val dst = allocSlot() + if (!ref.isOptional) { + val args = compileCallArgs(ref.args, ref.tailBlock) ?: return null + val encodedCount = encodeCallArgCount(args) ?: return null + builder.emit(Opcode.CALL_VIRTUAL, receiver.slot, methodId, args.base, encodedCount, dst) + return CompiledValue(dst, SlotType.UNKNOWN) + } + val nullSlot = allocSlot() + builder.emit(Opcode.CONST_NULL, nullSlot) + val cmpSlot = allocSlot() + builder.emit(Opcode.CMP_REF_EQ_OBJ, receiver.slot, nullSlot, cmpSlot) + val nullLabel = builder.label() + val endLabel = builder.label() + builder.emit( + Opcode.JMP_IF_TRUE, + listOf(CmdBuilder.Operand.IntVal(cmpSlot), CmdBuilder.Operand.LabelRef(nullLabel)) + ) + val args = compileCallArgs(ref.args, ref.tailBlock) ?: return null + val encodedCount = encodeCallArgCount(args) ?: return null builder.emit(Opcode.CALL_VIRTUAL, receiver.slot, methodId, args.base, encodedCount, dst) - return CompiledValue(dst, SlotType.UNKNOWN) + builder.emit(Opcode.JMP, listOf(CmdBuilder.Operand.LabelRef(endLabel))) + builder.mark(nullLabel) + builder.emit(Opcode.CONST_NULL, dst) + builder.mark(endLabel) + return CompiledValue(dst, SlotType.OBJ) } private data class CallArgs(val base: Int, val count: Int, val planId: Int?) @@ -1083,6 +1349,7 @@ class BytecodeCompiler( name, localCount, addrCount = nextAddrSlot, + returnLabels = returnLabels, scopeSlotDepths, scopeSlotIndices, scopeSlotNames, @@ -1100,6 +1367,45 @@ class BytecodeCompiler( name, localCount, addrCount = nextAddrSlot, + returnLabels = returnLabels, + scopeSlotDepths, + scopeSlotIndices, + scopeSlotNames, + localSlotNames, + localSlotMutables, + localSlotDepths + ) + } + + private fun compileWhile(name: String, stmt: net.sergeych.lyng.WhileStatement): CmdFunction? { + if (!allowLocalSlots) return null + val resultSlot = emitWhile(stmt, true) ?: return null + builder.emit(Opcode.RET, resultSlot) + val localCount = maxOf(nextSlot, resultSlot + 1) - scopeSlotCount + return builder.build( + name, + localCount, + addrCount = nextAddrSlot, + returnLabels = returnLabels, + scopeSlotDepths, + scopeSlotIndices, + scopeSlotNames, + localSlotNames, + localSlotMutables, + localSlotDepths + ) + } + + private fun compileDoWhile(name: String, stmt: net.sergeych.lyng.DoWhileStatement): CmdFunction? { + if (!allowLocalSlots) return null + val resultSlot = emitDoWhile(stmt, true) ?: return null + builder.emit(Opcode.RET, resultSlot) + val localCount = maxOf(nextSlot, resultSlot + 1) - scopeSlotCount + return builder.build( + name, + localCount, + addrCount = nextAddrSlot, + returnLabels = returnLabels, scopeSlotDepths, scopeSlotIndices, scopeSlotNames, @@ -1117,6 +1423,7 @@ class BytecodeCompiler( name, localCount, addrCount = nextAddrSlot, + returnLabels = returnLabels, scopeSlotDepths, scopeSlotIndices, scopeSlotNames, @@ -1134,6 +1441,7 @@ class BytecodeCompiler( name, localCount, addrCount = nextAddrSlot, + returnLabels = returnLabels, scopeSlotDepths, scopeSlotIndices, scopeSlotNames, @@ -1150,6 +1458,15 @@ class BytecodeCompiler( } } + private fun emitFallbackStatement(stmt: Statement): CompiledValue { + val slot = allocSlot() + val id = builder.addFallback(stmt) + builder.emit(Opcode.EVAL_FALLBACK, id, slot) + builder.emit(Opcode.BOX_OBJ, slot, slot) + updateSlotType(slot, SlotType.OBJ) + return CompiledValue(slot, SlotType.OBJ) + } + private fun compileStatementValueOrFallback(stmt: Statement, needResult: Boolean = true): CompiledValue? { val target = if (stmt is BytecodeStatement) stmt.original else stmt return if (needResult) { @@ -1161,15 +1478,30 @@ class BytecodeCompiler( updateSlotType(resultSlot, SlotType.OBJ) CompiledValue(resultSlot, SlotType.OBJ) } + is net.sergeych.lyng.WhileStatement -> { + if (!allowLocalSlots) emitFallbackStatement(target) + else { + val resultSlot = emitWhile(target, true) ?: return null + updateSlotType(resultSlot, SlotType.OBJ) + CompiledValue(resultSlot, SlotType.OBJ) + } + } + is net.sergeych.lyng.DoWhileStatement -> { + if (!allowLocalSlots) emitFallbackStatement(target) + else { + val resultSlot = emitDoWhile(target, true) ?: return null + updateSlotType(resultSlot, SlotType.OBJ) + CompiledValue(resultSlot, SlotType.OBJ) + } + } is BlockStatement -> emitBlock(target, true) is VarDeclStatement -> emitVarDecl(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) else -> { - val slot = allocSlot() - val id = builder.addFallback(target) - builder.emit(Opcode.EVAL_FALLBACK, id, slot) - builder.emit(Opcode.BOX_OBJ, slot, slot) - updateSlotType(slot, SlotType.OBJ) - CompiledValue(slot, SlotType.OBJ) + emitFallbackStatement(target) } } } else { @@ -1187,15 +1519,28 @@ class BytecodeCompiler( val resultSlot = emitForIn(target, false) ?: return null CompiledValue(resultSlot, SlotType.OBJ) } + is net.sergeych.lyng.WhileStatement -> { + if (!allowLocalSlots) emitFallbackStatement(target) + else { + val resultSlot = emitWhile(target, false) ?: return null + CompiledValue(resultSlot, SlotType.OBJ) + } + } + is net.sergeych.lyng.DoWhileStatement -> { + if (!allowLocalSlots) emitFallbackStatement(target) + else { + val resultSlot = emitDoWhile(target, false) ?: return null + CompiledValue(resultSlot, SlotType.OBJ) + } + } is BlockStatement -> emitBlock(target, false) is VarDeclStatement -> emitVarDecl(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) else -> { - val slot = allocSlot() - val id = builder.addFallback(target) - builder.emit(Opcode.EVAL_FALLBACK, id, slot) - builder.emit(Opcode.BOX_OBJ, slot, slot) - updateSlotType(slot, SlotType.OBJ) - CompiledValue(slot, SlotType.OBJ) + emitFallbackStatement(target) } } } @@ -1241,6 +1586,40 @@ class BytecodeCompiler( return result } + private fun emitInlineBlock(stmt: BlockStatement, needResult: Boolean): CompiledValue? { + val statements = stmt.statements() + var lastValue: CompiledValue? = null + for ((index, statement) in statements.withIndex()) { + val isLast = index == statements.lastIndex + val wantResult = needResult && isLast + val value = compileStatementValueOrFallback(statement, wantResult) ?: return null + if (wantResult) { + lastValue = value + } + } + return if (needResult) { + lastValue ?: run { + val slot = allocSlot() + val voidId = builder.addConst(BytecodeConst.ObjRef(ObjVoid)) + builder.emit(Opcode.CONST_OBJ, voidId, slot) + CompiledValue(slot, SlotType.OBJ) + } + } else { + lastValue ?: run { + val slot = allocSlot() + val voidId = builder.addConst(BytecodeConst.ObjRef(ObjVoid)) + builder.emit(Opcode.CONST_OBJ, voidId, slot) + CompiledValue(slot, SlotType.OBJ) + } + } + } + + private fun compileLoopBody(stmt: Statement, needResult: Boolean): CompiledValue? { + val target = if (stmt is BytecodeStatement) stmt.original else stmt + return if (target is BlockStatement) emitInlineBlock(target, needResult) + else compileStatementValueOrFallback(target, needResult) + } + private fun emitVarDecl(stmt: VarDeclStatement): CompiledValue? { val localSlot = if (allowLocalSlots && stmt.slotIndex != null) { val depth = stmt.slotDepth ?: 0 @@ -1292,25 +1671,42 @@ class BytecodeCompiler( return value } private fun emitForIn(stmt: net.sergeych.lyng.ForInStatement, wantResult: Boolean): Int? { - if (stmt.canBreak) return null - val range = stmt.constRange ?: return null + val range = stmt.constRange + val rangeRef = if (range == null) extractRangeRef(stmt.source) else null + if (range == null && rangeRef == null) return null val loopLocalIndex = localSlotIndexByName[stmt.loopVarName] ?: return null val loopSlotId = scopeSlotCount + loopLocalIndex val iSlot = allocSlot() val endSlot = allocSlot() - val startId = builder.addConst(BytecodeConst.IntVal(range.start)) - val endId = builder.addConst(BytecodeConst.IntVal(range.endExclusive)) - builder.emit(Opcode.CONST_INT, startId, iSlot) - builder.emit(Opcode.CONST_INT, endId, endSlot) - - val resultSlot = allocSlot() - if (wantResult) { - val voidId = builder.addConst(BytecodeConst.ObjRef(ObjVoid)) - builder.emit(Opcode.CONST_OBJ, voidId, resultSlot) + if (range != null) { + val startId = builder.addConst(BytecodeConst.IntVal(range.start)) + val endId = builder.addConst(BytecodeConst.IntVal(range.endExclusive)) + builder.emit(Opcode.CONST_INT, startId, iSlot) + builder.emit(Opcode.CONST_INT, endId, endSlot) + } else { + val left = rangeRef?.left ?: return null + val right = rangeRef.right ?: return null + val startValue = compileRef(left) ?: return null + val endValue = compileRef(right) ?: return null + if (startValue.type != SlotType.INT || endValue.type != SlotType.INT) return null + emitMove(startValue, iSlot) + emitMove(endValue, endSlot) + if (rangeRef.isEndInclusive) { + builder.emit(Opcode.INC_INT, endSlot) + } } + val breakFlagSlot = allocSlot() + val falseId = builder.addConst(BytecodeConst.Bool(false)) + builder.emit(Opcode.CONST_BOOL, falseId, breakFlagSlot) + + val resultSlot = allocSlot() + val voidId = builder.addConst(BytecodeConst.ObjRef(ObjVoid)) + builder.emit(Opcode.CONST_OBJ, voidId, resultSlot) + val loopLabel = builder.label() + val continueLabel = builder.label() val endLabel = builder.label() builder.mark(loopLabel) val cmpSlot = allocSlot() @@ -1322,21 +1718,127 @@ class BytecodeCompiler( builder.emit(Opcode.MOVE_INT, iSlot, loopSlotId) updateSlotType(loopSlotId, SlotType.INT) updateSlotTypeByName(stmt.loopVarName, SlotType.INT) - val bodyValue = compileStatementValueOrFallback(stmt.body, wantResult) ?: return null + loopStack.addLast( + LoopContext(stmt.label, endLabel, continueLabel, breakFlagSlot, if (wantResult) resultSlot else null) + ) + val bodyValue = compileLoopBody(stmt.body, wantResult) ?: return null + loopStack.removeLast() if (wantResult) { val bodyObj = ensureObjSlot(bodyValue) builder.emit(Opcode.MOVE_OBJ, bodyObj.slot, resultSlot) } + builder.mark(continueLabel) builder.emit(Opcode.INC_INT, iSlot) builder.emit(Opcode.JMP, listOf(CmdBuilder.Operand.LabelRef(loopLabel))) builder.mark(endLabel) if (stmt.elseStatement != null) { + val afterElse = builder.label() + builder.emit( + Opcode.JMP_IF_TRUE, + listOf(CmdBuilder.Operand.IntVal(breakFlagSlot), CmdBuilder.Operand.LabelRef(afterElse)) + ) val elseValue = compileStatementValueOrFallback(stmt.elseStatement, wantResult) ?: return null if (wantResult) { val elseObj = ensureObjSlot(elseValue) builder.emit(Opcode.MOVE_OBJ, elseObj.slot, resultSlot) } + builder.mark(afterElse) + } + return resultSlot + } + + private fun emitWhile(stmt: net.sergeych.lyng.WhileStatement, wantResult: Boolean): Int? { + val breakFlagSlot = allocSlot() + val falseId = builder.addConst(BytecodeConst.Bool(false)) + builder.emit(Opcode.CONST_BOOL, falseId, breakFlagSlot) + + val resultSlot = allocSlot() + val voidId = builder.addConst(BytecodeConst.ObjRef(ObjVoid)) + builder.emit(Opcode.CONST_OBJ, voidId, resultSlot) + + val loopLabel = builder.label() + val continueLabel = builder.label() + val endLabel = builder.label() + builder.mark(loopLabel) + val condition = compileCondition(stmt.condition, stmt.pos) ?: return null + if (condition.type != SlotType.BOOL) return null + builder.emit( + Opcode.JMP_IF_FALSE, + listOf(CmdBuilder.Operand.IntVal(condition.slot), CmdBuilder.Operand.LabelRef(endLabel)) + ) + loopStack.addLast( + LoopContext(stmt.label, endLabel, continueLabel, breakFlagSlot, if (wantResult) resultSlot else null) + ) + val bodyValue = compileLoopBody(stmt.body, wantResult) ?: return null + loopStack.removeLast() + if (wantResult) { + val bodyObj = ensureObjSlot(bodyValue) + builder.emit(Opcode.MOVE_OBJ, bodyObj.slot, resultSlot) + } + builder.mark(continueLabel) + builder.emit(Opcode.JMP, listOf(CmdBuilder.Operand.LabelRef(loopLabel))) + + builder.mark(endLabel) + if (stmt.elseStatement != null) { + val afterElse = builder.label() + builder.emit( + Opcode.JMP_IF_TRUE, + listOf(CmdBuilder.Operand.IntVal(breakFlagSlot), CmdBuilder.Operand.LabelRef(afterElse)) + ) + val elseValue = compileStatementValueOrFallback(stmt.elseStatement, wantResult) ?: return null + if (wantResult) { + val elseObj = ensureObjSlot(elseValue) + builder.emit(Opcode.MOVE_OBJ, elseObj.slot, resultSlot) + } + builder.mark(afterElse) + } + return resultSlot + } + + private fun emitDoWhile(stmt: net.sergeych.lyng.DoWhileStatement, wantResult: Boolean): Int? { + val breakFlagSlot = allocSlot() + val falseId = builder.addConst(BytecodeConst.Bool(false)) + builder.emit(Opcode.CONST_BOOL, falseId, breakFlagSlot) + + val resultSlot = allocSlot() + val voidId = builder.addConst(BytecodeConst.ObjRef(ObjVoid)) + builder.emit(Opcode.CONST_OBJ, voidId, resultSlot) + + val loopLabel = builder.label() + val continueLabel = builder.label() + val endLabel = builder.label() + builder.mark(loopLabel) + loopStack.addLast( + LoopContext(stmt.label, endLabel, continueLabel, breakFlagSlot, if (wantResult) resultSlot else null) + ) + val bodyValue = compileStatementValueOrFallback(stmt.body, wantResult) ?: return null + loopStack.removeLast() + if (wantResult) { + val bodyObj = ensureObjSlot(bodyValue) + builder.emit(Opcode.MOVE_OBJ, bodyObj.slot, resultSlot) + } + builder.mark(continueLabel) + val condition = compileCondition(stmt.condition, stmt.pos) ?: return null + if (condition.type != SlotType.BOOL) return null + builder.emit( + Opcode.JMP_IF_TRUE, + listOf(CmdBuilder.Operand.IntVal(condition.slot), CmdBuilder.Operand.LabelRef(loopLabel)) + ) + + builder.mark(endLabel) + if (stmt.elseStatement != null) { + val afterElse = builder.label() + builder.emit( + Opcode.JMP_IF_TRUE, + listOf(CmdBuilder.Operand.IntVal(breakFlagSlot), CmdBuilder.Operand.LabelRef(afterElse)) + ) + val elseValue = compileStatementValueOrFallback(stmt.elseStatement, wantResult) ?: return null + if (wantResult) { + val elseObj = ensureObjSlot(elseValue) + builder.emit(Opcode.MOVE_OBJ, elseObj.slot, resultSlot) + } + builder.mark(afterElse) } return resultSlot } @@ -1414,6 +1916,68 @@ class BytecodeCompiler( } } + private fun findLoopContext(label: String?): LoopContext? { + if (loopStack.isEmpty()) return null + if (label == null) return loopStack.last() + for (ctx in loopStack.reversed()) { + if (ctx.label == label) return ctx + } + return null + } + + private fun compileBreak(stmt: net.sergeych.lyng.BreakStatement): CompiledValue? { + val ctx = findLoopContext(stmt.label) ?: return null + val value = stmt.resultExpr?.let { compileStatementValueOrFallback(it) } + if (ctx.resultSlot != null) { + val objValue = value?.let { ensureObjSlot(it) } ?: run { + val slot = allocSlot() + val voidId = builder.addConst(BytecodeConst.ObjRef(ObjVoid)) + builder.emit(Opcode.CONST_OBJ, voidId, slot) + updateSlotType(slot, SlotType.OBJ) + CompiledValue(slot, SlotType.OBJ) + } + builder.emit(Opcode.MOVE_OBJ, objValue.slot, ctx.resultSlot) + } else if (value != null) { + ensureObjSlot(value) + } + 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))) + return CompiledValue(ctx.breakFlagSlot, SlotType.BOOL) + } + + private fun compileContinue(stmt: net.sergeych.lyng.ContinueStatement): CompiledValue? { + val ctx = findLoopContext(stmt.label) ?: return null + builder.emit(Opcode.JMP, listOf(CmdBuilder.Operand.LabelRef(ctx.continueLabel))) + return CompiledValue(ctx.breakFlagSlot, SlotType.BOOL) + } + + private fun compileReturn(stmt: net.sergeych.lyng.ReturnStatement): CompiledValue? { + val value = stmt.resultExpr?.let { compileStatementValueOrFallback(it) } ?: run { + val slot = allocSlot() + val voidId = builder.addConst(BytecodeConst.ObjRef(ObjVoid)) + builder.emit(Opcode.CONST_OBJ, voidId, slot) + updateSlotType(slot, SlotType.OBJ) + CompiledValue(slot, SlotType.OBJ) + } + val label = stmt.label + if (label == null || returnLabels.contains(label)) { + builder.emit(Opcode.RET, value.slot) + } else { + val labelId = builder.addConst(BytecodeConst.StringVal(label)) + builder.emit(Opcode.RET_LABEL, labelId, value.slot) + } + return value + } + + private fun compileThrow(stmt: net.sergeych.lyng.ThrowStatement): CompiledValue? { + val value = compileStatementValueOrFallback(stmt.throwExpr) ?: return null + val objValue = ensureObjSlot(value) + val posId = builder.addConst(BytecodeConst.PosVal(stmt.pos)) + builder.emit(Opcode.THROW, posId, objValue.slot) + return objValue + } + private fun resetAddrCache() { addrSlotByScopeSlot.clear() } @@ -1467,7 +2031,8 @@ class BytecodeCompiler( SlotType.INT -> builder.emit(Opcode.MOVE_INT, srcSlot, dstSlot) SlotType.REAL -> builder.emit(Opcode.MOVE_REAL, srcSlot, dstSlot) SlotType.BOOL -> builder.emit(Opcode.MOVE_BOOL, srcSlot, dstSlot) - else -> builder.emit(Opcode.MOVE_OBJ, srcSlot, dstSlot) + SlotType.OBJ -> builder.emit(Opcode.MOVE_OBJ, srcSlot, dstSlot) + else -> builder.emit(Opcode.BOX_OBJ, srcSlot, dstSlot) } } @@ -1515,7 +2080,7 @@ class BytecodeCompiler( if (localIndex != null) return scopeSlotCount + localIndex val nameIndex = localSlotIndexByName[ref.name] if (nameIndex != null) return scopeSlotCount + nameIndex - val scopeKey = ScopeSlotKey(refDepth(ref), refSlot(ref)) + val scopeKey = ScopeSlotKey(effectiveScopeDepth(ref), refSlot(ref)) return scopeSlotMap[scopeKey] } @@ -1542,8 +2107,11 @@ class BytecodeCompiler( declaredLocalKeys.clear() intLoopVarNames.clear() addrSlotByScopeSlot.clear() + loopStack.clear() + virtualScopeDepths.clear() if (allowLocalSlots) { collectLoopVarNames(stmt) + collectVirtualScopeDepths(stmt, 0) collectScopeSlots(stmt) collectLoopSlotPlans(stmt, 0) } @@ -1569,7 +2137,7 @@ class BytecodeCompiler( } names.add(info.name) mutables[index] = info.isMutable - depths[index] = info.depth + depths[index] = effectiveLocalDepth(info.depth) index += 1 } localSlotNames = names.toTypedArray() @@ -1595,7 +2163,11 @@ class BytecodeCompiler( val slotIndex = stmt.slotIndex val slotDepth = stmt.slotDepth if (allowLocalSlots && slotIndex != null && slotDepth != null) { - declaredLocalKeys.add(ScopeSlotKey(slotDepth, slotIndex)) + val key = ScopeSlotKey(slotDepth, slotIndex) + declaredLocalKeys.add(key) + if (!localSlotInfoMap.containsKey(key)) { + localSlotInfoMap[key] = LocalSlotInfo(stmt.name, stmt.isMutable, slotDepth) + } } stmt.initializer?.let { collectScopeSlots(it) } } @@ -1609,6 +2181,25 @@ class BytecodeCompiler( collectScopeSlots(stmt.body) stmt.elseStatement?.let { collectScopeSlots(it) } } + is net.sergeych.lyng.WhileStatement -> { + collectScopeSlots(stmt.condition) + collectScopeSlots(stmt.body) + stmt.elseStatement?.let { collectScopeSlots(it) } + } + is net.sergeych.lyng.DoWhileStatement -> { + collectScopeSlots(stmt.body) + collectScopeSlots(stmt.condition) + stmt.elseStatement?.let { collectScopeSlots(it) } + } + is net.sergeych.lyng.BreakStatement -> { + stmt.resultExpr?.let { collectScopeSlots(it) } + } + is net.sergeych.lyng.ReturnStatement -> { + stmt.resultExpr?.let { collectScopeSlots(it) } + } + is net.sergeych.lyng.ThrowStatement -> { + collectScopeSlots(stmt.throwExpr) + } else -> {} } } @@ -1631,6 +2222,30 @@ class BytecodeCompiler( collectLoopSlotPlans(stmt.body, loopDepth) stmt.elseStatement?.let { collectLoopSlotPlans(it, loopDepth) } } + is net.sergeych.lyng.WhileStatement -> { + collectLoopSlotPlans(stmt.condition, scopeDepth) + val loopDepth = scopeDepth + 1 + for ((name, slotIndex) in stmt.loopSlotPlan) { + val key = ScopeSlotKey(loopDepth, slotIndex) + if (!localSlotInfoMap.containsKey(key)) { + localSlotInfoMap[key] = LocalSlotInfo(name, isMutable = true, depth = loopDepth) + } + } + collectLoopSlotPlans(stmt.body, loopDepth) + stmt.elseStatement?.let { collectLoopSlotPlans(it, loopDepth) } + } + is net.sergeych.lyng.DoWhileStatement -> { + val loopDepth = scopeDepth + 1 + for ((name, slotIndex) in stmt.loopSlotPlan) { + val key = ScopeSlotKey(loopDepth, slotIndex) + if (!localSlotInfoMap.containsKey(key)) { + localSlotInfoMap[key] = LocalSlotInfo(name, isMutable = true, depth = loopDepth) + } + } + collectLoopSlotPlans(stmt.body, loopDepth) + collectLoopSlotPlans(stmt.condition, loopDepth) + stmt.elseStatement?.let { collectLoopSlotPlans(it, loopDepth) } + } is BlockStatement -> { val nextDepth = scopeDepth + 1 for (child in stmt.statements()) { @@ -1648,6 +2263,15 @@ class BytecodeCompiler( is ExpressionStatement -> { // no-op } + is net.sergeych.lyng.BreakStatement -> { + stmt.resultExpr?.let { collectLoopSlotPlans(it, scopeDepth) } + } + is net.sergeych.lyng.ReturnStatement -> { + stmt.resultExpr?.let { collectLoopSlotPlans(it, scopeDepth) } + } + is net.sergeych.lyng.ThrowStatement -> { + collectLoopSlotPlans(stmt.throwExpr, scopeDepth) + } else -> {} } } @@ -1666,6 +2290,16 @@ class BytecodeCompiler( collectLoopVarNames(stmt.body) stmt.elseStatement?.let { collectLoopVarNames(it) } } + is net.sergeych.lyng.WhileStatement -> { + collectLoopVarNames(stmt.condition) + collectLoopVarNames(stmt.body) + stmt.elseStatement?.let { collectLoopVarNames(it) } + } + is net.sergeych.lyng.DoWhileStatement -> { + collectLoopVarNames(stmt.body) + collectLoopVarNames(stmt.condition) + stmt.elseStatement?.let { collectLoopVarNames(it) } + } is BlockStatement -> { for (child in stmt.statements()) { collectLoopVarNames(child) @@ -1680,6 +2314,15 @@ class BytecodeCompiler( stmt.elseBody?.let { collectLoopVarNames(it) } } is ExpressionStatement -> collectLoopVarNamesRef(stmt.ref) + is net.sergeych.lyng.BreakStatement -> { + stmt.resultExpr?.let { collectLoopVarNames(it) } + } + is net.sergeych.lyng.ReturnStatement -> { + stmt.resultExpr?.let { collectLoopVarNames(it) } + } + is net.sergeych.lyng.ThrowStatement -> { + collectLoopVarNames(stmt.throwExpr) + } else -> {} } } @@ -1706,6 +2349,11 @@ class BytecodeCompiler( collectLoopVarNamesRef(ref.left) collectLoopVarNamesRef(ref.right) } + is FieldRef -> collectLoopVarNamesRef(ref.target) + is IndexRef -> { + collectLoopVarNamesRef(ref.targetRef) + collectLoopVarNamesRef(ref.indexRef) + } else -> {} } } @@ -1722,7 +2370,7 @@ class BytecodeCompiler( } return } - val key = ScopeSlotKey(refDepth(ref), refSlot(ref)) + val key = ScopeSlotKey(effectiveScopeDepth(ref), refSlot(ref)) if (!scopeSlotMap.containsKey(key)) { scopeSlotMap[key] = scopeSlotMap.size } @@ -1746,7 +2394,7 @@ class BytecodeCompiler( localSlotInfoMap[localKey] = LocalSlotInfo(target.name, target.isMutable, localKey.depth) } } else { - val key = ScopeSlotKey(refDepth(target), refSlot(target)) + val key = ScopeSlotKey(effectiveScopeDepth(target), refSlot(target)) if (!scopeSlotMap.containsKey(key)) { scopeSlotMap[key] = scopeSlotMap.size } @@ -1771,6 +2419,11 @@ class BytecodeCompiler( collectScopeSlotsRef(ref.left) collectScopeSlotsRef(ref.right) } + is FieldRef -> collectScopeSlotsRef(ref.target) + is IndexRef -> { + collectScopeSlotsRef(ref.targetRef) + collectScopeSlotsRef(ref.indexRef) + } is CallRef -> { collectScopeSlotsRef(ref.target) collectScopeSlotsArgs(ref.args) @@ -1792,5 +2445,98 @@ class BytecodeCompiler( } } + private fun collectVirtualScopeDepths(stmt: Statement, scopeDepth: Int) { + if (stmt is BytecodeStatement) { + collectVirtualScopeDepths(stmt.original, scopeDepth) + return + } + when (stmt) { + is net.sergeych.lyng.ForInStatement -> { + collectVirtualScopeDepths(stmt.source, scopeDepth) + val loopDepth = scopeDepth + 1 + virtualScopeDepths.add(loopDepth) + val bodyTarget = if (stmt.body is BytecodeStatement) stmt.body.original else stmt.body + if (bodyTarget is BlockStatement) { + // Loop bodies are inlined in bytecode, so their block scope is virtual. + virtualScopeDepths.add(loopDepth + 1) + } + collectVirtualScopeDepths(stmt.body, loopDepth) + stmt.elseStatement?.let { collectVirtualScopeDepths(it, loopDepth) } + } + is net.sergeych.lyng.WhileStatement -> { + collectVirtualScopeDepths(stmt.condition, scopeDepth) + val loopDepth = scopeDepth + 1 + virtualScopeDepths.add(loopDepth) + collectVirtualScopeDepths(stmt.body, loopDepth) + stmt.elseStatement?.let { collectVirtualScopeDepths(it, loopDepth) } + } + is net.sergeych.lyng.DoWhileStatement -> { + val loopDepth = scopeDepth + 1 + virtualScopeDepths.add(loopDepth) + collectVirtualScopeDepths(stmt.body, loopDepth) + collectVirtualScopeDepths(stmt.condition, loopDepth) + stmt.elseStatement?.let { collectVirtualScopeDepths(it, loopDepth) } + } + is BlockStatement -> { + val nextDepth = scopeDepth + 1 + for (child in stmt.statements()) { + collectVirtualScopeDepths(child, nextDepth) + } + } + is IfStatement -> { + collectVirtualScopeDepths(stmt.condition, scopeDepth) + collectVirtualScopeDepths(stmt.ifBody, scopeDepth) + stmt.elseBody?.let { collectVirtualScopeDepths(it, scopeDepth) } + } + is VarDeclStatement -> { + stmt.initializer?.let { collectVirtualScopeDepths(it, scopeDepth) } + } + is ExpressionStatement -> { + // no-op + } + is net.sergeych.lyng.BreakStatement -> { + stmt.resultExpr?.let { collectVirtualScopeDepths(it, scopeDepth) } + } + is net.sergeych.lyng.ReturnStatement -> { + stmt.resultExpr?.let { collectVirtualScopeDepths(it, scopeDepth) } + } + is net.sergeych.lyng.ThrowStatement -> { + collectVirtualScopeDepths(stmt.throwExpr, scopeDepth) + } + else -> {} + } + } + + private fun effectiveScopeDepth(ref: LocalSlotRef): Int { + val baseDepth = refDepth(ref) + if (baseDepth == 0 || virtualScopeDepths.isEmpty()) return baseDepth + val targetDepth = refScopeDepth(ref) + val currentDepth = targetDepth + baseDepth + var virtualCount = 0 + for (depth in virtualScopeDepths) { + if (depth > targetDepth && depth <= currentDepth) { + virtualCount += 1 + } + } + return baseDepth - virtualCount + } + + private fun extractRangeRef(source: Statement): RangeRef? { + val target = if (source is BytecodeStatement) source.original else source + val expr = target as? ExpressionStatement ?: return null + return expr.ref as? RangeRef + } + + private fun effectiveLocalDepth(depth: Int): Int { + if (depth == 0 || virtualScopeDepths.isEmpty()) return depth + var virtualCount = 0 + for (virtualDepth in virtualScopeDepths) { + if (virtualDepth <= depth) { + virtualCount += 1 + } + } + return depth - virtualCount + } + private data class ScopeSlotKey(val depth: Int, val slot: Int) } diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/BytecodeConst.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/BytecodeConst.kt index 0c555af..d89f18a 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/BytecodeConst.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/BytecodeConst.kt @@ -16,6 +16,7 @@ package net.sergeych.lyng.bytecode +import net.sergeych.lyng.Pos import net.sergeych.lyng.Visibility import net.sergeych.lyng.obj.Obj @@ -25,6 +26,7 @@ sealed class BytecodeConst { data class IntVal(val value: Long) : BytecodeConst() data class RealVal(val value: Double) : BytecodeConst() data class StringVal(val value: String) : BytecodeConst() + data class PosVal(val pos: Pos) : BytecodeConst() data class ObjRef(val value: Obj) : BytecodeConst() data class SlotPlan(val plan: Map) : BytecodeConst() data class LocalDecl( diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/BytecodeStatement.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/BytecodeStatement.kt index bad44bb..5bdfcf5 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/BytecodeStatement.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/BytecodeStatement.kt @@ -20,6 +20,7 @@ import net.sergeych.lyng.Pos import net.sergeych.lyng.Scope import net.sergeych.lyng.Statement import net.sergeych.lyng.obj.Obj +import net.sergeych.lyng.obj.RangeRef class BytecodeStatement private constructor( val original: Statement, @@ -34,12 +35,17 @@ class BytecodeStatement private constructor( internal fun bytecodeFunction(): CmdFunction = function companion object { - fun wrap(statement: Statement, nameHint: String, allowLocalSlots: Boolean): Statement { + fun wrap( + statement: Statement, + nameHint: String, + allowLocalSlots: Boolean, + returnLabels: Set = emptySet(), + ): Statement { if (statement is BytecodeStatement) return statement val hasUnsupported = containsUnsupportedStatement(statement) if (hasUnsupported) return unwrapDeep(statement) val safeLocals = allowLocalSlots - val compiler = BytecodeCompiler(allowLocalSlots = safeLocals) + val compiler = BytecodeCompiler(allowLocalSlots = safeLocals, returnLabels = returnLabels) val compiled = compiler.compileStatement(nameHint, statement) val fn = compiled ?: run { val builder = CmdBuilder() @@ -51,6 +57,7 @@ class BytecodeStatement private constructor( nameHint, localCount = 1, addrCount = 0, + returnLabels = returnLabels, localSlotNames = emptyArray(), localSlotMutables = BooleanArray(0), localSlotDepths = IntArray(0) @@ -69,15 +76,35 @@ class BytecodeStatement private constructor( (target.elseBody?.let { containsUnsupportedStatement(it) } ?: false) } is net.sergeych.lyng.ForInStatement -> { - target.constRange == null || target.canBreak || + val rangeSource = target.source + val rangeRef = (rangeSource as? net.sergeych.lyng.ExpressionStatement)?.ref as? RangeRef + val hasRange = target.constRange != null || rangeRef != null + !hasRange || containsUnsupportedStatement(target.source) || containsUnsupportedStatement(target.body) || (target.elseStatement?.let { containsUnsupportedStatement(it) } ?: false) } + is net.sergeych.lyng.WhileStatement -> { + containsUnsupportedStatement(target.condition) || + containsUnsupportedStatement(target.body) || + (target.elseStatement?.let { containsUnsupportedStatement(it) } ?: false) + } + is net.sergeych.lyng.DoWhileStatement -> { + containsUnsupportedStatement(target.body) || + containsUnsupportedStatement(target.condition) || + (target.elseStatement?.let { containsUnsupportedStatement(it) } ?: false) + } is net.sergeych.lyng.BlockStatement -> target.statements().any { containsUnsupportedStatement(it) } is net.sergeych.lyng.VarDeclStatement -> target.initializer?.let { containsUnsupportedStatement(it) } ?: false + is net.sergeych.lyng.BreakStatement -> + target.resultExpr?.let { containsUnsupportedStatement(it) } ?: false + is net.sergeych.lyng.ContinueStatement -> false + is net.sergeych.lyng.ReturnStatement -> + target.resultExpr?.let { containsUnsupportedStatement(it) } ?: false + is net.sergeych.lyng.ThrowStatement -> + containsUnsupportedStatement(target.throwExpr) else -> true } } @@ -126,6 +153,39 @@ class BytecodeStatement private constructor( stmt.pos ) } + is net.sergeych.lyng.WhileStatement -> { + net.sergeych.lyng.WhileStatement( + unwrapDeep(stmt.condition), + unwrapDeep(stmt.body), + stmt.elseStatement?.let { unwrapDeep(it) }, + stmt.label, + stmt.canBreak, + stmt.loopSlotPlan, + stmt.pos + ) + } + is net.sergeych.lyng.DoWhileStatement -> { + net.sergeych.lyng.DoWhileStatement( + unwrapDeep(stmt.body), + unwrapDeep(stmt.condition), + stmt.elseStatement?.let { unwrapDeep(it) }, + stmt.label, + stmt.loopSlotPlan, + stmt.pos + ) + } + is net.sergeych.lyng.BreakStatement -> { + val resultExpr = stmt.resultExpr?.let { unwrapDeep(it) } + net.sergeych.lyng.BreakStatement(stmt.label, resultExpr, stmt.pos) + } + is net.sergeych.lyng.ContinueStatement -> + net.sergeych.lyng.ContinueStatement(stmt.label, stmt.pos) + is net.sergeych.lyng.ReturnStatement -> { + val resultExpr = stmt.resultExpr?.let { unwrapDeep(it) } + net.sergeych.lyng.ReturnStatement(stmt.label, resultExpr, stmt.pos) + } + is net.sergeych.lyng.ThrowStatement -> + net.sergeych.lyng.ThrowStatement(unwrapDeep(stmt.throwExpr), stmt.pos) else -> stmt } } diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/CmdBuilder.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/CmdBuilder.kt index cd59492..884a8f4 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/CmdBuilder.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/CmdBuilder.kt @@ -60,6 +60,7 @@ class CmdBuilder { name: String, localCount: Int, addrCount: Int = 0, + returnLabels: Set = emptySet(), scopeSlotDepths: IntArray = IntArray(0), scopeSlotIndices: IntArray = IntArray(0), scopeSlotNames: Array = emptyArray(), @@ -100,6 +101,7 @@ class CmdBuilder { name = name, localCount = localCount, addrCount = addrCount, + returnLabels = returnLabels, scopeSlotCount = scopeSlotCount, scopeSlotDepths = scopeSlotDepths, scopeSlotIndices = scopeSlotIndices, @@ -120,6 +122,8 @@ class CmdBuilder { 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 -> listOf(OperandKind.SLOT, OperandKind.SLOT) + Opcode.RET_LABEL, Opcode.THROW -> + listOf(OperandKind.CONST, OperandKind.SLOT) Opcode.RESOLVE_SCOPE_SLOT -> listOf(OperandKind.SLOT, OperandKind.ADDR) Opcode.LOAD_OBJ_ADDR, Opcode.LOAD_INT_ADDR, Opcode.LOAD_REAL_ADDR, Opcode.LOAD_BOOL_ADDR -> @@ -205,6 +209,8 @@ class CmdBuilder { Opcode.CONST_BOOL -> CmdConstBool(operands[0], operands[1]) Opcode.CONST_NULL -> CmdConstNull(operands[0]) Opcode.BOX_OBJ -> CmdBoxObj(operands[0], operands[1]) + Opcode.RET_LABEL -> CmdRetLabel(operands[0], operands[1]) + Opcode.THROW -> CmdThrow(operands[0], operands[1]) Opcode.RESOLVE_SCOPE_SLOT -> CmdResolveScopeSlot(operands[0], operands[1]) Opcode.LOAD_OBJ_ADDR -> CmdLoadObjAddr(operands[0], operands[1]) Opcode.STORE_OBJ_ADDR -> CmdStoreObjAddr(operands[0], operands[1]) diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/CmdDisassembler.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/CmdDisassembler.kt index e444ef7..83f7641 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/CmdDisassembler.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/CmdDisassembler.kt @@ -160,7 +160,9 @@ object CmdDisassembler { is CmdJmpIfTrue -> Opcode.JMP_IF_TRUE to intArrayOf(cmd.cond, cmd.target) is CmdJmpIfFalse -> Opcode.JMP_IF_FALSE to intArrayOf(cmd.cond, cmd.target) is CmdRet -> Opcode.RET to intArrayOf(cmd.slot) + is CmdRetLabel -> Opcode.RET_LABEL to intArrayOf(cmd.labelId, cmd.slot) is CmdRetVoid -> Opcode.RET_VOID to intArrayOf() + is CmdThrow -> Opcode.THROW to intArrayOf(cmd.posId, cmd.slot) is CmdPushScope -> Opcode.PUSH_SCOPE to intArrayOf(cmd.planId) is CmdPopScope -> Opcode.POP_SCOPE to intArrayOf() is CmdPushSlotPlan -> Opcode.PUSH_SLOT_PLAN to intArrayOf(cmd.planId) @@ -194,6 +196,8 @@ object CmdDisassembler { 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 -> listOf(OperandKind.SLOT, OperandKind.SLOT) + Opcode.RET_LABEL, Opcode.THROW -> + listOf(OperandKind.CONST, OperandKind.SLOT) Opcode.RESOLVE_SCOPE_SLOT -> listOf(OperandKind.SLOT, OperandKind.ADDR) Opcode.LOAD_OBJ_ADDR, Opcode.LOAD_INT_ADDR, Opcode.LOAD_REAL_ADDR, Opcode.LOAD_BOOL_ADDR -> diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/CmdFunction.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/CmdFunction.kt index 933b777..2425ced 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/CmdFunction.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/CmdFunction.kt @@ -20,6 +20,7 @@ data class CmdFunction( val name: String, val localCount: Int, val addrCount: Int, + val returnLabels: Set, val scopeSlotCount: Int, val scopeSlotDepths: IntArray, val scopeSlotIndices: IntArray, diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/CmdRuntime.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/CmdRuntime.kt index 6a33631..f1de05b 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/CmdRuntime.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/CmdRuntime.kt @@ -18,6 +18,9 @@ package net.sergeych.lyng.bytecode import net.sergeych.lyng.Arguments import net.sergeych.lyng.PerfFlags +import net.sergeych.lyng.PerfStats +import net.sergeych.lyng.Pos +import net.sergeych.lyng.ReturnException import net.sergeych.lyng.Scope import net.sergeych.lyng.obj.* @@ -49,7 +52,7 @@ class CmdNop : Cmd() { class CmdMoveObj(internal val src: Int, internal val dst: Int) : Cmd() { override suspend fun perform(frame: CmdFrame) { - frame.setObj(dst, frame.getObj(src)) + frame.setObj(dst, frame.slotToObj(src)) return } } @@ -657,28 +660,28 @@ class CmdCmpNeqRealInt(internal val a: Int, internal val b: Int, internal val ds class CmdCmpEqObj(internal val a: Int, internal val b: Int, internal val dst: Int) : Cmd() { override suspend fun perform(frame: CmdFrame) { - frame.setBool(dst, frame.getObj(a) == frame.getObj(b)) + frame.setBool(dst, frame.slotToObj(a) == frame.slotToObj(b)) return } } class CmdCmpNeqObj(internal val a: Int, internal val b: Int, internal val dst: Int) : Cmd() { override suspend fun perform(frame: CmdFrame) { - frame.setBool(dst, frame.getObj(a) != frame.getObj(b)) + frame.setBool(dst, frame.slotToObj(a) != frame.slotToObj(b)) return } } class CmdCmpRefEqObj(internal val a: Int, internal val b: Int, internal val dst: Int) : Cmd() { override suspend fun perform(frame: CmdFrame) { - frame.setBool(dst, frame.getObj(a) === frame.getObj(b)) + frame.setBool(dst, frame.slotToObj(a) === frame.slotToObj(b)) return } } class CmdCmpRefNeqObj(internal val a: Int, internal val b: Int, internal val dst: Int) : Cmd() { override suspend fun perform(frame: CmdFrame) { - frame.setBool(dst, frame.getObj(a) !== frame.getObj(b)) + frame.setBool(dst, frame.slotToObj(a) !== frame.slotToObj(b)) return } } @@ -706,63 +709,148 @@ class CmdOrBool(internal val a: Int, internal val b: Int, internal val dst: Int) class CmdCmpLtObj(internal val a: Int, internal val b: Int, internal val dst: Int) : Cmd() { override suspend fun perform(frame: CmdFrame) { - frame.setBool(dst, frame.getObj(a).compareTo(frame.scope, frame.getObj(b)) < 0) + frame.setBool(dst, frame.slotToObj(a).compareTo(frame.scope, frame.slotToObj(b)) < 0) return } } class CmdCmpLteObj(internal val a: Int, internal val b: Int, internal val dst: Int) : Cmd() { override suspend fun perform(frame: CmdFrame) { - frame.setBool(dst, frame.getObj(a).compareTo(frame.scope, frame.getObj(b)) <= 0) + frame.setBool(dst, frame.slotToObj(a).compareTo(frame.scope, frame.slotToObj(b)) <= 0) return } } class CmdCmpGtObj(internal val a: Int, internal val b: Int, internal val dst: Int) : Cmd() { override suspend fun perform(frame: CmdFrame) { - frame.setBool(dst, frame.getObj(a).compareTo(frame.scope, frame.getObj(b)) > 0) + frame.setBool(dst, frame.slotToObj(a).compareTo(frame.scope, frame.slotToObj(b)) > 0) return } } class CmdCmpGteObj(internal val a: Int, internal val b: Int, internal val dst: Int) : Cmd() { override suspend fun perform(frame: CmdFrame) { - frame.setBool(dst, frame.getObj(a).compareTo(frame.scope, frame.getObj(b)) >= 0) + frame.setBool(dst, frame.slotToObj(a).compareTo(frame.scope, frame.slotToObj(b)) >= 0) return } } class CmdAddObj(internal val a: Int, internal val b: Int, internal val dst: Int) : Cmd() { override suspend fun perform(frame: CmdFrame) { - frame.setObj(dst, frame.getObj(a).plus(frame.scope, frame.getObj(b))) + val scopeSlotCount = frame.fn.scopeSlotCount + if (a >= scopeSlotCount && b >= scopeSlotCount) { + val la = a - scopeSlotCount + val lb = b - scopeSlotCount + val ta = frame.frame.getSlotTypeCode(la) + val tb = frame.frame.getSlotTypeCode(lb) + if (ta == SlotType.INT.code && tb == SlotType.INT.code) { + frame.setInt(dst, frame.frame.getInt(la) + frame.frame.getInt(lb)) + return + } + if (ta == SlotType.REAL.code || tb == SlotType.REAL.code) { + val av = if (ta == SlotType.REAL.code) frame.frame.getReal(la) else frame.frame.getInt(la).toDouble() + val bv = if (tb == SlotType.REAL.code) frame.frame.getReal(lb) else frame.frame.getInt(lb).toDouble() + frame.setReal(dst, av + bv) + return + } + } + frame.setObj(dst, frame.slotToObj(a).plus(frame.scope, frame.slotToObj(b))) return } } class CmdSubObj(internal val a: Int, internal val b: Int, internal val dst: Int) : Cmd() { override suspend fun perform(frame: CmdFrame) { - frame.setObj(dst, frame.getObj(a).minus(frame.scope, frame.getObj(b))) + val scopeSlotCount = frame.fn.scopeSlotCount + if (a >= scopeSlotCount && b >= scopeSlotCount) { + val la = a - scopeSlotCount + val lb = b - scopeSlotCount + val ta = frame.frame.getSlotTypeCode(la) + val tb = frame.frame.getSlotTypeCode(lb) + if (ta == SlotType.INT.code && tb == SlotType.INT.code) { + frame.setInt(dst, frame.frame.getInt(la) - frame.frame.getInt(lb)) + return + } + if (ta == SlotType.REAL.code || tb == SlotType.REAL.code) { + val av = if (ta == SlotType.REAL.code) frame.frame.getReal(la) else frame.frame.getInt(la).toDouble() + val bv = if (tb == SlotType.REAL.code) frame.frame.getReal(lb) else frame.frame.getInt(lb).toDouble() + frame.setReal(dst, av - bv) + return + } + } + frame.setObj(dst, frame.slotToObj(a).minus(frame.scope, frame.slotToObj(b))) return } } class CmdMulObj(internal val a: Int, internal val b: Int, internal val dst: Int) : Cmd() { override suspend fun perform(frame: CmdFrame) { - frame.setObj(dst, frame.getObj(a).mul(frame.scope, frame.getObj(b))) + val scopeSlotCount = frame.fn.scopeSlotCount + if (a >= scopeSlotCount && b >= scopeSlotCount) { + val la = a - scopeSlotCount + val lb = b - scopeSlotCount + val ta = frame.frame.getSlotTypeCode(la) + val tb = frame.frame.getSlotTypeCode(lb) + if (ta == SlotType.INT.code && tb == SlotType.INT.code) { + frame.setInt(dst, frame.frame.getInt(la) * frame.frame.getInt(lb)) + return + } + if (ta == SlotType.REAL.code || tb == SlotType.REAL.code) { + val av = if (ta == SlotType.REAL.code) frame.frame.getReal(la) else frame.frame.getInt(la).toDouble() + val bv = if (tb == SlotType.REAL.code) frame.frame.getReal(lb) else frame.frame.getInt(lb).toDouble() + frame.setReal(dst, av * bv) + return + } + } + frame.setObj(dst, frame.slotToObj(a).mul(frame.scope, frame.slotToObj(b))) return } } class CmdDivObj(internal val a: Int, internal val b: Int, internal val dst: Int) : Cmd() { override suspend fun perform(frame: CmdFrame) { - frame.setObj(dst, frame.getObj(a).div(frame.scope, frame.getObj(b))) + val scopeSlotCount = frame.fn.scopeSlotCount + if (a >= scopeSlotCount && b >= scopeSlotCount) { + val la = a - scopeSlotCount + val lb = b - scopeSlotCount + val ta = frame.frame.getSlotTypeCode(la) + val tb = frame.frame.getSlotTypeCode(lb) + if (ta == SlotType.INT.code && tb == SlotType.INT.code) { + frame.setInt(dst, frame.frame.getInt(la) / frame.frame.getInt(lb)) + return + } + if (ta == SlotType.REAL.code || tb == SlotType.REAL.code) { + val av = if (ta == SlotType.REAL.code) frame.frame.getReal(la) else frame.frame.getInt(la).toDouble() + val bv = if (tb == SlotType.REAL.code) frame.frame.getReal(lb) else frame.frame.getInt(lb).toDouble() + frame.setReal(dst, av / bv) + return + } + } + frame.setObj(dst, frame.slotToObj(a).div(frame.scope, frame.slotToObj(b))) return } } class CmdModObj(internal val a: Int, internal val b: Int, internal val dst: Int) : Cmd() { override suspend fun perform(frame: CmdFrame) { - frame.setObj(dst, frame.getObj(a).mod(frame.scope, frame.getObj(b))) + val scopeSlotCount = frame.fn.scopeSlotCount + if (a >= scopeSlotCount && b >= scopeSlotCount) { + val la = a - scopeSlotCount + val lb = b - scopeSlotCount + val ta = frame.frame.getSlotTypeCode(la) + val tb = frame.frame.getSlotTypeCode(lb) + if (ta == SlotType.INT.code && tb == SlotType.INT.code) { + frame.setInt(dst, frame.frame.getInt(la) % frame.frame.getInt(lb)) + return + } + if (ta == SlotType.REAL.code || tb == SlotType.REAL.code) { + val av = if (ta == SlotType.REAL.code) frame.frame.getReal(la) else frame.frame.getInt(la).toDouble() + val bv = if (tb == SlotType.REAL.code) frame.frame.getReal(lb) else frame.frame.getInt(lb).toDouble() + frame.setReal(dst, av % bv) + return + } + } + frame.setObj(dst, frame.slotToObj(a).mod(frame.scope, frame.slotToObj(b))) return } } @@ -799,6 +887,20 @@ class CmdRet(internal val slot: Int) : Cmd() { } } +class CmdRetLabel(internal val labelId: Int, internal val slot: Int) : Cmd() { + override suspend fun perform(frame: CmdFrame) { + val labelConst = frame.fn.constants.getOrNull(labelId) as? BytecodeConst.StringVal + ?: error("RET_LABEL expects StringVal at $labelId") + val value = frame.slotToObj(slot) + if (frame.fn.returnLabels.contains(labelConst.value)) { + frame.vm.result = value + } else { + throw ReturnException(value, labelConst.value) + } + return + } +} + class CmdRetVoid : Cmd() { override suspend fun perform(frame: CmdFrame) { frame.vm.result = ObjVoid @@ -806,6 +908,15 @@ class CmdRetVoid : Cmd() { } } +class CmdThrow(internal val posId: Int, internal val slot: Int) : Cmd() { + override suspend fun perform(frame: CmdFrame) { + val posConst = frame.fn.constants.getOrNull(posId) as? BytecodeConst.PosVal + ?: error("THROW expects PosVal at $posId") + frame.throwObj(posConst.pos, frame.slotToObj(slot)) + return + } +} + class CmdPushScope(internal val planId: Int) : Cmd() { override suspend fun perform(frame: CmdFrame) { val planConst = frame.fn.constants[planId] as? BytecodeConst.SlotPlan @@ -963,10 +1074,29 @@ class CmdGetField( internal val fieldId: Int, internal val dst: Int, ) : Cmd() { + private var rKey: Long = 0L + private var rVer: Int = -1 + override suspend fun perform(frame: CmdFrame) { val receiver = frame.slotToObj(recvSlot) val nameConst = frame.fn.constants.getOrNull(fieldId) as? BytecodeConst.StringVal ?: error("GET_FIELD expects StringVal at $fieldId") + if (PerfFlags.FIELD_PIC) { + val (key, ver) = when (receiver) { + is ObjInstance -> receiver.objClass.classId to receiver.objClass.layoutVersion + is ObjClass -> receiver.classId to receiver.layoutVersion + else -> 0L to -1 + } + if (key != 0L) { + if (key == rKey && ver == rVer) { + if (PerfFlags.PIC_DEBUG_COUNTERS) PerfStats.fieldPicHit++ + } else { + if (PerfFlags.PIC_DEBUG_COUNTERS) PerfStats.fieldPicMiss++ + rKey = key + rVer = ver + } + } + } val result = receiver.readField(frame.scope, nameConst.value).value frame.storeObjResult(dst, result) return @@ -978,10 +1108,29 @@ class CmdSetField( internal val fieldId: Int, internal val valueSlot: Int, ) : Cmd() { + private var wKey: Long = 0L + private var wVer: Int = -1 + override suspend fun perform(frame: CmdFrame) { val receiver = frame.slotToObj(recvSlot) val nameConst = frame.fn.constants.getOrNull(fieldId) as? BytecodeConst.StringVal ?: error("SET_FIELD expects StringVal at $fieldId") + if (PerfFlags.FIELD_PIC) { + val (key, ver) = when (receiver) { + is ObjInstance -> receiver.objClass.classId to receiver.objClass.layoutVersion + is ObjClass -> receiver.classId to receiver.layoutVersion + else -> 0L to -1 + } + if (key != 0L) { + if (key == wKey && ver == wVer) { + if (PerfFlags.PIC_DEBUG_COUNTERS) PerfStats.fieldPicSetHit++ + } else { + if (PerfFlags.PIC_DEBUG_COUNTERS) PerfStats.fieldPicSetMiss++ + wKey = key + wVer = ver + } + } + } receiver.writeField(frame.scope, nameConst.value, frame.slotToObj(valueSlot)) return } @@ -1266,6 +1415,30 @@ class CmdFrame( } } + suspend fun throwObj(pos: Pos, value: Obj) { + var errorObject = value + val throwScope = scope.createChildScope(pos = pos) + if (errorObject is ObjString) { + errorObject = ObjException(throwScope, errorObject.value).apply { getStackTrace() } + } + if (!errorObject.isInstanceOf(ObjException.Root)) { + throwScope.raiseError("this is not an exception object: $errorObject") + } + if (errorObject is ObjException) { + errorObject = ObjException( + errorObject.exceptionClass, + throwScope, + errorObject.message, + errorObject.extraData, + errorObject.useStackTrace + ).apply { getStackTrace() } + throwScope.raiseError(errorObject) + } else { + val msg = errorObject.invokeInstanceMethod(scope, "message").toString(scope).value + throwScope.raiseError(errorObject, pos, msg) + } + } + fun syncFrameToScope() { val names = fn.localSlotNames if (names.isEmpty()) return diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/Opcode.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/Opcode.kt index 94ae25a..17fa2d2 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/Opcode.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/bytecode/Opcode.kt @@ -107,6 +107,7 @@ enum class Opcode(val code: Int) { JMP_IF_FALSE(0x82), RET(0x83), RET_VOID(0x84), + RET_LABEL(0xBA), PUSH_SCOPE(0x85), POP_SCOPE(0x86), PUSH_SLOT_PLAN(0x87), @@ -133,6 +134,7 @@ enum class Opcode(val code: Int) { STORE_REAL_ADDR(0xB7), LOAD_BOOL_ADDR(0xB8), STORE_BOOL_ADDR(0xB9), + THROW(0xBB), ; companion object { diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjRef.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjRef.kt index b2dc520..84e3f92 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjRef.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjRef.kt @@ -1336,6 +1336,9 @@ class IndexRef( private val index: ObjRef, private val isOptional: Boolean, ) : ObjRef { + internal val targetRef: ObjRef get() = target + internal val indexRef: ObjRef get() = index + internal val optionalRef: Boolean get() = isOptional // Tiny 4-entry PIC for index reads (guarded implicitly by RVAL_FASTPATH); move-to-front on hits private var rKey1: Long = 0L; private var rVer1: Int = -1; private var rGetter1: (suspend (Obj, Scope, Obj) -> Obj)? = null private var rKey2: Long = 0L; private var rVer2: Int = -1; private var rGetter2: (suspend (Obj, Scope, Obj) -> Obj)? = null diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/statements.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/statements.kt index d0f0381..782ea9a 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/statements.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/statements.kt @@ -22,8 +22,10 @@ import net.sergeych.lyng.obj.ObjClass import net.sergeych.lyng.obj.ObjInt import net.sergeych.lyng.obj.ObjIterable import net.sergeych.lyng.obj.ObjNull +import net.sergeych.lyng.obj.ObjException import net.sergeych.lyng.obj.ObjRange import net.sergeych.lyng.obj.ObjRecord +import net.sergeych.lyng.obj.ObjString import net.sergeych.lyng.obj.ObjVoid import net.sergeych.lyng.obj.toBool import net.sergeych.lyng.obj.toInt @@ -297,6 +299,146 @@ class ForInStatement( } } +class WhileStatement( + val condition: Statement, + val body: Statement, + val elseStatement: Statement?, + val label: String?, + val canBreak: Boolean, + val loopSlotPlan: Map, + override val pos: Pos, +) : Statement() { + override suspend fun execute(scope: Scope): Obj { + var result: Obj = ObjVoid + var wasBroken = false + while (condition.execute(scope).toBool()) { + val loopScope = scope.createChildScope().apply { skipScopeCreation = true } + if (canBreak) { + try { + result = body.execute(loopScope) + } catch (lbe: LoopBreakContinueException) { + if (lbe.label == label || lbe.label == null) { + if (lbe.doContinue) continue + result = lbe.result + wasBroken = true + break + } else { + throw lbe + } + } + } else { + result = body.execute(loopScope) + } + } + if (!wasBroken) elseStatement?.let { s -> result = s.execute(scope) } + return result + } +} + +class DoWhileStatement( + val body: Statement, + val condition: Statement, + val elseStatement: Statement?, + val label: String?, + val loopSlotPlan: Map, + override val pos: Pos, +) : Statement() { + override suspend fun execute(scope: Scope): Obj { + var wasBroken = false + var result: Obj = ObjVoid + while (true) { + val doScope = scope.createChildScope().apply { skipScopeCreation = true } + try { + result = body.execute(doScope) + } catch (e: LoopBreakContinueException) { + if (e.label == label || e.label == null) { + if (!e.doContinue) { + result = e.result + wasBroken = true + break + } + // continue: fall through to condition check + } else { + throw e + } + } + if (!condition.execute(doScope).toBool()) { + break + } + } + if (!wasBroken) elseStatement?.let { s -> result = s.execute(scope) } + return result + } +} + +class BreakStatement( + val label: String?, + val resultExpr: Statement?, + override val pos: Pos, +) : Statement() { + override suspend fun execute(scope: Scope): Obj { + val returnValue = resultExpr?.execute(scope) + throw LoopBreakContinueException( + doContinue = false, + label = label, + result = returnValue ?: ObjVoid + ) + } +} + +class ContinueStatement( + val label: String?, + override val pos: Pos, +) : Statement() { + override suspend fun execute(scope: Scope): Obj { + throw LoopBreakContinueException( + doContinue = true, + label = label, + ) + } +} + +class ReturnStatement( + val label: String?, + val resultExpr: Statement?, + override val pos: Pos, +) : Statement() { + override suspend fun execute(scope: Scope): Obj { + val returnValue = resultExpr?.execute(scope) ?: ObjVoid + throw ReturnException(returnValue, label) + } +} + +class ThrowStatement( + val throwExpr: Statement, + override val pos: Pos, +) : Statement() { + override suspend fun execute(scope: Scope): Obj { + var errorObject = throwExpr.execute(scope) + val throwScope = scope.createChildScope(pos = pos) + if (errorObject is ObjString) { + errorObject = ObjException(throwScope, errorObject.value).apply { getStackTrace() } + } + if (!errorObject.isInstanceOf(ObjException.Root)) { + throwScope.raiseError("this is not an exception object: $errorObject") + } + if (errorObject is ObjException) { + errorObject = ObjException( + errorObject.exceptionClass, + throwScope, + errorObject.message, + errorObject.extraData, + errorObject.useStackTrace + ).apply { getStackTrace() } + throwScope.raiseError(errorObject) + } else { + val msg = errorObject.invokeInstanceMethod(scope, "message").toString(scope).value + throwScope.raiseError(errorObject, pos, msg) + } + return ObjVoid + } +} + class ToBoolStatement( val expr: Statement, override val pos: Pos, diff --git a/notes/bytecode_exprs_loops.md b/notes/bytecode_exprs_loops.md index 12f2808..3469466 100644 --- a/notes/bytecode_exprs_loops.md +++ b/notes/bytecode_exprs_loops.md @@ -5,6 +5,7 @@ Changes - Added ForInStatement and ConstIntRange to keep for-loop structure explicit (no anonymous Statement). - Added PUSH_SCOPE/POP_SCOPE opcodes with SlotPlan constants to create loop scopes in bytecode. - Bytecode compiler emits int-range for-in loops when const range is known and no break/continue. +- Temporary: CmdGetField/CmdSetField maintain lightweight PIC counters for regression tests; remove or guard under a flag once bytecode becomes the sole execution path. Tests - ./gradlew :lynglib:jvmTest