Skip to content

Commit

Permalink
Full UoM
Browse files Browse the repository at this point in the history
  • Loading branch information
Seggan committed Feb 28, 2024
1 parent b70bd4d commit f92a09a
Show file tree
Hide file tree
Showing 7 changed files with 160 additions and 149 deletions.

This file was deleted.

This file was deleted.

This file was deleted.

Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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
get() = toDouble(DurationUnit.SECONDS)
Original file line number Diff line number Diff line change
@@ -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)
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
130 changes: 117 additions & 13 deletions uom-processor/src/main/kotlin/io/github/seggan/uom/UomProcessor.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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)
Expand All @@ -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)
Expand All @@ -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(
Expand All @@ -100,15 +112,107 @@ class UomProcessor(
companion.addConversionFrom(clazzName, unit, ratio)
}

val extraImports = mutableListOf<Pair<String, String>>()
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)
}
}
}
Expand Down

0 comments on commit f92a09a

Please sign in to comment.