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)"