Current section
Files
Jump to
Current section
Files
lib/unity.ex
defmodule Unity do
@moduledoc """
An Elixir unit conversion calculator inspired by the Unix `units` utility.
Uses `Localize.Unit` as the primary engine for unit creation, conversion,
arithmetic, and localized output. Adds a NimbleParsec-based expression
parser and an interpreter that evaluates unit expressions interactively
or from the command line.
## Quick start
iex> result = Unity.eval!("3 meters to feet")
iex> result.name
"foot"
iex> Unity.eval!("60 mph to km/h") |> Unity.format!()
"96.56064 kilometers per hour"
## Expression syntax
* `3 meters to feet` — unit conversion.
* `60 mph + 10 km/h` — arithmetic on compatible units.
* `100 kg * 9.8 m/s^2` — unit multiplication.
* `sqrt(9 m^2)` — built-in functions.
* `1|3 cup` — rational numbers.
* `let x = 42 km` — variable binding.
"""
@type result :: Localize.Unit.t() | number() | {:decomposed, [Localize.Unit.t()]}
@type env :: Unity.Interpreter.env()
@doc """
Parses and evaluates a unit expression string.
### Arguments
* `input` - the expression string to evaluate.
* `environment` - a map of variable bindings. Defaults to `%{}`.
### Returns
* `{:ok, result, environment}` on success.
* `{:error, message}` on failure.
### Examples
iex> {:ok, result, _env} = Unity.eval("3 meters to feet")
iex> Float.round(result.value, 2)
9.84
"""
@spec eval(String.t(), env()) :: {:ok, result(), env()} | {:error, String.t()}
def eval(input, environment \\ %{}) do
case Unity.Parser.parse(input) do
{:ok, ast} ->
Unity.Interpreter.eval(ast, environment)
{:error, message} ->
{:error, message}
end
end
@doc """
Parses and evaluates a unit expression string, raising on failure.
### Arguments
* `input` - the expression string to evaluate.
* `environment` - a map of variable bindings. Defaults to `%{}`.
### Returns
The evaluation result (`Localize.Unit.t()` or a number).
### Examples
iex> result = Unity.eval!("3 meters to feet")
iex> result.name
"foot"
"""
@spec eval!(String.t(), env()) :: result()
def eval!(input, environment \\ %{}) do
case eval(input, environment) do
{:ok, result, _env} -> result
{:error, message} -> raise ArgumentError, message
end
end
@doc """
Formats a result value for display.
Delegates to `Unity.Formatter.format/2`.
### Arguments
* `result` - a `Localize.Unit.t()` or a number.
* `options` - formatting options. See `Unity.Formatter.format/2`.
### Returns
* `{:ok, formatted_string}` on success.
* `{:error, reason}` on failure.
### Examples
iex> {:ok, unit} = Localize.Unit.new(9.84252, "foot")
iex> Unity.format(unit)
{:ok, "9.84252 feet"}
"""
@spec format(result(), keyword()) :: {:ok, String.t()} | {:error, String.t()}
defdelegate format(result, options \\ []), to: Unity.Formatter
@doc """
Formats a result value for display, raising on failure.
### Arguments
* `result` - a `Localize.Unit.t()` or a number.
* `options` - formatting options. See `Unity.Formatter.format/2`.
### Returns
The formatted string.
### Examples
iex> {:ok, unit} = Localize.Unit.new(9.84252, "foot")
iex> Unity.format!(unit)
"9.84252 feet"
"""
@spec format!(result(), keyword()) :: String.t()
defdelegate format!(result, options \\ []), to: Unity.Formatter
end