Compare commits
6 Commits
c79584d807
...
2b449c80f5
Author | SHA1 | Date |
---|---|---|
Juhani Krekelä | 2b449c80f5 | |
Juhani Krekelä | e915a3f689 | |
Juhani Krekelä | da620a04aa | |
Juhani Krekelä | 7d82e46fd7 | |
Juhani Krekelä | 034ffed997 | |
Juhani Krekelä | 7170ac3240 |
|
@ -1,5 +1,12 @@
|
|||
use crate::units::{Metric, MetricQuantity, NonMetric, NonMetricQuantity};
|
||||
|
||||
pub fn convert(from: NonMetricQuantity) -> MetricQuantity {
|
||||
let conversion = get_conversion(from.unit);
|
||||
let amount = from.amount * conversion.to.amount / conversion.from;
|
||||
let unit = conversion.to.unit;
|
||||
MetricQuantity { amount, unit }
|
||||
}
|
||||
|
||||
struct Conversion {
|
||||
from: f64,
|
||||
to: MetricQuantity,
|
||||
|
@ -14,14 +21,14 @@ fn get_conversion(unit: NonMetric) -> Conversion {
|
|||
|
||||
match unit {
|
||||
// Length
|
||||
NonMetric::Foot => Conversion {
|
||||
from: inch_from,
|
||||
to: MetricQuantity { amount: 12.0 * inch_to, unit: Metric::Metre },
|
||||
},
|
||||
NonMetric::Inch => Conversion {
|
||||
from: inch_from,
|
||||
to: MetricQuantity { amount: inch_to, unit: Metric::Metre },
|
||||
},
|
||||
NonMetric::Foot => Conversion {
|
||||
from: inch_from,
|
||||
to: MetricQuantity { amount: 12.0 * inch_to, unit: Metric::Metre },
|
||||
},
|
||||
NonMetric::Yard => Conversion {
|
||||
from: inch_from,
|
||||
to: MetricQuantity { amount: 3.0 * 12.0 * inch_to, unit: Metric::Metre },
|
||||
|
@ -46,33 +53,12 @@ fn get_conversion(unit: NonMetric) -> Conversion {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn convert(from: NonMetricQuantity) -> MetricQuantity {
|
||||
let conversion = get_conversion(from.unit);
|
||||
let amount = from.amount * conversion.to.amount / conversion.from;
|
||||
let unit = conversion.to.unit;
|
||||
MetricQuantity { amount, unit }
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
||||
struct Test(NonMetric, f64);
|
||||
|
||||
fn run_tests(tests: &[Test], unit: Metric) {
|
||||
for test in tests {
|
||||
let from = NonMetricQuantity {
|
||||
amount: 1.0,
|
||||
unit: test.0,
|
||||
};
|
||||
let to = MetricQuantity {
|
||||
amount: test.1,
|
||||
unit: unit,
|
||||
};
|
||||
assert_eq!(convert(from), to);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn length() {
|
||||
let tests = [
|
||||
|
@ -93,4 +79,18 @@ mod test {
|
|||
];
|
||||
run_tests(&tests, Metric::Gram);
|
||||
}
|
||||
|
||||
fn run_tests(tests: &[Test], unit: Metric) {
|
||||
for test in tests {
|
||||
let from = NonMetricQuantity {
|
||||
amount: 1.0,
|
||||
unit: test.0,
|
||||
};
|
||||
let to = MetricQuantity {
|
||||
amount: test.1,
|
||||
unit: unit,
|
||||
};
|
||||
assert_eq!(convert(from), to);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,112 @@
|
|||
use crate::units::{Metric, MetricQuantity};
|
||||
|
||||
pub fn format(quantity: MetricQuantity) -> String {
|
||||
let (amount, prefix) = si_prefix(quantity.amount);
|
||||
let unit = abbreviation(quantity.unit);
|
||||
|
||||
format!("{amount} {prefix}{unit}")
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
struct SiPrefix {
|
||||
prefix: &'static str,
|
||||
size: f64,
|
||||
}
|
||||
|
||||
const SI_PREFIXES: [SiPrefix; 16] = [
|
||||
SiPrefix { prefix: "z", size: 0.000_000_000_000_000_000_001 },
|
||||
SiPrefix { prefix: "a", size: 0.000_000_000_000_000_001 },
|
||||
SiPrefix { prefix: "f", size: 0.000_000_000_000_001 },
|
||||
SiPrefix { prefix: "p", size: 0.000_000_000_001 },
|
||||
SiPrefix { prefix: "n", size: 0.000_000_001 },
|
||||
SiPrefix { prefix: "µ", size: 0.000_001 },
|
||||
SiPrefix { prefix: "m", size: 0.001 },
|
||||
SiPrefix { prefix: "c", size: 0.01 }, // Included for cm
|
||||
SiPrefix { prefix: "", size: 1.0 },
|
||||
SiPrefix { prefix: "k", size: 1_000.0 },
|
||||
SiPrefix { prefix: "M", size: 1_000_000.0 },
|
||||
SiPrefix { prefix: "G", size: 1_000_000_000.0 },
|
||||
SiPrefix { prefix: "T", size: 1_000_000_000_000.0 },
|
||||
SiPrefix { prefix: "P", size: 1_000_000_000_000_000.0 },
|
||||
SiPrefix { prefix: "E", size: 1_000_000_000_000_000_000.0 },
|
||||
SiPrefix { prefix: "Z", size: 1_000_000_000_000_000_000_000.0 },
|
||||
// Yotta and above cannot be represented exactly as a f64
|
||||
];
|
||||
|
||||
fn si_prefix(amount: f64) -> (f64, &'static str) {
|
||||
let absolute = amount.abs();
|
||||
if absolute < SI_PREFIXES[0].size {
|
||||
let prefix = SI_PREFIXES[0];
|
||||
return (amount / prefix.size, prefix.prefix);
|
||||
}
|
||||
if absolute >= SI_PREFIXES[SI_PREFIXES.len() - 1].size {
|
||||
let prefix = SI_PREFIXES[SI_PREFIXES.len() - 1];
|
||||
return (amount / prefix.size, prefix.prefix);
|
||||
}
|
||||
|
||||
// Find the correct prefix SI_PREFIX[index] such that:
|
||||
// SI_PREFIXES[index].size ≤ absolute < SI_PREFIXES[index + 1].size
|
||||
for index in 0..SI_PREFIXES.len() {
|
||||
if SI_PREFIXES[index].size <= absolute &&
|
||||
absolute < SI_PREFIXES[index + 1].size {
|
||||
let prefix = SI_PREFIXES[index];
|
||||
return (amount / prefix.size, prefix.prefix);
|
||||
}
|
||||
}
|
||||
|
||||
unreachable!();
|
||||
}
|
||||
|
||||
fn abbreviation(unit: Metric) -> &'static str {
|
||||
match unit {
|
||||
Metric::Metre => "m",
|
||||
Metric::Gram => "g",
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn quantities() {
|
||||
assert_eq!("1 m", &format(MetricQuantity {
|
||||
amount: 1.0,
|
||||
unit: Metric::Metre,
|
||||
}));
|
||||
assert_eq!("5 kg", &format(MetricQuantity {
|
||||
amount: 5_000.0,
|
||||
unit: Metric::Gram,
|
||||
}));
|
||||
assert_eq!("25.5 cm", &format(MetricQuantity {
|
||||
amount: 0.255,
|
||||
unit: Metric::Metre,
|
||||
}));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn prefixes() {
|
||||
assert_eq!(si_prefix(0.000_000_000_000_000_000_0005), (0.5, "z"));
|
||||
assert_eq!(si_prefix(0.000_000_000_000_000_000_001), (1.0, "z"));
|
||||
assert_eq!(si_prefix(0.000_000_000_000_000_000_01), (10.0, "z"));
|
||||
assert_eq!(si_prefix(0.000_000_000_000_000_00_01), (100.0, "z"));
|
||||
assert_eq!(si_prefix(0.000_000_000_000_000_001), (1.0, "a"));
|
||||
assert_eq!(si_prefix(0.000_000_000_000_001), (1.0, "f"));
|
||||
assert_eq!(si_prefix(0.000_000_000_001), (1.0, "p"));
|
||||
assert_eq!(si_prefix(0.000_000_001), (1.0, "n"));
|
||||
assert_eq!(si_prefix(0.000_001), (1.0, "µ"));
|
||||
assert_eq!(si_prefix(0.001), (1.0, "m"));
|
||||
assert_eq!(si_prefix(0.01), (1.0, "c"));
|
||||
assert_eq!(si_prefix(1.0), (1.0, ""));
|
||||
assert_eq!(si_prefix(1_000.0), (1.0, "k"));
|
||||
assert_eq!(si_prefix(1_000_000.0), (1.0, "M"));
|
||||
assert_eq!(si_prefix(1_000_000_000.0), (1.0, "G"));
|
||||
assert_eq!(si_prefix(1_000_000_000_000.0), (1.0, "T"));
|
||||
assert_eq!(si_prefix(1_000_000_000_000_000.0), (1.0, "P"));
|
||||
assert_eq!(si_prefix(1_000_000_000_000_000_000.0), (1.0, "E"));
|
||||
assert_eq!(si_prefix(10_000_000_000_000_000_000.0), (10.0, "E"));
|
||||
assert_eq!(si_prefix(100_000_000_000_000_000_000.0), (100.0, "E"));
|
||||
assert_eq!(si_prefix(1_000_000_000_000_000_000_000.0), (1.0, "Z"));
|
||||
assert_eq!(si_prefix(2_000_000_000_000_000_000_000.0), (2.0, "Z"));
|
||||
}
|
||||
}
|
89
src/lib.rs
89
src/lib.rs
|
@ -1,6 +1,89 @@
|
|||
mod units;
|
||||
mod conversions;
|
||||
mod format;
|
||||
mod parse;
|
||||
mod units;
|
||||
|
||||
pub use units::{NonMetric, NonMetricQuantity};
|
||||
use conversions::convert;
|
||||
use format::format;
|
||||
use parse::{parse, ParseError};
|
||||
use units::{MetricQuantity, NonMetric};
|
||||
|
||||
pub use conversions::convert;
|
||||
pub fn run(input: &str) -> Result<String, String> {
|
||||
let non_metric = match parse(input) {
|
||||
Ok(non_metric) => non_metric,
|
||||
Err(ParseError::NotValidNumber(string)) => {
|
||||
return Err(format!("Not a valid number: {string}"));
|
||||
}
|
||||
Err(ParseError::UnexpectedUnit(unit)) => {
|
||||
return Err(format!("Unexpected unit: {unit}"));
|
||||
}
|
||||
Err(ParseError::UnknownUnit(unit)) => {
|
||||
return Err(format!("Unknown unit: {unit}"));
|
||||
}
|
||||
Err(ParseError::ExpectedUnit) => {
|
||||
return Err("Expected a unit".to_string());
|
||||
}
|
||||
};
|
||||
|
||||
if non_metric.len() == 0 {
|
||||
return Err("Expected quantity or quantities to convert".to_string());
|
||||
}
|
||||
|
||||
let metric: Vec<MetricQuantity> = non_metric.clone().into_iter().map(convert).collect();
|
||||
|
||||
// Make sure the results of the conversions can be summed together
|
||||
// This is the case if the units after conversion are the same
|
||||
let mut metric_unit = None;
|
||||
for index in 0..metric.len() {
|
||||
match metric_unit {
|
||||
Some(metric_unit) => {
|
||||
if metric[index].unit != metric_unit {
|
||||
let first_unit_name = unit_to_name(non_metric[0].unit);
|
||||
let unit_name = unit_to_name(non_metric[index].unit);
|
||||
return Err(format!("Incompatible units: {first_unit_name}, {unit_name}"));
|
||||
}
|
||||
}
|
||||
None => {
|
||||
metric_unit = Some(metric[index].unit);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let amount = metric.into_iter().map(|quantity| { quantity.amount }).sum();
|
||||
|
||||
let quantity = MetricQuantity {
|
||||
amount: amount,
|
||||
unit: metric_unit.expect("we must have at least one quantity by this point"),
|
||||
};
|
||||
|
||||
Ok(format(quantity))
|
||||
}
|
||||
|
||||
fn unit_to_name(unit: NonMetric) -> &'static str {
|
||||
match unit {
|
||||
// Length
|
||||
NonMetric::Inch => "inches",
|
||||
NonMetric::Foot => "feet",
|
||||
NonMetric::Yard => "yards",
|
||||
NonMetric::Mile => "miles",
|
||||
// Weight
|
||||
NonMetric::Ounce => "ounces",
|
||||
NonMetric::Pound => "pounds",
|
||||
NonMetric::Stone => "stones",
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn errors() {
|
||||
assert_eq!(run("1.0."), Err("Not a valid number: 1.0.".to_string()));
|
||||
assert_eq!(run("ft"), Err("Unexpected unit: ft".to_string()));
|
||||
assert_eq!(run("1 tf"), Err("Unknown unit: tf".to_string()));
|
||||
assert_eq!(run("1"), Err("Expected a unit".to_string()));
|
||||
assert_eq!(run(""), Err("Expected quantity or quantities to convert".to_string()));
|
||||
assert_eq!(run("6 ft 1 lbs"), Err("Incompatible units: feet, pounds".to_string()));
|
||||
}
|
||||
}
|
||||
|
|
40
src/main.rs
40
src/main.rs
|
@ -1,7 +1,39 @@
|
|||
use metrify::{NonMetric, NonMetricQuantity};
|
||||
use metrify::convert;
|
||||
use metrify::run;
|
||||
use std::env;
|
||||
use std::io;
|
||||
use std::io::Write;
|
||||
use std::process;
|
||||
|
||||
fn main() {
|
||||
let quantity = NonMetricQuantity { amount: 6.0, unit: NonMetric::Foot };
|
||||
dbg!(convert(quantity));
|
||||
let args: Vec<String> = env::args().collect();
|
||||
let name = args[0].clone();
|
||||
let args = args[1..].join(" ");
|
||||
|
||||
let mut input = args;
|
||||
|
||||
if input.len() == 0 {
|
||||
print!("> ");
|
||||
match io::stdout().flush() {
|
||||
Ok(_) => {}
|
||||
Err(err) => {
|
||||
eprintln!("{name}: Error: {err}");
|
||||
process::exit(1);
|
||||
}
|
||||
}
|
||||
match io::stdin().read_line(&mut input) {
|
||||
Ok(_) => {}
|
||||
Err(err) => {
|
||||
eprintln!("{name}: Error: {err}");
|
||||
process::exit(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
match run(&input) {
|
||||
Ok(str) => println!("{str}"),
|
||||
Err(err) => {
|
||||
eprintln!("{name}: Error: {err}");
|
||||
process::exit(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,288 @@
|
|||
use crate::units::{NonMetric, NonMetricQuantity};
|
||||
|
||||
enum Expect {
|
||||
Number,
|
||||
Unit,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub enum ParseError {
|
||||
NotValidNumber(String),
|
||||
UnexpectedUnit(String),
|
||||
UnknownUnit(String),
|
||||
ExpectedUnit,
|
||||
}
|
||||
|
||||
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)) => {
|
||||
let number = match number.trim().parse() {
|
||||
Ok(number) => number,
|
||||
Err(_) => {
|
||||
return Err(ParseError::NotValidNumber(number));
|
||||
}
|
||||
};
|
||||
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)) => {
|
||||
let unit = match parse_unit(&unit) {
|
||||
Some(unit) => unit,
|
||||
None => {
|
||||
return Err(ParseError::UnknownUnit(unit));
|
||||
}
|
||||
};
|
||||
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 {
|
||||
Expect::Number => {},
|
||||
Expect::Unit => {
|
||||
return Err(ParseError::ExpectedUnit);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(quantities)
|
||||
}
|
||||
|
||||
fn parse_unit(input: &str) -> Option<NonMetric> {
|
||||
match input {
|
||||
// Length
|
||||
"inch" => Some(NonMetric::Inch),
|
||||
"inches" => Some(NonMetric::Inch),
|
||||
"in" => Some(NonMetric::Inch),
|
||||
"\"" => Some(NonMetric::Inch),
|
||||
"″" => Some(NonMetric::Inch),
|
||||
|
||||
"foot" => Some(NonMetric::Foot),
|
||||
"feet" => Some(NonMetric::Foot),
|
||||
"ft" => Some(NonMetric::Foot),
|
||||
"'" => Some(NonMetric::Foot),
|
||||
"′" => Some(NonMetric::Foot),
|
||||
|
||||
"yard" => Some(NonMetric::Yard),
|
||||
"yards" => Some(NonMetric::Yard),
|
||||
"yd" => Some(NonMetric::Yard),
|
||||
|
||||
"mile" => Some(NonMetric::Mile),
|
||||
"miles" => Some(NonMetric::Mile),
|
||||
"mi" => Some(NonMetric::Mile),
|
||||
"m" => Some(NonMetric::Mile),
|
||||
|
||||
// Weight
|
||||
"ounce" => Some(NonMetric::Ounce),
|
||||
"ounces" => Some(NonMetric::Ounce),
|
||||
"oz" => Some(NonMetric::Ounce),
|
||||
|
||||
"pound" => Some(NonMetric::Pound),
|
||||
"pounds" => Some(NonMetric::Pound),
|
||||
"lb" => Some(NonMetric::Pound),
|
||||
"lbs" => Some(NonMetric::Pound),
|
||||
"#" => Some(NonMetric::Pound),
|
||||
|
||||
"stone" => Some(NonMetric::Stone),
|
||||
"stones" => Some(NonMetric::Stone),
|
||||
"st" => Some(NonMetric::Stone),
|
||||
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
enum Token {
|
||||
Number(String),
|
||||
Unit(String),
|
||||
}
|
||||
|
||||
enum TokState {
|
||||
Neutral,
|
||||
Number,
|
||||
Unit,
|
||||
}
|
||||
|
||||
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);
|
||||
state = TokState::Unit;
|
||||
}
|
||||
}
|
||||
TokState::Number => {
|
||||
if c.is_ascii_digit() ||
|
||||
c.is_whitespace() ||
|
||||
c == '.' {
|
||||
token.push(c);
|
||||
} else {
|
||||
tokens.push(Token::Number(token.trim().to_string()));
|
||||
state = TokState::Unit;
|
||||
token = String::new();
|
||||
token.push(c);
|
||||
}
|
||||
}
|
||||
TokState::Unit => {
|
||||
if c.is_ascii_digit() || c == '-' {
|
||||
tokens.push(Token::Unit(token));
|
||||
state = TokState::Number;
|
||||
token = String::new();
|
||||
token.push(c);
|
||||
}
|
||||
else if !c.is_whitespace() {
|
||||
token.push(c);
|
||||
} else {
|
||||
tokens.push(Token::Unit(token));
|
||||
state = TokState::Neutral;
|
||||
token = String::new();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
match state {
|
||||
TokState::Neutral => { assert!(token.len() == 0); }
|
||||
TokState::Number => { tokens.push(Token::Number(token.trim().to_string())); }
|
||||
TokState::Unit => { tokens.push(Token::Unit(token)); }
|
||||
}
|
||||
|
||||
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 },
|
||||
]));
|
||||
|
||||
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));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn units() {
|
||||
// Length
|
||||
assert_eq!(parse_unit("inch"), Some(NonMetric::Inch));
|
||||
assert_eq!(parse_unit("inches"), Some(NonMetric::Inch));
|
||||
assert_eq!(parse_unit("in"), Some(NonMetric::Inch));
|
||||
assert_eq!(parse_unit("\""), Some(NonMetric::Inch));
|
||||
assert_eq!(parse_unit("″"), Some(NonMetric::Inch));
|
||||
|
||||
assert_eq!(parse_unit("foot"), Some(NonMetric::Foot));
|
||||
assert_eq!(parse_unit("feet"), Some(NonMetric::Foot));
|
||||
assert_eq!(parse_unit("ft"), Some(NonMetric::Foot));
|
||||
assert_eq!(parse_unit("'"), Some(NonMetric::Foot));
|
||||
assert_eq!(parse_unit("′"), Some(NonMetric::Foot));
|
||||
|
||||
assert_eq!(parse_unit("yard"), Some(NonMetric::Yard));
|
||||
assert_eq!(parse_unit("yards"), Some(NonMetric::Yard));
|
||||
assert_eq!(parse_unit("yd"), Some(NonMetric::Yard));
|
||||
|
||||
assert_eq!(parse_unit("mile"), Some(NonMetric::Mile));
|
||||
assert_eq!(parse_unit("miles"), Some(NonMetric::Mile));
|
||||
assert_eq!(parse_unit("mi"), Some(NonMetric::Mile));
|
||||
assert_eq!(parse_unit("m"), Some(NonMetric::Mile));
|
||||
|
||||
// Weight
|
||||
assert_eq!(parse_unit("ounce"), Some(NonMetric::Ounce));
|
||||
assert_eq!(parse_unit("ounces"), Some(NonMetric::Ounce));
|
||||
assert_eq!(parse_unit("oz"), Some(NonMetric::Ounce));
|
||||
|
||||
assert_eq!(parse_unit("pound"), Some(NonMetric::Pound));
|
||||
assert_eq!(parse_unit("pounds"), Some(NonMetric::Pound));
|
||||
assert_eq!(parse_unit("lb"), Some(NonMetric::Pound));
|
||||
assert_eq!(parse_unit("lbs"), Some(NonMetric::Pound));
|
||||
assert_eq!(parse_unit("#"), Some(NonMetric::Pound));
|
||||
|
||||
assert_eq!(parse_unit("stone"), Some(NonMetric::Stone));
|
||||
assert_eq!(parse_unit("stones"), Some(NonMetric::Stone));
|
||||
assert_eq!(parse_unit("st"), Some(NonMetric::Stone));
|
||||
|
||||
// Unknown unit
|
||||
assert_eq!(parse_unit("hutenosa"), None);
|
||||
}
|
||||
|
||||
#[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())]);
|
||||
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("ft in"),
|
||||
vec![
|
||||
Token::Unit("ft".to_string()),
|
||||
Token::Unit("in".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()),
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
|
@ -7,10 +7,10 @@ pub enum Metric {
|
|||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
pub enum NonMetric {
|
||||
// Length
|
||||
Foot,
|
||||
Inch,
|
||||
Mile,
|
||||
Foot,
|
||||
Yard,
|
||||
Mile,
|
||||
// Weight
|
||||
Ounce,
|
||||
Pound,
|
||||
|
@ -23,7 +23,7 @@ pub struct MetricQuantity {
|
|||
pub unit: Metric,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct NonMetricQuantity {
|
||||
pub amount: f64,
|
||||
pub unit: NonMetric,
|
||||
|
|
Loading…
Reference in New Issue