package Physics::Unit;

use strict;
use warnings;
use Carp;
use base qw(Exporter);
use vars qw(
    $VERSION
    @EXPORT_OK
    %EXPORT_TAGS
    $debug
    $number_re
);

my @export_ok_list = qw(
    $number_re
    GetTypeUnit
    GetUnit
    InitBaseUnit
    InitPrefix
    InitTypes
    InitUnit
    ListTypes
    ListUnits
    NumBases
);

%EXPORT_TAGS = ('ALL' => \@export_ok_list);
@EXPORT_OK   = @export_ok_list;
$VERSION = '0.01';

$number_re = '([-+]?((\d+\.?\d*)|(\.\d+))(E[-+]?((\d+\.?\d*)|(\.\d+)))?)';

# The value of this hash is a string representing the token returned 
# when this word is seen
my %reserved_word = (
    per     => 'divide',
    square  => 'square',
    sq      => 'square',
    cubic   => 'cubic',
    squared => 'squared',
    cubed   => 'cubed',
);

# Pre-defined units
my %unit_by_name;

# Values are references to units representing the prefix
my %prefix;

# Known quantity types.  The values of this hash are references to
# unit objects that exemplify these types
my %prototype;

# The number of base units
my $NumBases = 0;

# How many base units there are
my @BaseName;

InitBaseUnit (
    'Distance'    => ['meter', 'm', 'meters'],
    'Mass'        => ['gram', 'gm', 'grams'],
    'Time'        => ['second', 's', 'sec', 'secs', 'seconds'],
    'Temperature' => ['kelvin', 'k', 'kelvins',
                      'degree-kelvin', 'degrees-kelvin', 'degree-kelvins'],
    'Current'     => ['ampere', 'amp', 'amps', 'amperes'],
    'Substance'   => ['mole', 'mol', 'moles'],
    'Luminosity'  => ['candela', 'cd', 'candelas', 'candle', 'candles'],
    'Money'       => ['us-dollar', 'dollar', 'dollars', 'us-dollars', '$'],
    'Data'        => ['bit', 'bits'],
);

InitPrefix (
    'deka',    1e1,
    'deca',    1e1,
    'hecto',   1e2,
    'kilo',    1e3,
    'mega',    1e6,
    'giga',    1e9,
    'tera',    1e12,
    'peta',    1e15,
    'exa',     1e18,
    'zetta',   1e21,
    'yotta',   1e24,
    'deci',    1e-1,
    'centi',   1e-2,
    'milli',   1e-3,
    'micro',   1e-6,
    'nano',    1e-9,
    'pico',    1e-12,
    'femto',   1e-15,
    'atto',    1e-18,
    'zepto',   1e-21,
    'yocto',   1e-24,
);

