Skip to content

Commit

Permalink
Basic UoM
Browse files Browse the repository at this point in the history
  • Loading branch information
Seggan committed Feb 28, 2024
1 parent 4fd80d1 commit b70bd4d
Show file tree
Hide file tree
Showing 8 changed files with 195 additions and 7 deletions.
4 changes: 2 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,6 @@ dependency-reduced-pom.xml
.DS_Store
launch.cmd
/paper/
/.gradle
/build
.gradle/
build/
/run
5 changes: 5 additions & 0 deletions plugin/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ plugins {
id("com.github.johnrengelman.shadow") version "8.1.1"
id("net.minecrell.plugin-yml.bukkit") version "0.5.3"
id("xyz.jpenilla.run-paper") version "2.2.0"

id("com.google.devtools.ksp") version "1.9.22-1.0.17"
}

repositories {
Expand Down Expand Up @@ -41,6 +43,9 @@ dependencies {

implementation("com.github.Seggan:kfun:0.1.0")

implementation(project(":uom"))
ksp(project(":uom-processor"))

testImplementation(kotlin("test"))
testImplementation("io.kotest:kotest-assertions-core:5.8.0")
testImplementation("com.github.seeseemelk:MockBukkit-v1.20:3.78.0")
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
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 @@ -11,4 +13,8 @@ inline val Int.years: Duration
get() = this.toDouble().years

inline val Duration.doubleSeconds: Double
get() = toDouble(DurationUnit.SECONDS)
get() = toDouble(DurationUnit.SECONDS)

@Measure(base = "metersPerSecond")
@AlternateUnit(name = "kilometersPerHour", ratio = 3.6)
private class AVelocity
6 changes: 3 additions & 3 deletions uom-processor/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@ version = "0.1-SNAPSHOT"

dependencies {
implementation(kotlin("stdlib"))
implementation("com.google.devtools.ksp:symbol-processing-api:1.9.22-1.0.17") {
isTransitive = false
}
implementation(project(":uom"))
implementation("com.google.devtools.ksp:symbol-processing-api:1.9.22-1.0.17")
implementation("com.squareup:kotlinpoet-ksp:1.16.0")
}

kotlin {
Expand Down
164 changes: 164 additions & 0 deletions uom-processor/src/main/kotlin/io/github/seggan/uom/UomProcessor.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
package io.github.seggan.uom

import com.google.devtools.ksp.processing.CodeGenerator
import com.google.devtools.ksp.processing.KSPLogger
import com.google.devtools.ksp.processing.Resolver
import com.google.devtools.ksp.processing.SymbolProcessor
import com.google.devtools.ksp.symbol.*
import com.google.devtools.ksp.validate
import com.squareup.kotlinpoet.*
import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy
import com.squareup.kotlinpoet.jvm.jvmInline
import com.squareup.kotlinpoet.ksp.addOriginatingKSFile
import com.squareup.kotlinpoet.ksp.writeTo

class UomProcessor(
private val generator: CodeGenerator,
private val logger: KSPLogger
) : SymbolProcessor {
override fun process(resolver: Resolver): List<KSAnnotated> {
val symbols = resolver.getSymbolsWithAnnotation(Measure::class.qualifiedName!!)
.filterIsInstance<KSClassDeclaration>()
if (!symbols.iterator().hasNext()) return emptyList()
symbols.filter(KSNode::validate).forEach { it.accept(Visitor(), Unit) }
return symbols.filterNot(KSNode::validate).toList()
}

inner class Visitor : KSVisitorVoid() {
override fun visitClassDeclaration(classDeclaration: KSClassDeclaration, data: Unit) {
val measureAnnotation = classDeclaration.annotations.find { it.shortName.asString() == "Measure" } ?: return
val baseUnit = measureAnnotation.getArgument("base") as? String ?: error("Base unit is required")
var measure = classDeclaration.simpleName.asString()
if (!measure.startsWith("A")) {
logger.error("Measure names must start with 'A'", classDeclaration)
return
}
measure = measure.substring(1)

val clazzName = ClassName(classDeclaration.packageName.asString(), measure)
val clazz = TypeSpec.classBuilder(clazzName)
.addOriginatingKSFile(classDeclaration.containingFile!!)
.addModifiers(KModifier.VALUE)
.jvmInline()
.addSuperinterface(COMPARABLE.parameterizedBy(clazzName))

.primaryConstructor(
FunSpec.constructorBuilder()
.addModifiers(KModifier.PRIVATE)
.addParameter(baseUnit, Double::class)
.build()
)
.addProperty(
PropertySpec.builder(baseUnit, Double::class)
.initializer(baseUnit)
.build()
)

.addOperator("plus", "+", baseUnit, clazzName)
.addOperator("minus", "-", baseUnit, clazzName)
.addScalarOperator("times", "*", baseUnit, clazzName)
.addScalarOperator("div", "/", baseUnit, clazzName)
.addScalarOperator("rem", "%", baseUnit, clazzName)
.addFunction(
FunSpec.builder("compareTo")
.addModifiers(KModifier.OVERRIDE)
.addParameter("other", clazzName)
.returns(Int::class)
.addStatement("return %L.compareTo(other.%L)", baseUnit, baseUnit)
.build()
)

val companion = TypeSpec.companionObjectBuilder()
.addProperty(
PropertySpec.builder("ZERO", clazzName)
.initializer("%T(0.0)", clazzName)
.build()
)
.addConversionFrom(clazzName, baseUnit)

val unitAnnotation = classDeclaration.annotations.find { it.shortName.asString() == "AlternateUnit" }
if (unitAnnotation != null) {
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)
return
}
clazz.addProperty(
PropertySpec.builder(unit, Double::class)
.getter(
FunSpec.getterBuilder()
.addStatement("return %L / %L", baseUnit, ratio)
.build()
)
.build()
)
companion.addConversionFrom(clazzName, unit, ratio)
}

clazz.addType(companion.build())

FileSpec.builder(
classDeclaration.packageName.asString(),
"Uom$measure"
)
.addType(clazz.build())
.build()
.writeTo(generator, false)
}
}
}

private fun TypeSpec.Builder.addConversionFrom(
clazz: ClassName,
name: String,
ratio: Double = 1.0,
): TypeSpec.Builder {
for (type in listOf(Double::class, Int::class, Long::class)) {
val prop = PropertySpec.builder(name, clazz)
.receiver(type)
.getter(
FunSpec.getterBuilder()
.addStatement("return %T(this * %L)", clazz, ratio)
.build()
)
addProperty(prop.build())
}
return this
}

private fun TypeSpec.Builder.addOperator(
operator: String,
symbol: String,
baseUnit: String,
className: ClassName
): TypeSpec.Builder {
val operatorFun = FunSpec.builder(operator)
.addModifiers(KModifier.OPERATOR)
.addParameter("other", className)
.returns(className)
.addStatement("return %T(%L %L other.%L)", className, baseUnit, symbol, baseUnit)
return addFunction(operatorFun.build())
}

private fun TypeSpec.Builder.addScalarOperator(
operator: String,
symbol: String,
baseUnit: String,
className: ClassName
): TypeSpec.Builder {
val operatorFun = FunSpec.builder(operator)
.addModifiers(KModifier.OPERATOR)
.addParameter("scalar", Double::class)
.returns(className)
.addStatement("return %T(%L %L scalar)", className, baseUnit, symbol)
return addFunction(operatorFun.build())
}

fun KSAnnotation.getArgument(name: String): Any? {
return arguments.find { it.name?.asString() == name }?.value
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package io.github.seggan.uom

import com.google.devtools.ksp.processing.SymbolProcessor
import com.google.devtools.ksp.processing.SymbolProcessorEnvironment
import com.google.devtools.ksp.processing.SymbolProcessorProvider

class UomProcessorProvider : SymbolProcessorProvider {
override fun create(environment: SymbolProcessorEnvironment): SymbolProcessor {
return UomProcessor(environment.codeGenerator, environment.logger)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
io.github.seggan.uom.UomProcessorProvider
3 changes: 2 additions & 1 deletion uom/src/main/kotlin/io/github/seggan/uom/UoM.kt
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,9 @@ annotation class MultipliesTo(val multiplicand: KClass<*>, val product: KClass<*
@Target(AnnotationTarget.CLASS)
annotation class DividesTo(val divisor: KClass<*>, val quotient: KClass<*>)

@Repeatable
@Target(AnnotationTarget.CLASS)
annotation class Unit(val name: String, val ratio: Double)
annotation class AlternateUnit(val name: String, val ratio: Double)

@Target(AnnotationTarget.CLASS)
annotation class Measure(val base: String)

0 comments on commit b70bd4d

Please sign in to comment.