rscel 1.0.8

Cel interpreter in rust
Documentation
use rscel_macro::dispatch;

pub use methods::dispatch as uom_convert;

use crate::{CelError, CelResult};
use uom::si::f64::{Mass, ThermodynamicTemperature, Velocity, Volume};
use uom::si::mass::{gram, kilogram, milligram, ounce, pound, slug, ton};
use uom::si::thermodynamic_temperature::{degree_celsius, degree_fahrenheit, kelvin};
use uom::si::velocity::{
    foot_per_second, kilometer_per_hour, knot, meter_per_second, mile_per_hour,
};
use uom::si::volume::{
    cubic_foot, cubic_meter, cubic_yard, cup, fluid_ounce, gallon, liter, milliliter, pint_dry,
    pint_liquid, quart_dry, quart_liquid, tablespoon, teaspoon,
};

const STONE_IN_POUNDS: f64 = 14.0;

#[dispatch]
pub mod methods {
    use crate::{context::default_funcs::uom::uom_convert_internal, CelResult, CelValue};

    fn uom_convert(base: u64, from: String, to: String) -> CelResult<f64> {
        uom_convert_internal(base as f64, &from, &to)
    }

    fn uom_convert(base: i64, from: String, to: String) -> CelResult<f64> {
        uom_convert_internal(base as f64, &from, &to)
    }

    fn uom_convert(base: f64, from: String, to: String) -> CelResult<f64> {
        uom_convert_internal(base as f64, &from, &to)
    }
}

fn uom_convert_internal(base: f64, from: &str, to: &str) -> CelResult<f64> {
    let from_unit = Unit::from_str(from)
        .ok_or_else(|| CelError::argument(&format!("Unsupported unit '{}'.", from)))?;
    let to_unit = Unit::from_str(to)
        .ok_or_else(|| CelError::argument(&format!("Unsupported unit '{}'.", to)))?;

    match (from_unit, to_unit) {
        (Unit::Mass(from_mass), Unit::Mass(to_mass)) => {
            let mass = from_mass.into_mass(base);
            Ok(to_mass.from_mass(mass))
        }
        (Unit::Volume(from_volume), Unit::Volume(to_volume)) => {
            let volume = from_volume.into_volume(base);
            Ok(to_volume.from_volume(volume))
        }
        (Unit::Speed(from_speed), Unit::Speed(to_speed)) => {
            let speed = from_speed.into_velocity(base);
            Ok(to_speed.from_velocity(speed))
        }
        (Unit::Temperature(from_temp), Unit::Temperature(to_temp)) => {
            let temp = from_temp.into_temperature(base);
            Ok(to_temp.from_temperature(temp))
        }
        _ => Err(CelError::argument(&format!(
            "Cannot convert units '{}' -> '{}'.",
            from, to
        ))),
    }
}

#[derive(Clone, Copy, Debug, PartialEq, Eq)]
enum Unit {
    Mass(MassUnit),
    Volume(VolumeUnit),
    Speed(SpeedUnit),
    Temperature(TemperatureUnit),
}

