From 2ee4396978b2913c5917c9305a1d718ca8e9d7ed Mon Sep 17 00:00:00 2001 From: ConnorSkees <39542938+ConnorSkees@users.noreply.github.com> Date: Tue, 28 Apr 2020 12:15:10 -0400 Subject: [PATCH] handle scientific notation in numbers --- src/utils/number.rs | 110 +++++++++++++++++++++++++++++++++++--------- src/value/number.rs | 10 ++++ src/value/parse.rs | 20 ++++++-- tests/number.rs | 73 +++++++++++++++++++++++++++++ 4 files changed, 189 insertions(+), 24 deletions(-) diff --git a/src/utils/number.rs b/src/utils/number.rs index 68e03de..5e0e747 100644 --- a/src/utils/number.rs +++ b/src/utils/number.rs @@ -1,3 +1,4 @@ +use std::borrow::Cow; use std::iter::Iterator; use codemap::Spanned; @@ -7,26 +8,40 @@ use peekmore::PeekMoreIterator; use crate::error::SassResult; use crate::Token; -pub(crate) fn eat_number>( +pub(crate) struct ParsedNumber { + pub v: String, + // TODO: maybe we just return a bigint? + pub times_ten: Cow<'static, str>, + pub times_ten_is_postive: bool, +} + +impl ParsedNumber { + pub fn new(v: String, times_ten: Cow<'static, str>, times_ten_is_postive: bool) -> Self { + Self { + v, + times_ten, + times_ten_is_postive, + } + } +} + +pub(crate) fn eat_number<'a, I: Iterator>( toks: &mut PeekMoreIterator, -) -> SassResult> { +) -> SassResult> { let mut whole = String::new(); - let mut span = if let Some(tok) = toks.peek() { + // TODO: merge this span with chars + let span = if let Some(tok) = toks.peek() { tok.pos() } else { todo!() }; - while let Some(c) = toks.peek() { - if !c.kind.is_numeric() { - break; - } - let tok = toks.next().unwrap(); - span = span.merge(tok.pos()); - whole.push(tok.kind); - } + eat_whole_number(toks, &mut whole); if toks.peek().is_none() { - return Ok(Spanned { node: whole, span }); + return Ok(Spanned { + node: ParsedNumber::new(whole, Cow::from("0"), true), + span, + }); } let mut dec = String::new(); @@ -36,20 +51,73 @@ pub(crate) fn eat_number>( if next_tok.kind == '.' { toks.next(); dec.push('.'); - while let Some(c) = toks.peek() { - if !c.kind.is_numeric() { - break; - } - let tok = toks.next().unwrap(); - span = span.merge(tok.pos()); - dec.push(tok.kind); - } + eat_whole_number(toks, &mut dec); } if dec.len() == 1 { return Err(("Expected digit.", next_tok.pos()).into()); } + let mut times_ten = String::new(); + let mut times_ten_is_postive = true; + loop { + match toks.peek() { + // TODO: https://github.com/rust-lang/rust/issues/54883 + Some(Token { kind: 'e', .. }) | Some(Token { kind: 'E', .. }) => { + if toks.peek_forward(1).is_none() { + break; + } else { + let Token { kind, pos } = toks.peek().unwrap().clone(); + match kind { + '-' => { + toks.next(); + times_ten_is_postive = false; + } + '0'..='9' => {} + _ => break, + } + + toks.next(); + + eat_whole_number(toks, &mut times_ten); + + if times_ten.is_empty() && !times_ten_is_postive { + if let Some(t) = toks.peek() { + return Err(("Expected digit.", t.pos()).into()); + } + return Err(("Expected digit.", pos).into()); + } + } + } + Some(..) | None => break, + } + break; + } + + toks.reset_view(); + whole.push_str(&dec); - Ok(Spanned { node: whole, span }) + + Ok(Spanned { + node: ParsedNumber::new( + whole, + if !times_ten.is_empty() { + Cow::from(times_ten) + } else { + Cow::from("0") + }, + times_ten_is_postive, + ), + span, + }) +} + +fn eat_whole_number>(toks: &mut PeekMoreIterator, buf: &mut String) { + while let Some(c) = toks.peek() { + if !c.kind.is_numeric() { + break; + } + let tok = toks.next().unwrap(); + buf.push(tok.kind); + } } diff --git a/src/value/number.rs b/src/value/number.rs index 2784860..61dc2e9 100644 --- a/src/value/number.rs +++ b/src/value/number.rs @@ -237,6 +237,16 @@ impl Add for Number { } } +impl Add<&Self> for Number { + type Output = Self; + + fn add(self, other: &Self) -> Self { + Number { + val: self.val + &other.val, + } + } +} + impl AddAssign for Number { fn add_assign(&mut self, other: Self) { self.val += other.val diff --git a/src/value/parse.rs b/src/value/parse.rs index 3b651ff..80f8649 100644 --- a/src/value/parse.rs +++ b/src/value/parse.rs @@ -3,7 +3,7 @@ use std::mem; use num_bigint::BigInt; use num_rational::BigRational; -use num_traits::pow; +use num_traits::{pow, One, ToPrimitive}; use codemap::{Span, Spanned}; @@ -629,13 +629,23 @@ impl Value { } else { Unit::None }; - let n = if let Ok(v) = val.parse::() { + + let times_ten = pow( + BigInt::from(10), + val.times_ten + .parse::() + .unwrap() + .to_usize() + .ok_or(("Exponent too large (expected usize).", span))?, + ); + + let n = if let Ok(v) = val.v.parse::() { // the number is an integer! v // the number is floating point } else { let mut num = String::new(); - let mut chars = val.chars(); + let mut chars = val.v.chars(); let mut num_dec = 0; while let Some(c) = chars.next() { if c == '.' { @@ -648,6 +658,10 @@ impl Value { num.push(c); } BigRational::new(num.parse().unwrap(), pow(BigInt::from(10), num_dec)) + } * if val.times_ten_is_postive { + BigRational::new(times_ten, BigInt::one()) + } else { + BigRational::new(BigInt::one(), times_ten) }; Ok(IntermediateValue::Value( Value::Dimension(Number::new(n), unit).span(span), diff --git a/tests/number.rs b/tests/number.rs index 7a5630c..40aec65 100644 --- a/tests/number.rs +++ b/tests/number.rs @@ -91,3 +91,76 @@ test!( "a {\n color: 1in == 96px;\n}\n", "a {\n color: true;\n}\n" ); +test!( + positive_scientific_notation, + "a {\n color: 1e5;\n}\n", + "a {\n color: 100000;\n}\n" +); +test!( + positive_scientific_notation_leading_zeroes, + "a {\n color: 1e05;\n}\n", + "a {\n color: 100000;\n}\n" +); +test!( + positive_scientific_notation_capital, + "a {\n color: 1E5;\n}\n", + "a {\n color: 100000;\n}\n" +); +test!( + negative_scientific_notation, + "a {\n color: 1e-5;\n}\n", + "a {\n color: 0.00001;\n}\n" +); +test!( + negative_scientific_notation_leading_zeroes, + "a {\n color: 1e-05;\n}\n", + "a {\n color: 0.00001;\n}\n" +); +test!( + negative_scientific_notation_capital, + "a {\n color: 1E-5;\n}\n", + "a {\n color: 0.00001;\n}\n" +); +test!( + positive_scientific_notation_decimal, + "a {\n color: 1.2e5;\n}\n", + "a {\n color: 120000;\n}\n" +); +test!( + negative_scientific_notation_decimal, + "a {\n color: 1.2e-5;\n}\n", + "a {\n color: 0.000012;\n}\n" +); +test!(unit_e, "a {\n color: 1e;\n}\n"); +test!( + positive_scientific_notation_zero, + "a {\n color: 1e0;\n}\n", + "a {\n color: 1;\n}\n" +); +test!( + negative_scientific_notation_zero, + "a {\n color: 1e-0;\n}\n", + "a {\n color: 1;\n}\n" +); +test!( + scientific_notation_decimal, + "a {\n color: 1.2e5.5;\n}\n", + "a {\n color: 120000 0.5;\n}\n" +); +test!( + binary_op_with_e_as_unit, + "a {\n color: 1e - 2;\n}\n", + "a {\n color: -1e;\n}\n" +); +error!( + scientific_notation_nothing_after_dash, + "a {\n color: 1e-;\n}\n", "Error: Expected digit." +); +error!( + scientific_notation_whitespace_after_dash, + "a {\n color: 1e- 2;\n}\n", "Error: Expected digit." +); +error!( + scientific_notation_ident_char_after_dash, + "a {\n color: 1e-a;\n}\n", "Error: Expected digit." +);