replace lexical-core with minimal-lexical

minimal-lexical is a smaller library (compile faster), has no
dependencies, and is faster than lexical-core 0.7 (0.8 will have the
same algorithm).

It requires a separate tokenization phase, done manually, but this will
give more flexibility in supporting different syntaxes

This commit removes the "lexical" feature, as there is no need now
tosupport a separate version without the crate
This commit is contained in:
Geoffroy Couprie
2021-08-18 00:03:20 +02:00
parent e984bb256b
commit 2d63ed43c0
8 changed files with 355 additions and 202 deletions
+2 -2
View File
@@ -25,7 +25,7 @@ jobs:
- rust: stable
features: ''
- rust: stable
features: '--features "std lexical"'
features: '--features "std"'
- rust: stable
features: '--no-default-features'
- rust: stable
@@ -127,7 +127,7 @@ jobs:
uses: actions-rs/cargo@v1
with:
command: doc
args: --verbose --features "std lexical docsrs"
args: --verbose --features "std docsrs"
fmt:
name: Check formatting
+4 -6
View File
@@ -30,18 +30,16 @@ include = [
[features]
alloc = []
std = ["alloc", "memchr/use_std"]
default = ["std", "lexical"]
lexical = ["lexical-core"]
default = ["std"]
docsrs = []
[dependencies]
minimal-lexical = "0.1.2"
[dependencies.memchr]
version = "2.0"
default-features = false
[dependencies.lexical-core]
version = "^0.7.5"
optional = true
[dev-dependencies]
criterion = "0.3"
jemallocator = "^0.3"
+23
View File
@@ -191,12 +191,35 @@ fn float_str(c: &mut Criterion) {
});
}
use nom::ParseTo;
use nom::Err;
fn std_float(input: &[u8]) -> IResult<&[u8], f64, (&[u8], ErrorKind)> {
match recognize_float(input) {
Err(e) => Err(e),
Ok((i, s)) => match s.parse_to() {
Some(n) => Ok((i, n)),
None => Err(Err::Error((i, ErrorKind::Float))),
},
}
}
fn std_float_bytes(c: &mut Criterion) {
println!(
"std_float_bytes result: {:?}",
std_float(&b"-1.234E-12"[..])
);
c.bench_function("std_float bytes", |b| {
b.iter(|| std_float(&b"-1.234E-12"[..]));
});
}
criterion_group!(
benches,
json_bench,
recognize_float_bytes,
recognize_float_str,
float_bytes,
std_float_bytes,
float_str
);
criterion_main!(benches);
@@ -7,3 +7,4 @@
cc cc9654fa1abddf4d6045e4c4977fea390903ee6e6469630b0bb17fdf69219b6d # shrinks to s = "𑵧"
cc 7dcadb118055527708beb3c5eadd3e14202a8f70e019004c33e9696853691827 # shrinks to s = ""
cc e8af68daccf860a49177b5aab0dfeecea24c7530fec6c88469ca0f820188c6b1 # shrinks to s = "-"
cc c98c899dcd0a9359ddbf246e3a1edddb349e6dd7e1d166637e551e4dcf570db6 # shrinks to s = "+0"
@@ -6,3 +6,5 @@
# everyone who runs the test benefits from these saved cases.
cc 82a575ed1f031825e7474bff3702d0c42017471b5ac845bdbdc00c1534dbc4cb # shrinks to s = ""
cc 155f8f4b052941ba58b8b90a5f8fa7da78c04c1a451083a4a89a348c86226904 # shrinks to s = "0"
cc c35a5a751223822dd0a94416d5ca3cc53b4a61cdc4f9422251bc2c72712ed844 # shrinks to s = "-0"
cc 478373182b684c42ce3746ea62d57a35a9c764ef75943e0bb1dc08f88b295581 # shrinks to s = "- "
+13
View File
@@ -0,0 +1,13 @@
# Seeds for failure cases proptest has generated in the past. It is
# automatically read and these particular cases re-run before any
# novel cases are generated.
#
# It is recommended to check this file in to source control so that
# everyone who runs the test benefits from these saved cases.
cc b4267e69b3f62d9bfbc8b9a11d9250a4cc78a2f329841190fd2740b1b3e236d6 # shrinks to s = ""
cc cf51966684042789e64f4b99449551cb227309f9db61f0fdd4266b0b7b572ce4 # shrinks to s = "0"
cc fc0d8df9f3a0ea46ec05ff021be24244fe2533c4b5d75e74a78191148e2d07bb # shrinks to s = "0"
cc 15e795e3c045df60a2fa871336f9bf43ca9036aeda19e8e440b956866b031a65 # shrinks to s = "0e"
cc 20b201c32f3f8314cf32133c3e6a58dd1e4409ae883f7910efa1147ba7c73b6c # shrinks to s = "e"
cc 47f9c093d94bc952a3593a79adc2cafa75c9cca51bee8a77150cfaeb89acbaf7 # shrinks to s = "01"
cc 9d65816e63ee5da410b64aeb5f7d44dbfa7d0773c053380e05bff0beb1bc8d92 # shrinks to s = ".0"
+148 -89
View File
@@ -1,15 +1,15 @@
//! Parsers recognizing numbers, complete input version
use crate::branch::alt;
use crate::character::complete::{char, digit1};
use crate::character::complete::{char, digit0, digit1};
use crate::bytes::complete::take_while;
use crate::combinator::{cut, map, opt, recognize};
use crate::error::ParseError;
use crate::error::{make_error, ErrorKind};
use crate::internal::*;
use crate::lib::std::ops::{RangeFrom, RangeTo};
use crate::sequence::{pair, tuple};
use crate::traits::{AsChar, InputIter, InputLength, InputTakeAtPosition};
use crate::traits::{Offset, Slice};
use crate::traits::{AsChar, InputIter, InputLength, InputTakeAtPosition, AsBytes, Offset, Slice};
#[doc(hidden)]
macro_rules! map(
@@ -1398,7 +1398,6 @@ pub fn hex_u32<'a, E: ParseError<&'a [u8]>>(input: &'a [u8]) -> IResult<&'a [u8]
/// assert_eq!(parser("123K-01"), Ok(("K-01", "123")));
/// assert_eq!(parser("abc"), Err(Err::Error(("abc", ErrorKind::Char))));
/// ```
#[allow(unused_imports)]
#[rustfmt::skip]
pub fn recognize_float<T, E:ParseError<T>>(input: T) -> IResult<T, T, E>
where
@@ -1425,7 +1424,90 @@ where
)(input)
}
/// Recognizes floating point number in a byte string and returns a f32.
/// Recognizes a floating point number in text format and returns the integer, fraction and exponent parts of the input data
///
/// *Complete version*: Can parse until the end of input.
///
pub fn recognize_float_parts<T, E:ParseError<T>>(input: T) -> IResult<T, (bool, T, T, i32), E>
where
T: Slice<RangeFrom<usize>> + Slice<RangeTo<usize>>,
T: Clone + Offset,
T: InputIter + crate::traits::ParseTo<i32>,
<T as InputIter>::Item: AsChar,
T: InputTakeAtPosition + InputLength,
<T as InputTakeAtPosition>::Item: AsChar
{
let (i, opt_sign) = opt(alt((char('+'), char('-'))))(input.clone())?;
let sign = match opt_sign {
Some('+') => true,
Some('-') => false,
_ => true,
};
let (i, mut integer) = digit0(i)?;
if integer.input_len() >= 2 {
// left trim zeroes for faster parsing in minimal-lexical
let (_, int2) = take_while(|c: <T as InputTakeAtPosition>::Item| c.as_char() == '0')(integer.clone())?;
// if it's all zeroes, keep one of them
integer = if int2.input_len() == integer.input_len() {
integer.slice(integer.input_len()-1..)
} else {
integer.slice(int2.input_len()..)
};
}
let (i, opt_dot) = opt(char('.'))(i)?;
let (i, fraction) = if opt_dot.is_none() {
let i2 = i.clone();
(i2, i.slice(..0))
} else {
// match number, trim right zeroes
let mut zero_count = 0usize;
let mut position = None;
for (pos, c) in i.iter_indices() {
let c = c.as_char();
if c.is_ascii_digit() {
if c == '0' {
zero_count += 1;
} else {
zero_count = 0;
}
} else {
position = Some(pos);
break;
}
}
let position = position.unwrap_or(i.input_len());
let index = if zero_count == 0 {
position
} else if zero_count == position {
position - zero_count + 1
} else {
position - zero_count
};
(i.slice(position..), i.slice(..index))
};
if integer.input_len() == 0 && fraction.input_len() == 0 {
return Err(Err::Error(E::from_error_kind(input, ErrorKind::Float)))
}
let (i, e) = opt(alt((char('e'), char('E'))))(i)?;
let (i, exp) = if e.is_some() {
cut(crate::character::complete::i32)(i)?
} else {
(i, 0)
};
Ok((i, (sign, integer, fraction, exp)))
}
/// Recognizes floating point number in text format and returns a f32.
///
/// *Complete version*: Can parse until the end of input.
/// ```rust
@@ -1440,33 +1522,36 @@ where
/// assert_eq!(parser("11e-1"), Ok(("", 1.1)));
/// assert_eq!(parser("123E-02"), Ok(("", 1.23)));
/// assert_eq!(parser("123K-01"), Ok(("K-01", 123.0)));
/// assert_eq!(parser("abc"), Err(Err::Error(("abc", ErrorKind::Char))));
/// assert_eq!(parser("abc"), Err(Err::Error(("abc", ErrorKind::Float))));
/// ```
#[cfg(not(feature = "lexical"))]
pub fn float<T, E: ParseError<T>>(input: T) -> IResult<T, f32, E>
pub fn float<'a, T: 'a, E: ParseError<T>>(input: T) -> IResult<T, f32, E>
where
T: Slice<RangeFrom<usize>> + Slice<RangeTo<usize>>,
T: Clone + Offset,
T: InputIter + InputLength + crate::traits::ParseTo<f32>,
T: InputIter + InputLength + crate::traits::ParseTo<i32>,
<T as InputIter>::Item: AsChar,
<T as InputIter>::IterElem: Clone,
T: InputTakeAtPosition,
<T as InputTakeAtPosition>::Item: AsChar,
T: AsBytes,
{
match recognize_float(input) {
Err(e) => Err(e),
Ok((i, s)) => match s.parse_to() {
Some(n) => Ok((i, n)),
None => Err(Err::Error(E::from_error_kind(i, ErrorKind::Float))),
},
}
let (i, (sign, integer, fraction, exponent)) = recognize_float_parts(input)?;
let mut float: f32 =
minimal_lexical::parse_float(
integer.as_bytes().iter(),
fraction.as_bytes().iter(),
exponent);
if !sign {
float = -float;
}
Ok((i, float))
}
/// Recognizes floating point number in a byte string and returns a f32.
/// Recognizes floating point number in text format and returns a f32.
///
/// *Complete version*: Can parse until the end of input.
///
/// This function uses the `lexical-core` crate for float parsing by default, you
/// can deactivate it by removing the "lexical" feature.
/// ```rust
/// # use nom::{Err, error::ErrorKind, Needed};
/// # use nom::Needed::Size;
@@ -1476,87 +1561,34 @@ where
/// float(s)
/// };
///
/// assert_eq!(parser("1.1"), Ok(("", 1.1)));
/// assert_eq!(parser("123E-02"), Ok(("", 1.23)));
/// assert_eq!(parser("123K-01"), Ok(("K-01", 123.0)));
/// assert_eq!(parser("abc"), Err(Err::Error(("abc", ErrorKind::Float))));
/// ```
#[cfg(feature = "lexical")]
pub fn float<T, E: ParseError<T>>(input: T) -> IResult<T, f32, E>
where
T: crate::traits::AsBytes + InputLength + Slice<RangeFrom<usize>>,
{
match ::lexical_core::parse_partial(input.as_bytes()) {
Ok((value, processed)) => Ok((input.slice(processed..), value)),
Err(_) => Err(Err::Error(E::from_error_kind(input, ErrorKind::Float))),
}
}
/// Recognizes floating point number in a byte string and returns a f64.
///
/// *Complete version*: Can parse until the end of input.
/// ```rust
/// # use nom::{Err, error::ErrorKind, Needed};
/// # use nom::Needed::Size;
/// use nom::number::complete::double;
///
/// let parser = |s| {
/// double(s)
/// };
///
/// assert_eq!(parser("11e-1"), Ok(("", 1.1)));
/// assert_eq!(parser("123E-02"), Ok(("", 1.23)));
/// assert_eq!(parser("123K-01"), Ok(("K-01", 123.0)));
/// assert_eq!(parser("abc"), Err(Err::Error(("abc", ErrorKind::Char))));
/// assert_eq!(parser("abc"), Err(Err::Error(("abc", ErrorKind::Float))));
/// ```
#[cfg(not(feature = "lexical"))]
pub fn double<T, E: ParseError<T>>(input: T) -> IResult<T, f64, E>
pub fn double<'a, T: 'a, E: ParseError<T>>(input: T) -> IResult<T, f64, E>
where
T: Slice<RangeFrom<usize>> + Slice<RangeTo<usize>>,
T: Clone + Offset,
T: InputIter + InputLength + crate::traits::ParseTo<f64>,
T: InputIter + InputLength + crate::traits::ParseTo<i32>,
<T as InputIter>::Item: AsChar,
<T as InputIter>::IterElem: Clone,
T: InputTakeAtPosition,
<T as InputTakeAtPosition>::Item: AsChar,
T: AsBytes,
{
match recognize_float(input) {
Err(e) => Err(e),
Ok((i, s)) => match s.parse_to() {
Some(n) => Ok((i, n)),
None => Err(Err::Error(E::from_error_kind(i, ErrorKind::Float))),
},
}
}
let (i, (sign, integer, fraction, exponent)) = recognize_float_parts(input)?;
/// Recognizes floating point number in a byte string and returns a f64.
///
/// *Complete version*: Can parse until the end of input.
///
/// This function uses the `lexical-core` crate for float parsing by default, you
/// can deactivate it by removing the "lexical" feature.
/// ```rust
/// # use nom::{Err, error::ErrorKind, Needed};
/// # use nom::Needed::Size;
/// use nom::number::complete::double;
///
/// let parser = |s| {
/// double(s)
/// };
///
/// assert_eq!(parser("1.1"), Ok(("", 1.1)));
/// assert_eq!(parser("123E-02"), Ok(("", 1.23)));
/// assert_eq!(parser("123K-01"), Ok(("K-01", 123.0)));
/// assert_eq!(parser("abc"), Err(Err::Error(("abc", ErrorKind::Float))));
/// ```
#[cfg(feature = "lexical")]
pub fn double<T, E: ParseError<T>>(input: T) -> IResult<T, f64, E>
where
T: crate::traits::AsBytes + InputLength + Slice<RangeFrom<usize>>,
{
match ::lexical_core::parse_partial(input.as_bytes()) {
Ok((value, processed)) => Ok((input.slice(processed..), value)),
Err(_) => Err(Err::Error(E::from_error_kind(input, ErrorKind::Float))),
}
let mut float: f64 =
minimal_lexical::parse_float(
integer.as_bytes().iter(),
fraction.as_bytes().iter(),
exponent);
if !sign {
float = -float;
}
Ok((i, float))
}
#[cfg(test)]
@@ -1564,6 +1596,8 @@ mod tests {
use super::*;
use crate::error::ErrorKind;
use crate::internal::Err;
use crate::traits::ParseTo;
use proptest::prelude::*;
macro_rules! assert_parse(
($left: expr, $right: expr) => {
@@ -2003,4 +2037,29 @@ mod tests {
Ok((&b""[..], 36_028_874_334_732_032_i64))
);
}
fn parse_f64(i:&str) -> IResult<&str, f64, ()> {
match recognize_float(i) {
Err(e) => Err(e),
Ok((i, s)) => {
if s.is_empty() {
return Err(Err::Error(()));
}
match s.parse_to() {
Some(n) => Ok((i, n)),
None => Err(Err::Error(())),
}
},
}
}
proptest! {
#[test]
fn floats(s in "\\PC*") {
println!("testing {}", s);
let res1 = parse_f64(&s);
let res2 = double::<_, ()>(s.as_str());
assert_eq!(res1, res2);
}
}
}
+162 -105
View File
@@ -1,14 +1,14 @@
//! Parsers recognizing numbers, streaming version
use crate::branch::alt;
use crate::character::streaming::{char, digit1};
use crate::character::streaming::{char, digit0, digit1};
use crate::bytes::streaming::take_while;
use crate::combinator::{cut, map, opt, recognize};
use crate::error::{ErrorKind, ParseError};
use crate::internal::*;
use crate::lib::std::ops::{RangeFrom, RangeTo};
use crate::sequence::{pair, tuple};
use crate::traits::{AsChar, InputIter, InputLength, InputTakeAtPosition};
use crate::traits::{Offset, Slice};
use crate::traits::{AsChar, InputIter, InputLength, InputTakeAtPosition, Offset, Slice, AsBytes};
#[doc(hidden)]
macro_rules! map(
@@ -1367,7 +1367,6 @@ pub fn hex_u32<'a, E: ParseError<&'a [u8]>>(input: &'a [u8]) -> IResult<&'a [u8]
/// assert_eq!(parser("123K-01"), Ok(("K-01", "123")));
/// assert_eq!(parser("abc"), Err(Err::Error(("abc", ErrorKind::Char))));
/// ```
#[allow(unused_imports)]
#[rustfmt::skip]
pub fn recognize_float<T, E:ParseError<T>>(input: T) -> IResult<T, T, E>
where
@@ -1394,146 +1393,177 @@ where
)(input)
}
/// Recognizes floating point number in a byte string and returns a `f32`.
/// Recognizes a floating point number in text format and returns the integer, fraction and exponent parts of the input data
///
/// *Streaming version*: Will return `Err(nom::Err::Incomplete(_))` if there is not enough data.
///
pub fn recognize_float_parts<T, E:ParseError<T>>(input: T) -> IResult<T, (bool, T, T, i32), E>
where
T: Slice<RangeFrom<usize>> + Slice<RangeTo<usize>>,
T: Clone + Offset,
T: InputIter + crate::traits::ParseTo<i32>,
<T as InputIter>::Item: AsChar,
T: InputTakeAtPosition + InputLength,
<T as InputTakeAtPosition>::Item: AsChar
{
let (i, opt_sign) = opt(alt((char('+'), char('-'))))(input.clone())?;
let sign = match opt_sign {
Some('+') => true,
Some('-') => false,
_ => true,
};
let (i, mut integer) = digit0(i)?;
if integer.input_len() >= 2 {
// left trim zeroes for faster parsing in minimal-lexical
let (_, int2) = take_while(|c: <T as InputTakeAtPosition>::Item| c.as_char() == '0')(integer.clone())?;
// if it's all zeroes, keep one of them
integer = if int2.input_len() == integer.input_len() {
integer.slice(integer.input_len()-1..)
} else {
integer.slice(int2.input_len()..)
};
}
let (i, opt_dot) = opt(char('.'))(i)?;
let (i, fraction) = if opt_dot.is_none() {
let i2 = i.clone();
(i2, i.slice(..0))
} else {
// match number, trim right zeroes
let mut zero_count = 0usize;
let mut position = None;
for (pos, c) in i.iter_indices() {
let c = c.as_char();
if c.is_ascii_digit() {
if c == '0' {
zero_count += 1;
} else {
zero_count = 0;
}
} else {
position = Some(pos);
break;
}
}
let position = if let Some(pos) = position {
pos
} else {
return Err(Err::Incomplete(Needed::new(1)));
};
let index = if zero_count == 0 {
position
} else if zero_count == position {
position - zero_count + 1
} else {
position - zero_count
};
(i.slice(position..), i.slice(..index))
};
if integer.input_len() == 0 && fraction.input_len() == 0 {
return Err(Err::Error(E::from_error_kind(input, ErrorKind::Float)))
}
let (i, e) = opt(alt((char('e'), char('E'))))(i)?;
let (i, exp) = if e.is_some() {
cut(crate::character::streaming::i32)(i)?
} else {
(i, 0)
};
Ok((i, (sign, integer, fraction, exp)))
}
/// Recognizes floating point number in text format and returns a f32.
///
/// *Streaming version*: Will return `Err(nom::Err::Incomplete(_))` if there is not enough data.
///
/// *Streaming version*: Will return `Err(nom::Err::Incomplete(_))` if it reaches the end of input.
/// ```rust
/// # use nom::{Err, error::ErrorKind, Needed};
/// use nom::number::streaming::float;
/// # use nom::Needed::Size;
/// use nom::number::complete::float;
///
/// let parser = |s| {
/// float(s)
/// };
///
/// assert_eq!(parser("11e-1;"), Ok((";", 1.1)));
/// assert_eq!(parser("123E-02;"), Ok((";", 1.23)));
/// assert_eq!(parser("11e-1"), Ok(("", 1.1)));
/// assert_eq!(parser("123E-02"), Ok(("", 1.23)));
/// assert_eq!(parser("123K-01"), Ok(("K-01", 123.0)));
/// assert_eq!(parser("abc"), Err(Err::Error(("abc", ErrorKind::Char))));
/// assert_eq!(parser("abc"), Err(Err::Error(("abc", ErrorKind::Float))));
/// ```
#[cfg(not(feature = "lexical"))]
pub fn float<T, E: ParseError<T>>(input: T) -> IResult<T, f32, E>
pub fn float<'a, T: 'a, E: ParseError<T>>(input: T) -> IResult<T, f32, E>
where
T: Slice<RangeFrom<usize>> + Slice<RangeTo<usize>>,
T: Clone + Offset,
T: InputIter + InputLength + crate::traits::ParseTo<f32>,
T: InputIter + InputLength + crate::traits::ParseTo<i32>,
<T as InputIter>::Item: AsChar,
<T as InputIter>::IterElem: Clone,
T: InputTakeAtPosition,
<T as InputTakeAtPosition>::Item: AsChar,
T: AsBytes,
{
match recognize_float(input) {
Err(e) => Err(e),
Ok((i, s)) => match s.parse_to() {
Some(n) => Ok((i, n)),
None => Err(Err::Error(E::from_error_kind(i, ErrorKind::Float))),
},
}
let (i, (sign, integer, fraction, exponent)) = recognize_float_parts(input)?;
let mut float: f32 =
minimal_lexical::parse_float(
integer.as_bytes().iter(),
fraction.as_bytes().iter(),
exponent);
if !sign {
float = -float;
}
Ok((i, float))
}
/// Recognizes floating point number in a byte string and returns a `f32`.
/// Recognizes floating point number in text format and returns a f32.
///
/// *Streaming version*: Will return `Err(nom::Err::Incomplete(_))` if there is not enough data.
///
/// *Streaming version*: Will return `Err(nom::Err::Incomplete(_))` if it reaches the end of input.
/// ```rust
/// # use nom::{Err, error::ErrorKind, Needed};
/// use nom::number::streaming::float;
/// # use nom::Needed::Size;
/// use nom::number::complete::float;
///
/// let parser = |s| {
/// float(s)
/// };
///
/// assert_eq!(parser("11e-1;"), Ok((";", 1.1)));
/// assert_eq!(parser("123E-02;"), Ok((";", 1.23)));
/// assert_eq!(parser("11e-1"), Ok(("", 1.1)));
/// assert_eq!(parser("123E-02"), Ok(("", 1.23)));
/// assert_eq!(parser("123K-01"), Ok(("K-01", 123.0)));
/// assert_eq!(parser("abc"), Err(Err::Error(("abc", ErrorKind::Float))));
/// ```
///
/// this function uses the lexical-core crate for float parsing by default, you
/// can deactivate it by removing the "lexical" feature
#[cfg(feature = "lexical")]
pub fn float<T, E: ParseError<T>>(input: T) -> IResult<T, f32, E>
where
T: crate::traits::AsBytes + InputLength + Slice<RangeFrom<usize>>,
{
match ::lexical_core::parse_partial(input.as_bytes()) {
Ok((value, processed)) => {
if processed == input.input_len() {
Err(Err::Incomplete(Needed::Unknown))
} else {
Ok((input.slice(processed..), value))
}
}
Err(_) => Err(Err::Error(E::from_error_kind(input, ErrorKind::Float))),
}
}
/// Recognizes floating point number in a byte string and returns a `f64`.
///
/// *Streaming version*: Will return `Err(nom::Err::Incomplete(_))` if it reaches the end of input.
/// ```rust
/// # use nom::{Err, error::ErrorKind, Needed};
/// use nom::number::streaming::double;
///
/// let parser = |s| {
/// double(s)
/// };
///
/// assert_eq!(parser("11e-1;"), Ok((";", 1.1)));
/// assert_eq!(parser("123E-02;"), Ok((";", 1.23)));
/// assert_eq!(parser("123K-01"), Ok(("K-01", 123.0)));
/// assert_eq!(parser("abc"), Err(Err::Error(("abc", ErrorKind::Char))));
/// ```
#[cfg(not(feature = "lexical"))]
pub fn double<T, E: ParseError<T>>(input: T) -> IResult<T, f64, E>
pub fn double<'a, T: 'a, E: ParseError<T>>(input: T) -> IResult<T, f64, E>
where
T: Slice<RangeFrom<usize>> + Slice<RangeTo<usize>>,
T: Clone + Offset,
T: InputIter + InputLength + crate::traits::ParseTo<f64>,
T: InputIter + InputLength + crate::traits::ParseTo<i32>,
<T as InputIter>::Item: AsChar,
<T as InputIter>::IterElem: Clone,
T: InputTakeAtPosition,
<T as InputTakeAtPosition>::Item: AsChar,
T: AsBytes,
{
match recognize_float(input) {
Err(e) => Err(e),
Ok((i, s)) => match s.parse_to() {
Some(n) => Ok((i, n)),
None => Err(Err::Error(E::from_error_kind(i, ErrorKind::Float))),
},
}
}
let (i, (sign, integer, fraction, exponent)) = recognize_float_parts(input)?;
/// Recognizes floating point number in a byte string and returns a `f64`.
///
/// *Streaming version*: Will return `Err(nom::Err::Incomplete(_))` if it reaches the end of input.
/// ```rust
/// # use nom::{Err, error::ErrorKind, Needed};
/// use nom::number::streaming::double;
///
/// let parser = |s| {
/// double(s)
/// };
///
/// assert_eq!(parser("11e-1;"), Ok((";", 1.1)));
/// assert_eq!(parser("123E-02;"), Ok((";", 1.23)));
/// assert_eq!(parser("123K-01"), Ok(("K-01", 123.0)));
/// assert_eq!(parser("abc"), Err(Err::Error(("abc", ErrorKind::Float))));
/// ```
///
/// this function uses the lexical-core crate for float parsing by default, you
/// can deactivate it by removing the "lexical" feature
#[cfg(feature = "lexical")]
pub fn double<T, E: ParseError<T>>(input: T) -> IResult<T, f64, E>
where
T: crate::traits::AsBytes + InputLength + Slice<RangeFrom<usize>>,
{
match ::lexical_core::parse_partial(input.as_bytes()) {
Ok((value, processed)) => {
if processed == input.input_len() {
Err(Err::Incomplete(Needed::Unknown))
} else {
Ok((input.slice(processed..), value))
}
let mut float: f64 =
minimal_lexical::parse_float(
integer.as_bytes().iter(),
fraction.as_bytes().iter(),
exponent);
if !sign {
float = -float;
}
Err(_) => Err(Err::Error(E::from_error_kind(input, ErrorKind::Float))),
}
Ok((i, float))
}
#[cfg(test)]
@@ -1541,6 +1571,8 @@ mod tests {
use super::*;
use crate::error::ErrorKind;
use crate::internal::{Err, Needed};
use crate::traits::ParseTo;
use proptest::prelude::*;
macro_rules! assert_parse(
($left: expr, $right: expr) => {
@@ -2086,4 +2118,29 @@ mod tests {
Ok((&b""[..], 36_028_874_334_732_032_i64))
);
}
fn parse_f64(i:&str) -> IResult<&str, f64, ()> {
match recognize_float(i) {
Err(e) => Err(e),
Ok((i, s)) => {
if s.is_empty() {
return Err(Err::Error(()));
}
match s.parse_to() {
Some(n) => Ok((i, n)),
None => Err(Err::Error(())),
}
},
}
}
proptest! {
#[test]
fn floats(s in "\\PC*") {
println!("testing {}", s);
let res1 = parse_f64(&s);
let res2 = double::<_, ()>(s.as_str());
assert_eq!(res1, res2);
}
}
}