impl Unit {
    fn from_str(unit: &str) -> Option<Self> {
        let normalized = unit.trim().to_lowercase();
        let normalized = normalized.trim_matches('°');

        Some(match normalized {
            "kg" | "kilogram" | "kilograms" => Unit::Mass(MassUnit::Kilogram),
            "g" | "gram" | "grams" => Unit::Mass(MassUnit::Gram),
            "mg" | "milligram" | "milligrams" => Unit::Mass(MassUnit::Milligram),
            "lb" | "lbs" | "pound" | "pounds" => Unit::Mass(MassUnit::Pound),
            "oz" | "ounce" | "ounces" => Unit::Mass(MassUnit::Ounce),
            "stone" | "st" | "stones" => Unit::Mass(MassUnit::Stone),
            "slug" | "slugs" => Unit::Mass(MassUnit::Slug),
            "ton" | "tonne" | "metric_ton" | "metric ton" => Unit::Mass(MassUnit::Ton),

            "l" | "liter" | "liters" | "litre" | "litres" => Unit::Volume(VolumeUnit::Liter),
            "ml" | "milliliter" | "milliliters" | "millilitre" | "millilitres" => {
                Unit::Volume(VolumeUnit::Milliliter)
            }
            "gal" | "gallon" | "gallons" => Unit::Volume(VolumeUnit::Gallon),
            "quart" | "quarts" | "qt" | "qts" | "liquid quart" | "liquid_quart" => {
                Unit::Volume(VolumeUnit::QuartLiquid)
            }
            "dry quart" | "dry_quart" => Unit::Volume(VolumeUnit::QuartDry),
            "pint" | "pints" | "pt" | "pts" | "liquid pint" | "liquid_pint" => {
                Unit::Volume(VolumeUnit::PintLiquid)
            }
            "dry pint" | "dry_pint" => Unit::Volume(VolumeUnit::PintDry),
            "cup" | "cups" => Unit::Volume(VolumeUnit::Cup),
            "fl oz" | "floz" | "fluid ounce" | "fluid_ounce" | "fluid-ounce" => {
                Unit::Volume(VolumeUnit::FluidOunce)
            }
            "tbsp" | "tablespoon" | "tablespoons" => Unit::Volume(VolumeUnit::Tablespoon),
            "tsp" | "teaspoon" | "teaspoons" => Unit::Volume(VolumeUnit::Teaspoon),
            "cubic meter" | "cubic_meter" | "m3" => Unit::Volume(VolumeUnit::CubicMeter),
            "cubic foot" | "cubic_foot" | "ft3" | "cu ft" => Unit::Volume(VolumeUnit::CubicFoot),
            "cubic yard" | "cubic_yard" | "yd3" | "cu yd" => Unit::Volume(VolumeUnit::CubicYard),

            "m/s" | "meter per second" | "meters per second" | "meter_per_second" => {
                Unit::Speed(SpeedUnit::MeterPerSecond)
            }
            "km/h"
            | "kph"
            | "kilometer per hour"
            | "kilometers per hour"
            | "kilometer_per_hour" => Unit::Speed(SpeedUnit::KilometerPerHour),
            "mph" | "mile per hour" | "miles per hour" | "mile_per_hour" => {
                Unit::Speed(SpeedUnit::MilePerHour)
            }
            "kn" | "knot" | "knots" => Unit::Speed(SpeedUnit::Knot),
            "ft/s" | "fps" | "foot per second" | "feet per second" | "foot_per_second" => {
                Unit::Speed(SpeedUnit::FootPerSecond)
            }

            "k" | "kelvin" => Unit::Temperature(TemperatureUnit::Kelvin),
            "c" | "celsius" => Unit::Temperature(TemperatureUnit::Celsius),
            "f" | "fahrenheit" => Unit::Temperature(TemperatureUnit::Fahrenheit),
            _ => return None,
        })
    }
}

#[derive(Clone, Copy, Debug, PartialEq, Eq)]
enum MassUnit {
    Kilogram,
    Gram,
    Milligram,
    Pound,
    Ounce,
    Stone,
    Ton,
    Slug,
}

impl MassUnit {
    fn into_mass(self, value: f64) -> Mass {
        match self {
            MassUnit::Kilogram => Mass::new::<kilogram>(value),
            MassUnit::Gram => Mass::new::<gram>(value),
            MassUnit::Milligram => Mass::new::<milligram>(value),
            MassUnit::Pound => Mass::new::<pound>(value),
            MassUnit::Ounce => Mass::new::<ounce>(value),
            MassUnit::Stone => Mass::new::<pound>(value * STONE_IN_POUNDS),
            MassUnit::Ton => Mass::new::<ton>(value),
            MassUnit::Slug => Mass::new::<slug>(value),
        }
    }

    fn from_mass(self, mass: Mass) -> f64 {
        match self {
            MassUnit::Kilogram => mass.get::<kilogram>(),
            MassUnit::Gram => mass.get::<gram>(),
            MassUnit::Milligram => mass.get::<milligram>(),
            MassUnit::Pound => mass.get::<pound>(),
            MassUnit::Ounce => mass.get::<ounce>(),
            MassUnit::Stone => mass.get::<pound>() / STONE_IN_POUNDS,
            MassUnit::Ton => mass.get::<ton>(),
            MassUnit::Slug => mass.get::<slug>(),
        }
    }
}

#[derive(Clone, Copy, Debug, PartialEq, Eq)]
enum VolumeUnit {
    Liter,
    Milliliter,
    Gallon,
    QuartLiquid,
    QuartDry,
    PintLiquid,
    PintDry,
    Cup,
    FluidOunce,
    Tablespoon,
    Teaspoon,
    CubicMeter,
    CubicFoot,
    CubicYard,
}

