diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Script.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Script.kt index 155643c..1648184 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Script.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/Script.kt @@ -875,6 +875,7 @@ class Script( } addPackage("lyng.complex") { module -> module.eval(Source("lyng.complex", complexLyng)) + ObjComplexSupport.bindTo(module) } addPackage("lyng.buffer") { it.addConstDoc( diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjComplexSupport.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjComplexSupport.kt new file mode 100644 index 0000000..dde3bb6 --- /dev/null +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjComplexSupport.kt @@ -0,0 +1,99 @@ +/* + * Copyright 2026 Sergey S. Chernov real.sergeych@gmail.com + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package net.sergeych.lyng.obj + +import net.sergeych.lyng.* +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") + if (complexClass.kotlinClassData === BoundMarker) return + complexClass.kotlinClassData = BoundMarker + + 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) + + OperatorInteropRegistry.register( + leftClass = decimalClass, + rightClass = complexClass, + commonClass = complexClass, + operatorNames = listOf( + InteropOperator.Plus.name, + InteropOperator.Minus.name, + InteropOperator.Mul.name, + InteropOperator.Div.name + ), + leftToCommon = ObjExternCallable.fromBridge { + newComplex( + complexClass, + decimalToReal(requiredArg(0)), + 0.0 + ) + }, + rightToCommon = ObjExternCallable.fromBridge { + requiredArg(0) + } + ) + } + + private suspend fun ScopeFacade.newComplex(complexClass: ObjClass, real: Double, imag: Double): ObjInstance = + call( + complexClass, + Arguments(ObjReal.of(real), ObjReal.of(imag)) + ) as? ObjInstance ?: raiseIllegalState("Complex() did not return an object instance") + + private fun ScopeFacade.decimalToReal(value: Obj): Double = + ObjDecimalSupport.toDoubleOrNull(value) + ?: raiseClassCastError("expected Decimal-compatible value, got ${value.objClass.className}") +} 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 f15d9fa..cda492a 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjDecimalSupport.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjDecimalSupport.kt @@ -54,16 +54,20 @@ object ObjDecimalSupport { instance.kotlinInstanceData = zero } decimalClass.addFn("plus") { - newInstance(decimalClass, valueOf(thisObj).plus(coerceArg(requireScope(), args.firstAndOnly()))) + OperatorInteropRegistry.invokeBinary(requireScope(), thisObj, args.firstAndOnly(), InteropOperator.Plus) + ?: newInstance(decimalClass, valueOf(thisObj).plus(coerceArg(requireScope(), args.firstAndOnly()))) } decimalClass.addFn("minus") { - newInstance(decimalClass, valueOf(thisObj).minus(coerceArg(requireScope(), args.firstAndOnly()))) + OperatorInteropRegistry.invokeBinary(requireScope(), thisObj, args.firstAndOnly(), InteropOperator.Minus) + ?: newInstance(decimalClass, valueOf(thisObj).minus(coerceArg(requireScope(), args.firstAndOnly()))) } decimalClass.addFn("mul") { - newInstance(decimalClass, valueOf(thisObj).times(coerceArg(requireScope(), args.firstAndOnly()))) + OperatorInteropRegistry.invokeBinary(requireScope(), thisObj, args.firstAndOnly(), InteropOperator.Mul) + ?: newInstance(decimalClass, valueOf(thisObj).times(coerceArg(requireScope(), args.firstAndOnly()))) } decimalClass.addFn("div") { - newInstance(decimalClass, divideWithContext(valueOf(thisObj), coerceArg(requireScope(), args.firstAndOnly()), currentDivisionMode(requireScope()))) + OperatorInteropRegistry.invokeBinary(requireScope(), thisObj, args.firstAndOnly(), InteropOperator.Div) + ?: newInstance(decimalClass, divideWithContext(valueOf(thisObj), coerceArg(requireScope(), args.firstAndOnly()), currentDivisionMode(requireScope()))) } decimalClass.addFn("mod") { newInstance(decimalClass, valueOf(thisObj).rem(coerceArg(requireScope(), args.firstAndOnly()))) diff --git a/lynglib/src/commonTest/kotlin/net/sergeych/lyng/ComplexModuleTest.kt b/lynglib/src/commonTest/kotlin/net/sergeych/lyng/ComplexModuleTest.kt index 4d7e1ab..43f7da9 100644 --- a/lynglib/src/commonTest/kotlin/net/sergeych/lyng/ComplexModuleTest.kt +++ b/lynglib/src/commonTest/kotlin/net/sergeych/lyng/ComplexModuleTest.kt @@ -73,4 +73,32 @@ class ComplexModuleTest { """.trimIndent() ) } + + @Test + fun testInferences() = runTest { + eval( + $$""" + import lyng.decimal + import lyng.complex + + assert( 1.i is Complex ) + assert( 5 + 1.i is Complex ) + """.trimIndent() + ) + } + @Test + fun testDecimalInferences() = runTest { + eval( + $$""" + import lyng.decimal + import lyng.complex + + assert( 1.d.i is Complex ) + assert( 5 + 1.d.i is Complex ) + assert( 5.d + 1.i is Complex ) + assert( 5.d + 2.d.i is Complex ) + """.trimIndent() + ) + } + }