Skip to content

Commit

Permalink
coulomb-runtime for scala3 (#420)
Browse files Browse the repository at this point in the history
* fix merge conflict

* reformat build.sbt

* parser.scala

* toVersionIntroduced

* test f

* reformat

* expr of map

* test of runtime TypeRepr build (not working)

* closer

* hardcode import works

* poc string to TypeRef

* fqTypeRepr

* symbolValueType

* UnitAST

* widen

* astTypeRepr

* kernel

* bridge static -> runtime

* withUnitRuntime

* UnitAST -> RuntimeUnit, return Either

* add some annotation ideas

* RuntimeQuantity

* bump version-introduced

* align RuntimeQuantity with Quantity

* given staging.Compiler from above

* RuntimeUnit toString

* RuntimeUnit toString

* units % Test

* JVM only. SAD.

* are you kidding me?

* formatting troll

* more formatting troll

* refactor to CoefficientRuntime

* StagingCoefficientRuntime class

* stub MappingCoefficientRuntime

* UnitConst

* Canonical - missing base unit and derived unit awareness

* factor runtime meta to meta.scala

* staging.scala

* mapping.scala

* refactor canonical

* clean up signature of canonical to use Either

* utlClosure

* refactor derivedunit method

* working draft of MappingCoefficientRuntime

* stagingquantity.scala

* fix inverted coef ratio

* fix inverted coef ratio

* MappingRuntimeQuantitySuite and JS+Native builds

* support module names

* either testing predicates

* factor RuntimeQuantitySuite

* format

* RuntimeAdd

* format

* addition

* runtime policies

* standard addition

* add some syntax options

* stub coulomb-pureconfig

* ConfigReader[RuntimeUnit]

* ConfigReader[RuntimeUnit] and ConfigWriter[RuntimeUnit]

* stub coulomb-parser

* move &: and TNil to syntax

* fix typelist import

* factor typeReprList

* removed duplicate

* baseunitTR

* private utlClosure

* collect and strset

* format

* optimze with void

* foldLeft

* parse named units

* parse returns Either

* oneOf

* mul not working

* play with chainl1 (not working)

* chainl1 and named working correctly

* complete unit expression grammar

* replace &: with scala *:

* format

* factor toRational

* render

* factor parsing

* factor meta

* parsing.scala

* ConfigReader[Quantity[U, V]]

* remove hierarchy pattern example

* ConfigWriter[Quantity[V, U]]

* Rational polymorphic i/o

* factor pureconfig readers and writers into policies

* factor out dsl to dsl.scala

* fix new dsl path

* rename to RuntimeUnitDslParser

* PureconfigRuntime

* bump tlVersionIntroduced to 0.8.0

* factor into io and policy

* skeleton unit testing

* add parser and pureconfig to root proj

* scalafmt updates

* disable js and native platforms for parser and pureconfig

* testing skeleton for parser

* scalafmt

* parser builds for JS and Native

* wip json io for RuntimeUnit

* smoke test JSON RuntimeUnit reader

* add exponent op to json reader

* json writer for runtime unit

* remove operator addition example

* add case for no value conversion

* factor to runtimeq

* remove ops from unit tests

* resync ci.yml

* stub out coulomb-pureconfig doc

* add note about publish to s01.oss.sonatype.org

* quick start example

* dealias to get consistent mappings

* case class Config example

* improve definition of ConfigReader for case class

* io policy section

* integer i/o section

* stub coulomb-runtime docs

* stub coulomb-parser docs

* add note about pureconfig and jvm only

* some parsing example code

* add laika conf to support @:api

* add external target for Quantity typedef

* fix formatting

* documented example code for coulomb-parser

* add laika pr ref

* add explaination

* add coulomb package prefix

* format troll

* add scala 3 api links

* more coulomb-parser doc

* predefine doc page links

* quick start for coulomb-runtime

* doc massaging

* clean up link definitions

* modernize links
  • Loading branch information
erikerlandson authored Sep 30, 2023
1 parent 1975e6a commit 4df97fb
Show file tree
Hide file tree
Showing 31 changed files with 2,406 additions and 55 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -80,11 +80,11 @@ jobs:

- name: Make target directories
if: github.event_name != 'pull_request' && (startsWith(github.ref, 'refs/tags/v') || github.ref == 'refs/heads/scala3')
run: mkdir -p spire/.js/target spire/.jvm/target testkit/.native/target units/.jvm/target testkit/.js/target unidocs/target core/.native/target spire/.native/target core/.js/target units/.native/target core/.jvm/target refined/.native/target refined/.js/target refined/.jvm/target units/.js/target testkit/.jvm/target project/target
run: mkdir -p runtime/.js/target spire/.js/target spire/.jvm/target testkit/.native/target units/.jvm/target runtime/.native/target testkit/.js/target parser/.jvm/target unidocs/target parser/.js/target core/.native/target pureconfig/.jvm/target spire/.native/target parser/.native/target core/.js/target units/.native/target runtime/.jvm/target core/.jvm/target refined/.native/target refined/.js/target refined/.jvm/target units/.js/target testkit/.jvm/target project/target

- name: Compress target directories
if: github.event_name != 'pull_request' && (startsWith(github.ref, 'refs/tags/v') || github.ref == 'refs/heads/scala3')
run: tar cf targets.tar spire/.js/target spire/.jvm/target testkit/.native/target units/.jvm/target testkit/.js/target unidocs/target core/.native/target spire/.native/target core/.js/target units/.native/target core/.jvm/target refined/.native/target refined/.js/target refined/.jvm/target units/.js/target testkit/.jvm/target project/target
run: tar cf targets.tar runtime/.js/target spire/.js/target spire/.jvm/target testkit/.native/target units/.jvm/target runtime/.native/target testkit/.js/target parser/.jvm/target unidocs/target parser/.js/target core/.native/target pureconfig/.jvm/target spire/.native/target parser/.native/target core/.js/target units/.native/target runtime/.jvm/target core/.jvm/target refined/.native/target refined/.js/target refined/.jvm/target units/.js/target testkit/.jvm/target project/target

- name: Upload target directories
if: github.event_name != 'pull_request' && (startsWith(github.ref, 'refs/tags/v') || github.ref == 'refs/heads/scala3')
Expand Down
177 changes: 175 additions & 2 deletions build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
ThisBuild / tlBaseVersion := "0.7"

// publish settings
// artifacts now publish to s01.oss.sonatype.org, per:
// https://github.com/erikerlandson/coulomb/issues/500
ThisBuild / developers += tlGitHubDev("erikerlandson", "Erik Erlandson")
ThisBuild / organization := "com.manyangled"
ThisBuild / organizationName := "Erik Erlandson"
Expand Down Expand Up @@ -38,7 +40,17 @@ def commonSettings = Seq(
)

lazy val root = tlCrossRootProject
.aggregate(core, units, spire, refined, testkit, unidocs)
.aggregate(
core,
units,
runtime,
parser,
pureconfig,
spire,
refined,
testkit,
unidocs
)

lazy val core = crossProject(JVMPlatform, JSPlatform, NativePlatform)
.crossType(CrossType.Pure)
Expand All @@ -60,6 +72,69 @@ lazy val units = crossProject(JVMPlatform, JSPlatform, NativePlatform)
libraryDependencies += "io.github.cquiroz" %%% "scala-java-time" % "2.5.0" % Test
)

