Fix lynglib binding regressions and green test suite

This commit is contained in:
Sergey Chernov 2026-04-16 23:34:45 +03:00
parent bb2119b1d1
commit 8f66cd7680
9 changed files with 106 additions and 59 deletions

View File

@ -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,

View File

@ -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)
}
}

View File

@ -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 = {
decimalClass.bindProperty("re", 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 = {
})
decimalClass.bindProperty("i", getter = {
newComplex(
complexClass,
0.0,
decimalToReal(thisObj)
)
}
)
decimalClass.members["i"] = decimalClass.members.getValue("i").copy(typeDecl = complexTypeDecl)
})
OperatorInteropRegistry.register(
leftClass = decimalClass,

View File

@ -106,15 +106,15 @@ object ObjDecimalSupport {
decimalClass.addFn("toStringExpanded") {
ObjString(valueOf(thisObj).toStringExpanded())
}
decimalClass.addClassFn("fromInt") {
decimalClass.bindClassFn("fromInt") {
val value = requiredArg<ObjInt>(0).value
newInstance(decimalClass, IonBigDecimal.fromLong(value))
}
decimalClass.addClassFn("fromReal") {
decimalClass.bindClassFn("fromReal") {
val value = requiredArg<ObjReal>(0).value
newInstanceFromFiniteReal(decimalClass, value)
}
decimalClass.addClassFn("fromString") {
decimalClass.bindClassFn("fromString") {
val value = requiredArg<ObjString>(0).value
try {
newInstance(decimalClass, IonBigDecimal.parseStringWithMode(value))

View File

@ -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<ObjInt>(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<ObjInt>(0).value.toInt()
val cols = requiredArg<ObjInt>(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<ObjInt>(0).value.toInt()
if (size <= 0) requireScope().raiseIllegalArgument("identity matrix size must be positive")
val values = DoubleArray(size * size)

View File

@ -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<T>(context: DecimalContext, block: ()->T): T
/**
* Convenience overload for changing only precision.
*
* Equivalent to `withDecimalContext(DecimalContext(precision, DecimalRounding.HalfEven), block)`.
*/
extern fun withDecimalContext<T>(precision: Int, block: ()->T): T
extern fun withDecimalContext<T>(context: Object, block: ()->T): T
/**
* Convenience overload for changing precision and rounding explicitly.

View File

@ -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()
)

View File

@ -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,

View File

@ -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<Real>
@ -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<Int>
get() = []
/** Whether `rows == cols`. */
val isSquare: Bool
get() = false
/** Element-wise addition. Shapes must match. */
extern fun plus(other: Matrix): Matrix