optimize parsing of numbers

This makes parsing of floats roughly 10x faster
This commit is contained in:
ConnorSkees 2020-04-28 13:18:54 -04:00
parent 2ee4396978
commit a183a9ffa2
2 changed files with 41 additions and 40 deletions

View File

@ -1,4 +1,3 @@
use std::borrow::Cow;
use std::iter::Iterator; use std::iter::Iterator;
use codemap::Spanned; use codemap::Spanned;
@ -9,18 +8,28 @@ use crate::error::SassResult;
use crate::Token; use crate::Token;
pub(crate) struct ParsedNumber { pub(crate) struct ParsedNumber {
pub v: String, pub num: String,
pub dec_len: usize,
// TODO: maybe we just return a bigint? // TODO: maybe we just return a bigint?
pub times_ten: Cow<'static, str>, pub times_ten: String,
pub times_ten_is_postive: bool, pub times_ten_is_postive: bool,
pub is_float: bool,
} }
impl ParsedNumber { impl ParsedNumber {
pub fn new(v: String, times_ten: Cow<'static, str>, times_ten_is_postive: bool) -> Self { pub fn new(
num: String,
dec_len: usize,
times_ten: String,
times_ten_is_postive: bool,
is_float: bool,
) -> Self {
Self { Self {
v, num,
dec_len,
times_ten, times_ten,
times_ten_is_postive, times_ten_is_postive,
is_float,
} }
} }
} }
@ -28,7 +37,7 @@ impl ParsedNumber {
pub(crate) fn eat_number<'a, I: Iterator<Item = Token>>( pub(crate) fn eat_number<'a, I: Iterator<Item = Token>>(
toks: &mut PeekMoreIterator<I>, toks: &mut PeekMoreIterator<I>,
) -> SassResult<Spanned<ParsedNumber>> { ) -> SassResult<Spanned<ParsedNumber>> {
let mut whole = String::new(); let mut whole = String::with_capacity(1);
// TODO: merge this span with chars // TODO: merge this span with chars
let span = if let Some(tok) = toks.peek() { let span = if let Some(tok) = toks.peek() {
tok.pos() tok.pos()
@ -39,22 +48,23 @@ pub(crate) fn eat_number<'a, I: Iterator<Item = Token>>(
if toks.peek().is_none() { if toks.peek().is_none() {
return Ok(Spanned { return Ok(Spanned {
node: ParsedNumber::new(whole, Cow::from("0"), true), node: ParsedNumber::new(whole, 0, String::new(), true, false),
span, span,
}); });
} }
let mut dec = String::new(); let mut dec = String::new();
let mut is_float = false;
let next_tok = *toks.peek().unwrap(); let next_tok = *toks.peek().unwrap();
if next_tok.kind == '.' { if next_tok.kind == '.' {
toks.next(); toks.next();
dec.push('.'); is_float = true;
eat_whole_number(toks, &mut dec); eat_whole_number(toks, &mut dec);
} }
if dec.len() == 1 { if dec.is_empty() && is_float {
return Err(("Expected digit.", next_tok.pos()).into()); return Err(("Expected digit.", next_tok.pos()).into());
} }
@ -101,12 +111,14 @@ pub(crate) fn eat_number<'a, I: Iterator<Item = Token>>(
Ok(Spanned { Ok(Spanned {
node: ParsedNumber::new( node: ParsedNumber::new(
whole, whole,
dec.len(),
if !times_ten.is_empty() { if !times_ten.is_empty() {
Cow::from(times_ten) times_ten
} else { } else {
Cow::from("0") String::new()
}, },
times_ten_is_postive, times_ten_is_postive,
is_float,
), ),
span, span,
}) })

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, One, ToPrimitive}; use num_traits::{pow, One, ToPrimitive, Zero};
use codemap::{Span, Spanned}; use codemap::{Span, Spanned};
@ -630,36 +630,25 @@ impl Value {
Unit::None Unit::None
}; };
let times_ten = pow( let times_ten = if val.times_ten.is_empty() {
BigInt::zero()
} else {
pow(
BigInt::from(10), BigInt::from(10),
val.times_ten val.times_ten
.parse::<BigInt>() .parse::<BigInt>()
.unwrap() .unwrap()
.to_usize() .to_usize()
.ok_or(("Exponent too large (expected usize).", span))?, .ok_or(("Exponent too large (expected usize).", span))?,
); )
};
let n = if let Ok(v) = val.v.parse::<BigRational>() { let n = if val.is_float {
// the number is an integer! BigRational::new(val.num.parse().unwrap(), pow(BigInt::from(10), val.dec_len))
v
// the number is floating point
} else { } else {
let mut num = String::new(); BigRational::new_raw(val.num.parse::<BigInt>().unwrap(), BigInt::one())
let mut chars = val.v.chars();
let mut num_dec = 0;
while let Some(c) = chars.next() {
if c == '.' {
break;
}
num.push(c);
}
for c in chars {
num_dec += 1;
num.push(c);
}
BigRational::new(num.parse().unwrap(), pow(BigInt::from(10), num_dec))
} * if val.times_ten_is_postive { } * if val.times_ten_is_postive {
BigRational::new(times_ten, BigInt::one()) BigRational::new_raw(times_ten, BigInt::one())
} else { } else {
BigRational::new(BigInt::one(), times_ten) BigRational::new(BigInt::one(), times_ten)
}; };