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 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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
|
@ -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),
|
||||||
|
@ -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."
|
||||||
|
);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user