Compare commits
No commits in common. "cd007050a8392c3d84f5699f58aa41f08bdf55ed" and "86e8b2e2bcfbf9cb542844a39cbc99ddee4d038f" have entirely different histories.
cd007050a8
...
86e8b2e2bc
1
.gitignore
vendored
1
.gitignore
vendored
@ -27,4 +27,3 @@ debug.log
|
|||||||
/compile_jvm_output.txt
|
/compile_jvm_output.txt
|
||||||
/compile_metadata_output.txt
|
/compile_metadata_output.txt
|
||||||
test_output*.txt
|
test_output*.txt
|
||||||
/site/src/version-template/lyng-version.js
|
|
||||||
|
|||||||
@ -1,82 +0,0 @@
|
|||||||
# Complex Numbers (`lyng.complex`)
|
|
||||||
|
|
||||||
`lyng.complex` adds a pure-Lyng `Complex` type backed by `Real` components.
|
|
||||||
|
|
||||||
Import it when you want ordinary complex arithmetic:
|
|
||||||
|
|
||||||
```lyng
|
|
||||||
import lyng.complex
|
|
||||||
```
|
|
||||||
|
|
||||||
## Construction
|
|
||||||
|
|
||||||
Use any of these:
|
|
||||||
|
|
||||||
```lyng
|
|
||||||
import lyng.complex
|
|
||||||
|
|
||||||
val a = Complex(1.0, 2.0)
|
|
||||||
val b = complex(1.0, 2.0)
|
|
||||||
val c = 2.i
|
|
||||||
val d = 3.re
|
|
||||||
|
|
||||||
assertEquals(Complex(1.0, 2.0), 1 + 2.i)
|
|
||||||
```
|
|
||||||
|
|
||||||
Convenience extensions:
|
|
||||||
|
|
||||||
- `Int.re`, `Real.re`: embed a real value into the complex plane
|
|
||||||
- `Int.i`, `Real.i`: create a pure imaginary value
|
|
||||||
- `cis(angle)`: shorthand for `cos(angle) + i sin(angle)`
|
|
||||||
|
|
||||||
## Core Operations
|
|
||||||
|
|
||||||
`Complex` supports:
|
|
||||||
|
|
||||||
- `+`
|
|
||||||
- `-`
|
|
||||||
- `*`
|
|
||||||
- `/`
|
|
||||||
- unary `-`
|
|
||||||
- `conjugate`
|
|
||||||
- `magnitude`
|
|
||||||
- `phase`
|
|
||||||
|
|
||||||
Mixed arithmetic with `Int` and `Real` is enabled through `lyng.operators`, so both sides work naturally:
|
|
||||||
|
|
||||||
```lyng
|
|
||||||
import lyng.complex
|
|
||||||
|
|
||||||
assertEquals(Complex(1.0, 2.0), 1 + 2.i)
|
|
||||||
assertEquals(Complex(1.5, 2.0), 1.5 + 2.i)
|
|
||||||
assertEquals(Complex(2.0, 2.0), 2.i + 2)
|
|
||||||
```
|
|
||||||
|
|
||||||
Mixed equality with built-in numeric types is intentionally not promised yet. Keep equality checks in the `Complex` domain for now.
|
|
||||||
|
|
||||||
## Transcendental Functions
|
|
||||||
|
|
||||||
For now, use member-style calls:
|
|
||||||
|
|
||||||
```lyng
|
|
||||||
import lyng.complex
|
|
||||||
|
|
||||||
val z = 1 + π.i
|
|
||||||
val w = z.exp()
|
|
||||||
val s = z.sin()
|
|
||||||
val r = z.sqrt()
|
|
||||||
```
|
|
||||||
|
|
||||||
This is deliberate. Lyng already has built-in top-level real-valued functions such as `exp(x)` and `sin(x)`, and imported modules do not currently replace those root bindings. So plain `exp(z)` is not yet the right extension mechanism for complex math.
|
|
||||||
|
|
||||||
## Design Scope
|
|
||||||
|
|
||||||
This module intentionally uses `Complex` with `Real` parts, not `Complex<T>`.
|
|
||||||
|
|
||||||
Reasons:
|
|
||||||
|
|
||||||
- the existing math runtime is `Real`-centric
|
|
||||||
- the operator interop registry works with concrete runtime classes
|
|
||||||
- transcendental functions (`exp`, `sin`, `ln`, `sqrt`) are defined over the `Real` math backend here
|
|
||||||
|
|
||||||
If Lyng later gets a more general numeric-trait or callable-overload registry, a generic algebraic `Complex<T>` can be revisited on firmer ground.
|
|
||||||
@ -56,8 +56,6 @@ Sources: `lynglib/src/commonMain/kotlin/net/sergeych/lyng/Script.kt`, `lynglib/s
|
|||||||
## 5. Additional Built-in Modules (import explicitly)
|
## 5. Additional Built-in Modules (import explicitly)
|
||||||
- `import lyng.observable`
|
- `import lyng.observable`
|
||||||
- `Observable`, `Subscription`, `ObservableList`, `ListChange` and change subtypes, `ChangeRejectionException`.
|
- `Observable`, `Subscription`, `ObservableList`, `ListChange` and change subtypes, `ChangeRejectionException`.
|
||||||
- `import lyng.complex`
|
|
||||||
- `Complex`, `complex(re, im)`, `cis(angle)`, and numeric embedding extensions such as `2.i` / `3.re`.
|
|
||||||
- `import lyng.buffer`
|
- `import lyng.buffer`
|
||||||
- `Buffer`, `MutableBuffer`.
|
- `Buffer`, `MutableBuffer`.
|
||||||
- `import lyng.serialization`
|
- `import lyng.serialization`
|
||||||
|
|||||||
@ -21,7 +21,7 @@ import org.jetbrains.kotlin.gradle.ExperimentalWasmDsl
|
|||||||
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
|
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
|
||||||
|
|
||||||
group = "net.sergeych"
|
group = "net.sergeych"
|
||||||
version = "1.5.3-SNAPSHOT"
|
version = "1.5.2"
|
||||||
|
|
||||||
// Removed legacy buildscript classpath declarations; plugins are applied via the plugins DSL below
|
// Removed legacy buildscript classpath declarations; plugins are applied via the plugins DSL below
|
||||||
|
|
||||||
@ -149,9 +149,6 @@ abstract class GenerateLyngStdlib : DefaultTask() {
|
|||||||
val outBase = outputDir.get().asFile
|
val outBase = outputDir.get().asFile
|
||||||
val targetDir = outBase.resolve(pkgPath)
|
val targetDir = outBase.resolve(pkgPath)
|
||||||
targetDir.mkdirs()
|
targetDir.mkdirs()
|
||||||
targetDir.listFiles()
|
|
||||||
?.filter { it.isFile && it.name.endsWith(".generated.kt") }
|
|
||||||
?.forEach { it.delete() }
|
|
||||||
|
|
||||||
val srcDir = sourceDir.get().asFile
|
val srcDir = sourceDir.get().asFile
|
||||||
val files = srcDir.walkTopDown()
|
val files = srcDir.walkTopDown()
|
||||||
@ -159,38 +156,34 @@ abstract class GenerateLyngStdlib : DefaultTask() {
|
|||||||
.sortedBy { it.name }
|
.sortedBy { it.name }
|
||||||
.toList()
|
.toList()
|
||||||
|
|
||||||
|
val content = if (files.isEmpty()) "" else buildString {
|
||||||
|
files.forEachIndexed { idx, f ->
|
||||||
|
val text = f.readText()
|
||||||
|
if (idx > 0) append("\n\n")
|
||||||
|
append(text)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun escapeForQuoted(s: String): String = buildString {
|
fun escapeForQuoted(s: String): String = buildString {
|
||||||
for (ch in s) when (ch) {
|
for (ch in s) when (ch) {
|
||||||
'\\' -> append("\\\\")
|
'\\' -> append("\\\\")
|
||||||
'"' -> append("\\\"")
|
'"' -> append("\\\"")
|
||||||
'$' -> append("\\$")
|
|
||||||
'\n' -> append("\\n")
|
'\n' -> append("\\n")
|
||||||
'\r' -> {}
|
'\r' -> {}
|
||||||
'\t' -> append("\\t")
|
'\t' -> append("\\t")
|
||||||
else -> append(ch)
|
else -> append(ch)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
val body = escapeForQuoted(content)
|
||||||
|
|
||||||
fun constantName(baseName: String): String {
|
val sb = StringBuilder()
|
||||||
val parts = baseName.split(Regex("[^A-Za-z0-9]+")).filter { it.isNotEmpty() }
|
sb.append("package ").append(targetPkg).append("\n\n")
|
||||||
if (parts.isEmpty()) return "moduleLyng"
|
sb.append("@Suppress(\"Unused\", \"MemberVisibilityCanBePrivate\")\n")
|
||||||
val head = parts.first().replaceFirstChar { it.lowercase() }
|
sb.append("internal val rootLyng = \"")
|
||||||
val tail = parts.drop(1).joinToString("") { part ->
|
sb.append(body)
|
||||||
part.replaceFirstChar { it.uppercase() }
|
sb.append("\"\n")
|
||||||
}
|
|
||||||
return "${head}${tail}Lyng"
|
|
||||||
}
|
|
||||||
|
|
||||||
for (file in files) {
|
targetDir.resolve("root_lyng.generated.kt").writeText(sb.toString())
|
||||||
val body = escapeForQuoted(file.readText())
|
|
||||||
val sb = StringBuilder()
|
|
||||||
sb.append("package ").append(targetPkg).append("\n\n")
|
|
||||||
sb.append("@Suppress(\"Unused\", \"MemberVisibilityCanBePrivate\")\n")
|
|
||||||
sb.append("internal val ").append(constantName(file.nameWithoutExtension)).append(" = \"")
|
|
||||||
sb.append(body)
|
|
||||||
sb.append("\"\n")
|
|
||||||
targetDir.resolve("${file.nameWithoutExtension}_lyng.generated.kt").writeText(sb.toString())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -8253,7 +8253,7 @@ class Compiler(
|
|||||||
?: context.parent?.get(localName)
|
?: context.parent?.get(localName)
|
||||||
?: context.get(localName)
|
?: context.get(localName)
|
||||||
?: continue
|
?: continue
|
||||||
val value = if (record.type == ObjRecord.Type.Delegated || record.type == ObjRecord.Type.Property || record.value is ObjProperty) {
|
val value = if (record.type == ObjRecord.Type.Delegated || record.type == ObjRecord.Type.Property) {
|
||||||
context.resolve(record, localName)
|
context.resolve(record, localName)
|
||||||
} else {
|
} else {
|
||||||
record.value
|
record.value
|
||||||
|
|||||||
@ -167,19 +167,6 @@ class RecordSlotRef(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun read(scope: Scope, name: String?): Obj {
|
|
||||||
val direct = record.value
|
|
||||||
if (name != null && (record.type == ObjRecord.Type.Delegated || record.type == ObjRecord.Type.Property || direct is ObjProperty)) {
|
|
||||||
return scope.resolve(record, name)
|
|
||||||
}
|
|
||||||
return when (direct) {
|
|
||||||
is FrameSlotRef -> direct.read()
|
|
||||||
is RecordSlotRef -> direct.read(scope, name)
|
|
||||||
is ScopeSlotRef -> direct.read()
|
|
||||||
else -> direct
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun callOn(scope: Scope): Obj {
|
override suspend fun callOn(scope: Scope): Obj {
|
||||||
val resolved = read()
|
val resolved = read()
|
||||||
if (resolved === this) {
|
if (resolved === this) {
|
||||||
@ -206,18 +193,4 @@ class RecordSlotRef(
|
|||||||
record.value = value
|
record.value = value
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun write(scope: Scope, name: String?, value: Obj): Boolean {
|
|
||||||
val direct = record.value
|
|
||||||
if (name != null && (record.type == ObjRecord.Type.Delegated || record.type == ObjRecord.Type.Property || direct is ObjProperty)) {
|
|
||||||
scope.assign(record, name, value)
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
when (direct) {
|
|
||||||
is ScopeSlotRef -> direct.write(value)
|
|
||||||
is RecordSlotRef -> if (direct.write(scope, name, value)) return true else direct.write(value)
|
|
||||||
else -> record.value = value
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -27,7 +27,6 @@ import net.sergeych.lyng.bytecode.CmdVm
|
|||||||
import net.sergeych.lyng.miniast.*
|
import net.sergeych.lyng.miniast.*
|
||||||
import net.sergeych.lyng.obj.*
|
import net.sergeych.lyng.obj.*
|
||||||
import net.sergeych.lyng.pacman.ImportManager
|
import net.sergeych.lyng.pacman.ImportManager
|
||||||
import net.sergeych.lyng.stdlib_included.complexLyng
|
|
||||||
import net.sergeych.lyng.stdlib_included.decimalLyng
|
import net.sergeych.lyng.stdlib_included.decimalLyng
|
||||||
import net.sergeych.lyng.stdlib_included.observableLyng
|
import net.sergeych.lyng.stdlib_included.observableLyng
|
||||||
import net.sergeych.lyng.stdlib_included.operatorsLyng
|
import net.sergeych.lyng.stdlib_included.operatorsLyng
|
||||||
@ -616,91 +615,12 @@ class Script(
|
|||||||
type = type("lyng.Real")
|
type = type("lyng.Real")
|
||||||
)
|
)
|
||||||
getOrCreateNamespace("Math").apply {
|
getOrCreateNamespace("Math").apply {
|
||||||
fun ensureFn(name: String, fn: suspend ScopeFacade.() -> Obj) {
|
|
||||||
if (members.containsKey(name)) return
|
|
||||||
addFn(name, code = fn)
|
|
||||||
}
|
|
||||||
addConstDoc(
|
addConstDoc(
|
||||||
name = "PI",
|
name = "PI",
|
||||||
value = pi,
|
value = pi,
|
||||||
doc = "The mathematical constant pi (π) in the Math namespace.",
|
doc = "The mathematical constant pi (π) in the Math namespace.",
|
||||||
type = type("lyng.Real")
|
type = type("lyng.Real")
|
||||||
)
|
)
|
||||||
ensureFn("floor") {
|
|
||||||
val x = args.firstAndOnly()
|
|
||||||
if (x is ObjInt) x else ObjReal(floor(x.toDouble()))
|
|
||||||
}
|
|
||||||
ensureFn("ceil") {
|
|
||||||
val x = args.firstAndOnly()
|
|
||||||
if (x is ObjInt) x else ObjReal(ceil(x.toDouble()))
|
|
||||||
}
|
|
||||||
ensureFn("round") {
|
|
||||||
val x = args.firstAndOnly()
|
|
||||||
if (x is ObjInt) x else ObjReal(round(x.toDouble()))
|
|
||||||
}
|
|
||||||
ensureFn("sin") {
|
|
||||||
ObjReal(sin(args.firstAndOnly().toDouble()))
|
|
||||||
}
|
|
||||||
ensureFn("cos") {
|
|
||||||
ObjReal(cos(args.firstAndOnly().toDouble()))
|
|
||||||
}
|
|
||||||
ensureFn("tan") {
|
|
||||||
ObjReal(tan(args.firstAndOnly().toDouble()))
|
|
||||||
}
|
|
||||||
ensureFn("asin") {
|
|
||||||
ObjReal(asin(args.firstAndOnly().toDouble()))
|
|
||||||
}
|
|
||||||
ensureFn("acos") {
|
|
||||||
ObjReal(acos(args.firstAndOnly().toDouble()))
|
|
||||||
}
|
|
||||||
ensureFn("atan") {
|
|
||||||
ObjReal(atan(args.firstAndOnly().toDouble()))
|
|
||||||
}
|
|
||||||
ensureFn("sinh") {
|
|
||||||
ObjReal(sinh(args.firstAndOnly().toDouble()))
|
|
||||||
}
|
|
||||||
ensureFn("cosh") {
|
|
||||||
ObjReal(cosh(args.firstAndOnly().toDouble()))
|
|
||||||
}
|
|
||||||
ensureFn("tanh") {
|
|
||||||
ObjReal(tanh(args.firstAndOnly().toDouble()))
|
|
||||||
}
|
|
||||||
ensureFn("asinh") {
|
|
||||||
ObjReal(asinh(args.firstAndOnly().toDouble()))
|
|
||||||
}
|
|
||||||
ensureFn("acosh") {
|
|
||||||
ObjReal(acosh(args.firstAndOnly().toDouble()))
|
|
||||||
}
|
|
||||||
ensureFn("atanh") {
|
|
||||||
ObjReal(atanh(args.firstAndOnly().toDouble()))
|
|
||||||
}
|
|
||||||
ensureFn("exp") {
|
|
||||||
ObjReal(exp(args.firstAndOnly().toDouble()))
|
|
||||||
}
|
|
||||||
ensureFn("ln") {
|
|
||||||
ObjReal(ln(args.firstAndOnly().toDouble()))
|
|
||||||
}
|
|
||||||
ensureFn("log10") {
|
|
||||||
ObjReal(log10(args.firstAndOnly().toDouble()))
|
|
||||||
}
|
|
||||||
ensureFn("log2") {
|
|
||||||
ObjReal(log2(args.firstAndOnly().toDouble()))
|
|
||||||
}
|
|
||||||
ensureFn("pow") {
|
|
||||||
requireExactCount(2)
|
|
||||||
ObjReal(
|
|
||||||
(args[0].toDouble()).pow(args[1].toDouble())
|
|
||||||
)
|
|
||||||
}
|
|
||||||
ensureFn("sqrt") {
|
|
||||||
ObjReal(
|
|
||||||
sqrt(args.firstAndOnly().toDouble())
|
|
||||||
)
|
|
||||||
}
|
|
||||||
ensureFn("abs") {
|
|
||||||
val x = args.firstAndOnly()
|
|
||||||
if (x is ObjInt) ObjInt(x.value.absoluteValue) else ObjReal(x.toDouble().absoluteValue)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -839,9 +759,6 @@ class Script(
|
|||||||
module.eval(Source("lyng.decimal", decimalLyng))
|
module.eval(Source("lyng.decimal", decimalLyng))
|
||||||
ObjBigDecimalSupport.bindTo(module)
|
ObjBigDecimalSupport.bindTo(module)
|
||||||
}
|
}
|
||||||
addPackage("lyng.complex") { module ->
|
|
||||||
module.eval(Source("lyng.complex", complexLyng))
|
|
||||||
}
|
|
||||||
addPackage("lyng.buffer") {
|
addPackage("lyng.buffer") {
|
||||||
it.addConstDoc(
|
it.addConstDoc(
|
||||||
name = "Buffer",
|
name = "Buffer",
|
||||||
|
|||||||
@ -2396,8 +2396,7 @@ class BytecodeCompiler(
|
|||||||
builder.emit(Opcode.THROW, posId, msgSlot)
|
builder.emit(Opcode.THROW, posId, msgSlot)
|
||||||
return value
|
return value
|
||||||
}
|
}
|
||||||
val slot = resolveCapturedOwnerScopeSlot(localTarget)
|
val slot = resolveSlot(localTarget)
|
||||||
?: resolveSlot(localTarget)
|
|
||||||
?: resolveAssignableSlotByName(localTarget.name)?.first
|
?: resolveAssignableSlotByName(localTarget.name)?.first
|
||||||
?: return null
|
?: return null
|
||||||
if (slot < scopeSlotCount && value.type != SlotType.UNKNOWN) {
|
if (slot < scopeSlotCount && value.type != SlotType.UNKNOWN) {
|
||||||
@ -2703,7 +2702,7 @@ class BytecodeCompiler(
|
|||||||
return CompiledValue(result, SlotType.OBJ)
|
return CompiledValue(result, SlotType.OBJ)
|
||||||
}
|
}
|
||||||
if (localTarget.isDelegated) return compileEvalRef(ref)
|
if (localTarget.isDelegated) return compileEvalRef(ref)
|
||||||
val slot = resolveCapturedOwnerScopeSlot(localTarget) ?: resolveSlot(localTarget) ?: return null
|
val slot = resolveSlot(localTarget) ?: return null
|
||||||
val targetType = slotTypes[slot] ?: SlotType.OBJ
|
val targetType = slotTypes[slot] ?: SlotType.OBJ
|
||||||
if (!localTarget.isMutable) {
|
if (!localTarget.isMutable) {
|
||||||
if (targetType != SlotType.OBJ && targetType != SlotType.UNKNOWN) return compileEvalRef(ref)
|
if (targetType != SlotType.OBJ && targetType != SlotType.UNKNOWN) return compileEvalRef(ref)
|
||||||
@ -7631,13 +7630,6 @@ class BytecodeCompiler(
|
|||||||
return scopeSlotMap[scopeKey]
|
return scopeSlotMap[scopeKey]
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun resolveCapturedOwnerScopeSlot(ref: LocalSlotRef): Int? {
|
|
||||||
val ownerScopeId = ref.captureOwnerScopeId ?: return null
|
|
||||||
val ownerSlot = ref.captureOwnerSlot ?: return null
|
|
||||||
val key = ScopeSlotKey(ownerScopeId, ownerSlot)
|
|
||||||
return scopeSlotMap[key]
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun updateSlotType(slot: Int, type: SlotType) {
|
private fun updateSlotType(slot: Int, type: SlotType) {
|
||||||
if (forcedObjSlots.contains(slot) && type != SlotType.OBJ) return
|
if (forcedObjSlots.contains(slot) && type != SlotType.OBJ) return
|
||||||
if (type == SlotType.UNKNOWN) {
|
if (type == SlotType.UNKNOWN) {
|
||||||
|
|||||||
@ -50,7 +50,7 @@ class BytecodeStatement private constructor(
|
|||||||
?: scope.parent?.get(name)
|
?: scope.parent?.get(name)
|
||||||
?: scope.get(name)
|
?: scope.get(name)
|
||||||
?: continue
|
?: continue
|
||||||
val value = if (record.type == ObjRecord.Type.Delegated || record.type == ObjRecord.Type.Property || record.value is net.sergeych.lyng.obj.ObjProperty) {
|
val value = if (record.type == ObjRecord.Type.Delegated || record.type == ObjRecord.Type.Property) {
|
||||||
scope.resolve(record, name)
|
scope.resolve(record, name)
|
||||||
} else {
|
} else {
|
||||||
record.value
|
record.value
|
||||||
|
|||||||
@ -85,9 +85,6 @@ class CmdNop : Cmd() {
|
|||||||
class CmdMoveObj(internal val src: Int, internal val dst: Int) : Cmd() {
|
class CmdMoveObj(internal val src: Int, internal val dst: Int) : Cmd() {
|
||||||
override suspend fun perform(frame: CmdFrame) {
|
override suspend fun perform(frame: CmdFrame) {
|
||||||
val value = frame.slotToObj(src)
|
val value = frame.slotToObj(src)
|
||||||
if (frame.writeThroughPropertyLikeSlot(dst, value)) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if (frame.shouldBypassImmutableWrite(dst)) {
|
if (frame.shouldBypassImmutableWrite(dst)) {
|
||||||
frame.setObjUnchecked(dst, value)
|
frame.setObjUnchecked(dst, value)
|
||||||
} else {
|
} else {
|
||||||
@ -100,9 +97,6 @@ class CmdMoveObj(internal val src: Int, internal val dst: Int) : Cmd() {
|
|||||||
class CmdMoveInt(internal val src: Int, internal val dst: Int) : Cmd() {
|
class CmdMoveInt(internal val src: Int, internal val dst: Int) : Cmd() {
|
||||||
override suspend fun perform(frame: CmdFrame) {
|
override suspend fun perform(frame: CmdFrame) {
|
||||||
val value = frame.getInt(src)
|
val value = frame.getInt(src)
|
||||||
if (frame.writeThroughPropertyLikeSlot(dst, ObjInt.of(value))) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if (frame.shouldBypassImmutableWrite(dst)) {
|
if (frame.shouldBypassImmutableWrite(dst)) {
|
||||||
frame.setIntUnchecked(dst, value)
|
frame.setIntUnchecked(dst, value)
|
||||||
} else {
|
} else {
|
||||||
@ -123,9 +117,6 @@ class CmdMoveIntLocal(internal val src: Int, internal val dst: Int) : Cmd() {
|
|||||||
class CmdMoveReal(internal val src: Int, internal val dst: Int) : Cmd() {
|
class CmdMoveReal(internal val src: Int, internal val dst: Int) : Cmd() {
|
||||||
override suspend fun perform(frame: CmdFrame) {
|
override suspend fun perform(frame: CmdFrame) {
|
||||||
val value = frame.getReal(src)
|
val value = frame.getReal(src)
|
||||||
if (frame.writeThroughPropertyLikeSlot(dst, ObjReal.of(value))) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if (frame.shouldBypassImmutableWrite(dst)) {
|
if (frame.shouldBypassImmutableWrite(dst)) {
|
||||||
frame.setRealUnchecked(dst, value)
|
frame.setRealUnchecked(dst, value)
|
||||||
} else {
|
} else {
|
||||||
@ -146,9 +137,6 @@ class CmdMoveRealLocal(internal val src: Int, internal val dst: Int) : Cmd() {
|
|||||||
class CmdMoveBool(internal val src: Int, internal val dst: Int) : Cmd() {
|
class CmdMoveBool(internal val src: Int, internal val dst: Int) : Cmd() {
|
||||||
override suspend fun perform(frame: CmdFrame) {
|
override suspend fun perform(frame: CmdFrame) {
|
||||||
val value = frame.getBool(src)
|
val value = frame.getBool(src)
|
||||||
if (frame.writeThroughPropertyLikeSlot(dst, if (value) ObjTrue else ObjFalse)) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if (frame.shouldBypassImmutableWrite(dst)) {
|
if (frame.shouldBypassImmutableWrite(dst)) {
|
||||||
frame.setBoolUnchecked(dst, value)
|
frame.setBoolUnchecked(dst, value)
|
||||||
} else {
|
} else {
|
||||||
@ -2607,7 +2595,7 @@ private fun buildFunctionCaptureRecords(frame: CmdFrame, captureNames: List<Stri
|
|||||||
}
|
}
|
||||||
val scoped = frame.scope.chainLookupIgnoreClosure(name, followClosure = true) ?: frame.scope.get(name)
|
val scoped = frame.scope.chainLookupIgnoreClosure(name, followClosure = true) ?: frame.scope.get(name)
|
||||||
if (scoped != null) {
|
if (scoped != null) {
|
||||||
records += scoped
|
records += ObjRecord(RecordSlotRef(scoped), isMutable = scoped.isMutable)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
frame.ensureScope().raiseSymbolNotFound("capture $name not found")
|
frame.ensureScope().raiseSymbolNotFound("capture $name not found")
|
||||||
@ -2870,7 +2858,7 @@ class CmdDeclInstanceProperty(internal val constId: Int, internal val slot: Int)
|
|||||||
val decl = frame.fn.constants[constId] as? BytecodeConst.InstancePropertyDecl
|
val decl = frame.fn.constants[constId] as? BytecodeConst.InstancePropertyDecl
|
||||||
?: error("DECL_INSTANCE_PROPERTY expects InstancePropertyDecl at $constId")
|
?: error("DECL_INSTANCE_PROPERTY expects InstancePropertyDecl at $constId")
|
||||||
val scope = frame.ensureScope()
|
val scope = frame.ensureScope()
|
||||||
val prop = frame.storedSlotObj(slot)
|
val prop = frame.slotToObj(slot)
|
||||||
scope.addItem(
|
scope.addItem(
|
||||||
decl.name,
|
decl.name,
|
||||||
decl.isMutable,
|
decl.isMutable,
|
||||||
@ -3736,7 +3724,7 @@ class BytecodeLambdaCallable(
|
|||||||
?: context.parent?.get(name)
|
?: context.parent?.get(name)
|
||||||
?: context.get(name)
|
?: context.get(name)
|
||||||
?: continue
|
?: continue
|
||||||
val value = if (record.type == ObjRecord.Type.Delegated || record.type == ObjRecord.Type.Property || record.value is ObjProperty) {
|
val value = if (record.type == ObjRecord.Type.Delegated || record.type == ObjRecord.Type.Property) {
|
||||||
context.resolve(record, name)
|
context.resolve(record, name)
|
||||||
} else {
|
} else {
|
||||||
record.value
|
record.value
|
||||||
@ -3829,29 +3817,7 @@ class CmdFrame(
|
|||||||
}
|
}
|
||||||
|
|
||||||
internal fun getLocalSlotTypeCode(localIndex: Int): Byte = frame.getSlotTypeCode(localIndex)
|
internal fun getLocalSlotTypeCode(localIndex: Int): Byte = frame.getSlotTypeCode(localIndex)
|
||||||
internal fun readLocalObj(localIndex: Int): Obj {
|
internal fun readLocalObj(localIndex: Int): Obj = localSlotToObj(localIndex)
|
||||||
return when (frame.getSlotTypeCode(localIndex)) {
|
|
||||||
SlotType.INT.code -> ObjInt.of(frame.getInt(localIndex))
|
|
||||||
SlotType.REAL.code -> ObjReal.of(frame.getReal(localIndex))
|
|
||||||
SlotType.BOOL.code -> if (frame.getBool(localIndex)) ObjTrue else ObjFalse
|
|
||||||
SlotType.OBJ.code -> {
|
|
||||||
val obj = frame.getObj(localIndex)
|
|
||||||
when (obj) {
|
|
||||||
is FrameSlotRef -> obj.read()
|
|
||||||
is RecordSlotRef -> obj.read()
|
|
||||||
else -> obj
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else -> {
|
|
||||||
val obj = frame.getObj(localIndex)
|
|
||||||
when (obj) {
|
|
||||||
is FrameSlotRef -> obj.read()
|
|
||||||
is RecordSlotRef -> obj.read()
|
|
||||||
else -> obj
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
internal fun isFastLocalSlot(slot: Int): Boolean {
|
internal fun isFastLocalSlot(slot: Int): Boolean {
|
||||||
if (slot < fn.scopeSlotCount) return false
|
if (slot < fn.scopeSlotCount) return false
|
||||||
val localIndex = slot - fn.scopeSlotCount
|
val localIndex = slot - fn.scopeSlotCount
|
||||||
@ -4296,7 +4262,7 @@ class CmdFrame(
|
|||||||
return if (slot < fn.scopeSlotCount) {
|
return if (slot < fn.scopeSlotCount) {
|
||||||
getScopeSlotValue(slot)
|
getScopeSlotValue(slot)
|
||||||
} else {
|
} else {
|
||||||
readLocalSlotValue(slot - fn.scopeSlotCount)
|
localSlotToObj(slot - fn.scopeSlotCount)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -4339,35 +4305,6 @@ class CmdFrame(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun writeThroughPropertyLikeSlot(slot: Int, value: Obj): Boolean {
|
|
||||||
if (slot < fn.scopeSlotCount) {
|
|
||||||
val target = scopeTarget(slot)
|
|
||||||
val index = ensureScopeSlot(target, slot)
|
|
||||||
val name = fn.scopeSlotNames.getOrNull(slot)
|
|
||||||
val record = resolveScopeSlotRecordForWrite(target, index, name)
|
|
||||||
if (name != null && record != null && (record.type == ObjRecord.Type.Delegated || record.type == ObjRecord.Type.Property || record.value is ObjProperty)) {
|
|
||||||
target.assign(record, name, value)
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
val localIndex = slot - fn.scopeSlotCount
|
|
||||||
val name = fn.localSlotNames.getOrNull(localIndex) ?: return false
|
|
||||||
val isCapture = fn.localSlotCaptures.getOrNull(localIndex) == true
|
|
||||||
val raw = frame.getRawObj(localIndex)
|
|
||||||
if (raw is RecordSlotRef) {
|
|
||||||
if (raw.write(scope, name, value)) return true
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if (!isCapture && raw !== ObjUnset && raw !is ObjProperty) return false
|
|
||||||
val record = scope.parent?.get(name) ?: scope.get(name) ?: return false
|
|
||||||
if (record.type != ObjRecord.Type.Delegated && record.type != ObjRecord.Type.Property && record.value !is ObjProperty) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
scope.assign(record, name, value)
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
suspend fun getInt(slot: Int): Long {
|
suspend fun getInt(slot: Int): Long {
|
||||||
return if (slot < fn.scopeSlotCount) {
|
return if (slot < fn.scopeSlotCount) {
|
||||||
getScopeSlotValue(slot).toLong()
|
getScopeSlotValue(slot).toLong()
|
||||||
@ -4377,7 +4314,14 @@ class CmdFrame(
|
|||||||
SlotType.INT.code -> frame.getInt(local)
|
SlotType.INT.code -> frame.getInt(local)
|
||||||
SlotType.REAL.code -> frame.getReal(local).toLong()
|
SlotType.REAL.code -> frame.getReal(local).toLong()
|
||||||
SlotType.BOOL.code -> if (frame.getBool(local)) 1L else 0L
|
SlotType.BOOL.code -> if (frame.getBool(local)) 1L else 0L
|
||||||
SlotType.OBJ.code -> readLocalSlotValue(local).toLong()
|
SlotType.OBJ.code -> {
|
||||||
|
val obj = frame.getObj(local)
|
||||||
|
when (obj) {
|
||||||
|
is FrameSlotRef -> obj.read().toLong()
|
||||||
|
is RecordSlotRef -> obj.read().toLong()
|
||||||
|
else -> obj.toLong()
|
||||||
|
}
|
||||||
|
}
|
||||||
else -> 0L
|
else -> 0L
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -4457,7 +4401,14 @@ class CmdFrame(
|
|||||||
SlotType.REAL.code -> frame.getReal(local)
|
SlotType.REAL.code -> frame.getReal(local)
|
||||||
SlotType.INT.code -> frame.getInt(local).toDouble()
|
SlotType.INT.code -> frame.getInt(local).toDouble()
|
||||||
SlotType.BOOL.code -> if (frame.getBool(local)) 1.0 else 0.0
|
SlotType.BOOL.code -> if (frame.getBool(local)) 1.0 else 0.0
|
||||||
SlotType.OBJ.code -> readLocalSlotValue(local).toDouble()
|
SlotType.OBJ.code -> {
|
||||||
|
val obj = frame.getObj(local)
|
||||||
|
when (obj) {
|
||||||
|
is FrameSlotRef -> obj.read().toDouble()
|
||||||
|
is RecordSlotRef -> obj.read().toDouble()
|
||||||
|
else -> obj.toDouble()
|
||||||
|
}
|
||||||
|
}
|
||||||
else -> 0.0
|
else -> 0.0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -4526,7 +4477,14 @@ class CmdFrame(
|
|||||||
SlotType.BOOL.code -> frame.getBool(local)
|
SlotType.BOOL.code -> frame.getBool(local)
|
||||||
SlotType.INT.code -> frame.getInt(local) != 0L
|
SlotType.INT.code -> frame.getInt(local) != 0L
|
||||||
SlotType.REAL.code -> frame.getReal(local) != 0.0
|
SlotType.REAL.code -> frame.getReal(local) != 0.0
|
||||||
SlotType.OBJ.code -> readLocalSlotValue(local).toBool()
|
SlotType.OBJ.code -> {
|
||||||
|
val obj = frame.getObj(local)
|
||||||
|
when (obj) {
|
||||||
|
is FrameSlotRef -> obj.read().toBool()
|
||||||
|
is RecordSlotRef -> obj.read().toBool()
|
||||||
|
else -> obj.toBool()
|
||||||
|
}
|
||||||
|
}
|
||||||
else -> false
|
else -> false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -4638,42 +4596,21 @@ class CmdFrame(
|
|||||||
}
|
}
|
||||||
val local = slot - fn.scopeSlotCount
|
val local = slot - fn.scopeSlotCount
|
||||||
if (fn.localSlotCaptures.getOrNull(local) == true) {
|
if (fn.localSlotCaptures.getOrNull(local) == true) {
|
||||||
return readLocalSlotValue(local)
|
return localSlotToObj(local)
|
||||||
}
|
}
|
||||||
return when (frame.getSlotTypeCode(local)) {
|
return when (frame.getSlotTypeCode(local)) {
|
||||||
SlotType.INT.code -> ObjInt.of(frame.getInt(local))
|
SlotType.INT.code -> ObjInt.of(frame.getInt(local))
|
||||||
SlotType.REAL.code -> ObjReal.of(frame.getReal(local))
|
SlotType.REAL.code -> ObjReal.of(frame.getReal(local))
|
||||||
SlotType.BOOL.code -> if (frame.getBool(local)) ObjTrue else ObjFalse
|
SlotType.BOOL.code -> if (frame.getBool(local)) ObjTrue else ObjFalse
|
||||||
SlotType.OBJ.code -> readLocalSlotValue(local)
|
SlotType.OBJ.code -> {
|
||||||
else -> readLocalSlotValue(local)
|
val obj = frame.getObj(local)
|
||||||
}
|
when (obj) {
|
||||||
}
|
is FrameSlotRef -> obj.read()
|
||||||
|
is RecordSlotRef -> obj.read()
|
||||||
fun storedSlotObj(slot: Int): Obj {
|
else -> obj
|
||||||
if (slot < fn.scopeSlotCount) {
|
}
|
||||||
val target = scopeTarget(slot)
|
|
||||||
val index = ensureScopeSlot(target, slot)
|
|
||||||
val record = target.getSlotRecord(index)
|
|
||||||
return when (val direct = record.value) {
|
|
||||||
is FrameSlotRef -> direct.read()
|
|
||||||
is RecordSlotRef -> direct.read()
|
|
||||||
is ScopeSlotRef -> direct.read()
|
|
||||||
else -> direct
|
|
||||||
}
|
}
|
||||||
}
|
else -> localSlotToObj(local)
|
||||||
val local = slot - fn.scopeSlotCount
|
|
||||||
return when (frame.getSlotTypeCode(local)) {
|
|
||||||
SlotType.INT.code -> ObjInt.of(frame.getInt(local))
|
|
||||||
SlotType.REAL.code -> ObjReal.of(frame.getReal(local))
|
|
||||||
SlotType.BOOL.code -> if (frame.getBool(local)) ObjTrue else ObjFalse
|
|
||||||
SlotType.OBJ.code, SlotType.UNKNOWN.code -> when (val raw = frame.getRawObj(local)) {
|
|
||||||
is FrameSlotRef -> raw.read()
|
|
||||||
is RecordSlotRef -> raw.read()
|
|
||||||
is ScopeSlotRef -> raw.read()
|
|
||||||
null -> ObjNull
|
|
||||||
else -> raw
|
|
||||||
}
|
|
||||||
else -> frame.getRawObj(local) ?: ObjNull
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -4829,8 +4766,7 @@ class CmdFrame(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun readLocalSlotValue(localIndex: Int): Obj {
|
private fun localSlotToObj(localIndex: Int): Obj {
|
||||||
val localName = fn.localSlotNames.getOrNull(localIndex)
|
|
||||||
return when (frame.getSlotTypeCode(localIndex)) {
|
return when (frame.getSlotTypeCode(localIndex)) {
|
||||||
SlotType.INT.code -> ObjInt.of(frame.getInt(localIndex))
|
SlotType.INT.code -> ObjInt.of(frame.getInt(localIndex))
|
||||||
SlotType.REAL.code -> ObjReal.of(frame.getReal(localIndex))
|
SlotType.REAL.code -> ObjReal.of(frame.getReal(localIndex))
|
||||||
@ -4839,9 +4775,7 @@ class CmdFrame(
|
|||||||
val obj = frame.getObj(localIndex)
|
val obj = frame.getObj(localIndex)
|
||||||
when (obj) {
|
when (obj) {
|
||||||
is FrameSlotRef -> obj.read()
|
is FrameSlotRef -> obj.read()
|
||||||
is RecordSlotRef -> obj.read(scope, localName)
|
is RecordSlotRef -> obj.read()
|
||||||
is ObjProperty -> resolvePropertyLikeLocal(localName, obj)
|
|
||||||
ObjUnset -> resolveUnsetLocal(localName)
|
|
||||||
else -> obj
|
else -> obj
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -4849,34 +4783,13 @@ class CmdFrame(
|
|||||||
val obj = frame.getObj(localIndex)
|
val obj = frame.getObj(localIndex)
|
||||||
when (obj) {
|
when (obj) {
|
||||||
is FrameSlotRef -> obj.read()
|
is FrameSlotRef -> obj.read()
|
||||||
is RecordSlotRef -> obj.read(scope, localName)
|
is RecordSlotRef -> obj.read()
|
||||||
is ObjProperty -> resolvePropertyLikeLocal(localName, obj)
|
|
||||||
ObjUnset -> resolveUnsetLocal(localName)
|
|
||||||
else -> obj
|
else -> obj
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun resolvePropertyLikeLocal(localName: String?, property: ObjProperty): Obj {
|
|
||||||
if (localName != null) {
|
|
||||||
val record = scope.parent?.get(localName) ?: scope.get(localName)
|
|
||||||
if (record != null && (record.type == ObjRecord.Type.Delegated || record.type == ObjRecord.Type.Property || record.value is ObjProperty)) {
|
|
||||||
return scope.resolve(record, localName)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return property.callGetter(scope, scope.thisObj)
|
|
||||||
}
|
|
||||||
|
|
||||||
private suspend fun resolveUnsetLocal(localName: String?): Obj {
|
|
||||||
if (localName == null) return ObjUnset
|
|
||||||
val record = scope.parent?.get(localName) ?: scope.get(localName) ?: return ObjUnset
|
|
||||||
if (record.type == ObjRecord.Type.Delegated || record.type == ObjRecord.Type.Property || record.value is ObjProperty) {
|
|
||||||
return scope.resolve(record, localName)
|
|
||||||
}
|
|
||||||
return record.value
|
|
||||||
}
|
|
||||||
|
|
||||||
private suspend fun getScopeSlotValue(slot: Int): Obj {
|
private suspend fun getScopeSlotValue(slot: Int): Obj {
|
||||||
val target = scopeTarget(slot)
|
val target = scopeTarget(slot)
|
||||||
val name = fn.scopeSlotNames[slot]
|
val name = fn.scopeSlotNames[slot]
|
||||||
@ -4965,33 +4878,16 @@ class CmdFrame(
|
|||||||
private suspend fun setScopeSlotValueAtAddr(addrSlot: Int, value: Obj) {
|
private suspend fun setScopeSlotValueAtAddr(addrSlot: Int, value: Obj) {
|
||||||
val target = addrScopes[addrSlot] ?: error("Address slot $addrSlot is not resolved")
|
val target = addrScopes[addrSlot] ?: error("Address slot $addrSlot is not resolved")
|
||||||
val index = addrIndices[addrSlot]
|
val index = addrIndices[addrSlot]
|
||||||
|
val record = target.getSlotRecord(index)
|
||||||
val slotId = addrScopeSlots[addrSlot]
|
val slotId = addrScopeSlots[addrSlot]
|
||||||
val name = fn.scopeSlotNames.getOrNull(slotId)
|
val name = fn.scopeSlotNames.getOrNull(slotId)
|
||||||
val record = resolveScopeSlotRecordForWrite(target, index, name)
|
if (name != null && (record.type == ObjRecord.Type.Delegated || record.type == ObjRecord.Type.Property || record.value is ObjProperty)) {
|
||||||
if (name != null && record != null && (record.type == ObjRecord.Type.Delegated || record.type == ObjRecord.Type.Property || record.value is ObjProperty)) {
|
|
||||||
target.assign(record, name, value)
|
target.assign(record, name, value)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
target.setSlotValue(index, value)
|
target.setSlotValue(index, value)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun resolveScopeSlotRecordForWrite(target: Scope, index: Int, name: String?): ObjRecord? {
|
|
||||||
val record = target.getSlotRecord(index)
|
|
||||||
if (name == null) return record
|
|
||||||
if (record.type == ObjRecord.Type.Delegated || record.type == ObjRecord.Type.Property || record.value is ObjProperty) {
|
|
||||||
return record
|
|
||||||
}
|
|
||||||
if (record.value !== ObjUnset && record.memberName == null) {
|
|
||||||
return record
|
|
||||||
}
|
|
||||||
val resolved = target.get(name) ?: return record
|
|
||||||
if (resolved.value !== ObjUnset || resolved.type == ObjRecord.Type.Delegated || resolved.type == ObjRecord.Type.Property || resolved.value is ObjProperty) {
|
|
||||||
target.updateSlotFor(name, resolved)
|
|
||||||
return resolved
|
|
||||||
}
|
|
||||||
return record
|
|
||||||
}
|
|
||||||
|
|
||||||
internal fun ensureScopeSlot(target: Scope, slot: Int): Int {
|
internal fun ensureScopeSlot(target: Scope, slot: Int): Int {
|
||||||
val name = fn.scopeSlotNames[slot]
|
val name = fn.scopeSlotNames[slot]
|
||||||
if (name != null) {
|
if (name != null) {
|
||||||
|
|||||||
@ -31,7 +31,7 @@ internal suspend fun seedFrameLocalsFromScope(frame: CmdFrame, scope: Scope) {
|
|||||||
val record = scope.getLocalRecordDirect(name)
|
val record = scope.getLocalRecordDirect(name)
|
||||||
?: scope.chainLookupIgnoreClosure(name, followClosure = true)
|
?: scope.chainLookupIgnoreClosure(name, followClosure = true)
|
||||||
?: continue
|
?: continue
|
||||||
val value = if (record.type == ObjRecord.Type.Delegated || record.type == ObjRecord.Type.Property || record.value is net.sergeych.lyng.obj.ObjProperty) {
|
val value = if (record.type == ObjRecord.Type.Delegated || record.type == ObjRecord.Type.Property) {
|
||||||
scope.resolve(record, name)
|
scope.resolve(record, name)
|
||||||
} else {
|
} else {
|
||||||
record.value
|
record.value
|
||||||
|
|||||||
@ -591,14 +591,11 @@ open class Obj {
|
|||||||
return obj.copy(value = res, type = ObjRecord.Type.Other)
|
return obj.copy(value = res, type = ObjRecord.Type.Other)
|
||||||
}
|
}
|
||||||
val value = obj.value
|
val value = obj.value
|
||||||
if (value is ObjProperty) {
|
if (value is ObjProperty || obj.type == ObjRecord.Type.Property) {
|
||||||
val res = value.callGetter(scope, this, decl)
|
val prop = (value as? ObjProperty)
|
||||||
return obj.copy(value = res, type = ObjRecord.Type.Other)
|
?: scope.raiseError("Expected ObjProperty for property member $name, got ${value::class}")
|
||||||
}
|
val res = prop.callGetter(scope, this, decl)
|
||||||
if (obj.type == ObjRecord.Type.Property) {
|
return ObjRecord(res, obj.isMutable)
|
||||||
// Some runtime paths cache the resolved property value back into the record.
|
|
||||||
// Treat that as an already-resolved read result instead of trying to call a getter again.
|
|
||||||
return obj.copy(type = ObjRecord.Type.Other)
|
|
||||||
}
|
}
|
||||||
val caller = scope.currentClassCtx
|
val caller = scope.currentClassCtx
|
||||||
// Check visibility for non-property members here if they weren't checked before
|
// Check visibility for non-property members here if they weren't checked before
|
||||||
|
|||||||
@ -18,14 +18,8 @@
|
|||||||
package net.sergeych.lyng
|
package net.sergeych.lyng
|
||||||
|
|
||||||
import kotlinx.coroutines.test.runTest
|
import kotlinx.coroutines.test.runTest
|
||||||
import net.sergeych.lyng.bridge.bind
|
|
||||||
import net.sergeych.lyng.obj.ObjRecord
|
|
||||||
import net.sergeych.lyng.bridge.data
|
|
||||||
import net.sergeych.lyng.bridge.bindGlobalVar
|
import net.sergeych.lyng.bridge.bindGlobalVar
|
||||||
import net.sergeych.lyng.bridge.globalBinder
|
import net.sergeych.lyng.bridge.globalBinder
|
||||||
import net.sergeych.lyng.obj.ObjFalse
|
|
||||||
import net.sergeych.lyng.obj.ObjInstance
|
|
||||||
import net.sergeych.lyng.obj.ObjTrue
|
|
||||||
import kotlin.test.Test
|
import kotlin.test.Test
|
||||||
import kotlin.test.assertEquals
|
import kotlin.test.assertEquals
|
||||||
|
|
||||||
@ -55,97 +49,4 @@ class GlobalPropertyCaptureRegressionTest {
|
|||||||
|
|
||||||
assertEquals(2.0, x, "bound extern var should stay live inside function bodies")
|
assertEquals(2.0, x, "bound extern var should stay live inside function bodies")
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
|
||||||
fun externGlobalVarShouldStayLiveWhenScriptRunsInChildScope() = runTest {
|
|
||||||
val base = Script.newScope() as ModuleScope
|
|
||||||
var x = 1.0
|
|
||||||
|
|
||||||
base.eval("extern var X: Real")
|
|
||||||
base.globalBinder().bindGlobalVar(
|
|
||||||
name = "X",
|
|
||||||
get = { x },
|
|
||||||
set = { x = it }
|
|
||||||
)
|
|
||||||
|
|
||||||
val child = base.createChildScope()
|
|
||||||
child.eval(
|
|
||||||
Source(
|
|
||||||
"child-scope-probe",
|
|
||||||
"""
|
|
||||||
fun main() {
|
|
||||||
X = X + 1.0
|
|
||||||
}
|
|
||||||
""".trimIndent()
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
val mainRecord = child["main"]
|
|
||||||
check(mainRecord?.type == ObjRecord.Type.Fun)
|
|
||||||
child.eval("main()")
|
|
||||||
|
|
||||||
assertEquals(2.0, x, "bound extern var should stay live in child-scope execution")
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun externGlobalVarShouldStayLiveAfterExternClassPropertyBranchInChildScope() = runTest {
|
|
||||||
val base = Script.newScope() as ModuleScope
|
|
||||||
var x = 3.0
|
|
||||||
|
|
||||||
base.eval(
|
|
||||||
"""
|
|
||||||
extern var X: Real
|
|
||||||
|
|
||||||
class ChoiceInputResult {
|
|
||||||
extern val isSkip: Bool
|
|
||||||
}
|
|
||||||
|
|
||||||
extern fun requestChoice(): ChoiceInputResult
|
|
||||||
""".trimIndent()
|
|
||||||
)
|
|
||||||
|
|
||||||
base.bind("ChoiceInputResult") {
|
|
||||||
addVal("isSkip") {
|
|
||||||
if (thisObjData<ChoicePayload>().isSkip) ObjTrue else ObjFalse
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
base.globalBinder().bindGlobalVar(
|
|
||||||
name = "X",
|
|
||||||
get = { x },
|
|
||||||
set = { x = it }
|
|
||||||
)
|
|
||||||
|
|
||||||
base.globalBinder().bindGlobalFunRaw("requestChoice") { _, _ ->
|
|
||||||
val instance = base.requireClass("ChoiceInputResult").callOn(base.createChildScope()) as ObjInstance
|
|
||||||
instance.data = ChoicePayload(isSkip = true)
|
|
||||||
instance
|
|
||||||
}
|
|
||||||
|
|
||||||
val child = base.createChildScope()
|
|
||||||
child.eval(
|
|
||||||
"""
|
|
||||||
fun main() {
|
|
||||||
val c: ChoiceInputResult = requestChoice()
|
|
||||||
if (c.isSkip) {
|
|
||||||
X = 77.0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
""".trimIndent()
|
|
||||||
)
|
|
||||||
|
|
||||||
child.eval("main()")
|
|
||||||
|
|
||||||
assertEquals(77.0, x, "bound extern var should stay live after extern class property branch in child scope")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private data class ChoicePayload(
|
|
||||||
val isSkip: Boolean,
|
|
||||||
)
|
|
||||||
|
|
||||||
@Suppress("UNCHECKED_CAST")
|
|
||||||
private fun <T> ScopeFacade.thisObjData(): T {
|
|
||||||
val instance = thisObj as? ObjInstance ?: raiseClassCastError("Expected result object instance")
|
|
||||||
return instance.data as? T ?: raiseIllegalState("Bridge payload is not initialized")
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,76 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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
|
|
||||||
|
|
||||||
import kotlinx.coroutines.test.runTest
|
|
||||||
import kotlin.test.Test
|
|
||||||
|
|
||||||
class ComplexModuleTest {
|
|
||||||
@Test
|
|
||||||
fun testComplexArithmeticAndInterop() = runTest {
|
|
||||||
val scope = Script.newScope()
|
|
||||||
scope.eval(
|
|
||||||
"""
|
|
||||||
import lyng.complex
|
|
||||||
|
|
||||||
assertEquals(Complex(0.0, 2.0), 2.i)
|
|
||||||
assertEquals(Complex(2.0, 0.0), 2.re)
|
|
||||||
assertEquals(Complex(1.0, 2.0), 1 + 2.i)
|
|
||||||
assertEquals(Complex(1.5, 2.0), 1.5 + 2.i)
|
|
||||||
assertEquals(Complex(3.0, 2.0), 1 + Complex(2.0, 2.0))
|
|
||||||
val product: Complex = Complex(1.0, 2.0) * Complex(3.0, -1.0)
|
|
||||||
assertEquals(5.0, product.re)
|
|
||||||
assertEquals(5.0, product.im)
|
|
||||||
val quotient: Complex = Complex(5.0, 5.0) / Complex(3.0, -1.0)
|
|
||||||
assertEquals(1.0, quotient.re)
|
|
||||||
assertEquals(2.0, quotient.im)
|
|
||||||
assertEquals(Complex(1.0, -2.0), Complex(1.0, 2.0).conjugate)
|
|
||||||
""".trimIndent()
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun testComplexMemberMathFunctions() = runTest {
|
|
||||||
val scope = Script.newScope()
|
|
||||||
scope.eval(
|
|
||||||
"""
|
|
||||||
import lyng.complex
|
|
||||||
|
|
||||||
val eps = 1e-9
|
|
||||||
|
|
||||||
val eipi = Complex.imaginary(π).exp()
|
|
||||||
assert(abs(eipi.re + 1.0) < eps)
|
|
||||||
assert(abs(eipi.im) < eps)
|
|
||||||
|
|
||||||
val iy: Complex = 2.i
|
|
||||||
val sinIy = iy.sin()
|
|
||||||
assert(abs(sinIy.re) < eps)
|
|
||||||
assert(abs(sinIy.im - sinh(2.0)) < eps)
|
|
||||||
|
|
||||||
val minusOne: Complex = (-1).re
|
|
||||||
val root = minusOne.sqrt()
|
|
||||||
assert(abs(root.re) < eps)
|
|
||||||
assert(abs(root.im - 1.0) < eps)
|
|
||||||
|
|
||||||
val unitLeft: Complex = cis(π)
|
|
||||||
assert(abs(unitLeft.re + 1.0) < eps)
|
|
||||||
assert(abs(unitLeft.im) < eps)
|
|
||||||
""".trimIndent()
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,158 +0,0 @@
|
|||||||
package lyng.complex
|
|
||||||
|
|
||||||
import lyng.operators
|
|
||||||
|
|
||||||
/*
|
|
||||||
Complex number backed by `Real` components.
|
|
||||||
|
|
||||||
This module intentionally keeps the storage model simple:
|
|
||||||
- `re`: real part
|
|
||||||
- `im`: imaginary part
|
|
||||||
|
|
||||||
The algebra is implemented in pure Lyng and interoperates with `Int` and `Real`
|
|
||||||
through `lyng.operators`, so expressions like `1 + 2.i` and `0.5 * (1 + 2.i)` work.
|
|
||||||
|
|
||||||
For transcendental functions, use the member-style API for now:
|
|
||||||
- `z.exp()`
|
|
||||||
- `z.ln()`
|
|
||||||
- `z.sin()`
|
|
||||||
- `z.cos()`
|
|
||||||
- `z.tan()`
|
|
||||||
- `z.sqrt()`
|
|
||||||
|
|
||||||
This avoids collisions with the built-in real-valued top-level math functions
|
|
||||||
such as `exp(x)` and `sin(x)`.
|
|
||||||
*/
|
|
||||||
class Complex(val real: Real, val imag: Real = 0.0) {
|
|
||||||
val re get() = real
|
|
||||||
val im get() = imag
|
|
||||||
|
|
||||||
fun plus(other: Complex): Complex {
|
|
||||||
val ar = real
|
|
||||||
val ai = imag
|
|
||||||
val br = other.real
|
|
||||||
val bi = other.imag
|
|
||||||
val realPart = ar + br
|
|
||||||
val imagPart = ai + bi
|
|
||||||
Complex(realPart, imagPart)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun minus(other: Complex): Complex {
|
|
||||||
val ar = real
|
|
||||||
val ai = imag
|
|
||||||
val br = other.real
|
|
||||||
val bi = other.imag
|
|
||||||
val realPart = ar - br
|
|
||||||
val imagPart = ai - bi
|
|
||||||
Complex(realPart, imagPart)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun mul(other: Complex): Complex {
|
|
||||||
val ar = real
|
|
||||||
val ai = imag
|
|
||||||
val br = other.real
|
|
||||||
val bi = other.imag
|
|
||||||
val realPart = ar * br - ai * bi
|
|
||||||
val imagPart = ar * bi + ai * br
|
|
||||||
Complex(realPart, imagPart)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun div(other: Complex): Complex {
|
|
||||||
val ar = real
|
|
||||||
val ai = imag
|
|
||||||
val br = other.real
|
|
||||||
val bi = other.imag
|
|
||||||
val denominator = br * br + bi * bi
|
|
||||||
val realPart = (ar * br + ai * bi) / denominator
|
|
||||||
val imagPart = (ai * br - ar * bi) / denominator
|
|
||||||
Complex(realPart, imagPart)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun negate(): Complex = Complex(-real, -imag)
|
|
||||||
|
|
||||||
val conjugate get() = Complex(real, -imag)
|
|
||||||
val magnitude2 get() = real * real + imag * imag
|
|
||||||
val magnitude get() = Math.sqrt(magnitude2)
|
|
||||||
|
|
||||||
val phase: Real
|
|
||||||
get() = if (real > 0.0) {
|
|
||||||
Math.atan(imag / real)
|
|
||||||
} else if (real < 0.0 && imag >= 0.0) {
|
|
||||||
Math.atan(imag / real) + π
|
|
||||||
} else if (real < 0.0 && imag < 0.0) {
|
|
||||||
Math.atan(imag / real) - π
|
|
||||||
} else if (real == 0.0 && imag > 0.0) {
|
|
||||||
π / 2.0
|
|
||||||
} else if (real == 0.0 && imag < 0.0) {
|
|
||||||
-π / 2.0
|
|
||||||
} else {
|
|
||||||
0.0
|
|
||||||
}
|
|
||||||
|
|
||||||
fun exp(): Complex {
|
|
||||||
val scale = Math.exp(real)
|
|
||||||
Complex(scale * Math.cos(imag), scale * Math.sin(imag))
|
|
||||||
}
|
|
||||||
|
|
||||||
fun ln(): Complex = Complex(Math.ln(magnitude), phase)
|
|
||||||
|
|
||||||
fun sin(): Complex =
|
|
||||||
Complex(
|
|
||||||
Math.sin(real) * Math.cosh(imag),
|
|
||||||
Math.cos(real) * Math.sinh(imag)
|
|
||||||
)
|
|
||||||
|
|
||||||
fun cos(): Complex =
|
|
||||||
Complex(
|
|
||||||
Math.cos(real) * Math.cosh(imag),
|
|
||||||
-Math.sin(real) * Math.sinh(imag)
|
|
||||||
)
|
|
||||||
|
|
||||||
fun tan(): Complex = sin() / cos()
|
|
||||||
|
|
||||||
fun sqrt(): Complex = if (imag == 0.0 && real >= 0.0) {
|
|
||||||
Complex(Math.sqrt(real), 0.0)
|
|
||||||
} else {
|
|
||||||
val radius = magnitude
|
|
||||||
val realPart = Math.sqrt((radius + real) / 2.0)
|
|
||||||
val imagPart = Math.sqrt((radius - real) / 2.0)
|
|
||||||
Complex(realPart, if (imag < 0.0) -imagPart else imagPart)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun toString() =
|
|
||||||
real.toString() +
|
|
||||||
(if (imag < 0.0) imag.toString() else "+" + imag.toString()) +
|
|
||||||
"i"
|
|
||||||
|
|
||||||
static fun fromInt(value: Int): Complex = Complex(value + 0.0, 0.0)
|
|
||||||
static fun fromReal(value: Real): Complex = Complex(value, 0.0)
|
|
||||||
static fun imaginary(value: Real): Complex = Complex(0.0, value)
|
|
||||||
static fun fromPolar(radius: Real, angle: Real): Complex =
|
|
||||||
Complex(radius * Math.cos(angle), radius * Math.sin(angle))
|
|
||||||
}
|
|
||||||
|
|
||||||
fun complex(re: Real, im: Real = 0.0): Complex = Complex(re, im)
|
|
||||||
fun cis(angle: Real): Complex = Complex.fromPolar(1.0, angle)
|
|
||||||
|
|
||||||
val Int.re: Complex get() = Complex.fromInt(this)
|
|
||||||
val Real.re: Complex get() = Complex.fromReal(this)
|
|
||||||
val Int.i: Complex get() = Complex.imaginary(this + 0.0)
|
|
||||||
val Real.i: Complex get() = Complex.imaginary(this)
|
|
||||||
|
|
||||||
OperatorInterop.register(
|
|
||||||
Int,
|
|
||||||
Complex,
|
|
||||||
Complex,
|
|
||||||
[BinaryOperator.Plus, BinaryOperator.Minus, BinaryOperator.Mul, BinaryOperator.Div],
|
|
||||||
{ x: Int -> Complex.fromInt(x) },
|
|
||||||
{ x: Complex -> x }
|
|
||||||
)
|
|
||||||
|
|
||||||
OperatorInterop.register(
|
|
||||||
Real,
|
|
||||||
Complex,
|
|
||||||
Complex,
|
|
||||||
[BinaryOperator.Plus, BinaryOperator.Minus, BinaryOperator.Mul, BinaryOperator.Div],
|
|
||||||
{ x: Real -> Complex.fromReal(x) },
|
|
||||||
{ x: Complex -> x }
|
|
||||||
)
|
|
||||||
Loading…
x
Reference in New Issue
Block a user