handle scientific notation in numbers

This commit is contained in:
ConnorSkees 2020-04-28 12:15:10 -04:00
parent 269f37034a
commit 2ee4396978
4 changed files with 189 additions and 24 deletions

View File

@ -1,3 +1,4 @@
use std::borrow::Cow;
use std::iter::Iterator; use std::iter::Iterator;
use codemap::Spanned; use codemap::Spanned;
@ -7,26 +8,40 @@ use peekmore::PeekMoreIterator;
use crate::error::SassResult; use crate::error::SassResult;
use crate::Token; use crate::Token;
pub(crate) fn eat_number<I: Iterator<Item = Token>>( 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<Item = Token>>(
toks: &mut PeekMoreIterator<I>, toks: &mut PeekMoreIterator<I>,
) -> SassResult<Spanned<String>> { ) -> SassResult<Spanned<ParsedNumber>> {
let mut whole = String::new(); 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() tok.pos()
} else { } else {
todo!() todo!()
}; };
while let Some(c) = toks.peek() { eat_whole_number(toks, &mut whole);
if !c.kind.is_numeric() {
break;
}
let tok = toks.next().unwrap();
span = span.merge(tok.pos());
whole.push(tok.kind);
}
if toks.peek().is_none() { 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(); let mut dec = String::new();
@ -36,20 +51,73 @@ pub(crate) fn eat_number<I: Iterator<Item = Token>>(
if next_tok.kind == '.' { if next_tok.kind == '.' {
toks.next(); toks.next();
dec.push('.'); dec.push('.');
while let Some(c) = toks.peek() { eat_whole_number(toks, &mut dec);
if !c.kind.is_numeric() {
break;
}
let tok = toks.next().unwrap();
span = span.merge(tok.pos());
dec.push(tok.kind);
}
} }
if dec.len() == 1 { if dec.len() == 1 {
return Err(("Expected digit.", next_tok.pos()).into()); 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); 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<I: Iterator<Item = Token>>(toks: &mut PeekMoreIterator<I>, buf: &mut String) {
while let Some(c) = toks.peek() {
if !c.kind.is_numeric() {
break;
}
let tok = toks.next().unwrap();
buf.push(tok.kind);
}
} }

View File

@ -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 { impl AddAssign for Number {
fn add_assign(&mut self, other: Self) { fn add_assign(&mut self, other: Self) {
self.val += other.val self.val += other.val

View File

@ -3,7 +3,7 @@ use std::mem;
use num_bigint::BigInt; use num_bigint::BigInt;
use num_rational::BigRational; use num_rational::BigRational;
use num_traits::pow; use num_traits::{pow, One, ToPrimitive};
use codemap::{Span, Spanned}; use codemap::{Span, Spanned};
@ -629,13 +629,23 @@ impl Value {
} else { } else {
Unit::None Unit::None
}; };
let n = if let Ok(v) = val.parse::<BigRational>() {
let times_ten = pow(
BigInt::from(10),
val.times_ten
.parse::<BigInt>()
.unwrap()
.to_usize()
.ok_or(("Exponent too large (expected usize).", span))?,
);
let n = if let Ok(v) = val.v.parse::<BigRational>() {
// the number is an integer! // the number is an integer!
v v
// the number is floating point // the number is floating point
} else { } else {
let mut num = String::new(); let mut num = String::new();
let mut chars = val.chars(); let mut chars = val.v.chars();
let mut num_dec = 0; let mut num_dec = 0;
while let Some(c) = chars.next() { while let Some(c) = chars.next() {
if c == '.' { if c == '.' {
@ -648,6 +658,10 @@ impl Value {
num.push(c); num.push(c);
} }
BigRational::new(num.parse().unwrap(), pow(BigInt::from(10), num_dec)) 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( Ok(IntermediateValue::Value(
Value::Dimension(Number::new(n), unit).span(span), Value::Dimension(Number::new(n), unit).span(span),

View File

@ -91,3 +91,76 @@ test!(
"a {\n color: 1in == 96px;\n}\n", "a {\n color: 1in == 96px;\n}\n",
"a {\n color: true;\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."
);