diff --git a/.credo.exs b/.credo.exs new file mode 100644 index 0000000..57f105b --- /dev/null +++ b/.credo.exs @@ -0,0 +1,18 @@ +# SPDX-FileCopyrightText: 2026 James Harton +# +# SPDX-License-Identifier: Apache-2.0 + +%{ + configs: [ + %{ + name: "default", + files: %{ + included: ["lib/", "src/", "test/", "web/", "apps/*/lib/", "apps/*/src/"], + # Generated by `mix localize.unit.gen_conversions`; its style is the + # generator's, not ours, and any edit here is lost on regeneration. + excluded: [~r"/_build/", ~r"/deps/", "lib/bb/unit/conversions.ex"] + }, + strict: true + } + ] +} diff --git a/.dialyzer_ignore.exs b/.dialyzer_ignore.exs new file mode 100644 index 0000000..1adf449 --- /dev/null +++ b/.dialyzer_ignore.exs @@ -0,0 +1,12 @@ +# SPDX-FileCopyrightText: 2026 James Harton +# +# SPDX-License-Identifier: Apache-2.0 + +[ + # `BB.Unit.Conversions` is generated by `mix localize.unit.gen_conversions` + # and cannot be edited here. Its `parse_identifier/1` keeps a catch-all + # clause for a parser failure that dialyzer proves unreachable — unknown + # identifiers are rejected later, during decomposition, not by the parser. + # Reported upstream; drop this entry once the generator stops emitting it. + {"lib/bb/unit/conversions.ex", :pattern_match_cov} +] diff --git a/AGENTS.md b/AGENTS.md index 5b3d247..d3cb769 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -185,7 +185,10 @@ When adding a new payload, pick the convention that matches its shape rather tha ## Key Patterns -- Units: Use `Localize.Unit` throughout DSL, converted to floats (SI) in Robot struct +- Units: Use `BB.Unit` throughout DSL, converted to floats (SI) in Robot struct. + Conversion tables live in `lib/bb/unit/conversions.ex`, generated from CLDR by + `mix localize.unit.gen_conversions` — Localize is a `:dev`-only dependency, so + nothing ships to a target but the generated file - Transforms: 4x4 matrices in `BB.Math.Transform`, angles in radians - Process registration: Uses Registry with `:via` tuples, names must be globally unique per robot - DSL entities are structs in `lib/bb/dsl/` matching entity names diff --git a/documentation/dsls/DSL-BB.md b/documentation/dsls/DSL-BB.md index f4062c2..ae85e73 100644 --- a/documentation/dsls/DSL-BB.md +++ b/documentation/dsls/DSL-BB.md @@ -138,12 +138,12 @@ Specifies where the link's center of mass is located, relative to the link's ref | Name | Type | Default | Docs | |------|------|---------|------| -| [`roll`](#topology-link-inertial-origin-roll){: #topology-link-inertial-origin-roll } | `any` | `Localize.Unit.new!(0, "degree")` | rotation around the `x` axis | -| [`pitch`](#topology-link-inertial-origin-pitch){: #topology-link-inertial-origin-pitch } | `any` | `Localize.Unit.new!(0, "degree")` | rotation around the `y` axis | -| [`yaw`](#topology-link-inertial-origin-yaw){: #topology-link-inertial-origin-yaw } | `any` | `Localize.Unit.new!(0, "degree")` | rotation around the `z` axis | -| [`x`](#topology-link-inertial-origin-x){: #topology-link-inertial-origin-x } | `any` | `Localize.Unit.new!(0, "meter")` | translation along the `x` axis | -| [`y`](#topology-link-inertial-origin-y){: #topology-link-inertial-origin-y } | `any` | `Localize.Unit.new!(0, "meter")` | translation along the `y` axis | -| [`z`](#topology-link-inertial-origin-z){: #topology-link-inertial-origin-z } | `any` | `Localize.Unit.new!(0, "meter")` | translation along the `z` axis | +| [`roll`](#topology-link-inertial-origin-roll){: #topology-link-inertial-origin-roll } | `any` | `BB.Unit.new!(0, "degree")` | rotation around the `x` axis | +| [`pitch`](#topology-link-inertial-origin-pitch){: #topology-link-inertial-origin-pitch } | `any` | `BB.Unit.new!(0, "degree")` | rotation around the `y` axis | +| [`yaw`](#topology-link-inertial-origin-yaw){: #topology-link-inertial-origin-yaw } | `any` | `BB.Unit.new!(0, "degree")` | rotation around the `z` axis | +| [`x`](#topology-link-inertial-origin-x){: #topology-link-inertial-origin-x } | `any` | `BB.Unit.new!(0, "meter")` | translation along the `x` axis | +| [`y`](#topology-link-inertial-origin-y){: #topology-link-inertial-origin-y } | `any` | `BB.Unit.new!(0, "meter")` | translation along the `y` axis | +| [`z`](#topology-link-inertial-origin-z){: #topology-link-inertial-origin-z } | `any` | `BB.Unit.new!(0, "meter")` | translation along the `z` axis | @@ -444,12 +444,12 @@ The refrence frame of the visual element with respect to the reference frame of | Name | Type | Default | Docs | |------|------|---------|------| -| [`roll`](#topology-link-visual-origin-roll){: #topology-link-visual-origin-roll } | `any` | `Localize.Unit.new!(0, "degree")` | rotation around the `x` axis | -| [`pitch`](#topology-link-visual-origin-pitch){: #topology-link-visual-origin-pitch } | `any` | `Localize.Unit.new!(0, "degree")` | rotation around the `y` axis | -| [`yaw`](#topology-link-visual-origin-yaw){: #topology-link-visual-origin-yaw } | `any` | `Localize.Unit.new!(0, "degree")` | rotation around the `z` axis | -| [`x`](#topology-link-visual-origin-x){: #topology-link-visual-origin-x } | `any` | `Localize.Unit.new!(0, "meter")` | translation along the `x` axis | -| [`y`](#topology-link-visual-origin-y){: #topology-link-visual-origin-y } | `any` | `Localize.Unit.new!(0, "meter")` | translation along the `y` axis | -| [`z`](#topology-link-visual-origin-z){: #topology-link-visual-origin-z } | `any` | `Localize.Unit.new!(0, "meter")` | translation along the `z` axis | +| [`roll`](#topology-link-visual-origin-roll){: #topology-link-visual-origin-roll } | `any` | `BB.Unit.new!(0, "degree")` | rotation around the `x` axis | +| [`pitch`](#topology-link-visual-origin-pitch){: #topology-link-visual-origin-pitch } | `any` | `BB.Unit.new!(0, "degree")` | rotation around the `y` axis | +| [`yaw`](#topology-link-visual-origin-yaw){: #topology-link-visual-origin-yaw } | `any` | `BB.Unit.new!(0, "degree")` | rotation around the `z` axis | +| [`x`](#topology-link-visual-origin-x){: #topology-link-visual-origin-x } | `any` | `BB.Unit.new!(0, "meter")` | translation along the `x` axis | +| [`y`](#topology-link-visual-origin-y){: #topology-link-visual-origin-y } | `any` | `BB.Unit.new!(0, "meter")` | translation along the `y` axis | +| [`z`](#topology-link-visual-origin-z){: #topology-link-visual-origin-z } | `any` | `BB.Unit.new!(0, "meter")` | translation along the `z` axis | @@ -505,12 +505,12 @@ The refrence frame of the collision element, relative to the reference frame of | Name | Type | Default | Docs | |------|------|---------|------| -| [`roll`](#topology-link-collision-origin-roll){: #topology-link-collision-origin-roll } | `any` | `Localize.Unit.new!(0, "degree")` | rotation around the `x` axis | -| [`pitch`](#topology-link-collision-origin-pitch){: #topology-link-collision-origin-pitch } | `any` | `Localize.Unit.new!(0, "degree")` | rotation around the `y` axis | -| [`yaw`](#topology-link-collision-origin-yaw){: #topology-link-collision-origin-yaw } | `any` | `Localize.Unit.new!(0, "degree")` | rotation around the `z` axis | -| [`x`](#topology-link-collision-origin-x){: #topology-link-collision-origin-x } | `any` | `Localize.Unit.new!(0, "meter")` | translation along the `x` axis | -| [`y`](#topology-link-collision-origin-y){: #topology-link-collision-origin-y } | `any` | `Localize.Unit.new!(0, "meter")` | translation along the `y` axis | -| [`z`](#topology-link-collision-origin-z){: #topology-link-collision-origin-z } | `any` | `Localize.Unit.new!(0, "meter")` | translation along the `z` axis | +| [`roll`](#topology-link-collision-origin-roll){: #topology-link-collision-origin-roll } | `any` | `BB.Unit.new!(0, "degree")` | rotation around the `x` axis | +| [`pitch`](#topology-link-collision-origin-pitch){: #topology-link-collision-origin-pitch } | `any` | `BB.Unit.new!(0, "degree")` | rotation around the `y` axis | +| [`yaw`](#topology-link-collision-origin-yaw){: #topology-link-collision-origin-yaw } | `any` | `BB.Unit.new!(0, "degree")` | rotation around the `z` axis | +| [`x`](#topology-link-collision-origin-x){: #topology-link-collision-origin-x } | `any` | `BB.Unit.new!(0, "meter")` | translation along the `x` axis | +| [`y`](#topology-link-collision-origin-y){: #topology-link-collision-origin-y } | `any` | `BB.Unit.new!(0, "meter")` | translation along the `y` axis | +| [`z`](#topology-link-collision-origin-z){: #topology-link-collision-origin-z } | `any` | `BB.Unit.new!(0, "meter")` | translation along the `z` axis | @@ -1004,12 +1004,12 @@ This is the transform from the parent link to the child link. The joint is locat | Name | Type | Default | Docs | |------|------|---------|------| -| [`roll`](#topology-joint-origin-roll){: #topology-joint-origin-roll } | `any` | `Localize.Unit.new!(0, "degree")` | rotation around the `x` axis | -| [`pitch`](#topology-joint-origin-pitch){: #topology-joint-origin-pitch } | `any` | `Localize.Unit.new!(0, "degree")` | rotation around the `y` axis | -| [`yaw`](#topology-joint-origin-yaw){: #topology-joint-origin-yaw } | `any` | `Localize.Unit.new!(0, "degree")` | rotation around the `z` axis | -| [`x`](#topology-joint-origin-x){: #topology-joint-origin-x } | `any` | `Localize.Unit.new!(0, "meter")` | translation along the `x` axis | -| [`y`](#topology-joint-origin-y){: #topology-joint-origin-y } | `any` | `Localize.Unit.new!(0, "meter")` | translation along the `y` axis | -| [`z`](#topology-joint-origin-z){: #topology-joint-origin-z } | `any` | `Localize.Unit.new!(0, "meter")` | translation along the `z` axis | +| [`roll`](#topology-joint-origin-roll){: #topology-joint-origin-roll } | `any` | `BB.Unit.new!(0, "degree")` | rotation around the `x` axis | +| [`pitch`](#topology-joint-origin-pitch){: #topology-joint-origin-pitch } | `any` | `BB.Unit.new!(0, "degree")` | rotation around the `y` axis | +| [`yaw`](#topology-joint-origin-yaw){: #topology-joint-origin-yaw } | `any` | `BB.Unit.new!(0, "degree")` | rotation around the `z` axis | +| [`x`](#topology-joint-origin-x){: #topology-joint-origin-x } | `any` | `BB.Unit.new!(0, "meter")` | translation along the `x` axis | +| [`y`](#topology-joint-origin-y){: #topology-joint-origin-y } | `any` | `BB.Unit.new!(0, "meter")` | translation along the `y` axis | +| [`z`](#topology-joint-origin-z){: #topology-joint-origin-z } | `any` | `BB.Unit.new!(0, "meter")` | translation along the `z` axis | @@ -1040,9 +1040,9 @@ What it means depends on the joint type: | Name | Type | Default | Docs | |------|------|---------|------| -| [`roll`](#topology-joint-axis-roll){: #topology-joint-axis-roll } | `any` | `Localize.Unit.new!(0, "degree")` | rotation around the X axis | -| [`pitch`](#topology-joint-axis-pitch){: #topology-joint-axis-pitch } | `any` | `Localize.Unit.new!(0, "degree")` | rotation around the Y axis | -| [`yaw`](#topology-joint-axis-yaw){: #topology-joint-axis-yaw } | `any` | `Localize.Unit.new!(0, "degree")` | rotation around the Z axis | +| [`roll`](#topology-joint-axis-roll){: #topology-joint-axis-roll } | `any` | `BB.Unit.new!(0, "degree")` | rotation around the X axis | +| [`pitch`](#topology-joint-axis-pitch){: #topology-joint-axis-pitch } | `any` | `BB.Unit.new!(0, "degree")` | rotation around the Y axis | +| [`yaw`](#topology-joint-axis-yaw){: #topology-joint-axis-yaw } | `any` | `BB.Unit.new!(0, "degree")` | rotation around the Z axis | diff --git a/lib/bb/dsl/axis.ex b/lib/bb/dsl/axis.ex index df1d571..dd161cb 100644 --- a/lib/bb/dsl/axis.ex +++ b/lib/bb/dsl/axis.ex @@ -41,8 +41,8 @@ defmodule BB.Dsl.Axis do @type t :: %__MODULE__{ __identifier__: any, __spark_metadata__: Entity.spark_meta(), - roll: Localize.Unit.t(), - pitch: Localize.Unit.t(), - yaw: Localize.Unit.t() + roll: BB.Unit.t(), + pitch: BB.Unit.t(), + yaw: BB.Unit.t() } end diff --git a/lib/bb/dsl/box.ex b/lib/bb/dsl/box.ex index 2d9f4f7..f488069 100644 --- a/lib/bb/dsl/box.ex +++ b/lib/bb/dsl/box.ex @@ -12,7 +12,7 @@ defmodule BB.Dsl.Box do y: nil, z: nil - alias Localize.Unit + alias BB.Unit alias Spark.Dsl.Entity @type t :: %__MODULE__{ diff --git a/lib/bb/dsl/capsule.ex b/lib/bb/dsl/capsule.ex index 58abfde..30dffe5 100644 --- a/lib/bb/dsl/capsule.ex +++ b/lib/bb/dsl/capsule.ex @@ -19,7 +19,7 @@ defmodule BB.Dsl.Capsule do radius: nil, height: nil - alias Localize.Unit + alias BB.Unit alias Spark.Dsl.Entity @type t :: %__MODULE__{ diff --git a/lib/bb/dsl/cylinder.ex b/lib/bb/dsl/cylinder.ex index c83de77..adca13e 100644 --- a/lib/bb/dsl/cylinder.ex +++ b/lib/bb/dsl/cylinder.ex @@ -11,7 +11,7 @@ defmodule BB.Dsl.Cylinder do radius: nil, height: nil - alias Localize.Unit + alias BB.Unit alias Spark.Dsl.Entity @type t :: %__MODULE__{ diff --git a/lib/bb/dsl/dynamics.ex b/lib/bb/dsl/dynamics.ex index 632a2af..c577fd7 100644 --- a/lib/bb/dsl/dynamics.ex +++ b/lib/bb/dsl/dynamics.ex @@ -16,7 +16,7 @@ defmodule BB.Dsl.Dynamics do @type t :: %__MODULE__{ __identifier__: any, __spark_metadata__: Entity.spark_meta(), - damping: nil | Localize.Unit.t(), - friction: nil | Localize.Unit.t() + damping: nil | BB.Unit.t(), + friction: nil | BB.Unit.t() } end diff --git a/lib/bb/dsl/estimator.ex b/lib/bb/dsl/estimator.ex index d802320..486dd0f 100644 --- a/lib/bb/dsl/estimator.ex +++ b/lib/bb/dsl/estimator.ex @@ -38,9 +38,9 @@ defmodule BB.Dsl.Estimator do child_spec: child_spec, inputs: [Input.t()], outputs: [Output.t()], - sync_tolerance: nil | Localize.Unit.t(), - latency_budget: nil | Localize.Unit.t(), - lost_after: nil | Localize.Unit.t(), + sync_tolerance: nil | BB.Unit.t(), + latency_budget: nil | BB.Unit.t(), + lost_after: nil | BB.Unit.t(), recover_after: pos_integer(), on_degraded: nil | atom(), on_lost: nil | atom(), diff --git a/lib/bb/dsl/inertia.ex b/lib/bb/dsl/inertia.ex index bfb5b9d..219df9a 100644 --- a/lib/bb/dsl/inertia.ex +++ b/lib/bb/dsl/inertia.ex @@ -20,11 +20,11 @@ defmodule BB.Dsl.Inertia do @type t :: %__MODULE__{ __identifier__: any, __spark_metadata__: Entity.spark_meta(), - ixx: Localize.Unit.t(), - iyy: Localize.Unit.t(), - izz: Localize.Unit.t(), - ixy: Localize.Unit.t(), - ixz: Localize.Unit.t(), - iyz: Localize.Unit.t() + ixx: BB.Unit.t(), + iyy: BB.Unit.t(), + izz: BB.Unit.t(), + ixy: BB.Unit.t(), + ixz: BB.Unit.t(), + iyz: BB.Unit.t() } end diff --git a/lib/bb/dsl/inertial.ex b/lib/bb/dsl/inertial.ex index f6051cf..fc59f7b 100644 --- a/lib/bb/dsl/inertial.ex +++ b/lib/bb/dsl/inertial.ex @@ -15,7 +15,7 @@ defmodule BB.Dsl.Inertial do __identifier__: any, __spark_metadata__: Entity.spark_meta(), origin: nil | Origin.t(), - mass: Localize.Unit.t(), + mass: BB.Unit.t(), inertia: Inertia.t() } end diff --git a/lib/bb/dsl/limit.ex b/lib/bb/dsl/limit.ex index 42914ae..bda9fc5 100644 --- a/lib/bb/dsl/limit.ex +++ b/lib/bb/dsl/limit.ex @@ -19,10 +19,10 @@ defmodule BB.Dsl.Limit do @type t :: %__MODULE__{ __identifier__: any, __spark_metadata__: Entity.spark_meta(), - lower: nil | Localize.Unit.t(), - upper: nil | Localize.Unit.t(), - effort: Localize.Unit.t(), - velocity: Localize.Unit.t(), - acceleration: nil | Localize.Unit.t() + lower: nil | BB.Unit.t(), + upper: nil | BB.Unit.t(), + effort: BB.Unit.t(), + velocity: BB.Unit.t(), + acceleration: nil | BB.Unit.t() } end diff --git a/lib/bb/dsl/origin.ex b/lib/bb/dsl/origin.ex index b01ee32..6847f09 100644 --- a/lib/bb/dsl/origin.ex +++ b/lib/bb/dsl/origin.ex @@ -22,11 +22,11 @@ defmodule BB.Dsl.Origin do @type t :: %__MODULE__{ __identifier__: any, __spark_metadata__: Entity.spark_meta(), - roll: Localize.Unit.t(), - pitch: Localize.Unit.t(), - yaw: Localize.Unit.t(), - x: Localize.Unit.t(), - y: Localize.Unit.t(), - z: Localize.Unit.t() + roll: BB.Unit.t(), + pitch: BB.Unit.t(), + yaw: BB.Unit.t(), + x: BB.Unit.t(), + y: BB.Unit.t(), + z: BB.Unit.t() } end diff --git a/lib/bb/dsl/sphere.ex b/lib/bb/dsl/sphere.ex index 361ba7e..7b582ab 100644 --- a/lib/bb/dsl/sphere.ex +++ b/lib/bb/dsl/sphere.ex @@ -10,7 +10,7 @@ defmodule BB.Dsl.Sphere do __spark_metadata__: nil, radius: nil - alias Localize.Unit + alias BB.Unit alias Spark.Dsl.Entity @type t :: %__MODULE__{ diff --git a/lib/bb/dsl/transmission.ex b/lib/bb/dsl/transmission.ex index 34c4a51..97b9bba 100644 --- a/lib/bb/dsl/transmission.ex +++ b/lib/bb/dsl/transmission.ex @@ -22,7 +22,7 @@ defmodule BB.Dsl.Transmission do __identifier__: any, __spark_metadata__: Entity.spark_meta(), reduction: number, - offset: nil | Localize.Unit.t(), + offset: nil | BB.Unit.t(), reversed?: boolean } end diff --git a/lib/bb/dsl/uniqueness_transformer.ex b/lib/bb/dsl/uniqueness_transformer.ex index f34237c..3bbe06a 100644 --- a/lib/bb/dsl/uniqueness_transformer.ex +++ b/lib/bb/dsl/uniqueness_transformer.ex @@ -69,10 +69,9 @@ defmodule BB.Dsl.UniquenessTransformer do end end - # `Localize.Unit` and `Decimal` structs carry a `:name` field that is not a - # DSL entity identifier — skip them so they aren't treated as named entities. - defp retrieve_names(%Localize.Unit{}, _path, names), do: names - defp retrieve_names(%Decimal{}, _path, names), do: names + # `BB.Unit` structs carry a `:name` field that is not a DSL entity + # identifier — skip them so they aren't treated as named entities. + defp retrieve_names(%BB.Unit{}, _path, names), do: names # Estimator input/output entities use `:name` as a callback-side key # (matched in the user's `handle_input/2` payload map and `{:reply, [...]}` diff --git a/lib/bb/dsl/validate_limit_units_transformer.ex b/lib/bb/dsl/validate_limit_units_transformer.ex index 4b3f70b..f042980 100644 --- a/lib/bb/dsl/validate_limit_units_transformer.ex +++ b/lib/bb/dsl/validate_limit_units_transformer.ex @@ -159,7 +159,7 @@ defmodule BB.Dsl.ValidateLimitUnitsTransformer do do: {:cont, :ok} defp check_field( - %Localize.Unit{} = value, + %BB.Unit{} = value, section, field, expected_unit, diff --git a/lib/bb/error/invalid/unit.ex b/lib/bb/error/invalid/unit.ex new file mode 100644 index 0000000..a268eae --- /dev/null +++ b/lib/bb/error/invalid/unit.ex @@ -0,0 +1,33 @@ +# SPDX-FileCopyrightText: 2026 James Harton +# +# SPDX-License-Identifier: Apache-2.0 + +defmodule BB.Error.Invalid.Unit do + @moduledoc """ + Invalid unit identifier or incompatible unit conversion. + + Raised when an identifier does not name a unit `BB.Unit.Conversions` can + convert, or when two units are compared or converted across dimensional + categories. + """ + use BB.Error, + class: :invalid, + fields: [:unit, :expected] + + @type t :: %__MODULE__{ + unit: String.t(), + expected: String.t() | nil + } + + defimpl BB.Error.Severity do + def severity(_), do: :error + end + + def message(%{unit: unit, expected: nil}) do + "`#{unit}` is not a known unit" + end + + def message(%{unit: unit, expected: expected}) do + "The unit `#{unit}` is not compatible with `#{expected}`" + end +end diff --git a/lib/bb/estimator/wiring.ex b/lib/bb/estimator/wiring.ex index c98ab47..9485b97 100644 --- a/lib/bb/estimator/wiring.ex +++ b/lib/bb/estimator/wiring.ex @@ -138,7 +138,7 @@ defmodule BB.Estimator.Wiring do defp duration_to_ns(unit) do unit - |> Localize.Unit.convert!("second") + |> BB.Unit.convert!("second") |> Units.extract_float() |> Kernel.*(1_000_000_000) |> round() diff --git a/lib/bb/loop.ex b/lib/bb/loop.ex index 8bac823..d8ea616 100644 --- a/lib/bb/loop.ex +++ b/lib/bb/loop.ex @@ -173,7 +173,7 @@ defmodule BB.Loop do ## Options - `:clock` (required) - `{:rate, hertz}` or `:external`. A rate may be given - as a `Localize.Unit` in any frequency unit (`~u(100 hertz)`) or as a plain + as a `BB.Unit` in any frequency unit (`~u(100 hertz)`) or as a plain positive number of hertz. Building a loop does not start it; call `arm/1` once the component is ready to @@ -327,9 +327,9 @@ defmodule BB.Loop do defp period_ns(:external), do: nil defp period_ns({:rate, rate}), do: round(@ns_per_second / hertz(rate)) - defp hertz(%Localize.Unit{} = rate) do + defp hertz(%BB.Unit{} = rate) do rate - |> Localize.Unit.convert!("hertz") + |> BB.Unit.convert!("hertz") |> Units.extract_float() |> hertz() end diff --git a/lib/bb/robot/units.ex b/lib/bb/robot/units.ex index aea8765..5972256 100644 --- a/lib/bb/robot/units.ex +++ b/lib/bb/robot/units.ex @@ -4,10 +4,10 @@ defmodule BB.Robot.Units do @moduledoc """ - Unit conversion functions for transforming `Localize.Unit` values into base + Unit conversion functions for transforming `BB.Unit` values into base SI floats. - All functions in this module convert from `Localize.Unit.t()` structs to + All functions in this module convert from `BB.Unit.t()` structs to native floats in SI base units, suitable for efficient numerical computation. """ @@ -25,10 +25,10 @@ defmodule BB.Robot.Units do iex> BB.Robot.Units.to_meters(~u(1.5 meter)) 1.5 """ - @spec to_meters(Localize.Unit.t()) :: float() - def to_meters(%Localize.Unit{} = unit) do + @spec to_meters(BB.Unit.t()) :: float() + def to_meters(%BB.Unit{} = unit) do unit - |> Localize.Unit.convert!("meter") + |> BB.Unit.convert!("meter") |> extract_float() end @@ -45,10 +45,10 @@ defmodule BB.Robot.Units do iex> BB.Robot.Units.to_radians(~u(0 degree)) 0.0 """ - @spec to_radians(Localize.Unit.t()) :: float() - def to_radians(%Localize.Unit{} = unit) do + @spec to_radians(BB.Unit.t()) :: float() + def to_radians(%BB.Unit{} = unit) do unit - |> Localize.Unit.convert!("radian") + |> BB.Unit.convert!("radian") |> extract_float() end @@ -65,10 +65,10 @@ defmodule BB.Robot.Units do iex> BB.Robot.Units.to_kilograms(~u(2.5 kilogram)) 2.5 """ - @spec to_kilograms(Localize.Unit.t()) :: float() - def to_kilograms(%Localize.Unit{} = unit) do + @spec to_kilograms(BB.Unit.t()) :: float() + def to_kilograms(%BB.Unit{} = unit) do unit - |> Localize.Unit.convert!("kilogram") + |> BB.Unit.convert!("kilogram") |> extract_float() end @@ -81,10 +81,10 @@ defmodule BB.Robot.Units do iex> BB.Robot.Units.to_kilogram_square_meters(~u(0.5 kilogram_square_meter)) 0.5 """ - @spec to_kilogram_square_meters(Localize.Unit.t()) :: float() - def to_kilogram_square_meters(%Localize.Unit{} = unit) do + @spec to_kilogram_square_meters(BB.Unit.t()) :: float() + def to_kilogram_square_meters(%BB.Unit{} = unit) do unit - |> Localize.Unit.convert!("kilogram-square-meter") + |> BB.Unit.convert!("kilogram-square-meter") |> extract_float() end @@ -97,10 +97,10 @@ defmodule BB.Robot.Units do iex> BB.Robot.Units.to_newtons(~u(10 newton)) 10.0 """ - @spec to_newtons(Localize.Unit.t()) :: float() - def to_newtons(%Localize.Unit{} = unit) do + @spec to_newtons(BB.Unit.t()) :: float() + def to_newtons(%BB.Unit{} = unit) do unit - |> Localize.Unit.convert!("newton") + |> BB.Unit.convert!("newton") |> extract_float() end @@ -113,10 +113,10 @@ defmodule BB.Robot.Units do iex> BB.Robot.Units.to_newton(~u(5 newton)) 5.0 """ - @spec to_newton(Localize.Unit.t()) :: float() - def to_newton(%Localize.Unit{} = unit) do + @spec to_newton(BB.Unit.t()) :: float() + def to_newton(%BB.Unit{} = unit) do unit - |> Localize.Unit.convert!("newton") + |> BB.Unit.convert!("newton") |> extract_float() end @@ -129,10 +129,10 @@ defmodule BB.Robot.Units do iex> BB.Robot.Units.to_newton_meters(~u(5 newton_meter)) 5.0 """ - @spec to_newton_meters(Localize.Unit.t()) :: float() - def to_newton_meters(%Localize.Unit{} = unit) do + @spec to_newton_meters(BB.Unit.t()) :: float() + def to_newton_meters(%BB.Unit{} = unit) do unit - |> Localize.Unit.convert!("newton-meter") + |> BB.Unit.convert!("newton-meter") |> extract_float() end @@ -145,10 +145,10 @@ defmodule BB.Robot.Units do iex> BB.Robot.Units.to_meters_per_second(~u(10 meter_per_second)) 10.0 """ - @spec to_meters_per_second(Localize.Unit.t()) :: float() - def to_meters_per_second(%Localize.Unit{} = unit) do + @spec to_meters_per_second(BB.Unit.t()) :: float() + def to_meters_per_second(%BB.Unit{} = unit) do unit - |> Localize.Unit.convert!("meter-per-second") + |> BB.Unit.convert!("meter-per-second") |> extract_float() end @@ -161,10 +161,10 @@ defmodule BB.Robot.Units do iex> BB.Robot.Units.to_radians_per_second(~u(180 degree_per_second)) :math.pi() """ - @spec to_radians_per_second(Localize.Unit.t()) :: float() - def to_radians_per_second(%Localize.Unit{} = unit) do + @spec to_radians_per_second(BB.Unit.t()) :: float() + def to_radians_per_second(%BB.Unit{} = unit) do unit - |> Localize.Unit.convert!("radian-per-second") + |> BB.Unit.convert!("radian-per-second") |> extract_float() end @@ -177,10 +177,10 @@ defmodule BB.Robot.Units do iex> BB.Robot.Units.to_meters_per_square_second(~u(9.81 meter_per_square_second)) 9.81 """ - @spec to_meters_per_square_second(Localize.Unit.t()) :: float() - def to_meters_per_square_second(%Localize.Unit{} = unit) do + @spec to_meters_per_square_second(BB.Unit.t()) :: float() + def to_meters_per_square_second(%BB.Unit{} = unit) do unit - |> Localize.Unit.convert!("meter-per-square-second") + |> BB.Unit.convert!("meter-per-square-second") |> extract_float() end @@ -193,10 +193,10 @@ defmodule BB.Robot.Units do iex> BB.Robot.Units.to_radians_per_square_second(~u(360 degree_per_square_second)) :math.pi() * 2 """ - @spec to_radians_per_square_second(Localize.Unit.t()) :: float() - def to_radians_per_square_second(%Localize.Unit{} = unit) do + @spec to_radians_per_square_second(BB.Unit.t()) :: float() + def to_radians_per_square_second(%BB.Unit{} = unit) do unit - |> Localize.Unit.convert!("radian-per-square-second") + |> BB.Unit.convert!("radian-per-square-second") |> extract_float() end @@ -209,10 +209,10 @@ defmodule BB.Robot.Units do iex> BB.Robot.Units.to_linear_damping(~u(1.5 newton_second_per_meter)) 1.5 """ - @spec to_linear_damping(Localize.Unit.t()) :: float() - def to_linear_damping(%Localize.Unit{} = unit) do + @spec to_linear_damping(BB.Unit.t()) :: float() + def to_linear_damping(%BB.Unit{} = unit) do unit - |> Localize.Unit.convert!("newton-second-per-meter") + |> BB.Unit.convert!("newton-second-per-meter") |> extract_float() end @@ -222,79 +222,73 @@ defmodule BB.Robot.Units do Note: The DSL uses `newton_meter_second_per_degree` but we convert to radians for consistency with other angular quantities. """ - @spec to_rotational_damping(Localize.Unit.t()) :: float() - def to_rotational_damping(%Localize.Unit{} = unit) do + @spec to_rotational_damping(BB.Unit.t()) :: float() + def to_rotational_damping(%BB.Unit{} = unit) do unit - |> Localize.Unit.convert!("newton-meter-second-per-radian") + |> BB.Unit.convert!("newton-meter-second-per-radian") |> extract_float() end @doc """ - Extract the numeric value from a `Localize.Unit` as a float. - - Handles integer, float, and `Decimal` values. + Extract the numeric value from a `BB.Unit` as a float. """ - @spec extract_float(Localize.Unit.t()) :: float() - def extract_float(%Localize.Unit{value: value}) when is_integer(value) do + @spec extract_float(BB.Unit.t()) :: float() + def extract_float(%BB.Unit{value: value}) when is_integer(value) do value / 1 end - def extract_float(%Localize.Unit{value: value}) when is_float(value) do + def extract_float(%BB.Unit{value: value}) when is_float(value) do value end - def extract_float(%Localize.Unit{value: %Decimal{} = value}) do - Decimal.to_float(value) - end - @doc """ Convert an optional unit value to its base SI float, or return nil. """ - @spec to_meters_or_nil(Localize.Unit.t() | nil) :: float() | nil + @spec to_meters_or_nil(BB.Unit.t() | nil) :: float() | nil def to_meters_or_nil(nil), do: nil def to_meters_or_nil(unit), do: to_meters(unit) - @spec to_radians_or_nil(Localize.Unit.t() | nil) :: float() | nil + @spec to_radians_or_nil(BB.Unit.t() | nil) :: float() | nil def to_radians_or_nil(nil), do: nil def to_radians_or_nil(unit), do: to_radians(unit) - @spec to_kilograms_or_nil(Localize.Unit.t() | nil) :: float() | nil + @spec to_kilograms_or_nil(BB.Unit.t() | nil) :: float() | nil def to_kilograms_or_nil(nil), do: nil def to_kilograms_or_nil(unit), do: to_kilograms(unit) - @spec to_kilogram_square_meters_or_nil(Localize.Unit.t() | nil) :: float() | nil + @spec to_kilogram_square_meters_or_nil(BB.Unit.t() | nil) :: float() | nil def to_kilogram_square_meters_or_nil(nil), do: nil def to_kilogram_square_meters_or_nil(unit), do: to_kilogram_square_meters(unit) - @spec to_newtons_or_nil(Localize.Unit.t() | nil) :: float() | nil + @spec to_newtons_or_nil(BB.Unit.t() | nil) :: float() | nil def to_newtons_or_nil(nil), do: nil def to_newtons_or_nil(unit), do: to_newtons(unit) - @spec to_newton_meters_or_nil(Localize.Unit.t() | nil) :: float() | nil + @spec to_newton_meters_or_nil(BB.Unit.t() | nil) :: float() | nil def to_newton_meters_or_nil(nil), do: nil def to_newton_meters_or_nil(unit), do: to_newton_meters(unit) - @spec to_meters_per_second_or_nil(Localize.Unit.t() | nil) :: float() | nil + @spec to_meters_per_second_or_nil(BB.Unit.t() | nil) :: float() | nil def to_meters_per_second_or_nil(nil), do: nil def to_meters_per_second_or_nil(unit), do: to_meters_per_second(unit) - @spec to_radians_per_second_or_nil(Localize.Unit.t() | nil) :: float() | nil + @spec to_radians_per_second_or_nil(BB.Unit.t() | nil) :: float() | nil def to_radians_per_second_or_nil(nil), do: nil def to_radians_per_second_or_nil(unit), do: to_radians_per_second(unit) - @spec to_meters_per_square_second_or_nil(Localize.Unit.t() | nil) :: float() | nil + @spec to_meters_per_square_second_or_nil(BB.Unit.t() | nil) :: float() | nil def to_meters_per_square_second_or_nil(nil), do: nil def to_meters_per_square_second_or_nil(unit), do: to_meters_per_square_second(unit) - @spec to_radians_per_square_second_or_nil(Localize.Unit.t() | nil) :: float() | nil + @spec to_radians_per_square_second_or_nil(BB.Unit.t() | nil) :: float() | nil def to_radians_per_square_second_or_nil(nil), do: nil def to_radians_per_square_second_or_nil(unit), do: to_radians_per_square_second(unit) - @spec to_linear_damping_or_nil(Localize.Unit.t() | nil) :: float() | nil + @spec to_linear_damping_or_nil(BB.Unit.t() | nil) :: float() | nil def to_linear_damping_or_nil(nil), do: nil def to_linear_damping_or_nil(unit), do: to_linear_damping(unit) - @spec to_rotational_damping_or_nil(Localize.Unit.t() | nil) :: float() | nil + @spec to_rotational_damping_or_nil(BB.Unit.t() | nil) :: float() | nil def to_rotational_damping_or_nil(nil), do: nil def to_rotational_damping_or_nil(unit), do: to_rotational_damping(unit) end diff --git a/lib/bb/sensor/open_loop_position_estimator.ex b/lib/bb/sensor/open_loop_position_estimator.ex index f0949a9..b4c3e59 100644 --- a/lib/bb/sensor/open_loop_position_estimator.ex +++ b/lib/bb/sensor/open_loop_position_estimator.ex @@ -133,13 +133,13 @@ defmodule BB.Sensor.OpenLoopPositionEstimator do publish_interval_ms = publish_rate - |> Localize.Unit.convert!("hertz") + |> BB.Unit.convert!("hertz") |> Units.extract_float() |> then(&round(1000 / &1)) max_silence_ms = max_silence - |> Localize.Unit.convert!("second") + |> BB.Unit.convert!("second") |> Units.extract_float() |> then(&round(&1 * 1000)) diff --git a/lib/bb/transmission/resolver.ex b/lib/bb/transmission/resolver.ex index 2798045..2be82cd 100644 --- a/lib/bb/transmission/resolver.ex +++ b/lib/bb/transmission/resolver.ex @@ -171,13 +171,13 @@ defmodule BB.Transmission.Resolver do defp convert_for_field(:reversed?, value, _joint_type) when is_boolean(value), do: value - defp convert_for_field(:offset, %Localize.Unit{} = value, type) + defp convert_for_field(:offset, %BB.Unit{} = value, type) when type in [:revolute, :continuous], do: Units.to_radians(value) - defp convert_for_field(:offset, %Localize.Unit{} = value, :prismatic), + defp convert_for_field(:offset, %BB.Unit{} = value, :prismatic), do: Units.to_meters(value) - defp convert_for_field(:offset, %Localize.Unit{} = value, _), + defp convert_for_field(:offset, %BB.Unit{} = value, _), do: Units.to_radians(value) end diff --git a/lib/bb/unit.ex b/lib/bb/unit.ex index 8c4be59..8591e4e 100644 --- a/lib/bb/unit.ex +++ b/lib/bb/unit.ex @@ -4,14 +4,155 @@ defmodule BB.Unit do @moduledoc """ - Helpers for working with units in BB DSLs. + A physical quantity — a magnitude and the unit it is measured in. + + Conversion is backed by `BB.Unit.Conversions`, a module generated from CLDR + data by [Localize](https://hexdocs.pm/localize) and committed into this + repository. Localize itself is a build-time dependency only, so a robot + running on a Nerves target carries the conversion tables rather than the + whole localisation library. - Wraps `Localize.Unit` and provides a sigil for compact unit literals. Unit identifiers can be passed as either atoms (`:newton_meter`) or strings (`"newton-meter"`); the helpers in this module convert atoms with underscores into the CLDR canonical dash form before passing them through. """ + alias BB.Error.Invalid + alias BB.Unit.Conversions + + defstruct [:name, :value] + + @type t :: %__MODULE__{name: String.t(), value: number} + + @doc """ + Compare two units of the same dimensional category. + + Both are reduced to their common base unit before comparing, so units of + differing scale compare correctly. + + ## Examples + + iex> import BB.Unit + iex> BB.Unit.compare(~u(100 centimeter), ~u(1 meter)) + :eq + + iex> import BB.Unit + iex> BB.Unit.compare(~u(90 degree), ~u(1 radian)) + :gt + + Incompatible units return an error: + + iex> import BB.Unit + iex> {:error, error} = BB.Unit.compare(~u(1 meter), ~u(1 second)) + iex> Exception.message(error) + "The unit `meter` is not compatible with `second`" + """ + @spec compare(t, t) :: :lt | :eq | :gt | {:error, Exception.t()} + def compare(%__MODULE__{} = a, %__MODULE__{} = b) do + with {:ok, base_a, value_a} <- to_base(a), + {:ok, base_b, value_b} <- to_base(b) do + cond do + base_a != base_b -> {:error, incompatible(a.name, b.name)} + value_a < value_b -> :lt + value_a > value_b -> :gt + true -> :eq + end + end + end + + @doc """ + Check whether two units belong to the same dimensional category. + + Either argument may be a `t:t/0`, an atom or a string. + + ## Examples + + iex> BB.Unit.compatible?(:centimeter, :meter) + true + + iex> BB.Unit.compatible?(:degree, :meter) + false + """ + @spec compatible?(t | atom | binary, t | atom | binary) :: boolean + def compatible?(a, b) do + case {base_unit(a), base_unit(b)} do + {{:ok, base}, {:ok, base}} -> true + _mismatch -> false + end + end + + @doc """ + Convert a unit to another unit of the same dimensional category. + + ## Examples + + iex> import BB.Unit + iex> {:ok, converted} = BB.Unit.convert(~u(100 centimeter), :meter) + iex> {converted.name, converted.value} + {"meter", 1.0} + + iex> import BB.Unit + iex> {:error, error} = BB.Unit.convert(~u(1 meter), :second) + iex> Exception.message(error) + "The unit `meter` is not compatible with `second`" + """ + @spec convert(t, atom | binary) :: {:ok, t} | {:error, Exception.t()} + def convert(%__MODULE__{} = unit, target) do + target = unit_name(target) + + with {:ok, base, value} <- to_base(unit), + {:ok, {^base, factor, offset}} <- resolve(target) do + {:ok, %__MODULE__{name: target, value: (value - offset) / factor}} + else + {:ok, {_other_base, _factor, _offset}} -> {:error, incompatible(unit.name, target)} + {:error, error} -> {:error, error} + end + end + + @doc """ + Convert a unit to another unit of the same dimensional category, or raise. + """ + @spec convert!(t, atom | binary) :: t | no_return + def convert!(%__MODULE__{} = unit, target) do + case convert(unit, target) do + {:ok, converted} -> converted + {:error, error} -> raise error + end + end + + @doc """ + Build a unit from a magnitude and an identifier. + + ## Examples + + iex> {:ok, unit} = BB.Unit.new(5, :meter) + iex> {unit.name, unit.value} + {"meter", 5} + + iex> {:error, error} = BB.Unit.new(5, :bogus) + iex> Exception.message(error) + "`bogus` is not a known unit" + """ + @spec new(number, atom | binary) :: {:ok, t} | {:error, Exception.t()} + def new(value, name) when is_number(value) do + name = unit_name(name) + + with {:ok, _resolved} <- resolve(name) do + {:ok, %__MODULE__{name: name, value: value}} + end + end + + @doc """ + Build a unit from a magnitude and an identifier, or raise. + """ + @spec new!(number, atom | binary) :: t | no_return + def new!(value, name) do + case new(value, name) do + {:ok, unit} -> unit + {:error, error} -> raise error + end + end + @doc """ Parse a string input as a unit. @@ -19,9 +160,7 @@ defmodule BB.Unit do Whitespace between the magnitude and unit is optional. Units are generally referred to in the singular, even if it doesn't read as - nicely, for example `meter_per_second` rather than `meters_per_second`. For - a full list of supported units, see the - [Localize documentation](https://hexdocs.pm/localize/units.html). + nicely, for example `meter_per_second` rather than `meters_per_second`. ## Examples @@ -60,19 +199,29 @@ defmodule BB.Unit do iex> {u.name, u.value} {"meter-per-second", 10} """ - @spec sigil_u(binary, charlist) :: Localize.Unit.t() | no_return + @spec sigil_u(binary, charlist) :: t | no_return def sigil_u(input, []) do with :error <- maybe_parse_as_integer(input), :error <- maybe_parse_as_float(input) do raise "Invalid input `#{inspect(input)}`" else - {:ok, magnitude, unit} -> Localize.Unit.new!(magnitude, unit_name(unit)) + {:ok, magnitude, unit} -> new!(magnitude, unit) end end + @doc """ + Render a unit as its magnitude followed by its CLDR identifier. + + iex> import BB.Unit + iex> BB.Unit.to_string!(~u(1.5 meter_per_second)) + "1.5 meter-per-second" + """ + @spec to_string!(t) :: String.t() + def to_string!(%__MODULE__{name: name, value: value}), do: "#{value} #{name}" + @doc """ Convert an atom or underscored string unit identifier to the CLDR canonical - dash form that `Localize.Unit` expects. + dash form. iex> BB.Unit.unit_name(:newton_meter) "newton-meter" @@ -90,35 +239,35 @@ defmodule BB.Unit do @doc """ Validate that an identifier resolves to a known unit. - Returns `{:ok, unit}` for a known unit, `{:error, exception}` otherwise. + Returns `{:ok, canonical_name}` for a known unit, `{:error, exception}` + otherwise. + + iex> BB.Unit.validate_unit(:newton_meter) + {:ok, "newton-meter"} """ - @spec validate_unit(atom | binary) :: {:ok, Localize.Unit.t()} | {:error, Exception.t()} - def validate_unit(name), do: name |> unit_name() |> Localize.Unit.new() + @spec validate_unit(atom | binary) :: {:ok, binary} | {:error, Exception.t()} + def validate_unit(name) do + name = unit_name(name) - @doc """ - Check whether two units belong to the same dimensional category. + with {:ok, _resolved} <- resolve(name) do + {:ok, name} + end + end - Accepts atoms or strings as the second argument and translates them to the - CLDR canonical form. Either argument may also be a `Localize.Unit` struct. - """ - @spec compatible?(Localize.Unit.t() | atom | binary, Localize.Unit.t() | atom | binary) :: - boolean - def compatible?(a, b) when is_atom(a), do: compatible?(unit_name(a), b) - def compatible?(a, b) when is_atom(b), do: compatible?(a, unit_name(b)) - def compatible?(a, b), do: Localize.Unit.compatible?(a, b) - - @doc "Delegates to `Localize.Unit.compare/2`." - @spec compare(Localize.Unit.t(), Localize.Unit.t()) :: - :lt | :eq | :gt | {:error, Exception.t()} - defdelegate compare(a, b), to: Localize.Unit - - @doc "Delegates to `Localize.Unit.to_string!/2`." - @spec to_string!(Localize.Unit.t() | [Localize.Unit.t()], keyword) :: String.t() - defdelegate to_string!(unit, options \\ []), to: Localize.Unit - - defimpl Inspect, for: Localize.Unit do - def inspect(%Localize.Unit{name: name, value: value}, _opts) do - "Localize.Unit.new!(#{Kernel.inspect(value)}, #{Kernel.inspect(name)})" + defp base_unit(%__MODULE__{name: name}), do: base_unit(name) + + defp base_unit(name) do + with {:ok, {base, _factor, _offset}} <- resolve(unit_name(name)) do + {:ok, base} + end + end + + defp incompatible(name, expected), do: Invalid.Unit.exception(unit: name, expected: expected) + + defp maybe_parse_as_float(input) do + case Float.parse(input) do + {magnitude, unit} -> {:ok, magnitude, String.trim(unit)} + :error -> :error end end @@ -130,10 +279,22 @@ defmodule BB.Unit do end end - defp maybe_parse_as_float(input) do - case Float.parse(input) do - {magnitude, unit} -> {:ok, magnitude, String.trim(unit)} - :error -> :error + defp resolve(name) do + case Conversions.resolve(name) do + {:ok, resolved} -> {:ok, resolved} + {:error, {:unknown_unit, unknown}} -> {:error, Invalid.Unit.exception(unit: unknown)} end end + + defp to_base(%__MODULE__{name: name, value: value}) do + with {:ok, {base, factor, offset}} <- resolve(name) do + {:ok, base, value * factor + offset} + end + end +end + +defimpl Inspect, for: BB.Unit do + def inspect(%BB.Unit{name: name, value: value}, _opts) do + "BB.Unit.new!(#{Kernel.inspect(value)}, #{Kernel.inspect(name)})" + end end diff --git a/lib/bb/unit/conversions.ex b/lib/bb/unit/conversions.ex new file mode 100644 index 0000000..e12df8b --- /dev/null +++ b/lib/bb/unit/conversions.ex @@ -0,0 +1,4753 @@ +# Generated by `mix localize.unit.gen_conversions`. Do not edit. +# +# CLDR: 48.2.2 +# +# Regenerate with: +# +# mix localize.unit.gen_conversions --module BB.Unit.Conversions + +defmodule BB.Unit.Conversions.Parser.Helpers do + @moduledoc false + + # Runtime helpers called by the pre-compiled parser, and the post-traverse + # callbacks its grammar refers to by name. + + # Runtime helper functions called by the pre-compiled parser. + # These are extracted from the combinator module so the + # NimbleParsec dependency is not needed at runtime. + + def wrap_pow([_pow_string, digits]) do + {:power, {:pow, String.to_integer(digits)}} + end + + def wrap_constant(parts) do + {:constant, Enum.join(parts)} + end + + def wrap_currency_unit([code]) do + {:single_unit, prefix: nil, power: nil, base: "curr-" <> String.upcase(code)} + end + + def wrap_single_unit(parts) do + power = + case Enum.find(parts, fn + {:power, _} -> true + _ -> false + end) do + {:power, value} -> value + nil -> nil + end + + si_prefix = + case Enum.find(parts, &match?({:si_prefix, _}, &1)) do + {:si_prefix, value} -> si_prefix_atom(value) + nil -> nil + end + + base = + case Enum.find(parts, &match?({:base, _}, &1)) do + {:base, value} -> value + nil -> nil + end + + {:single_unit, prefix: si_prefix, power: power, base: base} + end + + def wrap_per_leading(products) do + denominator = Enum.flat_map(products, fn {:product, units} -> units end) + {:unit, type: nil, numerator: [], denominator: denominator} + end + + def wrap_core_unit([{:product, numerator}]) do + {:unit, type: nil, numerator: numerator, denominator: []} + end + + def wrap_core_unit([{:product, numerator} | denominator_products]) do + denominator = Enum.flat_map(denominator_products, fn {:product, units} -> units end) + {:unit, type: nil, numerator: numerator, denominator: denominator} + end + + def wrap_long_unit([category_name, {:unit, keyword}]) do + {:unit, Keyword.put(keyword, :type, category_name)} + end + + def wrap_mixed_unit(units) do + {:mixed_unit, units} + end + + @si_prefix_atoms %{ + "quecto" => :quecto, + "ronto" => :ronto, + "yocto" => :yocto, + "zepto" => :zepto, + "atto" => :atto, + "femto" => :femto, + "pico" => :pico, + "nano" => :nano, + "micro" => :micro, + "milli" => :milli, + "centi" => :centi, + "deci" => :deci, + "deka" => :deka, + "hecto" => :hecto, + "kilo" => :kilo, + "mega" => :mega, + "giga" => :giga, + "tera" => :tera, + "peta" => :peta, + "exa" => :exa, + "zetta" => :zetta, + "yotta" => :yotta, + "ronna" => :ronna, + "quetta" => :quetta, + "kibi" => :kibi, + "mebi" => :mebi, + "gibi" => :gibi, + "tebi" => :tebi, + "pebi" => :pebi, + "exbi" => :exbi, + "zebi" => :zebi, + "yobi" => :yobi + } + + defp si_prefix_atom(name), do: Map.get(@si_prefix_atoms, name) + + @reserved_categories MapSet.new([ + "acceleration", + "angle", + "area", + "concentr", + "consumption", + "digital", + "duration", + "electric", + "energy", + "force", + "frequency", + "graphics", + "length", + "light", + "magnetic", + "mass", + "power", + "pressure", + "speed", + "temperature", + "torque", + "volume" + ]) + + def reject_categories(rest, [name] = args, context, _line, _offset) do + if MapSet.member?(@reserved_categories, name) do + {:error, "#{name} is a reserved category name"} + else + {rest, args, context} + end + end + + # Custom units are registered at runtime against Localize, and a generated + # module has no registry, so this set is permanently empty. It stays a set + # rather than becoming a literal `false` because the compiler would then + # see every branch guarding on it as unreachable and warn — in a file the + # caller is expected to commit and compile with `--warnings-as-errors`. + @custom_units MapSet.new() + + def custom_unit?(name), do: MapSet.member?(@custom_units, name) + + def validate_prefixed_custom_unit(rest, args, context, _line, _offset) do + case List.keyfind(args, :base, 0) do + {:base, base} when base != nil -> + if custom_unit?(base) do + {rest, args, context} + else + {:error, "#{base} is not a registered custom unit"} + end + + _other -> + {:error, "no base unit found"} + end + end +end + +defmodule BB.Unit.Conversions.Parser do + import BB.Unit.Conversions.Parser.Helpers + + @spec parse(String.t()) :: {:ok, tuple()} | {:error, Exception.t()} + + def parse(input) when is_binary(input) do + cap = max_unit_bytes() + + cond do + byte_size(input) > cap -> + {:error, + parse_error( + reason: :input_too_large, + detail: "unit identifier", + size: byte_size(input), + limit: cap + )} + + custom_unit_name?(input) -> + {:ok, custom_unit_ast(String.trim(input))} + + true -> + do_parse(input) + end + end + + # A runtime-registered custom unit name matches wholesale before + # the grammar runs: hyphenated custom names ("double-cubit") would + # otherwise be split at the hyphens, which the grammar treats as + # the compound product operator. Standard hyphenated identifiers + # ("g-force", "kilowatt-hour") are compiled into the grammar's + # base-unit alternation and take the normal path. The AST shape is + # exactly what the grammar produces for a bare custom base. + defp custom_unit_name?(input) do + custom_unit?(String.trim(input)) + end + + defp custom_unit_ast(name) do + {:unit, + [ + type: nil, + numerator: [{:single_unit, [prefix: nil, power: nil, base: name]}], + denominator: [] + ]} + end + + # Maximum byte length accepted by `parse/1`/`parse!/1`. Caps the + # parser's CPU exposure on hostile input. Override with + # `config :localize, :max_unit_bytes, n` (default 256 — even a + # heavily-compounded `meter-per-second-per-second-per-…` fits well + # under this). + @default_max_unit_bytes 256 + + def max_unit_bytes do + @default_max_unit_bytes + end + + defp do_parse(input) do + case unit_identifier(input) do + {:ok, [result], "", _, _, _} -> + {:ok, result} + + {:ok, _result, rest, _, _, offset} -> + {:error, + parse_error( + input: input, + reason: :unexpected_trailing_input, + offset: offset, + rest: rest + )} + + {:error, detail, rest, _, _, offset} -> + {:error, + parse_error( + input: input, + reason: :unexpected_input, + detail: detail, + offset: offset, + rest: rest + )} + end + end + + @spec parse!(String.t()) :: {atom(), keyword()} | no_return() + @dialyzer {:nowarn_function, parse!: 1} + + def parse!(input) when is_binary(input) do + case parse(input) do + {:ok, parsed} -> parsed + {:error, exception} -> raise exception + end + end + + @spec unit_identifier(binary, keyword) :: + {:ok, [term], rest, context, line, byte_offset} + | {:error, reason, rest, context, line, byte_offset} + when line: {pos_integer, byte_offset}, + byte_offset: non_neg_integer, + rest: binary, + reason: String.t(), + context: map + def unit_identifier(binary, opts \\ []) when is_binary(binary) do + context = Map.new(Keyword.get(opts, :context, [])) + byte_offset = Keyword.get(opts, :byte_offset, 0) + + line = + case Keyword.get(opts, :line, 1) do + {_, _} = line -> line + line -> {line, byte_offset} + end + + case unit_identifier__0(binary, [], [], context, line, byte_offset) do + {:ok, acc, rest, context, line, offset} -> + {:ok, :lists.reverse(acc), rest, context, line, offset} + + {:error, _, _, _, _, _} = error -> + error + end + end + + defp unit_identifier__0(rest, acc, stack, context, line, offset) do + unit_identifier__52( + rest, + [], + [{rest, context, line, offset}, acc | stack], + context, + line, + offset + ) + end + + defp unit_identifier__2(rest, acc, stack, context, line, offset) do + unit_identifier__3(rest, [], [acc | stack], context, line, offset) + end + + defp unit_identifier__3( + <<"acceleration", rest::binary>>, + acc, + stack, + context, + comb__line, + comb__offset + ) do + unit_identifier__4( + rest, + ["acceleration"] ++ acc, + stack, + context, + comb__line, + comb__offset + 12 + ) + end + + defp unit_identifier__3( + <<"consumption", rest::binary>>, + acc, + stack, + context, + comb__line, + comb__offset + ) do + unit_identifier__4( + rest, + ["consumption"] ++ acc, + stack, + context, + comb__line, + comb__offset + 11 + ) + end + + defp unit_identifier__3( + <<"temperature", rest::binary>>, + acc, + stack, + context, + comb__line, + comb__offset + ) do + unit_identifier__4( + rest, + ["temperature"] ++ acc, + stack, + context, + comb__line, + comb__offset + 11 + ) + end + + defp unit_identifier__3( + <<"frequency", rest::binary>>, + acc, + stack, + context, + comb__line, + comb__offset + ) do + unit_identifier__4(rest, ["frequency"] ++ acc, stack, context, comb__line, comb__offset + 9) + end + + defp unit_identifier__3( + <<"concentr", rest::binary>>, + acc, + stack, + context, + comb__line, + comb__offset + ) do + unit_identifier__4(rest, ["concentr"] ++ acc, stack, context, comb__line, comb__offset + 8) + end + + defp unit_identifier__3( + <<"duration", rest::binary>>, + acc, + stack, + context, + comb__line, + comb__offset + ) do + unit_identifier__4(rest, ["duration"] ++ acc, stack, context, comb__line, comb__offset + 8) + end + + defp unit_identifier__3( + <<"electric", rest::binary>>, + acc, + stack, + context, + comb__line, + comb__offset + ) do + unit_identifier__4(rest, ["electric"] ++ acc, stack, context, comb__line, comb__offset + 8) + end + + defp unit_identifier__3( + <<"graphics", rest::binary>>, + acc, + stack, + context, + comb__line, + comb__offset + ) do + unit_identifier__4(rest, ["graphics"] ++ acc, stack, context, comb__line, comb__offset + 8) + end + + defp unit_identifier__3( + <<"magnetic", rest::binary>>, + acc, + stack, + context, + comb__line, + comb__offset + ) do + unit_identifier__4(rest, ["magnetic"] ++ acc, stack, context, comb__line, comb__offset + 8) + end + + defp unit_identifier__3( + <<"pressure", rest::binary>>, + acc, + stack, + context, + comb__line, + comb__offset + ) do + unit_identifier__4(rest, ["pressure"] ++ acc, stack, context, comb__line, comb__offset + 8) + end + + defp unit_identifier__3( + <<"digital", rest::binary>>, + acc, + stack, + context, + comb__line, + comb__offset + ) do + unit_identifier__4(rest, ["digital"] ++ acc, stack, context, comb__line, comb__offset + 7) + end + + defp unit_identifier__3( + <<"energy", rest::binary>>, + acc, + stack, + context, + comb__line, + comb__offset + ) do + unit_identifier__4(rest, ["energy"] ++ acc, stack, context, comb__line, comb__offset + 6) + end + + defp unit_identifier__3( + <<"length", rest::binary>>, + acc, + stack, + context, + comb__line, + comb__offset + ) do + unit_identifier__4(rest, ["length"] ++ acc, stack, context, comb__line, comb__offset + 6) + end + + defp unit_identifier__3( + <<"torque", rest::binary>>, + acc, + stack, + context, + comb__line, + comb__offset + ) do + unit_identifier__4(rest, ["torque"] ++ acc, stack, context, comb__line, comb__offset + 6) + end + + defp unit_identifier__3( + <<"volume", rest::binary>>, + acc, + stack, + context, + comb__line, + comb__offset + ) do + unit_identifier__4(rest, ["volume"] ++ acc, stack, context, comb__line, comb__offset + 6) + end + + defp unit_identifier__3( + <<"angle", rest::binary>>, + acc, + stack, + context, + comb__line, + comb__offset + ) do + unit_identifier__4(rest, ["angle"] ++ acc, stack, context, comb__line, comb__offset + 5) + end + + defp unit_identifier__3( + <<"force", rest::binary>>, + acc, + stack, + context, + comb__line, + comb__offset + ) do + unit_identifier__4(rest, ["force"] ++ acc, stack, context, comb__line, comb__offset + 5) + end + + defp unit_identifier__3( + <<"light", rest::binary>>, + acc, + stack, + context, + comb__line, + comb__offset + ) do + unit_identifier__4(rest, ["light"] ++ acc, stack, context, comb__line, comb__offset + 5) + end + + defp unit_identifier__3( + <<"power", rest::binary>>, + acc, + stack, + context, + comb__line, + comb__offset + ) do + unit_identifier__4(rest, ["power"] ++ acc, stack, context, comb__line, comb__offset + 5) + end + + defp unit_identifier__3( + <<"speed", rest::binary>>, + acc, + stack, + context, + comb__line, + comb__offset + ) do + unit_identifier__4(rest, ["speed"] ++ acc, stack, context, comb__line, comb__offset + 5) + end + + defp unit_identifier__3(<<"area", rest::binary>>, acc, stack, context, comb__line, comb__offset) do + unit_identifier__4(rest, ["area"] ++ acc, stack, context, comb__line, comb__offset + 4) + end + + defp unit_identifier__3(<<"mass", rest::binary>>, acc, stack, context, comb__line, comb__offset) do + unit_identifier__4(rest, ["mass"] ++ acc, stack, context, comb__line, comb__offset + 4) + end + + defp unit_identifier__3(rest, _acc, _stack, context, line, offset) do + {:error, + "expected single_unit_p, followed by string \"-and-\", followed by single_unit_p, followed by string \"-and-\", followed by single_unit_p or string \"per-\", followed by product_unit_p, followed by string \"-per-\", followed by product_unit_p or product_unit_p, followed by string \"-per-\", followed by product_unit_p or string \"acceleration\" or string \"consumption\" or string \"temperature\" or string \"frequency\" or string \"concentr\" or string \"duration\" or string \"electric\" or string \"graphics\" or string \"magnetic\" or string \"pressure\" or string \"digital\" or string \"energy\" or string \"length\" or string \"torque\" or string \"volume\" or string \"angle\" or string \"force\" or string \"light\" or string \"power\" or string \"speed\" or string \"area\" or string \"mass\", followed by string \"-\", followed by string \"per-\", followed by product_unit_p, followed by string \"-per-\", followed by product_unit_p or product_unit_p, followed by string \"-per-\", followed by product_unit_p", + rest, context, line, offset} + end + + defp unit_identifier__4(<<"-", rest::binary>>, acc, stack, context, comb__line, comb__offset) do + unit_identifier__5(rest, [] ++ acc, stack, context, comb__line, comb__offset + 1) + end + + defp unit_identifier__4(rest, _acc, _stack, context, line, offset) do + {:error, + "expected single_unit_p, followed by string \"-and-\", followed by single_unit_p, followed by string \"-and-\", followed by single_unit_p or string \"per-\", followed by product_unit_p, followed by string \"-per-\", followed by product_unit_p or product_unit_p, followed by string \"-per-\", followed by product_unit_p or string \"acceleration\" or string \"consumption\" or string \"temperature\" or string \"frequency\" or string \"concentr\" or string \"duration\" or string \"electric\" or string \"graphics\" or string \"magnetic\" or string \"pressure\" or string \"digital\" or string \"energy\" or string \"length\" or string \"torque\" or string \"volume\" or string \"angle\" or string \"force\" or string \"light\" or string \"power\" or string \"speed\" or string \"area\" or string \"mass\", followed by string \"-\", followed by string \"per-\", followed by product_unit_p, followed by string \"-per-\", followed by product_unit_p or product_unit_p, followed by string \"-per-\", followed by product_unit_p", + rest, context, line, offset} + end + + defp unit_identifier__5(rest, acc, stack, context, line, offset) do + unit_identifier__17( + rest, + [], + [{rest, context, line, offset}, acc | stack], + context, + line, + offset + ) + end + + defp unit_identifier__7(rest, acc, stack, context, line, offset) do + unit_identifier__8(rest, [], [acc | stack], context, line, offset) + end + + defp unit_identifier__8(rest, acc, stack, context, line, offset) do + case product_unit_p__0(rest, acc, [], context, line, offset) do + {:ok, acc, rest, context, line, offset} -> + unit_identifier__9(rest, acc, stack, context, line, offset) + + {:error, _, _, _, _, _} = error -> + error + end + end + + defp unit_identifier__9(rest, acc, stack, context, line, offset) do + unit_identifier__11( + rest, + [], + [{rest, acc, context, line, offset} | stack], + context, + line, + offset + ) + end + + defp unit_identifier__11( + <<"-per-", rest::binary>>, + acc, + stack, + context, + comb__line, + comb__offset + ) do + unit_identifier__12(rest, [] ++ acc, stack, context, comb__line, comb__offset + 5) + end + + defp unit_identifier__11(rest, acc, stack, context, line, offset) do + unit_identifier__10(rest, acc, stack, context, line, offset) + end + + defp unit_identifier__12(rest, acc, stack, context, line, offset) do + case product_unit_p__0(rest, acc, [], context, line, offset) do + {:ok, acc, rest, context, line, offset} -> + unit_identifier__13(rest, acc, stack, context, line, offset) + + {:error, _, _, _, _, _} -> + unit_identifier__10(rest, acc, stack, context, line, offset) + end + end + + defp unit_identifier__10(_, _, [{rest, acc, context, line, offset} | stack], _, _, _) do + unit_identifier__14(rest, acc, stack, context, line, offset) + end + + defp unit_identifier__13( + inner_rest, + inner_acc, + [{rest, acc, context, line, offset} | stack], + inner_context, + inner_line, + inner_offset + ) do + _ = {rest, acc, context, line, offset} + + unit_identifier__11( + inner_rest, + [], + [{inner_rest, inner_acc ++ acc, inner_context, inner_line, inner_offset} | stack], + inner_context, + inner_line, + inner_offset + ) + end + + defp unit_identifier__14(rest, user_acc, [acc | stack], context, line, offset) do + _ = user_acc + + unit_identifier__15( + rest, + [wrap_core_unit(:lists.reverse(user_acc))] ++ acc, + stack, + context, + line, + offset + ) + end + + defp unit_identifier__15(rest, acc, [_, previous_acc | stack], context, line, offset) do + unit_identifier__6(rest, acc ++ previous_acc, stack, context, line, offset) + end + + defp unit_identifier__16(_, _, [{rest, context, line, offset} | _] = stack, _, _, _) do + unit_identifier__7(rest, [], stack, context, line, offset) + end + + defp unit_identifier__17(rest, acc, stack, context, line, offset) do + unit_identifier__18(rest, [], [acc | stack], context, line, offset) + end + + defp unit_identifier__18( + <<"per-", rest::binary>>, + acc, + stack, + context, + comb__line, + comb__offset + ) do + unit_identifier__19(rest, [] ++ acc, stack, context, comb__line, comb__offset + 4) + end + + defp unit_identifier__18(rest, _acc, stack, context, line, offset) do + [acc | stack] = stack + unit_identifier__16(rest, acc, stack, context, line, offset) + end + + defp unit_identifier__19(rest, acc, stack, context, line, offset) do + case product_unit_p__0(rest, acc, [], context, line, offset) do + {:ok, acc, rest, context, line, offset} -> + unit_identifier__20(rest, acc, stack, context, line, offset) + + {:error, _, _, _, _, _} -> + [acc | stack] = stack + unit_identifier__16(rest, acc, stack, context, line, offset) + end + end + + defp unit_identifier__20(rest, acc, stack, context, line, offset) do + unit_identifier__22( + rest, + [], + [{rest, acc, context, line, offset} | stack], + context, + line, + offset + ) + end + + defp unit_identifier__22( + <<"-per-", rest::binary>>, + acc, + stack, + context, + comb__line, + comb__offset + ) do + unit_identifier__23(rest, [] ++ acc, stack, context, comb__line, comb__offset + 5) + end + + defp unit_identifier__22(rest, acc, stack, context, line, offset) do + unit_identifier__21(rest, acc, stack, context, line, offset) + end + + defp unit_identifier__23(rest, acc, stack, context, line, offset) do + case product_unit_p__0(rest, acc, [], context, line, offset) do + {:ok, acc, rest, context, line, offset} -> + unit_identifier__24(rest, acc, stack, context, line, offset) + + {:error, _, _, _, _, _} -> + unit_identifier__21(rest, acc, stack, context, line, offset) + end + end + + defp unit_identifier__21(_, _, [{rest, acc, context, line, offset} | stack], _, _, _) do + unit_identifier__25(rest, acc, stack, context, line, offset) + end + + defp unit_identifier__24( + inner_rest, + inner_acc, + [{rest, acc, context, line, offset} | stack], + inner_context, + inner_line, + inner_offset + ) do + _ = {rest, acc, context, line, offset} + + unit_identifier__22( + inner_rest, + [], + [{inner_rest, inner_acc ++ acc, inner_context, inner_line, inner_offset} | stack], + inner_context, + inner_line, + inner_offset + ) + end + + defp unit_identifier__25(rest, user_acc, [acc | stack], context, line, offset) do + _ = user_acc + + unit_identifier__26( + rest, + [wrap_per_leading(:lists.reverse(user_acc))] ++ acc, + stack, + context, + line, + offset + ) + end + + defp unit_identifier__26(rest, acc, [_, previous_acc | stack], context, line, offset) do + unit_identifier__6(rest, acc ++ previous_acc, stack, context, line, offset) + end + + defp unit_identifier__6(rest, user_acc, [acc | stack], context, line, offset) do + _ = user_acc + + unit_identifier__27( + rest, + [wrap_long_unit(:lists.reverse(user_acc))] ++ acc, + stack, + context, + line, + offset + ) + end + + defp unit_identifier__27(rest, acc, [_, previous_acc | stack], context, line, offset) do + unit_identifier__1(rest, acc ++ previous_acc, stack, context, line, offset) + end + + defp unit_identifier__28(_, _, [{rest, context, line, offset} | _] = stack, _, _, _) do + unit_identifier__2(rest, [], stack, context, line, offset) + end + + defp unit_identifier__29(rest, acc, stack, context, line, offset) do + unit_identifier__41( + rest, + [], + [{rest, context, line, offset}, acc | stack], + context, + line, + offset + ) + end + + defp unit_identifier__31(rest, acc, stack, context, line, offset) do + unit_identifier__32(rest, [], [acc | stack], context, line, offset) + end + + defp unit_identifier__32(rest, acc, stack, context, line, offset) do + case product_unit_p__0(rest, acc, [], context, line, offset) do + {:ok, acc, rest, context, line, offset} -> + unit_identifier__33(rest, acc, stack, context, line, offset) + + {:error, _, _, _, _, _} -> + [_, _, acc | stack] = stack + unit_identifier__28(rest, acc, stack, context, line, offset) + end + end + + defp unit_identifier__33(rest, acc, stack, context, line, offset) do + unit_identifier__35( + rest, + [], + [{rest, acc, context, line, offset} | stack], + context, + line, + offset + ) + end + + defp unit_identifier__35( + <<"-per-", rest::binary>>, + acc, + stack, + context, + comb__line, + comb__offset + ) do + unit_identifier__36(rest, [] ++ acc, stack, context, comb__line, comb__offset + 5) + end + + defp unit_identifier__35(rest, acc, stack, context, line, offset) do + unit_identifier__34(rest, acc, stack, context, line, offset) + end + + defp unit_identifier__36(rest, acc, stack, context, line, offset) do + case product_unit_p__0(rest, acc, [], context, line, offset) do + {:ok, acc, rest, context, line, offset} -> + unit_identifier__37(rest, acc, stack, context, line, offset) + + {:error, _, _, _, _, _} -> + unit_identifier__34(rest, acc, stack, context, line, offset) + end + end + + defp unit_identifier__34(_, _, [{rest, acc, context, line, offset} | stack], _, _, _) do + unit_identifier__38(rest, acc, stack, context, line, offset) + end + + defp unit_identifier__37( + inner_rest, + inner_acc, + [{rest, acc, context, line, offset} | stack], + inner_context, + inner_line, + inner_offset + ) do + _ = {rest, acc, context, line, offset} + + unit_identifier__35( + inner_rest, + [], + [{inner_rest, inner_acc ++ acc, inner_context, inner_line, inner_offset} | stack], + inner_context, + inner_line, + inner_offset + ) + end + + defp unit_identifier__38(rest, user_acc, [acc | stack], context, line, offset) do + _ = user_acc + + unit_identifier__39( + rest, + [wrap_core_unit(:lists.reverse(user_acc))] ++ acc, + stack, + context, + line, + offset + ) + end + + defp unit_identifier__39(rest, acc, [_, previous_acc | stack], context, line, offset) do + unit_identifier__30(rest, acc ++ previous_acc, stack, context, line, offset) + end + + defp unit_identifier__40(_, _, [{rest, context, line, offset} | _] = stack, _, _, _) do + unit_identifier__31(rest, [], stack, context, line, offset) + end + + defp unit_identifier__41(rest, acc, stack, context, line, offset) do + unit_identifier__42(rest, [], [acc | stack], context, line, offset) + end + + defp unit_identifier__42( + <<"per-", rest::binary>>, + acc, + stack, + context, + comb__line, + comb__offset + ) do + unit_identifier__43(rest, [] ++ acc, stack, context, comb__line, comb__offset + 4) + end + + defp unit_identifier__42(rest, _acc, stack, context, line, offset) do + [acc | stack] = stack + unit_identifier__40(rest, acc, stack, context, line, offset) + end + + defp unit_identifier__43(rest, acc, stack, context, line, offset) do + case product_unit_p__0(rest, acc, [], context, line, offset) do + {:ok, acc, rest, context, line, offset} -> + unit_identifier__44(rest, acc, stack, context, line, offset) + + {:error, _, _, _, _, _} -> + [acc | stack] = stack + unit_identifier__40(rest, acc, stack, context, line, offset) + end + end + + defp unit_identifier__44(rest, acc, stack, context, line, offset) do + unit_identifier__46( + rest, + [], + [{rest, acc, context, line, offset} | stack], + context, + line, + offset + ) + end + + defp unit_identifier__46( + <<"-per-", rest::binary>>, + acc, + stack, + context, + comb__line, + comb__offset + ) do + unit_identifier__47(rest, [] ++ acc, stack, context, comb__line, comb__offset + 5) + end + + defp unit_identifier__46(rest, acc, stack, context, line, offset) do + unit_identifier__45(rest, acc, stack, context, line, offset) + end + + defp unit_identifier__47(rest, acc, stack, context, line, offset) do + case product_unit_p__0(rest, acc, [], context, line, offset) do + {:ok, acc, rest, context, line, offset} -> + unit_identifier__48(rest, acc, stack, context, line, offset) + + {:error, _, _, _, _, _} -> + unit_identifier__45(rest, acc, stack, context, line, offset) + end + end + + defp unit_identifier__45(_, _, [{rest, acc, context, line, offset} | stack], _, _, _) do + unit_identifier__49(rest, acc, stack, context, line, offset) + end + + defp unit_identifier__48( + inner_rest, + inner_acc, + [{rest, acc, context, line, offset} | stack], + inner_context, + inner_line, + inner_offset + ) do + _ = {rest, acc, context, line, offset} + + unit_identifier__46( + inner_rest, + [], + [{inner_rest, inner_acc ++ acc, inner_context, inner_line, inner_offset} | stack], + inner_context, + inner_line, + inner_offset + ) + end + + defp unit_identifier__49(rest, user_acc, [acc | stack], context, line, offset) do + _ = user_acc + + unit_identifier__50( + rest, + [wrap_per_leading(:lists.reverse(user_acc))] ++ acc, + stack, + context, + line, + offset + ) + end + + defp unit_identifier__50(rest, acc, [_, previous_acc | stack], context, line, offset) do + unit_identifier__30(rest, acc ++ previous_acc, stack, context, line, offset) + end + + defp unit_identifier__30(rest, acc, [_, previous_acc | stack], context, line, offset) do + unit_identifier__1(rest, acc ++ previous_acc, stack, context, line, offset) + end + + defp unit_identifier__51(_, _, [{rest, context, line, offset} | _] = stack, _, _, _) do + unit_identifier__29(rest, [], stack, context, line, offset) + end + + defp unit_identifier__52(rest, acc, stack, context, line, offset) do + unit_identifier__53(rest, [], [acc | stack], context, line, offset) + end + + defp unit_identifier__53(rest, acc, stack, context, line, offset) do + case single_unit_p__0(rest, acc, [], context, line, offset) do + {:ok, acc, rest, context, line, offset} -> + unit_identifier__54(rest, acc, stack, context, line, offset) + + {:error, _, _, _, _, _} -> + [acc | stack] = stack + unit_identifier__51(rest, acc, stack, context, line, offset) + end + end + + defp unit_identifier__54( + <<"-and-", rest::binary>>, + acc, + stack, + context, + comb__line, + comb__offset + ) do + unit_identifier__55(rest, [] ++ acc, stack, context, comb__line, comb__offset + 5) + end + + defp unit_identifier__54(rest, _acc, stack, context, line, offset) do + [acc | stack] = stack + unit_identifier__51(rest, acc, stack, context, line, offset) + end + + defp unit_identifier__55(rest, acc, stack, context, line, offset) do + case single_unit_p__0(rest, acc, [], context, line, offset) do + {:ok, acc, rest, context, line, offset} -> + unit_identifier__56(rest, acc, stack, context, line, offset) + + {:error, _, _, _, _, _} -> + [acc | stack] = stack + unit_identifier__51(rest, acc, stack, context, line, offset) + end + end + + defp unit_identifier__56(rest, acc, stack, context, line, offset) do + unit_identifier__58( + rest, + [], + [{rest, acc, context, line, offset} | stack], + context, + line, + offset + ) + end + + defp unit_identifier__58( + <<"-and-", rest::binary>>, + acc, + stack, + context, + comb__line, + comb__offset + ) do + unit_identifier__59(rest, [] ++ acc, stack, context, comb__line, comb__offset + 5) + end + + defp unit_identifier__58(rest, acc, stack, context, line, offset) do + unit_identifier__57(rest, acc, stack, context, line, offset) + end + + defp unit_identifier__59(rest, acc, stack, context, line, offset) do + case single_unit_p__0(rest, acc, [], context, line, offset) do + {:ok, acc, rest, context, line, offset} -> + unit_identifier__60(rest, acc, stack, context, line, offset) + + {:error, _, _, _, _, _} -> + unit_identifier__57(rest, acc, stack, context, line, offset) + end + end + + defp unit_identifier__57(_, _, [{rest, acc, context, line, offset} | stack], _, _, _) do + unit_identifier__61(rest, acc, stack, context, line, offset) + end + + defp unit_identifier__60( + inner_rest, + inner_acc, + [{rest, acc, context, line, offset} | stack], + inner_context, + inner_line, + inner_offset + ) do + _ = {rest, acc, context, line, offset} + + unit_identifier__58( + inner_rest, + [], + [{inner_rest, inner_acc ++ acc, inner_context, inner_line, inner_offset} | stack], + inner_context, + inner_line, + inner_offset + ) + end + + defp unit_identifier__61(rest, user_acc, [acc | stack], context, line, offset) do + _ = user_acc + + unit_identifier__62( + rest, + [wrap_mixed_unit(:lists.reverse(user_acc))] ++ acc, + stack, + context, + line, + offset + ) + end + + defp unit_identifier__62(rest, acc, [_, previous_acc | stack], context, line, offset) do + unit_identifier__1(rest, acc ++ previous_acc, stack, context, line, offset) + end + + defp unit_identifier__1(<<""::binary>>, acc, stack, context, comb__line, comb__offset) do + unit_identifier__63("", [] ++ acc, stack, context, comb__line, comb__offset) + end + + defp unit_identifier__1(rest, _acc, _stack, context, line, offset) do + {:error, "expected end of string", rest, context, line, offset} + end + + defp unit_identifier__63(rest, acc, _stack, context, line, offset) do + {:ok, acc, rest, context, line, offset} + end + + @spec product_unit_p(binary, keyword) :: + {:ok, [term], rest, context, line, byte_offset} + | {:error, reason, rest, context, line, byte_offset} + when line: {pos_integer, byte_offset}, + byte_offset: non_neg_integer, + rest: binary, + reason: String.t(), + context: map + @doc false + def product_unit_p(binary, opts \\ []) when is_binary(binary) do + context = Map.new(Keyword.get(opts, :context, [])) + byte_offset = Keyword.get(opts, :byte_offset, 0) + + line = + case Keyword.get(opts, :line, 1) do + {_, _} = line -> line + line -> {line, byte_offset} + end + + case product_unit_p__0(binary, [], [], context, line, byte_offset) do + {:ok, acc, rest, context, line, offset} -> + {:ok, :lists.reverse(acc), rest, context, line, offset} + + {:error, _, _, _, _, _} = error -> + error + end + end + + defp product_unit_p__0(rest, acc, stack, context, line, offset) do + product_unit_p__1(rest, [], [acc | stack], context, line, offset) + end + + defp product_unit_p__1(rest, acc, stack, context, line, offset) do + case single_unit_p__0(rest, acc, [], context, line, offset) do + {:ok, acc, rest, context, line, offset} -> + product_unit_p__2(rest, acc, stack, context, line, offset) + + {:error, _, _, _, _, _} = error -> + error + end + end + + defp product_unit_p__2(rest, acc, stack, context, line, offset) do + product_unit_p__4( + rest, + [], + [{rest, acc, context, line, offset} | stack], + context, + line, + offset + ) + end + + defp product_unit_p__4(<<"-per-", _::binary>> = rest, acc, stack, context, line, offset) do + product_unit_p__3(rest, acc, stack, context, line, offset) + end + + defp product_unit_p__4(rest, acc, stack, context, line, offset) do + product_unit_p__5(rest, acc, stack, context, line, offset) + end + + defp product_unit_p__5(<<"-and-", _::binary>> = rest, acc, stack, context, line, offset) do + product_unit_p__3(rest, acc, stack, context, line, offset) + end + + defp product_unit_p__5(rest, acc, stack, context, line, offset) do + product_unit_p__6(rest, acc, stack, context, line, offset) + end + + defp product_unit_p__6(<<"-", rest::binary>>, acc, stack, context, comb__line, comb__offset) do + product_unit_p__7(rest, [] ++ acc, stack, context, comb__line, comb__offset + 1) + end + + defp product_unit_p__6(rest, acc, stack, context, line, offset) do + product_unit_p__3(rest, acc, stack, context, line, offset) + end + + defp product_unit_p__7(rest, acc, stack, context, line, offset) do + case single_unit_p__0(rest, acc, [], context, line, offset) do + {:ok, acc, rest, context, line, offset} -> + product_unit_p__8(rest, acc, stack, context, line, offset) + + {:error, _, _, _, _, _} -> + product_unit_p__3(rest, acc, stack, context, line, offset) + end + end + + defp product_unit_p__3(_, _, [{rest, acc, context, line, offset} | stack], _, _, _) do + product_unit_p__9(rest, acc, stack, context, line, offset) + end + + defp product_unit_p__8( + inner_rest, + inner_acc, + [{rest, acc, context, line, offset} | stack], + inner_context, + inner_line, + inner_offset + ) do + _ = {rest, acc, context, line, offset} + + product_unit_p__4( + inner_rest, + [], + [{inner_rest, inner_acc ++ acc, inner_context, inner_line, inner_offset} | stack], + inner_context, + inner_line, + inner_offset + ) + end + + defp product_unit_p__9(rest, user_acc, [acc | stack], context, line, offset) do + _ = user_acc + + product_unit_p__10( + rest, + [product: :lists.reverse(user_acc)] ++ acc, + stack, + context, + line, + offset + ) + end + + defp product_unit_p__10(rest, acc, _stack, context, line, offset) do + {:ok, acc, rest, context, line, offset} + end + + @spec single_unit_p(binary, keyword) :: + {:ok, [term], rest, context, line, byte_offset} + | {:error, reason, rest, context, line, byte_offset} + when line: {pos_integer, byte_offset}, + byte_offset: non_neg_integer, + rest: binary, + reason: String.t(), + context: map + @doc false + def single_unit_p(binary, opts \\ []) when is_binary(binary) do + context = Map.new(Keyword.get(opts, :context, [])) + byte_offset = Keyword.get(opts, :byte_offset, 0) + + line = + case Keyword.get(opts, :line, 1) do + {_, _} = line -> line + line -> {line, byte_offset} + end + + case single_unit_p__0(binary, [], [], context, line, byte_offset) do + {:ok, acc, rest, context, line, offset} -> + {:ok, :lists.reverse(acc), rest, context, line, offset} + + {:error, _, _, _, _, _} = error -> + error + end + end + + defp single_unit_p__0(rest, acc, stack, context, line, offset) do + single_unit_p__67( + rest, + [], + [{rest, context, line, offset}, acc | stack], + context, + line, + offset + ) + end + + defp single_unit_p__2(rest, acc, stack, context, line, offset) do + single_unit_p__14( + rest, + [], + [{rest, context, line, offset}, acc | stack], + context, + line, + offset + ) + end + + defp single_unit_p__4(rest, acc, stack, context, line, offset) do + single_unit_p__5(rest, [], [acc | stack], context, line, offset) + end + + defp single_unit_p__5(<>, acc, stack, context, comb__line, comb__offset) + when x0 >= 49 and x0 <= 57 do + single_unit_p__6(rest, [<>] ++ acc, stack, context, comb__line, comb__offset + 1) + end + + defp single_unit_p__5(rest, _acc, _stack, context, line, offset) do + {:error, + "expected currency_unit_p or string \"square-\" or string \"cubic-\" or string \"pow\", followed by ASCII character in the range \"0\" to \"9\", followed by ASCII character in the range \"0\" to \"9\", followed by string \"-\" or nothing, followed by base_unit_p or si_prefix_p, followed by base_unit_p or si_prefix_p, followed by custom_base_unit_p or custom_base_unit_p or string \"1e\", followed by ASCII character in the range \"0\" to \"9\", followed by ASCII character in the range \"0\" to \"9\" or ASCII character in the range \"1\" to \"9\", followed by ASCII character in the range \"0\" to \"9\", followed by ASCII character in the range \"0\" to \"9\"", + rest, context, line, offset} + end + + defp single_unit_p__6(rest, acc, stack, context, line, offset) do + single_unit_p__7(rest, [], [acc | stack], context, line, offset) + end + + defp single_unit_p__7(<>, acc, stack, context, comb__line, comb__offset) + when x0 >= 48 and x0 <= 57 do + single_unit_p__8(rest, [<>] ++ acc, stack, context, comb__line, comb__offset + 1) + end + + defp single_unit_p__7(rest, _acc, _stack, context, line, offset) do + {:error, + "expected currency_unit_p or string \"square-\" or string \"cubic-\" or string \"pow\", followed by ASCII character in the range \"0\" to \"9\", followed by ASCII character in the range \"0\" to \"9\", followed by string \"-\" or nothing, followed by base_unit_p or si_prefix_p, followed by base_unit_p or si_prefix_p, followed by custom_base_unit_p or custom_base_unit_p or string \"1e\", followed by ASCII character in the range \"0\" to \"9\", followed by ASCII character in the range \"0\" to \"9\" or ASCII character in the range \"1\" to \"9\", followed by ASCII character in the range \"0\" to \"9\", followed by ASCII character in the range \"0\" to \"9\"", + rest, context, line, offset} + end + + defp single_unit_p__8(<>, acc, stack, context, comb__line, comb__offset) + when x0 >= 48 and x0 <= 57 do + single_unit_p__10(rest, [x0] ++ acc, stack, context, comb__line, comb__offset + 1) + end + + defp single_unit_p__8(rest, acc, stack, context, line, offset) do + single_unit_p__9(rest, acc, stack, context, line, offset) + end + + defp single_unit_p__10(rest, acc, stack, context, line, offset) do + single_unit_p__8(rest, acc, stack, context, line, offset) + end + + defp single_unit_p__9(rest, user_acc, [acc | stack], context, line, offset) do + _ = user_acc + + single_unit_p__11( + rest, + [List.to_string(:lists.reverse(user_acc))] ++ acc, + stack, + context, + line, + offset + ) + end + + defp single_unit_p__11(rest, user_acc, [acc | stack], context, line, offset) do + _ = user_acc + + single_unit_p__12( + rest, + [wrap_constant(:lists.reverse(user_acc))] ++ acc, + stack, + context, + line, + offset + ) + end + + defp single_unit_p__12(rest, acc, [_, previous_acc | stack], context, line, offset) do + single_unit_p__3(rest, acc ++ previous_acc, stack, context, line, offset) + end + + defp single_unit_p__13(_, _, [{rest, context, line, offset} | _] = stack, _, _, _) do + single_unit_p__4(rest, [], stack, context, line, offset) + end + + defp single_unit_p__14(rest, acc, stack, context, line, offset) do + single_unit_p__15(rest, [], [acc | stack], context, line, offset) + end + + defp single_unit_p__15(<<"1e", rest::binary>>, acc, stack, context, comb__line, comb__offset) do + single_unit_p__16(rest, ["1e"] ++ acc, stack, context, comb__line, comb__offset + 2) + end + + defp single_unit_p__15(rest, _acc, stack, context, line, offset) do + [acc | stack] = stack + single_unit_p__13(rest, acc, stack, context, line, offset) + end + + defp single_unit_p__16(rest, acc, stack, context, line, offset) do + single_unit_p__17(rest, [], [acc | stack], context, line, offset) + end + + defp single_unit_p__17(<>, acc, stack, context, comb__line, comb__offset) + when x0 >= 48 and x0 <= 57 do + single_unit_p__18( + rest, + [<>] ++ acc, + stack, + context, + comb__line, + comb__offset + 1 + ) + end + + defp single_unit_p__17(rest, _acc, stack, context, line, offset) do + [_, acc | stack] = stack + single_unit_p__13(rest, acc, stack, context, line, offset) + end + + defp single_unit_p__18(<>, acc, stack, context, comb__line, comb__offset) + when x0 >= 48 and x0 <= 57 do + single_unit_p__20(rest, [x0] ++ acc, stack, context, comb__line, comb__offset + 1) + end + + defp single_unit_p__18(rest, acc, stack, context, line, offset) do + single_unit_p__19(rest, acc, stack, context, line, offset) + end + + defp single_unit_p__20(rest, acc, stack, context, line, offset) do + single_unit_p__18(rest, acc, stack, context, line, offset) + end + + defp single_unit_p__19(rest, user_acc, [acc | stack], context, line, offset) do + _ = user_acc + + single_unit_p__21( + rest, + [List.to_string(:lists.reverse(user_acc))] ++ acc, + stack, + context, + line, + offset + ) + end + + defp single_unit_p__21(rest, user_acc, [acc | stack], context, line, offset) do + _ = user_acc + + single_unit_p__22( + rest, + [wrap_constant(:lists.reverse(user_acc))] ++ acc, + stack, + context, + line, + offset + ) + end + + defp single_unit_p__22(rest, acc, [_, previous_acc | stack], context, line, offset) do + single_unit_p__3(rest, acc ++ previous_acc, stack, context, line, offset) + end + + defp single_unit_p__3(rest, acc, [_, previous_acc | stack], context, line, offset) do + single_unit_p__1(rest, acc ++ previous_acc, stack, context, line, offset) + end + + defp single_unit_p__23(_, _, [{rest, context, line, offset} | _] = stack, _, _, _) do + single_unit_p__2(rest, [], stack, context, line, offset) + end + + defp single_unit_p__24(rest, acc, stack, context, line, offset) do + single_unit_p__25(rest, [], [acc | stack], context, line, offset) + end + + defp single_unit_p__25(rest, acc, stack, context, line, offset) do + single_unit_p__29( + rest, + [], + [{rest, context, line, offset}, acc | stack], + context, + line, + offset + ) + end + + defp single_unit_p__27(rest, acc, [_, previous_acc | stack], context, line, offset) do + single_unit_p__26(rest, acc ++ previous_acc, stack, context, line, offset) + end + + defp single_unit_p__28(_, _, [{rest, context, line, offset} | _] = stack, _, _, _) do + single_unit_p__27(rest, [], stack, context, line, offset) + end + + defp single_unit_p__29(rest, acc, stack, context, line, offset) do + single_unit_p__47( + rest, + [], + [{rest, context, line, offset}, acc | stack], + context, + line, + offset + ) + end + + defp single_unit_p__31(rest, acc, stack, context, line, offset) do + single_unit_p__32(rest, [], [acc | stack], context, line, offset) + end + + defp single_unit_p__32(<<"pow", rest::binary>>, acc, stack, context, comb__line, comb__offset) do + single_unit_p__33(rest, ["pow"] ++ acc, stack, context, comb__line, comb__offset + 3) + end + + defp single_unit_p__32(rest, _acc, stack, context, line, offset) do + [_, _, acc | stack] = stack + single_unit_p__28(rest, acc, stack, context, line, offset) + end + + defp single_unit_p__33(rest, acc, stack, context, line, offset) do + single_unit_p__34(rest, [], [acc | stack], context, line, offset) + end + + defp single_unit_p__34(<>, acc, stack, context, comb__line, comb__offset) + when x0 >= 48 and x0 <= 57 do + single_unit_p__35( + rest, + [<>] ++ acc, + stack, + context, + comb__line, + comb__offset + 1 + ) + end + + defp single_unit_p__34(rest, _acc, stack, context, line, offset) do + [_, _, _, acc | stack] = stack + single_unit_p__28(rest, acc, stack, context, line, offset) + end + + defp single_unit_p__35(rest, acc, stack, context, line, offset) do + single_unit_p__37(rest, acc, [1 | stack], context, line, offset) + end + + defp single_unit_p__37(<>, acc, stack, context, comb__line, comb__offset) + when x0 >= 48 and x0 <= 57 do + single_unit_p__38(rest, [x0] ++ acc, stack, context, comb__line, comb__offset + 1) + end + + defp single_unit_p__37(rest, acc, stack, context, line, offset) do + single_unit_p__36(rest, acc, stack, context, line, offset) + end + + defp single_unit_p__36(rest, acc, [_ | stack], context, line, offset) do + single_unit_p__39(rest, acc, stack, context, line, offset) + end + + defp single_unit_p__38(rest, acc, [1 | stack], context, line, offset) do + single_unit_p__39(rest, acc, stack, context, line, offset) + end + + defp single_unit_p__38(rest, acc, [count | stack], context, line, offset) do + single_unit_p__37(rest, acc, [count - 1 | stack], context, line, offset) + end + + defp single_unit_p__39(rest, user_acc, [acc | stack], context, line, offset) do + _ = user_acc + + single_unit_p__40( + rest, + [List.to_string(:lists.reverse(user_acc))] ++ acc, + stack, + context, + line, + offset + ) + end + + defp single_unit_p__40(<<"-", rest::binary>>, acc, stack, context, comb__line, comb__offset) do + single_unit_p__41(rest, [] ++ acc, stack, context, comb__line, comb__offset + 1) + end + + defp single_unit_p__40(rest, _acc, stack, context, line, offset) do + [_, _, acc | stack] = stack + single_unit_p__28(rest, acc, stack, context, line, offset) + end + + defp single_unit_p__41(rest, user_acc, [acc | stack], context, line, offset) do + _ = user_acc + + single_unit_p__42( + rest, + [wrap_pow(:lists.reverse(user_acc))] ++ acc, + stack, + context, + line, + offset + ) + end + + defp single_unit_p__42(rest, acc, [_, previous_acc | stack], context, line, offset) do + single_unit_p__30(rest, acc ++ previous_acc, stack, context, line, offset) + end + + defp single_unit_p__43(_, _, [{rest, context, line, offset} | _] = stack, _, _, _) do + single_unit_p__31(rest, [], stack, context, line, offset) + end + + defp single_unit_p__44( + <<"cubic-", rest::binary>>, + acc, + stack, + context, + comb__line, + comb__offset + ) do + single_unit_p__45(rest, [power: :cubic] ++ acc, stack, context, comb__line, comb__offset + 6) + end + + defp single_unit_p__44(rest, acc, stack, context, line, offset) do + single_unit_p__43(rest, acc, stack, context, line, offset) + end + + defp single_unit_p__45(rest, acc, [_, previous_acc | stack], context, line, offset) do + single_unit_p__30(rest, acc ++ previous_acc, stack, context, line, offset) + end + + defp single_unit_p__46(_, _, [{rest, context, line, offset} | _] = stack, _, _, _) do + single_unit_p__44(rest, [], stack, context, line, offset) + end + + defp single_unit_p__47( + <<"square-", rest::binary>>, + acc, + stack, + context, + comb__line, + comb__offset + ) do + single_unit_p__48(rest, [power: :square] ++ acc, stack, context, comb__line, comb__offset + 7) + end + + defp single_unit_p__47(rest, acc, stack, context, line, offset) do + single_unit_p__46(rest, acc, stack, context, line, offset) + end + + defp single_unit_p__48(rest, acc, [_, previous_acc | stack], context, line, offset) do + single_unit_p__30(rest, acc ++ previous_acc, stack, context, line, offset) + end + + defp single_unit_p__30(rest, acc, [_, previous_acc | stack], context, line, offset) do + single_unit_p__26(rest, acc ++ previous_acc, stack, context, line, offset) + end + + defp single_unit_p__26(rest, acc, stack, context, line, offset) do + single_unit_p__63( + rest, + [], + [{rest, context, line, offset}, acc | stack], + context, + line, + offset + ) + end + + defp single_unit_p__50(rest, acc, stack, context, line, offset) do + case custom_base_unit_p__0(rest, acc, [], context, line, offset) do + {:ok, acc, rest, context, line, offset} -> + single_unit_p__51(rest, acc, stack, context, line, offset) + + {:error, _, _, _, _, _} -> + [_, _, acc | stack] = stack + single_unit_p__23(rest, acc, stack, context, line, offset) + end + end + + defp single_unit_p__51(rest, acc, [_, previous_acc | stack], context, line, offset) do + single_unit_p__49(rest, acc ++ previous_acc, stack, context, line, offset) + end + + defp single_unit_p__52(_, _, [{rest, context, line, offset} | _] = stack, _, _, _) do + single_unit_p__50(rest, [], stack, context, line, offset) + end + + defp single_unit_p__53(rest, acc, stack, context, line, offset) do + single_unit_p__54(rest, [], [acc | stack], context, line, offset) + end + + defp single_unit_p__54(rest, acc, stack, context, line, offset) do + case si_prefix_p__0(rest, acc, [], context, line, offset) do + {:ok, acc, rest, context, line, offset} -> + single_unit_p__55(rest, acc, stack, context, line, offset) + + {:error, _, _, _, _, _} -> + [acc | stack] = stack + single_unit_p__52(rest, acc, stack, context, line, offset) + end + end + + defp single_unit_p__55(rest, acc, stack, context, line, offset) do + case custom_base_unit_p__0(rest, acc, [], context, line, offset) do + {:ok, acc, rest, context, line, offset} -> + single_unit_p__56(rest, acc, stack, context, line, offset) + + {:error, _, _, _, _, _} -> + [acc | stack] = stack + single_unit_p__52(rest, acc, stack, context, line, offset) + end + end + + defp single_unit_p__56(rest, user_acc, [acc | stack], context, line, offset) do + case (case validate_prefixed_custom_unit(rest, user_acc, context, line, offset) do + {_, _, _} = res -> + res + + {:error, reason} -> + {:error, reason} + end) do + {rest, user_acc, context} when is_list(user_acc) -> + single_unit_p__57(rest, user_acc ++ acc, stack, context, line, offset) + + {:error, reason} -> + {:error, reason, rest, context, line, offset} + end + end + + defp single_unit_p__57(rest, acc, [_, previous_acc | stack], context, line, offset) do + single_unit_p__49(rest, acc ++ previous_acc, stack, context, line, offset) + end + + defp single_unit_p__58(_, _, [{rest, context, line, offset} | _] = stack, _, _, _) do + single_unit_p__53(rest, [], stack, context, line, offset) + end + + defp single_unit_p__59(rest, acc, stack, context, line, offset) do + case si_prefix_p__0(rest, acc, [], context, line, offset) do + {:ok, acc, rest, context, line, offset} -> + single_unit_p__60(rest, acc, stack, context, line, offset) + + {:error, _, _, _, _, _} -> + single_unit_p__58(rest, acc, stack, context, line, offset) + end + end + + defp single_unit_p__60(rest, acc, stack, context, line, offset) do + case base_unit_p__0(rest, acc, [], context, line, offset) do + {:ok, acc, rest, context, line, offset} -> + single_unit_p__61(rest, acc, stack, context, line, offset) + + {:error, _, _, _, _, _} -> + single_unit_p__58(rest, acc, stack, context, line, offset) + end + end + + defp single_unit_p__61(rest, acc, [_, previous_acc | stack], context, line, offset) do + single_unit_p__49(rest, acc ++ previous_acc, stack, context, line, offset) + end + + defp single_unit_p__62(_, _, [{rest, context, line, offset} | _] = stack, _, _, _) do + single_unit_p__59(rest, [], stack, context, line, offset) + end + + defp single_unit_p__63(rest, acc, stack, context, line, offset) do + case base_unit_p__0(rest, acc, [], context, line, offset) do + {:ok, acc, rest, context, line, offset} -> + single_unit_p__64(rest, acc, stack, context, line, offset) + + {:error, _, _, _, _, _} -> + single_unit_p__62(rest, acc, stack, context, line, offset) + end + end + + defp single_unit_p__64(rest, acc, [_, previous_acc | stack], context, line, offset) do + single_unit_p__49(rest, acc ++ previous_acc, stack, context, line, offset) + end + + defp single_unit_p__49(rest, user_acc, [acc | stack], context, line, offset) do + _ = user_acc + + single_unit_p__65( + rest, + [wrap_single_unit(:lists.reverse(user_acc))] ++ acc, + stack, + context, + line, + offset + ) + end + + defp single_unit_p__65(rest, acc, [_, previous_acc | stack], context, line, offset) do + single_unit_p__1(rest, acc ++ previous_acc, stack, context, line, offset) + end + + defp single_unit_p__66(_, _, [{rest, context, line, offset} | _] = stack, _, _, _) do + single_unit_p__24(rest, [], stack, context, line, offset) + end + + defp single_unit_p__67(rest, acc, stack, context, line, offset) do + case currency_unit_p__0(rest, acc, [], context, line, offset) do + {:ok, acc, rest, context, line, offset} -> + single_unit_p__68(rest, acc, stack, context, line, offset) + + {:error, _, _, _, _, _} -> + single_unit_p__66(rest, acc, stack, context, line, offset) + end + end + + defp single_unit_p__68(rest, acc, [_, previous_acc | stack], context, line, offset) do + single_unit_p__1(rest, acc ++ previous_acc, stack, context, line, offset) + end + + defp single_unit_p__1(rest, acc, _stack, context, line, offset) do + {:ok, acc, rest, context, line, offset} + end + + @spec custom_base_unit_p(binary, keyword) :: + {:ok, [term], rest, context, line, byte_offset} + | {:error, reason, rest, context, line, byte_offset} + when line: {pos_integer, byte_offset}, + byte_offset: non_neg_integer, + rest: binary, + reason: String.t(), + context: map + @doc false + def custom_base_unit_p(binary, opts \\ []) when is_binary(binary) do + context = Map.new(Keyword.get(opts, :context, [])) + byte_offset = Keyword.get(opts, :byte_offset, 0) + + line = + case Keyword.get(opts, :line, 1) do + {_, _} = line -> line + line -> {line, byte_offset} + end + + case custom_base_unit_p__0(binary, [], [], context, line, byte_offset) do + {:ok, acc, rest, context, line, offset} -> + {:ok, :lists.reverse(acc), rest, context, line, offset} + + {:error, _, _, _, _, _} = error -> + error + end + end + + defp custom_base_unit_p__0(rest, acc, stack, context, line, offset) do + custom_base_unit_p__1(rest, [], [acc | stack], context, line, offset) + end + + defp custom_base_unit_p__1(rest, acc, stack, context, line, offset) do + custom_base_unit_p__2(rest, [], [acc | stack], context, line, offset) + end + + defp custom_base_unit_p__2(rest, acc, stack, context, line, offset) do + custom_base_unit_p__3(rest, [], [acc | stack], context, line, offset) + end + + defp custom_base_unit_p__3(<>, acc, stack, context, comb__line, comb__offset) + when x0 >= 97 and x0 <= 122 do + custom_base_unit_p__4( + rest, + [<>] ++ acc, + stack, + context, + comb__line, + comb__offset + 1 + ) + end + + defp custom_base_unit_p__3(rest, _acc, _stack, context, line, offset) do + {:error, "expected ASCII character in the range \"a\" to \"z\"", rest, context, line, offset} + end + + defp custom_base_unit_p__4(<>, acc, stack, context, comb__line, comb__offset) + when x0 >= 97 and x0 <= 122 do + custom_base_unit_p__6(rest, [x0] ++ acc, stack, context, comb__line, comb__offset + 1) + end + + defp custom_base_unit_p__4(rest, acc, stack, context, line, offset) do + custom_base_unit_p__5(rest, acc, stack, context, line, offset) + end + + defp custom_base_unit_p__6(rest, acc, stack, context, line, offset) do + custom_base_unit_p__4(rest, acc, stack, context, line, offset) + end + + defp custom_base_unit_p__5(rest, user_acc, [acc | stack], context, line, offset) do + _ = user_acc + + custom_base_unit_p__7( + rest, + [List.to_string(:lists.reverse(user_acc))] ++ acc, + stack, + context, + line, + offset + ) + end + + defp custom_base_unit_p__7(rest, user_acc, [acc | stack], context, line, offset) do + case (case reject_categories(rest, user_acc, context, line, offset) do + {_, _, _} = res -> + res + + {:error, reason} -> + {:error, reason} + end) do + {rest, user_acc, context} when is_list(user_acc) -> + custom_base_unit_p__8(rest, user_acc ++ acc, stack, context, line, offset) + + {:error, reason} -> + {:error, reason, rest, context, line, offset} + end + end + + defp custom_base_unit_p__8(rest, user_acc, [acc | stack], context, line, offset) do + _ = user_acc + + custom_base_unit_p__9( + rest, + [ + base: + case :lists.reverse(user_acc) do + [one] -> one + many -> raise "unwrap_and_tag/3 expected a single token, got: #{inspect(many)}" + end + ] ++ acc, + stack, + context, + line, + offset + ) + end + + defp custom_base_unit_p__9(rest, acc, _stack, context, line, offset) do + {:ok, acc, rest, context, line, offset} + end + + @spec currency_unit_p(binary, keyword) :: + {:ok, [term], rest, context, line, byte_offset} + | {:error, reason, rest, context, line, byte_offset} + when line: {pos_integer, byte_offset}, + byte_offset: non_neg_integer, + rest: binary, + reason: String.t(), + context: map + @doc false + def currency_unit_p(binary, opts \\ []) when is_binary(binary) do + context = Map.new(Keyword.get(opts, :context, [])) + byte_offset = Keyword.get(opts, :byte_offset, 0) + + line = + case Keyword.get(opts, :line, 1) do + {_, _} = line -> line + line -> {line, byte_offset} + end + + case currency_unit_p__0(binary, [], [], context, line, byte_offset) do + {:ok, acc, rest, context, line, offset} -> + {:ok, :lists.reverse(acc), rest, context, line, offset} + + {:error, _, _, _, _, _} = error -> + error + end + end + + defp currency_unit_p__0( + <<"curr-", x0, x1, x2, rest::binary>>, + acc, + stack, + context, + comb__line, + comb__offset + ) + when ((x0 >= 97 and x0 <= 122) or (x0 >= 65 and x0 <= 90)) and + ((x1 >= 97 and x1 <= 122) or (x1 >= 65 and x1 <= 90)) and + ((x2 >= 97 and x2 <= 122) or (x2 >= 65 and x2 <= 90)) do + currency_unit_p__1( + rest, + [wrap_currency_unit([<>])] ++ acc, + stack, + context, + comb__line, + comb__offset + 8 + ) + end + + defp currency_unit_p__0(rest, _acc, _stack, context, line, offset) do + {:error, + "expected string \"curr-\", followed by ASCII character in the range \"a\" to \"z\" or in the range \"A\" to \"Z\", followed by ASCII character in the range \"a\" to \"z\" or in the range \"A\" to \"Z\", followed by ASCII character in the range \"a\" to \"z\" or in the range \"A\" to \"Z\"", + rest, context, line, offset} + end + + defp currency_unit_p__1(rest, acc, _stack, context, line, offset) do + {:ok, acc, rest, context, line, offset} + end + + @spec si_prefix_p(binary, keyword) :: + {:ok, [term], rest, context, line, byte_offset} + | {:error, reason, rest, context, line, byte_offset} + when line: {pos_integer, byte_offset}, + byte_offset: non_neg_integer, + rest: binary, + reason: String.t(), + context: map + @doc false + def si_prefix_p(binary, opts \\ []) when is_binary(binary) do + context = Map.new(Keyword.get(opts, :context, [])) + byte_offset = Keyword.get(opts, :byte_offset, 0) + + line = + case Keyword.get(opts, :line, 1) do + {_, _} = line -> line + line -> {line, byte_offset} + end + + case si_prefix_p__0(binary, [], [], context, line, byte_offset) do + {:ok, acc, rest, context, line, offset} -> + {:ok, :lists.reverse(acc), rest, context, line, offset} + + {:error, _, _, _, _, _} = error -> + error + end + end + + defp si_prefix_p__0(rest, acc, stack, context, line, offset) do + si_prefix_p__1(rest, [], [acc | stack], context, line, offset) + end + + defp si_prefix_p__1(<<"quecto", rest::binary>>, acc, stack, context, comb__line, comb__offset) do + si_prefix_p__2(rest, ["quecto"] ++ acc, stack, context, comb__line, comb__offset + 6) + end + + defp si_prefix_p__1(<<"quetta", rest::binary>>, acc, stack, context, comb__line, comb__offset) do + si_prefix_p__2(rest, ["quetta"] ++ acc, stack, context, comb__line, comb__offset + 6) + end + + defp si_prefix_p__1(<<"ronto", rest::binary>>, acc, stack, context, comb__line, comb__offset) do + si_prefix_p__2(rest, ["ronto"] ++ acc, stack, context, comb__line, comb__offset + 5) + end + + defp si_prefix_p__1(<<"yocto", rest::binary>>, acc, stack, context, comb__line, comb__offset) do + si_prefix_p__2(rest, ["yocto"] ++ acc, stack, context, comb__line, comb__offset + 5) + end + + defp si_prefix_p__1(<<"zepto", rest::binary>>, acc, stack, context, comb__line, comb__offset) do + si_prefix_p__2(rest, ["zepto"] ++ acc, stack, context, comb__line, comb__offset + 5) + end + + defp si_prefix_p__1(<<"femto", rest::binary>>, acc, stack, context, comb__line, comb__offset) do + si_prefix_p__2(rest, ["femto"] ++ acc, stack, context, comb__line, comb__offset + 5) + end + + defp si_prefix_p__1(<<"micro", rest::binary>>, acc, stack, context, comb__line, comb__offset) do + si_prefix_p__2(rest, ["micro"] ++ acc, stack, context, comb__line, comb__offset + 5) + end + + defp si_prefix_p__1(<<"milli", rest::binary>>, acc, stack, context, comb__line, comb__offset) do + si_prefix_p__2(rest, ["milli"] ++ acc, stack, context, comb__line, comb__offset + 5) + end + + defp si_prefix_p__1(<<"centi", rest::binary>>, acc, stack, context, comb__line, comb__offset) do + si_prefix_p__2(rest, ["centi"] ++ acc, stack, context, comb__line, comb__offset + 5) + end + + defp si_prefix_p__1(<<"hecto", rest::binary>>, acc, stack, context, comb__line, comb__offset) do + si_prefix_p__2(rest, ["hecto"] ++ acc, stack, context, comb__line, comb__offset + 5) + end + + defp si_prefix_p__1(<<"zetta", rest::binary>>, acc, stack, context, comb__line, comb__offset) do + si_prefix_p__2(rest, ["zetta"] ++ acc, stack, context, comb__line, comb__offset + 5) + end + + defp si_prefix_p__1(<<"yotta", rest::binary>>, acc, stack, context, comb__line, comb__offset) do + si_prefix_p__2(rest, ["yotta"] ++ acc, stack, context, comb__line, comb__offset + 5) + end + + defp si_prefix_p__1(<<"ronna", rest::binary>>, acc, stack, context, comb__line, comb__offset) do + si_prefix_p__2(rest, ["ronna"] ++ acc, stack, context, comb__line, comb__offset + 5) + end + + defp si_prefix_p__1(<<"atto", rest::binary>>, acc, stack, context, comb__line, comb__offset) do + si_prefix_p__2(rest, ["atto"] ++ acc, stack, context, comb__line, comb__offset + 4) + end + + defp si_prefix_p__1(<<"pico", rest::binary>>, acc, stack, context, comb__line, comb__offset) do + si_prefix_p__2(rest, ["pico"] ++ acc, stack, context, comb__line, comb__offset + 4) + end + + defp si_prefix_p__1(<<"nano", rest::binary>>, acc, stack, context, comb__line, comb__offset) do + si_prefix_p__2(rest, ["nano"] ++ acc, stack, context, comb__line, comb__offset + 4) + end + + defp si_prefix_p__1(<<"deci", rest::binary>>, acc, stack, context, comb__line, comb__offset) do + si_prefix_p__2(rest, ["deci"] ++ acc, stack, context, comb__line, comb__offset + 4) + end + + defp si_prefix_p__1(<<"deka", rest::binary>>, acc, stack, context, comb__line, comb__offset) do + si_prefix_p__2(rest, ["deka"] ++ acc, stack, context, comb__line, comb__offset + 4) + end + + defp si_prefix_p__1(<<"kilo", rest::binary>>, acc, stack, context, comb__line, comb__offset) do + si_prefix_p__2(rest, ["kilo"] ++ acc, stack, context, comb__line, comb__offset + 4) + end + + defp si_prefix_p__1(<<"mega", rest::binary>>, acc, stack, context, comb__line, comb__offset) do + si_prefix_p__2(rest, ["mega"] ++ acc, stack, context, comb__line, comb__offset + 4) + end + + defp si_prefix_p__1(<<"giga", rest::binary>>, acc, stack, context, comb__line, comb__offset) do + si_prefix_p__2(rest, ["giga"] ++ acc, stack, context, comb__line, comb__offset + 4) + end + + defp si_prefix_p__1(<<"tera", rest::binary>>, acc, stack, context, comb__line, comb__offset) do + si_prefix_p__2(rest, ["tera"] ++ acc, stack, context, comb__line, comb__offset + 4) + end + + defp si_prefix_p__1(<<"peta", rest::binary>>, acc, stack, context, comb__line, comb__offset) do + si_prefix_p__2(rest, ["peta"] ++ acc, stack, context, comb__line, comb__offset + 4) + end + + defp si_prefix_p__1(<<"kibi", rest::binary>>, acc, stack, context, comb__line, comb__offset) do + si_prefix_p__2(rest, ["kibi"] ++ acc, stack, context, comb__line, comb__offset + 4) + end + + defp si_prefix_p__1(<<"mebi", rest::binary>>, acc, stack, context, comb__line, comb__offset) do + si_prefix_p__2(rest, ["mebi"] ++ acc, stack, context, comb__line, comb__offset + 4) + end + + defp si_prefix_p__1(<<"gibi", rest::binary>>, acc, stack, context, comb__line, comb__offset) do + si_prefix_p__2(rest, ["gibi"] ++ acc, stack, context, comb__line, comb__offset + 4) + end + + defp si_prefix_p__1(<<"tebi", rest::binary>>, acc, stack, context, comb__line, comb__offset) do + si_prefix_p__2(rest, ["tebi"] ++ acc, stack, context, comb__line, comb__offset + 4) + end + + defp si_prefix_p__1(<<"pebi", rest::binary>>, acc, stack, context, comb__line, comb__offset) do + si_prefix_p__2(rest, ["pebi"] ++ acc, stack, context, comb__line, comb__offset + 4) + end + + defp si_prefix_p__1(<<"exbi", rest::binary>>, acc, stack, context, comb__line, comb__offset) do + si_prefix_p__2(rest, ["exbi"] ++ acc, stack, context, comb__line, comb__offset + 4) + end + + defp si_prefix_p__1(<<"zebi", rest::binary>>, acc, stack, context, comb__line, comb__offset) do + si_prefix_p__2(rest, ["zebi"] ++ acc, stack, context, comb__line, comb__offset + 4) + end + + defp si_prefix_p__1(<<"yobi", rest::binary>>, acc, stack, context, comb__line, comb__offset) do + si_prefix_p__2(rest, ["yobi"] ++ acc, stack, context, comb__line, comb__offset + 4) + end + + defp si_prefix_p__1(<<"exa", rest::binary>>, acc, stack, context, comb__line, comb__offset) do + si_prefix_p__2(rest, ["exa"] ++ acc, stack, context, comb__line, comb__offset + 3) + end + + defp si_prefix_p__1(rest, _acc, _stack, context, line, offset) do + {:error, + "expected string \"quecto\" or string \"quetta\" or string \"ronto\" or string \"yocto\" or string \"zepto\" or string \"femto\" or string \"micro\" or string \"milli\" or string \"centi\" or string \"hecto\" or string \"zetta\" or string \"yotta\" or string \"ronna\" or string \"atto\" or string \"pico\" or string \"nano\" or string \"deci\" or string \"deka\" or string \"kilo\" or string \"mega\" or string \"giga\" or string \"tera\" or string \"peta\" or string \"kibi\" or string \"mebi\" or string \"gibi\" or string \"tebi\" or string \"pebi\" or string \"exbi\" or string \"zebi\" or string \"yobi\" or string \"exa\"", + rest, context, line, offset} + end + + defp si_prefix_p__2(rest, user_acc, [acc | stack], context, line, offset) do + _ = user_acc + + si_prefix_p__3( + rest, + [ + si_prefix: + case :lists.reverse(user_acc) do + [one] -> one + many -> raise "unwrap_and_tag/3 expected a single token, got: #{inspect(many)}" + end + ] ++ acc, + stack, + context, + line, + offset + ) + end + + defp si_prefix_p__3(rest, acc, _stack, context, line, offset) do + {:ok, acc, rest, context, line, offset} + end + + @spec base_unit_p(binary, keyword) :: + {:ok, [term], rest, context, line, byte_offset} + | {:error, reason, rest, context, line, byte_offset} + when line: {pos_integer, byte_offset}, + byte_offset: non_neg_integer, + rest: binary, + reason: String.t(), + context: map + @doc false + def base_unit_p(binary, opts \\ []) when is_binary(binary) do + context = Map.new(Keyword.get(opts, :context, [])) + byte_offset = Keyword.get(opts, :byte_offset, 0) + + line = + case Keyword.get(opts, :line, 1) do + {_, _} = line -> line + line -> {line, byte_offset} + end + + case base_unit_p__0(binary, [], [], context, line, byte_offset) do + {:ok, acc, rest, context, line, offset} -> + {:ok, :lists.reverse(acc), rest, context, line, offset} + + {:error, _, _, _, _, _} = error -> + error + end + end + + defp base_unit_p__0(rest, acc, stack, context, line, offset) do + base_unit_p__1(rest, [], [acc | stack], context, line, offset) + end + + defp base_unit_p__1( + <<"gasoline-energy-density", rest::binary>>, + acc, + stack, + context, + comb__line, + comb__offset + ) do + base_unit_p__2( + rest, + ["gasoline-energy-density"] ++ acc, + stack, + context, + comb__line, + comb__offset + 23 + ) + end + + defp base_unit_p__1( + <<"british-thermal-unit-it", rest::binary>>, + acc, + stack, + context, + comb__line, + comb__offset + ) do + base_unit_p__2( + rest, + ["british-thermal-unit-it"] ++ acc, + stack, + context, + comb__line, + comb__offset + 23 + ) + end + + defp base_unit_p__1( + <<"dessert-spoon-imperial", rest::binary>>, + acc, + stack, + context, + comb__line, + comb__offset + ) do + base_unit_p__2( + rest, + ["dessert-spoon-imperial"] ++ acc, + stack, + context, + comb__line, + comb__offset + 22 + ) + end + + defp base_unit_p__1( + <<"fluid-ounce-imperial", rest::binary>>, + acc, + stack, + context, + comb__line, + comb__offset + ) do + base_unit_p__2( + rest, + ["fluid-ounce-imperial"] ++ acc, + stack, + context, + comb__line, + comb__offset + 20 + ) + end + + defp base_unit_p__1( + <<"british-thermal-unit", rest::binary>>, + acc, + stack, + context, + comb__line, + comb__offset + ) do + base_unit_p__2( + rest, + ["british-thermal-unit"] ++ acc, + stack, + context, + comb__line, + comb__offset + 20 + ) + end + + defp base_unit_p__1( + <<"fluid-ounce-metric", rest::binary>>, + acc, + stack, + context, + comb__line, + comb__offset + ) do + base_unit_p__2( + rest, + ["fluid-ounce-metric"] ++ acc, + stack, + context, + comb__line, + comb__offset + 18 + ) + end + + defp base_unit_p__1( + <<"mile-scandinavian", rest::binary>>, + acc, + stack, + context, + comb__line, + comb__offset + ) do + base_unit_p__2( + rest, + ["mile-scandinavian"] ++ acc, + stack, + context, + comb__line, + comb__offset + 17 + ) + end + + defp base_unit_p__1( + <<"astronomical-unit", rest::binary>>, + acc, + stack, + context, + comb__line, + comb__offset + ) do + base_unit_p__2( + rest, + ["astronomical-unit"] ++ acc, + stack, + context, + comb__line, + comb__offset + 17 + ) + end + + defp base_unit_p__1( + <<"solar-luminosity", rest::binary>>, + acc, + stack, + context, + comb__line, + comb__offset + ) do + base_unit_p__2( + rest, + ["solar-luminosity"] ++ acc, + stack, + context, + comb__line, + comb__offset + 16 + ) + end + + defp base_unit_p__1( + <<"gallon-imperial", rest::binary>>, + acc, + stack, + context, + comb__line, + comb__offset + ) do + base_unit_p__2( + rest, + ["gallon-imperial"] ++ acc, + stack, + context, + comb__line, + comb__offset + 15 + ) + end + + defp base_unit_p__1( + <<"quart-imperial", rest::binary>>, + acc, + stack, + context, + comb__line, + comb__offset + ) do + base_unit_p__2(rest, ["quart-imperial"] ++ acc, stack, context, comb__line, comb__offset + 14) + end + + defp base_unit_p__1( + <<"kilogram-force", rest::binary>>, + acc, + stack, + context, + comb__line, + comb__offset + ) do + base_unit_p__2(rest, ["kilogram-force"] ++ acc, stack, context, comb__line, comb__offset + 14) + end + + defp base_unit_p__1( + <<"dessert-spoon", rest::binary>>, + acc, + stack, + context, + comb__line, + comb__offset + ) do + base_unit_p__2(rest, ["dessert-spoon"] ++ acc, stack, context, comb__line, comb__offset + 13) + end + + defp base_unit_p__1( + <<"pint-imperial", rest::binary>>, + acc, + stack, + context, + comb__line, + comb__offset + ) do + base_unit_p__2(rest, ["pint-imperial"] ++ acc, stack, context, comb__line, comb__offset + 13) + end + + defp base_unit_p__1( + <<"nautical-mile", rest::binary>>, + acc, + stack, + context, + comb__line, + comb__offset + ) do + base_unit_p__2(rest, ["nautical-mile"] ++ acc, stack, context, comb__line, comb__offset + 13) + end + + defp base_unit_p__1( + <<"cup-imperial", rest::binary>>, + acc, + stack, + context, + comb__line, + comb__offset + ) do + base_unit_p__2(rest, ["cup-imperial"] ++ acc, stack, context, comb__line, comb__offset + 12) + end + + defp base_unit_p__1( + <<"earth-radius", rest::binary>>, + acc, + stack, + context, + comb__line, + comb__offset + ) do + base_unit_p__2(rest, ["earth-radius"] ++ acc, stack, context, comb__line, comb__offset + 12) + end + + defp base_unit_p__1( + <<"solar-radius", rest::binary>>, + acc, + stack, + context, + comb__line, + comb__offset + ) do + base_unit_p__2(rest, ["solar-radius"] ++ acc, stack, context, comb__line, comb__offset + 12) + end + + defp base_unit_p__1( + <<"month-person", rest::binary>>, + acc, + stack, + context, + comb__line, + comb__offset + ) do + base_unit_p__2(rest, ["month-person"] ++ acc, stack, context, comb__line, comb__offset + 12) + end + + defp base_unit_p__1( + <<"electronvolt", rest::binary>>, + acc, + stack, + context, + comb__line, + comb__offset + ) do + base_unit_p__2(rest, ["electronvolt"] ++ acc, stack, context, comb__line, comb__offset + 12) + end + + defp base_unit_p__1( + <<"shaku-length", rest::binary>>, + acc, + stack, + context, + comb__line, + comb__offset + ) do + base_unit_p__2(rest, ["shaku-length"] ++ acc, stack, context, comb__line, comb__offset + 12) + end + + defp base_unit_p__1( + <<"fluid-ounce", rest::binary>>, + acc, + stack, + context, + comb__line, + comb__offset + ) do + base_unit_p__2(rest, ["fluid-ounce"] ++ acc, stack, context, comb__line, comb__offset + 11) + end + + defp base_unit_p__1( + <<"pint-metric", rest::binary>>, + acc, + stack, + context, + comb__line, + comb__offset + ) do + base_unit_p__2(rest, ["pint-metric"] ++ acc, stack, context, comb__line, comb__offset + 11) + end + + defp base_unit_p__1( + <<"week-person", rest::binary>>, + acc, + stack, + context, + comb__line, + comb__offset + ) do + base_unit_p__2(rest, ["week-person"] ++ acc, stack, context, comb__line, comb__offset + 11) + end + + defp base_unit_p__1( + <<"year-person", rest::binary>>, + acc, + stack, + context, + comb__line, + comb__offset + ) do + base_unit_p__2(rest, ["year-person"] ++ acc, stack, context, comb__line, comb__offset + 11) + end + + defp base_unit_p__1( + <<"pound-force", rest::binary>>, + acc, + stack, + context, + comb__line, + comb__offset + ) do + base_unit_p__2(rest, ["pound-force"] ++ acc, stack, context, comb__line, comb__offset + 11) + end + + defp base_unit_p__1( + <<"foodcalorie", rest::binary>>, + acc, + stack, + context, + comb__line, + comb__offset + ) do + base_unit_p__2(rest, ["foodcalorie"] ++ acc, stack, context, comb__line, comb__offset + 11) + end + + defp base_unit_p__1( + <<"light-speed", rest::binary>>, + acc, + stack, + context, + comb__line, + comb__offset + ) do + base_unit_p__2(rest, ["light-speed"] ++ acc, stack, context, comb__line, comb__offset + 11) + end + + defp base_unit_p__1( + <<"shaku-cloth", rest::binary>>, + acc, + stack, + context, + comb__line, + comb__offset + ) do + base_unit_p__2(rest, ["shaku-cloth"] ++ acc, stack, context, comb__line, comb__offset + 11) + end + + defp base_unit_p__1( + <<"ounce-troy", rest::binary>>, + acc, + stack, + context, + comb__line, + comb__offset + ) do + base_unit_p__2(rest, ["ounce-troy"] ++ acc, stack, context, comb__line, comb__offset + 10) + end + + defp base_unit_p__1( + <<"earth-mass", rest::binary>>, + acc, + stack, + context, + comb__line, + comb__offset + ) do + base_unit_p__2(rest, ["earth-mass"] ++ acc, stack, context, comb__line, comb__offset + 10) + end + + defp base_unit_p__1( + <<"solar-mass", rest::binary>>, + acc, + stack, + context, + comb__line, + comb__offset + ) do + base_unit_p__2(rest, ["solar-mass"] ++ acc, stack, context, comb__line, comb__offset + 10) + end + + defp base_unit_p__1( + <<"tablespoon", rest::binary>>, + acc, + stack, + context, + comb__line, + comb__offset + ) do + base_unit_p__2(rest, ["tablespoon"] ++ acc, stack, context, comb__line, comb__offset + 10) + end + + defp base_unit_p__1( + <<"cup-metric", rest::binary>>, + acc, + stack, + context, + comb__line, + comb__offset + ) do + base_unit_p__2(rest, ["cup-metric"] ++ acc, stack, context, comb__line, comb__offset + 10) + end + + defp base_unit_p__1( + <<"light-year", rest::binary>>, + acc, + stack, + context, + comb__line, + comb__offset + ) do + base_unit_p__2(rest, ["light-year"] ++ acc, stack, context, comb__line, comb__offset + 10) + end + + defp base_unit_p__1( + <<"day-person", rest::binary>>, + acc, + stack, + context, + comb__line, + comb__offset + ) do + base_unit_p__2(rest, ["day-person"] ++ acc, stack, context, comb__line, comb__offset + 10) + end + + defp base_unit_p__1( + <<"fahrenheit", rest::binary>>, + acc, + stack, + context, + comb__line, + comb__offset + ) do + base_unit_p__2(rest, ["fahrenheit"] ++ acc, stack, context, comb__line, comb__offset + 10) + end + + defp base_unit_p__1( + <<"arc-second", rest::binary>>, + acc, + stack, + context, + comb__line, + comb__offset + ) do + base_unit_p__2(rest, ["arc-second"] ++ acc, stack, context, comb__line, comb__offset + 10) + end + + defp base_unit_p__1( + <<"arc-minute", rest::binary>>, + acc, + stack, + context, + comb__line, + comb__offset + ) do + base_unit_p__2(rest, ["arc-minute"] ++ acc, stack, context, comb__line, comb__offset + 10) + end + + defp base_unit_p__1( + <<"revolution", rest::binary>>, + acc, + stack, + context, + comb__line, + comb__offset + ) do + base_unit_p__2(rest, ["revolution"] ++ acc, stack, context, comb__line, comb__offset + 10) + end + + defp base_unit_p__1( + <<"atmosphere", rest::binary>>, + acc, + stack, + context, + comb__line, + comb__offset + ) do + base_unit_p__2(rest, ["atmosphere"] ++ acc, stack, context, comb__line, comb__offset + 10) + end + + defp base_unit_p__1( + <<"calorie-it", rest::binary>>, + acc, + stack, + context, + comb__line, + comb__offset + ) do + base_unit_p__2(rest, ["calorie-it"] ++ acc, stack, context, comb__line, comb__offset + 10) + end + + defp base_unit_p__1( + <<"horsepower", rest::binary>>, + acc, + stack, + context, + comb__line, + comb__offset + ) do + base_unit_p__2(rest, ["horsepower"] ++ acc, stack, context, comb__line, comb__offset + 10) + end + + defp base_unit_p__1( + <<"fortnight", rest::binary>>, + acc, + stack, + context, + comb__line, + comb__offset + ) do + base_unit_p__2(rest, ["fortnight"] ++ acc, stack, context, comb__line, comb__offset + 9) + end + + defp base_unit_p__1( + <<"permyriad", rest::binary>>, + acc, + stack, + context, + comb__line, + comb__offset + ) do + base_unit_p__2(rest, ["permyriad"] ++ acc, stack, context, comb__line, comb__offset + 9) + end + + defp base_unit_p__1( + <<"becquerel", rest::binary>>, + acc, + stack, + context, + comb__line, + comb__offset + ) do + base_unit_p__2(rest, ["becquerel"] ++ acc, stack, context, comb__line, comb__offset + 9) + end + + defp base_unit_p__1( + <<"steradian", rest::binary>>, + acc, + stack, + context, + comb__line, + comb__offset + ) do + base_unit_p__2(rest, ["steradian"] ++ acc, stack, context, comb__line, comb__offset + 9) + end + + defp base_unit_p__1( + <<"ofglucose", rest::binary>>, + acc, + stack, + context, + comb__line, + comb__offset + ) do + base_unit_p__2(rest, ["ofglucose"] ++ acc, stack, context, comb__line, comb__offset + 9) + end + + defp base_unit_p__1(<<"kilogram", rest::binary>>, acc, stack, context, comb__line, comb__offset) do + base_unit_p__2(rest, ["kilogram"] ++ acc, stack, context, comb__line, comb__offset + 8) + end + + defp base_unit_p__1(<<"teaspoon", rest::binary>>, acc, stack, context, comb__line, comb__offset) do + base_unit_p__2(rest, ["teaspoon"] ++ acc, stack, context, comb__line, comb__offset + 8) + end + + defp base_unit_p__1(<<"permille", rest::binary>>, acc, stack, context, comb__line, comb__offset) do + base_unit_p__2(rest, ["permille"] ++ acc, stack, context, comb__line, comb__offset + 8) + end + + defp base_unit_p__1(<<"therm-us", rest::binary>>, acc, stack, context, comb__line, comb__offset) do + base_unit_p__2(rest, ["therm-us"] ++ acc, stack, context, comb__line, comb__offset + 8) + end + + defp base_unit_p__1(<<"beaufort", rest::binary>>, acc, stack, context, comb__line, comb__offset) do + base_unit_p__2(rest, ["beaufort"] ++ acc, stack, context, comb__line, comb__offset + 8) + end + + defp base_unit_p__1(<<"candela", rest::binary>>, acc, stack, context, comb__line, comb__offset) do + base_unit_p__2(rest, ["candela"] ++ acc, stack, context, comb__line, comb__offset + 7) + end + + defp base_unit_p__1(<<"hectare", rest::binary>>, acc, stack, context, comb__line, comb__offset) do + base_unit_p__2(rest, ["hectare"] ++ acc, stack, context, comb__line, comb__offset + 7) + end + + defp base_unit_p__1(<<"furlong", rest::binary>>, acc, stack, context, comb__line, comb__offset) do + base_unit_p__2(rest, ["furlong"] ++ acc, stack, context, comb__line, comb__offset + 7) + end + + defp base_unit_p__1(<<"quarter", rest::binary>>, acc, stack, context, comb__line, comb__offset) do + base_unit_p__2(rest, ["quarter"] ++ acc, stack, context, comb__line, comb__offset + 7) + end + + defp base_unit_p__1(<<"century", rest::binary>>, acc, stack, context, comb__line, comb__offset) do + base_unit_p__2(rest, ["century"] ++ acc, stack, context, comb__line, comb__offset + 7) + end + + defp base_unit_p__1(<<"rankine", rest::binary>>, acc, stack, context, comb__line, comb__offset) do + base_unit_p__2(rest, ["rankine"] ++ acc, stack, context, comb__line, comb__offset + 7) + end + + defp base_unit_p__1(<<"celsius", rest::binary>>, acc, stack, context, comb__line, comb__offset) do + base_unit_p__2(rest, ["celsius"] ++ acc, stack, context, comb__line, comb__offset + 7) + end + + defp base_unit_p__1(<<"percent", rest::binary>>, acc, stack, context, comb__line, comb__offset) do + base_unit_p__2(rest, ["percent"] ++ acc, stack, context, comb__line, comb__offset + 7) + end + + defp base_unit_p__1(<<"calorie", rest::binary>>, acc, stack, context, comb__line, comb__offset) do + base_unit_p__2(rest, ["calorie"] ++ acc, stack, context, comb__line, comb__offset + 7) + end + + defp base_unit_p__1(<<"coulomb", rest::binary>>, acc, stack, context, comb__line, comb__offset) do + base_unit_p__2(rest, ["coulomb"] ++ acc, stack, context, comb__line, comb__offset + 7) + end + + defp base_unit_p__1(<<"siemens", rest::binary>>, acc, stack, context, comb__line, comb__offset) do + base_unit_p__2(rest, ["siemens"] ++ acc, stack, context, comb__line, comb__offset + 7) + end + + defp base_unit_p__1(<<"sievert", rest::binary>>, acc, stack, context, comb__line, comb__offset) do + base_unit_p__2(rest, ["sievert"] ++ acc, stack, context, comb__line, comb__offset + 7) + end + + defp base_unit_p__1(<<"g-force", rest::binary>>, acc, stack, context, comb__line, comb__offset) do + base_unit_p__2(rest, ["g-force"] ++ acc, stack, context, comb__line, comb__offset + 7) + end + + defp base_unit_p__1(<<"generic", rest::binary>>, acc, stack, context, comb__line, comb__offset) do + base_unit_p__2(rest, ["generic"] ++ acc, stack, context, comb__line, comb__offset + 7) + end + + defp base_unit_p__1(<<"dalton", rest::binary>>, acc, stack, context, comb__line, comb__offset) do + base_unit_p__2(rest, ["dalton"] ++ acc, stack, context, comb__line, comb__offset + 6) + end + + defp base_unit_p__1(<<"jigger", rest::binary>>, acc, stack, context, comb__line, comb__offset) do + base_unit_p__2(rest, ["jigger"] ++ acc, stack, context, comb__line, comb__offset + 6) + end + + defp base_unit_p__1(<<"gallon", rest::binary>>, acc, stack, context, comb__line, comb__offset) do + base_unit_p__2(rest, ["gallon"] ++ acc, stack, context, comb__line, comb__offset + 6) + end + + defp base_unit_p__1(<<"bushel", rest::binary>>, acc, stack, context, comb__line, comb__offset) do + base_unit_p__2(rest, ["bushel"] ++ acc, stack, context, comb__line, comb__offset + 6) + end + + defp base_unit_p__1(<<"barrel", rest::binary>>, acc, stack, context, comb__line, comb__offset) do + base_unit_p__2(rest, ["barrel"] ++ acc, stack, context, comb__line, comb__offset + 6) + end + + defp base_unit_p__1(<<"fathom", rest::binary>>, acc, stack, context, comb__line, comb__offset) do + base_unit_p__2(rest, ["fathom"] ++ acc, stack, context, comb__line, comb__offset + 6) + end + + defp base_unit_p__1(<<"parsec", rest::binary>>, acc, stack, context, comb__line, comb__offset) do + base_unit_p__2(rest, ["parsec"] ++ acc, stack, context, comb__line, comb__offset + 6) + end + + defp base_unit_p__1(<<"second", rest::binary>>, acc, stack, context, comb__line, comb__offset) do + base_unit_p__2(rest, ["second"] ++ acc, stack, context, comb__line, comb__offset + 6) + end + + defp base_unit_p__1(<<"minute", rest::binary>>, acc, stack, context, comb__line, comb__offset) do + base_unit_p__2(rest, ["minute"] ++ acc, stack, context, comb__line, comb__offset + 6) + end + + defp base_unit_p__1(<<"decade", rest::binary>>, acc, stack, context, comb__line, comb__offset) do + base_unit_p__2(rest, ["decade"] ++ acc, stack, context, comb__line, comb__offset + 6) + end + + defp base_unit_p__1(<<"ampere", rest::binary>>, acc, stack, context, comb__line, comb__offset) do + base_unit_p__2(rest, ["ampere"] ++ acc, stack, context, comb__line, comb__offset + 6) + end + + defp base_unit_p__1(<<"kelvin", rest::binary>>, acc, stack, context, comb__line, comb__offset) do + base_unit_p__2(rest, ["kelvin"] ++ acc, stack, context, comb__line, comb__offset + 6) + end + + defp base_unit_p__1(<<"degree", rest::binary>>, acc, stack, context, comb__line, comb__offset) do + base_unit_p__2(rest, ["degree"] ++ acc, stack, context, comb__line, comb__offset + 6) + end + + defp base_unit_p__1(<<"radian", rest::binary>>, acc, stack, context, comb__line, comb__offset) do + base_unit_p__2(rest, ["radian"] ++ acc, stack, context, comb__line, comb__offset + 6) + end + + defp base_unit_p__1(<<"newton", rest::binary>>, acc, stack, context, comb__line, comb__offset) do + base_unit_p__2(rest, ["newton"] ++ acc, stack, context, comb__line, comb__offset + 6) + end + + defp base_unit_p__1(<<"pascal", rest::binary>>, acc, stack, context, comb__line, comb__offset) do + base_unit_p__2(rest, ["pascal"] ++ acc, stack, context, comb__line, comb__offset + 6) + end + + defp base_unit_p__1(<<"kosaji", rest::binary>>, acc, stack, context, comb__line, comb__offset) do + base_unit_p__2(rest, ["kosaji"] ++ acc, stack, context, comb__line, comb__offset + 6) + end + + defp base_unit_p__1(<<"cup-jp", rest::binary>>, acc, stack, context, comb__line, comb__offset) do + base_unit_p__2(rest, ["cup-jp"] ++ acc, stack, context, comb__line, comb__offset + 6) + end + + defp base_unit_p__1(<<"grain", rest::binary>>, acc, stack, context, comb__line, comb__offset) do + base_unit_p__2(rest, ["grain"] ++ acc, stack, context, comb__line, comb__offset + 5) + end + + defp base_unit_p__1(<<"carat", rest::binary>>, acc, stack, context, comb__line, comb__offset) do + base_unit_p__2(rest, ["carat"] ++ acc, stack, context, comb__line, comb__offset + 5) + end + + defp base_unit_p__1(<<"ounce", rest::binary>>, acc, stack, context, comb__line, comb__offset) do + base_unit_p__2(rest, ["ounce"] ++ acc, stack, context, comb__line, comb__offset + 5) + end + + defp base_unit_p__1(<<"pound", rest::binary>>, acc, stack, context, comb__line, comb__offset) do + base_unit_p__2(rest, ["pound"] ++ acc, stack, context, comb__line, comb__offset + 5) + end + + defp base_unit_p__1(<<"stone", rest::binary>>, acc, stack, context, comb__line, comb__offset) do + base_unit_p__2(rest, ["stone"] ++ acc, stack, context, comb__line, comb__offset + 5) + end + + defp base_unit_p__1(<<"tonne", rest::binary>>, acc, stack, context, comb__line, comb__offset) do + base_unit_p__2(rest, ["tonne"] ++ acc, stack, context, comb__line, comb__offset + 5) + end + + defp base_unit_p__1(<<"pinch", rest::binary>>, acc, stack, context, comb__line, comb__offset) do + base_unit_p__2(rest, ["pinch"] ++ acc, stack, context, comb__line, comb__offset + 5) + end + + defp base_unit_p__1(<<"quart", rest::binary>>, acc, stack, context, comb__line, comb__offset) do + base_unit_p__2(rest, ["quart"] ++ acc, stack, context, comb__line, comb__offset + 5) + end + + defp base_unit_p__1(<<"liter", rest::binary>>, acc, stack, context, comb__line, comb__offset) do + base_unit_p__2(rest, ["liter"] ++ acc, stack, context, comb__line, comb__offset + 5) + end + + defp base_unit_p__1(<<"dunam", rest::binary>>, acc, stack, context, comb__line, comb__offset) do + base_unit_p__2(rest, ["dunam"] ++ acc, stack, context, comb__line, comb__offset + 5) + end + + defp base_unit_p__1(<<"point", rest::binary>>, acc, stack, context, comb__line, comb__offset) do + base_unit_p__2(rest, ["point"] ++ acc, stack, context, comb__line, comb__offset + 5) + end + + defp base_unit_p__1(<<"meter", rest::binary>>, acc, stack, context, comb__line, comb__offset) do + base_unit_p__2(rest, ["meter"] ++ acc, stack, context, comb__line, comb__offset + 5) + end + + defp base_unit_p__1(<<"chain", rest::binary>>, acc, stack, context, comb__line, comb__offset) do + base_unit_p__2(rest, ["chain"] ++ acc, stack, context, comb__line, comb__offset + 5) + end + + defp base_unit_p__1(<<"month", rest::binary>>, acc, stack, context, comb__line, comb__offset) do + base_unit_p__2(rest, ["month"] ++ acc, stack, context, comb__line, comb__offset + 5) + end + + defp base_unit_p__1(<<"night", rest::binary>>, acc, stack, context, comb__line, comb__offset) do + base_unit_p__2(rest, ["night"] ++ acc, stack, context, comb__line, comb__offset + 5) + end + + defp base_unit_p__1(<<"karat", rest::binary>>, acc, stack, context, comb__line, comb__offset) do + base_unit_p__2(rest, ["karat"] ++ acc, stack, context, comb__line, comb__offset + 5) + end + + defp base_unit_p__1(<<"pixel", rest::binary>>, acc, stack, context, comb__line, comb__offset) do + base_unit_p__2(rest, ["pixel"] ++ acc, stack, context, comb__line, comb__offset + 5) + end + + defp base_unit_p__1(<<"hertz", rest::binary>>, acc, stack, context, comb__line, comb__offset) do + base_unit_p__2(rest, ["hertz"] ++ acc, stack, context, comb__line, comb__offset + 5) + end + + defp base_unit_p__1(<<"joule", rest::binary>>, acc, stack, context, comb__line, comb__offset) do + base_unit_p__2(rest, ["joule"] ++ acc, stack, context, comb__line, comb__offset + 5) + end + + defp base_unit_p__1(<<"farad", rest::binary>>, acc, stack, context, comb__line, comb__offset) do + base_unit_p__2(rest, ["farad"] ++ acc, stack, context, comb__line, comb__offset + 5) + end + + defp base_unit_p__1(<<"henry", rest::binary>>, acc, stack, context, comb__line, comb__offset) do + base_unit_p__2(rest, ["henry"] ++ acc, stack, context, comb__line, comb__offset + 5) + end + + defp base_unit_p__1(<<"katal", rest::binary>>, acc, stack, context, comb__line, comb__offset) do + base_unit_p__2(rest, ["katal"] ++ acc, stack, context, comb__line, comb__offset + 5) + end + + defp base_unit_p__1(<<"tesla", rest::binary>>, acc, stack, context, comb__line, comb__offset) do + base_unit_p__2(rest, ["tesla"] ++ acc, stack, context, comb__line, comb__offset + 5) + end + + defp base_unit_p__1(<<"weber", rest::binary>>, acc, stack, context, comb__line, comb__offset) do + base_unit_p__2(rest, ["weber"] ++ acc, stack, context, comb__line, comb__offset + 5) + end + + defp base_unit_p__1(<<"lumen", rest::binary>>, acc, stack, context, comb__line, comb__offset) do + base_unit_p__2(rest, ["lumen"] ++ acc, stack, context, comb__line, comb__offset + 5) + end + + defp base_unit_p__1(<<"jo-jp", rest::binary>>, acc, stack, context, comb__line, comb__offset) do + base_unit_p__2(rest, ["jo-jp"] ++ acc, stack, context, comb__line, comb__offset + 5) + end + + defp base_unit_p__1(<<"ri-jp", rest::binary>>, acc, stack, context, comb__line, comb__offset) do + base_unit_p__2(rest, ["ri-jp"] ++ acc, stack, context, comb__line, comb__offset + 5) + end + + defp base_unit_p__1(<<"bu-jp", rest::binary>>, acc, stack, context, comb__line, comb__offset) do + base_unit_p__2(rest, ["bu-jp"] ++ acc, stack, context, comb__line, comb__offset + 5) + end + + defp base_unit_p__1(<<"se-jp", rest::binary>>, acc, stack, context, comb__line, comb__offset) do + base_unit_p__2(rest, ["se-jp"] ++ acc, stack, context, comb__line, comb__offset + 5) + end + + defp base_unit_p__1(<<"osaji", rest::binary>>, acc, stack, context, comb__line, comb__offset) do + base_unit_p__2(rest, ["osaji"] ++ acc, stack, context, comb__line, comb__offset + 5) + end + + defp base_unit_p__1(<<"shaku", rest::binary>>, acc, stack, context, comb__line, comb__offset) do + base_unit_p__2(rest, ["shaku"] ++ acc, stack, context, comb__line, comb__offset + 5) + end + + defp base_unit_p__1(<<"to-jp", rest::binary>>, acc, stack, context, comb__line, comb__offset) do + base_unit_p__2(rest, ["to-jp"] ++ acc, stack, context, comb__line, comb__offset + 5) + end + + defp base_unit_p__1(<<"gram", rest::binary>>, acc, stack, context, comb__line, comb__offset) do + base_unit_p__2(rest, ["gram"] ++ acc, stack, context, comb__line, comb__offset + 4) + end + + defp base_unit_p__1(<<"slug", rest::binary>>, acc, stack, context, comb__line, comb__offset) do + base_unit_p__2(rest, ["slug"] ++ acc, stack, context, comb__line, comb__offset + 4) + end + + defp base_unit_p__1(<<"drop", rest::binary>>, acc, stack, context, comb__line, comb__offset) do + base_unit_p__2(rest, ["drop"] ++ acc, stack, context, comb__line, comb__offset + 4) + end + + defp base_unit_p__1(<<"dram", rest::binary>>, acc, stack, context, comb__line, comb__offset) do + base_unit_p__2(rest, ["dram"] ++ acc, stack, context, comb__line, comb__offset + 4) + end + + defp base_unit_p__1(<<"pint", rest::binary>>, acc, stack, context, comb__line, comb__offset) do + base_unit_p__2(rest, ["pint"] ++ acc, stack, context, comb__line, comb__offset + 4) + end + + defp base_unit_p__1(<<"acre", rest::binary>>, acc, stack, context, comb__line, comb__offset) do + base_unit_p__2(rest, ["acre"] ++ acc, stack, context, comb__line, comb__offset + 4) + end + + defp base_unit_p__1(<<"inch", rest::binary>>, acc, stack, context, comb__line, comb__offset) do + base_unit_p__2(rest, ["inch"] ++ acc, stack, context, comb__line, comb__offset + 4) + end + + defp base_unit_p__1(<<"foot", rest::binary>>, acc, stack, context, comb__line, comb__offset) do + base_unit_p__2(rest, ["foot"] ++ acc, stack, context, comb__line, comb__offset + 4) + end + + defp base_unit_p__1(<<"yard", rest::binary>>, acc, stack, context, comb__line, comb__offset) do + base_unit_p__2(rest, ["yard"] ++ acc, stack, context, comb__line, comb__offset + 4) + end + + defp base_unit_p__1(<<"mile", rest::binary>>, acc, stack, context, comb__line, comb__offset) do + base_unit_p__2(rest, ["mile"] ++ acc, stack, context, comb__line, comb__offset + 4) + end + + defp base_unit_p__1(<<"hour", rest::binary>>, acc, stack, context, comb__line, comb__offset) do + base_unit_p__2(rest, ["hour"] ++ acc, stack, context, comb__line, comb__offset + 4) + end + + defp base_unit_p__1(<<"week", rest::binary>>, acc, stack, context, comb__line, comb__offset) do + base_unit_p__2(rest, ["week"] ++ acc, stack, context, comb__line, comb__offset + 4) + end + + defp base_unit_p__1(<<"year", rest::binary>>, acc, stack, context, comb__line, comb__offset) do + base_unit_p__2(rest, ["year"] ++ acc, stack, context, comb__line, comb__offset + 4) + end + + defp base_unit_p__1(<<"item", rest::binary>>, acc, stack, context, comb__line, comb__offset) do + base_unit_p__2(rest, ["item"] ++ acc, stack, context, comb__line, comb__offset + 4) + end + + defp base_unit_p__1(<<"mole", rest::binary>>, acc, stack, context, comb__line, comb__offset) do + base_unit_p__2(rest, ["mole"] ++ acc, stack, context, comb__line, comb__offset + 4) + end + + defp base_unit_p__1(<<"part", rest::binary>>, acc, stack, context, comb__line, comb__offset) do + base_unit_p__2(rest, ["part"] ++ acc, stack, context, comb__line, comb__offset + 4) + end + + defp base_unit_p__1(<<"byte", rest::binary>>, acc, stack, context, comb__line, comb__offset) do + base_unit_p__2(rest, ["byte"] ++ acc, stack, context, comb__line, comb__offset + 4) + end + + defp base_unit_p__1(<<"ofhg", rest::binary>>, acc, stack, context, comb__line, comb__offset) do + base_unit_p__2(rest, ["ofhg"] ++ acc, stack, context, comb__line, comb__offset + 4) + end + + defp base_unit_p__1(<<"watt", rest::binary>>, acc, stack, context, comb__line, comb__offset) do + base_unit_p__2(rest, ["watt"] ++ acc, stack, context, comb__line, comb__offset + 4) + end + + defp base_unit_p__1(<<"volt", rest::binary>>, acc, stack, context, comb__line, comb__offset) do + base_unit_p__2(rest, ["volt"] ++ acc, stack, context, comb__line, comb__offset + 4) + end + + defp base_unit_p__1(<<"gray", rest::binary>>, acc, stack, context, comb__line, comb__offset) do + base_unit_p__2(rest, ["gray"] ++ acc, stack, context, comb__line, comb__offset + 4) + end + + defp base_unit_p__1(<<"knot", rest::binary>>, acc, stack, context, comb__line, comb__offset) do + base_unit_p__2(rest, ["knot"] ++ acc, stack, context, comb__line, comb__offset + 4) + end + + defp base_unit_p__1(<<"koku", rest::binary>>, acc, stack, context, comb__line, comb__offset) do + base_unit_p__2(rest, ["koku"] ++ acc, stack, context, comb__line, comb__offset + 4) + end + + defp base_unit_p__1(<<"ton", rest::binary>>, acc, stack, context, comb__line, comb__offset) do + base_unit_p__2(rest, ["ton"] ++ acc, stack, context, comb__line, comb__offset + 3) + end + + defp base_unit_p__1(<<"cup", rest::binary>>, acc, stack, context, comb__line, comb__offset) do + base_unit_p__2(rest, ["cup"] ++ acc, stack, context, comb__line, comb__offset + 3) + end + + defp base_unit_p__1(<<"rod", rest::binary>>, acc, stack, context, comb__line, comb__offset) do + base_unit_p__2(rest, ["rod"] ++ acc, stack, context, comb__line, comb__offset + 3) + end + + defp base_unit_p__1(<<"day", rest::binary>>, acc, stack, context, comb__line, comb__offset) do + base_unit_p__2(rest, ["day"] ++ acc, stack, context, comb__line, comb__offset + 3) + end + + defp base_unit_p__1(<<"bit", rest::binary>>, acc, stack, context, comb__line, comb__offset) do + base_unit_p__2(rest, ["bit"] ++ acc, stack, context, comb__line, comb__offset + 3) + end + + defp base_unit_p__1(<<"dot", rest::binary>>, acc, stack, context, comb__line, comb__offset) do + base_unit_p__2(rest, ["dot"] ++ acc, stack, context, comb__line, comb__offset + 3) + end + + defp base_unit_p__1(<<"bar", rest::binary>>, acc, stack, context, comb__line, comb__offset) do + base_unit_p__2(rest, ["bar"] ++ acc, stack, context, comb__line, comb__offset + 3) + end + + defp base_unit_p__1(<<"ohm", rest::binary>>, acc, stack, context, comb__line, comb__offset) do + base_unit_p__2(rest, ["ohm"] ++ acc, stack, context, comb__line, comb__offset + 3) + end + + defp base_unit_p__1(<<"lux", rest::binary>>, acc, stack, context, comb__line, comb__offset) do + base_unit_p__2(rest, ["lux"] ++ acc, stack, context, comb__line, comb__offset + 3) + end + + defp base_unit_p__1(<<"rin", rest::binary>>, acc, stack, context, comb__line, comb__offset) do + base_unit_p__2(rest, ["rin"] ++ acc, stack, context, comb__line, comb__offset + 3) + end + + defp base_unit_p__1(<<"sun", rest::binary>>, acc, stack, context, comb__line, comb__offset) do + base_unit_p__2(rest, ["sun"] ++ acc, stack, context, comb__line, comb__offset + 3) + end + + defp base_unit_p__1(<<"ken", rest::binary>>, acc, stack, context, comb__line, comb__offset) do + base_unit_p__2(rest, ["ken"] ++ acc, stack, context, comb__line, comb__offset + 3) + end + + defp base_unit_p__1(<<"cho", rest::binary>>, acc, stack, context, comb__line, comb__offset) do + base_unit_p__2(rest, ["cho"] ++ acc, stack, context, comb__line, comb__offset + 3) + end + + defp base_unit_p__1(<<"sai", rest::binary>>, acc, stack, context, comb__line, comb__offset) do + base_unit_p__2(rest, ["sai"] ++ acc, stack, context, comb__line, comb__offset + 3) + end + + defp base_unit_p__1(<<"fun", rest::binary>>, acc, stack, context, comb__line, comb__offset) do + base_unit_p__2(rest, ["fun"] ++ acc, stack, context, comb__line, comb__offset + 3) + end + + defp base_unit_p__1(<<"em", rest::binary>>, acc, stack, context, comb__line, comb__offset) do + base_unit_p__2(rest, ["em"] ++ acc, stack, context, comb__line, comb__offset + 2) + end + + defp base_unit_p__1(rest, _acc, _stack, context, line, offset) do + {:error, + "expected string \"gasoline-energy-density\" or string \"british-thermal-unit-it\" or string \"dessert-spoon-imperial\" or string \"fluid-ounce-imperial\" or string \"british-thermal-unit\" or string \"fluid-ounce-metric\" or string \"mile-scandinavian\" or string \"astronomical-unit\" or string \"solar-luminosity\" or string \"gallon-imperial\" or string \"quart-imperial\" or string \"kilogram-force\" or string \"dessert-spoon\" or string \"pint-imperial\" or string \"nautical-mile\" or string \"cup-imperial\" or string \"earth-radius\" or string \"solar-radius\" or string \"month-person\" or string \"electronvolt\" or string \"shaku-length\" or string \"fluid-ounce\" or string \"pint-metric\" or string \"week-person\" or string \"year-person\" or string \"pound-force\" or string \"foodcalorie\" or string \"light-speed\" or string \"shaku-cloth\" or string \"ounce-troy\" or string \"earth-mass\" or string \"solar-mass\" or string \"tablespoon\" or string \"cup-metric\" or string \"light-year\" or string \"day-person\" or string \"fahrenheit\" or string \"arc-second\" or string \"arc-minute\" or string \"revolution\" or string \"atmosphere\" or string \"calorie-it\" or string \"horsepower\" or string \"fortnight\" or string \"permyriad\" or string \"becquerel\" or string \"steradian\" or string \"ofglucose\" or string \"kilogram\" or string \"teaspoon\" or string \"permille\" or string \"therm-us\" or string \"beaufort\" or string \"candela\" or string \"hectare\" or string \"furlong\" or string \"quarter\" or string \"century\" or string \"rankine\" or string \"celsius\" or string \"percent\" or string \"calorie\" or string \"coulomb\" or string \"siemens\" or string \"sievert\" or string \"g-force\" or string \"generic\" or string \"dalton\" or string \"jigger\" or string \"gallon\" or string \"bushel\" or string \"barrel\" or string \"fathom\" or string \"parsec\" or string \"second\" or string \"minute\" or string \"decade\" or string \"ampere\" or string \"kelvin\" or string \"degree\" or string \"radian\" or string \"newton\" or string \"pascal\" or string \"kosaji\" or string \"cup-jp\" or string \"grain\" or string \"carat\" or string \"ounce\" or string \"pound\" or string \"stone\" or string \"tonne\" or string \"pinch\" or string \"quart\" or string \"liter\" or string \"dunam\" or string \"point\" or string \"meter\" or string \"chain\" or string \"month\" or string \"night\" or string \"karat\" or string \"pixel\" or string \"hertz\" or string \"joule\" or string \"farad\" or string \"henry\" or string \"katal\" or string \"tesla\" or string \"weber\" or string \"lumen\" or string \"jo-jp\" or string \"ri-jp\" or string \"bu-jp\" or string \"se-jp\" or string \"osaji\" or string \"shaku\" or string \"to-jp\" or string \"gram\" or string \"slug\" or string \"drop\" or string \"dram\" or string \"pint\" or string \"acre\" or string \"inch\" or string \"foot\" or string \"yard\" or string \"mile\" or string \"hour\" or string \"week\" or string \"year\" or string \"item\" or string \"mole\" or string \"part\" or string \"byte\" or string \"ofhg\" or string \"watt\" or string \"volt\" or string \"gray\" or string \"knot\" or string \"koku\" or string \"ton\" or string \"cup\" or string \"rod\" or string \"day\" or string \"bit\" or string \"dot\" or string \"bar\" or string \"ohm\" or string \"lux\" or string \"rin\" or string \"sun\" or string \"ken\" or string \"cho\" or string \"sai\" or string \"fun\" or string \"em\"", + rest, context, line, offset} + end + + defp base_unit_p__2(rest, user_acc, [acc | stack], context, line, offset) do + _ = user_acc + + base_unit_p__3( + rest, + [ + base: + case :lists.reverse(user_acc) do + [one] -> one + many -> raise "unwrap_and_tag/3 expected a single token, got: #{inspect(many)}" + end + ] ++ acc, + stack, + context, + line, + offset + ) + end + + defp base_unit_p__3(rest, acc, _stack, context, line, offset) do + {:ok, acc, rest, context, line, offset} + end + + defp parse_error(bindings), do: {:parse_error, bindings} +end + +defmodule BB.Unit.Conversions do + @moduledoc """ + Converts units to their base units. Generated from CLDR data by + Localize; has no runtime dependencies. + + Every unit `Localize.Unit` converts is converted here — defined units, + SI-prefixed units, powers, and `-per-` and juxtaposed compounds — to + the same canonical base unit, with the same factor. + + Conversion is affine — `base = value * factor + offset` — so both + scaled units (kilometers) and offset units (degrees Celsius) are + handled. Two things are refused rather than approximated: a unit whose + scale is not affine, such as `beaufort`, which no factor and offset can + express — this one Localize does convert, through a scale this module + cannot represent; and a mixed unit, such as `foot-and-inch`, which is a + way of displaying a quantity rather than a unit to convert, and which + Localize declines to convert either. + + """ + + @conversions %{ + "acre" => "square-meter", + "ampere" => "ampere", + "arc-minute" => "revolution", + "arc-second" => "revolution", + "astronomical-unit" => "meter", + "atmosphere" => "kilogram-per-meter-square-second", + "bar" => "kilogram-per-meter-square-second", + "barrel" => "cubic-meter", + "beaufort" => "meter-per-second", + "becquerel" => "per-second", + "bit" => "bit", + "british-thermal-unit" => "kilogram-square-meter-per-square-second", + "british-thermal-unit-it" => "kilogram-square-meter-per-square-second", + "bu-jp" => "square-meter", + "bushel" => "cubic-meter", + "byte" => "bit", + "calorie" => "kilogram-square-meter-per-square-second", + "calorie-it" => "kilogram-square-meter-per-square-second", + "candela" => "candela", + "carat" => "kilogram", + "celsius" => "kelvin", + "century" => "year", + "chain" => "meter", + "cho" => "square-meter", + "coulomb" => "second-ampere", + "cup" => "cubic-meter", + "cup-imperial" => "cubic-meter", + "cup-jp" => "cubic-meter", + "cup-metric" => "cubic-meter", + "dalton" => "kilogram", + "day" => "second", + "day-person" => "second", + "decade" => "year", + "degree" => "revolution", + "dessert-spoon" => "cubic-meter", + "dessert-spoon-imperial" => "cubic-meter", + "dot" => "pixel", + "dram" => "cubic-meter", + "drop" => "cubic-meter", + "dunam" => "square-meter", + "earth-mass" => "kilogram", + "earth-radius" => "meter", + "electronvolt" => "kilogram-square-meter-per-square-second", + "em" => "em", + "fahrenheit" => "kelvin", + "farad" => "pow4-second-square-ampere-per-kilogram-square-meter", + "fathom" => "meter", + "fluid-ounce" => "cubic-meter", + "fluid-ounce-imperial" => "cubic-meter", + "fluid-ounce-metric" => "cubic-meter", + "foodcalorie" => "kilogram-square-meter-per-square-second", + "foot" => "meter", + "fortnight" => "second", + "fun" => "kilogram", + "furlong" => "meter", + "g-force" => "meter-per-square-second", + "gallon" => "cubic-meter", + "gallon-imperial" => "cubic-meter", + "gasoline-energy-density" => "kilogram-per-meter-square-second", + "grain" => "kilogram", + "gram" => "kilogram", + "gray" => "square-meter-per-square-second", + "hectare" => "square-meter", + "henry" => "kilogram-square-meter-per-square-second-square-ampere", + "hertz" => "revolution-per-second", + "horsepower" => "kilogram-square-meter-per-cubic-second", + "hour" => "second", + "inch" => "meter", + "item" => "item", + "jigger" => "cubic-meter", + "jo-jp" => "meter", + "joule" => "kilogram-square-meter-per-square-second", + "karat" => "part", + "katal" => "item-per-second", + "kelvin" => "kelvin", + "ken" => "meter", + "kilogram" => "kilogram", + "kilogram-force" => "kilogram-meter-per-square-second", + "knot" => "meter-per-second", + "koku" => "cubic-meter", + "kosaji" => "cubic-meter", + "light-speed" => "meter-per-second", + "light-year" => "meter", + "liter" => "cubic-meter", + "lumen" => "candela-square-meter-per-square-meter", + "lux" => "candela-per-square-meter", + "meter" => "meter", + "mile" => "meter", + "mile-scandinavian" => "meter", + "minute" => "second", + "mole" => "item", + "month" => "year", + "month-person" => "year", + "nautical-mile" => "meter", + "newton" => "kilogram-meter-per-square-second", + "night" => "night", + "ofglucose" => "item-per-kilogram", + "ofhg" => "kilogram-per-square-meter-square-second", + "ohm" => "kilogram-square-meter-per-cubic-second-square-ampere", + "osaji" => "cubic-meter", + "ounce" => "kilogram", + "ounce-troy" => "kilogram", + "parsec" => "meter", + "part" => "part", + "pascal" => "kilogram-per-meter-square-second", + "percent" => "part", + "permille" => "part", + "permyriad" => "part", + "pinch" => "cubic-meter", + "pint" => "cubic-meter", + "pint-imperial" => "cubic-meter", + "pint-metric" => "cubic-meter", + "pixel" => "pixel", + "point" => "meter", + "pound" => "kilogram", + "pound-force" => "kilogram-meter-per-square-second", + "quart" => "cubic-meter", + "quart-imperial" => "cubic-meter", + "quarter" => "year", + "radian" => "revolution", + "rankine" => "kelvin", + "revolution" => "revolution", + "ri-jp" => "meter", + "rin" => "meter", + "rod" => "meter", + "sai" => "cubic-meter", + "se-jp" => "square-meter", + "second" => "second", + "shaku" => "cubic-meter", + "shaku-cloth" => "meter", + "shaku-length" => "meter", + "siemens" => "cubic-second-square-ampere-per-kilogram-square-meter", + "sievert" => "square-meter-per-square-second", + "slug" => "kilogram", + "solar-luminosity" => "kilogram-square-meter-per-cubic-second", + "solar-mass" => "kilogram", + "solar-radius" => "meter", + "steradian" => "square-revolution", + "stone" => "kilogram", + "sun" => "meter", + "tablespoon" => "cubic-meter", + "teaspoon" => "cubic-meter", + "tesla" => "kilogram-per-square-second-ampere", + "therm-us" => "kilogram-square-meter-per-square-second", + "to-jp" => "cubic-meter", + "ton" => "kilogram", + "tonne" => "kilogram", + "volt" => "kilogram-square-meter-per-cubic-second-ampere", + "watt" => "kilogram-square-meter-per-cubic-second", + "weber" => "kilogram-square-meter-per-square-second-ampere", + "week" => "second", + "week-person" => "second", + "yard" => "meter", + "year" => "year", + "year-person" => "year" + } + + @factors %{ + "acre" => {4046.8564224, 0.0}, + "ampere" => {1.0, 0.0}, + "arc-minute" => {4.6296296296296294e-5, 0.0}, + "arc-second" => {7.716049382716049e-7, 0.0}, + "astronomical-unit" => {149_597_870_700.0, 0.0}, + "atmosphere" => {101_325.0, 0.0}, + "bar" => {100_000.0, 0.0}, + "barrel" => {0.158987294928, 0.0}, + "beaufort" => {:special, 0.0}, + "becquerel" => {1.0, 0.0}, + "bit" => {1.0, 0.0}, + "british-thermal-unit" => {1054.350264488889, 0.0}, + "british-thermal-unit-it" => {1055.05585262, 0.0}, + "bu-jp" => {3.3057851239669422, 0.0}, + "bushel" => {0.03523907016688001, 0.0}, + "byte" => {8.0, 0.0}, + "calorie" => {4.184, 0.0}, + "calorie-it" => {4.1868, 0.0}, + "candela" => {1.0, 0.0}, + "carat" => {0.0002, 0.0}, + "celsius" => {1.0, 273.15}, + "century" => {100.0, 0.0}, + "chain" => {20.1168, 0.0}, + "cho" => {9917.355371900827, 0.0}, + "coulomb" => {1.0, 0.0}, + "cup" => {2.3658823650000004e-4, 0.0}, + "cup-imperial" => {2.84130625e-4, 0.0}, + "cup-jp" => {9.999999999999999e-5, 0.0}, + "cup-metric" => {2.5e-4, 0.0}, + "dalton" => {1.66053878283e-27, 0.0}, + "day" => {86400.0, 0.0}, + "day-person" => {86400.0, 0.0}, + "decade" => {10.0, 0.0}, + "degree" => {0.002777777777777778, 0.0}, + "dessert-spoon" => {1.8483455976562503e-6, 0.0}, + "dessert-spoon-imperial" => {2.2197705078125e-6, 0.0}, + "dot" => {1.0, 0.0}, + "dram" => {3.6966911953125006e-6, 0.0}, + "drop" => {5.1342933268229176e-8, 0.0}, + "dunam" => {1000.0, 0.0}, + "earth-mass" => {5.9722e24, 0.0}, + "earth-radius" => {6_378_100.0, 0.0}, + "electronvolt" => {1.602177e-19, 0.0}, + "em" => {1.0, 0.0}, + "fahrenheit" => {0.5555555555555556, 255.3722222222222}, + "farad" => {1.0, 0.0}, + "fathom" => {1.8288000000000002, 0.0}, + "fluid-ounce" => {2.9573529562500005e-5, 0.0}, + "fluid-ounce-imperial" => {2.84130625e-5, 0.0}, + "fluid-ounce-metric" => {3.0e-5, 0.0}, + "foodcalorie" => {4184.0, 0.0}, + "foot" => {0.3048, 0.0}, + "fortnight" => {1_209_600.0, 0.0}, + "fun" => {3.75e-4, 0.0}, + "furlong" => {201.168, 0.0}, + "g-force" => {9.80665, 0.0}, + "gallon" => {0.0037854117840000006, 0.0}, + "gallon-imperial" => {0.00454609, 0.0}, + "gasoline-energy-density" => {32_054_108_489.03301, 0.0}, + "grain" => {6.479891000000001e-5, 0.0}, + "gram" => {0.001, 0.0}, + "gray" => {1.0, 0.0}, + "hectare" => {10000.0, 0.0}, + "henry" => {1.0, 0.0}, + "hertz" => {1.0, 0.0}, + "horsepower" => {745.6998715822704, 0.0}, + "hour" => {3600.0, 0.0}, + "inch" => {0.025400000000000002, 0.0}, + "item" => {1.0, 0.0}, + "jigger" => {4.4360294343750005e-5, 0.0}, + "jo-jp" => {0.3305785123966942, 0.0}, + "joule" => {1.0, 0.0}, + "karat" => {0.041666666666666664, 0.0}, + "katal" => {6.02214076e23, 0.0}, + "kelvin" => {1.0, 0.0}, + "ken" => {0.19834710743801653, 0.0}, + "kilogram" => {1.0, 0.0}, + "kilogram-force" => {9.80665, 0.0}, + "knot" => {0.5144444444444445, 0.0}, + "koku" => {0.18039068369646882, 0.0}, + "kosaji" => {4.9999999999999996e-6, 0.0}, + "light-speed" => {299_792_458.0, 0.0}, + "light-year" => {9_460_730_472_580_800.0, 0.0}, + "liter" => {0.001, 0.0}, + "lumen" => {1.0, 0.0}, + "lux" => {1.0, 0.0}, + "meter" => {1.0, 0.0}, + "mile" => {1609.344, 0.0}, + "mile-scandinavian" => {10000.0, 0.0}, + "minute" => {60.0, 0.0}, + "mole" => {6.02214076e23, 0.0}, + "month" => {0.08333333333333333, 0.0}, + "month-person" => {0.08333333333333333, 0.0}, + "nautical-mile" => {1852.0, 0.0}, + "newton" => {1.0, 0.0}, + "night" => {1.0, 0.0}, + "ofglucose" => {3.342742283480345e24, 0.0}, + "ofhg" => {133_322.387415, 0.0}, + "ohm" => {1.0, 0.0}, + "osaji" => {1.4999999999999999e-5, 0.0}, + "ounce" => {0.028349523125, 0.0}, + "ounce-troy" => {0.03110348, 0.0}, + "parsec" => {3.085677581491367e16, 0.0}, + "part" => {1.0, 0.0}, + "pascal" => {1.0, 0.0}, + "percent" => {0.01, 0.0}, + "permille" => {0.001, 0.0}, + "permyriad" => {0.0001, 0.0}, + "pinch" => {2.3104319970703129e-7, 0.0}, + "pint" => {4.7317647300000007e-4, 0.0}, + "pint-imperial" => {5.6826125e-4, 0.0}, + "pint-metric" => {0.0005, 0.0}, + "pixel" => {1.0, 0.0}, + "point" => {3.527777777777778e-4, 0.0}, + "pound" => {0.45359237, 0.0}, + "pound-force" => {4.4482216152605, 0.0}, + "quart" => {9.463529460000001e-4, 0.0}, + "quart-imperial" => {0.0011365225, 0.0}, + "quarter" => {0.25, 0.0}, + "radian" => {0.15915494309189535, 0.0}, + "rankine" => {0.5555555555555556, 0.0}, + "revolution" => {1.0, 0.0}, + "ri-jp" => {428.4297520661157, 0.0}, + "rin" => {3.305785123966942e-5, 0.0}, + "rod" => {5.0292, 0.0}, + "sai" => {1.8039068369646882e-6, 0.0}, + "se-jp" => {99.17355371900827, 0.0}, + "second" => {1.0, 0.0}, + "shaku" => {1.8039068369646883e-5, 0.0}, + "shaku-cloth" => {0.04132231404958678, 0.0}, + "shaku-length" => {0.03305785123966942, 0.0}, + "siemens" => {1.0, 0.0}, + "sievert" => {1.0, 0.0}, + "slug" => {14.593902937206362, 0.0}, + "solar-luminosity" => {3.828e26, 0.0}, + "solar-mass" => {1.98847e30, 0.0}, + "solar-radius" => {695_700_000.0, 0.0}, + "steradian" => {0.025330295910584444, 0.0}, + "stone" => {6.35029318, 0.0}, + "sun" => {0.003305785123966942, 0.0}, + "tablespoon" => {1.4786764781250002e-5, 0.0}, + "teaspoon" => {4.9289215937500005e-6, 0.0}, + "tesla" => {1.0, 0.0}, + "therm-us" => {105_480_400.0, 0.0}, + "to-jp" => {0.01803906836964688, 0.0}, + "ton" => {907.18474, 0.0}, + "tonne" => {1000.0, 0.0}, + "volt" => {1.0, 0.0}, + "watt" => {1.0, 0.0}, + "weber" => {1.0, 0.0}, + "week" => {604_800.0, 0.0}, + "week-person" => {604_800.0, 0.0}, + "yard" => {0.9144000000000001, 0.0}, + "year" => {1.0, 0.0}, + "year-person" => {1.0, 0.0} + } + + @simple_base_units [ + "ampere", + "bit", + "candela", + "em", + "item", + "kelvin", + "kilogram", + "meter", + "night", + "part", + "pixel", + "revolution", + "second", + "year" + ] + + @simple_base_units_by_length [ + "revolution", + "kilogram", + "candela", + "second", + "ampere", + "kelvin", + "meter", + "night", + "pixel", + "year", + "item", + "part", + "bit", + "em" + ] + + @simple_unit_order %{ + "candela" => 0, + "kilogram" => 1, + "meter" => 2, + "second" => 3, + "year" => 4, + "night" => 5, + "ampere" => 6, + "kelvin" => 7, + "revolution" => 8, + "item" => 9, + "part" => 10, + "bit" => 11, + "pixel" => 12, + "em" => 13 + } + + @si_prefixes %{ + :atto => 1.0e-18, + :centi => 0.01, + :deci => 0.1, + :deka => 10.0, + :exa => 1.0e18, + :exbi => 1.152921504606847e18, + :femto => 1.0e-15, + :gibi => 1_073_741_824.0, + :giga => 1_000_000_000.0, + :hecto => 100.0, + :kibi => 1024.0, + :kilo => 1000.0, + :mebi => 1_048_576.0, + :mega => 1_000_000.0, + :micro => 1.0e-6, + :milli => 0.001, + :nano => 1.0e-9, + :pebi => 1_125_899_906_842_624.0, + :peta => 1_000_000_000_000_000.0, + :pico => 1.0e-12, + :quecto => 1.0e-30, + :quetta => 1.0e30, + :ronna => 1.0e27, + :ronto => 1.0e-27, + :tebi => 1_099_511_627_776.0, + :tera => 1_000_000_000_000.0, + :yobi => 1.2089258196146292e24, + :yocto => 1.0e-24, + :yotta => 1.0e24, + :zebi => 1.1805916207174113e21, + :zepto => 1.0e-21, + :zetta => 1.0e21 + } + + @aliases %{ + "#" => "pound", + "%" => "percent", + "a" => "ampere", + "ac" => "acre", + "acre" => "acre", + "acres" => "acre", + "ampere" => "ampere", + "amperes" => "ampere", + "arc minute" => "arc-minute", + "arc second" => "arc-second", + "arc-minute" => "arc-minute", + "arc-second" => "arc-second", + "arcmin" => "arc-minute", + "arcminute" => "arc-minute", + "arcminutes" => "arc-minute", + "arcsec" => "arc-second", + "arcsecond" => "arc-second", + "arcseconds" => "arc-second", + "astronomical unit" => "astronomical-unit", + "astronomical units" => "astronomical-unit", + "astronomical-unit" => "astronomical-unit", + "atm" => "atmosphere", + "atmosphere" => "atmosphere", + "atmospheres" => "atmosphere", + "au" => "astronomical-unit", + "b" => "byte", + "b 1" => "beaufort", + "b1" => "beaufort", + "bar" => "bar", + "barrel" => "barrel", + "barrels" => "barrel", + "bars" => "bar", + "bbl" => "barrel", + "beaufort" => "beaufort", + "beaufort 1" => "beaufort", + "becquerel" => "becquerel", + "becquerels" => "becquerel", + "bit" => "bit", + "bits" => "bit", + "bq" => "becquerel", + "british thermal unit" => "british-thermal-unit", + "british thermal unit [it]" => "british-thermal-unit-it", + "british thermal unit it" => "british-thermal-unit-it", + "british thermal units" => "british-thermal-unit", + "british thermal units [it]" => "british-thermal-unit-it", + "british-thermal-unit" => "british-thermal-unit", + "british-thermal-unit-it" => "british-thermal-unit-it", + "btu" => "british-thermal-unit", + "btu-it" => "british-thermal-unit-it", + "bu" => "bushel", + "bu [jp]" => "bu-jp", + "bu jp" => "bu-jp", + "bu-jp" => "bu-jp", + "bushel" => "bushel", + "bushels" => "bushel", + "byte" => "byte", + "bytes" => "byte", + "c" => "century", + "cal" => "calorie", + "cal-it" => "calorie-it", + "calorie" => "calorie", + "calorie [it]" => "calorie-it", + "calorie it" => "calorie-it", + "calorie-it" => "calorie-it", + "calories" => "calorie", + "calories [it]" => "calorie-it", + "candela" => "candela", + "carat" => "carat", + "carats" => "carat", + "cd" => "candela", + "celsius" => "celsius", + "centimeter" => "centimeter", + "centimeter per hour" => "centimeter-per-hour", + "centimeter-per-hour" => "centimeter-per-hour", + "centimeters" => "centimeter", + "centimeters per hour" => "centimeter-per-hour", + "centuries" => "century", + "century" => "century", + "ch" => "chain", + "chain" => "chain", + "chains" => "chain", + "cho" => "cho", + "cho [jp]" => "cho", + "cm" => "centimeter", + "cm/h" => "centimeter-per-hour", + "cm²" => "square-centimeter", + "cm³" => "cubic-centimeter", + "coulomb" => "coulomb", + "coulombs" => "coulomb", + "cubic centimeter" => "cubic-centimeter", + "cubic centimeters" => "cubic-centimeter", + "cubic feet" => "cubic-foot", + "cubic foot" => "cubic-foot", + "cubic inch" => "cubic-inch", + "cubic inches" => "cubic-inch", + "cubic meter" => "cubic-meter", + "cubic meters" => "cubic-meter", + "cubic-centimeter" => "cubic-centimeter", + "cubic-foot" => "cubic-foot", + "cubic-inch" => "cubic-inch", + "cubic-meter" => "cubic-meter", + "cup" => "cup", + "cup [jp]" => "cup-jp", + "cup imp." => "cup-imperial", + "cup imperial" => "cup-imperial", + "cup jp" => "cup-jp", + "cup metric" => "cup-metric", + "cup-imperial" => "cup-imperial", + "cup-jp" => "cup-jp", + "cup-metric" => "cup-metric", + "cups" => "cup", + "cups imperial" => "cup-imperial", + "d" => "day", + "da" => "dalton", + "dalton" => "dalton", + "daltons" => "dalton", + "day" => "day", + "day person" => "day-person", + "day-person" => "day-person", + "days" => "day", + "dec" => "decade", + "decade" => "decade", + "decades" => "decade", + "deg" => "degree", + "degree" => "degree", + "degree celsius" => "celsius", + "degree fahrenheit" => "fahrenheit", + "degrees" => "degree", + "degrees celsius" => "celsius", + "degrees fahrenheit" => "fahrenheit", + "dessert spoon" => "dessert-spoon", + "dessert spoon imperial" => "dessert-spoon-imperial", + "dessert spoons" => "dessert-spoon", + "dessert-spoon" => "dessert-spoon", + "dessert-spoon-imperial" => "dessert-spoon-imperial", + "dot" => "dot", + "dots" => "dot", + "dr" => "drop", + "dram" => "dram", + "drams" => "dram", + "drop" => "drop", + "drops" => "drop", + "dsp" => "dessert-spoon", + "dsp-imp" => "dessert-spoon-imperial", + "dsp-imp." => "dessert-spoon-imperial", + "dunam" => "dunam", + "dunams" => "dunam", + "earth mass" => "earth-mass", + "earth masses" => "earth-mass", + "earth radius" => "earth-radius", + "earth-mass" => "earth-mass", + "earth-radius" => "earth-radius", + "electronvolt" => "electronvolt", + "electronvolts" => "electronvolt", + "em" => "em", + "ev" => "electronvolt", + "f" => "farad", + "fahrenheit" => "fahrenheit", + "farad" => "farad", + "farads" => "farad", + "fathom" => "fathom", + "fathoms" => "fathom", + "feet" => "foot", + "fl oz" => "fluid-ounce", + "fl oz im" => "fluid-ounce-imperial", + "fl oz imp." => "fluid-ounce-imperial", + "fl oz m." => "fluid-ounce-metric", + "fl.dr." => "dram", + "fluid ounce" => "fluid-ounce", + "fluid ounce imperial" => "fluid-ounce-imperial", + "fluid ounce metric" => "fluid-ounce-metric", + "fluid ounces" => "fluid-ounce", + "fluid-ounce" => "fluid-ounce", + "fluid-ounce-imperial" => "fluid-ounce-imperial", + "fluid-ounce-metric" => "fluid-ounce-metric", + "foodcalorie" => "foodcalorie", + "foot" => "foot", + "fortnight" => "fortnight", + "fortnights" => "fortnight", + "ft" => "foot", + "fth" => "fathom", + "ft²" => "square-foot", + "ft³" => "cubic-foot", + "fun" => "fun", + "fun [jp]" => "fun", + "fur" => "furlong", + "furlong" => "furlong", + "furlongs" => "furlong", + "fw" => "fortnight", + "g" => "g-force", + "g force" => "g-force", + "g-force" => "g-force", + "gal" => "gallon", + "gal imp." => "gallon-imperial", + "gal-im" => "gallon-imperial", + "gallon" => "gallon", + "gallon imperial" => "gallon-imperial", + "gallon-imperial" => "gallon-imperial", + "gallons" => "gallon", + "gas-equiv" => "gasoline-energy-density", + "gasoline energy density" => "gasoline-energy-density", + "gasoline-energy-density" => "gasoline-energy-density", + "gigawatt" => "gigawatt", + "gigawatts" => "gigawatt", + "glc" => "ofglucose", + "gr" => "grain", + "grain" => "grain", + "grains" => "grain", + "gram" => "gram", + "grams" => "gram", + "gray" => "gray", + "grays" => "gray", + "gw" => "gigawatt", + "gy" => "gray", + "h" => "henry", + "ha" => "hectare", + "hectare" => "hectare", + "hectares" => "hectare", + "hectopascal" => "hectopascal", + "hectopascals" => "hectopascal", + "henry" => "henry", + "henrys" => "henry", + "hertz" => "hertz", + "horsepower" => "horsepower", + "hour" => "hour", + "hours" => "hour", + "hp" => "horsepower", + "hpa" => "hectopascal", + "hr" => "hour", + "hz" => "hertz", + "imp. dessert spoon" => "dessert-spoon-imperial", + "imp. dessert spoons" => "dessert-spoon-imperial", + "imp. fluid ounce" => "fluid-ounce-imperial", + "imp. fluid ounces" => "fluid-ounce-imperial", + "imp. gallon" => "gallon-imperial", + "imp. gallons" => "gallon-imperial", + "imp. quart" => "quart-imperial", + "imp. quarts" => "quart-imperial", + "in" => "inch", + "in/h" => "inch-per-hour", + "inch" => "inch", + "inch of mercury" => "inch-ofhg", + "inch ofhg" => "inch-ofhg", + "inch per hour" => "inch-per-hour", + "inch-ofhg" => "inch-ofhg", + "inch-per-hour" => "inch-per-hour", + "inches" => "inch", + "inches of mercury" => "inch-ofhg", + "inches per hour" => "inch-per-hour", + "inhg" => "inch-ofhg", + "in²" => "square-inch", + "in³" => "cubic-inch", + "item" => "item", + "item per cubic meter" => "item-per-cubic-meter", + "item-per-cubic-meter" => "item-per-cubic-meter", + "item/m³" => "item-per-cubic-meter", + "items" => "item", + "items per cubic meter" => "item-per-cubic-meter", + "j" => "joule", + "jigger" => "jigger", + "jiggers" => "jigger", + "jo [jp]" => "jo-jp", + "jo jp" => "jo-jp", + "jo-jp" => "jo-jp", + "joule" => "joule", + "joules" => "joule", + "k" => "kelvin", + "karat" => "karat", + "karats" => "karat", + "kat" => "katal", + "katal" => "katal", + "katals" => "katal", + "kcal" => "kilocalorie", + "kelvin" => "kelvin", + "kelvins" => "kelvin", + "ken" => "ken", + "ken [jp]" => "ken", + "kg" => "kilogram", + "kg/m³" => "kilogram-per-cubic-meter", + "kgf" => "kilogram-force", + "kilocalorie" => "kilocalorie", + "kilocalories" => "kilocalorie", + "kilogram" => "kilogram", + "kilogram force" => "kilogram-force", + "kilogram per cubic meter" => "kilogram-per-cubic-meter", + "kilogram-force" => "kilogram-force", + "kilogram-per-cubic-meter" => "kilogram-per-cubic-meter", + "kilograms" => "kilogram", + "kilograms per cubic meter" => "kilogram-per-cubic-meter", + "kilograms-force" => "kilogram-force", + "kilometer" => "kilometer", + "kilometer per hour" => "kilometer-per-hour", + "kilometer-per-hour" => "kilometer-per-hour", + "kilometers" => "kilometer", + "kilometers per hour" => "kilometer-per-hour", + "kilowatt" => "kilowatt", + "kilowatt hour" => "kilowatt-hour", + "kilowatt-hour" => "kilowatt-hour", + "kilowatt-hours" => "kilowatt-hour", + "kilowatts" => "kilowatt", + "km" => "kilometer", + "km/h" => "kilometer-per-hour", + "km²" => "square-kilometer", + "kn" => "knot", + "knot" => "knot", + "knots" => "knot", + "koku" => "koku", + "koku [jp]" => "koku", + "kosaji" => "kosaji", + "kosaji [jp]" => "kosaji", + "kt" => "karat", + "kw" => "kilowatt", + "kwh" => "kilowatt-hour", + "l" => "liter", + "l/100 km" => "liter-per-100-kilometer", + "l/100km" => "liter-per-100-kilometer", + "l/km" => "liter-per-kilometer", + "lb" => "pound", + "lbf" => "pound-force", + "light" => "light-speed", + "light speed" => "light-speed", + "light year" => "light-year", + "light years" => "light-year", + "light-speed" => "light-speed", + "light-year" => "light-year", + "liter" => "liter", + "liter per 100 kilometer" => "liter-per-100-kilometer", + "liter per 100 kilometers" => "liter-per-100-kilometer", + "liter per kilometer" => "liter-per-kilometer", + "liter-per-100-kilometer" => "liter-per-100-kilometer", + "liter-per-kilometer" => "liter-per-kilometer", + "liters" => "liter", + "liters per 100 kilometers" => "liter-per-100-kilometer", + "liters per kilometer" => "liter-per-kilometer", + "lm" => "lumen", + "lumen" => "lumen", + "lux" => "lux", + "lx" => "lux", + "ly" => "light-year", + "l☉" => "solar-luminosity", + "m" => "meter", + "m/guk" => "mile-per-gallon-imperial", + "m/s" => "meter-per-second", + "mb" => "millibar", + "mbar" => "millibar", + "mc" => "cup-metric", + "megapascal" => "megapascal", + "megapascals" => "megapascal", + "megawatt" => "megawatt", + "megawatts" => "megawatt", + "meter" => "meter", + "meter per second" => "meter-per-second", + "meter-per-second" => "meter-per-second", + "meters" => "meter", + "meters per second" => "meter-per-second", + "metric cup" => "cup-metric", + "metric cups" => "cup-metric", + "metric fluid ounce" => "fluid-ounce-metric", + "metric fluid ounces" => "fluid-ounce-metric", + "metric pint" => "pint-metric", + "metric pints" => "pint-metric", + "metric ton" => "tonne", + "metric tons" => "tonne", + "mg" => "milligram", + "mg/dl" => "milligram-ofglucose-per-deciliter", + "mi" => "mile", + "microgram" => "microgram", + "micrograms" => "microgram", + "microsecond" => "microsecond", + "microseconds" => "microsecond", + "mile" => "mile", + "mile per gallon" => "mile-per-gallon", + "mile per gallon imperial" => "mile-per-gallon-imperial", + "mile per hour" => "mile-per-hour", + "mile per imp. gallon" => "mile-per-gallon-imperial", + "mile scandinavian" => "mile-scandinavian", + "mile-per-gallon" => "mile-per-gallon", + "mile-per-gallon-imperial" => "mile-per-gallon-imperial", + "mile-per-hour" => "mile-per-hour", + "mile-scandinavian" => "mile-scandinavian", + "miles" => "mile", + "miles per gallon" => "mile-per-gallon", + "miles per hour" => "mile-per-hour", + "miles per imp. gallon" => "mile-per-gallon-imperial", + "miles-scandinavian" => "mile-scandinavian", + "millibar" => "millibar", + "millibars" => "millibar", + "milligram" => "milligram", + "milligram ofglucose per deciliter" => "milligram-ofglucose-per-deciliter", + "milligram per deciliter" => "milligram-ofglucose-per-deciliter", + "milligram-ofglucose-per-deciliter" => "milligram-ofglucose-per-deciliter", + "milligrams" => "milligram", + "milligrams per deciliter" => "milligram-ofglucose-per-deciliter", + "milliliter" => "milliliter", + "milliliters" => "milliliter", + "millimeter" => "millimeter", + "millimeter of mercury" => "millimeter-ofhg", + "millimeter ofhg" => "millimeter-ofhg", + "millimeter per hour" => "millimeter-per-hour", + "millimeter-ofhg" => "millimeter-ofhg", + "millimeter-per-hour" => "millimeter-per-hour", + "millimeters" => "millimeter", + "millimeters of mercury" => "millimeter-ofhg", + "millimeters per hour" => "millimeter-per-hour", + "millimole per liter" => "millimole-per-liter", + "millimole-per-liter" => "millimole-per-liter", + "millimoles per liter" => "millimole-per-liter", + "millisecond" => "millisecond", + "milliseconds" => "millisecond", + "milliwatt" => "milliwatt", + "milliwatts" => "milliwatt", + "min" => "minute", + "minute" => "minute", + "minutes" => "minute", + "mi²" => "square-mile", + "ml" => "milliliter", + "mm" => "millimeter", + "mm/h" => "millimeter-per-hour", + "mmhg" => "millimeter-ofhg", + "mmol/l" => "millimole-per-liter", + "mol" => "mole", + "mole" => "mole", + "moles" => "mole", + "month" => "month", + "month person" => "month-person", + "month-person" => "month-person", + "months" => "month", + "mpa" => "megapascal", + "mpg" => "mile-per-gallon", + "mpg imp." => "mile-per-gallon-imperial", + "mph" => "mile-per-hour", + "mpt" => "pint-metric", + "ms" => "millisecond", + "mth" => "month", + "mw" => "megawatt", + "m²" => "square-meter", + "m³" => "cubic-meter", + "m⊕" => "earth-mass", + "m☉" => "solar-mass", + "n" => "newton", + "nanosecond" => "nanosecond", + "nanoseconds" => "nanosecond", + "nautical mile" => "nautical-mile", + "nautical miles" => "nautical-mile", + "nautical-mile" => "nautical-mile", + "newton" => "newton", + "newtons" => "newton", + "night" => "night", + "nights" => "night", + "nmi" => "nautical-mile", + "ns" => "nanosecond", + "of gasoline equivalent" => "gasoline-energy-density", + "of glucose" => "ofglucose", + "of hg" => "ofhg", + "of mercury" => "ofhg", + "ofglucose" => "ofglucose", + "ofhg" => "ofhg", + "ohm" => "ohm", + "ohms" => "ohm", + "osaji" => "osaji", + "osaji [jp]" => "osaji", + "ounce" => "ounce", + "ounce troy" => "ounce-troy", + "ounce-troy" => "ounce-troy", + "ounces" => "ounce", + "oz" => "ounce", + "oz t" => "ounce-troy", + "pa" => "pascal", + "parsec" => "parsec", + "parsecs" => "parsec", + "part" => "part", + "parts" => "part", + "pascal" => "pascal", + "pascals" => "pascal", + "pc" => "parsec", + "percent" => "percent", + "permille" => "permille", + "permyriad" => "permyriad", + "pinch" => "pinch", + "pinches" => "pinch", + "pint" => "pint", + "pint imperial" => "pint-imperial", + "pint metric" => "pint-metric", + "pint-imperial" => "pint-imperial", + "pint-metric" => "pint-metric", + "pints" => "pint", + "pints imperial" => "pint-imperial", + "pixel" => "pixel", + "pixels" => "pixel", + "pn" => "pinch", + "point" => "point", + "points" => "point", + "pound" => "pound", + "pound force" => "pound-force", + "pound force per square inch" => "pound-force-per-square-inch", + "pound of force" => "pound-force", + "pound-force" => "pound-force", + "pound-force per square inch" => "pound-force-per-square-inch", + "pound-force-per-square-inch" => "pound-force-per-square-inch", + "pounds" => "pound", + "pounds of force" => "pound-force", + "pounds-force per square inch" => "pound-force-per-square-inch", + "psi" => "pound-force-per-square-inch", + "pt" => "pint", + "pt imp." => "pint-imperial", + "px" => "pixel", + "q" => "quarter", + "qt" => "quart", + "qt-imp." => "quart-imperial", + "qtr" => "quarter", + "quart" => "quart", + "quart imperial" => "quart-imperial", + "quart-imperial" => "quart-imperial", + "quarter" => "quarter", + "quarters" => "quarter", + "quarts" => "quart", + "rad" => "radian", + "radian" => "radian", + "radians" => "radian", + "rankine" => "rankine", + "rankines" => "rankine", + "rd" => "rod", + "rev" => "revolution", + "revolution" => "revolution", + "revolutions" => "revolution", + "ri [jp]" => "ri-jp", + "ri jp" => "ri-jp", + "ri-jp" => "ri-jp", + "rin" => "rin", + "rin [jp]" => "rin", + "rod" => "rod", + "rods" => "rod", + "r⊕" => "earth-radius", + "r☉" => "solar-radius", + "s" => "second", + "sai" => "sai", + "sai [jp]" => "sai", + "se [jp]" => "se-jp", + "se jp" => "se-jp", + "se-jp" => "se-jp", + "sec" => "second", + "second" => "second", + "seconds" => "second", + "shaku" => "shaku", + "shaku [cloth, jp]" => "shaku-cloth", + "shaku [jp]" => "shaku-length", + "shaku [vol, jp]" => "shaku", + "shaku [volume, jp]" => "shaku", + "shaku cloth" => "shaku-cloth", + "shaku length" => "shaku-length", + "shaku-cloth" => "shaku-cloth", + "shaku-length" => "shaku-length", + "siemens" => "siemens", + "sievert" => "sievert", + "sieverts" => "sievert", + "slug" => "slug", + "slugs" => "slug", + "smi" => "mile-scandinavian", + "solar luminosities" => "solar-luminosity", + "solar luminosity" => "solar-luminosity", + "solar mass" => "solar-mass", + "solar masses" => "solar-mass", + "solar radii" => "solar-radius", + "solar radius" => "solar-radius", + "solar-luminosity" => "solar-luminosity", + "solar-mass" => "solar-mass", + "solar-radius" => "solar-radius", + "sq ft" => "square-foot", + "sq mi" => "square-mile", + "square centimeter" => "square-centimeter", + "square centimeters" => "square-centimeter", + "square feet" => "square-foot", + "square foot" => "square-foot", + "square inch" => "square-inch", + "square inches" => "square-inch", + "square kilometer" => "square-kilometer", + "square kilometers" => "square-kilometer", + "square meter" => "square-meter", + "square meters" => "square-meter", + "square mile" => "square-mile", + "square miles" => "square-mile", + "square-centimeter" => "square-centimeter", + "square-foot" => "square-foot", + "square-inch" => "square-inch", + "square-kilometer" => "square-kilometer", + "square-meter" => "square-meter", + "square-mile" => "square-mile", + "sr" => "steradian", + "st" => "stone", + "steradian" => "steradian", + "steradians" => "steradian", + "stone" => "stone", + "stones" => "stone", + "sun" => "sun", + "sun [jp]" => "sun", + "sv" => "sievert", + "t" => "tesla", + "tablespoon" => "tablespoon", + "tablespoons" => "tablespoon", + "tbsp" => "tablespoon", + "teaspoon" => "teaspoon", + "teaspoons" => "teaspoon", + "tesla" => "tesla", + "teslas" => "tesla", + "therm us" => "therm-us", + "therm-us" => "therm-us", + "tn" => "ton", + "to [jp]" => "to-jp", + "to jp" => "to-jp", + "to-jp" => "to-jp", + "ton" => "ton", + "tonne" => "tonne", + "tons" => "ton", + "troy ounce" => "ounce-troy", + "troy ounces" => "ounce-troy", + "tsp" => "teaspoon", + "typographic ems" => "em", + "us therm" => "therm-us", + "us therms" => "therm-us", + "v" => "volt", + "volt" => "volt", + "volts" => "volt", + "w" => "watt", + "watt" => "watt", + "watts" => "watt", + "wb" => "weber", + "weber" => "weber", + "webers" => "weber", + "week" => "week", + "week person" => "week-person", + "week-person" => "week-person", + "weeks" => "week", + "wk" => "week", + "y" => "year", + "yard" => "yard", + "yards" => "yard", + "yd" => "yard", + "year" => "year", + "year person" => "year-person", + "year-person" => "year-person", + "years" => "year", + "yr" => "year", + "°" => "degree", + "°c" => "celsius", + "°f" => "fahrenheit", + "°r" => "rankine", + "μg" => "microgram", + "μs" => "microsecond", + "ω" => "ohm", + "‰" => "permille", + "‱" => "permyriad", + "′" => "arc-minute", + "″" => "arc-second", + "″ hg" => "inch-ofhg", + "″/h" => "inch-per-hour" + } + + @doc """ + Converts a unit literal to its base unit at compile time. + + The sigil resolves during compilation and expands to a literal, so + no call to this module survives into the emitted code. Only literals + are accepted — a sigil carrying `\#{...}` interpolation could not be + resolved at compile time and is a compile error rather than a + silent fall back to runtime work. + + ### Examples + + iex> import __MODULE__ + iex> ~u"30 kilometers per hour" + {8.333333333333334, "meter-per-second"} + + iex> ~u"5 km" + {5000.0, "meter"} + + """ + defmacro sigil_u({:<<>>, _meta, [literal]}, _modifiers) when is_binary(literal) do + {value, unit} = split_literal!(literal) + + case to_base(value, unit) do + {:ok, result} -> + Macro.escape(result) + + {:error, {:unknown_unit, unknown}} -> + raise ArgumentError, "unknown unit #{inspect(unknown)} in ~u sigil" + end + end + + defmacro sigil_u(_ast, _modifiers) do + raise ArgumentError, + "the ~u sigil takes a literal with no interpolation so that the " <> + "conversion resolves at compile time — use to_base/2 for values " <> + "known only at runtime" + end + + defp split_literal!(literal) do + case String.split(String.trim(literal), " ", parts: 2) do + [value, unit] -> {parse_number!(value), String.trim(unit)} + _other -> raise ArgumentError, ~s(expected " ", got: #{inspect(literal)}) + end + end + + defp parse_number!(string) do + case Float.parse(string) do + {number, ""} -> number + _other -> raise ArgumentError, "invalid number #{inspect(string)} in ~u sigil" + end + end + + @doc """ + Converts a value in `unit` to its base unit. + + ### Arguments + + * `value` is a number. + + * `unit` is a unit name in any accepted spelling — the CLDR + identifier (`"kilometer-per-hour"`), a spaced form + (`"kilometers per hour"`), or the English symbol (`"km/h"`). + + ### Returns + + * `{:ok, {value_in_base_units, base_unit_name}}`, or + + * `{:error, {:unknown_unit, unit}}`. + + ### Examples + + iex> to_base(30, "kilometer-per-hour") + {:ok, {8.333333333333334, "meter-per-second"}} + + iex> to_base(30, "km/h") + {:ok, {8.333333333333334, "meter-per-second"}} + + """ + def to_base(value, unit) when is_number(value) and is_binary(unit) do + with {:ok, {base, factor, offset}} <- resolve(unit) do + {:ok, {value * factor + offset, base}} + end + end + + @doc """ + Resolves a unit name to `{base_unit, factor, offset}`. + + Any spelling in the alias table is mapped onto its CLDR identifier + first; the identifier is then parsed and reduced. Returns + `{:error, {:unknown_unit, unit}}` for anything that does not resolve. + + """ + def resolve(unit) when is_binary(unit) do + normalized = unit |> String.trim() |> String.downcase() + + case resolve_identifier(canonical(normalized)) do + {:ok, _resolved} = ok -> ok + {:error, _reason} -> {:error, {:unknown_unit, unit}} + end + end + + @doc """ + Returns the CLDR identifiers of the units in the generated tables. + + This is a floor rather than the full accepted set: every SI-prefixed, + powered and `-per-` compound form built from these also converts, and + that set is unbounded. + + """ + def known_units, do: Map.keys(@conversions) ++ @simple_base_units + + defp canonical(normalized) do + case Map.fetch(@aliases, normalized) do + {:ok, identifier} -> identifier + :error -> Map.get(@aliases, String.replace(normalized, " ", "-"), normalized) + end + end + + # ── Resolution ──────────────────────────────────────────────── + # + # Identifiers are parsed by the inlined Localize parser, so anything + # Localize accepts is accepted here: prefixes, powers, multi-unit + # numerators and denominators, and arbitrary `-per-` compounds. The + # parsed form is then reduced to a canonical base unit and an affine + # factor, exactly as Localize does. + + defp resolve_identifier(identifier) do + with {:ok, ast} <- parse_identifier(identifier), + {:ok, powers} <- decompose(ast), + {:ok, factor, offset} <- scale(ast) do + {:ok, {recompose(powers), factor, offset}} + end + end + + defp parse_identifier(identifier) do + case __MODULE__.Parser.parse(identifier) do + {:ok, ast} -> {:ok, ast} + _other -> {:error, {:unknown_unit, identifier}} + end + end + + # ── Base unit derivation ────────────────────────────────────── + + defp decompose({:unit, keyword}) do + with {:ok, numerator} <- decompose_list(Keyword.get(keyword, :numerator, [])), + {:ok, denominator} <- decompose_list(Keyword.get(keyword, :denominator, [])) do + {:ok, merge_powers(numerator, Map.new(denominator, fn {u, p} -> {u, -p} end))} + end + end + + defp decompose({:single_unit, _} = single), do: decompose_single(single) + + # A mixed unit ("foot-and-inch") is a way of displaying one quantity, not a + # unit with a conversion — one foot-and-inch is not a length. Localize + # refuses to convert them too. + defp decompose(_other), do: {:error, :unsupported_unit} + + defp decompose_list(units) do + Enum.reduce_while(units, {:ok, %{}}, fn unit, {:ok, acc} -> + case decompose_single(unit) do + {:ok, powers} -> {:cont, {:ok, merge_powers(acc, powers)}} + error -> {:halt, error} + end + end) + end + + defp decompose_single({:single_unit, keyword}) do + base = Keyword.fetch!(keyword, :base) + power = Keyword.get(keyword, :power) + + case Map.fetch(@conversions, base) do + {:ok, base_string} -> + {:ok, apply_power(parse_base_string(base_string), power)} + + :error -> + if base in @simple_base_units do + {:ok, apply_power(%{base => 1}, power)} + else + {:error, {:unknown_unit, base}} + end + end + end + + defp decompose_single({:constant, _value}), do: {:ok, %{}} + defp decompose_single(_other), do: {:error, :unsupported_unit} + + defp parse_base_string("per-" <> denominator), do: product(denominator, -1) + + defp parse_base_string(string) do + case String.split(string, "-per-", parts: 2) do + [numerator] -> product(numerator, 1) + [numerator, denominator] -> merge_powers(product(numerator, 1), product(denominator, -1)) + end + end + + defp product("", _sign), do: %{} + defp product(string, sign), do: product_tokens(string, sign, %{}) + + defp product_tokens("", _sign, acc), do: acc + + defp product_tokens(string, sign, acc) do + {multiplier, rest} = consume_power_prefix(string) + {name, remainder} = consume_simple_unit(rest) + acc = Map.update(acc, name, sign * multiplier, &(&1 + sign * multiplier)) + + case remainder do + "" -> acc + "-" <> tail -> product_tokens(tail, sign, acc) + _other -> acc + end + end + + defp consume_power_prefix("square-" <> rest), do: {2, rest} + defp consume_power_prefix("cubic-" <> rest), do: {3, rest} + + defp consume_power_prefix("pow" <> rest = string) do + case Integer.parse(rest) do + {n, "-" <> tail} -> {n, tail} + _other -> {1, string} + end + end + + defp consume_power_prefix(string), do: {1, string} + + # Base unit names may themselves contain hyphens, so the longest match + # wins — @simple_base_units is emitted longest-first for that reason. + defp consume_simple_unit(string) do + case Enum.find(@simple_base_units_by_length, &String.starts_with?(string, &1)) do + nil -> + case String.split(string, "-", parts: 2) do + [token, rest] -> {token, "-" <> rest} + [token] -> {token, ""} + end + + unit -> + {unit, String.slice(string, String.length(unit)..-1//1)} + end + end + + defp apply_power(powers, nil), do: powers + defp apply_power(powers, :square), do: Map.new(powers, fn {u, p} -> {u, p * 2} end) + defp apply_power(powers, :cubic), do: Map.new(powers, fn {u, p} -> {u, p * 3} end) + defp apply_power(powers, {:pow, n}), do: Map.new(powers, fn {u, p} -> {u, p * n} end) + + defp merge_powers(left, right) do + left + |> Map.merge(right, fn _unit, a, b -> a + b end) + |> Enum.reject(fn {_unit, power} -> power == 0 end) + |> Map.new() + end + + defp recompose(powers) when powers == %{}, do: "" + + defp recompose(powers) do + {numerator, denominator} = + powers + |> Enum.reject(fn {_unit, power} -> power == 0 end) + |> Enum.split_with(fn {_unit, power} -> power > 0 end) + + numerator_string = numerator |> sort_canonically() |> format_product() + + denominator_string = + denominator + |> Enum.map(fn {unit, power} -> {unit, abs(power)} end) + |> sort_canonically() + |> format_product() + + case {numerator_string, denominator_string} do + {"", ""} -> "" + {n, ""} -> n + {"", d} -> "per-" <> d + {n, d} -> n <> "-per-" <> d + end + end + + defp sort_canonically(units) do + Enum.sort_by(units, fn {unit, _power} -> Map.get(@simple_unit_order, unit, 999) end) + end + + defp format_product([]), do: "" + + defp format_product(units) do + Enum.map_join(units, "-", fn {unit, power} -> powered_unit(unit, power) end) + end + + defp powered_unit(unit, 1), do: unit + defp powered_unit(unit, 2), do: "square-" <> unit + defp powered_unit(unit, 3), do: "cubic-" <> unit + defp powered_unit(unit, n), do: "pow" <> Integer.to_string(n) <> "-" <> unit + + # ── Factor composition ──────────────────────────────────────── + + defp scale({:unit, keyword}) do + numerator = Keyword.get(keyword, :numerator, []) + denominator = Keyword.get(keyword, :denominator, []) + + with {:ok, factor_n, offset_n} <- scale_list(numerator), + {:ok, factor_d, _offset_d} <- scale_list(denominator) do + {:ok, factor_n / factor_d, offset(offset_n, numerator, denominator)} + end + end + + defp scale(_other), do: {:error, :unsupported_unit} + + # An offset only means something for a unit standing on its own. + # "celsius" is an affine temperature; "kilocelsius" and "celsius-per-hour" + # are scales built from one, and carry no zero point. A power keeps it — + # "square-celsius" converts like "celsius" — which is how CLDR reads them. + defp offset(offset, [{:single_unit, keyword}], []) do + if Keyword.get(keyword, :prefix), do: 0.0, else: offset + end + + defp offset(_offset, _numerator, _denominator), do: 0.0 + + defp scale_list(units) do + Enum.reduce_while(units, {:ok, 1.0, 0.0}, fn unit, {:ok, factor, offset} -> + case scale_single(unit) do + {:ok, f, o} -> {:cont, {:ok, factor * f, if(o != 0.0, do: o, else: offset)}} + error -> {:halt, error} + end + end) + end + + defp scale_single({:single_unit, keyword}) do + base = Keyword.fetch!(keyword, :base) + multiplier = prefix_multiplier(Keyword.get(keyword, :prefix)) + exponent = power_exponent(Keyword.get(keyword, :power)) + + case Map.fetch(@factors, base) do + # CLDR marks a non-affine scale (beaufort) as :special. It cannot be + # reduced to a factor and offset, so it is refused rather than guessed. + {:ok, {factor, _offset}} when not is_number(factor) -> + {:error, {:not_affine, base}} + + {:ok, {factor, offset}} -> + {:ok, :math.pow(factor * multiplier, exponent), offset} + + :error -> + if base in @simple_base_units do + {:ok, :math.pow(multiplier, exponent), 0.0} + else + {:error, {:unknown_unit, base}} + end + end + end + + defp scale_single({:constant, value}) when is_number(value), do: {:ok, value * 1.0, 0.0} + + defp scale_single({:constant, value}) when is_binary(value) do + case Float.parse(value) do + {number, ""} -> {:ok, number, 0.0} + _other -> {:error, {:unsupported_constant, value}} + end + end + + defp scale_single(_other), do: {:error, :unsupported_unit} + + # The parser tags prefixes as atoms; the multiplier table is keyed the same + # way so no conversion is needed at call time. + defp prefix_multiplier(nil), do: 1.0 + defp prefix_multiplier(prefix), do: Map.get(@si_prefixes, prefix, 1.0) + + defp power_exponent(nil), do: 1 + defp power_exponent(:square), do: 2 + defp power_exponent(:cubic), do: 3 + defp power_exponent({:pow, n}), do: n +end diff --git a/lib/bb/unit/conversions.ex.license b/lib/bb/unit/conversions.ex.license new file mode 100644 index 0000000..301fa7a --- /dev/null +++ b/lib/bb/unit/conversions.ex.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: 2026 James Harton + +SPDX-License-Identifier: Apache-2.0 diff --git a/lib/bb/unit/option.ex b/lib/bb/unit/option.ex index 06ebfb3..23bc046 100644 --- a/lib/bb/unit/option.ex +++ b/lib/bb/unit/option.ex @@ -35,9 +35,9 @@ defmodule BB.Unit.Option do @type schema_options :: [schema_option] @type schema_option :: {:compatible, atom | String.t()} - | {:min, Localize.Unit.t()} - | {:max, Localize.Unit.t()} - | {:eq, Localize.Unit.t()} + | {:min, BB.Unit.t()} + | {:max, BB.Unit.t()} + | {:eq, BB.Unit.t()} @doc """ Create a Spark.Options schema type for a unit. @@ -56,18 +56,18 @@ defmodule BB.Unit.Option do With min constraint: - iex> BB.Unit.Option.unit_type(min: Localize.Unit.new!(0, "meter")) - {:custom, BB.Unit.Option, :validate, [[min: Localize.Unit.new!(0, "meter")]]} + iex> BB.Unit.Option.unit_type(min: BB.Unit.new!(0, "meter")) + {:custom, BB.Unit.Option, :validate, [[min: BB.Unit.new!(0, "meter")]]} With max constraint: - iex> BB.Unit.Option.unit_type(max: Localize.Unit.new!(100, "meter")) - {:custom, BB.Unit.Option, :validate, [[max: Localize.Unit.new!(100, "meter")]]} + iex> BB.Unit.Option.unit_type(max: BB.Unit.new!(100, "meter")) + {:custom, BB.Unit.Option, :validate, [[max: BB.Unit.new!(100, "meter")]]} Combined constraints: - iex> BB.Unit.Option.unit_type(compatible: :meter, min: Localize.Unit.new!(0, "meter"), max: Localize.Unit.new!(100, "meter")) - {:custom, BB.Unit.Option, :validate, [[compatible: :meter, min: Localize.Unit.new!(0, "meter"), max: Localize.Unit.new!(100, "meter")]]} + iex> BB.Unit.Option.unit_type(compatible: :meter, min: BB.Unit.new!(0, "meter"), max: BB.Unit.new!(100, "meter")) + {:custom, BB.Unit.Option, :validate, [[compatible: :meter, min: BB.Unit.new!(0, "meter"), max: BB.Unit.new!(100, "meter")]]} """ @spec unit_type(Keyword.t()) :: {:custom, __MODULE__, :validate, [Keyword.t()]} def unit_type(options \\ []) do @@ -91,38 +91,38 @@ defmodule BB.Unit.Option do Valid unit passes through: - iex> BB.Unit.Option.validate(Localize.Unit.new!(5, "meter")) - {:ok, Localize.Unit.new!(5, "meter")} + iex> BB.Unit.Option.validate(BB.Unit.new!(5, "meter")) + {:ok, BB.Unit.new!(5, "meter")} Non-unit values are rejected: iex> BB.Unit.Option.validate("not a unit") - {:error, "Value `\\"not a unit\\"` is not a `Localize.Unit` struct"} + {:error, "Value `\\"not a unit\\"` is not a `BB.Unit` struct"} Compatible unit check passes for same category: - iex> BB.Unit.Option.validate(Localize.Unit.new!(100, "centimeter"), compatible: :meter) - {:ok, Localize.Unit.new!(100, "centimeter")} + iex> BB.Unit.Option.validate(BB.Unit.new!(100, "centimeter"), compatible: :meter) + {:ok, BB.Unit.new!(100, "centimeter")} Incompatible units are rejected: - iex> BB.Unit.Option.validate(Localize.Unit.new!(90, "degree"), compatible: :meter) + iex> BB.Unit.Option.validate(BB.Unit.new!(90, "degree"), compatible: :meter) {:error, "The unit `degree` is not compatible with `meter`"} Min constraint - value must be >= min: - iex> BB.Unit.Option.validate(Localize.Unit.new!(5, "meter"), min: Localize.Unit.new!(1, "meter")) - {:ok, Localize.Unit.new!(5, "meter")} + iex> BB.Unit.Option.validate(BB.Unit.new!(5, "meter"), min: BB.Unit.new!(1, "meter")) + {:ok, BB.Unit.new!(5, "meter")} Max constraint - value must be <= max: - iex> BB.Unit.Option.validate(Localize.Unit.new!(5, "meter"), max: Localize.Unit.new!(10, "meter")) - {:ok, Localize.Unit.new!(5, "meter")} + iex> BB.Unit.Option.validate(BB.Unit.new!(5, "meter"), max: BB.Unit.new!(10, "meter")) + {:ok, BB.Unit.new!(5, "meter")} Eq constraint - value must equal exactly: - iex> BB.Unit.Option.validate(Localize.Unit.new!(5, "meter"), eq: Localize.Unit.new!(5, "meter")) - {:ok, Localize.Unit.new!(5, "meter")} + iex> BB.Unit.Option.validate(BB.Unit.new!(5, "meter"), eq: BB.Unit.new!(5, "meter")) + {:ok, BB.Unit.new!(5, "meter")} ParamRef values are accepted and annotated with expected unit type: @@ -132,7 +132,7 @@ defmodule BB.Unit.Option do :meter """ @spec validate(any, Keyword.t()) :: - {:ok, Localize.Unit.t()} | {:ok, BB.Dsl.ParamRef.t()} | {:error, String.t()} + {:ok, BB.Unit.t()} | {:ok, BB.Dsl.ParamRef.t()} | {:error, String.t()} def validate(value, options \\ []) def validate(%BB.Dsl.ParamRef{} = ref, options) do @@ -148,10 +148,10 @@ defmodule BB.Unit.Option do end end - defp validate_is_unit(unit) when is_struct(unit, Localize.Unit), do: {:ok, unit} + defp validate_is_unit(unit) when is_struct(unit, Unit), do: {:ok, unit} defp validate_is_unit(unit), - do: {:error, "Value `#{inspect(unit)}` is not a `Localize.Unit` struct"} + do: {:error, "Value `#{inspect(unit)}` is not a `BB.Unit` struct"} defp validate_compatible(unit, nil), do: {:ok, unit} @@ -171,7 +171,7 @@ defmodule BB.Unit.Option do value, min, [:gt, :eq], - "Expected #{Unit.to_string!(value, style: :narrow)} to be greater than or equal to #{Unit.to_string!(min, style: :narrow)}" + "Expected #{Unit.to_string!(value)} to be greater than or equal to #{Unit.to_string!(min)}" ) defp validate_max(value, nil), do: {:ok, value} @@ -182,7 +182,7 @@ defmodule BB.Unit.Option do value, max, [:lt, :eq], - "Expected #{Unit.to_string!(value, style: :narrow)} to be less than or equal to #{Unit.to_string!(max, style: :narrow)}" + "Expected #{Unit.to_string!(value)} to be less than or equal to #{Unit.to_string!(max)}" ) defp validate_eq(value, nil), do: {:ok, value} @@ -193,7 +193,7 @@ defmodule BB.Unit.Option do value, eq, [:eq], - "Expected #{Unit.to_string!(value, style: :short)} to equal #{Unit.to_string!(eq, style: :short)}" + "Expected #{Unit.to_string!(value)} to equal #{Unit.to_string!(eq)}" ) defp validate_cmp(value, cmp, valid, message) do diff --git a/mix.exs b/mix.exs index 72b2ecb..15969b6 100644 --- a/mix.exs +++ b/mix.exs @@ -30,6 +30,7 @@ defmodule BB.MixProject do defp dialyzer do [ + ignore_warnings: ".dialyzer_ignore.exs", plt_add_apps: [:mix] ] end @@ -202,7 +203,6 @@ defmodule BB.MixProject do defp deps do [ {:ease, "~> 1.0"}, - {:localize, "~> 1.0"}, {:nx, "~> 0.10"}, {:spark, "~> 2.3"}, {:splode, "~> 0.2"}, @@ -216,6 +216,9 @@ defmodule BB.MixProject do {:ex_doc, ">= 0.0.0", only: [:dev, :test], runtime: false}, {:git_ops, "~> 2.9", only: [:dev, :test], runtime: false}, {:igniter, "~> 0.6", optional: true}, + # Generates `BB.Unit.Conversions`; see `mix localize.unit.gen_conversions`. + {:localize, + github: "elixir-localize/localize", ref: "b77cb38c", only: [:dev], runtime: false}, {:mimic, "~> 2.2", only: :test}, {:mix_audit, "~> 2.1", only: [:dev, :test], runtime: false}, {:usage_rules, "~> 1.2", only: [:dev], runtime: false} diff --git a/mix.lock b/mix.lock index d51a21e..fcf19ac 100644 --- a/mix.lock +++ b/mix.lock @@ -23,7 +23,7 @@ "hpax": {:hex, :hpax, "1.0.4", "777de5d433b0fbdc7c418159c8055910faa8047ffdb3d6b31098d2a46cd7685c", [:mix], [], "hexpm", "afc7cb142ebcc2d01ce7816190b98ce5dd49e799111b24249f3443d730f377ca"}, "igniter": {:hex, :igniter, "0.8.3", "9de74d3885efae43b0b58dc6f7b816963c4bbd391e6b6fe6922ee21c4e384c76", [:mix], [{:ex_ast, "~> 0.5", [hex: :ex_ast, repo: "hexpm", optional: false]}, {:glob_ex, "~> 0.1.7", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:jason, "~> 1.4.5", [hex: :jason, repo: "hexpm", optional: false]}, {:owl, "~> 0.11", [hex: :owl, repo: "hexpm", optional: false]}, {:phx_new, "~> 1.7", [hex: :phx_new, repo: "hexpm", optional: true]}, {:req, "~> 0.5", [hex: :req, repo: "hexpm", optional: false]}, {:rewrite, ">= 1.1.1 and < 2.0.0-0", [hex: :rewrite, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.4", [hex: :sourceror, repo: "hexpm", optional: false]}, {:spitfire, ">= 0.1.3 and < 1.0.0-0", [hex: :spitfire, repo: "hexpm", optional: false]}], "hexpm", "afc5e3848d885e680da5c3b65e5e7717555a08cd12305190ff2be76427af39ff"}, "jason": {:hex, :jason, "1.4.5", "2e3a008590b0b8d7388c20293e9dcc9cf3e5d642fd2a114e4cbbb52e595d940a", [:mix], [{:decimal, "~> 1.0 or ~> 2.0 or ~> 3.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "b0c823996102bcd0239b3c2444eb00409b72f6a140c1950bc8b457d836b30684"}, - "localize": {:hex, :localize, "1.0.1", "a7fdbd53836e96ef2af7cc2fe1eb4f06963291e9b10ec1a13daf4b1bd6d53696", [:mix], [{:decimal, "~> 2.0 or ~> 3.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:elixir_make, "~> 0.4", [hex: :elixir_make, repo: "hexpm", optional: true]}, {:gettext, "~> 1.0", [hex: :gettext, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "9186d20aecaadfee1e33ea089a450cf80347ff6a1bc8b58f0cfa18e81e2d31e8"}, + "localize": {:git, "https://github.com/elixir-localize/localize.git", "b77cb38c3755266fd94b83a41ad9568709fe3b27", [ref: "b77cb38c"]}, "makeup": {:hex, :makeup, "1.2.1", "e90ac1c65589ef354378def3ba19d401e739ee7ee06fb47f94c687016e3713d1", [:mix], [{:nimble_parsec, "~> 1.4", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "d36484867b0bae0fea568d10131197a4c2e47056a6fbe84922bf6ba71c8d17ce"}, "makeup_elixir": {:hex, :makeup_elixir, "1.0.1", "e928a4f984e795e41e3abd27bfc09f51db16ab8ba1aebdba2b3a575437efafc2", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.2.3 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "7284900d412a3e5cfd97fdaed4f5ed389b8f2b4cb49efc0eb3bd10e2febf9507"}, "makeup_erlang": {:hex, :makeup_erlang, "1.1.0", "835f7e60792e08824cda445639555d7bf1bbbddb1b60b306e33cb6f6db24dc74", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "1cd6780fb1dd1a03979abaed0fe82712b0625118fd5257d3ebbf73f960c73c3c"}, diff --git a/test/bb/dsl/parameter_test.exs b/test/bb/dsl/parameter_test.exs index 8d6d0b6..5b4124c 100644 --- a/test/bb/dsl/parameter_test.exs +++ b/test/bb/dsl/parameter_test.exs @@ -273,7 +273,7 @@ defmodule BB.Dsl.ParameterTest do {[:motion, :max_speed], default} = Enum.find(defaults, fn {path, _} -> path == [:motion, :max_speed] end) - assert %Localize.Unit{} = default + assert %BB.Unit{} = default assert default.name == "meter-per-second" end @@ -281,7 +281,7 @@ defmodule BB.Dsl.ParameterTest do start_supervised!(RobotWithUnitParams) assert {:ok, value} = Parameter.get(RobotWithUnitParams, [:motion, :max_speed]) - assert %Localize.Unit{} = value + assert %BB.Unit{} = value assert value.name == "meter-per-second" end @@ -293,7 +293,7 @@ defmodule BB.Dsl.ParameterTest do Parameter.set(RobotWithUnitParams, [:motion, :max_speed], ~u(2.0 meter_per_second)) assert {:ok, value} = Parameter.get(RobotWithUnitParams, [:motion, :max_speed]) - assert Localize.Unit.compare(value, ~u(2.0 meter_per_second)) == :eq + assert BB.Unit.compare(value, ~u(2.0 meter_per_second)) == :eq # Set with compatible unit (should work - kilometer_per_hour is compatible with meter_per_second) assert :ok = @@ -304,7 +304,7 @@ defmodule BB.Dsl.ParameterTest do ) assert {:ok, value} = Parameter.get(RobotWithUnitParams, [:motion, :max_speed]) - assert Localize.Unit.compare(value, ~u(10 meter_per_second)) == :eq + assert BB.Unit.compare(value, ~u(10 meter_per_second)) == :eq end test "unit params reject incompatible units" do @@ -406,7 +406,7 @@ defmodule BB.Dsl.ParameterTest do ) assert {:ok, value} = Parameter.get(RobotWithUnitParams, [:motion, :max_speed]) - assert Localize.Unit.compare(value, ~u(3.0 meter_per_second)) == :eq + assert BB.Unit.compare(value, ~u(3.0 meter_per_second)) == :eq end test "unit params with incompatible units cause startup failure" do diff --git a/test/bb/integration/child_spec_param_ref_test.exs b/test/bb/integration/child_spec_param_ref_test.exs index 2d3711b..ea339c3 100644 --- a/test/bb/integration/child_spec_param_ref_test.exs +++ b/test/bb/integration/child_spec_param_ref_test.exs @@ -238,7 +238,7 @@ defmodule BB.Integration.ChildSpecParamRefTest do # Should receive the resolved unit value (10 newton_meter as Cldr.Unit) assert max_effort != nil - assert {:ok, converted} = Localize.Unit.convert(max_effort, "newton-meter") + assert {:ok, converted} = BB.Unit.convert(max_effort, "newton-meter") assert_in_delta Units.extract_float(converted), 10.0, 0.001 end @@ -274,7 +274,7 @@ defmodule BB.Integration.ChildSpecParamRefTest do {:actuator_handle_options, new_max_effort} = handle_opts_event assert new_max_effort != nil - assert {:ok, converted} = Localize.Unit.convert(new_max_effort, "newton-meter") + assert {:ok, converted} = BB.Unit.convert(new_max_effort, "newton-meter") assert_in_delta Units.extract_float(converted), 20.0, 0.001 end end @@ -294,7 +294,7 @@ defmodule BB.Integration.ChildSpecParamRefTest do # Should receive the resolved unit value (100 hertz as Cldr.Unit) assert sample_rate != nil - assert {:ok, converted} = Localize.Unit.convert(sample_rate, "hertz") + assert {:ok, converted} = BB.Unit.convert(sample_rate, "hertz") assert_in_delta Units.extract_float(converted), 100.0, 0.001 end @@ -330,7 +330,7 @@ defmodule BB.Integration.ChildSpecParamRefTest do {:sensor_handle_options, new_sample_rate} = handle_opts_event assert new_sample_rate != nil - assert {:ok, converted} = Localize.Unit.convert(new_sample_rate, "hertz") + assert {:ok, converted} = BB.Unit.convert(new_sample_rate, "hertz") assert_in_delta Units.extract_float(converted), 200.0, 0.001 end end diff --git a/test/bb/parameter/store_test.exs b/test/bb/parameter/store_test.exs index e09027e..b5f68d0 100644 --- a/test/bb/parameter/store_test.exs +++ b/test/bb/parameter/store_test.exs @@ -102,8 +102,8 @@ defmodule BB.Parameter.StoreTest do :ok = Dets.save(state, [:string_val], "hello") # Atom :ok = Dets.save(state, [:atom_val], :test_atom) - # Unit (Cldr.Unit struct) - :ok = Dets.save(state, [:unit_val], Localize.Unit.new!(1.5, "meter")) + # Unit (BB.Unit struct) + :ok = Dets.save(state, [:unit_val], BB.Unit.new!(1.5, "meter")) {:ok, params} = Dets.load(state) assert length(params) == 6 @@ -115,7 +115,7 @@ defmodule BB.Parameter.StoreTest do assert {[:atom_val], :test_atom} in params {[:unit_val], unit} = Enum.find(params, fn {path, _} -> path == [:unit_val] end) - assert %Localize.Unit{} = unit + assert %BB.Unit{} = unit assert unit.name == "meter" :ok = Dets.close(state) diff --git a/test/bb/robot/units_test.exs b/test/bb/robot/units_test.exs index fb264b7..b0dd557 100644 --- a/test/bb/robot/units_test.exs +++ b/test/bb/robot/units_test.exs @@ -116,18 +116,13 @@ defmodule BB.Robot.UnitsTest do describe "extract_float/1" do test "extracts integer value" do - unit = Localize.Unit.new!(5, "meter") + unit = BB.Unit.new!(5, "meter") assert Units.extract_float(unit) == 5.0 end test "extracts float value" do - unit = Localize.Unit.new!(2.5, "meter") + unit = BB.Unit.new!(2.5, "meter") assert Units.extract_float(unit) == 2.5 end - - test "extracts decimal value" do - unit = Localize.Unit.new!(Decimal.new("1.5"), "meter") - assert Units.extract_float(unit) == 1.5 - end end end