From 85a669e126a2a166d5b1e96b1b0b4182a892e49f Mon Sep 17 00:00:00 2001 From: Mark Date: Sat, 1 Apr 2023 18:29:01 -0700 Subject: [PATCH] Added support for scientific notation --- src/main.rs | 7 +++++ src/parser/tokenize.rs | 68 +++++++++++++++++++++++++++++++++++----- src/quantity/quantity.rs | 42 ++++++++++++++++++++----- 3 files changed, 103 insertions(+), 14 deletions(-) diff --git a/src/main.rs b/src/main.rs index e41c1ae..9029a2c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -176,6 +176,13 @@ fn main() -> Result<(), std::io::Error> { // Tests to add: // 3!+1 // 3!3 +// 1e2 +// 1e-2 +// 1e0 +// 1e1.2 +// 1e(2) +// e2e +// 2 2e2 #[cfg(test)] mod tests { diff --git a/src/parser/tokenize.rs b/src/parser/tokenize.rs index 037c37e..789676d 100644 --- a/src/parser/tokenize.rs +++ b/src/parser/tokenize.rs @@ -31,6 +31,24 @@ fn push_token(g: &mut VecDeque, t: Option, stop_i: usize) { }; + // `2e` isn't exponential notation, it's 2*e. + // If a number ends in `e`, disconnect the `e` and make it a word. + if let PreToken::PreNumber(l, s) = &t { + let last = &s[s.len()-1..]; + if last == "e" { + g.push_back(PreToken::PreNumber( + LineLocation { pos: l.pos, len: l.len-1 }, + String::from(&s[0..s.len()-1]) + )); + g.push_back(PreToken::PreWord( + LineLocation { pos: l.pos + l.len - 1, len: 1 }, + String::from("e") + )); + + return; + } + } + if let PreToken::PreWord(l, s) = &t { let o = Operator::from_string(s); if o.is_some() { @@ -68,14 +86,50 @@ pub(in crate::parser) fn tokenize(input: &String) -> VecDeque { }; }, - // The minus sign can be both a Negative and an Operator. - // Needs special treatment, always starts a new token. + // 'e' needs special treatment. + // Can be both a word or a number. + 'e' => { + match &mut t { + Some(PreToken::PreWord(_, val)) => { val.push(c); }, + Some(PreToken::PreNumber(_, val)) => { val.push(c); }, + + _ => { + push_token(&mut g, t, i); + t = Some(PreToken::PreWord(LineLocation{pos: i, len: 0}, String::from(c))); + } + }; + } + + // The minus sign also needs special treatment. + // It can be the `neg` operator, the `minus` operator, + // or it can specify a negative exponent. '-' => { - push_token(&mut g, t, i); - t = Some(PreToken::PreOperator( - LineLocation{pos: i, len: 1}, - String::from("-") - )); + match &mut t { + Some(PreToken::PreNumber(_, val)) => { + if &val[val.len()-1..] == "e" { + // If the current number ends in an `e`, + // this negative specifies a negative exponent + // like 2e-2 = 0.02. + val.push(c); + } else { + // Otherwise, end the number. + // We probably have a subtraction. + push_token(&mut g, t, i); + t = Some(PreToken::PreOperator( + LineLocation{pos: i, len: 1}, + String::from("-") + )); + } + }, + + _ => { + push_token(&mut g, t, i); + t = Some(PreToken::PreOperator( + LineLocation{pos: i, len: 1}, + String::from("-") + )); + } + }; }, // Operator diff --git a/src/quantity/quantity.rs b/src/quantity/quantity.rs index cb48335..6a9f3b6 100644 --- a/src/quantity/quantity.rs +++ b/src/quantity/quantity.rs @@ -137,20 +137,48 @@ impl Quantity { pub fn new_rational_from_float_string(s: &str) -> Option { - let mut q = s.split("."); - let a = q.next().unwrap(); - let b = q.next(); + // Scientific notation + let mut sci = s.split("e"); + let num = sci.next().unwrap(); + let exp = sci.next(); + + let exp = if exp.is_some() { + let r = exp.unwrap().parse::(); + match r { + Ok(x) => x, + Err(_) => return None + } + } else {0isize}; + + // Split integer and decimal parts + let mut dec = num.split("."); + let a = dec.next().unwrap(); + let b = dec.next(); let b = if b.is_some() {b.unwrap()} else {""}; // Error conditions if { - q.next().is_some() || // We should have at most one `.` + dec.next().is_some() || // We should have at most one `.` + sci.next().is_some() || // We should have at most one `e` a.len() == 0 // We need something in the numerator } { return None; } - return Some(Quantity::new_rational_from_string( - &format!("{a}{b}/1{}", "0".repeat(b.len())) - )); + let s: String; + if exp < 0 { + let exp: usize = (-exp).try_into().unwrap(); + s = format!("{a}{b}/1{}", "0".repeat(b.len() + exp)); + } else if exp > 0 { + let exp: usize = exp.try_into().unwrap(); + s = format!( + "{a}{b}{}/1{}", + "0".repeat(exp), + "0".repeat(b.len()) + ); + } else { // exp == 0 + s = format!("{a}{b}/1{}", "0".repeat(b.len())); + }; + + return Quantity::new_rational_from_string(&s); } pub fn to_float(&self) -> Float {