// see also: https://github.com/lampepfl/dotty/issues/7647
lazy val runtime = crossProject(JVMPlatform, JSPlatform, NativePlatform)
.crossType(CrossType.Pure)
.in(file("runtime"))
.settings(name := "coulomb-runtime")
.dependsOn(
core % "compile->compile;test->test",
units % Test
)
.settings(
tlVersionIntroduced := Map("3" -> "0.8.0")
)
.settings(commonSettings: _*)
.settings(
// staging compiler is only supported on JVM
// but is also used to satisfy builds on JS and Native
libraryDependencies += "org.scala-lang" %% "scala3-staging" % scalaVersion.value
)
.platformsSettings(JSPlatform, NativePlatform)(
// any unit tests using staging must be excluded from JS and Native
Test / unmanagedSources / excludeFilter := HiddenFileFilter || "*stagingquantity.scala"
)

// cats-parse doesn't seem to build for JS or Native
lazy val parser = crossProject(JVMPlatform, JSPlatform, NativePlatform)
.crossType(CrossType.Pure)
.in(file("parser"))
.settings(name := "coulomb-parser")
.dependsOn(
core % "compile->compile;test->test",
runtime,
units % Test
)
.settings(
tlVersionIntroduced := Map("3" -> "0.8.0")
)
.settings(commonSettings: _*)
.settings(
libraryDependencies += "org.typelevel" %%% "cats-parse" % "0.3.10"
)

