coulomb-parser
The coulomb-parser
package defines a unit expression parsing API and a reference unit expression DSL,
which is used by runtime I/O integrations such as coulomb-pureconfig
Quick Start
Before you begin, it is recommended to first familiarize yourself with the coulomb-runtime documentation.
packages
libraryDependencies += "com.manyangled" %% "coulomb-parser" % "0.8.0"
// dependencies
libraryDependencies += "com.manyangled" %% "coulomb-core" % "0.8.0"
libraryDependencies += "com.manyangled" %% "coulomb-runtime" % "0.8.0"
// coulomb predefined units
libraryDependencies += "com.manyangled" %% "coulomb-units" % "0.8.0"
import
// fundamental coulomb types and methods
import coulomb.*
import coulomb.syntax.*
// algebraic definitions
import algebra.instances.all.given
import coulomb.ops.algebra.all.given
// unit and value type policies for operations
import coulomb.policy.standard.given
import scala.language.implicitConversions
// unit definitions
import coulomb.units.si.{*, given}
import coulomb.units.si.prefixes.{*, given}
import coulomb.units.info.{*, given}
import coulomb.units.time.{*, given}
// parsing definitions
import coulomb.parser.RuntimeUnitParser
import coulomb.parser.standard.RuntimeUnitDslParser
examples
The core API for coulomb-parser
is
RuntimeUnitParser.
A programmer may define their own parsing implementations against this API.
This package defines a reference implementation named
RuntimeUnitDslParser,
which implements a DSL for representing
RuntimeUnit
types.
The examples that follow illustrate the DSL syntax and semantics.
A RuntimeUnitDslParser is defined by giving it a list of package or object names, which contain unit type definitions. The following declaration creates a DSL parser that can understand unit definitions for SI units, SI prefixes and information units.
val dslparser: RuntimeUnitParser = RuntimeUnitDslParser.of[
"coulomb.units.si" *:
"coulomb.units.si.prefixes" *:
"coulomb.units.info" *:
EmptyTuple
]
// dslparser: RuntimeUnitParser = repl.MdocSession$MdocApp$$anon$1@3d3fe796
Parsing can fail, and so the parse
method returns an Either object.
In the following code, parsing a known unit meter
results in a successful Right value.
This example illustrates that unit names parse into a corresponding fully qualified
unit type name, as would be used in static unit type expressions.
RuntimeUnitDslParser
maintains a mapping between static unit types and their names,
as defined by the showFull
method,
such as "meter"
<-> coulomb.units.si$.Meter
Scala 3 metaprogramming returns package objects with the $
suffix,
for example si$
instead of si
.
This is mostly transparent to operations, unless you wish to refer
directly to fully qualified types using the @
prefix in the DSL,
as illustrated in later examples.
val u1 = dslparser.parse("meter")
// u1: Either[String, RuntimeUnit] = Right(
// value = UnitType(path = "coulomb.units.si$.Meter")
// )
// Most of the following examples will display with `toString` to improve readability.
u1.toString
// res0: String = "Right(Meter)"
A parsing failure, such as a unit name that the parser does not know about, results in a Left value containing a parsing error message.
dslparser.parse("nope")
// res1: Either[String, RuntimeUnit] = Left(
// value = "Error(4, NonEmptyList(FailWith(4,unrecognized unit 'nope')))"
// )
The reference DSL will parse any unit name that is composed of a prefix followed by a unit, into its correct product:
dslparser.parse("kilometer").toString
// res2: String = "Right(Kilo*Meter)"
dslparser.parse("megabyte").toString
// res3: String = "Right(Mega*Byte)"
As with static unit types and runtime units,
DSL units can be inductively combined with the operators *
, /
and ^
:
dslparser.parse("kilometer/second^2").toString
// res4: String = "Right((Kilo*Meter)/(Second^2))"
Numeric literals can also appear, and are equivalent to literal types in static unit type expressions:
dslparser.parse("(1000 * meter) / (second ^ 2)").toString
// res5: String = "Right((1000*Meter)/(Second^2))"
You can also directly specify a fully qualified unit type name,
by prepending with an @
symbol.
Note that these fully qualified names may require name$
instead of name
,
as with si$
in the following:
dslparser.parse("@coulomb.units.si$.Second").toString
// res6: String = "Right(Second)"