Enforce bytecode-only statements and wrap defaults

This commit is contained in:
Sergey Chernov 2026-02-13 06:44:00 +03:00
parent 489dae6604
commit 26564438e2
12 changed files with 113 additions and 396 deletions

View File

@ -28,21 +28,7 @@ class BlockStatement(
override val pos: Pos = startPos override val pos: Pos = startPos
override suspend fun execute(scope: Scope): Obj { override suspend fun execute(scope: Scope): Obj {
val target = if (scope.skipScopeCreation) scope else scope.createChildScope(startPos) return interpreterDisabled(scope, "block statement")
if (slotPlan.isNotEmpty()) target.applySlotPlan(slotPlan)
if (captureSlots.isNotEmpty()) {
val captureRecords = scope.captureRecords
if (captureRecords == null) {
scope.raiseIllegalState("missing bytecode capture records")
}
for (i in captureSlots.indices) {
val capture = captureSlots[i]
val rec = captureRecords.getOrNull(i)
?: scope.raiseSymbolNotFound("capture ${capture.name} not found")
target.updateSlotFor(capture.name, rec)
}
}
return block.execute(target)
} }
fun statements(): List<Statement> = block.debugStatements() fun statements(): List<Statement> = block.debugStatements()

View File

@ -17,20 +17,14 @@
package net.sergeych.lyng package net.sergeych.lyng
import net.sergeych.lyng.obj.Obj import net.sergeych.lyng.obj.Obj
import net.sergeych.lyng.obj.ObjClass
import net.sergeych.lyng.obj.ObjProperty import net.sergeych.lyng.obj.ObjProperty
import net.sergeych.lyng.obj.ObjRecord
import net.sergeych.lyng.obj.ObjVoid
class ClassInstanceInitDeclStatement( class ClassInstanceInitDeclStatement(
val initStatement: Statement, val initStatement: Statement,
override val pos: Pos, override val pos: Pos,
) : Statement() { ) : Statement() {
override suspend fun execute(scope: Scope): Obj { override suspend fun execute(scope: Scope): Obj {
val cls = scope.thisObj as? ObjClass return interpreterDisabled(scope, "class instance init declaration")
?: scope.raiseIllegalState("instance init declaration requires class scope")
cls.instanceInitializers += initStatement
return ObjVoid
} }
} }
@ -48,24 +42,7 @@ class ClassInstanceFieldDeclStatement(
override val pos: Pos, override val pos: Pos,
) : Statement() { ) : Statement() {
override suspend fun execute(scope: Scope): Obj { override suspend fun execute(scope: Scope): Obj {
val cls = scope.thisObj as? ObjClass return interpreterDisabled(scope, "class instance field declaration")
?: scope.raiseIllegalState("instance field declaration requires class scope")
cls.createField(
name,
net.sergeych.lyng.obj.ObjNull,
isMutable = isMutable,
visibility = visibility,
writeVisibility = writeVisibility,
isAbstract = isAbstract,
isClosed = isClosed,
isOverride = isOverride,
isTransient = isTransient,
declaringClass = cls,
type = ObjRecord.Type.Field,
fieldId = fieldId
)
if (!isAbstract) initStatement?.let { cls.instanceInitializers += it }
return ObjVoid
} }
} }
@ -84,22 +61,7 @@ class ClassInstancePropertyDeclStatement(
override val pos: Pos, override val pos: Pos,
) : Statement() { ) : Statement() {
override suspend fun execute(scope: Scope): Obj { override suspend fun execute(scope: Scope): Obj {
val cls = scope.thisObj as? ObjClass return interpreterDisabled(scope, "class instance property declaration")
?: scope.raiseIllegalState("instance property declaration requires class scope")
cls.addProperty(
name = name,
visibility = visibility,
writeVisibility = writeVisibility,
declaringClass = cls,
isAbstract = isAbstract,
isClosed = isClosed,
isOverride = isOverride,
pos = pos,
prop = prop,
methodId = methodId
)
if (!isAbstract) initStatement?.let { cls.instanceInitializers += it }
return ObjVoid
} }
} }
@ -117,23 +79,6 @@ class ClassInstanceDelegatedDeclStatement(
override val pos: Pos, override val pos: Pos,
) : Statement() { ) : Statement() {
override suspend fun execute(scope: Scope): Obj { override suspend fun execute(scope: Scope): Obj {
val cls = scope.thisObj as? ObjClass return interpreterDisabled(scope, "class instance delegated declaration")
?: scope.raiseIllegalState("instance delegated declaration requires class scope")
cls.createField(
name,
net.sergeych.lyng.obj.ObjUnset,
isMutable = isMutable,
visibility = visibility,
writeVisibility = writeVisibility,
isAbstract = isAbstract,
isClosed = isClosed,
isOverride = isOverride,
isTransient = isTransient,
declaringClass = cls,
type = ObjRecord.Type.Delegated,
methodId = methodId
)
if (!isAbstract) initStatement?.let { cls.instanceInitializers += it }
return ObjVoid
} }
} }