impl VolumeUnit {
    fn into_volume(self, value: f64) -> Volume {
        match self {
            VolumeUnit::Liter => Volume::new::<liter>(value),
            VolumeUnit::Milliliter => Volume::new::<milliliter>(value),
            VolumeUnit::Gallon => Volume::new::<gallon>(value),
            VolumeUnit::QuartLiquid => Volume::new::<quart_liquid>(value),
            VolumeUnit::QuartDry => Volume::new::<quart_dry>(value),
            VolumeUnit::PintLiquid => Volume::new::<pint_liquid>(value),
            VolumeUnit::PintDry => Volume::new::<pint_dry>(value),
            VolumeUnit::Cup => Volume::new::<cup>(value),
            VolumeUnit::FluidOunce => Volume::new::<fluid_ounce>(value),
            VolumeUnit::Tablespoon => Volume::new::<tablespoon>(value),
            VolumeUnit::Teaspoon => Volume::new::<teaspoon>(value),
            VolumeUnit::CubicMeter => Volume::new::<cubic_meter>(value),
            VolumeUnit::CubicFoot => Volume::new::<cubic_foot>(value),
            VolumeUnit::CubicYard => Volume::new::<cubic_yard>(value),
        }
    }

    fn from_volume(self, volume: Volume) -> f64 {
        match self {
            VolumeUnit::Liter => volume.get::<liter>(),
            VolumeUnit::Milliliter => volume.get::<milliliter>(),
            VolumeUnit::Gallon => volume.get::<gallon>(),
            VolumeUnit::QuartLiquid => volume.get::<quart_liquid>(),
            VolumeUnit::QuartDry => volume.get::<quart_dry>(),
            VolumeUnit::PintLiquid => volume.get::<pint_liquid>(),
            VolumeUnit::PintDry => volume.get::<pint_dry>(),
            VolumeUnit::Cup => volume.get::<cup>(),
            VolumeUnit::FluidOunce => volume.get::<fluid_ounce>(),
            VolumeUnit::Tablespoon => volume.get::<tablespoon>(),
            VolumeUnit::Teaspoon => volume.get::<teaspoon>(),
            VolumeUnit::CubicMeter => volume.get::<cubic_meter>(),
            VolumeUnit::CubicFoot => volume.get::<cubic_foot>(),
            VolumeUnit::CubicYard => volume.get::<cubic_yard>(),
        }
    }
}

#[derive(Clone, Copy, Debug, PartialEq, Eq)]
enum SpeedUnit {
    MeterPerSecond,
    KilometerPerHour,
    MilePerHour,
    FootPerSecond,
    Knot,
}

impl SpeedUnit {
    fn into_velocity(self, value: f64) -> Velocity {
        match self {
            SpeedUnit::MeterPerSecond => Velocity::new::<meter_per_second>(value),
            SpeedUnit::KilometerPerHour => Velocity::new::<kilometer_per_hour>(value),
            SpeedUnit::MilePerHour => Velocity::new::<mile_per_hour>(value),
            SpeedUnit::FootPerSecond => Velocity::new::<foot_per_second>(value),
            SpeedUnit::Knot => Velocity::new::<knot>(value),
        }
    }

    fn from_velocity(self, velocity: Velocity) -> f64 {
        match self {
            SpeedUnit::MeterPerSecond => velocity.get::<meter_per_second>(),
            SpeedUnit::KilometerPerHour => velocity.get::<kilometer_per_hour>(),
            SpeedUnit::MilePerHour => velocity.get::<mile_per_hour>(),
            SpeedUnit::FootPerSecond => velocity.get::<foot_per_second>(),
            SpeedUnit::Knot => velocity.get::<knot>(),
        }
    }
}

#[derive(Clone, Copy, Debug, PartialEq, Eq)]
enum TemperatureUnit {
    Kelvin,
    Celsius,
    Fahrenheit,
}

impl TemperatureUnit {
    fn into_temperature(self, value: f64) -> ThermodynamicTemperature {
        match self {
            TemperatureUnit::Kelvin => ThermodynamicTemperature::new::<kelvin>(value),
            TemperatureUnit::Celsius => ThermodynamicTemperature::new::<degree_celsius>(value),
            TemperatureUnit::Fahrenheit => {
                ThermodynamicTemperature::new::<degree_fahrenheit>(value)
            }
        }
    }

    fn from_temperature(self, temp: ThermodynamicTemperature) -> f64 {
        match self {
            TemperatureUnit::Kelvin => temp.get::<kelvin>(),
            TemperatureUnit::Celsius => temp.get::<degree_celsius>(),
            TemperatureUnit::Fahrenheit => temp.get::<degree_fahrenheit>(),
        }
    }
}