# InitUnit {{{
InitUnit (
    # Dimensionless
    ['pi',],    '3.1415926535897932385',
    ['e',],     '2.7182818284590452354',

    ['unity', 'one', 'ones',],           '1',
    ['two', 'twos',],                    '2',
    ['three', 'threes',],                '3',
    ['four', 'fours',],                  '4',
    ['five', 'fives',],                  '5',
    ['six', 'sixes',],                   '6',
    ['seven', 'sevens',],                '7',
    ['eight', 'eights',],                '8',
    ['nine', 'nines'],                   '9',
    ['ten', 'tens',],                   '10',
    ['eleven', 'elevens',],             '11',
    ['twelve', 'twelves',],             '12',
    ['thirteen', 'thirteens',],         '13',
    ['fourteen', 'fourteens',],         '14',
    ['fifteen', 'fifteens',],           '15',
    ['sixteen', 'sixteens',],           '16',
    ['seventeen', 'seventeens',],       '17',
    ['eighteen', 'eighteens',],         '18',
    ['nineteen', 'nineteens',],         '19',
    ['twenty', 'twenties',],            '20',
    ['thirty', 'thirties',],            '30',
    ['forty', 'forties',],              '40',
    ['fifty', 'fifties',],              '50',
    ['sixty', 'sixties',],              '60',
    ['seventy', 'seventies',],          '70',
    ['eighty', 'eighties',],            '80',
    ['ninety', 'nineties',],            '90',
    ['hundred', 'hundreds'],           '100',
    ['thousand', 'thousands'],        '1000',
    ['million', 'millions',],          '1e6',
    ['billion', 'billions',],          '1e9',
    ['trillion', 'trillions',],       '1e12',
    ['quadrillion', 'quadrillions',], '1e15',
    ['quintillion', 'quintillions',], '1e18',

    ['half', 'halves',],      '0.5',
    ['third', 'thirds',],     '1/3',
    ['fourth', 'fourths',],  '0.25',
    ['tenth',],               '0.1',
    ['hundredth',],          '0.01',
    ['thousandth',],        '0.001',
    ['millionth',],          '1e-6',
    ['billionth',],          '1e-9',
    ['trillionth',],        '1e-12',
    ['quadrillionth',],     '1e-15',
    ['quintillionth',],     '1e-18',

    ['percent', '%',],      '0.01',
    ['dozen', 'doz',],        '12',
    ['gross',],              '144',
    # Angular
    ['radian', 'radians',],                 '1',
    ['steradian', 'sr', 'steradians',],     '1',
    ['degree', 'deg', 'degrees',],          'pi radians / 180',
    ['arcminute', 'arcmin', 'arcminutes',], 'deg / 60',
    ['arcsecond', 'arcsec', 'arcseconds',], 'arcmin / 60',

    # Distance
    ['foot', 'ft', 'feet',],                    '.3048 m',          # exact
    ['inch', 'in', 'inches',],                  'ft/12',            # exact
    ['mil', 'mils',],                           'in/1000',          # exact
    ['yard', 'yards',],                         '3 ft',             # exact
    ['fathom', 'fathoms',],                     '2 yards',          # exact
    ['rod', 'rods',],                           '5.5 yards',        # exact
    ['pole', 'poles',],                         '1 rod',            # exact
    ['perch', 'perches',],                      '1 rod',            # exact
    ['furlong', 'furlongs',],                   '40 rods',          # exact
    ['mile', 'mi', 'miles',],                   '5280 ft',          # exact

    ['micron', 'microns', 'um',],               '1e-6 m',           # exact
    ['angstrom', 'a', 'angstroms',],            '1e-10 m',          # exact
    ['cm',],                                    'centimeter',       # exact
    ['km',],                                    'kilometer',        # exact

    ['nautical-mile', 'nm', 'nauticalmiles',
     'nauticalmile', 'nautical-miles',],        '1852 m',           # exact
    ['astronomical-unit', 'au',],               '1.49598e11 m',
    ['light-year', 'ly', 'light-years',
     'lightyear', 'lightyears'],                '9.46e15 m',
    ['parsec', 'parsecs',],                     '3.083e16 m',

    # equatorial radius of the reference geoid:
    ['re'],                          '6378388 m',    # exact
    # polar radius of the reference geoid:
    ['rp'],                          '6356912 m',    # exact

    # Acceleration
    ['g0', 'earth-gravity'],                    '9.80665 m/s^2',    # exact

    # Mass
    ['kg',],                                    'kilogram',         # exact
    ['metric-ton', 'metric-tons', 'tonne',
     'tonnes'],                                 '1000 kg',          # exact

    ['grain', 'grains'],                        '.0648 gm',

    ['pound-mass', 'lbm', 'lbms',
     'pounds-mass'],                            '0.45359237 kg',    # exact
    ['ounce', 'oz', 'ounces'],                  'lbm/16',           # exact
    ['hundredweight', 'hundredweights'],        '100 lbms',         # exact
    ['ton', 'tons', 'short-ton', 'short-tons'], '2000 lbms',        # exact
    ['long-ton', 'long-tons'],                  '2240 lbms',        # exact

    ['slug', 'slugs'],                          'lbm g0 s^2/ft',    # exact
    ['mg',],                                    'milligram',        # exact
    ['ug',],                                    'microgram',        # exact

    ['dram', 'drams'],                          'ounce / 16',       # exact
    ['troy-pound', 'troy-pounds'],              '0.373 kg',
    ['troy-ounce', 'troy-ounces',
     'ounce-troy', 'ounces-troy'],              '31.103 gm',
    ['pennyweight', 'pennyweights'],            '1.555 gm',
    ['scruple', 'scruples'],                    '1.296 gm',

    ['hg',],                                    'hectogram',        # exact
    ['dag',],                                   'decagram',         # exact
    ['dg',],                                    'decigram',         # exact
    ['cg',],                                    'centigram',        # exact

    ['atomic-mass-unit', 'amu', 'u',
     'atomic-mass-units'],                      '1.6605402e-27 kg',

    # Time
    ['minute', 'min', 'mins', 'minutes'],               '60 s',
    ['hour', 'hr', 'hrs', 'hours'],                     '60 min',
    ['day', 'days'],    '24 hr',
    ['week', 'wk', 'weeks'],                            '7 days',
    ['fortnight', 'fortnights'],                        '2 week',
    ['year', 'yr', 'yrs', 'years'],                     '365.25 days',
    ['month', 'mon', 'mons', 'months'],                 'year / 12',    # an average month
    ['score', 'scores'],                                '20 yr',
    ['century', 'centuries'],                           '100 yr',
    ['millenium', 'millenia',],                         '1000 yr',

    ['ms', 'msec', 'msecs'], 'millisecond',
    ['us', 'usec', 'usecs'], 'microsecond',
    ['ns', 'nsec', 'nsecs'], 'nanosecond',
    ['ps', 'psec', 'psecs'], 'picosecond',

    # Data
    ['byte', 'bytes'], '8 bits',

    # Frequency
    ['hertz', 'hz'],     '1/sec',
    ['becquerel', 'bq'], '1 hz',

    # Current
    ['abampere', 'abamp', 'abamps', 'abamperes'],         '10 amps',
    ['statampere', 'statamp', 'statamps', 'statamperes'], '3.336e-10 amps',

    ['ma',], 'milliamp',
    ['ua',], 'microamp',

    # Electric_potential
    ['volt', 'v', 'volts'],    'kg m^2 / amp s^3',
    ['mv',],                   'millivolt',
    ['uv',],                   'microvolt',
    ['abvolt', 'abvolts'],     '1e-8 volt',
    ['statvolt', 'statvolts'], '299.8 volt',

    # Resistance
    ['ohm', 'ohms'],         'kg m^2 / amp^2 s^3',
    ['abohm', 'abohms'],     'nano ohm',
    ['statohm', 'statohms'], '8.987e11 ohm',
    ['kilohm', 'kilohms',],  'kilo ohm',
    ['megohm', 'megohms'],   'mega ohm',

    # Conductance
    ['siemens',],    'amp^2 s^3 / kg m^2',
    ['mho', 'mhos'], '1/ohm',

    # Capacitance
    ['farad', 'f', 'farads'],    'amp^2 s^4 / kg m^2',
    ['abfarad', 'abfarads'],     'giga farad',
    ['statfarad', 'statfarads'], '1.113e-12 farad',

    ['uf',], 'microfarad',
    ['pf',], 'picofarads',

    # Inductance
    ['henry', 'henrys'],         'kg m^2 / amp^2 s^2',
    ['abhenry', 'abhenrys'],     'nano henry',
    ['stathenry', 'stathenrys'], '8.987e11 henry',

    ['uh',], 'microhenry',
    ['mh',], 'millihenry',

    # Magnetic_flux
    ['weber', 'wb', 'webers'],      'kg m^2 / amp s^2',
    ['maxwell', 'maxwells'],    '1e-8 weber',

    # Magnetic_field
    ['tesla', 'teslas'],      'kg / amp sec^2',
    ['gauss',],       '1e-4 tesla',

    # Temperature
    ['degree-rankine',],                        '5/9 * kelvin',     # exact

    # Force
    ['pound', 'lb', 'lbs', 'pounds',
     'pound-force', 'lbf',
     'pounds-force', 'pound-weight'],           'slug foot / s^2',  # exact
    ['ounce-force', 'ozf'],                     'pound-force / 16', # exact
    ['newton', 'nt', 'newtons'],                'kg m / s^2',       # exact
    ['dyne', 'dynes'],                          'gm cm / s^2',      # exact
    ['gram-weight', 'gram-force'],              'gm g0',            # exact
    ['kgf',],                                   'kilo gram-force',  # exact

    # Area
    ['are', 'ares'],          '100 square meters',
    ['hectare', 'hectares',], '100 ares',
    ['acre', 'acres'],        '43560 square feet',
    ['barn', 'barns'],        '1e-28 square meters',

    # Volume
    ['liter', 'l', 'liters'],                   'm^3/1000',         # exact
    ['cl',],                                    'centiliter',       # exact
    ['dl',],                                    'deciliter',        # exact
    ['cc', 'ml',],                              'cubic centimeter', # exact

    ['gallon', 'gal', 'gallons'],               '3.785411784 liter',
    ['quart', 'qt', 'quarts'],                  'gallon/4',
    ['peck', 'pecks'],                          '8 quarts',
    ['bushel', 'bushels'],                      '4 pecks',
    ['fifth', 'fifths'],                        'gallon/5',
    ['pint', 'pt', 'pints'],                    'quart/2',
    ['cup', 'cups'],                            'pint/2',
    ['fluid-ounce', 'floz', 'fluidounce',
     'fluidounces', 'fluid-ounces'],            'cup/8',
    ['gill', 'gills'],                          '4 fluid-ounces',
    ['fluidram', 'fluidrams'],                  '3.5516 cc',
    ['minim', 'minims'],                        '0.059194 cc',
    ['tablespoon', 'tbsp', 'tablespoons'],      'fluid-ounce / 2',
    ['teaspoon', 'tsp', 'teaspoons'],           'tablespoon / 3',

    # Power
    ['watt', 'w', 'watts'], 'kg m^2 / s^3',
    ['horsepower', 'hp'],   '550 foot pound-force / s',

    # Energy
    ['joule', 'j', 'joules'],                   'kg m^2 / s^2',    # exact
    ['electron-volt', 'ev', 'electronvolt',
     'electronvolts', 'electron-volts'],        '1.60217733e-19 joule',

    ['mev',], 'mega electron-volt',
    ['gev',], 'giga electron-volt',
    ['tev',], 'tera electron-volt',

    ['calorie', 'cal', 'calories'],                '4.184 joules',  # exact
    ['kcal',],                                     'kilocalorie',   # exact
    ['british-thermal-unit', 'btu', 'btus',
     'britishthermalunit', 'britishthermalunits',
     'british-thermal-units'],                     '1055.056 joule',
    ['erg', 'ergs'],                               '1.0e-7 joule',  # exact
    ['kwh',],                                      'kilowatt hour', # exact

    # Torque
    ['foot-pound', 'ftlb', 'ft-lb',
     'footpound', 'footpounds', 'foot-pounds'], 'foot pound-force',

    # Charge
    ['coulomb', 'coul', 'coulombs'],             'ampere second',
    ['abcoulomb', 'abcoul', 'abcoulombs'],       '10 coulomb',
    ['statcoulomb', 'statcoul', 'statcoulombs'], '3.336e-10 coulomb',
    ['elementary-charge', 'eq'],     '1.6021892e-19 coulomb',

    # Pressure
    ['pascal', 'pa'],      'newton / m^2',
    ['bar', 'bars'],       '1e5 pascal',
    ['torr',],             '(101325 / 760) pascal',
    ['psi',],              'pounds per inch^2',
    ['atmosphere', 'atm'], '101325 pascal',             # exact

    # Speed
    ['mph',],          'mi/hr',
    ['kph',],          'km/hr',
    ['kps',],          'km/s',
    ['fps',],          'ft/s',
    ['knot', 'knots'], 'nm/hr',
    ['mps',],          'meter/s',
    ['speed-of-light', 'c'],         '2.99792458e8 m/sec',

    # Dose of radiation
    ['gray', 'gy'],    'joule / kg',
    ['sievert', 'sv'], 'joule / kg',
    ['rad',],          'gray / 100',
    ['rem',],          'sievert / 100',

    # Other
    ['gravitational-constant', 'g'], '6.6720e-11  m^3 / kg s^2',
    # Planck constant:
    ['h'],                            '6.626196e-34 J/s',
    # Avogadro constant:
    ['na'],                              '6.022169/mol',
);
# }}}