View File

@ -1928,6 +1928,64 @@ class Compiler(
) )
} }
private fun wrapDefaultArgsBytecode(
args: ArgsDeclaration,
forcedLocalSlots: Map<String, Int>,
forcedLocalScopeId: Int
): ArgsDeclaration {
if (!compileBytecode) return args
if (args.params.none { it.defaultValue is Statement }) return args
val updated = args.params.map { param ->
val defaultValue = param.defaultValue
val stmt = defaultValue as? Statement
if (stmt == null) return@map param
val bytecode = when (stmt) {
is BytecodeStatement -> stmt
is BytecodeBodyProvider -> stmt.bytecodeBody()
else -> null
}
if (bytecode != null) {
param
} else {
val wrapped = wrapFunctionBytecode(
stmt,
"argDefault@${param.name}",
forcedLocalSlots = forcedLocalSlots,
forcedLocalScopeId = forcedLocalScopeId
)
param.copy(defaultValue = wrapped)
}
}
return if (updated == args.params) args else args.copy(params = updated)
}
private fun wrapParsedArgsBytecode(
args: List<ParsedArgument>?,
forcedLocalSlots: Map<String, Int> = emptyMap(),
forcedLocalScopeId: Int? = null
): List<ParsedArgument>? {
if (!compileBytecode || args == null || args.isEmpty()) return args
var changed = false
val updated = args.mapIndexed { index, arg ->
val stmt = arg.value as? Statement ?: return@mapIndexed arg
val bytecode = when (stmt) {
is BytecodeStatement -> stmt
is BytecodeBodyProvider -> stmt.bytecodeBody()
else -> null
}
if (bytecode != null) return@mapIndexed arg
val wrapped = wrapFunctionBytecode(
stmt,
"arg@${index}",
forcedLocalSlots = forcedLocalSlots,
forcedLocalScopeId = forcedLocalScopeId
)
changed = true
arg.copy(value = wrapped)
}
return if (changed) updated else args
}
private fun containsDelegatedRefs(stmt: Statement): Boolean { private fun containsDelegatedRefs(stmt: Statement): Boolean {
val target = if (stmt is BytecodeStatement) stmt.original else stmt val target = if (stmt is BytecodeStatement) stmt.original else stmt
return when (target) { return when (target) {
@ -5827,6 +5885,7 @@ class Compiler(
if (cc.skipTokenOfType(Token.Type.LPAREN, isOptional = true)) { if (cc.skipTokenOfType(Token.Type.LPAREN, isOptional = true)) {
argsList = parseArgsNoTailBlock() argsList = parseArgsNoTailBlock()
} }
argsList = wrapParsedArgsBytecode(argsList)
baseSpecs += BaseSpec(baseId.value, argsList) baseSpecs += BaseSpec(baseId.value, argsList)
} while (cc.skipTokenOfType(Token.Type.COMMA, isOptional = true)) } while (cc.skipTokenOfType(Token.Type.COMMA, isOptional = true))
} }
@ -5978,7 +6037,7 @@ class Compiler(
val classTypeParams = typeParamDecls.map { it.name }.toSet() val classTypeParams = typeParamDecls.map { it.name }.toSet()
classCtx?.typeParams = classTypeParams classCtx?.typeParams = classTypeParams
pendingTypeParamStack.add(classTypeParams) pendingTypeParamStack.add(classTypeParams)
val constructorArgsDeclaration: ArgsDeclaration? var constructorArgsDeclaration: ArgsDeclaration?
try { try {
constructorArgsDeclaration = constructorArgsDeclaration =
if (cc.skipTokenOfType(Token.Type.LPAREN, isOptional = true)) if (cc.skipTokenOfType(Token.Type.LPAREN, isOptional = true))
@ -6009,6 +6068,16 @@ class Compiler(
val mutable = param.accessType?.isMutable ?: false val mutable = param.accessType?.isMutable ?: false
declareSlotNameIn(classSlotPlan, param.name, mutable, isDelegated = false) declareSlotNameIn(classSlotPlan, param.name, mutable, isDelegated = false)
} }
val ctorForcedLocalSlots = LinkedHashMap<String, Int>()
if (constructorArgsDeclaration != null) {
val snapshot = slotPlanIndices(classSlotPlan)
for (param in constructorArgsDeclaration!!.params) {
val idx = snapshot[param.name] ?: continue
ctorForcedLocalSlots[param.name] = idx
}
constructorArgsDeclaration =
wrapDefaultArgsBytecode(constructorArgsDeclaration!!, ctorForcedLocalSlots, classSlotPlan.id)
}
constructorArgsDeclaration?.params?.forEach { param -> constructorArgsDeclaration?.params?.forEach { param ->
if (param.accessType != null) { if (param.accessType != null) {
classCtx?.declaredMembers?.add(param.name) classCtx?.declaredMembers?.add(param.name)
@ -6036,6 +6105,7 @@ class Compiler(
// Parse args without consuming any following block so that a class body can follow safely // Parse args without consuming any following block so that a class body can follow safely
argsList = parseArgsNoTailBlock() argsList = parseArgsNoTailBlock()
} }
argsList = wrapParsedArgsBytecode(argsList, ctorForcedLocalSlots, classSlotPlan.id)
baseSpecs += BaseSpec(baseName, argsList) baseSpecs += BaseSpec(baseName, argsList)
} while (cc.skipTokenOfType(Token.Type.COMMA, isOptional = true)) } while (cc.skipTokenOfType(Token.Type.COMMA, isOptional = true))
} }
@ -6717,7 +6787,7 @@ class Compiler(
} }
val typeParams = mergedTypeParamDecls.map { it.name }.toSet() val typeParams = mergedTypeParamDecls.map { it.name }.toSet()
pendingTypeParamStack.add(typeParams) pendingTypeParamStack.add(typeParams)
val argsDeclaration: ArgsDeclaration var argsDeclaration: ArgsDeclaration
val returnTypeMini: MiniTypeRef? val returnTypeMini: MiniTypeRef?
val returnTypeDecl: TypeDecl? val returnTypeDecl: TypeDecl?
try { try {
@ -6811,6 +6881,17 @@ class Compiler(
val typeParamNames = mergedTypeParamDecls.map { it.name } val typeParamNames = mergedTypeParamDecls.map { it.name }
val paramNames: Set<String> = paramNamesList.toSet() val paramNames: Set<String> = paramNamesList.toSet()
val paramSlotPlan = buildParamSlotPlan(paramNamesList + typeParamNames) val paramSlotPlan = buildParamSlotPlan(paramNamesList + typeParamNames)
val paramSlotPlanSnapshot = slotPlanIndices(paramSlotPlan)
val forcedLocalSlots = LinkedHashMap<String, Int>()
for (name in paramNamesList) {
val idx = paramSlotPlanSnapshot[name] ?: continue
forcedLocalSlots[name] = idx
}
for (name in typeParamNames) {
val idx = paramSlotPlanSnapshot[name] ?: continue
forcedLocalSlots[name] = idx
}
argsDeclaration = wrapDefaultArgsBytecode(argsDeclaration, forcedLocalSlots, paramSlotPlan.id)
val capturePlan = CapturePlan(paramSlotPlan, isFunction = true, propagateToParentFunction = false) val capturePlan = CapturePlan(paramSlotPlan, isFunction = true, propagateToParentFunction = false)
val rangeParamNames = argsDeclaration.params val rangeParamNames = argsDeclaration.params
.filter { isRangeType(it.type) } .filter { isRangeType(it.type) }
@ -6887,12 +6968,6 @@ class Compiler(
inferredReturnClass inferredReturnClass
} }
} }
val paramSlotPlanSnapshot = slotPlanIndices(paramSlotPlan)
val forcedLocalSlots = LinkedHashMap<String, Int>()
for (name in paramNamesList) {
val idx = paramSlotPlanSnapshot[name] ?: continue
forcedLocalSlots[name] = idx
}
val fnStatements = rawFnStatements?.let { stmt -> val fnStatements = rawFnStatements?.let { stmt ->
if (!compileBytecode) return@let stmt if (!compileBytecode) return@let stmt
val paramKnownClasses = mutableMapOf<String, ObjClass>() val paramKnownClasses = mutableMapOf<String, ObjClass>()

View File

@ -18,9 +18,6 @@
package net.sergeych.lyng package net.sergeych.lyng
import net.sergeych.lyng.obj.Obj import net.sergeych.lyng.obj.Obj
import net.sergeych.lyng.obj.ObjNull
import net.sergeych.lyng.obj.ObjRecord
import net.sergeych.lyng.obj.ObjString
class DelegatedVarDeclStatement( class DelegatedVarDeclStatement(
val name: String, val name: String,
@ -35,23 +32,6 @@ class DelegatedVarDeclStatement(
override val pos: Pos = startPos override val pos: Pos = startPos
override suspend fun execute(context: Scope): Obj { override suspend fun execute(context: Scope): Obj {
val initValue = initializer.execute(context) return interpreterDisabled(context, "delegated var declaration")
val accessTypeStr = if (isMutable) "Var" else "Val"
val accessType = ObjString(accessTypeStr)
val finalDelegate = try {
initValue.invokeInstanceMethod(context, "bind", Arguments(ObjString(name), accessType, ObjNull))
} catch (e: Exception) {
initValue
}
val rec = context.addItem(
name,
isMutable,
ObjNull,
visibility,
recordType = ObjRecord.Type.Delegated,
isTransient = isTransient
)
rec.delegate = finalDelegate
return finalDelegate
} }
} }

View File

@ -18,7 +18,6 @@ package net.sergeych.lyng
import net.sergeych.lyng.obj.ListLiteralRef import net.sergeych.lyng.obj.ListLiteralRef
import net.sergeych.lyng.obj.Obj import net.sergeych.lyng.obj.Obj
import net.sergeych.lyng.obj.ObjVoid
class DestructuringVarDeclStatement( class DestructuringVarDeclStatement(
val pattern: ListLiteralRef, val pattern: ListLiteralRef,
@ -30,20 +29,6 @@ class DestructuringVarDeclStatement(
override val pos: Pos, override val pos: Pos,
) : Statement() { ) : Statement() {
override suspend fun execute(context: Scope): Obj { override suspend fun execute(context: Scope): Obj {
val value = initializer.execute(context) return interpreterDisabled(context, "destructuring declaration")
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

@ -17,8 +17,6 @@
package net.sergeych.lyng package net.sergeych.lyng
import net.sergeych.lyng.obj.Obj import net.sergeych.lyng.obj.Obj
import net.sergeych.lyng.obj.ObjEnumClass
import net.sergeych.lyng.obj.ObjRecord
class EnumDeclStatement( class EnumDeclStatement(
val declaredName: String, val declaredName: String,
@ -30,17 +28,7 @@ class EnumDeclStatement(
override val pos: Pos = startPos override val pos: Pos = startPos
override suspend fun execute(scope: Scope): Obj { override suspend fun execute(scope: Scope): Obj {
val enumClass = ObjEnumClass.createSimpleEnum(qualifiedName, entries) return interpreterDisabled(scope, "enum declaration")
scope.addItem(declaredName, false, enumClass, recordType = ObjRecord.Type.Enum)
if (lifted) {
for (entry in entries) {
val rec = enumClass.getInstanceMemberOrNull(entry, includeAbstract = false, includeStatic = true)
if (rec != null) {
scope.addItem(entry, false, rec.value)
}
}
}
return enumClass
} }
override suspend fun callOn(scope: Scope): Obj { override suspend fun callOn(scope: Scope): Obj {

View File

@ -17,11 +17,7 @@
package net.sergeych.lyng package net.sergeych.lyng
import net.sergeych.lyng.obj.Obj import net.sergeych.lyng.obj.Obj
import net.sergeych.lyng.obj.ObjClass
import net.sergeych.lyng.obj.ObjExtensionPropertyGetterCallable
import net.sergeych.lyng.obj.ObjExtensionPropertySetterCallable
import net.sergeych.lyng.obj.ObjProperty import net.sergeych.lyng.obj.ObjProperty
import net.sergeych.lyng.obj.ObjRecord
class ExtensionPropertyDeclStatement( class ExtensionPropertyDeclStatement(
val extTypeName: String, val extTypeName: String,
@ -33,28 +29,6 @@ class ExtensionPropertyDeclStatement(
override val pos: Pos = startPos override val pos: Pos = startPos
override suspend fun execute(context: Scope): Obj { override suspend fun execute(context: Scope): Obj {
val type = context[extTypeName]?.value ?: context.raiseSymbolNotFound("class $extTypeName not found") return interpreterDisabled(context, "extension property declaration")
if (type !is ObjClass) context.raiseClassCastError("$extTypeName is not the class instance")
context.addExtension(
type,
property.name,
ObjRecord(
property,
isMutable = false,
visibility = visibility,
writeVisibility = setterVisibility,
declaringClass = null,
type = ObjRecord.Type.Property
)
)
val getterName = extensionPropertyGetterName(extTypeName, property.name)
val getterWrapper = ObjExtensionPropertyGetterCallable(property.name, property)
context.addItem(getterName, false, getterWrapper, visibility, recordType = ObjRecord.Type.Fun)
if (property.setter != null) {
val setterName = extensionPropertySetterName(extTypeName, property.name)
val setterWrapper = ObjExtensionPropertySetterCallable(property.name, property)
context.addItem(setterName, false, setterWrapper, visibility, recordType = ObjRecord.Type.Fun)
}
return property
} }
} }

View File

@ -19,8 +19,6 @@ package net.sergeych.lyng
import net.sergeych.lyng.obj.Obj import net.sergeych.lyng.obj.Obj
import net.sergeych.lyng.obj.ObjClass import net.sergeych.lyng.obj.ObjClass
import net.sergeych.lyng.obj.ObjException import net.sergeych.lyng.obj.ObjException
import net.sergeych.lyng.obj.ObjUnknownException
import net.sergeych.lyng.obj.ObjVoid
class TryStatement( class TryStatement(
val body: Statement, val body: Statement,
@ -38,43 +36,7 @@ class TryStatement(
) )
override suspend fun execute(scope: Scope): Obj { override suspend fun execute(scope: Scope): Obj {
var result: Obj = ObjVoid return interpreterDisabled(scope, "try statement")
try {
result = body.execute(scope)
} catch (e: ReturnException) {
throw e
} catch (e: LoopBreakContinueException) {
throw e
} catch (e: Exception) {
val caughtObj = when (e) {
is ExecutionError -> e.errorObject
else -> ObjUnknownException(scope, e.message ?: e.toString())
}
var isCaught = false
for (cdata in catches) {
var match: Obj? = null
for (exceptionClassName in cdata.classNames) {
val exObj = resolveExceptionClass(scope, exceptionClassName)
if (caughtObj.isInstanceOf(exObj)) {
match = caughtObj
break
}
}
if (match != null) {
val catchContext = scope.createChildScope(pos = cdata.catchVarPos).apply {
skipScopeCreation = true
}
catchContext.addItem(cdata.catchVarName, false, caughtObj)
result = cdata.block.execute(catchContext)
isCaught = true
break
}
}
if (!isCaught) throw e
} finally {
finallyClause?.execute(scope)
}
return result
} }
private fun resolveExceptionClass(scope: Scope, name: String): ObjClass { private fun resolveExceptionClass(scope: Scope, name: String): ObjClass {

View File

@ -18,9 +18,6 @@ package net.sergeych.lyng
import net.sergeych.lyng.obj.Obj import net.sergeych.lyng.obj.Obj
import net.sergeych.lyng.obj.ObjClass import net.sergeych.lyng.obj.ObjClass
import net.sergeych.lyng.obj.ObjNull
import net.sergeych.lyng.obj.ObjRecord
import net.sergeych.lyng.obj.ObjUnset
class VarDeclStatement( class VarDeclStatement(
val name: String, val name: String,
@ -36,15 +33,6 @@ class VarDeclStatement(
override val pos: Pos = startPos override val pos: Pos = startPos
override suspend fun execute(context: Scope): Obj { override suspend fun execute(context: Scope): Obj {
val initValue = initializer?.execute(context)?.byValueCopy() ?: ObjUnset return interpreterDisabled(context, "var declaration")
context.addItem(
name,
isMutable,
initValue,
visibility,
recordType = ObjRecord.Type.Other,
isTransient = isTransient
)
return initValue
} }
} }

View File

@ -17,7 +17,6 @@
package net.sergeych.lyng package net.sergeych.lyng
import net.sergeych.lyng.obj.Obj import net.sergeych.lyng.obj.Obj
import net.sergeych.lyng.obj.ObjVoid
sealed class WhenCondition(open val expr: Statement, open val pos: Pos) { sealed class WhenCondition(open val expr: Statement, open val pos: Pos) {
abstract suspend fun matches(scope: Scope, value: Obj): Boolean abstract suspend fun matches(scope: Scope, value: Obj): Boolean
@ -67,14 +66,6 @@ class WhenStatement(
override val pos: Pos, override val pos: Pos,
) : Statement() { ) : Statement() {
override suspend fun execute(scope: Scope): Obj { override suspend fun execute(scope: Scope): Obj {
val whenValue = value.execute(scope) return interpreterDisabled(scope, "when statement")
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

@ -19,10 +19,10 @@ package net.sergeych.lyng
import net.sergeych.lyng.obj.Obj import net.sergeych.lyng.obj.Obj
import net.sergeych.lyng.obj.ObjClass import net.sergeych.lyng.obj.ObjClass
import net.sergeych.lyng.obj.ObjException
import net.sergeych.lyng.obj.ObjInt import net.sergeych.lyng.obj.ObjInt
import net.sergeych.lyng.obj.ObjIterable import net.sergeych.lyng.obj.ObjIterable
import net.sergeych.lyng.obj.ObjNull import net.sergeych.lyng.obj.ObjNull
import net.sergeych.lyng.obj.ObjException
import net.sergeych.lyng.obj.ObjRange import net.sergeych.lyng.obj.ObjRange
import net.sergeych.lyng.obj.ObjRecord import net.sergeych.lyng.obj.ObjRecord
import net.sergeych.lyng.obj.ObjString import net.sergeych.lyng.obj.ObjString
@ -71,6 +71,10 @@ abstract class Statement(
suspend fun call(scope: Scope, vararg args: Obj) = execute(scope.createChildScope(args = Arguments(*args))) suspend fun call(scope: Scope, vararg args: Obj) = execute(scope.createChildScope(args = Arguments(*args)))
protected fun interpreterDisabled(scope: Scope, label: String): Nothing {
return scope.raiseIllegalState("interpreter execution is not supported; $label requires bytecode")
}
} }
class IfStatement( class IfStatement(
@ -80,11 +84,7 @@ class IfStatement(
override val pos: Pos, override val pos: Pos,
) : Statement() { ) : Statement() {
override suspend fun execute(scope: Scope): Obj { override suspend fun execute(scope: Scope): Obj {
return if (condition.execute(scope).toBool()) { return interpreterDisabled(scope, "if statement")
ifBody.execute(scope)
} else {
elseBody?.execute(scope) ?: ObjVoid
}
} }
} }
@ -103,89 +103,7 @@ class ForInStatement(
override val pos: Pos, override val pos: Pos,
) : Statement() { ) : Statement() {
override suspend fun execute(scope: Scope): Obj { override suspend fun execute(scope: Scope): Obj {
val forContext = scope.createChildScope(pos) return interpreterDisabled(scope, "for-in statement")
if (loopSlotPlan.isNotEmpty()) {
forContext.applySlotPlan(loopSlotPlan)
}
val loopSO = forContext.addItem(loopVarName, true, ObjNull)
val loopSlotIndex = forContext.getSlotIndexOf(loopVarName) ?: -1
if (constRange != null && PerfFlags.PRIMITIVE_FASTOPS) {
return loopIntRange(
forContext,
constRange.start,
constRange.endExclusive,
loopSO,
loopSlotIndex,
body,
elseStatement,
label,
canBreak
)
}
val sourceObj = source.execute(forContext)
return if (sourceObj is ObjRange && sourceObj.isIntRange && !sourceObj.hasExplicitStep && PerfFlags.PRIMITIVE_FASTOPS) {
loopIntRange(
forContext,
sourceObj.start!!.toLong(),
if (sourceObj.isEndInclusive) sourceObj.end!!.toLong() + 1 else sourceObj.end!!.toLong(),
loopSO,
loopSlotIndex,
body,
elseStatement,
label,
canBreak
)
} else if (sourceObj.isInstanceOf(ObjIterable)) {
loopIterable(forContext, sourceObj, loopSO, body, elseStatement, label, canBreak)
} else {
val size = runCatching { sourceObj.readField(forContext, "size").value.toInt() }
.getOrElse {
throw ScriptError(
pos,
"object is not enumerable: no size in $sourceObj",
it
)
}
var result: Obj = ObjVoid
var breakCaught = false
if (size > 0) {
var current = runCatching { sourceObj.getAt(forContext, ObjInt.of(0)) }
.getOrElse {
throw ScriptError(
pos,
"object is not enumerable: no index access for ${sourceObj.inspect(scope)}",
it
)
}
var index = 0
while (true) {
loopSO.value = current
try {
result = body.execute(forContext)
} catch (lbe: LoopBreakContinueException) {
if (lbe.label == label || lbe.label == null) {
breakCaught = true
if (lbe.doContinue) continue
result = lbe.result
break
} else {
throw lbe
}
}
if (++index >= size) break
current = sourceObj.getAt(forContext, ObjInt.of(index.toLong()))
}
}
if (!breakCaught && elseStatement != null) {
result = elseStatement.execute(scope)
}
result
}
} }
private suspend fun loopIntRange( private suspend fun loopIntRange(
@ -310,29 +228,7 @@ class WhileStatement(
override val pos: Pos, override val pos: Pos,
) : Statement() { ) : Statement() {
override suspend fun execute(scope: Scope): Obj { override suspend fun execute(scope: Scope): Obj {
var result: Obj = ObjVoid return interpreterDisabled(scope, "while statement")
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
} }
} }
@ -345,30 +241,7 @@ class DoWhileStatement(
override val pos: Pos, override val pos: Pos,
) : Statement() { ) : Statement() {
override suspend fun execute(scope: Scope): Obj { override suspend fun execute(scope: Scope): Obj {
var wasBroken = false return interpreterDisabled(scope, "do-while statement")
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
} }
} }
@ -378,12 +251,7 @@ class BreakStatement(
override val pos: Pos, override val pos: Pos,
) : Statement() { ) : Statement() {
override suspend fun execute(scope: Scope): Obj { override suspend fun execute(scope: Scope): Obj {
val returnValue = resultExpr?.execute(scope) return interpreterDisabled(scope, "break statement")
throw LoopBreakContinueException(
doContinue = false,
label = label,
result = returnValue ?: ObjVoid
)
} }
} }
@ -392,10 +260,7 @@ class ContinueStatement(
override val pos: Pos, override val pos: Pos,
) : Statement() { ) : Statement() {
override suspend fun execute(scope: Scope): Obj { override suspend fun execute(scope: Scope): Obj {
throw LoopBreakContinueException( return interpreterDisabled(scope, "continue statement")
doContinue = true,
label = label,
)
} }
} }
@ -405,8 +270,7 @@ class ReturnStatement(
override val pos: Pos, override val pos: Pos,
) : Statement() { ) : Statement() {
override suspend fun execute(scope: Scope): Obj { override suspend fun execute(scope: Scope): Obj {
val returnValue = resultExpr?.execute(scope) ?: ObjVoid return interpreterDisabled(scope, "return statement")
throw ReturnException(returnValue, label)
} }
} }
@ -415,28 +279,7 @@ class ThrowStatement(
override val pos: Pos, override val pos: Pos,
) : Statement() { ) : Statement() {
override suspend fun execute(scope: Scope): Obj { override suspend fun execute(scope: Scope): Obj {
var errorObject = throwExpr.execute(scope) return interpreterDisabled(scope, "throw statement")
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
} }
} }
@ -444,7 +287,7 @@ class ExpressionStatement(
val ref: net.sergeych.lyng.obj.ObjRef, val ref: net.sergeych.lyng.obj.ObjRef,
override val pos: Pos override val pos: Pos
) : Statement() { ) : Statement() {
override suspend fun execute(scope: Scope): Obj = ref.evalValue(scope) override suspend fun execute(scope: Scope): Obj = interpreterDisabled(scope, "expression statement")
} }
fun Statement.raise(text: String): Nothing { fun Statement.raise(text: String): Nothing {
@ -459,17 +302,17 @@ fun Statement.require(cond: Boolean, message: () -> String) {
fun statement(pos: Pos, isStaticConst: Boolean = false, isConst: Boolean = false, f: suspend (Scope) -> Obj): Statement = fun statement(pos: Pos, isStaticConst: Boolean = false, isConst: Boolean = false, f: suspend (Scope) -> Obj): Statement =
object : Statement(isStaticConst, isConst) { object : Statement(isStaticConst, isConst) {
override val pos: Pos = pos override val pos: Pos = pos
override suspend fun execute(scope: Scope): Obj = f(scope) override suspend fun execute(scope: Scope): Obj = interpreterDisabled(scope, "statement bridge")
} }
fun statement(isStaticConst: Boolean = false, isConst: Boolean = false, f: suspend Scope.() -> Obj): Statement = fun statement(isStaticConst: Boolean = false, isConst: Boolean = false, f: suspend Scope.() -> Obj): Statement =
object : Statement(isStaticConst, isConst) { object : Statement(isStaticConst, isConst) {
override val pos: Pos = Pos.builtIn override val pos: Pos = Pos.builtIn
override suspend fun execute(scope: Scope): Obj = f(scope) override suspend fun execute(scope: Scope): Obj = interpreterDisabled(scope, "statement bridge")
} }
object NopStatement: Statement(true, true, ObjType.Void) { object NopStatement: Statement(true, true, ObjType.Void) {
override val pos: Pos = Pos.builtIn override val pos: Pos = Pos.builtIn
override suspend fun execute(scope: Scope): Obj = ObjVoid override suspend fun execute(scope: Scope): Obj = interpreterDisabled(scope, "nop statement")
} }

View File

@ -734,7 +734,7 @@ class ScriptTest {
listOf( listOf(
ArgsDeclaration.Item("a"), ArgsDeclaration.Item("a"),
ArgsDeclaration.Item("b"), ArgsDeclaration.Item("b"),
ArgsDeclaration.Item("c", defaultValue = statement { ObjInt(100) }), ArgsDeclaration.Item("c", defaultValue = ObjExternCallable.fromBridge { ObjInt(100) }),
), ttEnd ), ttEnd
) )
pa.assignToContext(c) pa.assignToContext(c)