Compare commits
38 Commits
473a5dd6ed
...
db4f7d0973
| Author | SHA1 | Date | |
|---|---|---|---|
| db4f7d0973 | |||
| a481371349 | |||
| 51319fa8b7 | |||
| 86ac7eee54 | |||
| 18278794d6 | |||
| ac680ceb6c | |||
| 6efdfc1964 | |||
| b9af80a1b2 | |||
| bde32ca7b5 | |||
| 99ca15d20f | |||
| c14c7d43d9 | |||
| 1271f347bd | |||
| 4e08339756 | |||
| b32a937636 | |||
| 4db0a7fbab | |||
| 8b196c7b0c | |||
| 54ca886753 | |||
| abbebec153 | |||
| c066dc7150 | |||
| 6c0b86f6e6 | |||
| 8f1c660f4e | |||
| 3ce9029162 | |||
| 0caa9849cf | |||
| 8314127fdb | |||
| dab0b9f165 | |||
| 6aa23e8ef3 | |||
| c035b4c34c | |||
| 9d508e219f | |||
| b49f291bff | |||
| ae88898f58 | |||
| 565dbf98ed | |||
| 5305ced89f | |||
| b2d5897aa8 | |||
| 0b94b46d40 | |||
| 541738646f | |||
| f0dc0d2396 | |||
| 7850d5fbde | |||
| cde7cf2caf |
@ -43,6 +43,98 @@ Goal: migrate the compiler so all values live in frames/bytecode, keeping JVM te
|
|||||||
- [x] Fix post-inc return value for object slots stored in scope frames.
|
- [x] Fix post-inc return value for object slots stored in scope frames.
|
||||||
- [x] Handle optional receivers for member assign-ops and inc/dec without evaluating operands on null.
|
- [x] Handle optional receivers for member assign-ops and inc/dec without evaluating operands on null.
|
||||||
- [x] Support class-scope and index optional inc/dec paths in bytecode.
|
- [x] Support class-scope and index optional inc/dec paths in bytecode.
|
||||||
|
- [x] Step 13: Qualified `this` value refs in bytecode.
|
||||||
|
- [x] Compile `QualifiedThisRef` (`this@Type`) via `LOAD_THIS_VARIANT`.
|
||||||
|
- [x] Add a JVM test that evaluates `this@Type` as a value inside nested classes.
|
||||||
|
- [x] Step 14: Fast local ref reads in bytecode.
|
||||||
|
- [x] Support `FastLocalVarRef` reads with the same slot resolution as `LocalVarRef`.
|
||||||
|
- [x] If `BoundLocalVarRef` is still emitted, map it to a direct slot read instead of failing.
|
||||||
|
- [x] Add a JVM test that exercises fast-local reads in a bytecode-compiled function.
|
||||||
|
- [x] Step 15: Class-scope `?=` in bytecode.
|
||||||
|
- [x] Handle `C.x ?= v` and `C?.x ?= v` for class-scope members without falling back.
|
||||||
|
- [x] Add a JVM test for class-scope `?=` on static vars.
|
||||||
|
- [x] Step 16: Remove dead `ToBoolStatement`.
|
||||||
|
- [x] Confirm no parser/compiler paths construct `ToBoolStatement` and delete it plus interpreter hooks.
|
||||||
|
- [x] Keep JVM tests green after removal.
|
||||||
|
- [x] Step 17: Callable property calls in bytecode.
|
||||||
|
- [x] Support `CallRef` where the target is a `FieldRef` (e.g., `(obj.fn)()`), keeping compile-time resolution.
|
||||||
|
- [x] Add a JVM test for a callable property call compiled to bytecode.
|
||||||
|
- [x] Step 18: Delegated member access in bytecode.
|
||||||
|
- [x] Remove `containsDelegatedRefs` guard once bytecode emits delegated get/set/call correctly.
|
||||||
|
- [x] Add JVM coverage for delegated member get/set/call in bytecode.
|
||||||
|
- [x] Step 19: Unknown receiver member access in bytecode.
|
||||||
|
- [x] Reject Object/unknown receiver member calls without explicit cast or Dynamic.
|
||||||
|
- [x] Add union-member dispatch with ordered type checks and runtime mismatch error.
|
||||||
|
- [x] Add JVM tests for unknown receiver and union member access.
|
||||||
|
- [x] Step 20: Bytecode support for `NopStatement`.
|
||||||
|
- [x] Allow `NopStatement` in `containsUnsupportedForBytecode`.
|
||||||
|
- [x] Emit `ObjVoid` directly in bytecode for `NopStatement` in statement/value contexts.
|
||||||
|
- [x] Add a JVM test that exercises a code path returning `NopStatement` in bytecode (e.g., static class member decl in class body).
|
||||||
|
- [x] Step 21: Union mismatch path in bytecode.
|
||||||
|
- [x] Replace `UnionTypeMismatchStatement` branch with a bytecode-compilable throw path (no custom `StatementRef` that blocks bytecode).
|
||||||
|
- [x] Add a JVM test that forces the union mismatch at runtime and asserts the error message.
|
||||||
|
- [x] Step 22: Delegated local slots in bytecode.
|
||||||
|
- [x] Support reads/writes/assign-ops/inc/dec for delegated locals (`LocalSlotRef.isDelegated`) in `BytecodeCompiler`.
|
||||||
|
- [x] Remove `containsDelegatedRefs` guard once delegated locals are bytecode-safe.
|
||||||
|
- [x] Add JVM tests that use delegated locals inside bytecode-compiled functions.
|
||||||
|
- [x] Step 23: Refactor delegated locals to keep delegate objects in frame slots.
|
||||||
|
- [x] Add bytecode ops to bind/get/set delegated locals without scope storage.
|
||||||
|
- [x] Store delegated locals in frame slots and compile get/set/assign ops with new ops.
|
||||||
|
- [x] Preserve reflection facade by syncing delegated locals into scope only when needed.
|
||||||
|
- [x] Step 24: Remove `ASSIGN_SCOPE_SLOT` now that delegated locals are always frame-backed.
|
||||||
|
- [x] Force delegated locals into local slots (even module) and avoid scope-slot resolution.
|
||||||
|
- [x] Drop opcode/runtime support for `ASSIGN_SCOPE_SLOT`.
|
||||||
|
|
||||||
|
## Frame-Only Execution (new, before interpreter removal)
|
||||||
|
|
||||||
|
- [x] Step 24A: Bytecode capture tables for lambdas (compiler only).
|
||||||
|
- [x] Emit per-lambda capture tables containing (ownerFrameKind, ownerSlotId).
|
||||||
|
- [x] Create captures only when detected; do not allocate scope slots.
|
||||||
|
- [x] Add disassembler output for capture tables.
|
||||||
|
- [x] JVM tests must be green before commit.
|
||||||
|
- [x] Step 24B: Frame-slot captures in bytecode runtime.
|
||||||
|
- [x] Build lambdas from bytecode + capture table (no capture names).
|
||||||
|
- [x] Read captured values via `FrameSlotRef` only.
|
||||||
|
- [x] Forbid `resolveCaptureRecord` in bytecode paths; keep only in interpreter.
|
||||||
|
- [x] JVM tests must be green before commit.
|
||||||
|
- [x] Step 24C: Remove scope local mirroring in bytecode execution.
|
||||||
|
- [x] Remove/disable any bytecode runtime code that writes locals into Scope for execution.
|
||||||
|
- [x] Keep Scope creation only for reflection/Kotlin interop paths.
|
||||||
|
- [x] JVM tests must be green before commit.
|
||||||
|
- [x] Step 24D: Eliminate `ClosureScope` usage on bytecode execution paths.
|
||||||
|
- [x] Avoid `ClosureScope` in bytecode-related call paths (Block/Lambda/ObjDynamic/ObjProperty).
|
||||||
|
- [x] Keep interpreter path using `ClosureScope` until interpreter removal.
|
||||||
|
- [x] JVM tests must be green before commit.
|
||||||
|
- [x] Step 24E: Isolate interpreter-only capture logic.
|
||||||
|
- [x] Mark `resolveCaptureRecord` paths as interpreter-only.
|
||||||
|
- [x] Guard or delete any bytecode path that tries to sync captures into scopes.
|
||||||
|
- [x] JVM tests must be green before commit.
|
||||||
|
|
||||||
|
## Interpreter Removal (next)
|
||||||
|
|
||||||
|
- [ ] Step 25: Replace Statement-based declaration calls in bytecode.
|
||||||
|
- [x] Add bytecode const/op for enum declarations (no `Statement` objects in constants).
|
||||||
|
- [x] Add bytecode const/op for class declarations (no `Statement` objects in constants).
|
||||||
|
- [x] Add bytecode const/op for function declarations (no `Statement` objects in constants).
|
||||||
|
- [x] Replace `emitStatementCall` usage for `EnumDeclStatement`.
|
||||||
|
- [x] Replace `emitStatementCall` usage for `ClassDeclStatement`.
|
||||||
|
- [x] Replace `emitStatementCall` usage for `FunctionDeclStatement`.
|
||||||
|
- [x] Add JVM disasm coverage to ensure module init has no `CALL_SLOT` to `Callable@...` for declarations.
|
||||||
|
- [ ] Step 26: Bytecode-backed lambdas (remove `ValueFnRef` runtime execution).
|
||||||
|
- [x] Compile lambda bodies to bytecode and emit an opcode to create a callable from bytecode + capture plan.
|
||||||
|
- [x] Remove `containsValueFnRef` helper now that lambdas are bytecode-backed.
|
||||||
|
- [x] Remove `forceScopeSlots` branches once no bytecode paths depend on scope slots.
|
||||||
|
- [x] Add JVM tests for captured locals and delegated locals inside lambdas on the bytecode path.
|
||||||
|
- [x] Step 27: Remove interpreter opcodes and constants from bytecode runtime.
|
||||||
|
- [x] Delete `BytecodeConst.ValueFn`, `CmdMakeValueFn`, and `MAKE_VALUE_FN`.
|
||||||
|
- [x] Delete `BytecodeConst.StatementVal`, `CmdEvalStmt`, and `EVAL_STMT`.
|
||||||
|
- [x] Add bytecode-backed `::class` via `ClassOperatorRef` + `GET_OBJ_CLASS` to avoid ValueFn for class operator.
|
||||||
|
- [x] Add a bytecode fallback reporter hook for lambdas to locate remaining non-bytecode cases.
|
||||||
|
- [x] Remove `emitStatementCall`/`emitStatementEval` once unused.
|
||||||
|
- [ ] Step 28: Scope as facade only.
|
||||||
|
- [x] Audit bytecode execution paths for `Statement.execute` usage and remove remaining calls.
|
||||||
|
- [x] Keep scope sync only for reflection/Kotlin interop, not for execution.
|
||||||
|
- [x] Replace bytecode entry seeding from Scope with frame-only arg/local binding.
|
||||||
|
|
||||||
## Notes
|
## Notes
|
||||||
|
|
||||||
|
|||||||
@ -27,7 +27,9 @@ import com.github.ajalt.clikt.parameters.arguments.optional
|
|||||||
import com.github.ajalt.clikt.parameters.options.flag
|
import com.github.ajalt.clikt.parameters.options.flag
|
||||||
import com.github.ajalt.clikt.parameters.options.option
|
import com.github.ajalt.clikt.parameters.options.option
|
||||||
import kotlinx.coroutines.runBlocking
|
import kotlinx.coroutines.runBlocking
|
||||||
|
import net.sergeych.lyng.Compiler
|
||||||
import net.sergeych.lyng.LyngVersion
|
import net.sergeych.lyng.LyngVersion
|
||||||
|
import net.sergeych.lyng.Pos
|
||||||
import net.sergeych.lyng.Script
|
import net.sergeych.lyng.Script
|
||||||
import net.sergeych.lyng.ScriptError
|
import net.sergeych.lyng.ScriptError
|
||||||
import net.sergeych.lyng.Source
|
import net.sergeych.lyng.Source
|
||||||
@ -156,6 +158,7 @@ private class Lyng(val launcher: (suspend () -> Unit) -> Unit) : CliktCommand()
|
|||||||
|
|
||||||
val version by option("-v", "--version", help = "Print version and exit").flag()
|
val version by option("-v", "--version", help = "Print version and exit").flag()
|
||||||
val benchmark by option("--benchmark", help = "Run JVM microbenchmarks and exit").flag()
|
val benchmark by option("--benchmark", help = "Run JVM microbenchmarks and exit").flag()
|
||||||
|
val bytecodeFallbacks by option("--bytecode-fallbacks", help = "Report lambdas that fall back to interpreter").flag()
|
||||||
val script by argument(help = "one or more scripts to execute").optional()
|
val script by argument(help = "one or more scripts to execute").optional()
|
||||||
val execute: String? by option(
|
val execute: String? by option(
|
||||||
"-x", "--execute", help = """
|
"-x", "--execute", help = """
|
||||||
@ -198,7 +201,15 @@ private class Lyng(val launcher: (suspend () -> Unit) -> Unit) : CliktCommand()
|
|||||||
launcher {
|
launcher {
|
||||||
// there is no script name, it is a first argument instead:
|
// there is no script name, it is a first argument instead:
|
||||||
processErrors {
|
processErrors {
|
||||||
baseScope.eval(execute!!)
|
val reporter = bytecodeFallbackReporter(bytecodeFallbacks)
|
||||||
|
val script = Compiler.compileWithResolution(
|
||||||
|
Source("<eval>", execute!!),
|
||||||
|
baseScope.currentImportProvider,
|
||||||
|
seedScope = baseScope,
|
||||||
|
bytecodeFallbackReporter = reporter
|
||||||
|
)
|
||||||
|
script.execute(baseScope)
|
||||||
|
flushBytecodeFallbacks(reporter)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -209,7 +220,7 @@ private class Lyng(val launcher: (suspend () -> Unit) -> Unit) : CliktCommand()
|
|||||||
echoFormattedHelp()
|
echoFormattedHelp()
|
||||||
} else {
|
} else {
|
||||||
baseScope.addConst("ARGV", ObjList(args.map { ObjString(it) }.toMutableList()))
|
baseScope.addConst("ARGV", ObjList(args.map { ObjString(it) }.toMutableList()))
|
||||||
launcher { executeFile(script!!) }
|
launcher { executeFile(script!!, bytecodeFallbacks) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -217,14 +228,14 @@ private class Lyng(val launcher: (suspend () -> Unit) -> Unit) : CliktCommand()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun executeFileWithArgs(fileName: String, args: List<String>) {
|
fun executeFileWithArgs(fileName: String, args: List<String>, reportBytecodeFallbacks: Boolean = false) {
|
||||||
runBlocking {
|
runBlocking {
|
||||||
baseScopeDefer.await().addConst("ARGV", ObjList(args.map { ObjString(it) }.toMutableList()))
|
baseScopeDefer.await().addConst("ARGV", ObjList(args.map { ObjString(it) }.toMutableList()))
|
||||||
executeFile(fileName)
|
executeFile(fileName, reportBytecodeFallbacks)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun executeFile(fileName: String) {
|
suspend fun executeFile(fileName: String, reportBytecodeFallbacks: Boolean = false) {
|
||||||
var text = FileSystem.SYSTEM.source(fileName.toPath()).use { fileSource ->
|
var text = FileSystem.SYSTEM.source(fileName.toPath()).use { fileSource ->
|
||||||
fileSource.buffer().use { bs ->
|
fileSource.buffer().use { bs ->
|
||||||
bs.readUtf8()
|
bs.readUtf8()
|
||||||
@ -236,7 +247,16 @@ suspend fun executeFile(fileName: String) {
|
|||||||
text = text.substring(pos + 1)
|
text = text.substring(pos + 1)
|
||||||
}
|
}
|
||||||
processErrors {
|
processErrors {
|
||||||
baseScopeDefer.await().eval(Source(fileName, text))
|
val scope = baseScopeDefer.await()
|
||||||
|
val reporter = bytecodeFallbackReporter(reportBytecodeFallbacks)
|
||||||
|
val script = Compiler.compileWithResolution(
|
||||||
|
Source(fileName, text),
|
||||||
|
scope.currentImportProvider,
|
||||||
|
seedScope = scope,
|
||||||
|
bytecodeFallbackReporter = reporter
|
||||||
|
)
|
||||||
|
script.execute(scope)
|
||||||
|
flushBytecodeFallbacks(reporter)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -248,3 +268,22 @@ suspend fun processErrors(block: suspend () -> Unit) {
|
|||||||
println("\nError executing the script:\n$e\n")
|
println("\nError executing the script:\n$e\n")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun bytecodeFallbackReporter(enabled: Boolean): ((Pos, String) -> Unit)? {
|
||||||
|
if (!enabled) return null
|
||||||
|
val reports = ArrayList<String>()
|
||||||
|
val reporter: (Pos, String) -> Unit = { pos, msg ->
|
||||||
|
reports.add("$pos: $msg")
|
||||||
|
}
|
||||||
|
return object : (Pos, String) -> Unit by reporter {
|
||||||
|
override fun invoke(pos: Pos, msg: String) = reporter(pos, msg)
|
||||||
|
override fun toString(): String = reports.joinToString("\n")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun flushBytecodeFallbacks(reporter: ((Pos, String) -> Unit)?) {
|
||||||
|
val text = reporter?.toString().orEmpty()
|
||||||
|
if (text.isBlank()) return
|
||||||
|
println("Bytecode lambda fallbacks:")
|
||||||
|
println(text)
|
||||||
|
}
|
||||||
|
|||||||
@ -250,6 +250,212 @@ data class ArgsDeclaration(val params: List<Item>, val endTokenType: Token.Type)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Assign arguments directly into frame slots using [paramSlotPlan] without creating scope locals.
|
||||||
|
* Still allows default expressions to evaluate by exposing FrameSlotRef facades in [scope].
|
||||||
|
*/
|
||||||
|
suspend fun assignToFrame(
|
||||||
|
scope: Scope,
|
||||||
|
arguments: Arguments = scope.args,
|
||||||
|
paramSlotPlan: Map<String, Int>,
|
||||||
|
frame: FrameAccess,
|
||||||
|
slotOffset: Int = 0,
|
||||||
|
defaultAccessType: AccessType = AccessType.Var,
|
||||||
|
defaultVisibility: Visibility = Visibility.Public,
|
||||||
|
declaringClass: net.sergeych.lyng.obj.ObjClass? = scope.currentClassCtx
|
||||||
|
) {
|
||||||
|
fun slotFor(name: String): Int {
|
||||||
|
val full = paramSlotPlan[name] ?: scope.raiseIllegalState("parameter slot for '$name' is missing")
|
||||||
|
val slot = full - slotOffset
|
||||||
|
if (slot < 0) scope.raiseIllegalState("parameter slot for '$name' is out of range")
|
||||||
|
return slot
|
||||||
|
}
|
||||||
|
|
||||||
|
fun ensureScopeRef(a: Item, slot: Int, recordType: ObjRecord.Type) {
|
||||||
|
if (scope.getLocalRecordDirect(a.name) != null) return
|
||||||
|
scope.addItem(
|
||||||
|
a.name,
|
||||||
|
(a.accessType ?: defaultAccessType).isMutable,
|
||||||
|
FrameSlotRef(frame, slot),
|
||||||
|
a.visibility ?: defaultVisibility,
|
||||||
|
recordType = recordType,
|
||||||
|
declaringClass = declaringClass,
|
||||||
|
isTransient = a.isTransient
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setFrameValue(slot: Int, value: Obj) {
|
||||||
|
when (value) {
|
||||||
|
is net.sergeych.lyng.obj.ObjInt -> frame.setInt(slot, value.value)
|
||||||
|
is net.sergeych.lyng.obj.ObjReal -> frame.setReal(slot, value.value)
|
||||||
|
is net.sergeych.lyng.obj.ObjBool -> frame.setBool(slot, value.value)
|
||||||
|
else -> frame.setObj(slot, value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun assign(a: Item, value: Obj) {
|
||||||
|
val recordType = if (declaringClass != null && a.accessType != null) {
|
||||||
|
ObjRecord.Type.ConstructorField
|
||||||
|
} else {
|
||||||
|
ObjRecord.Type.Argument
|
||||||
|
}
|
||||||
|
val slot = slotFor(a.name)
|
||||||
|
setFrameValue(slot, value.byValueCopy())
|
||||||
|
ensureScopeRef(a, slot, recordType)
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun missingValue(a: Item, error: String): Obj {
|
||||||
|
return a.defaultValue?.execute(scope)
|
||||||
|
?: if (a.type.isNullable) ObjNull else scope.raiseIllegalArgument(error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fast path for simple positional-only calls with no ellipsis and no defaults
|
||||||
|
if (arguments.named.isEmpty() && !arguments.tailBlockMode) {
|
||||||
|
var hasComplex = false
|
||||||
|
for (p in params) {
|
||||||
|
if (p.isEllipsis || p.defaultValue != null) {
|
||||||
|
hasComplex = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!hasComplex) {
|
||||||
|
if (arguments.list.size > params.size)
|
||||||
|
scope.raiseIllegalArgument("expected ${params.size} arguments, got ${arguments.list.size}")
|
||||||
|
if (arguments.list.size < params.size) {
|
||||||
|
for (i in arguments.list.size until params.size) {
|
||||||
|
val a = params[i]
|
||||||
|
if (!a.type.isNullable) {
|
||||||
|
scope.raiseIllegalArgument("expected ${params.size} arguments, got ${arguments.list.size}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (i in params.indices) {
|
||||||
|
val a = params[i]
|
||||||
|
val value = if (i < arguments.list.size) arguments.list[i] else ObjNull
|
||||||
|
assign(a, value)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prepare positional args and parameter count, handle tail-block binding
|
||||||
|
val callArgs: List<Obj>
|
||||||
|
val paramsSize: Int
|
||||||
|
if (arguments.tailBlockMode) {
|
||||||
|
val lastParam = params.last()
|
||||||
|
if (arguments.named.containsKey(lastParam.name))
|
||||||
|
scope.raiseIllegalArgument("trailing block cannot be used when the last parameter is already assigned by a named argument")
|
||||||
|
paramsSize = params.size - 1
|
||||||
|
assign(lastParam, arguments.list.last())
|
||||||
|
callArgs = arguments.list.dropLast(1)
|
||||||
|
} else {
|
||||||
|
paramsSize = params.size
|
||||||
|
callArgs = arguments.list
|
||||||
|
}
|
||||||
|
|
||||||
|
val coveredByPositional = BooleanArray(paramsSize)
|
||||||
|
run {
|
||||||
|
var headRequired = 0
|
||||||
|
var tailRequired = 0
|
||||||
|
val ellipsisIdx = params.subList(0, paramsSize).indexOfFirst { it.isEllipsis }
|
||||||
|
if (ellipsisIdx >= 0) {
|
||||||
|
for (i in 0 until ellipsisIdx) if (!params[i].isEllipsis && params[i].defaultValue == null) headRequired++
|
||||||
|
for (i in paramsSize - 1 downTo ellipsisIdx + 1) if (params[i].defaultValue == null) tailRequired++
|
||||||
|
} else {
|
||||||
|
for (i in 0 until paramsSize) if (params[i].defaultValue == null) headRequired++
|
||||||
|
}
|
||||||
|
val P = callArgs.size
|
||||||
|
if (ellipsisIdx < 0) {
|
||||||
|
val k = minOf(P, paramsSize)
|
||||||
|
for (i in 0 until k) coveredByPositional[i] = true
|
||||||
|
} else {
|
||||||
|
val headTake = minOf(P, headRequired)
|
||||||
|
for (i in 0 until headTake) coveredByPositional[i] = true
|
||||||
|
val remaining = P - headTake
|
||||||
|
val tailTake = minOf(remaining, tailRequired)
|
||||||
|
var j = paramsSize - 1
|
||||||
|
var taken = 0
|
||||||
|
while (j > ellipsisIdx && taken < tailTake) {
|
||||||
|
coveredByPositional[j] = true
|
||||||
|
j--
|
||||||
|
taken++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val assignedByName = BooleanArray(paramsSize)
|
||||||
|
val namedValues = arrayOfNulls<Obj>(paramsSize)
|
||||||
|
if (arguments.named.isNotEmpty()) {
|
||||||
|
for ((k, v) in arguments.named) {
|
||||||
|
val idx = params.subList(0, paramsSize).indexOfFirst { it.name == k }
|
||||||
|
if (idx < 0) scope.raiseIllegalArgument("unknown parameter '$k'")
|
||||||
|
if (params[idx].isEllipsis) scope.raiseIllegalArgument("ellipsis (variadic) parameter cannot be assigned by name: '$k'")
|
||||||
|
if (coveredByPositional[idx]) scope.raiseIllegalArgument("argument '$k' is already set by positional argument")
|
||||||
|
if (assignedByName[idx]) scope.raiseIllegalArgument("argument '$k' is already set")
|
||||||
|
assignedByName[idx] = true
|
||||||
|
namedValues[idx] = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun processHead(index: Int, headPos: Int): Pair<Int, Int> {
|
||||||
|
var i = index
|
||||||
|
var hp = headPos
|
||||||
|
while (i < paramsSize) {
|
||||||
|
val a = params[i]
|
||||||
|
if (a.isEllipsis) break
|
||||||
|
if (assignedByName[i]) {
|
||||||
|
assign(a, namedValues[i]!!)
|
||||||
|
} else {
|
||||||
|
val value = if (hp < callArgs.size) callArgs[hp++]
|
||||||
|
else missingValue(a, "too few arguments for the call (missing ${a.name})")
|
||||||
|
assign(a, value)
|
||||||
|
}
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
return i to hp
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun processTail(startExclusive: Int, tailStart: Int, headPosBound: Int): Int {
|
||||||
|
var i = paramsSize - 1
|
||||||
|
var tp = tailStart
|
||||||
|
while (i > startExclusive) {
|
||||||
|
val a = params[i]
|
||||||
|
if (a.isEllipsis) break
|
||||||
|
if (i < assignedByName.size && assignedByName[i]) {
|
||||||
|
assign(a, namedValues[i]!!)
|
||||||
|
} else {
|
||||||
|
val value = if (tp >= headPosBound) callArgs[tp--]
|
||||||
|
else missingValue(a, "too few arguments for the call")
|
||||||
|
assign(a, value)
|
||||||
|
}
|
||||||
|
i--
|
||||||
|
}
|
||||||
|
return tp
|
||||||
|
}
|
||||||
|
|
||||||
|
fun processEllipsis(index: Int, headPos: Int, tailPos: Int) {
|
||||||
|
val a = params[index]
|
||||||
|
val from = headPos
|
||||||
|
val to = tailPos
|
||||||
|
val l = if (from > to) ObjList()
|
||||||
|
else ObjList(callArgs.subList(from, to + 1).toMutableList())
|
||||||
|
assign(a, l)
|
||||||
|
}
|
||||||
|
|
||||||
|
val ellipsisIndex = params.subList(0, paramsSize).indexOfFirst { it.isEllipsis }
|
||||||
|
|
||||||
|
if (ellipsisIndex >= 0) {
|
||||||
|
val (_, headConsumedTo) = processHead(0, 0)
|
||||||
|
val tailConsumedFrom = processTail(ellipsisIndex, callArgs.size - 1, headConsumedTo)
|
||||||
|
processEllipsis(ellipsisIndex, headConsumedTo, tailConsumedFrom)
|
||||||
|
} else {
|
||||||
|
val (_, headConsumedTo) = processHead(0, 0)
|
||||||
|
if (headConsumedTo != callArgs.size)
|
||||||
|
scope.raiseIllegalArgument("too many arguments for the call")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Single argument declaration descriptor.
|
* Single argument declaration descriptor.
|
||||||
*
|
*
|
||||||
|
|||||||
@ -31,22 +31,33 @@ class BlockStatement(
|
|||||||
val target = if (scope.skipScopeCreation) scope else scope.createChildScope(startPos)
|
val target = if (scope.skipScopeCreation) scope else scope.createChildScope(startPos)
|
||||||
if (slotPlan.isNotEmpty()) target.applySlotPlan(slotPlan)
|
if (slotPlan.isNotEmpty()) target.applySlotPlan(slotPlan)
|
||||||
if (captureSlots.isNotEmpty()) {
|
if (captureSlots.isNotEmpty()) {
|
||||||
val applyScope = scope as? ApplyScope
|
val captureRecords = scope.captureRecords
|
||||||
for (capture in captureSlots) {
|
if (captureRecords != null) {
|
||||||
val rec = if (applyScope != null) {
|
for (i in captureSlots.indices) {
|
||||||
applyScope.resolveCaptureRecord(capture.name)
|
val capture = captureSlots[i]
|
||||||
?: applyScope.callScope.resolveCaptureRecord(capture.name)
|
val rec = captureRecords.getOrNull(i)
|
||||||
} else {
|
?: scope.raiseSymbolNotFound("capture ${capture.name} not found")
|
||||||
scope.resolveCaptureRecord(capture.name)
|
target.updateSlotFor(capture.name, rec)
|
||||||
}
|
}
|
||||||
if (rec == null) {
|
} else {
|
||||||
if (scope.getSlotIndexOf(capture.name) == null && scope.getLocalRecordDirect(capture.name) == null) {
|
val applyScope = scope as? ApplyScope
|
||||||
continue
|
for (capture in captureSlots) {
|
||||||
|
// Interpreter-only capture resolution; bytecode paths must use captureRecords instead.
|
||||||
|
val rec = if (applyScope != null) {
|
||||||
|
applyScope.resolveCaptureRecord(capture.name)
|
||||||
|
?: applyScope.callScope.resolveCaptureRecord(capture.name)
|
||||||
|
} else {
|
||||||
|
scope.resolveCaptureRecord(capture.name)
|
||||||
}
|
}
|
||||||
(applyScope?.callScope ?: scope)
|
if (rec == null) {
|
||||||
.raiseSymbolNotFound("symbol ${capture.name} not found")
|
if (scope.getSlotIndexOf(capture.name) == null && scope.getLocalRecordDirect(capture.name) == null) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
(applyScope?.callScope ?: scope)
|
||||||
|
.raiseSymbolNotFound("symbol ${capture.name} not found")
|
||||||
|
}
|
||||||
|
target.updateSlotFor(capture.name, rec)
|
||||||
}
|
}
|
||||||
target.updateSlotFor(capture.name, rec)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return block.execute(target)
|
return block.execute(target)
|
||||||
|
|||||||
@ -0,0 +1,19 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2026 Sergey S. Chernov real.sergeych@gmail.com
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package net.sergeych.lyng
|
||||||
|
|
||||||
|
interface BytecodeCallable
|
||||||
@ -17,20 +17,147 @@
|
|||||||
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.ObjException
|
||||||
|
import net.sergeych.lyng.obj.ObjInstance
|
||||||
|
import net.sergeych.lyng.obj.ObjInstanceClass
|
||||||
|
import net.sergeych.lyng.obj.ObjNull
|
||||||
|
import net.sergeych.lyng.obj.ObjRecord
|
||||||
|
|
||||||
|
data class ClassDeclBaseSpec(
|
||||||
|
val name: String,
|
||||||
|
val args: List<ParsedArgument>?
|
||||||
|
)
|
||||||
|
|
||||||
|
data class ClassDeclSpec(
|
||||||
|
val declaredName: String?,
|
||||||
|
val className: String,
|
||||||
|
val typeName: String,
|
||||||
|
val startPos: Pos,
|
||||||
|
val isExtern: Boolean,
|
||||||
|
val isAbstract: Boolean,
|
||||||
|
val isObject: Boolean,
|
||||||
|
val isAnonymous: Boolean,
|
||||||
|
val baseSpecs: List<ClassDeclBaseSpec>,
|
||||||
|
val constructorArgs: ArgsDeclaration?,
|
||||||
|
val constructorFieldIds: Map<String, Int>?,
|
||||||
|
val bodyInit: Statement?,
|
||||||
|
val initScope: List<Statement>,
|
||||||
|
)
|
||||||
|
|
||||||
|
internal suspend fun executeClassDecl(scope: Scope, spec: ClassDeclSpec): Obj {
|
||||||
|
if (spec.isObject) {
|
||||||
|
val parentClasses = spec.baseSpecs.map { baseSpec ->
|
||||||
|
val rec = scope[baseSpec.name] ?: throw ScriptError(spec.startPos, "unknown base class: ${baseSpec.name}")
|
||||||
|
(rec.value as? ObjClass) ?: throw ScriptError(spec.startPos, "${baseSpec.name} is not a class")
|
||||||
|
}
|
||||||
|
|
||||||
|
val newClass = ObjInstanceClass(spec.className, *parentClasses.toTypedArray())
|
||||||
|
newClass.isAnonymous = spec.isAnonymous
|
||||||
|
newClass.constructorMeta = ArgsDeclaration(emptyList(), Token.Type.RPAREN)
|
||||||
|
for (i in parentClasses.indices) {
|
||||||
|
val argsList = spec.baseSpecs[i].args
|
||||||
|
if (argsList != null) newClass.directParentArgs[parentClasses[i]] = argsList
|
||||||
|
}
|
||||||
|
|
||||||
|
val classScope = scope.createChildScope(newThisObj = newClass)
|
||||||
|
classScope.currentClassCtx = newClass
|
||||||
|
newClass.classScope = classScope
|
||||||
|
classScope.addConst("object", newClass)
|
||||||
|
|
||||||
|
spec.bodyInit?.execute(classScope)
|
||||||
|
|
||||||
|
val instance = newClass.callOn(scope.createChildScope(Arguments.EMPTY))
|
||||||
|
if (spec.declaredName != null) {
|
||||||
|
scope.addItem(spec.declaredName, false, instance)
|
||||||
|
}
|
||||||
|
return instance
|
||||||
|
}
|
||||||
|
|
||||||
|
if (spec.isExtern) {
|
||||||
|
val rec = scope[spec.className]
|
||||||
|
val existing = rec?.value as? ObjClass
|
||||||
|
val resolved = if (existing != null) {
|
||||||
|
existing
|
||||||
|
} else if (spec.className.contains('.')) {
|
||||||
|
scope.resolveQualifiedIdentifier(spec.className) as? ObjClass
|
||||||
|
} else {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
val stub = resolved ?: ObjInstanceClass(spec.className).apply { this.isAbstract = true }
|
||||||
|
spec.declaredName?.let { scope.addItem(it, false, stub) }
|
||||||
|
return stub
|
||||||
|
}
|
||||||
|
|
||||||
|
val parentClasses = spec.baseSpecs.map { baseSpec ->
|
||||||
|
val rec = scope[baseSpec.name]
|
||||||
|
val cls = rec?.value as? ObjClass
|
||||||
|
if (cls != null) return@map cls
|
||||||
|
if (baseSpec.name == "Exception") return@map ObjException.Root
|
||||||
|
if (rec == null) throw ScriptError(spec.startPos, "unknown base class: ${baseSpec.name}")
|
||||||
|
throw ScriptError(spec.startPos, "${baseSpec.name} is not a class")
|
||||||
|
}
|
||||||
|
|
||||||
|
val constructorCode = object : Statement() {
|
||||||
|
override val pos: Pos = spec.startPos
|
||||||
|
override suspend fun execute(scope: Scope): Obj {
|
||||||
|
val instance = scope.thisObj as ObjInstance
|
||||||
|
return instance
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val newClass = ObjInstanceClass(spec.className, *parentClasses.toTypedArray()).also {
|
||||||
|
it.isAbstract = spec.isAbstract
|
||||||
|
it.instanceConstructor = constructorCode
|
||||||
|
it.constructorMeta = spec.constructorArgs
|
||||||
|
for (i in parentClasses.indices) {
|
||||||
|
val argsList = spec.baseSpecs[i].args
|
||||||
|
if (argsList != null) it.directParentArgs[parentClasses[i]] = argsList
|
||||||
|
}
|
||||||
|
spec.constructorArgs?.params?.forEach { p ->
|
||||||
|
if (p.accessType != null) {
|
||||||
|
it.createField(
|
||||||
|
p.name,
|
||||||
|
ObjNull,
|
||||||
|
isMutable = p.accessType == AccessType.Var,
|
||||||
|
visibility = p.visibility ?: Visibility.Public,
|
||||||
|
declaringClass = it,
|
||||||
|
pos = Pos.builtIn,
|
||||||
|
isTransient = p.isTransient,
|
||||||
|
type = ObjRecord.Type.ConstructorField,
|
||||||
|
fieldId = spec.constructorFieldIds?.get(p.name)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
spec.declaredName?.let { scope.addItem(it, false, newClass) }
|
||||||
|
val classScope = scope.createChildScope(newThisObj = newClass)
|
||||||
|
classScope.currentClassCtx = newClass
|
||||||
|
newClass.classScope = classScope
|
||||||
|
spec.bodyInit?.execute(classScope)
|
||||||
|
if (spec.initScope.isNotEmpty()) {
|
||||||
|
for (s in spec.initScope) {
|
||||||
|
s.execute(classScope)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
newClass.checkAbstractSatisfaction(spec.startPos)
|
||||||
|
return newClass
|
||||||
|
}
|
||||||
|
|
||||||
class ClassDeclStatement(
|
class ClassDeclStatement(
|
||||||
private val delegate: Statement,
|
val spec: ClassDeclSpec,
|
||||||
private val startPos: Pos,
|
|
||||||
val declaredName: String?,
|
|
||||||
) : Statement() {
|
) : Statement() {
|
||||||
override val pos: Pos = startPos
|
override val pos: Pos = spec.startPos
|
||||||
|
val declaredName: String? get() = spec.declaredName
|
||||||
|
val typeName: String get() = spec.typeName
|
||||||
|
|
||||||
override suspend fun execute(scope: Scope): Obj {
|
override suspend fun execute(scope: Scope): Obj {
|
||||||
return delegate.execute(scope)
|
return executeClassDecl(scope, spec)
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun callOn(scope: Scope): Obj {
|
override suspend fun callOn(scope: Scope): Obj {
|
||||||
val target = scope.parent ?: scope
|
val target = scope.parent ?: scope
|
||||||
return delegate.execute(target)
|
return executeClassDecl(target, spec)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -84,6 +84,32 @@ class ClosureScope(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Bytecode-oriented closure scope that keeps the call scope parent chain for stack traces
|
||||||
|
* while carrying the lexical closure for `this` variants and module resolution.
|
||||||
|
* Unlike [ClosureScope], it does not override name lookup.
|
||||||
|
*/
|
||||||
|
class BytecodeClosureScope(
|
||||||
|
val callScope: Scope,
|
||||||
|
val closureScope: Scope,
|
||||||
|
private val preferredThisType: String? = null
|
||||||
|
) :
|
||||||
|
Scope(callScope, callScope.args, thisObj = closureScope.thisObj) {
|
||||||
|
|
||||||
|
init {
|
||||||
|
val desired = preferredThisType?.let { typeName ->
|
||||||
|
callScope.thisVariants.firstOrNull { it.objClass.className == typeName }
|
||||||
|
}
|
||||||
|
val primaryThis = closureScope.thisObj
|
||||||
|
val merged = ArrayList<Obj>(callScope.thisVariants.size + closureScope.thisVariants.size + 1)
|
||||||
|
desired?.let { merged.add(it) }
|
||||||
|
merged.addAll(callScope.thisVariants)
|
||||||
|
merged.addAll(closureScope.thisVariants)
|
||||||
|
setThisVariants(primaryThis, merged)
|
||||||
|
this.currentClassCtx = closureScope.currentClassCtx ?: callScope.currentClassCtx
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
class ApplyScope(val callScope: Scope, val applied: Scope) :
|
class ApplyScope(val callScope: Scope, val applied: Scope) :
|
||||||
Scope(callScope, thisObj = applied.thisObj) {
|
Scope(callScope, thisObj = applied.thisObj) {
|
||||||
|
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,27 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2026 Sergey S. Chernov
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package net.sergeych.lyng
|
||||||
|
|
||||||
|
import net.sergeych.lyng.obj.Obj
|
||||||
|
|
||||||
|
interface DeclExecutable {
|
||||||
|
suspend fun execute(scope: Scope): Obj
|
||||||
|
}
|
||||||
|
|
||||||
|
class StatementDeclExecutable(private val delegate: Statement) : DeclExecutable {
|
||||||
|
override suspend fun execute(scope: Scope): Obj = delegate.execute(scope)
|
||||||
|
}
|
||||||
@ -28,6 +28,8 @@ class DelegatedVarDeclStatement(
|
|||||||
val visibility: Visibility,
|
val visibility: Visibility,
|
||||||
val initializer: Statement,
|
val initializer: Statement,
|
||||||
val isTransient: Boolean,
|
val isTransient: Boolean,
|
||||||
|
val slotIndex: Int?,
|
||||||
|
val scopeId: Int?,
|
||||||
private val startPos: Pos,
|
private val startPos: Pos,
|
||||||
) : Statement() {
|
) : Statement() {
|
||||||
override val pos: Pos = startPos
|
override val pos: Pos = startPos
|
||||||
|
|||||||
@ -17,19 +17,34 @@
|
|||||||
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(
|
||||||
private val delegate: Statement,
|
val declaredName: String,
|
||||||
|
val qualifiedName: String,
|
||||||
|
val entries: List<String>,
|
||||||
|
val lifted: Boolean,
|
||||||
private val startPos: Pos,
|
private val startPos: Pos,
|
||||||
) : Statement() {
|
) : Statement() {
|
||||||
override val pos: Pos = startPos
|
override val pos: Pos = startPos
|
||||||
|
|
||||||
override suspend fun execute(scope: Scope): Obj {
|
override suspend fun execute(scope: Scope): Obj {
|
||||||
return delegate.execute(scope)
|
val enumClass = ObjEnumClass.createSimpleEnum(qualifiedName, entries)
|
||||||
|
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 {
|
||||||
val target = scope.parent ?: scope
|
val target = scope.parent ?: scope
|
||||||
return delegate.execute(target)
|
return execute(target)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -55,3 +55,16 @@ class FrameSlotRef(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class RecordSlotRef(
|
||||||
|
private val record: ObjRecord,
|
||||||
|
) : net.sergeych.lyng.obj.Obj() {
|
||||||
|
fun read(): Obj {
|
||||||
|
val direct = record.value
|
||||||
|
return if (direct is FrameSlotRef) direct.read() else direct
|
||||||
|
}
|
||||||
|
|
||||||
|
fun write(value: Obj) {
|
||||||
|
record.value = value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -17,19 +17,247 @@
|
|||||||
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.ObjExtensionMethodCallable
|
||||||
|
import net.sergeych.lyng.obj.ObjInstance
|
||||||
|
import net.sergeych.lyng.obj.ObjRecord
|
||||||
|
import net.sergeych.lyng.obj.ObjString
|
||||||
|
import net.sergeych.lyng.obj.ObjUnset
|
||||||
|
import net.sergeych.lyng.obj.ObjVoid
|
||||||
|
|
||||||
|
class FunctionClosureBox(
|
||||||
|
var closure: Scope? = null,
|
||||||
|
var captureContext: Scope? = null,
|
||||||
|
)
|
||||||
|
|
||||||
|
data class FunctionDeclSpec(
|
||||||
|
val name: String,
|
||||||
|
val visibility: Visibility,
|
||||||
|
val isAbstract: Boolean,
|
||||||
|
val isClosed: Boolean,
|
||||||
|
val isOverride: Boolean,
|
||||||
|
val isStatic: Boolean,
|
||||||
|
val isTransient: Boolean,
|
||||||
|
val isDelegated: Boolean,
|
||||||
|
val delegateExpression: Statement?,
|
||||||
|
val extTypeName: String?,
|
||||||
|
val extensionWrapperName: String?,
|
||||||
|
val memberMethodId: Int?,
|
||||||
|
val actualExtern: Boolean,
|
||||||
|
val parentIsClassBody: Boolean,
|
||||||
|
val externCallSignature: CallSignature?,
|
||||||
|
val annotation: (suspend (Scope, ObjString, Statement) -> Statement)?,
|
||||||
|
val fnBody: Statement,
|
||||||
|
val closureBox: FunctionClosureBox,
|
||||||
|
val captureSlots: List<CaptureSlot>,
|
||||||
|
val startPos: Pos,
|
||||||
|
)
|
||||||
|
|
||||||
|
internal suspend fun executeFunctionDecl(scope: Scope, spec: FunctionDeclSpec): Obj {
|
||||||
|
if (spec.actualExtern && spec.extTypeName == null && !spec.parentIsClassBody) {
|
||||||
|
val existing = scope.get(spec.name)
|
||||||
|
if (existing != null) {
|
||||||
|
scope.addItem(
|
||||||
|
spec.name,
|
||||||
|
false,
|
||||||
|
existing.value,
|
||||||
|
spec.visibility,
|
||||||
|
callSignature = existing.callSignature
|
||||||
|
)
|
||||||
|
return existing.value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (spec.isDelegated) {
|
||||||
|
val delegateExpr = spec.delegateExpression ?: scope.raiseError("delegated function missing delegate")
|
||||||
|
val accessType = ObjString("Callable")
|
||||||
|
val initValue = delegateExpr.execute(scope)
|
||||||
|
val finalDelegate = try {
|
||||||
|
initValue.invokeInstanceMethod(scope, "bind", Arguments(ObjString(spec.name), accessType, scope.thisObj))
|
||||||
|
} catch (e: Exception) {
|
||||||
|
initValue
|
||||||
|
}
|
||||||
|
|
||||||
|
if (spec.extTypeName != null) {
|
||||||
|
val type = scope[spec.extTypeName]?.value ?: scope.raiseSymbolNotFound("class ${spec.extTypeName} not found")
|
||||||
|
if (type !is ObjClass) scope.raiseClassCastError("${spec.extTypeName} is not the class instance")
|
||||||
|
scope.addExtension(
|
||||||
|
type,
|
||||||
|
spec.name,
|
||||||
|
ObjRecord(ObjUnset, isMutable = false, visibility = spec.visibility, declaringClass = null, type = ObjRecord.Type.Delegated).apply {
|
||||||
|
delegate = finalDelegate
|
||||||
|
}
|
||||||
|
)
|
||||||
|
return ObjVoid
|
||||||
|
}
|
||||||
|
|
||||||
|
val th = scope.thisObj
|
||||||
|
if (spec.isStatic) {
|
||||||
|
(th as ObjClass).createClassField(
|
||||||
|
spec.name,
|
||||||
|
ObjUnset,
|
||||||
|
false,
|
||||||
|
spec.visibility,
|
||||||
|
null,
|
||||||
|
spec.startPos,
|
||||||
|
isTransient = spec.isTransient,
|
||||||
|
type = ObjRecord.Type.Delegated
|
||||||
|
).apply {
|
||||||
|
delegate = finalDelegate
|
||||||
|
}
|
||||||
|
scope.addItem(
|
||||||
|
spec.name,
|
||||||
|
false,
|
||||||
|
ObjUnset,
|
||||||
|
spec.visibility,
|
||||||
|
recordType = ObjRecord.Type.Delegated,
|
||||||
|
isTransient = spec.isTransient
|
||||||
|
).apply {
|
||||||
|
delegate = finalDelegate
|
||||||
|
}
|
||||||
|
} else if (th is ObjClass) {
|
||||||
|
val cls: ObjClass = th
|
||||||
|
val storageName = "${cls.className}::${spec.name}"
|
||||||
|
cls.createField(
|
||||||
|
spec.name,
|
||||||
|
ObjUnset,
|
||||||
|
false,
|
||||||
|
spec.visibility,
|
||||||
|
null,
|
||||||
|
spec.startPos,
|
||||||
|
declaringClass = cls,
|
||||||
|
isAbstract = spec.isAbstract,
|
||||||
|
isClosed = spec.isClosed,
|
||||||
|
isOverride = spec.isOverride,
|
||||||
|
isTransient = spec.isTransient,
|
||||||
|
type = ObjRecord.Type.Delegated,
|
||||||
|
methodId = spec.memberMethodId
|
||||||
|
)
|
||||||
|
cls.instanceInitializers += object : Statement() {
|
||||||
|
override val pos: Pos = spec.startPos
|
||||||
|
override suspend fun execute(scp: Scope): Obj {
|
||||||
|
val accessType2 = ObjString("Callable")
|
||||||
|
val initValue2 = delegateExpr.execute(scp)
|
||||||
|
val finalDelegate2 = try {
|
||||||
|
initValue2.invokeInstanceMethod(scp, "bind", Arguments(ObjString(spec.name), accessType2, scp.thisObj))
|
||||||
|
} catch (e: Exception) {
|
||||||
|
initValue2
|
||||||
|
}
|
||||||
|
scp.addItem(
|
||||||
|
storageName,
|
||||||
|
false,
|
||||||
|
ObjUnset,
|
||||||
|
spec.visibility,
|
||||||
|
null,
|
||||||
|
recordType = ObjRecord.Type.Delegated,
|
||||||
|
isAbstract = spec.isAbstract,
|
||||||
|
isClosed = spec.isClosed,
|
||||||
|
isOverride = spec.isOverride,
|
||||||
|
isTransient = spec.isTransient
|
||||||
|
).apply {
|
||||||
|
delegate = finalDelegate2
|
||||||
|
}
|
||||||
|
return ObjVoid
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
scope.addItem(
|
||||||
|
spec.name,
|
||||||
|
false,
|
||||||
|
ObjUnset,
|
||||||
|
spec.visibility,
|
||||||
|
recordType = ObjRecord.Type.Delegated,
|
||||||
|
isTransient = spec.isTransient
|
||||||
|
).apply {
|
||||||
|
delegate = finalDelegate
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ObjVoid
|
||||||
|
}
|
||||||
|
|
||||||
|
if (spec.isStatic || !spec.parentIsClassBody) {
|
||||||
|
spec.closureBox.closure = scope
|
||||||
|
}
|
||||||
|
if (spec.parentIsClassBody && spec.captureSlots.isNotEmpty()) {
|
||||||
|
spec.closureBox.captureContext = scope
|
||||||
|
}
|
||||||
|
|
||||||
|
val annotatedFnBody = spec.annotation?.invoke(scope, ObjString(spec.name), spec.fnBody) ?: spec.fnBody
|
||||||
|
val compiledFnBody = annotatedFnBody
|
||||||
|
|
||||||
|
spec.extTypeName?.let { typeName ->
|
||||||
|
val type = scope[typeName]?.value ?: scope.raiseSymbolNotFound("class $typeName not found")
|
||||||
|
if (type !is ObjClass) scope.raiseClassCastError("$typeName is not the class instance")
|
||||||
|
val stmt = object : Statement() {
|
||||||
|
override val pos: Pos = spec.startPos
|
||||||
|
override suspend fun execute(scope: Scope): Obj {
|
||||||
|
val result = (scope.thisObj as? ObjInstance)?.let { i ->
|
||||||
|
val execScope = if (compiledFnBody is net.sergeych.lyng.bytecode.BytecodeStatement) {
|
||||||
|
scope.applyClosureForBytecode(i.instanceScope).also {
|
||||||
|
it.args = scope.args
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ClosureScope(scope, i.instanceScope)
|
||||||
|
}
|
||||||
|
compiledFnBody.execute(execScope)
|
||||||
|
} ?: compiledFnBody.execute(scope.thisObj.autoInstanceScope(scope))
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
}
|
||||||
|
scope.addExtension(type, spec.name, ObjRecord(stmt, isMutable = false, visibility = spec.visibility, declaringClass = null))
|
||||||
|
val wrapperName = spec.extensionWrapperName ?: extensionCallableName(typeName, spec.name)
|
||||||
|
val wrapper = ObjExtensionMethodCallable(spec.name, stmt)
|
||||||
|
scope.addItem(wrapperName, false, wrapper, spec.visibility, recordType = ObjRecord.Type.Fun)
|
||||||
|
} ?: run {
|
||||||
|
val th = scope.thisObj
|
||||||
|
if (!spec.isStatic && th is ObjClass) {
|
||||||
|
val cls: ObjClass = th
|
||||||
|
cls.addFn(
|
||||||
|
spec.name,
|
||||||
|
isMutable = true,
|
||||||
|
visibility = spec.visibility,
|
||||||
|
isAbstract = spec.isAbstract,
|
||||||
|
isClosed = spec.isClosed,
|
||||||
|
isOverride = spec.isOverride,
|
||||||
|
pos = spec.startPos,
|
||||||
|
methodId = spec.memberMethodId
|
||||||
|
) {
|
||||||
|
val savedCtx = this.currentClassCtx
|
||||||
|
this.currentClassCtx = cls
|
||||||
|
try {
|
||||||
|
(thisObj as? ObjInstance)?.let { i ->
|
||||||
|
val execScope = i.instanceScope.createChildScope(
|
||||||
|
pos = this.pos,
|
||||||
|
args = this.args,
|
||||||
|
newThisObj = i
|
||||||
|
)
|
||||||
|
execScope.currentClassCtx = cls
|
||||||
|
compiledFnBody.execute(execScope)
|
||||||
|
} ?: compiledFnBody.execute(thisObj.autoInstanceScope(this))
|
||||||
|
} finally {
|
||||||
|
this.currentClassCtx = savedCtx
|
||||||
|
}
|
||||||
|
}
|
||||||
|
scope.addItem(spec.name, false, compiledFnBody, spec.visibility, callSignature = spec.externCallSignature)
|
||||||
|
compiledFnBody
|
||||||
|
} else {
|
||||||
|
scope.addItem(spec.name, false, compiledFnBody, spec.visibility, callSignature = spec.externCallSignature)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return annotatedFnBody
|
||||||
|
}
|
||||||
|
|
||||||
class FunctionDeclStatement(
|
class FunctionDeclStatement(
|
||||||
private val delegate: Statement,
|
val spec: FunctionDeclSpec,
|
||||||
private val startPos: Pos,
|
|
||||||
) : Statement() {
|
) : Statement() {
|
||||||
override val pos: Pos = startPos
|
override val pos: Pos = spec.startPos
|
||||||
|
|
||||||
override suspend fun execute(scope: Scope): Obj {
|
override suspend fun execute(scope: Scope): Obj {
|
||||||
return delegate.execute(scope)
|
return executeFunctionDecl(scope, spec)
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun callOn(scope: Scope): Obj {
|
override suspend fun callOn(scope: Scope): Obj {
|
||||||
val target = scope.parent ?: scope
|
val target = scope.parent ?: scope
|
||||||
return delegate.execute(target)
|
return executeFunctionDecl(target, spec)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -59,6 +59,8 @@ open class Scope(
|
|||||||
// Enabled by default for child scopes; module/class scopes can ignore it.
|
// Enabled by default for child scopes; module/class scopes can ignore it.
|
||||||
private val slots: MutableList<ObjRecord> = mutableListOf()
|
private val slots: MutableList<ObjRecord> = mutableListOf()
|
||||||
private val nameToSlot: MutableMap<String, Int> = mutableMapOf()
|
private val nameToSlot: MutableMap<String, Int> = mutableMapOf()
|
||||||
|
internal var captureRecords: List<ObjRecord>? = null
|
||||||
|
internal var captureNames: List<String>? = null
|
||||||
/**
|
/**
|
||||||
* Auxiliary per-frame map of local bindings (locals declared in this frame).
|
* Auxiliary per-frame map of local bindings (locals declared in this frame).
|
||||||
* This helps resolving locals across suspension when slot ownership isn't
|
* This helps resolving locals across suspension when slot ownership isn't
|
||||||
@ -169,6 +171,9 @@ open class Scope(
|
|||||||
}
|
}
|
||||||
|
|
||||||
internal fun resolveCaptureRecord(name: String): ObjRecord? {
|
internal fun resolveCaptureRecord(name: String): ObjRecord? {
|
||||||
|
if (captureRecords != null) {
|
||||||
|
raiseIllegalState("resolveCaptureRecord is interpreter-only; bytecode captures use captureRecords")
|
||||||
|
}
|
||||||
return chainLookupIgnoreClosure(name, followClosure = true, caller = currentClassCtx)
|
return chainLookupIgnoreClosure(name, followClosure = true, caller = currentClassCtx)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -831,6 +836,10 @@ open class Scope(
|
|||||||
open fun applyClosure(closure: Scope, preferredThisType: String? = null): Scope =
|
open fun applyClosure(closure: Scope, preferredThisType: String? = null): Scope =
|
||||||
ClosureScope(this, closure, preferredThisType)
|
ClosureScope(this, closure, preferredThisType)
|
||||||
|
|
||||||
|
internal fun applyClosureForBytecode(closure: Scope, preferredThisType: String? = null): Scope {
|
||||||
|
return BytecodeClosureScope(this, closure, preferredThisType)
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Resolve and evaluate a qualified identifier exactly as compiled code would.
|
* Resolve and evaluate a qualified identifier exactly as compiled code would.
|
||||||
* For input like `A.B.C`, it builds the same ObjRef chain the compiler emits:
|
* For input like `A.B.C`, it builds the same ObjRef chain the compiler emits:
|
||||||
|
|||||||
@ -84,7 +84,7 @@ class Script(
|
|||||||
seedModuleSlots(moduleTarget)
|
seedModuleSlots(moduleTarget)
|
||||||
}
|
}
|
||||||
moduleBytecode?.let { fn ->
|
moduleBytecode?.let { fn ->
|
||||||
return CmdVm().execute(fn, scope, scope.args.list)
|
return CmdVm().execute(fn, scope, scope.args)
|
||||||
}
|
}
|
||||||
var lastResult: Obj = ObjVoid
|
var lastResult: Obj = ObjVoid
|
||||||
for (s in statements) {
|
for (s in statements) {
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@ -16,6 +16,7 @@
|
|||||||
|
|
||||||
package net.sergeych.lyng.bytecode
|
package net.sergeych.lyng.bytecode
|
||||||
|
|
||||||
|
import net.sergeych.lyng.ArgsDeclaration
|
||||||
import net.sergeych.lyng.Pos
|
import net.sergeych.lyng.Pos
|
||||||
import net.sergeych.lyng.Visibility
|
import net.sergeych.lyng.Visibility
|
||||||
import net.sergeych.lyng.obj.ListLiteralRef
|
import net.sergeych.lyng.obj.ListLiteralRef
|
||||||
@ -30,11 +31,32 @@ sealed class BytecodeConst {
|
|||||||
data class StringVal(val value: String) : BytecodeConst()
|
data class StringVal(val value: String) : BytecodeConst()
|
||||||
data class PosVal(val pos: Pos) : BytecodeConst()
|
data class PosVal(val pos: Pos) : BytecodeConst()
|
||||||
data class ObjRef(val value: Obj) : BytecodeConst()
|
data class ObjRef(val value: Obj) : BytecodeConst()
|
||||||
data class Ref(val value: net.sergeych.lyng.obj.ObjRef) : BytecodeConst()
|
|
||||||
data class StatementVal(val statement: net.sergeych.lyng.Statement) : BytecodeConst()
|
|
||||||
data class ListLiteralPlan(val spreads: List<Boolean>) : BytecodeConst()
|
data class ListLiteralPlan(val spreads: List<Boolean>) : BytecodeConst()
|
||||||
data class ValueFn(val fn: suspend (net.sergeych.lyng.Scope) -> net.sergeych.lyng.obj.ObjRecord) : BytecodeConst()
|
data class LambdaFn(
|
||||||
|
val fn: CmdFunction,
|
||||||
|
val captureTableId: Int?,
|
||||||
|
val captureNames: List<String>,
|
||||||
|
val paramSlotPlan: Map<String, Int>,
|
||||||
|
val argsDeclaration: ArgsDeclaration?,
|
||||||
|
val preferredThisType: String?,
|
||||||
|
val wrapAsExtensionCallable: Boolean,
|
||||||
|
val returnLabels: Set<String>,
|
||||||
|
val pos: Pos,
|
||||||
|
) : BytecodeConst()
|
||||||
|
data class EnumDecl(
|
||||||
|
val declaredName: String,
|
||||||
|
val qualifiedName: String,
|
||||||
|
val entries: List<String>,
|
||||||
|
val lifted: Boolean,
|
||||||
|
) : BytecodeConst()
|
||||||
|
data class FunctionDecl(
|
||||||
|
val spec: net.sergeych.lyng.FunctionDeclSpec,
|
||||||
|
) : BytecodeConst()
|
||||||
|
data class ClassDecl(
|
||||||
|
val spec: net.sergeych.lyng.ClassDeclSpec,
|
||||||
|
) : BytecodeConst()
|
||||||
data class SlotPlan(val plan: Map<String, Int>, val captures: List<String> = emptyList()) : BytecodeConst()
|
data class SlotPlan(val plan: Map<String, Int>, val captures: List<String> = emptyList()) : BytecodeConst()
|
||||||
|
data class CaptureTable(val entries: List<BytecodeCaptureEntry>) : BytecodeConst()
|
||||||
data class ExtensionPropertyDecl(
|
data class ExtensionPropertyDecl(
|
||||||
val extTypeName: String,
|
val extTypeName: String,
|
||||||
val property: ObjProperty,
|
val property: ObjProperty,
|
||||||
|
|||||||
@ -40,9 +40,23 @@ class BytecodeFrame(
|
|||||||
slotTypes[slot] = type.code
|
slotTypes[slot] = type.code
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getObj(slot: Int): Obj = objSlots[slot] ?: ObjNull
|
override fun getObj(slot: Int): Obj {
|
||||||
|
val value = objSlots[slot] ?: return ObjNull
|
||||||
|
return when (value) {
|
||||||
|
is net.sergeych.lyng.FrameSlotRef -> value.read()
|
||||||
|
is net.sergeych.lyng.RecordSlotRef -> value.read()
|
||||||
|
else -> value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun getRawObj(slot: Int): Obj? = objSlots[slot]
|
||||||
|
|
||||||
override fun setObj(slot: Int, value: Obj) {
|
override fun setObj(slot: Int, value: Obj) {
|
||||||
objSlots[slot] = value
|
when (val current = objSlots[slot]) {
|
||||||
|
is net.sergeych.lyng.FrameSlotRef -> current.write(value)
|
||||||
|
is net.sergeych.lyng.RecordSlotRef -> current.write(value)
|
||||||
|
else -> objSlots[slot] = value
|
||||||
|
}
|
||||||
slotTypes[slot] = SlotType.OBJ.code
|
slotTypes[slot] = SlotType.OBJ.code
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -20,6 +20,7 @@ package net.sergeych.lyng.bytecode
|
|||||||
import net.sergeych.lyng.*
|
import 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.ValueFnRef
|
||||||
|
|
||||||
class BytecodeStatement private constructor(
|
class BytecodeStatement private constructor(
|
||||||
val original: Statement,
|
val original: Statement,
|
||||||
@ -29,7 +30,7 @@ class BytecodeStatement private constructor(
|
|||||||
|
|
||||||
override suspend fun execute(scope: Scope): Obj {
|
override suspend fun execute(scope: Scope): Obj {
|
||||||
scope.pos = pos
|
scope.pos = pos
|
||||||
return CmdVm().execute(function, scope, scope.args.list)
|
return CmdVm().execute(function, scope, scope.args)
|
||||||
}
|
}
|
||||||
|
|
||||||
internal fun bytecodeFunction(): CmdFunction = function
|
internal fun bytecodeFunction(): CmdFunction = function
|
||||||
@ -43,6 +44,8 @@ class BytecodeStatement private constructor(
|
|||||||
rangeLocalNames: Set<String> = emptySet(),
|
rangeLocalNames: Set<String> = emptySet(),
|
||||||
allowedScopeNames: Set<String>? = null,
|
allowedScopeNames: Set<String>? = null,
|
||||||
moduleScopeId: Int? = null,
|
moduleScopeId: Int? = null,
|
||||||
|
forcedLocalSlots: Map<String, Int> = emptyMap(),
|
||||||
|
forcedLocalScopeId: Int? = null,
|
||||||
slotTypeByScopeId: Map<Int, Map<Int, ObjClass>> = emptyMap(),
|
slotTypeByScopeId: Map<Int, Map<Int, ObjClass>> = emptyMap(),
|
||||||
knownNameObjClass: Map<String, ObjClass> = emptyMap(),
|
knownNameObjClass: Map<String, ObjClass> = emptyMap(),
|
||||||
knownObjectNames: Set<String> = emptySet(),
|
knownObjectNames: Set<String> = emptySet(),
|
||||||
@ -50,6 +53,7 @@ class BytecodeStatement private constructor(
|
|||||||
enumEntriesByName: Map<String, List<String>> = emptyMap(),
|
enumEntriesByName: Map<String, List<String>> = emptyMap(),
|
||||||
callableReturnTypeByScopeId: Map<Int, Map<Int, ObjClass>> = emptyMap(),
|
callableReturnTypeByScopeId: Map<Int, Map<Int, ObjClass>> = emptyMap(),
|
||||||
callableReturnTypeByName: Map<String, ObjClass> = emptyMap(),
|
callableReturnTypeByName: Map<String, ObjClass> = emptyMap(),
|
||||||
|
lambdaCaptureEntriesByRef: Map<ValueFnRef, List<LambdaCaptureEntry>> = emptyMap(),
|
||||||
): Statement {
|
): Statement {
|
||||||
if (statement is BytecodeStatement) return statement
|
if (statement is BytecodeStatement) return statement
|
||||||
val hasUnsupported = containsUnsupportedStatement(statement)
|
val hasUnsupported = containsUnsupportedStatement(statement)
|
||||||
@ -67,13 +71,16 @@ class BytecodeStatement private constructor(
|
|||||||
rangeLocalNames = rangeLocalNames,
|
rangeLocalNames = rangeLocalNames,
|
||||||
allowedScopeNames = allowedScopeNames,
|
allowedScopeNames = allowedScopeNames,
|
||||||
moduleScopeId = moduleScopeId,
|
moduleScopeId = moduleScopeId,
|
||||||
|
forcedLocalSlots = forcedLocalSlots,
|
||||||
|
forcedLocalScopeId = forcedLocalScopeId,
|
||||||
slotTypeByScopeId = slotTypeByScopeId,
|
slotTypeByScopeId = slotTypeByScopeId,
|
||||||
knownNameObjClass = knownNameObjClass,
|
knownNameObjClass = knownNameObjClass,
|
||||||
knownObjectNames = knownObjectNames,
|
knownObjectNames = knownObjectNames,
|
||||||
classFieldTypesByName = classFieldTypesByName,
|
classFieldTypesByName = classFieldTypesByName,
|
||||||
enumEntriesByName = enumEntriesByName,
|
enumEntriesByName = enumEntriesByName,
|
||||||
callableReturnTypeByScopeId = callableReturnTypeByScopeId,
|
callableReturnTypeByScopeId = callableReturnTypeByScopeId,
|
||||||
callableReturnTypeByName = callableReturnTypeByName
|
callableReturnTypeByName = callableReturnTypeByName,
|
||||||
|
lambdaCaptureEntriesByRef = lambdaCaptureEntriesByRef
|
||||||
)
|
)
|
||||||
val compiled = compiler.compileStatement(nameHint, statement)
|
val compiled = compiler.compileStatement(nameHint, statement)
|
||||||
val fn = compiled ?: throw BytecodeCompileException(
|
val fn = compiled ?: throw BytecodeCompileException(
|
||||||
@ -132,6 +139,7 @@ class BytecodeStatement private constructor(
|
|||||||
target.resultExpr?.let { containsUnsupportedStatement(it) } ?: false
|
target.resultExpr?.let { containsUnsupportedStatement(it) } ?: false
|
||||||
is net.sergeych.lyng.ThrowStatement ->
|
is net.sergeych.lyng.ThrowStatement ->
|
||||||
containsUnsupportedStatement(target.throwExpr)
|
containsUnsupportedStatement(target.throwExpr)
|
||||||
|
is net.sergeych.lyng.NopStatement -> false
|
||||||
is net.sergeych.lyng.ExtensionPropertyDeclStatement -> false
|
is net.sergeych.lyng.ExtensionPropertyDeclStatement -> false
|
||||||
is net.sergeych.lyng.ClassDeclStatement -> false
|
is net.sergeych.lyng.ClassDeclStatement -> false
|
||||||
is net.sergeych.lyng.FunctionDeclStatement -> false
|
is net.sergeych.lyng.FunctionDeclStatement -> false
|
||||||
@ -208,6 +216,7 @@ class BytecodeStatement private constructor(
|
|||||||
stmt.label,
|
stmt.label,
|
||||||
stmt.canBreak,
|
stmt.canBreak,
|
||||||
stmt.loopSlotPlan,
|
stmt.loopSlotPlan,
|
||||||
|
stmt.loopScopeId,
|
||||||
stmt.pos
|
stmt.pos
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -68,7 +68,9 @@ class CmdBuilder {
|
|||||||
scopeSlotNames: Array<String?> = emptyArray(),
|
scopeSlotNames: Array<String?> = emptyArray(),
|
||||||
scopeSlotIsModule: BooleanArray = BooleanArray(0),
|
scopeSlotIsModule: BooleanArray = BooleanArray(0),
|
||||||
localSlotNames: Array<String?> = emptyArray(),
|
localSlotNames: Array<String?> = emptyArray(),
|
||||||
localSlotMutables: BooleanArray = BooleanArray(0)
|
localSlotMutables: BooleanArray = BooleanArray(0),
|
||||||
|
localSlotDelegated: BooleanArray = BooleanArray(0),
|
||||||
|
localSlotCaptures: BooleanArray = BooleanArray(0)
|
||||||
): CmdFunction {
|
): CmdFunction {
|
||||||
val scopeSlotCount = scopeSlotIndices.size
|
val scopeSlotCount = scopeSlotIndices.size
|
||||||
require(scopeSlotNames.isEmpty() || scopeSlotNames.size == scopeSlotCount) {
|
require(scopeSlotNames.isEmpty() || scopeSlotNames.size == scopeSlotCount) {
|
||||||
@ -78,6 +80,8 @@ class CmdBuilder {
|
|||||||
"scope slot module mapping size mismatch"
|
"scope slot module mapping size mismatch"
|
||||||
}
|
}
|
||||||
require(localSlotNames.size == localSlotMutables.size) { "local slot metadata size mismatch" }
|
require(localSlotNames.size == localSlotMutables.size) { "local slot metadata size mismatch" }
|
||||||
|
require(localSlotNames.size == localSlotDelegated.size) { "local slot delegation size mismatch" }
|
||||||
|
require(localSlotNames.size == localSlotCaptures.size) { "local slot capture size mismatch" }
|
||||||
val labelIps = mutableMapOf<Label, Int>()
|
val labelIps = mutableMapOf<Label, Int>()
|
||||||
for ((label, idx) in labelPositions) {
|
for ((label, idx) in labelPositions) {
|
||||||
labelIps[label] = idx
|
labelIps[label] = idx
|
||||||
@ -111,6 +115,8 @@ class CmdBuilder {
|
|||||||
scopeSlotIsModule = if (scopeSlotIsModule.isEmpty()) BooleanArray(scopeSlotCount) else scopeSlotIsModule,
|
scopeSlotIsModule = if (scopeSlotIsModule.isEmpty()) BooleanArray(scopeSlotCount) else scopeSlotIsModule,
|
||||||
localSlotNames = localSlotNames,
|
localSlotNames = localSlotNames,
|
||||||
localSlotMutables = localSlotMutables,
|
localSlotMutables = localSlotMutables,
|
||||||
|
localSlotDelegated = localSlotDelegated,
|
||||||
|
localSlotCaptures = localSlotCaptures,
|
||||||
constants = constPool.toList(),
|
constants = constPool.toList(),
|
||||||
cmds = cmds.toTypedArray(),
|
cmds = cmds.toTypedArray(),
|
||||||
posByIp = posByInstr.toTypedArray()
|
posByIp = posByInstr.toTypedArray()
|
||||||
@ -123,7 +129,7 @@ class CmdBuilder {
|
|||||||
Opcode.CLEAR_PENDING_THROWABLE, Opcode.RETHROW_PENDING -> emptyList()
|
Opcode.CLEAR_PENDING_THROWABLE, Opcode.RETHROW_PENDING -> 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.OBJ_TO_BOOL,
|
Opcode.OBJ_TO_BOOL, Opcode.GET_OBJ_CLASS,
|
||||||
Opcode.NEG_INT, Opcode.NEG_REAL, Opcode.NOT_BOOL, Opcode.INV_INT,
|
Opcode.NEG_INT, Opcode.NEG_REAL, Opcode.NOT_BOOL, Opcode.INV_INT,
|
||||||
Opcode.ASSERT_IS ->
|
Opcode.ASSERT_IS ->
|
||||||
listOf(OperandKind.SLOT, OperandKind.SLOT)
|
listOf(OperandKind.SLOT, OperandKind.SLOT)
|
||||||
@ -135,19 +141,27 @@ class CmdBuilder {
|
|||||||
listOf(OperandKind.CONST, OperandKind.SLOT)
|
listOf(OperandKind.CONST, OperandKind.SLOT)
|
||||||
Opcode.RESOLVE_SCOPE_SLOT ->
|
Opcode.RESOLVE_SCOPE_SLOT ->
|
||||||
listOf(OperandKind.SLOT, OperandKind.ADDR)
|
listOf(OperandKind.SLOT, OperandKind.ADDR)
|
||||||
|
Opcode.DELEGATED_GET_LOCAL ->
|
||||||
|
listOf(OperandKind.SLOT, OperandKind.CONST, OperandKind.SLOT)
|
||||||
|
Opcode.DELEGATED_SET_LOCAL ->
|
||||||
|
listOf(OperandKind.SLOT, OperandKind.CONST, OperandKind.SLOT)
|
||||||
|
Opcode.BIND_DELEGATE_LOCAL ->
|
||||||
|
listOf(OperandKind.SLOT, OperandKind.CONST, OperandKind.CONST, OperandKind.SLOT)
|
||||||
Opcode.LOAD_OBJ_ADDR, Opcode.LOAD_INT_ADDR, Opcode.LOAD_REAL_ADDR, Opcode.LOAD_BOOL_ADDR ->
|
Opcode.LOAD_OBJ_ADDR, Opcode.LOAD_INT_ADDR, Opcode.LOAD_REAL_ADDR, Opcode.LOAD_BOOL_ADDR ->
|
||||||
listOf(OperandKind.ADDR, OperandKind.SLOT)
|
listOf(OperandKind.ADDR, OperandKind.SLOT)
|
||||||
Opcode.STORE_OBJ_ADDR, Opcode.STORE_INT_ADDR, Opcode.STORE_REAL_ADDR, Opcode.STORE_BOOL_ADDR ->
|
Opcode.STORE_OBJ_ADDR, Opcode.STORE_INT_ADDR, Opcode.STORE_REAL_ADDR, Opcode.STORE_BOOL_ADDR ->
|
||||||
listOf(OperandKind.SLOT, OperandKind.ADDR)
|
listOf(OperandKind.SLOT, OperandKind.ADDR)
|
||||||
Opcode.CONST_NULL ->
|
Opcode.CONST_NULL ->
|
||||||
listOf(OperandKind.SLOT)
|
listOf(OperandKind.SLOT)
|
||||||
Opcode.CONST_OBJ, Opcode.CONST_INT, Opcode.CONST_REAL, Opcode.CONST_BOOL, Opcode.MAKE_VALUE_FN ->
|
Opcode.CONST_OBJ, Opcode.CONST_INT, Opcode.CONST_REAL, Opcode.CONST_BOOL,
|
||||||
|
Opcode.MAKE_LAMBDA_FN ->
|
||||||
listOf(OperandKind.CONST, OperandKind.SLOT)
|
listOf(OperandKind.CONST, OperandKind.SLOT)
|
||||||
Opcode.PUSH_SCOPE, Opcode.PUSH_SLOT_PLAN ->
|
Opcode.PUSH_SCOPE, Opcode.PUSH_SLOT_PLAN ->
|
||||||
listOf(OperandKind.CONST)
|
listOf(OperandKind.CONST)
|
||||||
Opcode.PUSH_TRY ->
|
Opcode.PUSH_TRY ->
|
||||||
listOf(OperandKind.SLOT, OperandKind.IP, OperandKind.IP)
|
listOf(OperandKind.SLOT, OperandKind.IP, OperandKind.IP)
|
||||||
Opcode.DECL_LOCAL, Opcode.DECL_EXT_PROPERTY, Opcode.DECL_DELEGATED, Opcode.DECL_DESTRUCTURE,
|
Opcode.DECL_LOCAL, Opcode.DECL_EXT_PROPERTY, Opcode.DECL_DELEGATED, Opcode.DECL_DESTRUCTURE,
|
||||||
|
Opcode.DECL_ENUM, Opcode.DECL_FUNCTION, Opcode.DECL_CLASS,
|
||||||
Opcode.ASSIGN_DESTRUCTURE ->
|
Opcode.ASSIGN_DESTRUCTURE ->
|
||||||
listOf(OperandKind.CONST, OperandKind.SLOT)
|
listOf(OperandKind.CONST, OperandKind.SLOT)
|
||||||
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,
|
||||||
@ -240,9 +254,10 @@ class CmdBuilder {
|
|||||||
Opcode.CONST_REAL -> CmdConstReal(operands[0], operands[1])
|
Opcode.CONST_REAL -> CmdConstReal(operands[0], operands[1])
|
||||||
Opcode.CONST_BOOL -> CmdConstBool(operands[0], operands[1])
|
Opcode.CONST_BOOL -> CmdConstBool(operands[0], operands[1])
|
||||||
Opcode.CONST_NULL -> CmdConstNull(operands[0])
|
Opcode.CONST_NULL -> CmdConstNull(operands[0])
|
||||||
Opcode.MAKE_VALUE_FN -> CmdMakeValueFn(operands[0], operands[1])
|
Opcode.MAKE_LAMBDA_FN -> CmdMakeLambda(operands[0], operands[1])
|
||||||
Opcode.BOX_OBJ -> CmdBoxObj(operands[0], operands[1])
|
Opcode.BOX_OBJ -> CmdBoxObj(operands[0], operands[1])
|
||||||
Opcode.OBJ_TO_BOOL -> CmdObjToBool(operands[0], operands[1])
|
Opcode.OBJ_TO_BOOL -> CmdObjToBool(operands[0], operands[1])
|
||||||
|
Opcode.GET_OBJ_CLASS -> CmdGetObjClass(operands[0], operands[1])
|
||||||
Opcode.RANGE_INT_BOUNDS -> CmdRangeIntBounds(operands[0], operands[1], operands[2], operands[3])
|
Opcode.RANGE_INT_BOUNDS -> CmdRangeIntBounds(operands[0], operands[1], operands[2], operands[3])
|
||||||
Opcode.LOAD_THIS -> CmdLoadThis(operands[0])
|
Opcode.LOAD_THIS -> CmdLoadThis(operands[0])
|
||||||
Opcode.LOAD_THIS_VARIANT -> CmdLoadThisVariant(operands[0], operands[1])
|
Opcode.LOAD_THIS_VARIANT -> CmdLoadThisVariant(operands[0], operands[1])
|
||||||
@ -254,6 +269,9 @@ class CmdBuilder {
|
|||||||
Opcode.THROW -> CmdThrow(operands[0], operands[1])
|
Opcode.THROW -> CmdThrow(operands[0], operands[1])
|
||||||
Opcode.RETHROW_PENDING -> CmdRethrowPending()
|
Opcode.RETHROW_PENDING -> CmdRethrowPending()
|
||||||
Opcode.RESOLVE_SCOPE_SLOT -> CmdResolveScopeSlot(operands[0], operands[1])
|
Opcode.RESOLVE_SCOPE_SLOT -> CmdResolveScopeSlot(operands[0], operands[1])
|
||||||
|
Opcode.DELEGATED_GET_LOCAL -> CmdDelegatedGetLocal(operands[0], operands[1], operands[2])
|
||||||
|
Opcode.DELEGATED_SET_LOCAL -> CmdDelegatedSetLocal(operands[0], operands[1], operands[2])
|
||||||
|
Opcode.BIND_DELEGATE_LOCAL -> CmdBindDelegateLocal(operands[0], operands[1], operands[2], operands[3])
|
||||||
Opcode.LOAD_OBJ_ADDR -> CmdLoadObjAddr(operands[0], operands[1])
|
Opcode.LOAD_OBJ_ADDR -> CmdLoadObjAddr(operands[0], operands[1])
|
||||||
Opcode.STORE_OBJ_ADDR -> CmdStoreObjAddr(operands[0], operands[1])
|
Opcode.STORE_OBJ_ADDR -> CmdStoreObjAddr(operands[0], operands[1])
|
||||||
Opcode.LOAD_INT_ADDR -> CmdLoadIntAddr(operands[0], operands[1])
|
Opcode.LOAD_INT_ADDR -> CmdLoadIntAddr(operands[0], operands[1])
|
||||||
@ -397,6 +415,9 @@ class CmdBuilder {
|
|||||||
Opcode.DECL_LOCAL -> CmdDeclLocal(operands[0], operands[1])
|
Opcode.DECL_LOCAL -> CmdDeclLocal(operands[0], operands[1])
|
||||||
Opcode.DECL_DELEGATED -> CmdDeclDelegated(operands[0], operands[1])
|
Opcode.DECL_DELEGATED -> CmdDeclDelegated(operands[0], operands[1])
|
||||||
Opcode.DECL_DESTRUCTURE -> CmdDeclDestructure(operands[0], operands[1])
|
Opcode.DECL_DESTRUCTURE -> CmdDeclDestructure(operands[0], operands[1])
|
||||||
|
Opcode.DECL_ENUM -> CmdDeclEnum(operands[0], operands[1])
|
||||||
|
Opcode.DECL_FUNCTION -> CmdDeclFunction(operands[0], operands[1])
|
||||||
|
Opcode.DECL_CLASS -> CmdDeclClass(operands[0], operands[1])
|
||||||
Opcode.DECL_EXT_PROPERTY -> CmdDeclExtProperty(operands[0], operands[1])
|
Opcode.DECL_EXT_PROPERTY -> CmdDeclExtProperty(operands[0], operands[1])
|
||||||
Opcode.CALL_DIRECT -> CmdCallDirect(operands[0], operands[1], operands[2], operands[3])
|
Opcode.CALL_DIRECT -> CmdCallDirect(operands[0], operands[1], operands[2], operands[3])
|
||||||
Opcode.ASSIGN_DESTRUCTURE -> CmdAssignDestructure(operands[0], operands[1])
|
Opcode.ASSIGN_DESTRUCTURE -> CmdAssignDestructure(operands[0], operands[1])
|
||||||
|
|||||||
@ -50,6 +50,23 @@ object CmdDisassembler {
|
|||||||
}
|
}
|
||||||
out.append('\n')
|
out.append('\n')
|
||||||
}
|
}
|
||||||
|
val captureConsts = fn.constants.withIndex().mapNotNull { (idx, constVal) ->
|
||||||
|
val table = constVal as? BytecodeConst.CaptureTable ?: return@mapNotNull null
|
||||||
|
idx to table
|
||||||
|
}
|
||||||
|
if (captureConsts.isNotEmpty()) {
|
||||||
|
out.append("consts:\n")
|
||||||
|
for ((idx, table) in captureConsts) {
|
||||||
|
val entries = if (table.entries.isEmpty()) {
|
||||||
|
"[]"
|
||||||
|
} else {
|
||||||
|
table.entries.joinToString(prefix = "[", postfix = "]") { entry ->
|
||||||
|
"${entry.ownerKind}#${entry.ownerScopeId}:${entry.ownerSlotId}@s${entry.slotIndex}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
out.append("k").append(idx).append(" CAPTURE_TABLE ").append(entries).append('\n')
|
||||||
|
}
|
||||||
|
}
|
||||||
return out.toString()
|
return out.toString()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -69,9 +86,10 @@ object CmdDisassembler {
|
|||||||
is CmdLoadThis -> Opcode.LOAD_THIS to intArrayOf(cmd.dst)
|
is CmdLoadThis -> Opcode.LOAD_THIS to intArrayOf(cmd.dst)
|
||||||
is CmdLoadThisVariant -> Opcode.LOAD_THIS_VARIANT to intArrayOf(cmd.typeId, cmd.dst)
|
is CmdLoadThisVariant -> Opcode.LOAD_THIS_VARIANT to intArrayOf(cmd.typeId, cmd.dst)
|
||||||
is CmdConstNull -> Opcode.CONST_NULL to intArrayOf(cmd.dst)
|
is CmdConstNull -> Opcode.CONST_NULL to intArrayOf(cmd.dst)
|
||||||
is CmdMakeValueFn -> Opcode.MAKE_VALUE_FN to intArrayOf(cmd.id, cmd.dst)
|
is CmdMakeLambda -> Opcode.MAKE_LAMBDA_FN to intArrayOf(cmd.id, cmd.dst)
|
||||||
is CmdBoxObj -> Opcode.BOX_OBJ to intArrayOf(cmd.src, cmd.dst)
|
is CmdBoxObj -> Opcode.BOX_OBJ to intArrayOf(cmd.src, cmd.dst)
|
||||||
is CmdObjToBool -> Opcode.OBJ_TO_BOOL to intArrayOf(cmd.src, cmd.dst)
|
is CmdObjToBool -> Opcode.OBJ_TO_BOOL to intArrayOf(cmd.src, cmd.dst)
|
||||||
|
is CmdGetObjClass -> Opcode.GET_OBJ_CLASS to intArrayOf(cmd.src, cmd.dst)
|
||||||
is CmdCheckIs -> Opcode.CHECK_IS to intArrayOf(cmd.objSlot, cmd.typeSlot, cmd.dst)
|
is CmdCheckIs -> Opcode.CHECK_IS to intArrayOf(cmd.objSlot, cmd.typeSlot, cmd.dst)
|
||||||
is CmdAssertIs -> Opcode.ASSERT_IS to intArrayOf(cmd.objSlot, cmd.typeSlot)
|
is CmdAssertIs -> Opcode.ASSERT_IS to intArrayOf(cmd.objSlot, cmd.typeSlot)
|
||||||
is CmdMakeQualifiedView -> Opcode.MAKE_QUALIFIED_VIEW to intArrayOf(cmd.objSlot, cmd.typeSlot, cmd.dst)
|
is CmdMakeQualifiedView -> Opcode.MAKE_QUALIFIED_VIEW to intArrayOf(cmd.objSlot, cmd.typeSlot, cmd.dst)
|
||||||
@ -84,6 +102,9 @@ object CmdDisassembler {
|
|||||||
cmd.dst
|
cmd.dst
|
||||||
)
|
)
|
||||||
is CmdResolveScopeSlot -> Opcode.RESOLVE_SCOPE_SLOT to intArrayOf(cmd.scopeSlot, cmd.addrSlot)
|
is CmdResolveScopeSlot -> Opcode.RESOLVE_SCOPE_SLOT to intArrayOf(cmd.scopeSlot, cmd.addrSlot)
|
||||||
|
is CmdDelegatedGetLocal -> Opcode.DELEGATED_GET_LOCAL to intArrayOf(cmd.delegateSlot, cmd.nameId, cmd.dst)
|
||||||
|
is CmdDelegatedSetLocal -> Opcode.DELEGATED_SET_LOCAL to intArrayOf(cmd.delegateSlot, cmd.nameId, cmd.valueSlot)
|
||||||
|
is CmdBindDelegateLocal -> Opcode.BIND_DELEGATE_LOCAL to intArrayOf(cmd.delegateSlot, cmd.nameId, cmd.accessId, cmd.dst)
|
||||||
is CmdLoadObjAddr -> Opcode.LOAD_OBJ_ADDR to intArrayOf(cmd.addrSlot, cmd.dst)
|
is CmdLoadObjAddr -> Opcode.LOAD_OBJ_ADDR to intArrayOf(cmd.addrSlot, cmd.dst)
|
||||||
is CmdStoreObjAddr -> Opcode.STORE_OBJ_ADDR to intArrayOf(cmd.src, cmd.addrSlot)
|
is CmdStoreObjAddr -> Opcode.STORE_OBJ_ADDR to intArrayOf(cmd.src, cmd.addrSlot)
|
||||||
is CmdLoadIntAddr -> Opcode.LOAD_INT_ADDR to intArrayOf(cmd.addrSlot, cmd.dst)
|
is CmdLoadIntAddr -> Opcode.LOAD_INT_ADDR to intArrayOf(cmd.addrSlot, cmd.dst)
|
||||||
@ -191,6 +212,9 @@ object CmdDisassembler {
|
|||||||
is CmdDeclLocal -> Opcode.DECL_LOCAL to intArrayOf(cmd.constId, cmd.slot)
|
is CmdDeclLocal -> Opcode.DECL_LOCAL to intArrayOf(cmd.constId, cmd.slot)
|
||||||
is CmdDeclDelegated -> Opcode.DECL_DELEGATED to intArrayOf(cmd.constId, cmd.slot)
|
is CmdDeclDelegated -> Opcode.DECL_DELEGATED to intArrayOf(cmd.constId, cmd.slot)
|
||||||
is CmdDeclDestructure -> Opcode.DECL_DESTRUCTURE to intArrayOf(cmd.constId, cmd.slot)
|
is CmdDeclDestructure -> Opcode.DECL_DESTRUCTURE to intArrayOf(cmd.constId, cmd.slot)
|
||||||
|
is CmdDeclEnum -> Opcode.DECL_ENUM to intArrayOf(cmd.constId, cmd.slot)
|
||||||
|
is CmdDeclFunction -> Opcode.DECL_FUNCTION to intArrayOf(cmd.constId, cmd.slot)
|
||||||
|
is CmdDeclClass -> Opcode.DECL_CLASS to intArrayOf(cmd.constId, cmd.slot)
|
||||||
is CmdDeclExtProperty -> Opcode.DECL_EXT_PROPERTY to intArrayOf(cmd.constId, cmd.slot)
|
is CmdDeclExtProperty -> Opcode.DECL_EXT_PROPERTY to intArrayOf(cmd.constId, cmd.slot)
|
||||||
is CmdCallDirect -> Opcode.CALL_DIRECT to intArrayOf(cmd.id, cmd.argBase, cmd.argCount, cmd.dst)
|
is CmdCallDirect -> Opcode.CALL_DIRECT to intArrayOf(cmd.id, cmd.argBase, cmd.argCount, cmd.dst)
|
||||||
is CmdAssignDestructure -> Opcode.ASSIGN_DESTRUCTURE to intArrayOf(cmd.constId, cmd.slot)
|
is CmdAssignDestructure -> Opcode.ASSIGN_DESTRUCTURE to intArrayOf(cmd.constId, cmd.slot)
|
||||||
@ -231,7 +255,7 @@ object CmdDisassembler {
|
|||||||
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 ->
|
||||||
listOf(OperandKind.SLOT, OperandKind.SLOT)
|
listOf(OperandKind.SLOT, OperandKind.SLOT)
|
||||||
Opcode.OBJ_TO_BOOL, Opcode.ASSERT_IS ->
|
Opcode.OBJ_TO_BOOL, Opcode.GET_OBJ_CLASS, Opcode.ASSERT_IS ->
|
||||||
listOf(OperandKind.SLOT, OperandKind.SLOT)
|
listOf(OperandKind.SLOT, OperandKind.SLOT)
|
||||||
Opcode.CHECK_IS, Opcode.MAKE_QUALIFIED_VIEW ->
|
Opcode.CHECK_IS, Opcode.MAKE_QUALIFIED_VIEW ->
|
||||||
listOf(OperandKind.SLOT, OperandKind.SLOT, OperandKind.SLOT)
|
listOf(OperandKind.SLOT, OperandKind.SLOT, OperandKind.SLOT)
|
||||||
@ -243,19 +267,27 @@ object CmdDisassembler {
|
|||||||
listOf(OperandKind.CONST, OperandKind.SLOT)
|
listOf(OperandKind.CONST, OperandKind.SLOT)
|
||||||
Opcode.RESOLVE_SCOPE_SLOT ->
|
Opcode.RESOLVE_SCOPE_SLOT ->
|
||||||
listOf(OperandKind.SLOT, OperandKind.ADDR)
|
listOf(OperandKind.SLOT, OperandKind.ADDR)
|
||||||
|
Opcode.DELEGATED_GET_LOCAL ->
|
||||||
|
listOf(OperandKind.SLOT, OperandKind.CONST, OperandKind.SLOT)
|
||||||
|
Opcode.DELEGATED_SET_LOCAL ->
|
||||||
|
listOf(OperandKind.SLOT, OperandKind.CONST, OperandKind.SLOT)
|
||||||
|
Opcode.BIND_DELEGATE_LOCAL ->
|
||||||
|
listOf(OperandKind.SLOT, OperandKind.CONST, OperandKind.CONST, OperandKind.SLOT)
|
||||||
Opcode.LOAD_OBJ_ADDR, Opcode.LOAD_INT_ADDR, Opcode.LOAD_REAL_ADDR, Opcode.LOAD_BOOL_ADDR ->
|
Opcode.LOAD_OBJ_ADDR, Opcode.LOAD_INT_ADDR, Opcode.LOAD_REAL_ADDR, Opcode.LOAD_BOOL_ADDR ->
|
||||||
listOf(OperandKind.ADDR, OperandKind.SLOT)
|
listOf(OperandKind.ADDR, OperandKind.SLOT)
|
||||||
Opcode.STORE_OBJ_ADDR, Opcode.STORE_INT_ADDR, Opcode.STORE_REAL_ADDR, Opcode.STORE_BOOL_ADDR ->
|
Opcode.STORE_OBJ_ADDR, Opcode.STORE_INT_ADDR, Opcode.STORE_REAL_ADDR, Opcode.STORE_BOOL_ADDR ->
|
||||||
listOf(OperandKind.SLOT, OperandKind.ADDR)
|
listOf(OperandKind.SLOT, OperandKind.ADDR)
|
||||||
Opcode.CONST_NULL ->
|
Opcode.CONST_NULL ->
|
||||||
listOf(OperandKind.SLOT)
|
listOf(OperandKind.SLOT)
|
||||||
Opcode.CONST_OBJ, Opcode.CONST_INT, Opcode.CONST_REAL, Opcode.CONST_BOOL, Opcode.MAKE_VALUE_FN ->
|
Opcode.CONST_OBJ, Opcode.CONST_INT, Opcode.CONST_REAL, Opcode.CONST_BOOL,
|
||||||
|
Opcode.MAKE_LAMBDA_FN ->
|
||||||
listOf(OperandKind.CONST, OperandKind.SLOT)
|
listOf(OperandKind.CONST, OperandKind.SLOT)
|
||||||
Opcode.PUSH_SCOPE, Opcode.PUSH_SLOT_PLAN ->
|
Opcode.PUSH_SCOPE, Opcode.PUSH_SLOT_PLAN ->
|
||||||
listOf(OperandKind.CONST)
|
listOf(OperandKind.CONST)
|
||||||
Opcode.PUSH_TRY ->
|
Opcode.PUSH_TRY ->
|
||||||
listOf(OperandKind.SLOT, OperandKind.IP, OperandKind.IP)
|
listOf(OperandKind.SLOT, OperandKind.IP, OperandKind.IP)
|
||||||
Opcode.DECL_LOCAL, Opcode.DECL_EXT_PROPERTY, Opcode.DECL_DELEGATED, Opcode.DECL_DESTRUCTURE,
|
Opcode.DECL_LOCAL, Opcode.DECL_EXT_PROPERTY, Opcode.DECL_DELEGATED, Opcode.DECL_DESTRUCTURE,
|
||||||
|
Opcode.DECL_ENUM, Opcode.DECL_FUNCTION, Opcode.DECL_CLASS,
|
||||||
Opcode.ASSIGN_DESTRUCTURE ->
|
Opcode.ASSIGN_DESTRUCTURE ->
|
||||||
listOf(OperandKind.CONST, OperandKind.SLOT)
|
listOf(OperandKind.CONST, OperandKind.SLOT)
|
||||||
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,
|
||||||
|
|||||||
@ -28,6 +28,8 @@ data class CmdFunction(
|
|||||||
val scopeSlotIsModule: BooleanArray,
|
val scopeSlotIsModule: BooleanArray,
|
||||||
val localSlotNames: Array<String?>,
|
val localSlotNames: Array<String?>,
|
||||||
val localSlotMutables: BooleanArray,
|
val localSlotMutables: BooleanArray,
|
||||||
|
val localSlotDelegated: BooleanArray,
|
||||||
|
val localSlotCaptures: BooleanArray,
|
||||||
val constants: List<BytecodeConst>,
|
val constants: List<BytecodeConst>,
|
||||||
val cmds: Array<Cmd>,
|
val cmds: Array<Cmd>,
|
||||||
val posByIp: Array<net.sergeych.lyng.Pos?>,
|
val posByIp: Array<net.sergeych.lyng.Pos?>,
|
||||||
@ -37,10 +39,31 @@ data class CmdFunction(
|
|||||||
require(scopeSlotNames.size == scopeSlotCount) { "scopeSlotNames size mismatch" }
|
require(scopeSlotNames.size == scopeSlotCount) { "scopeSlotNames size mismatch" }
|
||||||
require(scopeSlotIsModule.size == scopeSlotCount) { "scopeSlotIsModule size mismatch" }
|
require(scopeSlotIsModule.size == scopeSlotCount) { "scopeSlotIsModule size mismatch" }
|
||||||
require(localSlotNames.size == localSlotMutables.size) { "localSlot metadata size mismatch" }
|
require(localSlotNames.size == localSlotMutables.size) { "localSlot metadata size mismatch" }
|
||||||
|
require(localSlotNames.size == localSlotDelegated.size) { "localSlot delegation size mismatch" }
|
||||||
|
require(localSlotNames.size == localSlotCaptures.size) { "localSlot capture size mismatch" }
|
||||||
require(localSlotNames.size <= localCount) { "localSlotNames exceed localCount" }
|
require(localSlotNames.size <= localCount) { "localSlotNames exceed localCount" }
|
||||||
require(addrCount >= 0) { "addrCount must be non-negative" }
|
require(addrCount >= 0) { "addrCount must be non-negative" }
|
||||||
if (posByIp.isNotEmpty()) {
|
if (posByIp.isNotEmpty()) {
|
||||||
require(posByIp.size == cmds.size) { "posByIp size mismatch" }
|
require(posByIp.size == cmds.size) { "posByIp size mismatch" }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun localSlotPlanByName(): Map<String, Int> {
|
||||||
|
val result = LinkedHashMap<String, Int>()
|
||||||
|
for (i in localSlotNames.indices) {
|
||||||
|
val name = localSlotNames[i] ?: continue
|
||||||
|
val existing = result[name]
|
||||||
|
if (existing == null) {
|
||||||
|
result[name] = i
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
val existingIsCapture = localSlotCaptures.getOrNull(existing) == true
|
||||||
|
val currentIsCapture = localSlotCaptures.getOrNull(i) == true
|
||||||
|
if (existingIsCapture && !currentIsCapture) {
|
||||||
|
result[name] = i
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,35 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2026 Sergey S. Chernov
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package net.sergeych.lyng.bytecode
|
||||||
|
|
||||||
|
enum class CaptureOwnerFrameKind { MODULE, LOCAL }
|
||||||
|
|
||||||
|
data class LambdaCaptureEntry(
|
||||||
|
val ownerKind: CaptureOwnerFrameKind,
|
||||||
|
val ownerScopeId: Int,
|
||||||
|
val ownerSlotId: Int,
|
||||||
|
val ownerName: String,
|
||||||
|
val ownerIsMutable: Boolean,
|
||||||
|
val ownerIsDelegated: Boolean,
|
||||||
|
)
|
||||||
|
|
||||||
|
data class BytecodeCaptureEntry(
|
||||||
|
val ownerKind: CaptureOwnerFrameKind,
|
||||||
|
val ownerScopeId: Int,
|
||||||
|
val ownerSlotId: Int,
|
||||||
|
val slotIndex: Int,
|
||||||
|
)
|
||||||
@ -31,7 +31,6 @@ enum class Opcode(val code: Int) {
|
|||||||
RANGE_INT_BOUNDS(0x0B),
|
RANGE_INT_BOUNDS(0x0B),
|
||||||
MAKE_RANGE(0x0C),
|
MAKE_RANGE(0x0C),
|
||||||
LOAD_THIS(0x0D),
|
LOAD_THIS(0x0D),
|
||||||
MAKE_VALUE_FN(0x0E),
|
|
||||||
LOAD_THIS_VARIANT(0x0F),
|
LOAD_THIS_VARIANT(0x0F),
|
||||||
|
|
||||||
INT_TO_REAL(0x10),
|
INT_TO_REAL(0x10),
|
||||||
@ -42,6 +41,8 @@ enum class Opcode(val code: Int) {
|
|||||||
CHECK_IS(0x15),
|
CHECK_IS(0x15),
|
||||||
ASSERT_IS(0x16),
|
ASSERT_IS(0x16),
|
||||||
MAKE_QUALIFIED_VIEW(0x17),
|
MAKE_QUALIFIED_VIEW(0x17),
|
||||||
|
MAKE_LAMBDA_FN(0x18),
|
||||||
|
GET_OBJ_CLASS(0x19),
|
||||||
|
|
||||||
ADD_INT(0x20),
|
ADD_INT(0x20),
|
||||||
SUB_INT(0x21),
|
SUB_INT(0x21),
|
||||||
@ -158,9 +159,15 @@ enum class Opcode(val code: Int) {
|
|||||||
STORE_BOOL_ADDR(0xB9),
|
STORE_BOOL_ADDR(0xB9),
|
||||||
THROW(0xBB),
|
THROW(0xBB),
|
||||||
RETHROW_PENDING(0xBC),
|
RETHROW_PENDING(0xBC),
|
||||||
|
DECL_ENUM(0xBE),
|
||||||
ITER_PUSH(0xBF),
|
ITER_PUSH(0xBF),
|
||||||
ITER_POP(0xC0),
|
ITER_POP(0xC0),
|
||||||
ITER_CANCEL(0xC1),
|
ITER_CANCEL(0xC1),
|
||||||
|
DELEGATED_GET_LOCAL(0xC2),
|
||||||
|
DELEGATED_SET_LOCAL(0xC3),
|
||||||
|
BIND_DELEGATE_LOCAL(0xC4),
|
||||||
|
DECL_FUNCTION(0xC5),
|
||||||
|
DECL_CLASS(0xC6),
|
||||||
;
|
;
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|||||||
@ -0,0 +1,33 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2026 Sergey S. Chernov real.sergeych@gmail.com
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package net.sergeych.lyng.obj
|
||||||
|
|
||||||
|
import net.sergeych.lyng.ArgsDeclaration
|
||||||
|
import net.sergeych.lyng.Pos
|
||||||
|
import net.sergeych.lyng.Scope
|
||||||
|
import net.sergeych.lyng.bytecode.CmdFunction
|
||||||
|
|
||||||
|
class LambdaFnRef(
|
||||||
|
valueFn: suspend (Scope) -> ObjRecord,
|
||||||
|
val bytecodeFn: CmdFunction?,
|
||||||
|
val paramSlotPlan: Map<String, Int>,
|
||||||
|
val argsDeclaration: ArgsDeclaration?,
|
||||||
|
val preferredThisType: String?,
|
||||||
|
val wrapAsExtensionCallable: Boolean,
|
||||||
|
val returnLabels: Set<String>,
|
||||||
|
val pos: Pos,
|
||||||
|
) : ValueFnRef(valueFn)
|
||||||
@ -18,7 +18,6 @@
|
|||||||
package net.sergeych.lyng.obj
|
package net.sergeych.lyng.obj
|
||||||
|
|
||||||
import net.sergeych.lyng.Arguments
|
import net.sergeych.lyng.Arguments
|
||||||
import net.sergeych.lyng.ClosureScope
|
|
||||||
import net.sergeych.lyng.Scope
|
import net.sergeych.lyng.Scope
|
||||||
import net.sergeych.lyng.Statement
|
import net.sergeych.lyng.Statement
|
||||||
|
|
||||||
@ -63,7 +62,7 @@ open class ObjDynamic(var readCallback: Statement? = null, var writeCallback: St
|
|||||||
* with method invocation which is implemented separately in [invokeInstanceMethod] below.
|
* with method invocation which is implemented separately in [invokeInstanceMethod] below.
|
||||||
*/
|
*/
|
||||||
override suspend fun readField(scope: Scope, name: String): ObjRecord {
|
override suspend fun readField(scope: Scope, name: String): ObjRecord {
|
||||||
val execBase = builderScope?.let { ClosureScope(scope, it) } ?: scope
|
val execBase = builderScope?.let { scope.applyClosure(it) } ?: scope
|
||||||
return readCallback?.execute(execBase.createChildScope(Arguments(ObjString(name))))?.let {
|
return readCallback?.execute(execBase.createChildScope(Arguments(ObjString(name))))?.let {
|
||||||
if (writeCallback != null)
|
if (writeCallback != null)
|
||||||
it.asMutable
|
it.asMutable
|
||||||
@ -83,26 +82,26 @@ open class ObjDynamic(var readCallback: Statement? = null, var writeCallback: St
|
|||||||
args: Arguments,
|
args: Arguments,
|
||||||
onNotFoundResult: (suspend () -> Obj?)?
|
onNotFoundResult: (suspend () -> Obj?)?
|
||||||
): Obj {
|
): Obj {
|
||||||
val execBase = builderScope?.let { ClosureScope(scope, it) } ?: scope
|
val execBase = builderScope?.let { scope.applyClosure(it) } ?: scope
|
||||||
val over = readCallback?.execute(execBase.createChildScope(Arguments(ObjString(name))))
|
val over = readCallback?.execute(execBase.createChildScope(Arguments(ObjString(name))))
|
||||||
return over?.invoke(scope, scope.thisObj, args)
|
return over?.invoke(scope, scope.thisObj, args)
|
||||||
?: super.invokeInstanceMethod(scope, name, args, onNotFoundResult)
|
?: super.invokeInstanceMethod(scope, name, args, onNotFoundResult)
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun writeField(scope: Scope, name: String, newValue: Obj) {
|
override suspend fun writeField(scope: Scope, name: String, newValue: Obj) {
|
||||||
val execBase = builderScope?.let { ClosureScope(scope, it) } ?: scope
|
val execBase = builderScope?.let { scope.applyClosure(it) } ?: scope
|
||||||
writeCallback?.execute(execBase.createChildScope(Arguments(ObjString(name), newValue)))
|
writeCallback?.execute(execBase.createChildScope(Arguments(ObjString(name), newValue)))
|
||||||
?: super.writeField(scope, name, newValue)
|
?: super.writeField(scope, name, newValue)
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun getAt(scope: Scope, index: Obj): Obj {
|
override suspend fun getAt(scope: Scope, index: Obj): Obj {
|
||||||
val execBase = builderScope?.let { ClosureScope(scope, it) } ?: scope
|
val execBase = builderScope?.let { scope.applyClosure(it) } ?: scope
|
||||||
return readCallback?.execute(execBase.createChildScope(Arguments(index)))
|
return readCallback?.execute(execBase.createChildScope(Arguments(index)))
|
||||||
?: super.getAt(scope, index)
|
?: super.getAt(scope, index)
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun putAt(scope: Scope, index: Obj, newValue: Obj) {
|
override suspend fun putAt(scope: Scope, index: Obj, newValue: Obj) {
|
||||||
val execBase = builderScope?.let { ClosureScope(scope, it) } ?: scope
|
val execBase = builderScope?.let { scope.applyClosure(it) } ?: scope
|
||||||
writeCallback?.execute(execBase.createChildScope(Arguments(index, newValue)))
|
writeCallback?.execute(execBase.createChildScope(Arguments(index, newValue)))
|
||||||
?: super.putAt(scope, index, newValue)
|
?: super.putAt(scope, index, newValue)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -18,7 +18,6 @@
|
|||||||
package net.sergeych.lyng.obj
|
package net.sergeych.lyng.obj
|
||||||
|
|
||||||
import net.sergeych.lyng.Arguments
|
import net.sergeych.lyng.Arguments
|
||||||
import net.sergeych.lyng.ClosureScope
|
|
||||||
import net.sergeych.lyng.Scope
|
import net.sergeych.lyng.Scope
|
||||||
import net.sergeych.lyng.Statement
|
import net.sergeych.lyng.Statement
|
||||||
|
|
||||||
@ -35,9 +34,9 @@ class ObjProperty(
|
|||||||
suspend fun callGetter(scope: Scope, instance: Obj, declaringClass: ObjClass? = null): Obj {
|
suspend fun callGetter(scope: Scope, instance: Obj, declaringClass: ObjClass? = null): Obj {
|
||||||
val g = getter ?: scope.raiseError("property $name has no getter")
|
val g = getter ?: scope.raiseError("property $name has no getter")
|
||||||
// Execute getter in a child scope of the instance with 'this' properly set
|
// Execute getter in a child scope of the instance with 'this' properly set
|
||||||
// Use ClosureScope to match extension function behavior (access to instance scope + call scope)
|
// Match extension function behavior (access to instance scope + call scope).
|
||||||
val instanceScope = (instance as? ObjInstance)?.instanceScope ?: instance.autoInstanceScope(scope)
|
val instanceScope = (instance as? ObjInstance)?.instanceScope ?: instance.autoInstanceScope(scope)
|
||||||
val execScope = ClosureScope(scope, instanceScope).createChildScope(newThisObj = instance)
|
val execScope = scope.applyClosure(instanceScope).createChildScope(newThisObj = instance)
|
||||||
execScope.currentClassCtx = declaringClass
|
execScope.currentClassCtx = declaringClass
|
||||||
return g.execute(execScope)
|
return g.execute(execScope)
|
||||||
}
|
}
|
||||||
@ -45,9 +44,9 @@ class ObjProperty(
|
|||||||
suspend fun callSetter(scope: Scope, instance: Obj, value: Obj, declaringClass: ObjClass? = null) {
|
suspend fun callSetter(scope: Scope, instance: Obj, value: Obj, declaringClass: ObjClass? = null) {
|
||||||
val s = setter ?: scope.raiseError("property $name has no setter")
|
val s = setter ?: scope.raiseError("property $name has no setter")
|
||||||
// Execute setter in a child scope of the instance with 'this' properly set and the value as an argument
|
// Execute setter in a child scope of the instance with 'this' properly set and the value as an argument
|
||||||
// Use ClosureScope to match extension function behavior
|
// Match extension function behavior (access to instance scope + call scope).
|
||||||
val instanceScope = (instance as? ObjInstance)?.instanceScope ?: instance.autoInstanceScope(scope)
|
val instanceScope = (instance as? ObjInstance)?.instanceScope ?: instance.autoInstanceScope(scope)
|
||||||
val execScope = ClosureScope(scope, instanceScope).createChildScope(args = Arguments(value), newThisObj = instance)
|
val execScope = scope.applyClosure(instanceScope).createChildScope(args = Arguments(value), newThisObj = instance)
|
||||||
execScope.currentClassCtx = declaringClass
|
execScope.currentClassCtx = declaringClass
|
||||||
s.execute(execScope)
|
s.execute(execScope)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -20,6 +20,8 @@
|
|||||||
package net.sergeych.lyng.obj
|
package net.sergeych.lyng.obj
|
||||||
|
|
||||||
import net.sergeych.lyng.*
|
import net.sergeych.lyng.*
|
||||||
|
import net.sergeych.lyng.FrameSlotRef
|
||||||
|
import net.sergeych.lyng.RecordSlotRef
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A reference to a value with optional write-back path.
|
* A reference to a value with optional write-back path.
|
||||||
@ -43,7 +45,12 @@ sealed interface ObjRef {
|
|||||||
if (rec.receiver != null && rec.declaringClass != null) {
|
if (rec.receiver != null && rec.declaringClass != null) {
|
||||||
return rec.receiver!!.resolveRecord(scope, rec, "unknown", rec.declaringClass).value
|
return rec.receiver!!.resolveRecord(scope, rec, "unknown", rec.declaringClass).value
|
||||||
}
|
}
|
||||||
return rec.value
|
val value = rec.value
|
||||||
|
return when (value) {
|
||||||
|
is FrameSlotRef -> value.read()
|
||||||
|
is RecordSlotRef -> value.read()
|
||||||
|
else -> value
|
||||||
|
}
|
||||||
}
|
}
|
||||||
suspend fun setAt(pos: Pos, scope: Scope, newValue: Obj) {
|
suspend fun setAt(pos: Pos, scope: Scope, newValue: Obj) {
|
||||||
throw ScriptError(pos, "can't assign value")
|
throw ScriptError(pos, "can't assign value")
|
||||||
@ -65,12 +72,19 @@ sealed interface ObjRef {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** Runtime-computed read-only reference backed by a lambda. */
|
/** Runtime-computed read-only reference backed by a lambda. */
|
||||||
class ValueFnRef(private val fn: suspend (Scope) -> ObjRecord) : ObjRef {
|
open class ValueFnRef(private val fn: suspend (Scope) -> ObjRecord) : ObjRef {
|
||||||
internal fun valueFn(): suspend (Scope) -> ObjRecord = fn
|
internal fun valueFn(): suspend (Scope) -> ObjRecord = fn
|
||||||
|
|
||||||
override suspend fun get(scope: Scope): ObjRecord = fn(scope)
|
override suspend fun get(scope: Scope): ObjRecord = fn(scope)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Compile-time supported ::class operator reference. */
|
||||||
|
class ClassOperatorRef(val target: ObjRef, val pos: Pos) : ObjRef {
|
||||||
|
override suspend fun get(scope: Scope): ObjRecord {
|
||||||
|
return target.evalValue(scope).objClass.asReadonly
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/** Unary operations supported by ObjRef. */
|
/** Unary operations supported by ObjRef. */
|
||||||
enum class UnaryOp { NOT, NEGATE, BITNOT }
|
enum class UnaryOp { NOT, NEGATE, BITNOT }
|
||||||
|
|
||||||
@ -1971,6 +1985,7 @@ class BoundLocalVarRef(
|
|||||||
private val slot: Int,
|
private val slot: Int,
|
||||||
private val atPos: Pos,
|
private val atPos: Pos,
|
||||||
) : ObjRef {
|
) : ObjRef {
|
||||||
|
internal fun slotIndex(): Int = slot
|
||||||
override suspend fun get(scope: Scope): ObjRecord {
|
override suspend fun get(scope: Scope): ObjRecord {
|
||||||
scope.pos = atPos
|
scope.pos = atPos
|
||||||
val rec = scope.getSlotRecord(slot)
|
val rec = scope.getSlotRecord(slot)
|
||||||
|
|||||||
@ -99,6 +99,7 @@ class ForInStatement(
|
|||||||
val label: String?,
|
val label: String?,
|
||||||
val canBreak: Boolean,
|
val canBreak: Boolean,
|
||||||
val loopSlotPlan: Map<String, Int>,
|
val loopSlotPlan: Map<String, Int>,
|
||||||
|
val loopScopeId: Int,
|
||||||
override val pos: Pos,
|
override val pos: Pos,
|
||||||
) : Statement() {
|
) : Statement() {
|
||||||
override suspend fun execute(scope: Scope): Obj {
|
override suspend fun execute(scope: Scope): Obj {
|
||||||
@ -439,15 +440,6 @@ class ThrowStatement(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class ToBoolStatement(
|
|
||||||
val expr: Statement,
|
|
||||||
override val pos: Pos,
|
|
||||||
) : Statement() {
|
|
||||||
override suspend fun execute(scope: Scope): Obj {
|
|
||||||
return if (expr.execute(scope).toBool()) net.sergeych.lyng.obj.ObjTrue else net.sergeych.lyng.obj.ObjFalse
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class ExpressionStatement(
|
class ExpressionStatement(
|
||||||
val ref: net.sergeych.lyng.obj.ObjRef,
|
val ref: net.sergeych.lyng.obj.ObjRef,
|
||||||
override val pos: Pos
|
override val pos: Pos
|
||||||
|
|||||||
@ -16,8 +16,22 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import kotlinx.coroutines.test.runTest
|
import kotlinx.coroutines.test.runTest
|
||||||
|
import net.sergeych.lyng.Compiler
|
||||||
|
import net.sergeych.lyng.ExecutionError
|
||||||
|
import net.sergeych.lyng.Pos
|
||||||
|
import net.sergeych.lyng.Script
|
||||||
|
import net.sergeych.lyng.ScriptError
|
||||||
|
import net.sergeych.lyng.Source
|
||||||
import net.sergeych.lyng.eval
|
import net.sergeych.lyng.eval
|
||||||
|
import net.sergeych.lyng.toSource
|
||||||
|
import net.sergeych.lyng.bytecode.CmdDisassembler
|
||||||
|
import net.sergeych.lyng.bytecode.CmdFunction
|
||||||
|
import net.sergeych.lyng.obj.toInt
|
||||||
import kotlin.test.Test
|
import kotlin.test.Test
|
||||||
|
import kotlin.test.assertEquals
|
||||||
|
import kotlin.test.assertFailsWith
|
||||||
|
import kotlin.test.assertNotNull
|
||||||
|
import kotlin.test.assertTrue
|
||||||
|
|
||||||
class BytecodeRecentOpsTest {
|
class BytecodeRecentOpsTest {
|
||||||
|
|
||||||
@ -88,4 +102,300 @@ class BytecodeRecentOpsTest {
|
|||||||
""".trimIndent()
|
""".trimIndent()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun optionalIndexIncDecSkipsOnNullReceiver() = runTest {
|
||||||
|
eval(
|
||||||
|
"""
|
||||||
|
var count = 0
|
||||||
|
fun idx() { count = count + 1; return 1 }
|
||||||
|
var a: List<Int>? = null
|
||||||
|
val r = a?[idx()]++
|
||||||
|
assertEquals(null, r)
|
||||||
|
assertEquals(0, count)
|
||||||
|
""".trimIndent()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun optionalIndexIncDecUpdatesOnNonNullReceiver() = runTest {
|
||||||
|
eval(
|
||||||
|
"""
|
||||||
|
var a = [1, 2, 3]
|
||||||
|
val r = a?[1]++
|
||||||
|
assertEquals(2, r)
|
||||||
|
assertEquals(3, a[1])
|
||||||
|
""".trimIndent()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun optionalIndexPreIncSkipsOnNullReceiver() = runTest {
|
||||||
|
eval(
|
||||||
|
"""
|
||||||
|
var count = 0
|
||||||
|
fun idx() { count = count + 1; return 1 }
|
||||||
|
var a: List<Int>? = null
|
||||||
|
val r = ++a?[idx()]
|
||||||
|
assertEquals(null, r)
|
||||||
|
assertEquals(0, count)
|
||||||
|
""".trimIndent()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun optionalClassScopeIncDec() = runTest {
|
||||||
|
eval(
|
||||||
|
"""
|
||||||
|
class C { static var x = 1 }
|
||||||
|
val r = C?.x++
|
||||||
|
assertEquals(1, r)
|
||||||
|
assertEquals(2, C.x)
|
||||||
|
""".trimIndent()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun classScopeIfNullAssign() = runTest {
|
||||||
|
eval(
|
||||||
|
"""
|
||||||
|
class C { static var x: Object? = null }
|
||||||
|
C.x ?= 7
|
||||||
|
assertEquals(7, C.x)
|
||||||
|
C.x ?= 9
|
||||||
|
assertEquals(7, C.x)
|
||||||
|
""".trimIndent()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun callablePropertyCall() = runTest {
|
||||||
|
eval(
|
||||||
|
"""
|
||||||
|
class C { var f = { x -> x + 1 } }
|
||||||
|
val c = C()
|
||||||
|
val r = (c.f)(2)
|
||||||
|
assertEquals(3, r)
|
||||||
|
""".trimIndent()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun lambdaCapturesLocalByReference() = runTest {
|
||||||
|
eval(
|
||||||
|
"""
|
||||||
|
fun make() {
|
||||||
|
var base = 3
|
||||||
|
val f = { x -> x + base }
|
||||||
|
base = 7
|
||||||
|
return f(1)
|
||||||
|
}
|
||||||
|
assertEquals(8, make())
|
||||||
|
""".trimIndent()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun lambdaCapturesDelegatedLocal() = runTest {
|
||||||
|
eval(
|
||||||
|
"""
|
||||||
|
class BoxDelegate(var v) : Delegate {
|
||||||
|
override fun getValue(thisRef: Object, name: String): Object = v
|
||||||
|
override fun setValue(thisRef: Object, name: String, value: Object) { v = value }
|
||||||
|
}
|
||||||
|
fun make() {
|
||||||
|
var x by BoxDelegate(1)
|
||||||
|
val f = { y -> x += y; return x }
|
||||||
|
return f(2)
|
||||||
|
}
|
||||||
|
assertEquals(3, make())
|
||||||
|
""".trimIndent()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun delegatedMemberAccessAndCall() = runTest {
|
||||||
|
eval(
|
||||||
|
"""
|
||||||
|
class ConstDelegate(val v) : Delegate {
|
||||||
|
override fun getValue(thisRef: Object, name: String): Object = v
|
||||||
|
}
|
||||||
|
class ActionDelegate : Delegate {
|
||||||
|
override fun invoke(thisRef: Object, name: String, args...) {
|
||||||
|
val list: List = args as List
|
||||||
|
"Called %s with %d args: %s"(name, list.size, list.toString())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
class C {
|
||||||
|
val a by ConstDelegate(7)
|
||||||
|
fun greet by ActionDelegate()
|
||||||
|
}
|
||||||
|
val c = C()
|
||||||
|
assertEquals(7, c.a)
|
||||||
|
assertEquals("Called greet with 2 args: [hi,world]", c.greet("hi", "world"))
|
||||||
|
""".trimIndent()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun delegatedLocalAssignAndIncDec() = runTest {
|
||||||
|
eval(
|
||||||
|
"""
|
||||||
|
class BoxDelegate(var v) : Delegate {
|
||||||
|
override fun getValue(thisRef: Object, name: String): Object = v
|
||||||
|
override fun setValue(thisRef: Object, name: String, value: Object) { v = value }
|
||||||
|
}
|
||||||
|
fun calc() {
|
||||||
|
var x by BoxDelegate(1)
|
||||||
|
x += 2
|
||||||
|
x++
|
||||||
|
return x
|
||||||
|
}
|
||||||
|
assertEquals(4, calc())
|
||||||
|
""".trimIndent()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun delegatedLocalDisasmUsesDelegateOps() = runTest {
|
||||||
|
val script = """
|
||||||
|
class BoxDelegate(var v) : Delegate {
|
||||||
|
override fun getValue(thisRef: Object, name: String): Object = v
|
||||||
|
override fun setValue(thisRef: Object, name: String, value: Object) { v = value }
|
||||||
|
}
|
||||||
|
fun calc() {
|
||||||
|
var x by BoxDelegate(1)
|
||||||
|
x += 2
|
||||||
|
x++
|
||||||
|
return x
|
||||||
|
}
|
||||||
|
""".trimIndent()
|
||||||
|
val compiled = Compiler.compile(script.toSource(), Script.defaultImportManager)
|
||||||
|
val scope = Script.defaultImportManager.newModuleAt(Pos.builtIn)
|
||||||
|
compiled.execute(scope)
|
||||||
|
val disasm = scope.disassembleSymbol("calc")
|
||||||
|
assertTrue(disasm.contains("DELEGATED_GET_LOCAL"), disasm)
|
||||||
|
assertTrue(disasm.contains("DELEGATED_SET_LOCAL"), disasm)
|
||||||
|
assertTrue(disasm.contains("DECL_DELEGATED"), disasm)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun moduleDeclsAvoidCallableCallSlots() = runTest {
|
||||||
|
val script = """
|
||||||
|
class A {}
|
||||||
|
fun f() { 1 }
|
||||||
|
enum E { one }
|
||||||
|
""".trimIndent()
|
||||||
|
val compiled = Compiler.compile(script.toSource(), Script.defaultImportManager)
|
||||||
|
val field = Script::class.java.getDeclaredField("moduleBytecode")
|
||||||
|
field.isAccessible = true
|
||||||
|
val moduleFn = field.get(compiled) as? CmdFunction
|
||||||
|
assertNotNull(moduleFn, "module bytecode missing")
|
||||||
|
val disasm = CmdDisassembler.disassemble(moduleFn)
|
||||||
|
assertTrue(!disasm.contains("CALL_SLOT"), disasm)
|
||||||
|
assertTrue(!disasm.contains("Callable@"), disasm)
|
||||||
|
assertTrue(disasm.contains("DECL_CLASS"), disasm)
|
||||||
|
assertTrue(disasm.contains("DECL_FUNCTION"), disasm)
|
||||||
|
assertTrue(disasm.contains("DECL_ENUM"), disasm)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun unionMemberDispatchSubtype() = runTest {
|
||||||
|
eval(
|
||||||
|
"""
|
||||||
|
class A { fun who() = "A" }
|
||||||
|
class B : A { override fun who() = "B" }
|
||||||
|
fun pick(x: A | B) { x.who() }
|
||||||
|
assertEquals("B", pick(B()))
|
||||||
|
""".trimIndent()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun staticMemberDeclNopStatement() = runTest {
|
||||||
|
eval(
|
||||||
|
"""
|
||||||
|
class C {
|
||||||
|
static fun ping() { 7 }
|
||||||
|
}
|
||||||
|
assertEquals(7, C.ping())
|
||||||
|
""".trimIndent()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun unionMemberDispatchMismatch() = runTest {
|
||||||
|
val err = assertFailsWith<ExecutionError> {
|
||||||
|
eval(
|
||||||
|
"""
|
||||||
|
class A { fun who() = "A" }
|
||||||
|
class B { fun who() = "B" }
|
||||||
|
val x: A | B = 1
|
||||||
|
x.who()
|
||||||
|
""".trimIndent()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
assertTrue(err.message?.contains("value is not A | B") == true)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun objectReceiverMemberError() = runTest {
|
||||||
|
val failed = try {
|
||||||
|
eval("fun bad(x) { x.missing() }")
|
||||||
|
false
|
||||||
|
} catch (_: ScriptError) {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
assertTrue(failed)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun unionMissingMemberError() = runTest {
|
||||||
|
val failed = try {
|
||||||
|
eval(
|
||||||
|
"""
|
||||||
|
class A { fun who() = "A" }
|
||||||
|
class B { fun other() = "B" }
|
||||||
|
fun pick(x: A | B) { x.who() }
|
||||||
|
""".trimIndent()
|
||||||
|
)
|
||||||
|
false
|
||||||
|
} catch (_: ScriptError) {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
assertTrue(failed)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun qualifiedThisValueRef() = runTest {
|
||||||
|
eval(
|
||||||
|
"""
|
||||||
|
class T(val v) {
|
||||||
|
fun get() {
|
||||||
|
this@T.v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assertEquals(7, T(7).get())
|
||||||
|
""".trimIndent()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun fastLocalVarRefRead() = runTest {
|
||||||
|
val code = """
|
||||||
|
fun addOne(x) {
|
||||||
|
val y = x + 1
|
||||||
|
y
|
||||||
|
}
|
||||||
|
addOne(1)
|
||||||
|
""".trimIndent()
|
||||||
|
val script = Compiler.compileWithResolution(
|
||||||
|
Source("<fast-local>", code),
|
||||||
|
Script.defaultImportManager,
|
||||||
|
useBytecodeStatements = true,
|
||||||
|
useFastLocalRefs = true
|
||||||
|
)
|
||||||
|
val result = script.execute(Script.defaultImportManager.newStdScope())
|
||||||
|
assertEquals(2, result.toInt())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -31,6 +31,12 @@ extern class List<T> : Array<T> {
|
|||||||
fun add(value: T, more...): Void
|
fun add(value: T, more...): Void
|
||||||
}
|
}
|
||||||
|
|
||||||
|
extern class RingBuffer<T> : Iterable<T> {
|
||||||
|
val size: Int
|
||||||
|
fun first(): T
|
||||||
|
fun add(value: T): Void
|
||||||
|
}
|
||||||
|
|
||||||
extern class Set<T> : Collection<T> {
|
extern class Set<T> : Collection<T> {
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -270,8 +276,10 @@ fun Iterable<T>.shuffled(): List<T> {
|
|||||||
*/
|
*/
|
||||||
fun Iterable<Iterable<T>>.flatten(): List<T> {
|
fun Iterable<Iterable<T>>.flatten(): List<T> {
|
||||||
var result: List<T> = List()
|
var result: List<T> = List()
|
||||||
forEach { i ->
|
for (i in this) {
|
||||||
i.forEach { result += it }
|
for (item in i) {
|
||||||
|
result += item
|
||||||
|
}
|
||||||
}
|
}
|
||||||
result
|
result
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user