InitTypes (
    'Dimensionless'      => 'unity',
    'Frequency'          => 'hertz',
    'Electric_potential' => 'volt',
    'Resistance'         => 'ohm',
    'Conductance'        => 'siemens',
    'Capacitance'        => 'farad',
    'Inductance'         => 'henry',
    'Magnetic_flux'      => 'weber',
    'Magnetic_field'     => 'tesla',
    'Force'              => 'newton',
    'Area'               => 'are',
    'Volume'             => 'liter',
    'Power'              => 'watt',
    'Energy'             => 'joule',
    'Torque'             => 'kg m^2/s^2',
    'Charge'             => 'coulomb',
    'Pressure'           => 'pascal',
    'Speed'              => 'mps',
    'Dose'               => 'gray',       #  of radiation
    'Acceleration'       => 'm/s^2',
);
# }}}

# GetUnit {{{
GetUnit('joule')->type('Energy');
GetUnit('ev')->type('Energy');
GetUnit('mev')->type('Energy');
GetUnit('gev')->type('Energy');
GetUnit('tev')->type('Energy');
GetUnit('cal')->type('Energy');
GetUnit('kcal')->type('Energy');
GetUnit('btu')->type('Energy');
GetUnit('erg')->type('Energy');
GetUnit('kwh')->type('Energy');
GetUnit('ftlb')->type('Torque');
# }}}

#InitSystem (
#    'si', [ qw(m kg s newton square-meter cubic-meter watt joule
#               pascal m/s coulomb volt farad ohm siemens tesla weber henry)
#          ],
#    'cgs', [ qw(cm gm s dyne square-centimeter cubic-centimeter watt
#                erg dynes-per-square-centimeter centimeters-per-second)
#           ],
#    'english', [ qw(foot slug s pound square-foot cubic-foot horsepower
#                    foot-pound pounds-per-square-inch feet-per-second)
#               ],
#);

sub InitBaseUnit {
    while (@_) {
        my ($t, $names) = (shift, shift);
        croak 'Invalid arguments to InitBaseUnit'
            if ref $t || (ref $names ne "ARRAY");

        print "Initializing Base Unit $$names[0]\n"
            if $debug;

        my $unit = NewOne();
        $unit->AddNames(@$names);
        $unit->{def} = $unit->name();  # def same as name

        # The dimension vector for this Unit has zeros in every place
        # except the last
        $unit->{dim}->[$NumBases] = 1;
        $BaseName[$NumBases] = $unit->abbr();
        $NumBases++;

        $unit->NewType($t);
    }
}

sub InitPrefix {
    while (@_) {
        my ($name, $factor) = (shift, shift);
        croak 'Invalid arguments to InitPrefix'
            if !$name || !$factor || ref $name || ref $factor;

        print "Initializing Prefix $name\n"
            if $debug;

        my $u = NewOne();
        $u->AddNames($name);
        $u->{factor} = $factor;
        $u->{type} = 'prefix';
        $prefix{$name} = $u;

        $u->{def} = $factor;
    }
}

sub InitUnit {
    while (@_) {
        my ($names, $def) = (shift, shift);

        if (ref $names ne "ARRAY" || !$def) {
            print "InitUnit, second argument is '$def'\n";
            croak 'Invalid arguments to InitUnit';
        }

        print "Initializing Unit $$names[0]\n" if $debug;

        my $u = CreateUnit($def);

        $u->AddNames(@$names);
    }
}

sub InitTypes {
    while (@_) {
        my ($t, $u) = (shift, shift);
        croak 'Invalid arguments to InitTypes'
            if !$t || ref $t || !$u;

        my $unit = GetUnit($u);
        $unit->NewType($t);
    }
}

sub GetUnit {
    my $u = shift;

    croak 'GetUnit: expected an argument'
        unless $u;

    return $u if ref $u;

    if ($unit_by_name{$u}) {
        #print "GetUnit, $u yields ", $unit_by_name{$u}->name, "\n";
        return $unit_by_name{$u};
    }

    # Try it as an expression
    return CreateUnit($u);
}

sub ListUnits {
    return sort keys %unit_by_name;
}

sub ListTypes {
    return sort keys %prototype;
}

sub NumBases {
    return $NumBases;
}

sub GetTypeUnit {
    my $t = shift;
    return $prototype{$t};
}

sub new {
    my $proto = shift;
    my $class;

    my $self;
    if (ref $proto) {        # object method
        $self = $proto->copy;
    }
    else {                    # class method
        my $r = shift;
        $self = CreateUnit($r);
    }

    $self->AddNames(@_);

    return $self;
}