// pureconfig doesn't currently build for JS or Native
// https://github.com/pureconfig/pureconfig/issues/1307
lazy val pureconfig = crossProject(
JVMPlatform /*, JSPlatform, NativePlatform */
)
.crossType(CrossType.Pure)
.in(file("pureconfig"))
.settings(name := "coulomb-pureconfig")
.dependsOn(
core % "compile->compile;test->test",
runtime,
parser,
units % Test
)
.settings(
tlVersionIntroduced := Map("3" -> "0.8.0")
)
.settings(commonSettings: _*)
.settings(
libraryDependencies += "com.github.pureconfig" %%% "pureconfig-core" % "0.17.4"
)

lazy val spire = crossProject(JVMPlatform, JSPlatform, NativePlatform)
.crossType(CrossType.Pure)
.in(file("spire"))
Expand Down Expand Up @@ -102,6 +177,9 @@ lazy val all = project
.dependsOn(
core.jvm,
units.jvm,
runtime.jvm,
parser.jvm,
pureconfig.jvm,
spire.jvm,
refined.jvm
) // scala repl only needs JVMPlatform subproj builds
Expand All @@ -122,15 +200,110 @@ lazy val unidocs = project
// https://typelevel.org/sbt-typelevel/site.html
// sbt docs/tlSitePreview
// http://localhost:4242
import laika.ast.{ExternalTarget, InternalTarget, VirtualPath}
import laika.rewrite.link.{LinkConfig, ApiLinks, SourceLinks, TargetDefinition}
lazy val docs = project
.in(file("site"))
.dependsOn(core.jvm, units.jvm, spire.jvm, refined.jvm)
.dependsOn(
core.jvm,
units.jvm,
runtime.jvm,
parser.jvm,
pureconfig.jvm,
spire.jvm,
refined.jvm
)
.enablePlugins(TypelevelSitePlugin)
.settings(
// turn off the new -W warnings in mdoc scala compilations
// at least until I can get a better handle on how to work with them
Compile / scalacOptions ~= (_.filterNot { x => x.startsWith("-W") })
)
.settings(
laikaConfig := LaikaConfig.defaults
.withConfigValue(
LinkConfig.empty
.addApiLinks(
ApiLinks(
baseUri =
"https://www.javadoc.io/doc/com.manyangled/coulomb-docs_3/latest/",
packagePrefix = "coulomb"
),
ApiLinks(
baseUri = "https://scala-lang.org/api/3.x/",
packagePrefix = "scala"
),
ApiLinks(
baseUri =
"https://javadoc.io/doc/com.github.pureconfig/pureconfig-core_3/latest/",
packagePrefix = "pureconfig"
)
)
.addTargets(
// Target names need to be all lowercase.
// Note, this does not align with Laika docs.
// In future laika releases the names will be case insensitive, see:
// https://github.com/typelevel/Laika/pull/541
TargetDefinition(
// intended usage: [Quantity][quantitytypedef]
// Links to type defs do not work properly with laika '@:api(...)' constructs
// which is going to make a lot of coulomb references harder to do.
"quantitytypedef",
ExternalTarget(
"https://www.javadoc.io/doc/com.manyangled/coulomb-docs_3/latest/coulomb.html#Quantity[V,U]=V"
)
),
TargetDefinition(
"coulomb-introduction",
InternalTarget(
VirtualPath.parse("README.md")
)
),
TargetDefinition(
"coulomb-core",
InternalTarget(
VirtualPath.parse("coulomb-core.md")
)
),
TargetDefinition(
"coulomb-units",
InternalTarget(
VirtualPath.parse("coulomb-units.md")
)
),
TargetDefinition(
"coulomb-spire",
InternalTarget(
VirtualPath.parse("coulomb-spire.md")
)
),
TargetDefinition(
"coulomb-refined",
InternalTarget(
VirtualPath.parse("coulomb-refined.md")
)
),
TargetDefinition(
"coulomb-runtime",
InternalTarget(
VirtualPath.parse("coulomb-runtime.md")
)
),
TargetDefinition(
"coulomb-parser",
InternalTarget(
VirtualPath.parse("coulomb-parser.md")
)
),
TargetDefinition(
"coulomb-pureconfig",
InternalTarget(
VirtualPath.parse("coulomb-pureconfig.md")
)
)
)
)
)

