From f92a09a5165c2b6374e7241461562d0860b9a1ec Mon Sep 17 00:00:00 2001 From: Seggan Date: Wed, 28 Feb 2024 18:36:48 -0500 Subject: [PATCH] Full UoM --- .../addoncommunity/galactifun/units/Angle.kt | 68 --------- .../galactifun/units/Distance.kt | 41 ------ .../addoncommunity/galactifun/units/Mass.kt | 20 --- .../addoncommunity/galactifun/units/Time.kt | 8 +- .../galactifun/units/UnitsOfMeasure.kt | 41 ++++++ .../test/api/objects/properties/OrbitTest.kt | 1 + .../io/github/seggan/uom/UomProcessor.kt | 130 ++++++++++++++++-- 7 files changed, 160 insertions(+), 149 deletions(-) delete mode 100644 plugin/src/main/kotlin/io/github/addoncommunity/galactifun/units/Angle.kt delete mode 100644 plugin/src/main/kotlin/io/github/addoncommunity/galactifun/units/Distance.kt delete mode 100644 plugin/src/main/kotlin/io/github/addoncommunity/galactifun/units/Mass.kt create mode 100644 plugin/src/main/kotlin/io/github/addoncommunity/galactifun/units/UnitsOfMeasure.kt diff --git a/plugin/src/main/kotlin/io/github/addoncommunity/galactifun/units/Angle.kt b/plugin/src/main/kotlin/io/github/addoncommunity/galactifun/units/Angle.kt deleted file mode 100644 index cb62d00..0000000 --- a/plugin/src/main/kotlin/io/github/addoncommunity/galactifun/units/Angle.kt +++ /dev/null @@ -1,68 +0,0 @@ -package io.github.addoncommunity.galactifun.units - -import io.github.addoncommunity.galactifun.units.Angle.Companion.radians -import kotlin.math.* - -@JvmInline -value class Angle private constructor(val radians: Double) : Comparable { - - val degrees: Double - get() = radians * DEGREES_PER_RADIAN - - val arcminutes: Double - get() = degrees * 60 - - val arcseconds: Double - get() = arcminutes * 60 - - val milliarcseconds: Double - get() = arcseconds * 1000 - - val standardForm: Angle - get() = Angle((radians % (2 * PI) + 2 * PI) % (2 * PI)) - - companion object { - const val DEGREES_PER_RADIAN = 180 / Math.PI - - val Double.degrees: Angle - get() = (this / DEGREES_PER_RADIAN).radians - - val Double.arcminutes: Angle - get() = (this / 60).degrees - - val Double.arcseconds: Angle - get() = (this / 60).arcminutes - - val Double.milliarcseconds: Angle - get() = (this / 1000).arcseconds - - val Double.radians: Angle - get() = Angle(this) - } - - operator fun plus(other: Angle): Angle = Angle(radians + other.radians) - operator fun minus(other: Angle): Angle = Angle(radians - other.radians) - operator fun times(scalar: Double): Angle = Angle(radians * scalar) - operator fun div(scalar: Double): Angle = Angle(radians / scalar) - operator fun rem(scalar: Double): Angle = Angle(radians % scalar) - operator fun unaryMinus() = Angle(-radians) - operator fun unaryPlus() = this - - override fun compareTo(other: Angle): Int = radians.compareTo(other.radians) - - override fun toString(): String { - val degrees = standardForm.degrees - val minutes = (degrees % 1) * 60 - val seconds = (minutes % 1) * 60 - return "${degrees.toInt()}° ${minutes.toInt()}' ${seconds.toInt()}\"" - } -} - -fun sin(angle: Angle): Double = sin(angle.radians) -fun cos(angle: Angle): Double = cos(angle.radians) -fun tan(angle: Angle): Double = tan(angle.radians) -fun sinh(angle: Angle): Double = sinh(angle.radians) -fun cosh(angle: Angle): Double = cosh(angle.radians) -fun tanh(angle: Angle): Double = tanh(angle.radians) - -fun abs(angle: Angle): Angle = abs(angle.radians).radians \ No newline at end of file diff --git a/plugin/src/main/kotlin/io/github/addoncommunity/galactifun/units/Distance.kt b/plugin/src/main/kotlin/io/github/addoncommunity/galactifun/units/Distance.kt deleted file mode 100644 index 55776f9..0000000 --- a/plugin/src/main/kotlin/io/github/addoncommunity/galactifun/units/Distance.kt +++ /dev/null @@ -1,41 +0,0 @@ -package io.github.addoncommunity.galactifun.units - -import io.github.addoncommunity.galactifun.Constants - -@JvmInline -value class Distance private constructor(val meters: Double) : Comparable { - - val lightYears: Double - get() = kilometers / Constants.KM_PER_LY - - val kilometers: Double - get() = meters / 1000 - - companion object { - val Double.lightYears: Distance - get() = (this * Constants.KM_PER_LY).kilometers - - val Double.kilometers: Distance - get() = (this * 1000).meters - - val Double.meters: Distance - get() = Distance(this) - - val Double.au: Distance - get() = (this * Constants.KM_PER_AU).kilometers - - fun fromParallax(parallax: Angle): Distance { - return ((1 / parallax.arcseconds) * Constants.KM_PER_PC).kilometers - } - } - - operator fun plus(other: Distance): Distance = Distance(meters + other.meters) - operator fun minus(other: Distance): Distance = Distance(meters - other.meters) - operator fun times(scalar: Double): Distance = Distance(meters * scalar) - operator fun div(scalar: Double): Distance = Distance(meters / scalar) - operator fun rem(scalar: Double): Distance = Distance(meters % scalar) - operator fun unaryMinus() = Distance(-meters) - operator fun unaryPlus() = this - - override fun compareTo(other: Distance): Int = meters.compareTo(other.meters) -} \ No newline at end of file diff --git a/plugin/src/main/kotlin/io/github/addoncommunity/galactifun/units/Mass.kt b/plugin/src/main/kotlin/io/github/addoncommunity/galactifun/units/Mass.kt deleted file mode 100644 index 42f3f4a..0000000 --- a/plugin/src/main/kotlin/io/github/addoncommunity/galactifun/units/Mass.kt +++ /dev/null @@ -1,20 +0,0 @@ -package io.github.addoncommunity.galactifun.units - -@JvmInline -value class Mass private constructor(val kilograms: Double) : Comparable { - - companion object { - val Double.kilograms: Mass - get() = Mass(this) - } - - operator fun plus(other: Mass): Mass = Mass(kilograms + other.kilograms) - operator fun minus(other: Mass): Mass = Mass(kilograms - other.kilograms) - operator fun times(scalar: Double): Mass = Mass(kilograms * scalar) - operator fun div(scalar: Double): Mass = Mass(kilograms / scalar) - operator fun rem(scalar: Double): Mass = Mass(kilograms % scalar) - operator fun unaryMinus() = Mass(-kilograms) - operator fun unaryPlus() = this - - override fun compareTo(other: Mass): Int = kilograms.compareTo(other.kilograms) -} \ No newline at end of file diff --git a/plugin/src/main/kotlin/io/github/addoncommunity/galactifun/units/Time.kt b/plugin/src/main/kotlin/io/github/addoncommunity/galactifun/units/Time.kt index 372137b..a371f5b 100644 --- a/plugin/src/main/kotlin/io/github/addoncommunity/galactifun/units/Time.kt +++ b/plugin/src/main/kotlin/io/github/addoncommunity/galactifun/units/Time.kt @@ -1,7 +1,5 @@ package io.github.addoncommunity.galactifun.units -import io.github.seggan.uom.AlternateUnit -import io.github.seggan.uom.Measure import kotlin.time.Duration import kotlin.time.Duration.Companion.days import kotlin.time.DurationUnit @@ -13,8 +11,4 @@ inline val Int.years: Duration get() = this.toDouble().years inline val Duration.doubleSeconds: Double - get() = toDouble(DurationUnit.SECONDS) - -@Measure(base = "metersPerSecond") -@AlternateUnit(name = "kilometersPerHour", ratio = 3.6) -private class AVelocity \ No newline at end of file + get() = toDouble(DurationUnit.SECONDS) \ No newline at end of file diff --git a/plugin/src/main/kotlin/io/github/addoncommunity/galactifun/units/UnitsOfMeasure.kt b/plugin/src/main/kotlin/io/github/addoncommunity/galactifun/units/UnitsOfMeasure.kt new file mode 100644 index 0000000..3bc1c86 --- /dev/null +++ b/plugin/src/main/kotlin/io/github/addoncommunity/galactifun/units/UnitsOfMeasure.kt @@ -0,0 +1,41 @@ +@file:Suppress("unused") + +package io.github.addoncommunity.galactifun.units + +import io.github.addoncommunity.galactifun.Constants +import io.github.addoncommunity.galactifun.units.Angle.Companion.radians +import io.github.addoncommunity.galactifun.units.Distance.Companion.meters +import io.github.addoncommunity.galactifun.units.Velocity.Companion.metersPerSecond +import io.github.seggan.uom.AlternateUnit +import io.github.seggan.uom.Measure +import kotlin.math.PI +import kotlin.time.Duration + +@Measure(base = "meters") +@AlternateUnit(name = "lightYears", ratio = Constants.KM_PER_LY * 1000) +@AlternateUnit(name = "kilometers", ratio = 1000.0) +@AlternateUnit(name = "au", ratio = Constants.KM_PER_AU * 1000) +private class ADistance + +@Measure(base = "kilograms") +@AlternateUnit(name = "pounds", ratio = 2.20462) +private class AMass + +@Measure(base = "metersPerSecond") +private class AVelocity +operator fun Distance.div(time: Duration): Velocity = (meters / time.doubleSeconds).metersPerSecond +operator fun Velocity.times(time: Duration): Distance = (metersPerSecond * time.doubleSeconds).meters + +@Measure(base = "radians") +@AlternateUnit(name = "degrees", ratio = 180.0 / Math.PI) +private class AAngle + +val Angle.standardForm: Angle + get() = ((radians % (2 * PI) + 2 * PI) % (2 * PI)).radians + +fun sin(angle: Angle): Double = kotlin.math.sin(angle.radians) +fun cos(angle: Angle): Double = kotlin.math.cos(angle.radians) +fun tan(angle: Angle): Double = kotlin.math.tan(angle.radians) +fun sinh(angle: Angle): Double = kotlin.math.sinh(angle.radians) +fun cosh(angle: Angle): Double = kotlin.math.cosh(angle.radians) +fun tanh(angle: Angle): Double = kotlin.math.tanh(angle.radians) diff --git a/plugin/src/test/kotlin/io/github/addoncommunity/test/api/objects/properties/OrbitTest.kt b/plugin/src/test/kotlin/io/github/addoncommunity/test/api/objects/properties/OrbitTest.kt index 525ac7f..a34a26e 100644 --- a/plugin/src/test/kotlin/io/github/addoncommunity/test/api/objects/properties/OrbitTest.kt +++ b/plugin/src/test/kotlin/io/github/addoncommunity/test/api/objects/properties/OrbitTest.kt @@ -7,6 +7,7 @@ import io.github.addoncommunity.galactifun.units.Angle import io.github.addoncommunity.galactifun.units.Angle.Companion.degrees import io.github.addoncommunity.galactifun.units.Angle.Companion.radians import io.github.addoncommunity.galactifun.units.Distance.Companion.au +import io.github.addoncommunity.galactifun.units.standardForm import io.github.addoncommunity.test.CommonTest import io.kotest.matchers.doubles.percent import io.kotest.matchers.doubles.plusOrMinus diff --git a/uom-processor/src/main/kotlin/io/github/seggan/uom/UomProcessor.kt b/uom-processor/src/main/kotlin/io/github/seggan/uom/UomProcessor.kt index 601e07e..5cacd03 100644 --- a/uom-processor/src/main/kotlin/io/github/seggan/uom/UomProcessor.kt +++ b/uom-processor/src/main/kotlin/io/github/seggan/uom/UomProcessor.kt @@ -24,6 +24,7 @@ class UomProcessor( return symbols.filterNot(KSNode::validate).toList() } + @Suppress("DuplicatedCode") inner class Visitor : KSVisitorVoid() { override fun visitClassDeclaration(classDeclaration: KSClassDeclaration, data: Unit) { val measureAnnotation = classDeclaration.annotations.find { it.shortName.asString() == "Measure" } ?: return @@ -34,8 +35,9 @@ class UomProcessor( return } measure = measure.substring(1) + val pkg = classDeclaration.packageName.asString() - val clazzName = ClassName(classDeclaration.packageName.asString(), measure) + val clazzName = ClassName(pkg, measure) val clazz = TypeSpec.classBuilder(clazzName) .addOriginatingKSFile(classDeclaration.containingFile!!) .addModifiers(KModifier.VALUE) @@ -59,6 +61,20 @@ class UomProcessor( .addScalarOperator("times", "*", baseUnit, clazzName) .addScalarOperator("div", "/", baseUnit, clazzName) .addScalarOperator("rem", "%", baseUnit, clazzName) + .addFunction( + FunSpec.builder("unaryMinus") + .addModifiers(KModifier.OPERATOR) + .returns(clazzName) + .addStatement("return %T(-%L)", clazzName, baseUnit) + .build() + ) + .addFunction( + FunSpec.builder("unaryPlus") + .addModifiers(KModifier.OPERATOR) + .returns(clazzName) + .addStatement("return this") + .build() + ) .addFunction( FunSpec.builder("compareTo") .addModifiers(KModifier.OVERRIDE) @@ -76,16 +92,12 @@ class UomProcessor( ) .addConversionFrom(clazzName, baseUnit) - val unitAnnotation = classDeclaration.annotations.find { it.shortName.asString() == "AlternateUnit" } - if (unitAnnotation != null) { + val unitAnnotations = classDeclaration.annotations.filter { it.shortName.asString() == "AlternateUnit" } + for (unitAnnotation in unitAnnotations) { val unit = unitAnnotation.getArgument("name") as? String - if (unit == null) { - logger.error("Unit name is required", classDeclaration) - return - } val ratio = unitAnnotation.getArgument("ratio") as? Double - if (ratio == null) { - logger.error("Unit ratio is required", classDeclaration) + if (unit == null || ratio == null) { + logger.error("Name and ratio are required", classDeclaration) return } clazz.addProperty( @@ -100,15 +112,107 @@ class UomProcessor( companion.addConversionFrom(clazzName, unit, ratio) } + val extraImports = mutableListOf>() + val mulAnnotations = classDeclaration.annotations.filter { it.shortName.asString() == "MultipliesTo" } + for (mulAnnotation in mulAnnotations) { + val multiplicand = mulAnnotation.getArgument("multiplicand") as? KSTypeReference + val product = mulAnnotation.getArgument("product") as? KSTypeReference + if (multiplicand == null || product == null) { + logger.error("Multiplicand and product are required", classDeclaration) + return + } + val multiplicandClass = multiplicand.resolve().declaration + val productClass = product.resolve().declaration + val multiplicandBaseUnit = multiplicandClass.annotations + .find { it.shortName.asString() == "Measure" } + ?.getArgument("base") as? String + val productBaseUnit = productClass.annotations.find { it.shortName.asString() == "Measure" } + ?.getArgument("base") as? String + if (multiplicandBaseUnit == null || productBaseUnit == null) { + logger.error("Multiplicand and product must be measures", classDeclaration) + return + } + + val multiplicandType = ClassName( + multiplicandClass.packageName.asString(), + multiplicandClass.simpleName.asString().substring(1) + ) + val productType = ClassName( + productClass.packageName.asString(), + productClass.simpleName.asString().substring(1) + ) + extraImports.add("$productType.Companion" to productBaseUnit) + + clazz.addFunction( + FunSpec.builder("times") + .addModifiers(KModifier.OPERATOR) + .addParameter("other", multiplicandType) + .returns(productType) + .addStatement("return (%L * other.%L).%L", baseUnit, multiplicandBaseUnit, productBaseUnit) + .build() + ) + } + + val divAnnotations = classDeclaration.annotations.filter { it.shortName.asString() == "DividesTo" } + for (divAnnotation in divAnnotations) { + val divisor = divAnnotation.getArgument("divisor") as? KSTypeReference + val quotient = divAnnotation.getArgument("quotient") as? KSTypeReference + if (divisor == null || quotient == null) { + logger.error("Divisor and quotient are required", classDeclaration) + return + } + val divisorClass = divisor.resolve().declaration + val quotientClass = quotient.resolve().declaration + val divisorBaseUnit = divisorClass.annotations + .find { it.shortName.asString() == "Measure" } + ?.getArgument("base") as? String + val quotientBaseUnit = quotientClass.annotations. + find { it.shortName.asString() == "Measure" } + ?.getArgument("base") as? String + if (divisorBaseUnit == null || quotientBaseUnit == null) { + logger.error("Divisor and quotient must be measures", classDeclaration) + return + } + + val divisorType = ClassName( + divisorClass.packageName.asString(), + divisorClass.simpleName.asString().substring(1) + ) + val quotientType = ClassName( + quotientClass.packageName.asString(), + quotientClass.simpleName.asString().substring(1) + ) + extraImports.add("$quotientType.Companion" to divisorBaseUnit) + + clazz.addFunction( + FunSpec.builder("div") + .addModifiers(KModifier.OPERATOR) + .addParameter("other", divisorType) + .returns(quotientType) + .addStatement("return (%L / other.%L).%L", baseUnit, divisorBaseUnit, quotientBaseUnit) + .build() + ) + } + clazz.addType(companion.build()) - FileSpec.builder( - classDeclaration.packageName.asString(), + val fileSpec = FileSpec.builder( + pkg, "Uom$measure" ) .addType(clazz.build()) - .build() - .writeTo(generator, false) + .addImport("$clazzName.Companion", baseUnit) + .addFunction( + FunSpec.builder("abs") + .returns(clazzName) + .addParameter("value", clazzName) + .addStatement("return kotlin.math.abs(value.%L).%L", baseUnit, baseUnit) + .build() + ) + for ((import, unit) in extraImports) { + fileSpec.addImport(import, unit) + } + fileSpec.build().writeTo(generator, false) } } }