sub type {
    my $self = shift;

    # See if the user is setting the type
    my $t;
    if ($t = shift) {
        # XXX Maybe we should check that $t is a valid type name, and 
        # XXX that it's type really does match.
        return $self->{type} = $t;
    }

    # If the type is known already, return it
    return $self->{type} if $self->{type};

    # See if it is a prefix
    my $name = $self->name();

    return $self->{type} = 'prefix'
        if defined $name && defined $prefix{$name};

    # Collect all matching types
    my @t;
    for (keys %prototype) {
        push @t, $_
            unless CompareDim($self, $prototype{$_});
    }

    # Return value depends on whether we got zero, one, or multiple types
    return undef 
        unless @t;

    return $self->{type} = $t[0]
        if @t == 1;

    return \@t;
}

sub name {
    my $self = shift;
    my $n = $self->{names};
    return $$n[0];
}

sub abbr {
    my $self = shift;
    my $n = ${$self->{names}}[0];
    return undef 
        unless defined $n;

    for ($self->names()) {
        $n = $_ if length $_ < length $n;
    }

    return $n;
}

sub names {
    my $self = shift;
    return @{$self->{names}};
}

sub def {
    my $self = shift;
    return $self->{def};
}

sub expanded {
    my $self = shift;

    my $s = $self->{factor};
    $s = '' if $s == 1;

    my $i = 0;
    for my $d (@{$self->{dim}}) {
        if ($d) {
            #print "Dimension index $i is $d\n";
            if ($s) { $s .= " "; }
            $s .= $BaseName[$i];
            $s .= "^$d" unless $d == 1;
        }
        $i++;
    }

    $s = 1 if $s eq '';

    return $s;
}

sub ToString {
    my $self = shift;
    return $self->name || $self->def || $self->expanded;
}

sub factor {
    my $self = shift;
    if (@_) {
        $self->CheckChange;
        $self->{factor} = shift;
    }
    return $self->{factor};
}

sub convert {
    my ($self, $other) = @_;
    my $u = GetUnit($other);
    carp "Can't convert ". $self->name() .' to '. $u->name()
        if CompareDim($self, $u);
    return $self->{factor} / $u->{factor};
}

sub times {
    my $self = shift;
    $self->CheckChange;
    my $u = GetUnit(shift);
    $self->{factor} *= $u->{factor};

    for (0 .. $NumBases) {
        my $u_val = defined $u->{dim}[$_] ? $u->{dim}[$_] : 0;
        if (defined $self->{dim}[$_]) {
            $self->{dim}[$_] += $u_val;
        }
        else {
            $self->{dim}[$_] = $u_val;
        }
    }

    $self->{type} = '';

    return $self;
}

sub recip {
    my $self = shift;
    $self->CheckChange;
    $self->{factor} = 1 / $self->{factor};

    for (0 .. $NumBases) {
        if (defined $self->{dim}[$_]) {
            $self->{dim}->[$_] = -$self->{dim}->[$_];
        }
        else {
            $self->{dim}[$_] = 0;
        }
    }

    return $self;
}

sub divide {
    my ($self, $other) = @_;
    my $u = GetUnit($other)->copy;
    $self->times($u->recip);
}

sub power {
    my $self = shift;
    $self->CheckChange;
    my $p = shift;
    die 'Exponentiation to integer values only, please'
        unless $p == int $p;
    $self->{factor} **= $p;

    for (0 .. $NumBases) {
        $self->{dim}[$_] = 0 unless defined $self->{dim}[$_];
        $self->{dim}[$_] *= $p;
    }

    $self->{type} = '';

    return $self;
}

sub add {
    my $self = shift;

    $self->CheckChange;

    my $other = shift;
    my $u = GetUnit($other);

    croak "Can't add ". $u->type .' to a '. $self->type
        if CompareDim($self, $u);

    $self->{factor} += $u->{factor};

    return $self;
}

sub neg {
    my $self = shift;
    $self->CheckChange;
    $self->{factor} = -$self->{factor};
    return $self;
}

sub subtract {
    my ($self, $other) = @_;
    my $u = GetUnit($other)->copy;
    $self->add( $u->neg );
}

sub copy {
    my $self = shift;

    my $n = {
        'factor' => $self->{factor},
        'dim'    => [@{$self->{dim}}],
        'type'   => $self->{type},
        'names'  => [],
        'def'    => $self->{def},
    };

    bless $n, 'Physics::Unit';

    return $n;
}

sub equal {
    my $obj1 = shift;

    # If it was called as a class method, throw away the first
    # argument (the class name)
    $obj1 = shift unless ref $obj1;

    $obj1 = GetUnit($obj1);

    my $obj2 = GetUnit(shift);

    return 0 if CompareDim($obj1, $obj2);
    return 0 unless $obj1->{factor} == $obj2->{factor};
    return 1;
}