// https://github.com/sbt/sbt-jmh
// sbt "benchmarks/Jmh/run .*Benchmark"
Expand Down
69 changes: 54 additions & 15 deletions core/src/main/scala/coulomb/infra/meta.scala
Original file line number Diff line number Diff line change
Expand Up @@ -263,19 +263,49 @@ object meta:

object baseunit:
def unapply(using Quotes)(u: quotes.reflect.TypeRepr): Boolean =
u match
case baseunitTR(_) => true
case _ => false

object derivedunit:
def unapply(using qq: Quotes, mode: SigMode)(
u: quotes.reflect.TypeRepr
): Option[(Rational, List[(quotes.reflect.TypeRepr, Rational)])] =
import quotes.reflect.*
u match
case derivedunitTR(dtr) =>
mode match
case SigMode.Simplify =>
// don't expand the signature definition in simplify mode
Some((Rational.const1, (u, Rational.const1) :: Nil))
case _ =>
val AppliedType(_, List(_, d, _, _)) =
dtr: @unchecked
Some(cansig(d))
case _ => None

object baseunitTR:
def unapply(using Quotes)(
u: quotes.reflect.TypeRepr
): Option[quotes.reflect.TypeRepr] =
import quotes.reflect.*
Implicits.search(
TypeRepr
.of[BaseUnit]
.appliedTo(List(u, TypeBounds.empty, TypeBounds.empty))
) match
case iss: ImplicitSearchSuccess => true
case _ => false
case iss: ImplicitSearchSuccess =>
Some(
iss.tree.tpe.baseType(
TypeRepr.of[BaseUnit].typeSymbol
)
)
case _ => None

object derivedunit:
def unapply(using qq: Quotes, mode: SigMode)(
object derivedunitTR:
def unapply(using Quotes)(
u: quotes.reflect.TypeRepr
): Option[(Rational, List[(quotes.reflect.TypeRepr, Rational)])] =
): Option[quotes.reflect.TypeRepr] =
import quotes.reflect.*
Implicits.search(
TypeRepr
Expand All @@ -290,16 +320,11 @@ object meta:
)
) match
case iss: ImplicitSearchSuccess =>
mode match
case SigMode.Simplify =>
// don't expand the signature definition in simplify mode
Some((Rational.const1, (u, Rational.const1) :: Nil))
case _ =>
val AppliedType(_, List(_, d, _, _)) =
iss.tree.tpe.baseType(
TypeRepr.of[DerivedUnit].typeSymbol
): @unchecked
Some(cansig(d))
Some(
iss.tree.tpe.baseType(
TypeRepr.of[DerivedUnit].typeSymbol
)
)
case _ => None

