Expand bytecode expressions and loops
This commit is contained in:
parent
72901d9d4c
commit
144082733c
@ -30,6 +30,9 @@ slots[localCount .. localCount+argCount-1] arguments
|
|||||||
- scopeSlotNames: array sized scopeSlotCount, each entry nullable.
|
- scopeSlotNames: array sized scopeSlotCount, each entry nullable.
|
||||||
- Intended for disassembly/debug tooling; VM semantics do not depend on it.
|
- Intended for disassembly/debug tooling; VM semantics do not depend on it.
|
||||||
|
|
||||||
|
### Constant pool extras
|
||||||
|
- SlotPlan: map of name -> slot index, used by PUSH_SCOPE to pre-allocate and map loop locals.
|
||||||
|
|
||||||
## 2) Slot ID Width
|
## 2) Slot ID Width
|
||||||
|
|
||||||
Per frame, select:
|
Per frame, select:
|
||||||
@ -185,6 +188,12 @@ Note: Any opcode can be compiled to FALLBACK if not implemented in a VM pass.
|
|||||||
- JMP_IF_FALSE S, I
|
- JMP_IF_FALSE S, I
|
||||||
- RET S
|
- RET S
|
||||||
- RET_VOID
|
- RET_VOID
|
||||||
|
- PUSH_SCOPE K
|
||||||
|
- POP_SCOPE
|
||||||
|
|
||||||
|
### Scope setup
|
||||||
|
- PUSH_SCOPE uses const `SlotPlan` (name -> slot index) to create a child scope and apply slot mapping.
|
||||||
|
- POP_SCOPE restores the parent scope.
|
||||||
|
|
||||||
### Calls
|
### Calls
|
||||||
- CALL_DIRECT F, S, C, S
|
- CALL_DIRECT F, S, C, S
|
||||||
|
|||||||
@ -2556,174 +2556,23 @@ class Compiler(
|
|||||||
}
|
}
|
||||||
val loopSlotPlanSnapshot = slotPlanIndices(loopSlotPlan)
|
val loopSlotPlanSnapshot = slotPlanIndices(loopSlotPlan)
|
||||||
|
|
||||||
return object : Statement() {
|
return ForInStatement(
|
||||||
override val pos: Pos = body.pos
|
loopVarName = tVar.value,
|
||||||
override suspend fun execute(scope: Scope): Obj {
|
source = source,
|
||||||
val forContext = scope.createChildScope(start)
|
constRange = constRange,
|
||||||
if (loopSlotPlanSnapshot.isNotEmpty()) {
|
body = body,
|
||||||
forContext.applySlotPlan(loopSlotPlanSnapshot)
|
elseStatement = elseStatement,
|
||||||
}
|
label = label,
|
||||||
|
canBreak = canBreak,
|
||||||
// loop var: StoredObject
|
loopSlotPlan = loopSlotPlanSnapshot,
|
||||||
val loopSO = forContext.addItem(tVar.value, true, ObjNull)
|
pos = body.pos
|
||||||
|
)
|
||||||
if (constRange != null && PerfFlags.PRIMITIVE_FASTOPS) {
|
|
||||||
val loopSlotIndex = forContext.getSlotIndexOf(tVar.value) ?: -1
|
|
||||||
return loopIntRange(
|
|
||||||
forContext,
|
|
||||||
constRange.start,
|
|
||||||
constRange.endExclusive,
|
|
||||||
loopSO,
|
|
||||||
loopSlotIndex,
|
|
||||||
body,
|
|
||||||
elseStatement,
|
|
||||||
label,
|
|
||||||
canBreak
|
|
||||||
)
|
|
||||||
}
|
|
||||||
// insofar we suggest source object is enumerable. Later we might need to add checks
|
|
||||||
val sourceObj = source.execute(forContext)
|
|
||||||
|
|
||||||
if (sourceObj is ObjRange && sourceObj.isIntRange && PerfFlags.PRIMITIVE_FASTOPS) {
|
|
||||||
val loopSlotIndex = forContext.getSlotIndexOf(tVar.value) ?: -1
|
|
||||||
return 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)) {
|
|
||||||
return loopIterable(forContext, sourceObj, loopSO, body, elseStatement, label, canBreak)
|
|
||||||
} else {
|
|
||||||
val size = runCatching { sourceObj.readField(forContext, "size").value.toInt() }
|
|
||||||
.getOrElse {
|
|
||||||
throw ScriptError(
|
|
||||||
tOp.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(
|
|
||||||
tOp.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
|
|
||||||
else {
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
// maybe other loops?
|
// maybe other loops?
|
||||||
throw ScriptError(tOp.pos, "Unsupported for-loop syntax")
|
throw ScriptError(tOp.pos, "Unsupported for-loop syntax")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun loopIntRange(
|
|
||||||
forScope: Scope, start: Long, end: Long, loopVar: ObjRecord, loopSlotIndex: Int,
|
|
||||||
body: Statement, elseStatement: Statement?, label: String?, catchBreak: Boolean
|
|
||||||
): Obj {
|
|
||||||
var result: Obj = ObjVoid
|
|
||||||
val cacheLow = ObjInt.CACHE_LOW
|
|
||||||
val cacheHigh = ObjInt.CACHE_HIGH
|
|
||||||
val useCache = start >= cacheLow && end <= cacheHigh + 1
|
|
||||||
val cache = if (useCache) ObjInt.cacheArray() else null
|
|
||||||
val useSlot = loopSlotIndex >= 0
|
|
||||||
if (catchBreak) {
|
|
||||||
if (useCache && cache != null) {
|
|
||||||
var i = start
|
|
||||||
while (i < end) {
|
|
||||||
val v = cache[(i - cacheLow).toInt()]
|
|
||||||
if (useSlot) forScope.setSlotValue(loopSlotIndex, v) else loopVar.value = v
|
|
||||||
try {
|
|
||||||
result = body.execute(forScope)
|
|
||||||
} catch (lbe: LoopBreakContinueException) {
|
|
||||||
if (lbe.label == label || lbe.label == null) {
|
|
||||||
if (lbe.doContinue) {
|
|
||||||
i++
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
return lbe.result
|
|
||||||
}
|
|
||||||
throw lbe
|
|
||||||
}
|
|
||||||
i++
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
for (i in start..<end) {
|
|
||||||
val v = ObjInt.of(i)
|
|
||||||
if (useSlot) forScope.setSlotValue(loopSlotIndex, v) else loopVar.value = v
|
|
||||||
try {
|
|
||||||
result = body.execute(forScope)
|
|
||||||
} catch (lbe: LoopBreakContinueException) {
|
|
||||||
if (lbe.label == label || lbe.label == null) {
|
|
||||||
if (lbe.doContinue) continue
|
|
||||||
return lbe.result
|
|
||||||
}
|
|
||||||
throw lbe
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (useCache && cache != null) {
|
|
||||||
var i = start
|
|
||||||
while (i < end) {
|
|
||||||
val v = cache[(i - cacheLow).toInt()]
|
|
||||||
if (useSlot) forScope.setSlotValue(loopSlotIndex, v) else loopVar.value = v
|
|
||||||
result = body.execute(forScope)
|
|
||||||
i++
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
for (i in start..<end) {
|
|
||||||
val v = ObjInt.of(i)
|
|
||||||
if (useSlot) forScope.setSlotValue(loopSlotIndex, v) else loopVar.value = v
|
|
||||||
result = body.execute(forScope)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return elseStatement?.execute(forScope) ?: result
|
|
||||||
}
|
|
||||||
|
|
||||||
private data class ConstIntRange(val start: Long, val endExclusive: Long)
|
|
||||||
|
|
||||||
private fun constIntRangeOrNull(ref: ObjRef): ConstIntRange? {
|
private fun constIntRangeOrNull(ref: ObjRef): ConstIntRange? {
|
||||||
if (ref !is RangeRef) return null
|
if (ref !is RangeRef) return null
|
||||||
val start = constIntValueOrNull(ref.left) ?: return null
|
val start = constIntValueOrNull(ref.left) ?: return null
|
||||||
@ -2743,40 +2592,6 @@ class Compiler(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun loopIterable(
|
|
||||||
forScope: Scope, sourceObj: Obj, loopVar: ObjRecord,
|
|
||||||
body: Statement, elseStatement: Statement?, label: String?,
|
|
||||||
catchBreak: Boolean
|
|
||||||
): Obj {
|
|
||||||
var result: Obj = ObjVoid
|
|
||||||
var breakCaught = false
|
|
||||||
sourceObj.enumerate(forScope) { item ->
|
|
||||||
loopVar.value = item
|
|
||||||
if (catchBreak) {
|
|
||||||
try {
|
|
||||||
result = body.execute(forScope)
|
|
||||||
true
|
|
||||||
} catch (lbe: LoopBreakContinueException) {
|
|
||||||
if (lbe.label == label || lbe.label == null) {
|
|
||||||
if (lbe.doContinue) true
|
|
||||||
else {
|
|
||||||
result = lbe.result
|
|
||||||
breakCaught = true
|
|
||||||
false
|
|
||||||
}
|
|
||||||
} else
|
|
||||||
throw lbe
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
result = body.execute(forScope)
|
|
||||||
true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return if (!breakCaught && elseStatement != null) {
|
|
||||||
elseStatement.execute(forScope)
|
|
||||||
} else result
|
|
||||||
}
|
|
||||||
|
|
||||||
@Suppress("UNUSED_VARIABLE")
|
@Suppress("UNUSED_VARIABLE")
|
||||||
private suspend fun parseDoWhileStatement(): Statement {
|
private suspend fun parseDoWhileStatement(): Statement {
|
||||||
val label = getLabel()?.also { cc.labels += it }
|
val label = getLabel()?.also { cc.labels += it }
|
||||||
|
|||||||
@ -129,7 +129,7 @@ class BytecodeBuilder {
|
|||||||
|
|
||||||
private fun operandKinds(op: Opcode): List<OperandKind> {
|
private fun operandKinds(op: Opcode): List<OperandKind> {
|
||||||
return when (op) {
|
return when (op) {
|
||||||
Opcode.NOP, Opcode.RET_VOID -> emptyList()
|
Opcode.NOP, Opcode.RET_VOID, Opcode.POP_SCOPE -> emptyList()
|
||||||
Opcode.MOVE_OBJ, Opcode.MOVE_INT, Opcode.MOVE_REAL, Opcode.MOVE_BOOL, Opcode.BOX_OBJ,
|
Opcode.MOVE_OBJ, Opcode.MOVE_INT, Opcode.MOVE_REAL, Opcode.MOVE_BOOL, Opcode.BOX_OBJ,
|
||||||
Opcode.INT_TO_REAL, Opcode.REAL_TO_INT, Opcode.BOOL_TO_INT, Opcode.INT_TO_BOOL,
|
Opcode.INT_TO_REAL, Opcode.REAL_TO_INT, Opcode.BOOL_TO_INT, Opcode.INT_TO_BOOL,
|
||||||
Opcode.NEG_INT, Opcode.NEG_REAL, Opcode.NOT_BOOL, Opcode.INV_INT ->
|
Opcode.NEG_INT, Opcode.NEG_REAL, Opcode.NOT_BOOL, Opcode.INV_INT ->
|
||||||
@ -138,6 +138,8 @@ class BytecodeBuilder {
|
|||||||
listOf(OperandKind.SLOT)
|
listOf(OperandKind.SLOT)
|
||||||
Opcode.CONST_OBJ, Opcode.CONST_INT, Opcode.CONST_REAL, Opcode.CONST_BOOL ->
|
Opcode.CONST_OBJ, Opcode.CONST_INT, Opcode.CONST_REAL, Opcode.CONST_BOOL ->
|
||||||
listOf(OperandKind.CONST, OperandKind.SLOT)
|
listOf(OperandKind.CONST, OperandKind.SLOT)
|
||||||
|
Opcode.PUSH_SCOPE ->
|
||||||
|
listOf(OperandKind.CONST)
|
||||||
Opcode.ADD_INT, Opcode.SUB_INT, Opcode.MUL_INT, Opcode.DIV_INT, Opcode.MOD_INT,
|
Opcode.ADD_INT, Opcode.SUB_INT, Opcode.MUL_INT, Opcode.DIV_INT, Opcode.MOD_INT,
|
||||||
Opcode.ADD_REAL, Opcode.SUB_REAL, Opcode.MUL_REAL, Opcode.DIV_REAL,
|
Opcode.ADD_REAL, Opcode.SUB_REAL, Opcode.MUL_REAL, Opcode.DIV_REAL,
|
||||||
Opcode.AND_INT, Opcode.OR_INT, Opcode.XOR_INT, Opcode.SHL_INT, Opcode.SHR_INT, Opcode.USHR_INT,
|
Opcode.AND_INT, Opcode.OR_INT, Opcode.XOR_INT, Opcode.SHL_INT, Opcode.SHR_INT, Opcode.USHR_INT,
|
||||||
|
|||||||
@ -42,6 +42,7 @@ class BytecodeCompiler(
|
|||||||
return when (stmt) {
|
return when (stmt) {
|
||||||
is ExpressionStatement -> compileExpression(name, stmt)
|
is ExpressionStatement -> compileExpression(name, stmt)
|
||||||
is net.sergeych.lyng.IfStatement -> compileIf(name, stmt)
|
is net.sergeych.lyng.IfStatement -> compileIf(name, stmt)
|
||||||
|
is net.sergeych.lyng.ForInStatement -> compileForIn(name, stmt)
|
||||||
else -> null
|
else -> null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -71,6 +72,10 @@ class BytecodeCompiler(
|
|||||||
is BinaryOpRef -> compileBinary(ref)
|
is BinaryOpRef -> compileBinary(ref)
|
||||||
is UnaryOpRef -> compileUnary(ref)
|
is UnaryOpRef -> compileUnary(ref)
|
||||||
is AssignRef -> compileAssign(ref)
|
is AssignRef -> compileAssign(ref)
|
||||||
|
is AssignOpRef -> compileAssignOp(ref)
|
||||||
|
is IncDecRef -> compileIncDec(ref)
|
||||||
|
is ConditionalRef -> compileConditional(ref)
|
||||||
|
is ElvisRef -> compileElvis(ref)
|
||||||
is CallRef -> compileCall(ref)
|
is CallRef -> compileCall(ref)
|
||||||
is MethodCallRef -> compileMethodCall(ref)
|
is MethodCallRef -> compileMethodCall(ref)
|
||||||
else -> null
|
else -> null
|
||||||
@ -590,6 +595,173 @@ class BytecodeCompiler(
|
|||||||
return CompiledValue(slot, value.type)
|
return CompiledValue(slot, value.type)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
if (refDepth(target) > 0) return null
|
||||||
|
val slot = scopeSlotMap[ScopeSlotKey(refDepth(target), refSlot(target))] ?: return null
|
||||||
|
val targetType = slotTypes[slot] ?: return null
|
||||||
|
val rhs = compileRef(ref.value) ?: return null
|
||||||
|
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)
|
||||||
|
else -> null
|
||||||
|
} ?: return null
|
||||||
|
updateSlotType(out, result.type)
|
||||||
|
return CompiledValue(out, result.type)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun compileAssignOpBinary(
|
||||||
|
targetType: SlotType,
|
||||||
|
rhs: CompiledValue,
|
||||||
|
out: Int,
|
||||||
|
intOp: Opcode,
|
||||||
|
realOp: Opcode?,
|
||||||
|
objOp: Opcode?,
|
||||||
|
): CompiledValue? {
|
||||||
|
return when (targetType) {
|
||||||
|
SlotType.INT -> {
|
||||||
|
when (rhs.type) {
|
||||||
|
SlotType.INT -> {
|
||||||
|
builder.emit(intOp, out, rhs.slot, out)
|
||||||
|
CompiledValue(out, SlotType.INT)
|
||||||
|
}
|
||||||
|
SlotType.REAL -> {
|
||||||
|
if (realOp == null) return null
|
||||||
|
val left = allocSlot()
|
||||||
|
builder.emit(Opcode.INT_TO_REAL, out, left)
|
||||||
|
builder.emit(realOp, left, rhs.slot, out)
|
||||||
|
CompiledValue(out, SlotType.REAL)
|
||||||
|
}
|
||||||
|
else -> null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
SlotType.REAL -> {
|
||||||
|
if (realOp == null) return null
|
||||||
|
when (rhs.type) {
|
||||||
|
SlotType.REAL -> {
|
||||||
|
builder.emit(realOp, out, rhs.slot, out)
|
||||||
|
CompiledValue(out, SlotType.REAL)
|
||||||
|
}
|
||||||
|
SlotType.INT -> {
|
||||||
|
val right = allocSlot()
|
||||||
|
builder.emit(Opcode.INT_TO_REAL, rhs.slot, right)
|
||||||
|
builder.emit(realOp, out, right, out)
|
||||||
|
CompiledValue(out, SlotType.REAL)
|
||||||
|
}
|
||||||
|
else -> null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
SlotType.OBJ -> {
|
||||||
|
if (objOp == null) return null
|
||||||
|
if (rhs.type != SlotType.OBJ) return null
|
||||||
|
builder.emit(objOp, out, rhs.slot, out)
|
||||||
|
CompiledValue(out, SlotType.OBJ)
|
||||||
|
}
|
||||||
|
else -> null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun compileIncDec(ref: IncDecRef): CompiledValue? {
|
||||||
|
val target = ref.target as? LocalSlotRef ?: return null
|
||||||
|
if (!allowLocalSlots) return null
|
||||||
|
if (!target.isMutable || target.isDelegated) return null
|
||||||
|
if (refDepth(target) > 0) return null
|
||||||
|
val slot = scopeSlotMap[ScopeSlotKey(refDepth(target), refSlot(target))] ?: return null
|
||||||
|
val slotType = slotTypes[slot] ?: return null
|
||||||
|
return when (slotType) {
|
||||||
|
SlotType.INT -> {
|
||||||
|
if (ref.isPost) {
|
||||||
|
val old = allocSlot()
|
||||||
|
builder.emit(Opcode.MOVE_INT, slot, old)
|
||||||
|
builder.emit(if (ref.isIncrement) Opcode.INC_INT else Opcode.DEC_INT, slot)
|
||||||
|
CompiledValue(old, SlotType.INT)
|
||||||
|
} else {
|
||||||
|
builder.emit(if (ref.isIncrement) Opcode.INC_INT else Opcode.DEC_INT, slot)
|
||||||
|
CompiledValue(slot, SlotType.INT)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
SlotType.REAL -> {
|
||||||
|
val oneSlot = allocSlot()
|
||||||
|
val oneId = builder.addConst(BytecodeConst.RealVal(1.0))
|
||||||
|
builder.emit(Opcode.CONST_REAL, oneId, oneSlot)
|
||||||
|
if (ref.isPost) {
|
||||||
|
val old = allocSlot()
|
||||||
|
builder.emit(Opcode.MOVE_REAL, slot, old)
|
||||||
|
val op = if (ref.isIncrement) Opcode.ADD_REAL else Opcode.SUB_REAL
|
||||||
|
builder.emit(op, slot, oneSlot, slot)
|
||||||
|
CompiledValue(old, SlotType.REAL)
|
||||||
|
} else {
|
||||||
|
val op = if (ref.isIncrement) Opcode.ADD_REAL else Opcode.SUB_REAL
|
||||||
|
builder.emit(op, slot, oneSlot, slot)
|
||||||
|
CompiledValue(slot, SlotType.REAL)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else -> null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun compileConditional(ref: ConditionalRef): CompiledValue? {
|
||||||
|
val condition = compileRefWithFallback(ref.condition, SlotType.BOOL, Pos.builtIn) ?: return null
|
||||||
|
if (condition.type != SlotType.BOOL) return null
|
||||||
|
val resultSlot = allocSlot()
|
||||||
|
val elseLabel = builder.label()
|
||||||
|
val endLabel = builder.label()
|
||||||
|
builder.emit(
|
||||||
|
Opcode.JMP_IF_FALSE,
|
||||||
|
listOf(BytecodeBuilder.Operand.IntVal(condition.slot), BytecodeBuilder.Operand.LabelRef(elseLabel))
|
||||||
|
)
|
||||||
|
val thenValue = compileRefWithFallback(ref.ifTrue, null, Pos.builtIn) ?: return null
|
||||||
|
val thenObj = ensureObjSlot(thenValue)
|
||||||
|
builder.emit(Opcode.MOVE_OBJ, thenObj.slot, resultSlot)
|
||||||
|
builder.emit(Opcode.JMP, listOf(BytecodeBuilder.Operand.LabelRef(endLabel)))
|
||||||
|
builder.mark(elseLabel)
|
||||||
|
val elseValue = compileRefWithFallback(ref.ifFalse, null, Pos.builtIn) ?: return null
|
||||||
|
val elseObj = ensureObjSlot(elseValue)
|
||||||
|
builder.emit(Opcode.MOVE_OBJ, elseObj.slot, resultSlot)
|
||||||
|
builder.mark(endLabel)
|
||||||
|
updateSlotType(resultSlot, SlotType.OBJ)
|
||||||
|
return CompiledValue(resultSlot, SlotType.OBJ)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun compileElvis(ref: ElvisRef): CompiledValue? {
|
||||||
|
val leftValue = compileRefWithFallback(ref.left, null, Pos.builtIn) ?: return null
|
||||||
|
val leftObj = ensureObjSlot(leftValue)
|
||||||
|
val resultSlot = allocSlot()
|
||||||
|
val nullSlot = allocSlot()
|
||||||
|
builder.emit(Opcode.CONST_NULL, nullSlot)
|
||||||
|
val cmpSlot = allocSlot()
|
||||||
|
builder.emit(Opcode.CMP_REF_EQ_OBJ, leftObj.slot, nullSlot, cmpSlot)
|
||||||
|
val rightLabel = builder.label()
|
||||||
|
val endLabel = builder.label()
|
||||||
|
builder.emit(
|
||||||
|
Opcode.JMP_IF_TRUE,
|
||||||
|
listOf(BytecodeBuilder.Operand.IntVal(cmpSlot), BytecodeBuilder.Operand.LabelRef(rightLabel))
|
||||||
|
)
|
||||||
|
builder.emit(Opcode.MOVE_OBJ, leftObj.slot, resultSlot)
|
||||||
|
builder.emit(Opcode.JMP, listOf(BytecodeBuilder.Operand.LabelRef(endLabel)))
|
||||||
|
builder.mark(rightLabel)
|
||||||
|
val rightValue = compileRefWithFallback(ref.right, null, Pos.builtIn) ?: return null
|
||||||
|
val rightObj = ensureObjSlot(rightValue)
|
||||||
|
builder.emit(Opcode.MOVE_OBJ, rightObj.slot, resultSlot)
|
||||||
|
builder.mark(endLabel)
|
||||||
|
updateSlotType(resultSlot, SlotType.OBJ)
|
||||||
|
return CompiledValue(resultSlot, SlotType.OBJ)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun ensureObjSlot(value: CompiledValue): CompiledValue {
|
||||||
|
if (value.type == SlotType.OBJ) return value
|
||||||
|
val dst = allocSlot()
|
||||||
|
builder.emit(Opcode.BOX_OBJ, value.slot, dst)
|
||||||
|
updateSlotType(dst, SlotType.OBJ)
|
||||||
|
return CompiledValue(dst, SlotType.OBJ)
|
||||||
|
}
|
||||||
|
|
||||||
private data class CallArgs(val base: Int, val count: Int)
|
private data class CallArgs(val base: Int, val count: Int)
|
||||||
|
|
||||||
private fun compileCall(ref: CallRef): CompiledValue? {
|
private fun compileCall(ref: CallRef): CompiledValue? {
|
||||||
@ -680,6 +852,54 @@ class BytecodeCompiler(
|
|||||||
return builder.build(name, localCount, scopeSlotDepths, scopeSlotIndices, scopeSlotNames)
|
return builder.build(name, localCount, scopeSlotDepths, scopeSlotIndices, scopeSlotNames)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun compileForIn(name: String, stmt: net.sergeych.lyng.ForInStatement): BytecodeFunction? {
|
||||||
|
if (stmt.canBreak) return null
|
||||||
|
val range = stmt.constRange ?: return null
|
||||||
|
val loopSlotIndex = stmt.loopSlotPlan[stmt.loopVarName] ?: return null
|
||||||
|
val loopSlot = scopeSlotMap[ScopeSlotKey(0, loopSlotIndex)] ?: return null
|
||||||
|
val planId = builder.addConst(BytecodeConst.SlotPlan(stmt.loopSlotPlan))
|
||||||
|
builder.emit(Opcode.PUSH_SCOPE, planId)
|
||||||
|
|
||||||
|
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()
|
||||||
|
val voidId = builder.addConst(BytecodeConst.ObjRef(ObjVoid))
|
||||||
|
builder.emit(Opcode.CONST_OBJ, voidId, resultSlot)
|
||||||
|
|
||||||
|
val loopLabel = builder.label()
|
||||||
|
val endLabel = builder.label()
|
||||||
|
builder.mark(loopLabel)
|
||||||
|
val cmpSlot = allocSlot()
|
||||||
|
builder.emit(Opcode.CMP_GTE_INT, iSlot, endSlot, cmpSlot)
|
||||||
|
builder.emit(
|
||||||
|
Opcode.JMP_IF_TRUE,
|
||||||
|
listOf(BytecodeBuilder.Operand.IntVal(cmpSlot), BytecodeBuilder.Operand.LabelRef(endLabel))
|
||||||
|
)
|
||||||
|
builder.emit(Opcode.MOVE_INT, iSlot, loopSlot)
|
||||||
|
val bodyValue = compileStatementValueOrFallback(stmt.body) ?: return null
|
||||||
|
val bodyObj = ensureObjSlot(bodyValue)
|
||||||
|
builder.emit(Opcode.MOVE_OBJ, bodyObj.slot, resultSlot)
|
||||||
|
builder.emit(Opcode.INC_INT, iSlot)
|
||||||
|
builder.emit(Opcode.JMP, listOf(BytecodeBuilder.Operand.LabelRef(loopLabel)))
|
||||||
|
|
||||||
|
builder.mark(endLabel)
|
||||||
|
if (stmt.elseStatement != null) {
|
||||||
|
val elseValue = compileStatementValueOrFallback(stmt.elseStatement) ?: return null
|
||||||
|
val elseObj = ensureObjSlot(elseValue)
|
||||||
|
builder.emit(Opcode.MOVE_OBJ, elseObj.slot, resultSlot)
|
||||||
|
}
|
||||||
|
builder.emit(Opcode.POP_SCOPE)
|
||||||
|
builder.emit(Opcode.RET, resultSlot)
|
||||||
|
|
||||||
|
val localCount = maxOf(nextSlot, resultSlot + 1) - scopeSlotCount
|
||||||
|
return builder.build(name, localCount, scopeSlotDepths, scopeSlotIndices, scopeSlotNames)
|
||||||
|
}
|
||||||
|
|
||||||
private fun compileStatementValue(stmt: Statement): CompiledValue? {
|
private fun compileStatementValue(stmt: Statement): CompiledValue? {
|
||||||
return when (stmt) {
|
return when (stmt) {
|
||||||
is ExpressionStatement -> compileRefWithFallback(stmt.ref, null, stmt.pos)
|
is ExpressionStatement -> compileRefWithFallback(stmt.ref, null, stmt.pos)
|
||||||
@ -687,6 +907,61 @@ class BytecodeCompiler(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun compileStatementValueOrFallback(stmt: Statement): CompiledValue? {
|
||||||
|
return when (stmt) {
|
||||||
|
is ExpressionStatement -> compileRefWithFallback(stmt.ref, null, stmt.pos)
|
||||||
|
is IfStatement -> compileIfExpression(stmt)
|
||||||
|
else -> {
|
||||||
|
val slot = allocSlot()
|
||||||
|
val id = builder.addFallback(stmt)
|
||||||
|
builder.emit(Opcode.EVAL_FALLBACK, id, slot)
|
||||||
|
updateSlotType(slot, SlotType.OBJ)
|
||||||
|
CompiledValue(slot, SlotType.OBJ)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun compileIfExpression(stmt: IfStatement): CompiledValue? {
|
||||||
|
val condition = compileCondition(stmt.condition, stmt.pos) ?: return null
|
||||||
|
if (condition.type != SlotType.BOOL) return null
|
||||||
|
val resultSlot = allocSlot()
|
||||||
|
val elseLabel = builder.label()
|
||||||
|
val endLabel = builder.label()
|
||||||
|
builder.emit(
|
||||||
|
Opcode.JMP_IF_FALSE,
|
||||||
|
listOf(BytecodeBuilder.Operand.IntVal(condition.slot), BytecodeBuilder.Operand.LabelRef(elseLabel))
|
||||||
|
)
|
||||||
|
val thenValue = compileStatementValueOrFallback(stmt.ifBody) ?: return null
|
||||||
|
val thenObj = ensureObjSlot(thenValue)
|
||||||
|
builder.emit(Opcode.MOVE_OBJ, thenObj.slot, resultSlot)
|
||||||
|
builder.emit(Opcode.JMP, listOf(BytecodeBuilder.Operand.LabelRef(endLabel)))
|
||||||
|
builder.mark(elseLabel)
|
||||||
|
if (stmt.elseBody != null) {
|
||||||
|
val elseValue = compileStatementValueOrFallback(stmt.elseBody) ?: return null
|
||||||
|
val elseObj = ensureObjSlot(elseValue)
|
||||||
|
builder.emit(Opcode.MOVE_OBJ, elseObj.slot, resultSlot)
|
||||||
|
} else {
|
||||||
|
val id = builder.addConst(BytecodeConst.ObjRef(ObjVoid))
|
||||||
|
builder.emit(Opcode.CONST_OBJ, id, resultSlot)
|
||||||
|
}
|
||||||
|
builder.mark(endLabel)
|
||||||
|
updateSlotType(resultSlot, SlotType.OBJ)
|
||||||
|
return CompiledValue(resultSlot, SlotType.OBJ)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun compileCondition(stmt: Statement, pos: Pos): CompiledValue? {
|
||||||
|
return when (stmt) {
|
||||||
|
is ExpressionStatement -> compileRefWithFallback(stmt.ref, SlotType.BOOL, stmt.pos)
|
||||||
|
else -> {
|
||||||
|
val slot = allocSlot()
|
||||||
|
val id = builder.addFallback(ToBoolStatement(stmt, pos))
|
||||||
|
builder.emit(Opcode.EVAL_FALLBACK, id, slot)
|
||||||
|
updateSlotType(slot, SlotType.BOOL)
|
||||||
|
CompiledValue(slot, SlotType.BOOL)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun emitMove(value: CompiledValue, dstSlot: Int) {
|
private fun emitMove(value: CompiledValue, dstSlot: Int) {
|
||||||
when (value.type) {
|
when (value.type) {
|
||||||
SlotType.INT -> builder.emit(Opcode.MOVE_INT, value.slot, dstSlot)
|
SlotType.INT -> builder.emit(Opcode.MOVE_INT, value.slot, dstSlot)
|
||||||
@ -764,6 +1039,21 @@ class BytecodeCompiler(
|
|||||||
collectScopeSlots(stmt.ifBody)
|
collectScopeSlots(stmt.ifBody)
|
||||||
stmt.elseBody?.let { collectScopeSlots(it) }
|
stmt.elseBody?.let { collectScopeSlots(it) }
|
||||||
}
|
}
|
||||||
|
is net.sergeych.lyng.ForInStatement -> {
|
||||||
|
val loopSlotIndex = stmt.loopSlotPlan[stmt.loopVarName]
|
||||||
|
if (loopSlotIndex != null) {
|
||||||
|
val key = ScopeSlotKey(0, loopSlotIndex)
|
||||||
|
if (!scopeSlotMap.containsKey(key)) {
|
||||||
|
scopeSlotMap[key] = scopeSlotMap.size
|
||||||
|
}
|
||||||
|
if (!scopeSlotNameMap.containsKey(key)) {
|
||||||
|
scopeSlotNameMap[key] = stmt.loopVarName
|
||||||
|
}
|
||||||
|
}
|
||||||
|
collectScopeSlots(stmt.source)
|
||||||
|
collectScopeSlots(stmt.body)
|
||||||
|
stmt.elseStatement?.let { collectScopeSlots(it) }
|
||||||
|
}
|
||||||
else -> {}
|
else -> {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -797,6 +1087,20 @@ class BytecodeCompiler(
|
|||||||
}
|
}
|
||||||
collectScopeSlotsRef(assignValue(ref))
|
collectScopeSlotsRef(assignValue(ref))
|
||||||
}
|
}
|
||||||
|
is AssignOpRef -> {
|
||||||
|
collectScopeSlotsRef(ref.target)
|
||||||
|
collectScopeSlotsRef(ref.value)
|
||||||
|
}
|
||||||
|
is IncDecRef -> collectScopeSlotsRef(ref.target)
|
||||||
|
is ConditionalRef -> {
|
||||||
|
collectScopeSlotsRef(ref.condition)
|
||||||
|
collectScopeSlotsRef(ref.ifTrue)
|
||||||
|
collectScopeSlotsRef(ref.ifFalse)
|
||||||
|
}
|
||||||
|
is ElvisRef -> {
|
||||||
|
collectScopeSlotsRef(ref.left)
|
||||||
|
collectScopeSlotsRef(ref.right)
|
||||||
|
}
|
||||||
is CallRef -> {
|
is CallRef -> {
|
||||||
collectScopeSlotsRef(ref.target)
|
collectScopeSlotsRef(ref.target)
|
||||||
collectScopeSlotsArgs(ref.args)
|
collectScopeSlotsArgs(ref.args)
|
||||||
|
|||||||
@ -25,4 +25,5 @@ sealed class BytecodeConst {
|
|||||||
data class RealVal(val value: Double) : BytecodeConst()
|
data class RealVal(val value: Double) : BytecodeConst()
|
||||||
data class StringVal(val value: String) : BytecodeConst()
|
data class StringVal(val value: String) : BytecodeConst()
|
||||||
data class ObjRef(val value: Obj) : BytecodeConst()
|
data class ObjRef(val value: Obj) : BytecodeConst()
|
||||||
|
data class SlotPlan(val plan: Map<String, Int>) : BytecodeConst()
|
||||||
}
|
}
|
||||||
|
|||||||
@ -82,7 +82,7 @@ object BytecodeDisassembler {
|
|||||||
|
|
||||||
private fun operandKinds(op: Opcode): List<OperandKind> {
|
private fun operandKinds(op: Opcode): List<OperandKind> {
|
||||||
return when (op) {
|
return when (op) {
|
||||||
Opcode.NOP, Opcode.RET_VOID -> emptyList()
|
Opcode.NOP, Opcode.RET_VOID, Opcode.POP_SCOPE -> emptyList()
|
||||||
Opcode.MOVE_OBJ, Opcode.MOVE_INT, Opcode.MOVE_REAL, Opcode.MOVE_BOOL, Opcode.BOX_OBJ,
|
Opcode.MOVE_OBJ, Opcode.MOVE_INT, Opcode.MOVE_REAL, Opcode.MOVE_BOOL, Opcode.BOX_OBJ,
|
||||||
Opcode.INT_TO_REAL, Opcode.REAL_TO_INT, Opcode.BOOL_TO_INT, Opcode.INT_TO_BOOL,
|
Opcode.INT_TO_REAL, Opcode.REAL_TO_INT, Opcode.BOOL_TO_INT, Opcode.INT_TO_BOOL,
|
||||||
Opcode.NEG_INT, Opcode.NEG_REAL, Opcode.NOT_BOOL, Opcode.INV_INT ->
|
Opcode.NEG_INT, Opcode.NEG_REAL, Opcode.NOT_BOOL, Opcode.INV_INT ->
|
||||||
@ -91,6 +91,8 @@ object BytecodeDisassembler {
|
|||||||
listOf(OperandKind.SLOT)
|
listOf(OperandKind.SLOT)
|
||||||
Opcode.CONST_OBJ, Opcode.CONST_INT, Opcode.CONST_REAL, Opcode.CONST_BOOL ->
|
Opcode.CONST_OBJ, Opcode.CONST_INT, Opcode.CONST_REAL, Opcode.CONST_BOOL ->
|
||||||
listOf(OperandKind.CONST, OperandKind.SLOT)
|
listOf(OperandKind.CONST, OperandKind.SLOT)
|
||||||
|
Opcode.PUSH_SCOPE ->
|
||||||
|
listOf(OperandKind.CONST)
|
||||||
Opcode.ADD_INT, Opcode.SUB_INT, Opcode.MUL_INT, Opcode.DIV_INT, Opcode.MOD_INT,
|
Opcode.ADD_INT, Opcode.SUB_INT, Opcode.MUL_INT, Opcode.DIV_INT, Opcode.MOD_INT,
|
||||||
Opcode.ADD_REAL, Opcode.SUB_REAL, Opcode.MUL_REAL, Opcode.DIV_REAL,
|
Opcode.ADD_REAL, Opcode.SUB_REAL, Opcode.MUL_REAL, Opcode.DIV_REAL,
|
||||||
Opcode.AND_INT, Opcode.OR_INT, Opcode.XOR_INT, Opcode.SHL_INT, Opcode.SHR_INT, Opcode.USHR_INT,
|
Opcode.AND_INT, Opcode.OR_INT, Opcode.XOR_INT, Opcode.SHL_INT, Opcode.SHR_INT, Opcode.USHR_INT,
|
||||||
|
|||||||
@ -22,7 +22,9 @@ import net.sergeych.lyng.Scope
|
|||||||
import net.sergeych.lyng.obj.*
|
import net.sergeych.lyng.obj.*
|
||||||
|
|
||||||
class BytecodeVm {
|
class BytecodeVm {
|
||||||
suspend fun execute(fn: BytecodeFunction, scope: Scope, args: List<Obj>): Obj {
|
suspend fun execute(fn: BytecodeFunction, scope0: Scope, args: List<Obj>): Obj {
|
||||||
|
val scopeStack = ArrayDeque<Scope>()
|
||||||
|
var scope = scope0
|
||||||
val frame = BytecodeFrame(fn.localCount, args.size)
|
val frame = BytecodeFrame(fn.localCount, args.size)
|
||||||
for (i in args.indices) {
|
for (i in args.indices) {
|
||||||
frame.setObj(frame.argBase + i, args[i])
|
frame.setObj(frame.argBase + i, args[i])
|
||||||
@ -721,6 +723,21 @@ class BytecodeVm {
|
|||||||
ip = target
|
ip = target
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Opcode.PUSH_SCOPE -> {
|
||||||
|
val constId = decoder.readConstId(code, ip, fn.constIdWidth)
|
||||||
|
ip += fn.constIdWidth
|
||||||
|
val planConst = fn.constants[constId] as? BytecodeConst.SlotPlan
|
||||||
|
?: error("PUSH_SCOPE expects SlotPlan at $constId")
|
||||||
|
scopeStack.addLast(scope)
|
||||||
|
scope = scope.createChildScope()
|
||||||
|
if (planConst.plan.isNotEmpty()) {
|
||||||
|
scope.applySlotPlan(planConst.plan)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Opcode.POP_SCOPE -> {
|
||||||
|
scope = scopeStack.removeLastOrNull()
|
||||||
|
?: error("Scope stack underflow in POP_SCOPE")
|
||||||
|
}
|
||||||
Opcode.CALL_SLOT -> {
|
Opcode.CALL_SLOT -> {
|
||||||
val calleeSlot = decoder.readSlot(code, ip)
|
val calleeSlot = decoder.readSlot(code, ip)
|
||||||
ip += fn.slotWidth
|
ip += fn.slotWidth
|
||||||
|
|||||||
@ -107,6 +107,8 @@ enum class Opcode(val code: Int) {
|
|||||||
JMP_IF_FALSE(0x82),
|
JMP_IF_FALSE(0x82),
|
||||||
RET(0x83),
|
RET(0x83),
|
||||||
RET_VOID(0x84),
|
RET_VOID(0x84),
|
||||||
|
PUSH_SCOPE(0x85),
|
||||||
|
POP_SCOPE(0x86),
|
||||||
|
|
||||||
CALL_DIRECT(0x90),
|
CALL_DIRECT(0x90),
|
||||||
CALL_VIRTUAL(0x91),
|
CALL_VIRTUAL(0x91),
|
||||||
|
|||||||
@ -385,9 +385,9 @@ class BinaryOpRef(internal val op: BinOp, internal val left: ObjRef, internal va
|
|||||||
|
|
||||||
/** Conditional (ternary) operator reference: cond ? a : b */
|
/** Conditional (ternary) operator reference: cond ? a : b */
|
||||||
class ConditionalRef(
|
class ConditionalRef(
|
||||||
private val condition: ObjRef,
|
internal val condition: ObjRef,
|
||||||
private val ifTrue: ObjRef,
|
internal val ifTrue: ObjRef,
|
||||||
private val ifFalse: ObjRef
|
internal val ifFalse: ObjRef
|
||||||
) : ObjRef {
|
) : ObjRef {
|
||||||
override suspend fun get(scope: Scope): ObjRecord {
|
override suspend fun get(scope: Scope): ObjRecord {
|
||||||
return evalCondition(scope).get(scope)
|
return evalCondition(scope).get(scope)
|
||||||
@ -661,9 +661,9 @@ class QualifiedThisMethodSlotCallRef(
|
|||||||
|
|
||||||
/** Assignment compound op: target op= value */
|
/** Assignment compound op: target op= value */
|
||||||
class AssignOpRef(
|
class AssignOpRef(
|
||||||
private val op: BinOp,
|
internal val op: BinOp,
|
||||||
private val target: ObjRef,
|
internal val target: ObjRef,
|
||||||
private val value: ObjRef,
|
internal val value: ObjRef,
|
||||||
private val atPos: Pos,
|
private val atPos: Pos,
|
||||||
) : ObjRef {
|
) : ObjRef {
|
||||||
override suspend fun get(scope: Scope): ObjRecord {
|
override suspend fun get(scope: Scope): ObjRecord {
|
||||||
@ -723,9 +723,9 @@ class AssignOpRef(
|
|||||||
|
|
||||||
/** Pre/post ++/-- on l-values */
|
/** Pre/post ++/-- on l-values */
|
||||||
class IncDecRef(
|
class IncDecRef(
|
||||||
private val target: ObjRef,
|
internal val target: ObjRef,
|
||||||
private val isIncrement: Boolean,
|
internal val isIncrement: Boolean,
|
||||||
private val isPost: Boolean,
|
internal val isPost: Boolean,
|
||||||
private val atPos: Pos,
|
private val atPos: Pos,
|
||||||
) : ObjRef {
|
) : ObjRef {
|
||||||
override suspend fun get(scope: Scope): ObjRecord {
|
override suspend fun get(scope: Scope): ObjRecord {
|
||||||
@ -751,7 +751,7 @@ class IncDecRef(
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** Elvis operator reference: a ?: b */
|
/** Elvis operator reference: a ?: b */
|
||||||
class ElvisRef(private val left: ObjRef, private val right: ObjRef) : ObjRef {
|
class ElvisRef(internal val left: ObjRef, internal val right: ObjRef) : ObjRef {
|
||||||
override suspend fun get(scope: Scope): ObjRecord {
|
override suspend fun get(scope: Scope): ObjRecord {
|
||||||
val a = left.evalValue(scope)
|
val a = left.evalValue(scope)
|
||||||
val r = if (a != ObjNull) a else right.evalValue(scope)
|
val r = if (a != ObjNull) a else right.evalValue(scope)
|
||||||
|
|||||||
@ -19,8 +19,15 @@ 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.ObjInt
|
||||||
|
import net.sergeych.lyng.obj.ObjIterable
|
||||||
|
import net.sergeych.lyng.obj.ObjNull
|
||||||
|
import net.sergeych.lyng.obj.ObjRange
|
||||||
|
import net.sergeych.lyng.obj.ObjRecord
|
||||||
import net.sergeych.lyng.obj.ObjVoid
|
import net.sergeych.lyng.obj.ObjVoid
|
||||||
import net.sergeych.lyng.obj.toBool
|
import net.sergeych.lyng.obj.toBool
|
||||||
|
import net.sergeych.lyng.obj.toInt
|
||||||
|
import net.sergeych.lyng.obj.toLong
|
||||||
|
|
||||||
fun String.toSource(name: String = "eval"): Source = Source(name, this)
|
fun String.toSource(name: String = "eval"): Source = Source(name, this)
|
||||||
|
|
||||||
@ -79,6 +86,217 @@ class IfStatement(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
data class ConstIntRange(val start: Long, val endExclusive: Long)
|
||||||
|
|
||||||
|
class ForInStatement(
|
||||||
|
val loopVarName: String,
|
||||||
|
val source: Statement,
|
||||||
|
val constRange: ConstIntRange?,
|
||||||
|
val body: Statement,
|
||||||
|
val elseStatement: Statement?,
|
||||||
|
val label: String?,
|
||||||
|
val canBreak: Boolean,
|
||||||
|
val loopSlotPlan: Map<String, Int>,
|
||||||
|
override val pos: Pos,
|
||||||
|
) : Statement() {
|
||||||
|
override suspend fun execute(scope: Scope): Obj {
|
||||||
|
val forContext = scope.createChildScope(pos)
|
||||||
|
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 && 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(
|
||||||
|
forScope: Scope,
|
||||||
|
start: Long,
|
||||||
|
end: Long,
|
||||||
|
loopVar: ObjRecord,
|
||||||
|
loopSlotIndex: Int,
|
||||||
|
body: Statement,
|
||||||
|
elseStatement: Statement?,
|
||||||
|
label: String?,
|
||||||
|
catchBreak: Boolean,
|
||||||
|
): Obj {
|
||||||
|
var result: Obj = ObjVoid
|
||||||
|
val cacheLow = ObjInt.CACHE_LOW
|
||||||
|
val cacheHigh = ObjInt.CACHE_HIGH
|
||||||
|
val useCache = start >= cacheLow && end <= cacheHigh + 1
|
||||||
|
val cache = if (useCache) ObjInt.cacheArray() else null
|
||||||
|
val useSlot = loopSlotIndex >= 0
|
||||||
|
if (catchBreak) {
|
||||||
|
if (useCache && cache != null) {
|
||||||
|
var i = start
|
||||||
|
while (i < end) {
|
||||||
|
val v = cache[(i - cacheLow).toInt()]
|
||||||
|
if (useSlot) forScope.setSlotValue(loopSlotIndex, v) else loopVar.value = v
|
||||||
|
try {
|
||||||
|
result = body.execute(forScope)
|
||||||
|
} catch (lbe: LoopBreakContinueException) {
|
||||||
|
if (lbe.label == label || lbe.label == null) {
|
||||||
|
if (lbe.doContinue) {
|
||||||
|
i++
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
return lbe.result
|
||||||
|
}
|
||||||
|
throw lbe
|
||||||
|
}
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for (i in start..<end) {
|
||||||
|
val v = ObjInt.of(i)
|
||||||
|
if (useSlot) forScope.setSlotValue(loopSlotIndex, v) else loopVar.value = v
|
||||||
|
try {
|
||||||
|
result = body.execute(forScope)
|
||||||
|
} catch (lbe: LoopBreakContinueException) {
|
||||||
|
if (lbe.label == label || lbe.label == null) {
|
||||||
|
if (lbe.doContinue) continue
|
||||||
|
return lbe.result
|
||||||
|
}
|
||||||
|
throw lbe
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (useCache && cache != null) {
|
||||||
|
var i = start
|
||||||
|
while (i < end) {
|
||||||
|
val v = cache[(i - cacheLow).toInt()]
|
||||||
|
if (useSlot) forScope.setSlotValue(loopSlotIndex, v) else loopVar.value = v
|
||||||
|
result = body.execute(forScope)
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for (i in start..<end) {
|
||||||
|
val v = ObjInt.of(i)
|
||||||
|
if (useSlot) forScope.setSlotValue(loopSlotIndex, v) else loopVar.value = v
|
||||||
|
result = body.execute(forScope)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return elseStatement?.execute(forScope) ?: result
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun loopIterable(
|
||||||
|
forScope: Scope,
|
||||||
|
sourceObj: Obj,
|
||||||
|
loopVar: ObjRecord,
|
||||||
|
body: Statement,
|
||||||
|
elseStatement: Statement?,
|
||||||
|
label: String?,
|
||||||
|
catchBreak: Boolean,
|
||||||
|
): Obj {
|
||||||
|
var result: Obj = ObjVoid
|
||||||
|
var breakCaught = false
|
||||||
|
sourceObj.enumerate(forScope) { item ->
|
||||||
|
loopVar.value = item
|
||||||
|
if (catchBreak) {
|
||||||
|
try {
|
||||||
|
result = body.execute(forScope)
|
||||||
|
true
|
||||||
|
} catch (lbe: LoopBreakContinueException) {
|
||||||
|
if (lbe.label == label || lbe.label == null) {
|
||||||
|
breakCaught = true
|
||||||
|
if (lbe.doContinue) true else {
|
||||||
|
result = lbe.result
|
||||||
|
false
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw lbe
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
result = body.execute(forScope)
|
||||||
|
true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!breakCaught && elseStatement != null) {
|
||||||
|
result = elseStatement.execute(forScope)
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
class ToBoolStatement(
|
class ToBoolStatement(
|
||||||
val expr: Statement,
|
val expr: Statement,
|
||||||
override val pos: Pos,
|
override val pos: Pos,
|
||||||
|
|||||||
11
notes/bytecode_exprs_loops.md
Normal file
11
notes/bytecode_exprs_loops.md
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
# Bytecode expression + for-in loop support
|
||||||
|
|
||||||
|
Changes
|
||||||
|
- Added bytecode compilation for conditional/elvis expressions, inc/dec, and compound assignments where safe.
|
||||||
|
- 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.
|
||||||
|
|
||||||
|
Tests
|
||||||
|
- ./gradlew :lynglib:jvmTest
|
||||||
|
- ./gradlew :lynglib:allTests -x :lynglib:jvmTest
|
||||||
Loading…
x
Reference in New Issue
Block a user