2023-05-14 00:04:47 +00:00
|
|
|
|
use crate::units::{NonMetric, NonMetricQuantity};
|
|
|
|
|
|
|
|
|
|
enum Expect {
|
|
|
|
|
Number,
|
|
|
|
|
Unit,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[derive(Debug, PartialEq)]
|
|
|
|
|
pub enum ParseError {
|
|
|
|
|
NotValidNumber(String),
|
|
|
|
|
UnexpectedUnit(String),
|
|
|
|
|
UnknownUnit(String),
|
|
|
|
|
ExpectedUnit,
|
2023-05-31 20:19:57 +00:00
|
|
|
|
AmbiguousUnit(String, &'static str, &'static str),
|
2023-05-14 00:04:47 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn parse(input: &str) -> Result<Vec<NonMetricQuantity>, ParseError> {
|
|
|
|
|
let mut quantities = Vec::new();
|
|
|
|
|
let mut state = Expect::Number;
|
|
|
|
|
let mut amount = None;
|
|
|
|
|
|
|
|
|
|
for token in tokenize(input) {
|
|
|
|
|
match (&state, token) {
|
|
|
|
|
(Expect::Number, Token::Number(number)) => {
|
2023-05-28 15:22:49 +00:00
|
|
|
|
let number = parse_number(number)?;
|
2023-05-14 00:04:47 +00:00
|
|
|
|
amount = Some(number);
|
|
|
|
|
state = Expect::Unit;
|
|
|
|
|
}
|
|
|
|
|
(Expect::Number, Token::Unit(unit)) => {
|
|
|
|
|
return Err(ParseError::UnexpectedUnit(unit));
|
|
|
|
|
}
|
|
|
|
|
(Expect::Unit, Token::Number(_)) => {
|
|
|
|
|
unreachable!("token stream can't contain two numbers in a row");
|
|
|
|
|
}
|
|
|
|
|
(Expect::Unit, Token::Unit(unit)) => {
|
2023-05-28 15:22:49 +00:00
|
|
|
|
let unit = parse_unit(unit)?;
|
2023-05-14 00:04:47 +00:00
|
|
|
|
let quantity = NonMetricQuantity {
|
|
|
|
|
amount: amount.take().expect("must have read a number to be in this state"),
|
|
|
|
|
unit: unit,
|
|
|
|
|
};
|
|
|
|
|
quantities.push(quantity);
|
|
|
|
|
state = Expect::Number;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
match state {
|
2023-05-28 20:44:49 +00:00
|
|
|
|
Expect::Number => {}
|
2023-05-14 00:04:47 +00:00
|
|
|
|
Expect::Unit => {
|
|
|
|
|
return Err(ParseError::ExpectedUnit);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Ok(quantities)
|
|
|
|
|
}
|
|
|
|
|
|
2023-05-28 15:22:49 +00:00
|
|
|
|
fn parse_number(input: String) -> Result<f64, ParseError> {
|
2023-05-28 15:07:28 +00:00
|
|
|
|
let no_whitespace: String = input.chars().filter(|c| !c.is_whitespace()).collect();
|
2023-05-28 15:22:49 +00:00
|
|
|
|
no_whitespace.parse().or_else(|_| Err(ParseError::NotValidNumber(input)))
|
2023-05-28 15:07:28 +00:00
|
|
|
|
}
|
|
|
|
|
|
2023-05-28 15:22:49 +00:00
|
|
|
|
fn parse_unit(input: String) -> Result<NonMetric, ParseError> {
|
|
|
|
|
match input.as_str() {
|
2023-05-14 00:04:47 +00:00
|
|
|
|
// Length
|
2023-05-28 15:22:49 +00:00
|
|
|
|
"inch" => Ok(NonMetric::Inch),
|
|
|
|
|
"inches" => Ok(NonMetric::Inch),
|
|
|
|
|
"in" => Ok(NonMetric::Inch),
|
|
|
|
|
"\"" => Ok(NonMetric::Inch),
|
|
|
|
|
"″" => Ok(NonMetric::Inch),
|
|
|
|
|
|
|
|
|
|
"foot" => Ok(NonMetric::Foot),
|
|
|
|
|
"feet" => Ok(NonMetric::Foot),
|
|
|
|
|
"ft" => Ok(NonMetric::Foot),
|
|
|
|
|
"'" => Ok(NonMetric::Foot),
|
|
|
|
|
"′" => Ok(NonMetric::Foot),
|
|
|
|
|
|
|
|
|
|
"yard" => Ok(NonMetric::Yard),
|
|
|
|
|
"yards" => Ok(NonMetric::Yard),
|
|
|
|
|
"yd" => Ok(NonMetric::Yard),
|
|
|
|
|
|
|
|
|
|
"mile" => Ok(NonMetric::Mile),
|
|
|
|
|
"miles" => Ok(NonMetric::Mile),
|
|
|
|
|
"mi" => Ok(NonMetric::Mile),
|
|
|
|
|
"m" => Ok(NonMetric::Mile),
|
2023-05-14 00:04:47 +00:00
|
|
|
|
|
|
|
|
|
// Weight
|
2023-05-28 15:22:49 +00:00
|
|
|
|
"ounce" => Ok(NonMetric::Ounce),
|
|
|
|
|
"ounces" => Ok(NonMetric::Ounce),
|
|
|
|
|
"oz" => Ok(NonMetric::Ounce),
|
2023-05-14 00:04:47 +00:00
|
|
|
|
|
2023-05-28 15:22:49 +00:00
|
|
|
|
"pound" => Ok(NonMetric::Pound),
|
|
|
|
|
"pounds" => Ok(NonMetric::Pound),
|
|
|
|
|
"lb" => Ok(NonMetric::Pound),
|
|
|
|
|
"lbs" => Ok(NonMetric::Pound),
|
|
|
|
|
"#" => Ok(NonMetric::Pound),
|
2023-05-14 00:04:47 +00:00
|
|
|
|
|
2023-05-28 15:22:49 +00:00
|
|
|
|
"stone" => Ok(NonMetric::Stone),
|
|
|
|
|
"stones" => Ok(NonMetric::Stone),
|
|
|
|
|
"st" => Ok(NonMetric::Stone),
|
2023-05-14 00:04:47 +00:00
|
|
|
|
|
2023-05-28 17:32:31 +00:00
|
|
|
|
// Temperature
|
2023-05-29 18:52:34 +00:00
|
|
|
|
"degree Fahrenheit" => Ok(NonMetric::Fahrenheit),
|
|
|
|
|
"degrees Fahrenheit" => Ok(NonMetric::Fahrenheit),
|
|
|
|
|
"degree fahrenheit" => Ok(NonMetric::Fahrenheit),
|
|
|
|
|
"degrees fahrenheit" => Ok(NonMetric::Fahrenheit),
|
|
|
|
|
"Fahrenheit" => Ok(NonMetric::Fahrenheit),
|
|
|
|
|
"fahrenheit" => Ok(NonMetric::Fahrenheit),
|
2023-05-28 17:32:31 +00:00
|
|
|
|
"°F" => Ok(NonMetric::Fahrenheit),
|
|
|
|
|
"F" => Ok(NonMetric::Fahrenheit),
|
|
|
|
|
|
2023-05-28 23:55:43 +00:00
|
|
|
|
// Area
|
2023-05-29 18:58:29 +00:00
|
|
|
|
"square inch" => Ok(NonMetric::SquareInch),
|
|
|
|
|
"square inches" => Ok(NonMetric::SquareInch),
|
|
|
|
|
"square in" => Ok(NonMetric::SquareInch),
|
|
|
|
|
"sq inch" => Ok(NonMetric::SquareInch),
|
|
|
|
|
"sq inches" => Ok(NonMetric::SquareInch),
|
|
|
|
|
"sq in" => Ok(NonMetric::SquareInch),
|
2023-05-28 23:55:43 +00:00
|
|
|
|
"inch²" => Ok(NonMetric::SquareInch),
|
|
|
|
|
"inches²" => Ok(NonMetric::SquareInch),
|
|
|
|
|
"in²" => Ok(NonMetric::SquareInch),
|
2023-05-29 18:58:29 +00:00
|
|
|
|
"\"²" => Ok(NonMetric::SquareInch),
|
|
|
|
|
"″²" => Ok(NonMetric::SquareInch),
|
|
|
|
|
"inch^2" => Ok(NonMetric::SquareInch),
|
|
|
|
|
"inches^2" => Ok(NonMetric::SquareInch),
|
|
|
|
|
"in^2" => Ok(NonMetric::SquareInch),
|
|
|
|
|
"\"^2" => Ok(NonMetric::SquareInch),
|
2023-05-28 23:55:43 +00:00
|
|
|
|
|
2023-05-29 19:03:08 +00:00
|
|
|
|
"square foot" => Ok(NonMetric::SquareFoot),
|
|
|
|
|
"square feet" => Ok(NonMetric::SquareFoot),
|
|
|
|
|
"square ft" => Ok(NonMetric::SquareFoot),
|
|
|
|
|
"sq foot" => Ok(NonMetric::SquareFoot),
|
|
|
|
|
"sq feet" => Ok(NonMetric::SquareFoot),
|
|
|
|
|
"sq ft" => Ok(NonMetric::SquareFoot),
|
2023-05-29 00:01:56 +00:00
|
|
|
|
"foot²" => Ok(NonMetric::SquareFoot),
|
|
|
|
|
"feet²" => Ok(NonMetric::SquareFoot),
|
|
|
|
|
"ft²" => Ok(NonMetric::SquareFoot),
|
2023-05-29 19:03:08 +00:00
|
|
|
|
"'²" => Ok(NonMetric::SquareFoot),
|
|
|
|
|
"′²" => Ok(NonMetric::SquareFoot),
|
|
|
|
|
"foot^2" => Ok(NonMetric::SquareFoot),
|
|
|
|
|
"feet^2" => Ok(NonMetric::SquareFoot),
|
|
|
|
|
"ft^2" => Ok(NonMetric::SquareFoot),
|
|
|
|
|
"'^2" => Ok(NonMetric::SquareFoot),
|
|
|
|
|
"sf" => Ok(NonMetric::SquareFoot),
|
2023-05-29 00:01:56 +00:00
|
|
|
|
|
2023-05-29 00:18:30 +00:00
|
|
|
|
"acre" => Ok(NonMetric::Acre),
|
|
|
|
|
"acres" => Ok(NonMetric::Acre),
|
|
|
|
|
"ac" => Ok(NonMetric::Acre),
|
|
|
|
|
|
2023-05-29 19:05:08 +00:00
|
|
|
|
"square mile" => Ok(NonMetric::SquareMile),
|
|
|
|
|
"square miles" => Ok(NonMetric::SquareMile),
|
|
|
|
|
"square mi" => Ok(NonMetric::SquareMile),
|
|
|
|
|
"sq mile" => Ok(NonMetric::SquareMile),
|
|
|
|
|
"sq miles" => Ok(NonMetric::SquareMile),
|
|
|
|
|
"sq mi" => Ok(NonMetric::SquareMile),
|
2023-05-29 00:27:55 +00:00
|
|
|
|
"mile²" => Ok(NonMetric::SquareMile),
|
|
|
|
|
"miles²" => Ok(NonMetric::SquareMile),
|
|
|
|
|
"mi²" => Ok(NonMetric::SquareMile),
|
2023-05-29 19:05:08 +00:00
|
|
|
|
"mile^2" => Ok(NonMetric::SquareMile),
|
|
|
|
|
"miles^2" => Ok(NonMetric::SquareMile),
|
|
|
|
|
"mi^2" => Ok(NonMetric::SquareMile),
|
2023-05-29 00:27:55 +00:00
|
|
|
|
|
2023-05-29 19:29:10 +00:00
|
|
|
|
// Volume
|
|
|
|
|
"cubic inch" => Ok(NonMetric::CubicInch),
|
|
|
|
|
"cubic inches" => Ok(NonMetric::CubicInch),
|
|
|
|
|
"cubic in" => Ok(NonMetric::CubicInch),
|
|
|
|
|
"cu inch" => Ok(NonMetric::CubicInch),
|
|
|
|
|
"cu inches" => Ok(NonMetric::CubicInch),
|
|
|
|
|
"cu in" => Ok(NonMetric::CubicInch),
|
|
|
|
|
"inch³" => Ok(NonMetric::CubicInch),
|
|
|
|
|
"inches³" => Ok(NonMetric::CubicInch),
|
|
|
|
|
"in³" => Ok(NonMetric::CubicInch),
|
|
|
|
|
"inch^3" => Ok(NonMetric::CubicInch),
|
|
|
|
|
"inches^3" => Ok(NonMetric::CubicInch),
|
|
|
|
|
"in^3" => Ok(NonMetric::CubicInch),
|
|
|
|
|
|
2023-05-29 19:40:03 +00:00
|
|
|
|
"cubic foot" => Ok(NonMetric::CubicFoot),
|
|
|
|
|
"cubic feet" => Ok(NonMetric::CubicFoot),
|
|
|
|
|
"cubic ft" => Ok(NonMetric::CubicFoot),
|
|
|
|
|
"cu foot" => Ok(NonMetric::CubicFoot),
|
|
|
|
|
"cu feet" => Ok(NonMetric::CubicFoot),
|
|
|
|
|
"cu ft" => Ok(NonMetric::CubicFoot),
|
|
|
|
|
"foot³" => Ok(NonMetric::CubicFoot),
|
|
|
|
|
"feet³" => Ok(NonMetric::CubicFoot),
|
|
|
|
|
"ft³" => Ok(NonMetric::CubicFoot),
|
|
|
|
|
"foot^3" => Ok(NonMetric::CubicFoot),
|
|
|
|
|
"feet^3" => Ok(NonMetric::CubicFoot),
|
|
|
|
|
"ft^3" => Ok(NonMetric::CubicFoot),
|
|
|
|
|
|
2023-05-30 17:01:35 +00:00
|
|
|
|
// Fluid volume
|
2023-05-30 17:34:38 +00:00
|
|
|
|
"imperial fluid ounce" => Ok(NonMetric::ImperialFluidOunce),
|
|
|
|
|
"imperial fluid ounces" => Ok(NonMetric::ImperialFluidOunce),
|
|
|
|
|
"imp fl oz" => Ok(NonMetric::ImperialFluidOunce),
|
|
|
|
|
"imp fl. oz." => Ok(NonMetric::ImperialFluidOunce),
|
|
|
|
|
"imp oz. fl." => Ok(NonMetric::ImperialFluidOunce),
|
|
|
|
|
|
2023-05-31 14:21:41 +00:00
|
|
|
|
"imperial pint" => Ok(NonMetric::ImperialPint),
|
|
|
|
|
"imperial pints" => Ok(NonMetric::ImperialPint),
|
|
|
|
|
"imp pt" => Ok(NonMetric::ImperialPint),
|
2023-05-31 14:43:49 +00:00
|
|
|
|
"imp p" => Ok(NonMetric::ImperialPint),
|
2023-05-31 14:21:41 +00:00
|
|
|
|
|
|
|
|
|
"imperial quart" => Ok(NonMetric::ImperialQuart),
|
|
|
|
|
"imperial quarts" => Ok(NonMetric::ImperialQuart),
|
|
|
|
|
"imp qt" => Ok(NonMetric::ImperialQuart),
|
|
|
|
|
|
|
|
|
|
"imperial gallon" => Ok(NonMetric::ImperialGallon),
|
|
|
|
|
"imperial gallons" => Ok(NonMetric::ImperialGallon),
|
|
|
|
|
"imp gal" => Ok(NonMetric::ImperialGallon),
|
|
|
|
|
|
2023-05-31 19:38:50 +00:00
|
|
|
|
"US teaspoon" => Ok(NonMetric::USTeaspoon),
|
|
|
|
|
"US teaspoons" => Ok(NonMetric::USTeaspoon),
|
|
|
|
|
"US tsp." => Ok(NonMetric::USTeaspoon),
|
|
|
|
|
"US tsp" => Ok(NonMetric::USTeaspoon),
|
|
|
|
|
"us teaspoon" => Ok(NonMetric::USTeaspoon),
|
|
|
|
|
"us teaspoons" => Ok(NonMetric::USTeaspoon),
|
|
|
|
|
"us tsp." => Ok(NonMetric::USTeaspoon),
|
|
|
|
|
"us tsp" => Ok(NonMetric::USTeaspoon),
|
|
|
|
|
"teaspoon" => Ok(NonMetric::USTeaspoon),
|
|
|
|
|
"teaspoons" => Ok(NonMetric::USTeaspoon),
|
|
|
|
|
"tsp." => Ok(NonMetric::USTeaspoon),
|
|
|
|
|
"tsp" => Ok(NonMetric::USTeaspoon),
|
|
|
|
|
|
2023-05-31 19:29:58 +00:00
|
|
|
|
"US tablespoon" => Ok(NonMetric::USTablespoon),
|
|
|
|
|
"US tablespoons" => Ok(NonMetric::USTablespoon),
|
|
|
|
|
"US Tbsp." => Ok(NonMetric::USTablespoon),
|
|
|
|
|
"US Tbsp" => Ok(NonMetric::USTablespoon),
|
|
|
|
|
"us tablespoon" => Ok(NonMetric::USTablespoon),
|
|
|
|
|
"us tablespoons" => Ok(NonMetric::USTablespoon),
|
|
|
|
|
"us tbsp." => Ok(NonMetric::USTablespoon),
|
|
|
|
|
"us tbsp" => Ok(NonMetric::USTablespoon),
|
|
|
|
|
"tablespoon" => Ok(NonMetric::USTablespoon),
|
|
|
|
|
"tablespoons" => Ok(NonMetric::USTablespoon),
|
|
|
|
|
"Tbsp." => Ok(NonMetric::USTablespoon),
|
|
|
|
|
"Tbsp" => Ok(NonMetric::USTablespoon),
|
|
|
|
|
"tbsp." => Ok(NonMetric::USTablespoon),
|
|
|
|
|
"tbsp" => Ok(NonMetric::USTablespoon),
|
|
|
|
|
|
2023-05-31 19:19:55 +00:00
|
|
|
|
"US fluid ounce" => Ok(NonMetric::USFluidOunce),
|
|
|
|
|
"US fluid ounces" => Ok(NonMetric::USFluidOunce),
|
|
|
|
|
"US fl oz" => Ok(NonMetric::USFluidOunce),
|
|
|
|
|
"US fl. oz." => Ok(NonMetric::USFluidOunce),
|
|
|
|
|
"US oz. fl." => Ok(NonMetric::USFluidOunce),
|
|
|
|
|
"us fluid ounce" => Ok(NonMetric::USFluidOunce),
|
|
|
|
|
"us fluid ounces" => Ok(NonMetric::USFluidOunce),
|
|
|
|
|
"us fl oz" => Ok(NonMetric::USFluidOunce),
|
|
|
|
|
"us fl. oz." => Ok(NonMetric::USFluidOunce),
|
|
|
|
|
"us oz. fl." => Ok(NonMetric::USFluidOunce),
|
|
|
|
|
|
2023-05-31 19:10:49 +00:00
|
|
|
|
"US cup" => Ok(NonMetric::USCup),
|
|
|
|
|
"US cups" => Ok(NonMetric::USCup),
|
|
|
|
|
"us cup" => Ok(NonMetric::USCup),
|
|
|
|
|
"us cups" => Ok(NonMetric::USCup),
|
|
|
|
|
|
2023-05-31 14:49:24 +00:00
|
|
|
|
"US liquid pint" => Ok(NonMetric::USLiquidPint),
|
|
|
|
|
"US liquid pints" => Ok(NonMetric::USLiquidPint),
|
|
|
|
|
"US pint" => Ok(NonMetric::USLiquidPint),
|
|
|
|
|
"US pints" => Ok(NonMetric::USLiquidPint),
|
|
|
|
|
"US pt" => Ok(NonMetric::USLiquidPint),
|
|
|
|
|
"US p" => Ok(NonMetric::USLiquidPint),
|
|
|
|
|
"us liquid pint" => Ok(NonMetric::USLiquidPint),
|
|
|
|
|
"us liquid pints" => Ok(NonMetric::USLiquidPint),
|
|
|
|
|
"us pint" => Ok(NonMetric::USLiquidPint),
|
|
|
|
|
"us pints" => Ok(NonMetric::USLiquidPint),
|
|
|
|
|
"us pt" => Ok(NonMetric::USLiquidPint),
|
|
|
|
|
"us p" => Ok(NonMetric::USLiquidPint),
|
|
|
|
|
|
2023-05-31 14:42:18 +00:00
|
|
|
|
"US liquid quart" => Ok(NonMetric::USLiquidQuart),
|
|
|
|
|
"US liquid quarts" => Ok(NonMetric::USLiquidQuart),
|
|
|
|
|
"US quart" => Ok(NonMetric::USLiquidQuart),
|
|
|
|
|
"US quarts" => Ok(NonMetric::USLiquidQuart),
|
|
|
|
|
"US qt" => Ok(NonMetric::USLiquidQuart),
|
|
|
|
|
"us liquid quart" => Ok(NonMetric::USLiquidQuart),
|
|
|
|
|
"us liquid quarts" => Ok(NonMetric::USLiquidQuart),
|
|
|
|
|
"us quart" => Ok(NonMetric::USLiquidQuart),
|
|
|
|
|
"us quarts" => Ok(NonMetric::USLiquidQuart),
|
|
|
|
|
"us qt" => Ok(NonMetric::USLiquidQuart),
|
|
|
|
|
|
2023-05-31 14:35:19 +00:00
|
|
|
|
"US gallon" => Ok(NonMetric::USGallon),
|
|
|
|
|
"US gallons" => Ok(NonMetric::USGallon),
|
|
|
|
|
"US gal" => Ok(NonMetric::USGallon),
|
|
|
|
|
"us gallon" => Ok(NonMetric::USGallon),
|
|
|
|
|
"us gallons" => Ok(NonMetric::USGallon),
|
|
|
|
|
"us gal" => Ok(NonMetric::USGallon),
|
|
|
|
|
|
2023-05-31 20:19:57 +00:00
|
|
|
|
// Ambiguous units
|
|
|
|
|
"fluid ounce" => Err(ParseError::AmbiguousUnit(input, "imperial", "US")),
|
|
|
|
|
"fluid ounces" => Err(ParseError::AmbiguousUnit(input, "imperial", "US")),
|
|
|
|
|
"fl oz" => Err(ParseError::AmbiguousUnit(input, "imp", "US")),
|
|
|
|
|
"fl. oz." => Err(ParseError::AmbiguousUnit(input, "imp", "US")),
|
|
|
|
|
"oz. fl." => Err(ParseError::AmbiguousUnit(input, "imp", "US")),
|
|
|
|
|
|
|
|
|
|
"pint" => Err(ParseError::AmbiguousUnit(input, "imperial", "US")),
|
|
|
|
|
"pints" => Err(ParseError::AmbiguousUnit(input, "imperial", "US")),
|
|
|
|
|
"pt" => Err(ParseError::AmbiguousUnit(input, "imp", "US")),
|
|
|
|
|
"p" => Err(ParseError::AmbiguousUnit(input, "imp", "US")),
|
|
|
|
|
|
|
|
|
|
"quart" => Err(ParseError::AmbiguousUnit(input, "imperial", "US")),
|
|
|
|
|
"quarts" => Err(ParseError::AmbiguousUnit(input, "imperial", "US")),
|
|
|
|
|
"qt" => Err(ParseError::AmbiguousUnit(input, "imp", "US")),
|
|
|
|
|
|
|
|
|
|
"gallon" => Err(ParseError::AmbiguousUnit(input, "imperial", "US")),
|
|
|
|
|
"gallons" => Err(ParseError::AmbiguousUnit(input, "imperial", "US")),
|
|
|
|
|
"gal" => Err(ParseError::AmbiguousUnit(input, "imp", "US")),
|
|
|
|
|
|
|
|
|
|
// Unknown unit
|
2023-05-28 15:22:49 +00:00
|
|
|
|
_ => Err(ParseError::UnknownUnit(input)),
|
2023-05-14 00:04:47 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[derive(Debug, PartialEq)]
|
|
|
|
|
enum Token {
|
|
|
|
|
Number(String),
|
|
|
|
|
Unit(String),
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
enum TokState {
|
|
|
|
|
Neutral,
|
|
|
|
|
Number,
|
2023-05-29 18:48:45 +00:00
|
|
|
|
Unit(bool),
|
2023-05-14 00:04:47 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn tokenize(input: &str) -> Vec<Token> {
|
|
|
|
|
let mut tokens = Vec::new();
|
|
|
|
|
let mut token = String::new();
|
|
|
|
|
let mut state = TokState::Neutral;
|
|
|
|
|
|
|
|
|
|
for c in input.chars() {
|
|
|
|
|
match state {
|
|
|
|
|
TokState::Neutral => {
|
|
|
|
|
if c.is_ascii_digit() || c == '-' {
|
|
|
|
|
token.push(c);
|
|
|
|
|
state = TokState::Number;
|
|
|
|
|
} else if !c.is_whitespace() {
|
|
|
|
|
token.push(c);
|
2023-05-29 18:48:45 +00:00
|
|
|
|
state = TokState::Unit(false);
|
2023-05-14 00:04:47 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
TokState::Number => {
|
|
|
|
|
if c.is_ascii_digit() ||
|
|
|
|
|
c.is_whitespace() ||
|
|
|
|
|
c == '.' {
|
|
|
|
|
token.push(c);
|
|
|
|
|
} else {
|
|
|
|
|
tokens.push(Token::Number(token.trim().to_string()));
|
2023-05-29 18:48:45 +00:00
|
|
|
|
state = TokState::Unit(false);
|
2023-05-14 00:04:47 +00:00
|
|
|
|
token = String::new();
|
|
|
|
|
token.push(c);
|
|
|
|
|
}
|
|
|
|
|
}
|
2023-05-29 18:48:45 +00:00
|
|
|
|
TokState::Unit(after_caret) => {
|
|
|
|
|
if !after_caret && (c.is_ascii_digit() || c == '-') {
|
2023-05-29 18:43:02 +00:00
|
|
|
|
tokens.push(Token::Unit(token.trim().to_string()));
|
2023-05-14 00:04:47 +00:00
|
|
|
|
state = TokState::Number;
|
|
|
|
|
token = String::new();
|
|
|
|
|
token.push(c);
|
2023-05-29 18:48:45 +00:00
|
|
|
|
} else if c == '^' {
|
|
|
|
|
token.push(c);
|
|
|
|
|
state = TokState::Unit(true);
|
|
|
|
|
} else if c.is_whitespace() {
|
|
|
|
|
token.push(c);
|
|
|
|
|
state = TokState::Unit(false);
|
2023-05-14 00:04:47 +00:00
|
|
|
|
} else {
|
2023-05-29 18:43:02 +00:00
|
|
|
|
token.push(c);
|
2023-05-14 00:04:47 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
match state {
|
|
|
|
|
TokState::Neutral => { assert!(token.len() == 0); }
|
|
|
|
|
TokState::Number => { tokens.push(Token::Number(token.trim().to_string())); }
|
2023-05-29 18:48:45 +00:00
|
|
|
|
TokState::Unit(_) => { tokens.push(Token::Unit(token.trim().to_string())); }
|
2023-05-14 00:04:47 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
tokens
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
|
mod test {
|
|
|
|
|
use super::*;
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn parsing() {
|
|
|
|
|
assert_eq!(parse(""), Ok(vec![]));
|
|
|
|
|
assert_eq!(parse("5 ft"), Ok(vec![
|
|
|
|
|
NonMetricQuantity { amount: 5.0, unit: NonMetric::Foot },
|
|
|
|
|
]));
|
|
|
|
|
assert_eq!(parse("5 ft 8 in"), Ok(vec![
|
|
|
|
|
NonMetricQuantity { amount: 5.0, unit: NonMetric::Foot },
|
|
|
|
|
NonMetricQuantity { amount: 8.0, unit: NonMetric::Inch },
|
|
|
|
|
]));
|
2023-05-28 15:07:28 +00:00
|
|
|
|
assert_eq!(parse("20 000 lbs"), Ok(vec![
|
|
|
|
|
NonMetricQuantity { amount: 20_000.0, unit: NonMetric::Pound },
|
|
|
|
|
]));
|
2023-05-14 00:04:47 +00:00
|
|
|
|
|
|
|
|
|
assert_eq!(parse("12.0."), Err(ParseError::NotValidNumber("12.0.".to_string())));
|
|
|
|
|
assert_eq!(parse("ft"), Err(ParseError::UnexpectedUnit("ft".to_string())));
|
|
|
|
|
assert_eq!(parse("5 tf"), Err(ParseError::UnknownUnit("tf".to_string())));
|
|
|
|
|
assert_eq!(parse("12"), Err(ParseError::ExpectedUnit));
|
2023-05-31 20:19:57 +00:00
|
|
|
|
assert_eq!(parse("1 gallon"), Err(ParseError::AmbiguousUnit("gallon".to_string(), "imperial", "US")));
|
2023-05-14 00:04:47 +00:00
|
|
|
|
}
|
|
|
|
|
|
2023-05-28 15:07:28 +00:00
|
|
|
|
#[test]
|
|
|
|
|
fn numbers() {
|
2023-05-28 15:22:49 +00:00
|
|
|
|
assert_eq!(parse_number("".to_string()), Err(ParseError::NotValidNumber("".to_string())));
|
|
|
|
|
assert_eq!(parse_number("1".to_string()), Ok(1.0));
|
|
|
|
|
assert_eq!(parse_number("1.0".to_string()), Ok(1.0));
|
|
|
|
|
assert_eq!(parse_number("0.1".to_string()), Ok(0.1));
|
|
|
|
|
assert_eq!(parse_number("0.1.".to_string()), Err(ParseError::NotValidNumber("0.1.".to_string())));
|
|
|
|
|
assert_eq!(parse_number("-10".to_string()), Ok(-10.0));
|
|
|
|
|
assert_eq!(parse_number("10\t00\u{1680}000".to_string()), Ok(10_00_000.0));
|
2023-05-28 15:07:28 +00:00
|
|
|
|
}
|
|
|
|
|
|
2023-05-14 00:04:47 +00:00
|
|
|
|
#[test]
|
|
|
|
|
fn units() {
|
|
|
|
|
// Length
|
2023-05-28 15:22:49 +00:00
|
|
|
|
assert_eq!(parse_unit("inch".to_string()), Ok(NonMetric::Inch));
|
|
|
|
|
assert_eq!(parse_unit("inches".to_string()), Ok(NonMetric::Inch));
|
|
|
|
|
assert_eq!(parse_unit("in".to_string()), Ok(NonMetric::Inch));
|
|
|
|
|
assert_eq!(parse_unit("\"".to_string()), Ok(NonMetric::Inch));
|
|
|
|
|
assert_eq!(parse_unit("″".to_string()), Ok(NonMetric::Inch));
|
|
|
|
|
|
|
|
|
|
assert_eq!(parse_unit("foot".to_string()), Ok(NonMetric::Foot));
|
|
|
|
|
assert_eq!(parse_unit("feet".to_string()), Ok(NonMetric::Foot));
|
|
|
|
|
assert_eq!(parse_unit("ft".to_string()), Ok(NonMetric::Foot));
|
|
|
|
|
assert_eq!(parse_unit("'".to_string()), Ok(NonMetric::Foot));
|
|
|
|
|
assert_eq!(parse_unit("′".to_string()), Ok(NonMetric::Foot));
|
|
|
|
|
|
|
|
|
|
assert_eq!(parse_unit("yard".to_string()), Ok(NonMetric::Yard));
|
|
|
|
|
assert_eq!(parse_unit("yards".to_string()), Ok(NonMetric::Yard));
|
|
|
|
|
assert_eq!(parse_unit("yd".to_string()), Ok(NonMetric::Yard));
|
|
|
|
|
|
|
|
|
|
assert_eq!(parse_unit("mile".to_string()), Ok(NonMetric::Mile));
|
|
|
|
|
assert_eq!(parse_unit("miles".to_string()), Ok(NonMetric::Mile));
|
|
|
|
|
assert_eq!(parse_unit("mi".to_string()), Ok(NonMetric::Mile));
|
|
|
|
|
assert_eq!(parse_unit("m".to_string()), Ok(NonMetric::Mile));
|
2023-05-14 00:04:47 +00:00
|
|
|
|
|
|
|
|
|
// Weight
|
2023-05-28 15:22:49 +00:00
|
|
|
|
assert_eq!(parse_unit("ounce".to_string()), Ok(NonMetric::Ounce));
|
|
|
|
|
assert_eq!(parse_unit("ounces".to_string()), Ok(NonMetric::Ounce));
|
|
|
|
|
assert_eq!(parse_unit("oz".to_string()), Ok(NonMetric::Ounce));
|
2023-05-14 00:04:47 +00:00
|
|
|
|
|
2023-05-28 15:22:49 +00:00
|
|
|
|
assert_eq!(parse_unit("pound".to_string()), Ok(NonMetric::Pound));
|
|
|
|
|
assert_eq!(parse_unit("pounds".to_string()), Ok(NonMetric::Pound));
|
|
|
|
|
assert_eq!(parse_unit("lb".to_string()), Ok(NonMetric::Pound));
|
|
|
|
|
assert_eq!(parse_unit("lbs".to_string()), Ok(NonMetric::Pound));
|
|
|
|
|
assert_eq!(parse_unit("#".to_string()), Ok(NonMetric::Pound));
|
2023-05-14 00:04:47 +00:00
|
|
|
|
|
2023-05-28 15:22:49 +00:00
|
|
|
|
assert_eq!(parse_unit("stone".to_string()), Ok(NonMetric::Stone));
|
|
|
|
|
assert_eq!(parse_unit("stones".to_string()), Ok(NonMetric::Stone));
|
|
|
|
|
assert_eq!(parse_unit("st".to_string()), Ok(NonMetric::Stone));
|
2023-05-14 00:04:47 +00:00
|
|
|
|
|
2023-05-28 17:32:31 +00:00
|
|
|
|
// Temperature
|
2023-05-29 18:52:34 +00:00
|
|
|
|
assert_eq!(parse_unit("degree Fahrenheit".to_string()), Ok(NonMetric::Fahrenheit));
|
|
|
|
|
assert_eq!(parse_unit("degrees Fahrenheit".to_string()), Ok(NonMetric::Fahrenheit));
|
|
|
|
|
assert_eq!(parse_unit("degree fahrenheit".to_string()), Ok(NonMetric::Fahrenheit));
|
|
|
|
|
assert_eq!(parse_unit("degrees fahrenheit".to_string()), Ok(NonMetric::Fahrenheit));
|
|
|
|
|
assert_eq!(parse_unit("Fahrenheit".to_string()), Ok(NonMetric::Fahrenheit));
|
|
|
|
|
assert_eq!(parse_unit("fahrenheit".to_string()), Ok(NonMetric::Fahrenheit));
|
2023-05-28 17:32:31 +00:00
|
|
|
|
assert_eq!(parse_unit("°F".to_string()), Ok(NonMetric::Fahrenheit));
|
|
|
|
|
assert_eq!(parse_unit("F".to_string()), Ok(NonMetric::Fahrenheit));
|
|
|
|
|
|
2023-05-28 23:55:43 +00:00
|
|
|
|
// Area
|
2023-05-29 18:58:29 +00:00
|
|
|
|
assert_eq!(parse_unit("square inch".to_string()), Ok(NonMetric::SquareInch));
|
|
|
|
|
assert_eq!(parse_unit("square inches".to_string()), Ok(NonMetric::SquareInch));
|
|
|
|
|
assert_eq!(parse_unit("square in".to_string()), Ok(NonMetric::SquareInch));
|
|
|
|
|
assert_eq!(parse_unit("sq inch".to_string()), Ok(NonMetric::SquareInch));
|
|
|
|
|
assert_eq!(parse_unit("sq inches".to_string()), Ok(NonMetric::SquareInch));
|
|
|
|
|
assert_eq!(parse_unit("sq in".to_string()), Ok(NonMetric::SquareInch));
|
2023-05-28 23:55:43 +00:00
|
|
|
|
assert_eq!(parse_unit("inch²".to_string()), Ok(NonMetric::SquareInch));
|
|
|
|
|
assert_eq!(parse_unit("inches²".to_string()), Ok(NonMetric::SquareInch));
|
|
|
|
|
assert_eq!(parse_unit("in²".to_string()), Ok(NonMetric::SquareInch));
|
2023-05-29 18:58:29 +00:00
|
|
|
|
assert_eq!(parse_unit("\"²".to_string()), Ok(NonMetric::SquareInch));
|
|
|
|
|
assert_eq!(parse_unit("″²".to_string()), Ok(NonMetric::SquareInch));
|
|
|
|
|
assert_eq!(parse_unit("inch^2".to_string()), Ok(NonMetric::SquareInch));
|
|
|
|
|
assert_eq!(parse_unit("inches^2".to_string()), Ok(NonMetric::SquareInch));
|
|
|
|
|
assert_eq!(parse_unit("in^2".to_string()), Ok(NonMetric::SquareInch));
|
|
|
|
|
assert_eq!(parse_unit("\"^2".to_string()), Ok(NonMetric::SquareInch));
|
2023-05-28 23:55:43 +00:00
|
|
|
|
|
2023-05-29 19:03:08 +00:00
|
|
|
|
assert_eq!(parse_unit("square foot".to_string()), Ok(NonMetric::SquareFoot));
|
|
|
|
|
assert_eq!(parse_unit("square feet".to_string()), Ok(NonMetric::SquareFoot));
|
|
|
|
|
assert_eq!(parse_unit("square ft".to_string()), Ok(NonMetric::SquareFoot));
|
|
|
|
|
assert_eq!(parse_unit("sq foot".to_string()), Ok(NonMetric::SquareFoot));
|
|
|
|
|
assert_eq!(parse_unit("sq feet".to_string()), Ok(NonMetric::SquareFoot));
|
|
|
|
|
assert_eq!(parse_unit("sq ft".to_string()), Ok(NonMetric::SquareFoot));
|
2023-05-29 00:01:56 +00:00
|
|
|
|
assert_eq!(parse_unit("foot²".to_string()), Ok(NonMetric::SquareFoot));
|
|
|
|
|
assert_eq!(parse_unit("feet²".to_string()), Ok(NonMetric::SquareFoot));
|
|
|
|
|
assert_eq!(parse_unit("ft²".to_string()), Ok(NonMetric::SquareFoot));
|
2023-05-29 19:03:08 +00:00
|
|
|
|
assert_eq!(parse_unit("'²".to_string()), Ok(NonMetric::SquareFoot));
|
|
|
|
|
assert_eq!(parse_unit("′²".to_string()), Ok(NonMetric::SquareFoot));
|
|
|
|
|
assert_eq!(parse_unit("foot^2".to_string()), Ok(NonMetric::SquareFoot));
|
|
|
|
|
assert_eq!(parse_unit("feet^2".to_string()), Ok(NonMetric::SquareFoot));
|
|
|
|
|
assert_eq!(parse_unit("ft^2".to_string()), Ok(NonMetric::SquareFoot));
|
|
|
|
|
assert_eq!(parse_unit("'^2".to_string()), Ok(NonMetric::SquareFoot));
|
|
|
|
|
assert_eq!(parse_unit("sf".to_string()), Ok(NonMetric::SquareFoot));
|
2023-05-29 00:01:56 +00:00
|
|
|
|
|
2023-05-29 00:18:30 +00:00
|
|
|
|
assert_eq!(parse_unit("acre".to_string()), Ok(NonMetric::Acre));
|
|
|
|
|
assert_eq!(parse_unit("acres".to_string()), Ok(NonMetric::Acre));
|
|
|
|
|
assert_eq!(parse_unit("ac".to_string()), Ok(NonMetric::Acre));
|
|
|
|
|
|
2023-05-29 19:05:08 +00:00
|
|
|
|
assert_eq!(parse_unit("square mile".to_string()), Ok(NonMetric::SquareMile));
|
|
|
|
|
assert_eq!(parse_unit("square miles".to_string()), Ok(NonMetric::SquareMile));
|
|
|
|
|
assert_eq!(parse_unit("square mi".to_string()), Ok(NonMetric::SquareMile));
|
|
|
|
|
assert_eq!(parse_unit("sq mile".to_string()), Ok(NonMetric::SquareMile));
|
|
|
|
|
assert_eq!(parse_unit("sq miles".to_string()), Ok(NonMetric::SquareMile));
|
|
|
|
|
assert_eq!(parse_unit("sq mi".to_string()), Ok(NonMetric::SquareMile));
|
2023-05-29 00:27:55 +00:00
|
|
|
|
assert_eq!(parse_unit("mile²".to_string()), Ok(NonMetric::SquareMile));
|
|
|
|
|
assert_eq!(parse_unit("miles²".to_string()), Ok(NonMetric::SquareMile));
|
|
|
|
|
assert_eq!(parse_unit("mi²".to_string()), Ok(NonMetric::SquareMile));
|
2023-05-29 19:05:08 +00:00
|
|
|
|
assert_eq!(parse_unit("mile^2".to_string()), Ok(NonMetric::SquareMile));
|
|
|
|
|
assert_eq!(parse_unit("miles^2".to_string()), Ok(NonMetric::SquareMile));
|
|
|
|
|
assert_eq!(parse_unit("mi^2".to_string()), Ok(NonMetric::SquareMile));
|
2023-05-29 00:27:55 +00:00
|
|
|
|
|
2023-05-29 19:29:10 +00:00
|
|
|
|
// Volume
|
|
|
|
|
assert_eq!(parse_unit("cubic inch".to_string()), Ok(NonMetric::CubicInch));
|
|
|
|
|
assert_eq!(parse_unit("cubic inches".to_string()), Ok(NonMetric::CubicInch));
|
|
|
|
|
assert_eq!(parse_unit("cubic in".to_string()), Ok(NonMetric::CubicInch));
|
|
|
|
|
assert_eq!(parse_unit("cu inch".to_string()), Ok(NonMetric::CubicInch));
|
|
|
|
|
assert_eq!(parse_unit("cu inches".to_string()), Ok(NonMetric::CubicInch));
|
|
|
|
|
assert_eq!(parse_unit("cu in".to_string()), Ok(NonMetric::CubicInch));
|
|
|
|
|
assert_eq!(parse_unit("inch³".to_string()), Ok(NonMetric::CubicInch));
|
|
|
|
|
assert_eq!(parse_unit("inches³".to_string()), Ok(NonMetric::CubicInch));
|
|
|
|
|
assert_eq!(parse_unit("in³".to_string()), Ok(NonMetric::CubicInch));
|
|
|
|
|
assert_eq!(parse_unit("inch^3".to_string()), Ok(NonMetric::CubicInch));
|
|
|
|
|
assert_eq!(parse_unit("inches^3".to_string()), Ok(NonMetric::CubicInch));
|
|
|
|
|
assert_eq!(parse_unit("in^3".to_string()), Ok(NonMetric::CubicInch));
|
|
|
|
|
|
2023-05-29 19:40:03 +00:00
|
|
|
|
assert_eq!(parse_unit("cubic foot".to_string()), Ok(NonMetric::CubicFoot));
|
|
|
|
|
assert_eq!(parse_unit("cubic feet".to_string()), Ok(NonMetric::CubicFoot));
|
|
|
|
|
assert_eq!(parse_unit("cubic ft".to_string()), Ok(NonMetric::CubicFoot));
|
|
|
|
|
assert_eq!(parse_unit("cu foot".to_string()), Ok(NonMetric::CubicFoot));
|
|
|
|
|
assert_eq!(parse_unit("cu feet".to_string()), Ok(NonMetric::CubicFoot));
|
|
|
|
|
assert_eq!(parse_unit("cu ft".to_string()), Ok(NonMetric::CubicFoot));
|
|
|
|
|
assert_eq!(parse_unit("foot³".to_string()), Ok(NonMetric::CubicFoot));
|
|
|
|
|
assert_eq!(parse_unit("feet³".to_string()), Ok(NonMetric::CubicFoot));
|
|
|
|
|
assert_eq!(parse_unit("ft³".to_string()), Ok(NonMetric::CubicFoot));
|
|
|
|
|
assert_eq!(parse_unit("foot^3".to_string()), Ok(NonMetric::CubicFoot));
|
|
|
|
|
assert_eq!(parse_unit("feet^3".to_string()), Ok(NonMetric::CubicFoot));
|
|
|
|
|
assert_eq!(parse_unit("ft^3".to_string()), Ok(NonMetric::CubicFoot));
|
|
|
|
|
|
2023-05-30 17:01:35 +00:00
|
|
|
|
// Fluid volume
|
2023-05-30 17:34:38 +00:00
|
|
|
|
assert_eq!(parse_unit("imperial fluid ounce".to_string()), Ok(NonMetric::ImperialFluidOunce));
|
|
|
|
|
assert_eq!(parse_unit("imperial fluid ounces".to_string()), Ok(NonMetric::ImperialFluidOunce));
|
|
|
|
|
assert_eq!(parse_unit("imp fl oz".to_string()), Ok(NonMetric::ImperialFluidOunce));
|
|
|
|
|
assert_eq!(parse_unit("imp fl. oz.".to_string()), Ok(NonMetric::ImperialFluidOunce));
|
|
|
|
|
assert_eq!(parse_unit("imp oz. fl.".to_string()), Ok(NonMetric::ImperialFluidOunce));
|
|
|
|
|
|
2023-05-31 14:21:41 +00:00
|
|
|
|
assert_eq!(parse_unit("imperial pint".to_string()), Ok(NonMetric::ImperialPint));
|
|
|
|
|
assert_eq!(parse_unit("imperial pints".to_string()), Ok(NonMetric::ImperialPint));
|
|
|
|
|
assert_eq!(parse_unit("imp pt".to_string()), Ok(NonMetric::ImperialPint));
|
2023-05-31 14:43:49 +00:00
|
|
|
|
assert_eq!(parse_unit("imp p".to_string()), Ok(NonMetric::ImperialPint));
|
2023-05-31 14:21:41 +00:00
|
|
|
|
|
|
|
|
|
assert_eq!(parse_unit("imperial quart".to_string()), Ok(NonMetric::ImperialQuart));
|
|
|
|
|
assert_eq!(parse_unit("imperial quarts".to_string()), Ok(NonMetric::ImperialQuart));
|
|
|
|
|
assert_eq!(parse_unit("imp qt".to_string()), Ok(NonMetric::ImperialQuart));
|
|
|
|
|
|
|
|
|
|
assert_eq!(parse_unit("imperial gallon".to_string()), Ok(NonMetric::ImperialGallon));
|
|
|
|
|
assert_eq!(parse_unit("imperial gallons".to_string()), Ok(NonMetric::ImperialGallon));
|
|
|
|
|
assert_eq!(parse_unit("imp gal".to_string()), Ok(NonMetric::ImperialGallon));
|
|
|
|
|
|
2023-05-31 19:38:50 +00:00
|
|
|
|
assert_eq!(parse_unit("US teaspoon".to_string()), Ok(NonMetric::USTeaspoon));
|
|
|
|
|
assert_eq!(parse_unit("US teaspoons".to_string()), Ok(NonMetric::USTeaspoon));
|
|
|
|
|
assert_eq!(parse_unit("US tsp.".to_string()), Ok(NonMetric::USTeaspoon));
|
|
|
|
|
assert_eq!(parse_unit("US tsp".to_string()), Ok(NonMetric::USTeaspoon));
|
|
|
|
|
assert_eq!(parse_unit("us teaspoon".to_string()), Ok(NonMetric::USTeaspoon));
|
|
|
|
|
assert_eq!(parse_unit("us teaspoons".to_string()), Ok(NonMetric::USTeaspoon));
|
|
|
|
|
assert_eq!(parse_unit("us tsp.".to_string()), Ok(NonMetric::USTeaspoon));
|
|
|
|
|
assert_eq!(parse_unit("us tsp".to_string()), Ok(NonMetric::USTeaspoon));
|
|
|
|
|
assert_eq!(parse_unit("teaspoon".to_string()), Ok(NonMetric::USTeaspoon));
|
|
|
|
|
assert_eq!(parse_unit("teaspoons".to_string()), Ok(NonMetric::USTeaspoon));
|
|
|
|
|
assert_eq!(parse_unit("tsp.".to_string()), Ok(NonMetric::USTeaspoon));
|
|
|
|
|
assert_eq!(parse_unit("tsp".to_string()), Ok(NonMetric::USTeaspoon));
|
|
|
|
|
|
2023-05-31 19:29:58 +00:00
|
|
|
|
assert_eq!(parse_unit("US tablespoon".to_string()), Ok(NonMetric::USTablespoon));
|
|
|
|
|
assert_eq!(parse_unit("US tablespoons".to_string()), Ok(NonMetric::USTablespoon));
|
|
|
|
|
assert_eq!(parse_unit("US Tbsp.".to_string()), Ok(NonMetric::USTablespoon));
|
|
|
|
|
assert_eq!(parse_unit("US Tbsp".to_string()), Ok(NonMetric::USTablespoon));
|
|
|
|
|
assert_eq!(parse_unit("us tablespoon".to_string()), Ok(NonMetric::USTablespoon));
|
|
|
|
|
assert_eq!(parse_unit("us tablespoons".to_string()), Ok(NonMetric::USTablespoon));
|
|
|
|
|
assert_eq!(parse_unit("us tbsp.".to_string()), Ok(NonMetric::USTablespoon));
|
|
|
|
|
assert_eq!(parse_unit("us tbsp".to_string()), Ok(NonMetric::USTablespoon));
|
|
|
|
|
assert_eq!(parse_unit("tablespoon".to_string()), Ok(NonMetric::USTablespoon));
|
|
|
|
|
assert_eq!(parse_unit("tablespoons".to_string()), Ok(NonMetric::USTablespoon));
|
|
|
|
|
assert_eq!(parse_unit("Tbsp.".to_string()), Ok(NonMetric::USTablespoon));
|
|
|
|
|
assert_eq!(parse_unit("Tbsp".to_string()), Ok(NonMetric::USTablespoon));
|
|
|
|
|
assert_eq!(parse_unit("tbsp.".to_string()), Ok(NonMetric::USTablespoon));
|
|
|
|
|
assert_eq!(parse_unit("tbsp".to_string()), Ok(NonMetric::USTablespoon));
|
|
|
|
|
|
2023-05-31 19:19:55 +00:00
|
|
|
|
assert_eq!(parse_unit("US fluid ounce".to_string()), Ok(NonMetric::USFluidOunce));
|
|
|
|
|
assert_eq!(parse_unit("US fluid ounces".to_string()), Ok(NonMetric::USFluidOunce));
|
|
|
|
|
assert_eq!(parse_unit("US fl oz".to_string()), Ok(NonMetric::USFluidOunce));
|
|
|
|
|
assert_eq!(parse_unit("US fl. oz.".to_string()), Ok(NonMetric::USFluidOunce));
|
|
|
|
|
assert_eq!(parse_unit("US oz. fl.".to_string()), Ok(NonMetric::USFluidOunce));
|
|
|
|
|
assert_eq!(parse_unit("us fluid ounce".to_string()), Ok(NonMetric::USFluidOunce));
|
|
|
|
|
assert_eq!(parse_unit("us fluid ounces".to_string()), Ok(NonMetric::USFluidOunce));
|
|
|
|
|
assert_eq!(parse_unit("us fl oz".to_string()), Ok(NonMetric::USFluidOunce));
|
|
|
|
|
assert_eq!(parse_unit("us fl. oz.".to_string()), Ok(NonMetric::USFluidOunce));
|
|
|
|
|
assert_eq!(parse_unit("us oz. fl.".to_string()), Ok(NonMetric::USFluidOunce));
|
|
|
|
|
|
2023-05-31 19:10:49 +00:00
|
|
|
|
assert_eq!(parse_unit("US cup".to_string()), Ok(NonMetric::USCup));
|
|
|
|
|
assert_eq!(parse_unit("US cups".to_string()), Ok(NonMetric::USCup));
|
|
|
|
|
assert_eq!(parse_unit("us cup".to_string()), Ok(NonMetric::USCup));
|
|
|
|
|
assert_eq!(parse_unit("us cups".to_string()), Ok(NonMetric::USCup));
|
|
|
|
|
|
2023-05-31 14:49:24 +00:00
|
|
|
|
assert_eq!(parse_unit("US liquid pint".to_string()), Ok(NonMetric::USLiquidPint));
|
|
|
|
|
assert_eq!(parse_unit("US liquid pints".to_string()), Ok(NonMetric::USLiquidPint));
|
|
|
|
|
assert_eq!(parse_unit("US pint".to_string()), Ok(NonMetric::USLiquidPint));
|
|
|
|
|
assert_eq!(parse_unit("US pints".to_string()), Ok(NonMetric::USLiquidPint));
|
|
|
|
|
assert_eq!(parse_unit("US pt".to_string()), Ok(NonMetric::USLiquidPint));
|
|
|
|
|
assert_eq!(parse_unit("US p".to_string()), Ok(NonMetric::USLiquidPint));
|
|
|
|
|
assert_eq!(parse_unit("us liquid pint".to_string()), Ok(NonMetric::USLiquidPint));
|
|
|
|
|
assert_eq!(parse_unit("us liquid pints".to_string()), Ok(NonMetric::USLiquidPint));
|
|
|
|
|
assert_eq!(parse_unit("us pint".to_string()), Ok(NonMetric::USLiquidPint));
|
|
|
|
|
assert_eq!(parse_unit("us pints".to_string()), Ok(NonMetric::USLiquidPint));
|
|
|
|
|
assert_eq!(parse_unit("us pt".to_string()), Ok(NonMetric::USLiquidPint));
|
|
|
|
|
assert_eq!(parse_unit("us p".to_string()), Ok(NonMetric::USLiquidPint));
|
|
|
|
|
|
2023-05-31 14:42:18 +00:00
|
|
|
|
assert_eq!(parse_unit("US liquid quart".to_string()), Ok(NonMetric::USLiquidQuart));
|
|
|
|
|
assert_eq!(parse_unit("US liquid quarts".to_string()), Ok(NonMetric::USLiquidQuart));
|
|
|
|
|
assert_eq!(parse_unit("US quart".to_string()), Ok(NonMetric::USLiquidQuart));
|
|
|
|
|
assert_eq!(parse_unit("US quarts".to_string()), Ok(NonMetric::USLiquidQuart));
|
|
|
|
|
assert_eq!(parse_unit("US qt".to_string()), Ok(NonMetric::USLiquidQuart));
|
|
|
|
|
assert_eq!(parse_unit("us liquid quart".to_string()), Ok(NonMetric::USLiquidQuart));
|
|
|
|
|
assert_eq!(parse_unit("us liquid quarts".to_string()), Ok(NonMetric::USLiquidQuart));
|
|
|
|
|
assert_eq!(parse_unit("us quart".to_string()), Ok(NonMetric::USLiquidQuart));
|
|
|
|
|
assert_eq!(parse_unit("us quarts".to_string()), Ok(NonMetric::USLiquidQuart));
|
|
|
|
|
assert_eq!(parse_unit("us qt".to_string()), Ok(NonMetric::USLiquidQuart));
|
|
|
|
|
|
2023-05-31 14:35:19 +00:00
|
|
|
|
assert_eq!(parse_unit("US gallon".to_string()), Ok(NonMetric::USGallon));
|
|
|
|
|
assert_eq!(parse_unit("US gallons".to_string()), Ok(NonMetric::USGallon));
|
|
|
|
|
assert_eq!(parse_unit("US gal".to_string()), Ok(NonMetric::USGallon));
|
|
|
|
|
assert_eq!(parse_unit("us gallon".to_string()), Ok(NonMetric::USGallon));
|
|
|
|
|
assert_eq!(parse_unit("us gallons".to_string()), Ok(NonMetric::USGallon));
|
|
|
|
|
assert_eq!(parse_unit("us gal".to_string()), Ok(NonMetric::USGallon));
|
2023-05-31 20:19:57 +00:00
|
|
|
|
}
|
2023-05-31 14:35:19 +00:00
|
|
|
|
|
2023-05-31 20:19:57 +00:00
|
|
|
|
#[test]
|
|
|
|
|
fn ambiguous_units() {
|
2023-05-31 23:18:55 +00:00
|
|
|
|
test_ambiguous_units(NonMetric::ImperialFluidOunce, NonMetric::USFluidOunce, &[
|
2023-05-31 20:19:57 +00:00
|
|
|
|
"fluid ounce",
|
|
|
|
|
"fluid ounces",
|
|
|
|
|
"fl oz",
|
|
|
|
|
"fl. oz.",
|
|
|
|
|
"oz. fl.",
|
2023-05-31 23:18:55 +00:00
|
|
|
|
]);
|
2023-05-31 20:19:57 +00:00
|
|
|
|
|
2023-05-31 23:18:55 +00:00
|
|
|
|
test_ambiguous_units(NonMetric::ImperialPint, NonMetric::USLiquidPint, &[
|
2023-05-31 20:19:57 +00:00
|
|
|
|
"pint",
|
|
|
|
|
"pints",
|
|
|
|
|
"pt",
|
|
|
|
|
"p",
|
2023-05-31 23:18:55 +00:00
|
|
|
|
]);
|
2023-05-31 20:19:57 +00:00
|
|
|
|
|
2023-05-31 23:18:55 +00:00
|
|
|
|
test_ambiguous_units(NonMetric::ImperialQuart, NonMetric::USLiquidQuart, &[
|
2023-05-31 20:19:57 +00:00
|
|
|
|
"quart",
|
|
|
|
|
"quarts",
|
|
|
|
|
"qt",
|
2023-05-31 23:18:55 +00:00
|
|
|
|
]);
|
2023-05-31 20:19:57 +00:00
|
|
|
|
|
2023-05-31 23:18:55 +00:00
|
|
|
|
test_ambiguous_units(NonMetric::ImperialGallon, NonMetric::USGallon, &[
|
2023-05-31 20:19:57 +00:00
|
|
|
|
"gallon",
|
|
|
|
|
"gallons",
|
|
|
|
|
"gal",
|
2023-05-31 23:18:55 +00:00
|
|
|
|
]);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn test_ambiguous_units(unit1: NonMetric, unit2: NonMetric, spellings: &[&str]) {
|
|
|
|
|
for spelling in spellings {
|
|
|
|
|
let parsed = parse_unit(spelling.to_string());
|
|
|
|
|
if let Err(ParseError::AmbiguousUnit(unit_name, prefix1, prefix2)) = parsed {
|
|
|
|
|
assert_eq!(&unit_name, spelling);
|
|
|
|
|
let suggestion1 = format!("{prefix1} {unit_name}");
|
|
|
|
|
let suggestion2 = format!("{prefix2} {unit_name}");
|
|
|
|
|
assert_eq!(parse_unit(suggestion1), Ok(unit1));
|
|
|
|
|
assert_eq!(parse_unit(suggestion2), Ok(unit2));
|
2023-05-31 20:19:57 +00:00
|
|
|
|
} else {
|
2023-05-31 23:18:55 +00:00
|
|
|
|
panic!("units passed to test_ambiguous_units() must be ambiguous");
|
2023-05-31 20:19:57 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn unknown_unit() {
|
2023-05-28 15:22:49 +00:00
|
|
|
|
assert_eq!(parse_unit("hutenosa".to_string()), Err(ParseError::UnknownUnit("hutenosa".to_string())));
|
2023-05-14 00:04:47 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn tokens() {
|
|
|
|
|
assert_eq!(tokenize(""), vec![]);
|
|
|
|
|
assert_eq!(tokenize("10"), vec![Token::Number("10".to_string())]);
|
|
|
|
|
assert_eq!(tokenize(" 10 "), vec![Token::Number("10".to_string())]);
|
|
|
|
|
assert_eq!(tokenize("10 000"), vec![Token::Number("10 000".to_string())]);
|
2023-05-28 15:07:28 +00:00
|
|
|
|
assert_eq!(tokenize("10\t000"), vec![Token::Number("10\t000".to_string())]);
|
|
|
|
|
assert_eq!(tokenize("10\u{1680}000"), vec![Token::Number("10\u{1680}000".to_string())]);
|
2023-05-14 00:04:47 +00:00
|
|
|
|
assert_eq!(tokenize("10.0.1"), vec![Token::Number("10.0.1".to_string())]);
|
|
|
|
|
assert_eq!(tokenize("ft"), vec![Token::Unit("ft".to_string())]);
|
|
|
|
|
assert_eq!(
|
|
|
|
|
tokenize("10 ft"),
|
|
|
|
|
vec![
|
|
|
|
|
Token::Number("10".to_string()),
|
|
|
|
|
Token::Unit("ft".to_string()),
|
|
|
|
|
]
|
|
|
|
|
);
|
|
|
|
|
assert_eq!(
|
|
|
|
|
tokenize("5 ft 7 in"),
|
|
|
|
|
vec![
|
|
|
|
|
Token::Number("5".to_string()),
|
|
|
|
|
Token::Unit("ft".to_string()),
|
|
|
|
|
Token::Number("7".to_string()),
|
|
|
|
|
Token::Unit("in".to_string()),
|
|
|
|
|
]
|
|
|
|
|
);
|
|
|
|
|
assert_eq!(
|
|
|
|
|
tokenize("5\"7'"),
|
|
|
|
|
vec![
|
|
|
|
|
Token::Number("5".to_string()),
|
|
|
|
|
Token::Unit("\"".to_string()),
|
|
|
|
|
Token::Number("7".to_string()),
|
|
|
|
|
Token::Unit("'".to_string()),
|
|
|
|
|
]
|
|
|
|
|
);
|
|
|
|
|
assert_eq!(
|
|
|
|
|
tokenize(" 2.2lbs "),
|
|
|
|
|
vec![
|
|
|
|
|
Token::Number("2.2".to_string()),
|
|
|
|
|
Token::Unit("lbs".to_string()),
|
|
|
|
|
]
|
|
|
|
|
);
|
2023-05-29 18:43:02 +00:00
|
|
|
|
assert_eq!(
|
|
|
|
|
tokenize("sq ft"),
|
|
|
|
|
vec![
|
|
|
|
|
Token::Unit("sq ft".to_string()),
|
|
|
|
|
]
|
|
|
|
|
);
|
|
|
|
|
assert_eq!(
|
|
|
|
|
tokenize("sq ft2"),
|
|
|
|
|
vec![
|
|
|
|
|
Token::Unit("sq ft".to_string()),
|
|
|
|
|
Token::Number("2".to_string()),
|
|
|
|
|
]
|
|
|
|
|
);
|
2023-05-29 18:48:45 +00:00
|
|
|
|
assert_eq!(
|
|
|
|
|
tokenize("ft^2"),
|
|
|
|
|
vec![
|
|
|
|
|
Token::Unit("ft^2".to_string()),
|
|
|
|
|
]
|
|
|
|
|
);
|
|
|
|
|
assert_eq!(
|
|
|
|
|
tokenize("ft^22"),
|
|
|
|
|
vec![
|
|
|
|
|
Token::Unit("ft^22".to_string()),
|
|
|
|
|
]
|
|
|
|
|
);
|
|
|
|
|
assert_eq!(
|
|
|
|
|
tokenize("ft^2 2"),
|
|
|
|
|
vec![
|
|
|
|
|
Token::Unit("ft^2".to_string()),
|
|
|
|
|
Token::Number("2".to_string()),
|
|
|
|
|
]
|
|
|
|
|
);
|
|
|
|
|
assert_eq!(
|
|
|
|
|
tokenize("ft^2 s^-1 lb 2"),
|
|
|
|
|
vec![
|
|
|
|
|
Token::Unit("ft^2 s^-1 lb".to_string()),
|
|
|
|
|
Token::Number("2".to_string()),
|
|
|
|
|
]
|
|
|
|
|
);
|
2023-05-14 00:04:47 +00:00
|
|
|
|
}
|
|
|
|
|
}
|