handle scientific notation in numbers
This commit is contained in:
parent
269f37034a
commit
2ee4396978
@ -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<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>,
|
||||
) -> SassResult<Spanned<String>> {
|
||||
) -> SassResult<Spanned<ParsedNumber>> {
|
||||
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<I: Iterator<Item = Token>>(
|
||||
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<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);
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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::<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!
|
||||
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),
|
||||
|
@ -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."
|
||||
);
|
||||
|
Loading…
x
Reference in New Issue
Block a user