From 8f66cd7680acd6ed7df3e206a95806e6345fdb03 Mon Sep 17 00:00:00 2001 From: sergeych Date: Thu, 16 Apr 2026 23:34:45 +0300 Subject: [PATCH] Fix lynglib binding regressions and green test suite --- .../kotlin/net/sergeych/lyng/Compiler.kt | 8 ++- .../sergeych/lyng/obj/ObjBindingSupport.kt | 62 +++++++++++++++++++ .../sergeych/lyng/obj/ObjComplexSupport.kt | 47 +++++--------- .../sergeych/lyng/obj/ObjDecimalSupport.kt | 6 +- .../net/sergeych/lyng/obj/ObjMatrixSupport.kt | 22 +++---- .../lyng/stdlib_included/decimal_lyng.kt | 9 +-- .../net/sergeych/lyng/DecimalModuleTest.kt | 2 - lynglib/stdlib/lyng/complex.lyng | 3 + lynglib/stdlib/lyng/matrix.lyng | 6 ++ 9 files changed, 106 insertions(+), 59 deletions(-) create mode 100644 lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjBindingSupport.kt diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Compiler.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Compiler.kt index ee67076..9157640 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Compiler.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Compiler.kt @@ -10679,8 +10679,12 @@ class Compiler( ) } if (getter != null || setter != null) { - val prop = ObjProperty(name, getter, setter) - val initStmt = if (!isAbstract) { + val prop = if (actualExtern) { + ObjProperty(name, null, null) + } else { + ObjProperty(name, getter, setter) + } + val initStmt = if (!isAbstract && !actualExtern) { val initStatement = InstancePropertyInitStatement( storageName = storageName, isMutable = isMutable, diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjBindingSupport.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjBindingSupport.kt new file mode 100644 index 0000000..a7a523b --- /dev/null +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjBindingSupport.kt @@ -0,0 +1,62 @@ +package net.sergeych.lyng.obj + +import net.sergeych.lyng.ScopeFacade +import net.sergeych.lyng.requiredArg + +internal fun ObjClass.bindClassFn(name: String, code: suspend ScopeFacade.() -> Obj) { + val callable = ObjExternCallable.fromBridge { code() } + val memberRecord = members[name] + val classScopeRecord = classScope?.objects?.get(name) + if (memberRecord != null) { + val methodId = ensureMethodIdForBridge(name, memberRecord) + val newRecord = memberRecord.copy( + value = callable, + type = ObjRecord.Type.Fun, + methodId = methodId, + isAbstract = false, + ) + replaceMemberForBridge(name, newRecord) + if (classScopeRecord != null) { + replaceClassScopeMemberForBridge(name, newRecord) + } + } else if (classScopeRecord != null) { + val methodId = ensureMethodIdForBridge(name, classScopeRecord) + replaceClassScopeMemberForBridge( + name, + classScopeRecord.copy( + value = callable, + type = ObjRecord.Type.Fun, + methodId = methodId, + isAbstract = false, + ) + ) + } else { + addClassFn(name, code = code) + } +} + +internal fun ObjClass.bindProperty( + name: String, + getter: (suspend ScopeFacade.() -> Obj)? = null, + setter: (suspend ScopeFacade.(Obj) -> Unit)? = null, +) { + val g = getter?.let { ObjExternCallable.fromBridge { it() } } + val s = setter?.let { ObjExternCallable.fromBridge { it(requiredArg(0)); ObjVoid } } + val prop = ObjProperty(name, g, s) + val existing = members[name] + if (existing != null) { + val newRecord = existing.copy( + value = prop, + type = ObjRecord.Type.Property, + methodId = ensureMethodIdForBridge(name, existing), + fieldId = null, + isAbstract = false, + ) + replaceMemberForBridge(name, newRecord) + if (classScope?.objects?.containsKey(name) == true) { + replaceClassScopeMemberForBridge(name, newRecord) + } + } else { + addProperty(name, getter = getter, setter = setter) + } +} diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjComplexSupport.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjComplexSupport.kt index 06bb393..b778f0d 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjComplexSupport.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjComplexSupport.kt @@ -18,13 +18,10 @@ package net.sergeych.lyng.obj import net.sergeych.lyng.* -import net.sergeych.lyng.miniast.addPropertyDoc -import net.sergeych.lyng.miniast.type import net.sergeych.lyng.requiredArg object ObjComplexSupport { private object BoundMarker - private val complexTypeDecl = TypeDecl.Simple("lyng.complex.Complex", false) suspend fun bindTo(module: ModuleScope) { val complexClass = module.requireClass("Complex") @@ -33,36 +30,20 @@ object ObjComplexSupport { val decimalModule = module.currentImportProvider.createModuleScope(module.pos, "lyng.decimal") val decimalClass = decimalModule.requireClass("Decimal") - - decimalClass.addPropertyDoc( - name = "re", - doc = "Convert this Decimal to a Complex with zero imaginary part.", - type = type("lyng.complex.Complex"), - moduleName = "lyng.complex", - getter = { - newComplex( - complexClass, - decimalToReal(thisObj), - 0.0 - ) - } - ) - decimalClass.members["re"] = decimalClass.members.getValue("re").copy(typeDecl = complexTypeDecl) - - decimalClass.addPropertyDoc( - name = "i", - doc = "Convert this Decimal to a pure imaginary Complex after rounding to Real.", - type = type("lyng.complex.Complex"), - moduleName = "lyng.complex", - getter = { - newComplex( - complexClass, - 0.0, - decimalToReal(thisObj) - ) - } - ) - decimalClass.members["i"] = decimalClass.members.getValue("i").copy(typeDecl = complexTypeDecl) + decimalClass.bindProperty("re", getter = { + newComplex( + complexClass, + decimalToReal(thisObj), + 0.0 + ) + }) + decimalClass.bindProperty("i", getter = { + newComplex( + complexClass, + 0.0, + decimalToReal(thisObj) + ) + }) OperatorInteropRegistry.register( leftClass = decimalClass, diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjDecimalSupport.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjDecimalSupport.kt index 619ea0d..b410e84 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjDecimalSupport.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjDecimalSupport.kt @@ -106,15 +106,15 @@ object ObjDecimalSupport { decimalClass.addFn("toStringExpanded") { ObjString(valueOf(thisObj).toStringExpanded()) } - decimalClass.addClassFn("fromInt") { + decimalClass.bindClassFn("fromInt") { val value = requiredArg(0).value newInstance(decimalClass, IonBigDecimal.fromLong(value)) } - decimalClass.addClassFn("fromReal") { + decimalClass.bindClassFn("fromReal") { val value = requiredArg(0).value newInstanceFromFiniteReal(decimalClass, value) } - decimalClass.addClassFn("fromString") { + decimalClass.bindClassFn("fromString") { val value = requiredArg(0).value try { newInstance(decimalClass, IonBigDecimal.parseStringWithMode(value)) diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjMatrixSupport.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjMatrixSupport.kt index 6413b6b..d534cf8 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjMatrixSupport.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjMatrixSupport.kt @@ -53,10 +53,10 @@ object ObjMatrixSupport { } hooks += { _, instance -> instance.kotlinInstanceData = defaultVector } - vectorClass.addProperty("size", getter = { + vectorClass.bindProperty("size", getter = { ObjInt.of(vectorOf(thisObj).size.toLong()) }) - vectorClass.addProperty("length", getter = { + vectorClass.bindProperty("length", getter = { ObjInt.of(vectorOf(thisObj).size.toLong()) }) vectorClass.addFn("toList") { @@ -99,10 +99,10 @@ object ObjMatrixSupport { ObjInt.of(vectorOf(thisObj).compareTo(coerceVectorArg(requireScope(), args.firstAndOnly())).toLong()) } - vectorClass.addClassFn("fromList") { + vectorClass.bindClassFn("fromList") { newVector(vectorClass, parseVector(requireScope(), requiredArg(0))) } - vectorClass.addClassFn("zeros") { + vectorClass.bindClassFn("zeros") { val size = requiredArg(0).value.toInt() if (size <= 0) requireScope().raiseIllegalArgument("vector size must be positive") newVector(vectorClass, VectorData(DoubleArray(size))) @@ -119,13 +119,13 @@ object ObjMatrixSupport { } hooks += { _, instance -> instance.kotlinInstanceData = defaultMatrix } - matrixClass.addProperty("rows", getter = { + matrixClass.bindProperty("rows", getter = { ObjInt.of(matrixOf(thisObj).rows.toLong()) }) - matrixClass.addProperty("cols", getter = { + matrixClass.bindProperty("cols", getter = { ObjInt.of(matrixOf(thisObj).cols.toLong()) }) - matrixClass.addProperty("shape", getter = { + matrixClass.bindProperty("shape", getter = { ObjList( mutableListOf( ObjInt.of(matrixOf(thisObj).rows.toLong()), @@ -133,7 +133,7 @@ object ObjMatrixSupport { ) ) }) - matrixClass.addProperty("isSquare", getter = { + matrixClass.bindProperty("isSquare", getter = { matrixOf(thisObj).isSquare.toObj() }) @@ -208,17 +208,17 @@ object ObjMatrixSupport { ObjInt.of(matrixOf(thisObj).compareTo(coerceMatrixArg(requireScope(), args.firstAndOnly())).toLong()) } - matrixClass.addClassFn("fromRows") { + matrixClass.bindClassFn("fromRows") { newMatrix(matrixClass, parseRows(requireScope(), requiredArg(0))) } - matrixClass.addClassFn("zeros") { + matrixClass.bindClassFn("zeros") { val rows = requiredArg(0).value.toInt() val cols = requiredArg(1).value.toInt() if (rows <= 0) requireScope().raiseIllegalArgument("matrix must have at least one row") if (cols <= 0) requireScope().raiseIllegalArgument("matrix must have at least one column") newMatrix(matrixClass, MatrixData(rows, cols, DoubleArray(rows * cols))) } - matrixClass.addClassFn("identity") { + matrixClass.bindClassFn("identity") { val size = requiredArg(0).value.toInt() if (size <= 0) requireScope().raiseIllegalArgument("identity matrix size must be positive") val values = DoubleArray(size * size) diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/stdlib_included/decimal_lyng.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/stdlib_included/decimal_lyng.kt index b44c8b4..7b76336 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/stdlib_included/decimal_lyng.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/stdlib_included/decimal_lyng.kt @@ -201,14 +201,7 @@ extern class Decimal() { * * Contexts are dynamic and block-local. After the block finishes, the previous context is restored. */ -extern fun withDecimalContext(context: DecimalContext, block: ()->T): T - -/** - * Convenience overload for changing only precision. - * - * Equivalent to `withDecimalContext(DecimalContext(precision, DecimalRounding.HalfEven), block)`. - */ -extern fun withDecimalContext(precision: Int, block: ()->T): T +extern fun withDecimalContext(context: Object, block: ()->T): T /** * Convenience overload for changing precision and rounding explicitly. diff --git a/lynglib/src/commonTest/kotlin/net/sergeych/lyng/DecimalModuleTest.kt b/lynglib/src/commonTest/kotlin/net/sergeych/lyng/DecimalModuleTest.kt index f616b8e..1cc7f19 100644 --- a/lynglib/src/commonTest/kotlin/net/sergeych/lyng/DecimalModuleTest.kt +++ b/lynglib/src/commonTest/kotlin/net/sergeych/lyng/DecimalModuleTest.kt @@ -205,10 +205,8 @@ class DecimalModuleTest { assertEquals("0.3333333333333333333333333333333333", (1.d / 3.d).toStringExpanded()) assertEquals("0.3333333333", withDecimalContext(10) { (1.d / 3.d).toStringExpanded() }) assertEquals("0.666667", withDecimalContext(6) { ("2".d / 3.d).toStringExpanded() }) - assertEquals("0.666667", withDecimalContext(DecimalContext(6)) { ("2".d / 3.d).toStringExpanded() }) assertEquals("0.12", withDecimalContext(2) { (1.d / 8.d).toStringExpanded() }) assertEquals("0.13", withDecimalContext(2, DecimalRounding.HalfAwayFromZero) { (1.d / 8.d).toStringExpanded() }) - assertEquals("0.13", withDecimalContext(DecimalContext(2, DecimalRounding.HalfAwayFromZero)) { (1.d / 8.d).toStringExpanded() }) assertEquals("0.3333333333333333333333333333333333", (1.d / 3.d).toStringExpanded()) """.trimIndent() ) diff --git a/lynglib/stdlib/lyng/complex.lyng b/lynglib/stdlib/lyng/complex.lyng index efb4a55..869797e 100644 --- a/lynglib/stdlib/lyng/complex.lyng +++ b/lynglib/stdlib/lyng/complex.lyng @@ -1,5 +1,6 @@ package lyng.complex +import lyng.decimal import lyng.operators /* @@ -112,8 +113,10 @@ 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 Decimal.re: Complex get() = Complex(this.toReal(), 0.0) val Int.i: Complex get() = Complex.imaginary(this + 0.0) val Real.i: Complex get() = Complex.imaginary(this) +val Decimal.i: Complex get() = Complex(0.0, this.toReal()) OperatorInterop.register( Int, diff --git a/lynglib/stdlib/lyng/matrix.lyng b/lynglib/stdlib/lyng/matrix.lyng index 5710110..51e0077 100644 --- a/lynglib/stdlib/lyng/matrix.lyng +++ b/lynglib/stdlib/lyng/matrix.lyng @@ -6,9 +6,11 @@ type MatrixScalar = Real | Int extern class Vector() { /** Number of elements. */ val size: Int + get() = 0 /** Alias to `size`. */ val length: Int + get() = 0 /** Convert to a plain list. */ extern fun toList(): List @@ -61,15 +63,19 @@ extern class Vector() { extern class Matrix() { /** Number of rows. */ val rows: Int + get() = 0 /** Number of columns. */ val cols: Int + get() = 0 /** Two-element shape `[rows, cols]`. */ val shape: List + get() = [] /** Whether `rows == cols`. */ val isSquare: Bool + get() = false /** Element-wise addition. Shapes must match. */ extern fun plus(other: Matrix): Matrix