Compare commits
3 Commits
86e8b2e2bc
...
cd007050a8
| Author | SHA1 | Date | |
|---|---|---|---|
| cd007050a8 | |||
| 418b1ae2b6 | |||
| b0fb65a036 |
1
.gitignore
vendored
1
.gitignore
vendored
@ -27,3 +27,4 @@ debug.log
|
||||
/compile_jvm_output.txt
|
||||
/compile_metadata_output.txt
|
||||
test_output*.txt
|
||||
/site/src/version-template/lyng-version.js
|
||||
|
||||
82
docs/Complex.md
Normal file
82
docs/Complex.md
Normal file
@ -0,0 +1,82 @@
|
||||
# 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,6 +56,8 @@ Sources: `lynglib/src/commonMain/kotlin/net/sergeych/lyng/Script.kt`, `lynglib/s
|
||||
## 5. Additional Built-in Modules (import explicitly)
|
||||
- `import lyng.observable`
|
||||
- `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`
|
||||
- `Buffer`, `MutableBuffer`.
|
||||
- `import lyng.serialization`
|
||||
|
||||
@ -21,7 +21,7 @@ import org.jetbrains.kotlin.gradle.ExperimentalWasmDsl
|
||||
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
|
||||
|
||||
group = "net.sergeych"
|
||||
version = "1.5.2"
|
||||
version = "1.5.3-SNAPSHOT"
|
||||
|
||||
// Removed legacy buildscript classpath declarations; plugins are applied via the plugins DSL below
|
||||
|
||||
@ -149,6 +149,9 @@ abstract class GenerateLyngStdlib : DefaultTask() {
|
||||
val outBase = outputDir.get().asFile
|
||||
val targetDir = outBase.resolve(pkgPath)
|
||||
targetDir.mkdirs()
|
||||
targetDir.listFiles()
|
||||
?.filter { it.isFile && it.name.endsWith(".generated.kt") }
|
||||
?.forEach { it.delete() }
|
||||
|
||||
val srcDir = sourceDir.get().asFile
|
||||
val files = srcDir.walkTopDown()
|
||||
@ -156,34 +159,38 @@ abstract class GenerateLyngStdlib : DefaultTask() {
|
||||
.sortedBy { it.name }
|
||||
.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 {
|
||||
for (ch in s) when (ch) {
|
||||
'\\' -> append("\\\\")
|
||||
'"' -> append("\\\"")
|
||||
'$' -> append("\\$")
|
||||
'\n' -> append("\\n")
|
||||
'\r' -> {}
|
||||
'\t' -> append("\\t")
|
||||
else -> append(ch)
|
||||
}
|
||||
}
|
||||
val body = escapeForQuoted(content)
|
||||
|
||||
fun constantName(baseName: String): String {
|
||||
val parts = baseName.split(Regex("[^A-Za-z0-9]+")).filter { it.isNotEmpty() }
|
||||
if (parts.isEmpty()) return "moduleLyng"
|
||||
val head = parts.first().replaceFirstChar { it.lowercase() }
|
||||
val tail = parts.drop(1).joinToString("") { part ->
|
||||
part.replaceFirstChar { it.uppercase() }
|
||||
}
|
||||
return "${head}${tail}Lyng"
|
||||
}
|
||||
|
||||
for (file in files) {
|
||||
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 rootLyng = \"")
|
||||
sb.append("internal val ").append(constantName(file.nameWithoutExtension)).append(" = \"")
|
||||
sb.append(body)
|
||||
sb.append("\"\n")
|
||||
|
||||
targetDir.resolve("root_lyng.generated.kt").writeText(sb.toString())
|
||||
targetDir.resolve("${file.nameWithoutExtension}_lyng.generated.kt").writeText(sb.toString())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -8253,7 +8253,7 @@ class Compiler(
|
||||
?: context.parent?.get(localName)
|
||||
?: context.get(localName)
|
||||
?: continue
|
||||
val value = if (record.type == ObjRecord.Type.Delegated || record.type == ObjRecord.Type.Property) {
|
||||
val value = if (record.type == ObjRecord.Type.Delegated || record.type == ObjRecord.Type.Property || record.value is ObjProperty) {
|
||||
context.resolve(record, localName)
|
||||
} else {
|
||||
record.value
|
||||
|
||||
@ -167,6 +167,19 @@ 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 {
|
||||
val resolved = read()
|
||||
if (resolved === this) {
|
||||
@ -193,4 +206,18 @@ class RecordSlotRef(
|
||||
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,6 +27,7 @@ import net.sergeych.lyng.bytecode.CmdVm
|
||||
import net.sergeych.lyng.miniast.*
|
||||
import net.sergeych.lyng.obj.*
|
||||
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.observableLyng
|
||||
import net.sergeych.lyng.stdlib_included.operatorsLyng
|
||||
@ -615,12 +616,91 @@ class Script(
|
||||
type = type("lyng.Real")
|
||||
)
|
||||
getOrCreateNamespace("Math").apply {
|
||||
fun ensureFn(name: String, fn: suspend ScopeFacade.() -> Obj) {
|
||||
if (members.containsKey(name)) return
|
||||
addFn(name, code = fn)
|
||||
}
|
||||
addConstDoc(
|
||||
name = "PI",
|
||||
value = pi,
|
||||
doc = "The mathematical constant pi (π) in the Math namespace.",
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -759,6 +839,9 @@ class Script(
|
||||
module.eval(Source("lyng.decimal", decimalLyng))
|
||||
ObjBigDecimalSupport.bindTo(module)
|
||||
}
|
||||
addPackage("lyng.complex") { module ->
|
||||
module.eval(Source("lyng.complex", complexLyng))
|
||||
}
|
||||
addPackage("lyng.buffer") {
|
||||
it.addConstDoc(
|
||||
name = "Buffer",
|
||||
|
||||
@ -2396,7 +2396,8 @@ class BytecodeCompiler(
|
||||
builder.emit(Opcode.THROW, posId, msgSlot)
|
||||
return value
|
||||
}
|
||||
val slot = resolveSlot(localTarget)
|
||||
val slot = resolveCapturedOwnerScopeSlot(localTarget)
|
||||
?: resolveSlot(localTarget)
|
||||
?: resolveAssignableSlotByName(localTarget.name)?.first
|
||||
?: return null
|
||||
if (slot < scopeSlotCount && value.type != SlotType.UNKNOWN) {
|
||||
@ -2702,7 +2703,7 @@ class BytecodeCompiler(
|
||||
return CompiledValue(result, SlotType.OBJ)
|
||||
}
|
||||
if (localTarget.isDelegated) return compileEvalRef(ref)
|
||||
val slot = resolveSlot(localTarget) ?: return null
|
||||
val slot = resolveCapturedOwnerScopeSlot(localTarget) ?: resolveSlot(localTarget) ?: return null
|
||||
val targetType = slotTypes[slot] ?: SlotType.OBJ
|
||||
if (!localTarget.isMutable) {
|
||||
if (targetType != SlotType.OBJ && targetType != SlotType.UNKNOWN) return compileEvalRef(ref)
|
||||
@ -7630,6 +7631,13 @@ class BytecodeCompiler(
|
||||
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) {
|
||||
if (forcedObjSlots.contains(slot) && type != SlotType.OBJ) return
|
||||
if (type == SlotType.UNKNOWN) {
|
||||
|
||||
@ -50,7 +50,7 @@ class BytecodeStatement private constructor(
|
||||
?: scope.parent?.get(name)
|
||||
?: scope.get(name)
|
||||
?: continue
|
||||
val value = if (record.type == ObjRecord.Type.Delegated || record.type == ObjRecord.Type.Property) {
|
||||
val value = if (record.type == ObjRecord.Type.Delegated || record.type == ObjRecord.Type.Property || record.value is net.sergeych.lyng.obj.ObjProperty) {
|
||||
scope.resolve(record, name)
|
||||
} else {
|
||||
record.value
|
||||
|
||||
@ -85,6 +85,9 @@ class CmdNop : Cmd() {
|
||||
class CmdMoveObj(internal val src: Int, internal val dst: Int) : Cmd() {
|
||||
override suspend fun perform(frame: CmdFrame) {
|
||||
val value = frame.slotToObj(src)
|
||||
if (frame.writeThroughPropertyLikeSlot(dst, value)) {
|
||||
return
|
||||
}
|
||||
if (frame.shouldBypassImmutableWrite(dst)) {
|
||||
frame.setObjUnchecked(dst, value)
|
||||
} else {
|
||||
@ -97,6 +100,9 @@ class CmdMoveObj(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) {
|
||||
val value = frame.getInt(src)
|
||||
if (frame.writeThroughPropertyLikeSlot(dst, ObjInt.of(value))) {
|
||||
return
|
||||
}
|
||||
if (frame.shouldBypassImmutableWrite(dst)) {
|
||||
frame.setIntUnchecked(dst, value)
|
||||
} else {
|
||||
@ -117,6 +123,9 @@ class CmdMoveIntLocal(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) {
|
||||
val value = frame.getReal(src)
|
||||
if (frame.writeThroughPropertyLikeSlot(dst, ObjReal.of(value))) {
|
||||
return
|
||||
}
|
||||
if (frame.shouldBypassImmutableWrite(dst)) {
|
||||
frame.setRealUnchecked(dst, value)
|
||||
} else {
|
||||
@ -137,6 +146,9 @@ class CmdMoveRealLocal(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) {
|
||||
val value = frame.getBool(src)
|
||||
if (frame.writeThroughPropertyLikeSlot(dst, if (value) ObjTrue else ObjFalse)) {
|
||||
return
|
||||
}
|
||||
if (frame.shouldBypassImmutableWrite(dst)) {
|
||||
frame.setBoolUnchecked(dst, value)
|
||||
} else {
|
||||
@ -2595,7 +2607,7 @@ private fun buildFunctionCaptureRecords(frame: CmdFrame, captureNames: List<Stri
|
||||
}
|
||||
val scoped = frame.scope.chainLookupIgnoreClosure(name, followClosure = true) ?: frame.scope.get(name)
|
||||
if (scoped != null) {
|
||||
records += ObjRecord(RecordSlotRef(scoped), isMutable = scoped.isMutable)
|
||||
records += scoped
|
||||
continue
|
||||
}
|
||||
frame.ensureScope().raiseSymbolNotFound("capture $name not found")
|
||||
@ -2858,7 +2870,7 @@ class CmdDeclInstanceProperty(internal val constId: Int, internal val slot: Int)
|
||||
val decl = frame.fn.constants[constId] as? BytecodeConst.InstancePropertyDecl
|
||||
?: error("DECL_INSTANCE_PROPERTY expects InstancePropertyDecl at $constId")
|
||||
val scope = frame.ensureScope()
|
||||
val prop = frame.slotToObj(slot)
|
||||
val prop = frame.storedSlotObj(slot)
|
||||
scope.addItem(
|
||||
decl.name,
|
||||
decl.isMutable,
|
||||
@ -3724,7 +3736,7 @@ class BytecodeLambdaCallable(
|
||||
?: context.parent?.get(name)
|
||||
?: context.get(name)
|
||||
?: continue
|
||||
val value = if (record.type == ObjRecord.Type.Delegated || record.type == ObjRecord.Type.Property) {
|
||||
val value = if (record.type == ObjRecord.Type.Delegated || record.type == ObjRecord.Type.Property || record.value is ObjProperty) {
|
||||
context.resolve(record, name)
|
||||
} else {
|
||||
record.value
|
||||
@ -3817,7 +3829,29 @@ class CmdFrame(
|
||||
}
|
||||
|
||||
internal fun getLocalSlotTypeCode(localIndex: Int): Byte = frame.getSlotTypeCode(localIndex)
|
||||
internal fun readLocalObj(localIndex: Int): Obj = localSlotToObj(localIndex)
|
||||
internal fun readLocalObj(localIndex: Int): Obj {
|
||||
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 {
|
||||
if (slot < fn.scopeSlotCount) return false
|
||||
val localIndex = slot - fn.scopeSlotCount
|
||||
@ -4262,7 +4296,7 @@ class CmdFrame(
|
||||
return if (slot < fn.scopeSlotCount) {
|
||||
getScopeSlotValue(slot)
|
||||
} else {
|
||||
localSlotToObj(slot - fn.scopeSlotCount)
|
||||
readLocalSlotValue(slot - fn.scopeSlotCount)
|
||||
}
|
||||
}
|
||||
|
||||
@ -4305,6 +4339,35 @@ 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 {
|
||||
return if (slot < fn.scopeSlotCount) {
|
||||
getScopeSlotValue(slot).toLong()
|
||||
@ -4314,14 +4377,7 @@ class CmdFrame(
|
||||
SlotType.INT.code -> frame.getInt(local)
|
||||
SlotType.REAL.code -> frame.getReal(local).toLong()
|
||||
SlotType.BOOL.code -> if (frame.getBool(local)) 1L else 0L
|
||||
SlotType.OBJ.code -> {
|
||||
val obj = frame.getObj(local)
|
||||
when (obj) {
|
||||
is FrameSlotRef -> obj.read().toLong()
|
||||
is RecordSlotRef -> obj.read().toLong()
|
||||
else -> obj.toLong()
|
||||
}
|
||||
}
|
||||
SlotType.OBJ.code -> readLocalSlotValue(local).toLong()
|
||||
else -> 0L
|
||||
}
|
||||
}
|
||||
@ -4401,14 +4457,7 @@ class CmdFrame(
|
||||
SlotType.REAL.code -> frame.getReal(local)
|
||||
SlotType.INT.code -> frame.getInt(local).toDouble()
|
||||
SlotType.BOOL.code -> if (frame.getBool(local)) 1.0 else 0.0
|
||||
SlotType.OBJ.code -> {
|
||||
val obj = frame.getObj(local)
|
||||
when (obj) {
|
||||
is FrameSlotRef -> obj.read().toDouble()
|
||||
is RecordSlotRef -> obj.read().toDouble()
|
||||
else -> obj.toDouble()
|
||||
}
|
||||
}
|
||||
SlotType.OBJ.code -> readLocalSlotValue(local).toDouble()
|
||||
else -> 0.0
|
||||
}
|
||||
}
|
||||
@ -4477,14 +4526,7 @@ class CmdFrame(
|
||||
SlotType.BOOL.code -> frame.getBool(local)
|
||||
SlotType.INT.code -> frame.getInt(local) != 0L
|
||||
SlotType.REAL.code -> frame.getReal(local) != 0.0
|
||||
SlotType.OBJ.code -> {
|
||||
val obj = frame.getObj(local)
|
||||
when (obj) {
|
||||
is FrameSlotRef -> obj.read().toBool()
|
||||
is RecordSlotRef -> obj.read().toBool()
|
||||
else -> obj.toBool()
|
||||
}
|
||||
}
|
||||
SlotType.OBJ.code -> readLocalSlotValue(local).toBool()
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
@ -4596,21 +4638,42 @@ class CmdFrame(
|
||||
}
|
||||
val local = slot - fn.scopeSlotCount
|
||||
if (fn.localSlotCaptures.getOrNull(local) == true) {
|
||||
return localSlotToObj(local)
|
||||
return readLocalSlotValue(local)
|
||||
}
|
||||
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 -> {
|
||||
val obj = frame.getObj(local)
|
||||
when (obj) {
|
||||
is FrameSlotRef -> obj.read()
|
||||
is RecordSlotRef -> obj.read()
|
||||
else -> obj
|
||||
SlotType.OBJ.code -> readLocalSlotValue(local)
|
||||
else -> readLocalSlotValue(local)
|
||||
}
|
||||
}
|
||||
else -> localSlotToObj(local)
|
||||
|
||||
fun storedSlotObj(slot: Int): 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
|
||||
}
|
||||
}
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
@ -4766,7 +4829,8 @@ class CmdFrame(
|
||||
}
|
||||
}
|
||||
|
||||
private fun localSlotToObj(localIndex: Int): Obj {
|
||||
private suspend fun readLocalSlotValue(localIndex: Int): Obj {
|
||||
val localName = fn.localSlotNames.getOrNull(localIndex)
|
||||
return when (frame.getSlotTypeCode(localIndex)) {
|
||||
SlotType.INT.code -> ObjInt.of(frame.getInt(localIndex))
|
||||
SlotType.REAL.code -> ObjReal.of(frame.getReal(localIndex))
|
||||
@ -4775,7 +4839,9 @@ class CmdFrame(
|
||||
val obj = frame.getObj(localIndex)
|
||||
when (obj) {
|
||||
is FrameSlotRef -> obj.read()
|
||||
is RecordSlotRef -> obj.read()
|
||||
is RecordSlotRef -> obj.read(scope, localName)
|
||||
is ObjProperty -> resolvePropertyLikeLocal(localName, obj)
|
||||
ObjUnset -> resolveUnsetLocal(localName)
|
||||
else -> obj
|
||||
}
|
||||
}
|
||||
@ -4783,13 +4849,34 @@ class CmdFrame(
|
||||
val obj = frame.getObj(localIndex)
|
||||
when (obj) {
|
||||
is FrameSlotRef -> obj.read()
|
||||
is RecordSlotRef -> obj.read()
|
||||
is RecordSlotRef -> obj.read(scope, localName)
|
||||
is ObjProperty -> resolvePropertyLikeLocal(localName, obj)
|
||||
ObjUnset -> resolveUnsetLocal(localName)
|
||||
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 {
|
||||
val target = scopeTarget(slot)
|
||||
val name = fn.scopeSlotNames[slot]
|
||||
@ -4878,16 +4965,33 @@ class CmdFrame(
|
||||
private suspend fun setScopeSlotValueAtAddr(addrSlot: Int, value: Obj) {
|
||||
val target = addrScopes[addrSlot] ?: error("Address slot $addrSlot is not resolved")
|
||||
val index = addrIndices[addrSlot]
|
||||
val record = target.getSlotRecord(index)
|
||||
val slotId = addrScopeSlots[addrSlot]
|
||||
val name = fn.scopeSlotNames.getOrNull(slotId)
|
||||
if (name != null && (record.type == ObjRecord.Type.Delegated || record.type == ObjRecord.Type.Property || record.value is ObjProperty)) {
|
||||
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
|
||||
}
|
||||
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 {
|
||||
val name = fn.scopeSlotNames[slot]
|
||||
if (name != null) {
|
||||
|
||||
@ -31,7 +31,7 @@ internal suspend fun seedFrameLocalsFromScope(frame: CmdFrame, scope: Scope) {
|
||||
val record = scope.getLocalRecordDirect(name)
|
||||
?: scope.chainLookupIgnoreClosure(name, followClosure = true)
|
||||
?: continue
|
||||
val value = if (record.type == ObjRecord.Type.Delegated || record.type == ObjRecord.Type.Property) {
|
||||
val value = if (record.type == ObjRecord.Type.Delegated || record.type == ObjRecord.Type.Property || record.value is net.sergeych.lyng.obj.ObjProperty) {
|
||||
scope.resolve(record, name)
|
||||
} else {
|
||||
record.value
|
||||
|
||||
@ -591,11 +591,14 @@ open class Obj {
|
||||
return obj.copy(value = res, type = ObjRecord.Type.Other)
|
||||
}
|
||||
val value = obj.value
|
||||
if (value is ObjProperty || obj.type == ObjRecord.Type.Property) {
|
||||
val prop = (value as? ObjProperty)
|
||||
?: scope.raiseError("Expected ObjProperty for property member $name, got ${value::class}")
|
||||
val res = prop.callGetter(scope, this, decl)
|
||||
return ObjRecord(res, obj.isMutable)
|
||||
if (value is ObjProperty) {
|
||||
val res = value.callGetter(scope, this, decl)
|
||||
return obj.copy(value = res, type = ObjRecord.Type.Other)
|
||||
}
|
||||
if (obj.type == ObjRecord.Type.Property) {
|
||||
// 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
|
||||
// Check visibility for non-property members here if they weren't checked before
|
||||
|
||||
@ -18,8 +18,14 @@
|
||||
package net.sergeych.lyng
|
||||
|
||||
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.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.assertEquals
|
||||
|
||||
@ -49,4 +55,97 @@ class GlobalPropertyCaptureRegressionTest {
|
||||
|
||||
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")
|
||||
}
|
||||
|
||||
@ -0,0 +1,76 @@
|
||||
/*
|
||||
* 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()
|
||||
)
|
||||
}
|
||||
}
|
||||
158
lynglib/stdlib/lyng/complex.lyng
Normal file
158
lynglib/stdlib/lyng/complex.lyng
Normal file
@ -0,0 +1,158 @@
|
||||
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