//
// Copyright (c) 2008, Brian Frank and Andy Frank
// Licensed under the Academic Free License version 3.0
//
// History:
// 19 Dec 08 Brian Frank Creation
// 15 Sep 10 Brian Frank Significant rework of API
//
**
** Unit models a unit of measurement. Units are represented as:
**
** - ids: each unit has one or more unique identifiers for the unit
** within the VM. Units are typically defined in the unit database
** "etc/sys/units.txt" or can be via by the `define` method.
** Every id assigned to a unit must be unique within the VM.
**
** - name: the first identifier in the ids list is called the *name*
** and should be a descriptive summary of the unit using words separated
** by underbar such as "miles_per_hour".
**
** - symbol: the last identifier in the ids list should be the
** abbreviated symbol; for example "kilogram" has the symbol "kg".
** In units with only one id, the symbol is the same as the name.
** Units with exponents should use Unicode superscript chars, not
** ASCII digits.
**
** - dimension: defines the ratio of the seven SI base units: m, kg,
** sec, A, K, mol, and cd
**
** - scale/factor: defines the normalization equations for unit conversion
**
** A unit identifier is limited to the following characters:
** - any Unicode char over 128
** - ASCII letters 'a' - 'z' and 'A' - 'Z'
** - underbar '_'
** - division sign '/'
** - percent sign '%'
** - dollar sign '$'
**
** Units with equal dimensions are considered to measure the same
** physical quantity. This is not always true, but good enough for
** practice. Conversions with the 'convertTo' method are expressed with
** the following equations:
**
** unit = dimension * scale + offset
** toNormal = scalar * scale + offset
** fromNormal = (scalar - offset) / scale
** toUnit = fromUnit.fromNormal( toUnit.toNormal(sclar) )
**
** As a simple, pragmatic solution for modeling Units, there are some
** units which don't fit this model including logarithm and angular units.
** Units which don't cleanly fit this model should be represented as
** dimensionless (all ratios set to zero).
**
** Fantom's model for units of measurement and the unit database are
** derived from the OASIS oBIX specification.
**
@Serializable { simple = true }
const class Unit
{
//////////////////////////////////////////////////////////////////////////
// Unit Database
//////////////////////////////////////////////////////////////////////////
**
** Define a new Unit definition in the VM's unit database
** using the following string format:
**
** unit := <ids> [";" <dim> [";" <scale> [";" <offset>]]]
** names := <ids> ("," <id>)*
** id := <idChar>*
** idChar := 'a'-'z' | 'A'-'Z' | '_' | '%' | '/' | any char > 128
** dim := <ratio> ["*" <ratio>]* // no whitespace allowed
** ratio := <base> <exp>
** base := "kg" | "m" | "sec" | "K" | "A" | "mol" | "cd"
** exp := <int>
** scale := <float>
** offset := <float>
**
** If the format is incorrect or any identifiers are already
** defined then throw an exception.
**
static Unit define(Str s)
**
** Find a unit by one of its identifiers if it has been defined in this
** VM. If the unit isn't defined yet and checked is false then return
** null, otherwise throw Err. Any units declared in "etc/sys/units.txt"
** are implicitly defined.
**
static new fromStr(Str s, Bool checked := true)
**
** List all the units currently defined in the VM. Any units
** declared in "etc/sys/units.txt" are implicitly defined.
**
static Unit[] list()
**
** List the quantity names used to organize the unit database in
** "etc/sys/units.txt". Quantities are merely a convenient mechanism
** to organize the unit database - there is no guarantee that they
** include all current VM definitions.
**
static Str[] quantities()
**
** Get the units organized under a specific quantity name in the
** unit database "etc/sys/units.txt". Quantities are merely a convenient
** mechanism to organize the unit database - there is no guarantee that
** they include all current VM definitions.
**
static Unit[] quantity(Str quantity)
//////////////////////////////////////////////////////////////////////////
// Identity
//////////////////////////////////////////////////////////////////////////
**
** Two units are equal if they have reference equality
** because all units are interned during definition.
**
override Bool equals(Obj? that)
**
** Return 'toStr.hash'.
**
override Int hash()
**
** Return `symbol`.
**
override Str toStr()
**
** Return the list of programmatic identifiers for this unit.
** The first item is always `name` and the last is always `symbol`.
**
Str[] ids()
**
** Return the primary name identifier of this unit.
** This is always the first item in `ids`.
**
Str name()
**
** Return the abbreviated symbol for this unit.
** This is always the last item in `ids`.
**
Str symbol()
**
** Return the scale factor used to convert this unit "from normal".
** For example the scale factor for kilometer is 1000 because it is
** defined as a 1000 meters where meter is the normalized unit for
** length. See class header for normalization and conversion equations.
** The scale factor for the normalized unit is always one.
**
Float scale()
**
** Return the offset factor used to convert this unit "from normal".
** See class header for normalization and conversion equations. Offset
** is used most commonly with temperature units. The offset for
** normalized unit is always zero.
**
Float offset()
**
** Return string format as specified by `define`.
**
Str definition()
//////////////////////////////////////////////////////////////////////////
// Dimension
//////////////////////////////////////////////////////////////////////////
**
** Return the string format of the dimension portion of `definition`
**
Str dim()
**
** Kilogram (mass) component of the unit dimension.
**
Int kg()
**
** Meter (length) component of the unit dimension.
**
Int m()
**
** Second (time) component of the unit dimension.
**
Int sec()
**
** Kelvin (thermodynamic temperature) component of the unit dimension.
**
Int K()
**
** Ampere (electric current) component of the unit dimension.
**
Int A()
**
** Mole (amount of substance) component of the unit dimension.
**
Int mol()
**
** Candela (luminous intensity) component of the unit dimension.
**
Int cd()
//////////////////////////////////////////////////////////////////////////
// Arithmetic
//////////////////////////////////////////////////////////////////////////
**
** Match the product of this and b against current database definitions.
** If an unambiguous match cannot be made then throw Err.
**
@Operator Unit mult(Unit that)
**
** Match quotient of this divided by b against current database definitions.
** If an unambiguous match cannot be made then throw Err.
**
@Operator Unit div(Unit b)
//////////////////////////////////////////////////////////////////////////
// Conversion
//////////////////////////////////////////////////////////////////////////
**
** Convert a scalar value from this unit to the given unit. If
** the units do not have the same dimension then throw Err.
** For example, to convert 3km to meters:
** m := Unit("meter")
** km := Unit("kilometer")
** km.convertTo(3f, m) => 3000f
**
Float convertTo(Float scalar, Unit unit)
}