sub NewOne {
    my $u = {
        'factor' => 1,
        'dim'    => [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
        'type'   => undef,
        'names'  => [],
        'def'    => undef,
    };
    bless $u, 'Physics::Unit';
}

sub AddNames {
    my $self = shift;

    my $n;
    while ($n = shift) {
        croak "Can't use a reference as a name!"
            if ref $n;
        carp "Name $n is already defined"
            if LookName($n);

        push @{$self->{names}}, "\L$n";

        $unit_by_name{$n} = $self;
    }
}

sub NewType {
    my ($self, $t) = @_;
#    my $oldtype = $self->type;
#    croak "NewType: the type $t is already defined as $oldtype"
#        if $oldtype ne 'unknown';
    $self->{type} = $t;
    $prototype{$t} = $self;
}

sub CreateUnit {
    my $def = shift;

    # argument was a Unit object
    return $def->new() if ref $def;

    # argument was either a simple name or an expression - doesn't matter
    $def = lc $def;

    my $u = expr($def);

    $u->{def} = $def;

    return $u;
}

sub CompareDim {
    my ($u1, $u2) = @_;

    my $d1 = $u1->{dim};
    my $d2 = $u2->{dim};

    for (0 .. $NumBases) {
        $$d1[$_] = 0 unless defined $$d1[$_];
        $$d2[$_] = 0 unless defined $$d2[$_];

        my $c = ($$d1[$_] <=> $$d2[$_]);
        return $c if $c;
    }

    return 0;
}

sub LookName {
    my $name = shift;
    return 3 if defined $prototype{$name};
    return 2 if defined $unit_by_name{$name};
    return 1 if defined $reserved_word{$name};
    return 0;
}

sub DebugString {
    my $self = shift;
    my $s = $self->{factor};
    $s .= '['. join (', ', @{$self->{dim}}) .']';
    return $s;
}

sub CheckChange {
    my $self = shift;
    carp "You're not spozed to change named units!"
        if $self->{names}[0];
    $self->{names} = [];
    $self->{type} = $self->{def} = undef;
}

# global variables used for parsing.
my $def;      # string being parsed
my $tok;      # the token type
my $numval;   # the value when the token is a number
my $tokname;  # when it is a name
my $indent;   # ?

# parser
sub expr {
    if (@_) {
        $def = shift;
        get_token();
    }

    print ' ' x $indent, "inside expr\n" if $debug;

    $indent++;

    my $u = term();

    for (;;) {
        print ' ' x $indent, "inside expr loop\n" if $debug;

        if ($tok eq 'times') {
            get_token();

            $u->times(term());
        }
        elsif ($tok eq 'divide') {
            get_token();

            $u->divide(term());
        }
        else {
            print ' ' x $indent, "expr: returning ", $u->DebugString, "\n"
                if $debug;

            $indent--;

            return $u;
        }
    }
}

sub term {
    print ' ' x $indent, "inside term\n" if $debug;

    $indent++;

    my $u = Factor();

    for (;;) {
        print ' ' x $indent, "inside term loop\n" if $debug;

        if ($tok eq 'number' ||
            $tok eq 'name'   ||
            $tok eq 'prefix' ||
            $tok eq 'square' ||
            $tok eq 'cubic')
        {
            $u->times(Factor());
        }
        else {
            print ' ' x $indent, "term: returning ",
                $u->DebugString, "\n" if $debug;

            $indent--;

            return $u;
        }
    }
}

sub Factor {
    print ' ' x $indent, "inside factor\n" if $debug;

    $indent++;

    my $u = prim();

    for (;;) {
        print ' ' x $indent, "inside factor loop\n" if $debug;

        if ($tok eq 'exponent') {
            get_token();

            die 'Exponent must be an integer'
                unless $tok eq 'number';

            $u->power($numval);

            get_token();
        }
        else {
            print ' ' x $indent, "factor: returning ",

            $u->DebugString, "\n" if $debug;

            $indent--;

            return $u;
        }
    }
}

sub prim {
    print ' ' x $indent, "inside prim\n" if $debug;

    $indent++;

    my $u;

    if ($tok eq 'number') {
        print ' ' x $indent, "got number $numval\n" if $debug;

        # Create a new Unit object to represent this number
        $u = NewOne();

        $u->{factor} = $numval;

        get_token();
    }
    elsif ($tok eq 'prefix') {
        print ' ' x $indent, "got a prefix: ", "$tokname\n" if $debug;

        $u = GetUnit($tokname)->copy();

        get_token();

        $u->times(prim());
    }
    elsif ($tok eq 'name') {
        print ' ' x $indent, "got a name: ", "$tokname\n" if $debug;

        $u = GetUnit($tokname)->copy();

        get_token();
    }
    elsif ($tok eq 'lparen') {
        print ' ' x $indent, "got a left paren\n" if $debug;

        get_token();

        $u = expr();

        die 'Missing right parenthesis'
            unless $tok eq 'rparen';

        get_token();
    }
    elsif ($tok eq 'end') {
        print ' ' x $indent, "got end\n" if $debug;

        $u = NewOne();
    }
    elsif ($tok eq 'square') {
        get_token();

        $u = prim()->power(2);
    }
    elsif ($tok eq 'cubic') {
        get_token();

        $u = prim()->power(3);
    }
    else {
        die 'Primary expected';
    }

    print ' ' x $indent, "prim: returning ", $u->DebugString, "\n"
        if $debug;

    $indent--;

    # Before returning, see if the *next* token is 'squared' or 'cubed'
    for(;;) {
        if ($tok eq 'squared') {
            get_token();
            $u->power(2);
        }
        elsif ($tok eq 'cubed') {
            get_token();
            $u->power(3);
        }
        else {
            last;
        }
    }

    return $u;
}

sub get_token {
    print ' ' x $indent, "get_token, looking at '$def'\n" if $debug;

    # First remove whitespace at the begining
    $def =~ s/^\s+//;

    if ($def eq '') {
        $tok = 'end';
        return;
    }

    if ($def =~ s/^\(//) {
        $tok = 'lparen';
    }
    elsif ($def =~ s/^\)//) {
        $tok = 'rparen';
    }
    elsif ($def =~ s/^\*\*// || $def =~ s/^\^//) {
        $tok = 'exponent';
    }
    elsif ($def =~ s/^\*//) {
        $tok = 'times';
    }
    elsif ($def =~ s/^\///) {
        $tok = 'divide';
    }
    elsif ($def =~ s/^$number_re//io) {
        $numval = $1 + 0;  # convert to a number
        $tok = 'number';
    }
    elsif ($def =~ /^([^\ \n\r\t\f\(\)\/\^\*]+)/) {
        my $t = $1;

        my $l = LookName($t);

        if ($l == 1) {
            $tok = $reserved_word{$t};

            $tokname = $t;

            $def = substr $def, length($t);

            return;
        }
        elsif ($l == 2) {
            $tok = 'name';

            $tokname = $t;

            $def = substr $def, length($t);

            return;
        }

        # Couldn't find the name on the first try, look for prefix
        for my $p (keys %prefix) {
            if ($t =~ /^$p/i) {
                $tok = 'prefix';

                $tokname = $p;

                $def = substr $def, length($p);

                return;
            }
        }

        die "Unknown unit: $t\n";
    }
    else {
        die "Illegal token in $def";
    }
}

1;
__END__

=head1 NAME

Physics::Unit - Manipulate physics units and dimensions.

=head1 SYNOPSIS

  use Physics::Unit ':ALL';   # exports all util. function names

  # Define your own units
  $ss = new Physics::Unit('furlong / fortnight', 'ff');

  # Print the expanded representation of a unit
  print $ss->expanded, "\n";

  # Convert from one to another
  print 'One ', $ss->name, ' is ', $ss->convert('mph'), " miles per hour\n";

  # Get a Unit's conversion factor
  print 'Conversion factor of foot is ', GetUnit('foot')->factor, "\n";

=head1 DESCRIPTION

This module allows for the representation of physical quantities with 
encapsulated units.

A Unit is defined by three things: a list of names, a conversion 
factor, and a dimensionality vector. From the dimensionality, a type 
can be derived (usually). 

There are two main types of Unit objects: named and anonymous. Named 
Unit objects are constant. Anonymous Unit objects, however, can 
dynamically change.

Objects of class Unit define units of measurement that correspond to 
physical quantities. This module allows you to manipulate these 
units, generate new derived units from other units, and convert from 
one unit to another. Each unit is defined by a conversion factor and 
a dimensionality vector. 

The conversion factor is a floating point number that specifies how 
this unit relates to a reference unit of the same dimensionality.

The dimensionality vector holds a list of integers - each of which 
records the power of a base unit in this unit. 

For example, consider the unit of speed, "miles per hour". This has a 
dimensionality of (Distance / Time), or of (Distance ^ 1 * Time ^ -1). 
So this unit's dimensionality vector has a 1 in the place 
corresponding to Distance, and a -1 in the place corresponding to 
Time. 

The reference unit for speed is "meters per second" (since meter is 
the base unit corresponding to Distance, and second is the base unit 
corresponding to Time). Therefore, the conversion factor for the unit 
"miles per hour" is 0.44704, since 1 mph equals 0.44704 meters / sec. 

Try this: 

  print "One mph is ", GetUnit('mph')->factor, " meters / sec\n";

Units that have the same dimensionality can be compared, and 
converted from one to the other. 

Units are created through the use of unit expressions, which allow 
you to combine previously defined named units in new and interesting 
ways. In the synopsis above, "furlong / fortnight" is a unit 
expression.

=head1 Types of Units

A Unit can have one or more names associated with it, or it can be 
unnamed (anonymous). 

Named units are constant. This ensures that expressions used to 
derive other units will remain consistent. For example, consider the 
expression "miles per hour", which uses the unit name "hour" to 
create a new, derived unit. It is possible that the same expression 
is used multiple times during the life of a program to create new 
Unit objects. If the unit refered to by the name "hour" was allowed 
to change, then a new, derived unit could possibly be different from 
another unit derived with the same expression. 

Anonymous units, however, can be changed. 

Among named Units, there are three types: prefixes, base units, and 
derived units. 

A prefix Unit is a special case Unit object that: 

  * is dimensionless 
  * has only one name 

A prefix name can be used in unit expressions in a special manner. 
They can be used as prefixes to other unit names, with no intervening 
whitespace. For example, "kilo" is a commonly used prefix. It can 
appear as in the following unit expressions: 

  kilogram
  kilomegameter

Prefixes are described more fully in the unit expressions section 
below. 

A base unit is one that defines a new base dimension. For example, 
the unit meter is a base unit; it defines the dimension for Distance. 

A derived unit is one that is built up from other named units from a 
unit expression. 

The terms base dimension and derived dimension (or derived type) are 
sometimes used. Distance is an example of a base dimension. It is not 
derived from any other set of dimensional quantities. Speed, however, 
is a is a derived dimension (or derived type), corresponding to 
Distance / Time.

=head1 Unit Names

Unit names are not allowed to contain whitespace, or any of the 
characters ^, *, /, (, ). Case is not significant. Also, they may not 
begin with any sequence of characters that could be interpreted as a 
decimal number. Furthermore, these reserved words are not allowed as 
unit names: per, square, sq, cubic, squared, or cubed. Other than 
that, pretty much anything goes. 

So, for example, each of these is a valid unit name: 

  blather
  blather-hour
  ..splather!min_glub

But these are not: 

  ^glub_glub   # contains invalid character ^
  .1foo        # '.1' looks a lot like a number
  123abc       # so does '123'

=head1 Unit Expressions

Unit Expressions allow you to create new unit objects from the set of 
existing named units. Some examples of unit expressions are: 

  megaparsec / femtosecond
  kg / feet^2 sec
  square millimeter
  kilogram meters per second squared

The explicit grammar for unit expressions is defined in the 
implementation page. 

The operators allowed in unit expressions are, in order from high to 
low precedence: 

prefix

Any prefix that is attached to a unit name is applied to that unit 
immediately (highest precedence). Note that if there is whitespace 
between the prefix and the unit name, this would be the space 
operator, which is not the same (see below). 

  square, sq, or cubic

square or cube the next thing on the line 

  squared or cubed

square or cube the previous thing on the line 

  ** or ^

exponentiation (must be to an integral power) 

  space

any amount of whitespace between units is considered a multiplication 

  *, /, or per

multiplication or division 

Parentheses can be used to override the precedence of any of the 
operators. 

Prefixes are special case units, whose names can be attached to 
beginning of other units, with no intervening whitespace. The Unit 
module comes with a rather complete set of predefined SI prefixes; 
see the Units by Type page. 

The prefixes are allowed before units, or by themselves. Thus, these 
are equivalent: 

  megaparsec
  mega parsec
  kilo kilo parsec
  kilo**2 parsec
  square kilo parsec

Note in the last example that square applies only to kilo, and not to 
parsec. That's because the square operator has higher precedence than 
the space. 

Note, however, that the space operator has higher precedence than 
'*', '/', or 'per'. This means that units separated by only 
whitespace in the denominator of an expression do not need to be 
enclosed in parentheses. Thus 

  meters / sec sec

is a unit of acceleration, but 

  meters / sec*sec

is not. The latter is equivalent to just 'meters'.

=head1 Predefined Units

A rather complete set of units is pre-defined in the library, so it 
will probably be rare that you'll need to define your own. See the 
units by name page, or the units by type page for a complete list. 

A pound is a unit of force. I was very much tempted to make it a unit 
of mass, since that is much more often the way it is used, but I have 
a feeling I would have had to take more guff for that than I'm 
prepared to. The everyday pound, then, is named 'pound-mass', 'lbm', 
'lbms', or 'pounds-mass'. 

However, I couldn't bring myself to do the same thing to all the 
other American units derived from a pound. Therefore, ounce, ton, 
long-ton, and hundredweight are all units of mass. 

A few physical constants were defined as Unit objects. This list is 
very restricted, however. I limited them to physical constants which 
really qualify as universal, according to (as much as I know) of the 
laws of physics, and a few constants which have been defined by 
international agreement. Thus, they are: 

  * c   - the speed of light 
  * G   - the universal gravitational constant 
  * eq  - elementary charge 
  * em  - electron mass 
  * u   - atomic mass unit 
  * g0  - standard gravity 
  * atm - standard atmosphere 
  * re  - equatorial radius of the reference geoid 
  * rp  - polar radius of the reference geoid 
  * h   - Planck constant 
  * Na  - Avogadro constant 

A few unit abbreviations had to be changed in order to avoid name 
conflicts: 

  * Elementary charge - abbreviated 'eq' instead of 'e' 
  * Earth gravity - abbreviated 'g0' instead of 'g' 

=head1 Export Options

By default, this module exports nothing. You can request all of the 
utility functions to be exported as follows: 

  use Physics::Unit ':ALL';

Or, you can just get specific ones. For example: 

  use Physics::Unit qw( GetUnit ListUnits );

=head1 COMPILE-TIME STUFF

This section of this module contains stuff which is actually executed
at compile time, when this module is first read in as the result of
the 'use Unit;' statement.  For the most part, all this initializes
the pre-defined units.

=head1 Class Data

=head2 $debug

Turning this on enables copious debugging information. This is a 
package global, not a file-scoped lexical. So it can be turned on 
like this 

  BEGIN { $Physics::Unit::debug = 1; }

  use Physics::Unit;

=head2 $number_re

This is the regular expression used to parse out a number.  It is 
here so that other modules can use it for convenience.

A (correct) regular expression for a floating point number, 
optionally in exponent form. This is hard to come by. 

=head2 %reserved_word

A list of reserved words in Unit expressions. 

=head2 %unit_by_name

A list of all known unit names. The value of the hash is a reference 
to the named unit object. 

=head2 %prefix

A list of all the valid prefixes. The value of the hash is a 
reference to the unit object. Note that the names here are also in 
%unit_by_name. 

These are special case unit names that can be attached to other units 
with no intervening spaces.

=head2 %prototype

A list of all the known types. The values of this hash are references
to unit objects that exemplify these types. I.e., any other units 
that have the same type will have the same dimensionality as the 
example unit. 

=head2 $NumBases

The number of base units. 

=head2 @BaseName

The name of each of the base units. These names also appear in 
%unit_by_name. 

=head2 InitBaseUnit()

Initialize the default set of simple base units.

This is called to inform the library how many independent dimensional 
quantities there are, and what the 'base' unit is for each quantity.

The library is initialized to know about these nine quantities.  More 
can be added at run-time, if desired using this utility function.

=head2 InitPrefix()

Prefixes are just like dimensionless units, but they don't need to be 
separated from the next unit by a space.

Prefix units can only have one name.

=head2 InitTypes()

=head2 InitUnit()

Define units in terms of other units.

Note that forward references are not allowed here.

=head2 GetUnit()

Resolve the energy-torque ambiguity.

=head1 PUBLIC UTILITY FUNCTIONS

=head2 InitBaseUnit()

  Physics::Unit::InitBaseUnit( type, name-list,
                               type, name-list, ... );

InitBaseUnit is used to define any number of new, fundamental, 
independent dimensional quantities. Each such quantity is represented 
by a Unit object, which must have at least one name. From these base 
units, all the units in the system are derived. 

The library is initialized to know about nine base quantities. These 
quantities, and the base units which represent them, are: 

  1.  Distance - meter
  2.  Mass - gram
  3.  Time - second
  4.  Temperature - kelvin
  5.  Current - ampere
  6.  Substance - mole
  7.  Luminosity - candela
  8.  Money - us-dollar
  9.  Data - bit

More base quantities can be added at run-time, by calling this 
function. The arguments to this function are in pairs. Each pair 
consists of a type name followed by a reference to an array. The 
array consists of a list of names which can be used to reference the 
unit. For example: 

  InitBaseUnit('Beauty' => ['sarah', 'sarahs', 'smw']);

This defines a new basic physical type, called Beauty. This also 
causes the creation of a single new Unit object, which has three 
names: sarah, sarahs, and smw. The type Beauty is refered to as a 
base type. The Unit sarah is refered to as the base unit 
corresponding to the type Beauty. 

After defining a new base unit and type, you can then create other 
units derived from this unit, and other types derived from this type.

=head2 InitPrefix()

  Physics::Unit::InitPrefix( name, number,
                             name, number, ... );

This function is used to define strings that can be used to prefix 
any other unit name. As with InitBaseUnit, the library is initialized 
to know about a fair set of prefixes, and more can be added at 
run-time. 

If you desire to create a prefix at run-time, call InitPrefix with a 
list of name-value pairs, for example: 

  InitPrefix('gonzo' => 1e100, 'piccolo' => 1e-100);

From then on, you can use those prefixes to define new units, as in: 

  $beauty_rate = new Physics::Unit('5 piccolosarah / hour');

=head2 InitUnit()

  Physics::Unit::InitUnit( name-list, unit-def,
                           name-list, unit-def, ... );

This function creates one or more new named Units. This may also be 
called by users at runtime, to expand the unit system. For example: 

  InitUnit( ['chris', 'cfm'] => '3 piccolosarahs' );

creates another unit of type Beauty equal to 3 * 10-100 sarahs. 

Both this utility function and the new class method can be used to 
create new, named Units. There are minor differences between these 
two. The new method only allows you to create one unit at a time, 
whereas the InitUnit function can be used to create a large set of 
units with one call. The other difference is that units created with 
InitUnit must have a name, whereas new can be used to create anonymous 
Unit objects. 

In this function and in others, wherever an argument is specified as 
unit-def, you can use either a Unit object, a single unit name, or a 
unit expression. So, for example, these are the same: 

  InitUnit( ['mycron'], '3600 sec' );
  InitUnit( ['mycron'], 'hour' );
  $h = GetUnit('hour');  InitUnit( ['mycron'], $h );

creates a new unit named mycron which is the same as one hour.

=head2 InitTypes()

  Physics::Unit::InitTypes( type-name, unit-def,
                            type-name, unit-def, ... );

Use this function to define derived types. For example: 

  InitTypes( 'Aging' => 'chris / year' );

might describe the loss of beauty with time. 

This function associates a type name with a specific dimensionality. 
The factor of the unit is not used. I.e., in the above example, Aging 
is associated with ( Beauty / Time ). The factor of the unit 
'chris / year' is not used. 

The unit-def argument can be a single unit name, a unit expression, 
or a Unit object. 

=head2 GetUnit()

  $u = Physics::Unit::GetUnit( unit-def );

Returns a unit associated with the the argument passed in. The 
argument can either be a name, a unit expression, or a Unit object.

If the argument is a Unit object, it is simply returned. If the 
argument is a simple unit name, then this returns a reference to the 
named unit. 

If the argument cannot be found as a simple unit name, then this 
method attempts to evaluate it as an expression. If it is successful, 
it will create a new, anonymous Unit object and return a reference to 
it. 

For example: 

  # This returns a reference to a pre-defined, named unit:
  $u = GetUnit('gram');

  # This creates a new, anonymous unit and returns a reference to it:
  $u = GetUnit('km / hour');

=head2 ListUnits()

  @l = Physics::Unit::ListUnits;

Returns a list of all unit names known, sorted alphabetically. 

=head2 ListTypes()

  @l = Physics::Unit::ListTypes;

Returns a list of all the quantity types known to the library, sorted 
alphabetically. 

=head2 NumBases()

  $n = Physics::Unit::NumBases;

Returns the number of base dimension units.

=head2 GetTypeUnit()

  $u = Physics::Unit::GetTypeUnit( type-name );

Returns the Unit object corresponding to a given type. 

=head1 PUBLIC METHODS

=head2 new()

  $u1 = new Physics::Unit( unit-def [, name, name, ... ] );
  $u2 = $u1->new( [name, name, ... ] );

This method creates a new Unit object. The names are optional. (Note: 
the square brackets above are used to indicate that the name list is 
optional, not that the argument is a reference to an anonymous array. 
See the examples below.) 

If names are given, then the new Unit will be given those names, and 
that object will thereafter be constant. 

If more than one name is given, the first is the primary name. The 
primary name is retrieved whenever the name method is called. 

If a unit has a name or names, those names must be different than 
every other unit name known to the library. See the Unit by Names 
page to see an alphabetical list of all the pre-defined unit names. 

If no names are given, then an anonymous unit is created. Note that 
another way of creating new anonymous units is with the GetUnit 
utility function. 

Examples: 

  # Create a new, anonymous unit:
  $u = new Physics::Unit ('3 pi sarahs per s');

  # Create a new, named unit:
  $u = new Physics::Unit ('3 pi sarahs per s', 'bloom');

  # Or, create a new unit with a list of names:
  $u  = new Physics::Unit ('3 pi sarahs per s', 'b', 'blooms', 'blm');
  $n = $u->name;   # returns 'b'

=head2 type()

  $t = $u->type;
  $u->type( type-name );

Get or set this unit's type. 

For example: 

  GetUnit('rod')->type;    # returns 'Distance'

It will almost never be necessary to set a Unit object's type. The type 
is normally determined uniquely from the dimensionality of the unit. 
However, occasionally, more than one type can match a given unit's 
dimensionality. For example, Torque and Energy have the same 
dimensionality. 

In that case, all of the predefined, named units are explicitly 
designated to be one type or the other. For example, the unit newton 
is defined to have the type Energy. See the list of units by type to 
see which units are defined as Energy and which as Torque. 

However, if you are going to create new Unit objects from unit 
expressions that have that dimensionality, it will be necessary to 
explicitly specify which type that unit object is. 

When this method is called to set the unit's type, only one type 
string argument is allowed, and it must be a predefined type name 
(see InitTypes above). 

Once a single type has been associated with a unit, then that will 
remain that unit's type for the rest of the program, unless it is 
re-set again. 

This method returns one of: 

  undef

no type was found to match the unit's dimensionality 

  'prefix'

in the special case where the unit is a named prefix 

  type_name

the prototype unit for type_name matches the unit's dimensionality 
(see InitTypes above) 

  ref to an array of type_name's

more than one type was found to match the unit's dimensionality 

Some examples may perhaps make this clear: 

  $u1 = new Physics::Unit('kg m^2/s^2');
  $t = $u1->type;       #  $t == ['Energy', 'Torque']

  $u1->type('Energy');  #  This establishes the type once and for all
  $t = $u1->type;       #  $t == 'Energy'

  # Now create another Unit object from the same definition
  $u2 = new Physics::Unit('kg m^2/s^2');

  # This is a brand-new object, so the explicit type is unknown, as before:
  $t = $u2->type;    # $t == ['Energy', 'Torque']

  # But if we use a predefined, named unit, we get a single type:
  $u3 = GetUnit('joule')->new;    # *not*  Physics::Unit->new('joule');
  $t = $u3->type;    # $t == 'Energy'

=head2 name()

  $n = $u->name;

Returns the primary name of the unit. If this unit has no names, then 
this method returns undef. 

=head2 abbr()

  $a = $u->abbr;

Returns the shortest name of the unit. If this unit has no names, 
this method will return the undef.

=head2 names()

  @a = $u->names;

Returns a list of names that can be used to reference the unit. 
Returns undef if the unit is unnamed.

Be aware: this might be an empty list - whereas the name() method
above will always return something meaningful.

=head2 def()

  $s = $u->def;

Returns the string that was used to define this unit. Note that if 
the unit has been manipulated with any of the arithmetic methods, 
then the def method will return undef, since the definition string is 
no longer a valid definition of the unit. 

=head2 expanded()

  $s = $u->expanded;

Produces a string representation of the unit, in terms of the base 
units (see InitBaseUnit above).

For example:

  print GetUnit('calorie')->expanded, "\n";

produces 

  4184 m^2 gm s^-2

=head2 ToString()

  $s = $u->ToString;

There are several ways to have a Unit object print itself to a string. 
This method is designed to give you what you usually want, and to be 
guaranteed to always print out something meaningful. 

If the object is named, this does the same as the name method above. 
Otherwise, if the object's definition string is still valid, this 
does the same as the def method above. Otherwise, this does the same 
thing as the expanded method. 

=head2 factor()

  $f = $u->factor;
  $u->factor(newvalue);    # $u must be unnamed

Get or set the unit's conversion factor. If this is used to set a 
Unit's factor, then the Unit object must be anonymous. 

=head2 convert()

  $f = $u->convert( unit-def );

Returns the number which converts this unit to another. The types of 
the units must match. For example: 

  $mile = GetUnit('mile');
  $foot = GetUnit('foot');
  $c = $mile->convert($foot);     # returns 5280

For example:

   $mile = Unit::GetUnit('mile');
   $foot = Unit::GetUnit('foot');
   $c = $mile->convert($foot);     # $c == 5280

=head2 times()

  $u->times( unit-def );

$u is multiplied by unit.  $u must be anonymous.

=head2 recip()

  $u->recip;

$u is replaced with its reciprocal.  $u must be anonymous.

=head2 divide()

  $u->divide( unit-def );

$u is divided by unit and the result replaces $u.  $u must be anonymous. 

For example: 

  $u = new Physics::Unit('36 m^2');
  $u->divide('3 meters');    # $u is now '12 m'
  $u->divide(3);             # $u is now '4 m'
  $u->divide( new Physics::Unit('.5 sec') );  # $u is now '8 m/s'

=head2 power()

  $u->power( integer );

Raises a unit to an integral power. $u must be anonymous. 

=head2 add()

  $u->add( unit );   # unit types must match

unit is added to $u. $u must be anonymous. 

=head2 neg()

  $u->neg;

$u is replaced with its arithmetic negative. $u must be anonymous. 

=head2 subtract()

  $u->subtract( unit-def );   # unit types must match

unit is subtracted from $u. $u must be anonymous.

=head2 copy()

  $n = $u->copy;

This creates a copy of an existing unit. It doesn't copy the names, 
however. So you are free to modify the copy (while modification of 
named units is verboten). 

If the type of the existing unit is well-defined, then it, also, is 
copied. 

This is the same as the new method, when new is called as an object 
method with no names. 

=head2 equal()

  $u1->equal( unit-def );
  Physics::Unit->equal( unit-def, unit-def );

This returns 1 if the two unit objects have the same type and the 
same conversion factor. 

=head1 PRIVATE IMPLEMENTATION ROUTINES

These are not part of the user-interface.  These are only for use 
within the package.

=head2 NewOne()

Creates a new dimensionless unit.

=head2 AddNames()

This is called during the object's construction to add a list of
names to the 'names' array, and to set the reference in the
%unit_by_names cross-reference hash.

=head2 NewType()

   $u->NewType('type');

This is called when we are adding a new type to the system.  This
happens both in InitBaseUnits() and in InitTypes().

=head2 CreateUnit()

This is used by several of the interface utility functions to create
a new unit object from a unit definition (either a simple name, a
unit expression, or a unit object).

It differs from GetUnit in that it always creates a new, anonymous
unit, whereas GetUnit, if given a simple name, returns a reference
to a named unit.

=head2 CompareDim()

Compare the dimension arrays.

Return 0 if they are the same.

=head2 LookName()

Look up the name.

Returns:

  0 not defined
  1 reserved word
  2 unit name
  3 type name

=head2 DebugString()

Convert the unit to a factor - dimension vector format string, e.g.
the unit '3 meters' would be converted to something like

  3 [1, 0, 0]

=head2 CheckChange()

Complain if this unit has a name.  This is used by all the methods
that modify the value of the unit.

=head1 PARSER

=head2 expr()

=head2 term()

  term : factor
       | term factor

A term is any number of factors separated (nominally) by whitespace.
Whitespace is an 'operator' that means the same thing as multiplication,
but has a higher priority than either '*' or '/'.

Examples of terms (the following lines each contain one term):

  3pi radians
  3e+4 globules

=head2 Factor()

  factor : primary
         | primary '**' integer

Note that a primary can be an integer, of course, so factors can
look like this:

  meter ** 3 ^ 5    # note, '**' and '^' are synonymous

=head2 prim()

The unary operators square, sq, cubic, squared, and cubed are
implemented as part of this function.  Thus they have quite a
high precedence, as I think they should.

=head2 get_token()

only used by new above to parse the unit definition string

=head1 AUTHOR

Written by Chris Maloney <Dude@ChrisMaloney.com>

Formatted for distribution by Gene Boggs <cpan@ology.net>

=head1 COPYRIGHT AND LICENSE

Copyright 2002-2003 by Chris Maloney

This library is free software; you can redistribute it and/or modify
it under the same terms as Perl itself.