object deltaunit:
Expand Down Expand Up @@ -360,6 +385,20 @@ object meta:
case Nil => Nil
case (u, e0) :: tail => (u, e0 * e) :: unifyPow(e, tail)

def typeReprList(using Quotes)(
tlist: quotes.reflect.TypeRepr
): List[quotes.reflect.TypeRepr] =
import quotes.reflect.*
tlist match
case tnil if (tnil =:= TypeRepr.of[EmptyTuple]) => Nil
case AppliedType(t, List(head, tail)) if (t =:= TypeRepr.of[*:]) =>
head :: typeReprList(tail)
case _ =>
report.errorAndAbort(
s"typeReprList: bad type list ${tlist.show}"
)
null.asInstanceOf[Nothing]

def typestr(using Quotes)(t: quotes.reflect.TypeRepr): String =
// The policy goal here is that type aliases are never expanded.
typestring(t, false)
Expand Down
29 changes: 13 additions & 16 deletions core/src/main/scala/coulomb/ops/ops.scala
Original file line number Diff line number Diff line change
Expand Up @@ -168,10 +168,7 @@ object ValuePromotion:
import scala.quoted.*
import scala.language.implicitConversions

import coulomb.infra.meta.typestr

final type &:[H, T]
final type TNil
import coulomb.infra.meta.*

transparent inline given ctx_VP_Path[VF, VT]: ValuePromotion[VF, VT] = ${
vpPath[VF, VT]
Expand Down Expand Up @@ -206,20 +203,19 @@ object ValuePromotion:
iss.tree.tpe.baseType(
TypeRepr.of[ValuePromotionPolicy].typeSymbol
): @unchecked
vpp2str(vppt)
vpp2str(typeReprList(vppt))
case _ =>
report.error("no ValuePromotionPolicy was found in scope")
VppSet.empty[(String, String)]
null.asInstanceOf[Nothing]

private def vpp2str(using Quotes)(
vpp: quotes.reflect.TypeRepr
vppl: List[quotes.reflect.TypeRepr]
): VppSet[(String, String)] =
import quotes.reflect.*
vpp match
case t if (t =:= TypeRepr.of[TNil]) =>
VppSet.empty[(String, String)]
case AppliedType(v, List(AppliedType(t2, List(vf, vt)), tail))
if ((v =:= TypeRepr.of[&:]) && (t2 =:= TypeRepr.of[Tuple2])) =>
vppl match
case Nil => VppSet.empty[(String, String)]
case AppliedType(t2, List(vf, vt)) :: tail
if (t2 =:= TypeRepr.of[Tuple2]) =>
val vppset = vpp2str(tail)
vppset.add(
(
Expand All @@ -230,9 +226,9 @@ object ValuePromotion:
vppset
case _ =>
report.error(
s"type ${typestr(vpp)} is not a valid value promotion policy"
s"type ${typestr(vppl.head)} is not a valid promotion pair"
)
VppSet.empty[(String, String)]
null.asInstanceOf[Nothing]

private def pathexists(
vf: String,
Expand All @@ -257,9 +253,10 @@ object ValuePromotion:
done = true
haspath

final class ValuePromotionPolicy[Pairs]
final class ValuePromotionPolicy[Pairs <: Tuple]
object ValuePromotionPolicy:
def apply[P](): ValuePromotionPolicy[P] = new ValuePromotionPolicy[P]
def apply[P <: Tuple](): ValuePromotionPolicy[P] =
new ValuePromotionPolicy[P]

final case class ShowUnit[U](value: String)
object ShowUnit:
Expand Down
Loading

0 comments on commit 4df97fb

Please sign in to comment.