Skip to content

Commit 67d3b3f

Browse files
committed
Add support for requiring integer or fraction digits with exponents.
This patches support for Rust literals as well as other formats.
1 parent a654076 commit 67d3b3f

File tree

8 files changed

+410
-24
lines changed

8 files changed

+410
-24
lines changed

lexical-parse-float/src/parse.rs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -567,6 +567,8 @@ pub fn parse_number<'a, const FORMAT: u128, const IS_PARTIAL: bool>(
567567
});
568568
let mut n_digits = byte.current_count() - start.current_count();
569569
#[cfg(feature = "format")]
570+
let n_before_dot = n_digits;
571+
#[cfg(feature = "format")]
570572
if format.required_integer_digits() && n_digits == 0 {
571573
return Err(Error::EmptyInteger(byte.cursor()));
572574
}
@@ -697,6 +699,19 @@ pub fn parse_number<'a, const FORMAT: u128, const IS_PARTIAL: bool>(
697699
if format.no_exponent_without_fraction() && fraction_digits.is_none() {
698700
return Err(Error::ExponentWithoutFraction(byte.cursor() - 1));
699701
}
702+
703+
// We require digits before the dot, but we have none.
704+
if format.required_integer_digits_with_exponent() && n_before_dot == 0 {
705+
return Err(Error::ExponentWithoutIntegerDigits(byte.cursor() - 1));
706+
}
707+
708+
// We require digits after the dot, but we have none.
709+
if format.required_fraction_digits_with_exponent()
710+
&& fraction_digits.is_some()
711+
&& n_after_dot == 0
712+
{
713+
return Err(Error::ExponentWithoutFractionDigits(byte.cursor() - 1));
714+
}
700715
}
701716

702717
let is_negative_exponent = parse_exponent_sign(&mut byte)?;

lexical-parse-float/tests/api_tests.rs

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -912,6 +912,46 @@ fn f64_no_exponent_without_fraction_test() {
912912
assert!(f64::from_lexical_with_options::<F2>(b"3e7", &OPTIONS).is_err());
913913
}
914914

915+
#[test]
916+
#[cfg(feature = "format")]
917+
fn f64_required_integer_digits_with_exponent() {
918+
const F1: u128 = rebuild(format::STANDARD)
919+
.required_mantissa_digits(false)
920+
.required_integer_digits_with_exponent(true)
921+
.build_strict();
922+
const OPTIONS: Options = Options::new();
923+
assert!(f64::from_lexical_with_options::<F1>(b"1e5", &OPTIONS).is_ok());
924+
assert!(f64::from_lexical_with_options::<F1>(b".1e5", &OPTIONS).is_err());
925+
assert!(f64::from_lexical_with_options::<F1>(b".e5", &OPTIONS).is_err());
926+
assert!(f64::from_lexical_with_options::<F1>(b"1.e5", &OPTIONS).is_ok());
927+
assert!(f64::from_lexical_with_options::<F1>(b"1.0e5", &OPTIONS).is_ok());
928+
929+
const F2: u128 = rebuild(F1).required_fraction_digits(true).build_strict();
930+
assert!(f64::from_lexical_with_options::<F2>(b"1e5", &OPTIONS).is_ok());
931+
assert!(f64::from_lexical_with_options::<F2>(b"1.e5", &OPTIONS).is_err());
932+
assert!(f64::from_lexical_with_options::<F2>(b"1.0e5", &OPTIONS).is_ok());
933+
}
934+
935+
#[test]
936+
#[cfg(feature = "format")]
937+
fn f64_required_fraction_digits_with_exponent() {
938+
const F1: u128 = rebuild(format::STANDARD)
939+
.required_mantissa_digits(false)
940+
.required_fraction_digits_with_exponent(true)
941+
.build_strict();
942+
const OPTIONS: Options = Options::new();
943+
assert!(f64::from_lexical_with_options::<F1>(b"1e5", &OPTIONS).is_ok());
944+
assert!(f64::from_lexical_with_options::<F1>(b".1e5", &OPTIONS).is_ok());
945+
assert!(f64::from_lexical_with_options::<F1>(b".e5", &OPTIONS).is_err());
946+
assert!(f64::from_lexical_with_options::<F1>(b"1.e5", &OPTIONS).is_err());
947+
assert!(f64::from_lexical_with_options::<F1>(b"1.0e5", &OPTIONS).is_ok());
948+
949+
const F2: u128 = rebuild(F1).required_fraction_digits(true).build_strict();
950+
assert!(f64::from_lexical_with_options::<F2>(b"1e5", &OPTIONS).is_ok());
951+
assert!(f64::from_lexical_with_options::<F2>(b"1.e5", &OPTIONS).is_err());
952+
assert!(f64::from_lexical_with_options::<F2>(b"1.0e5", &OPTIONS).is_ok());
953+
}
954+
915955
#[test]
916956
#[cfg(feature = "format")]
917957
fn f64_no_leading_zeros_test() {

lexical-util/src/error.rs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,10 @@ pub enum Error {
4242
MissingExponentSign(usize),
4343
/// Exponent was present without fraction component.
4444
ExponentWithoutFraction(usize),
45+
/// Exponent was present without any digits in the integer component.
46+
ExponentWithoutIntegerDigits(usize),
47+
/// Exponent was present without any digits in the fraction component.
48+
ExponentWithoutFractionDigits(usize),
4549
/// Integer or integer component of float had invalid leading zeros.
4650
InvalidLeadingZeros(usize),
4751
/// No exponent with required exponent notation.
@@ -154,6 +158,8 @@ impl Error {
154158
Self::InvalidPositiveExponentSign(_) => "'invalid `+` sign in exponent'",
155159
Self::MissingExponentSign(_) => "'missing required `+/-` sign for exponent'",
156160
Self::ExponentWithoutFraction(_) => "'invalid float containing exponent without fraction'",
161+
Self::ExponentWithoutIntegerDigits(_) => "'invalid float containing exponent without integer digits'",
162+
Self::ExponentWithoutFractionDigits(_) => "'invalid float containing exponent without fraction digits'",
157163
Self::InvalidLeadingZeros(_) => "'invalid number with leading zeros before digits'",
158164
Self::MissingExponent(_) => "'missing required exponent'",
159165
Self::MissingSign(_) => "'missing required `+/-` sign for integer'",
@@ -217,6 +223,8 @@ impl Error {
217223
Self::InvalidPositiveExponentSign(index) => Some(index),
218224
Self::MissingExponentSign(index) => Some(index),
219225
Self::ExponentWithoutFraction(index) => Some(index),
226+
Self::ExponentWithoutIntegerDigits(index) => Some(index),
227+
Self::ExponentWithoutFractionDigits(index) => Some(index),
220228
Self::InvalidLeadingZeros(index) => Some(index),
221229
Self::MissingExponent(index) => Some(index),
222230
Self::MissingSign(index) => Some(index),
@@ -367,6 +375,12 @@ impl fmt::Display for Error {
367375
Self::ExponentWithoutFraction(index) => {
368376
write_parse_error!(formatter, description, index)
369377
},
378+
Self::ExponentWithoutIntegerDigits(index) => {
379+
write_parse_error!(formatter, description, index)
380+
},
381+
Self::ExponentWithoutFractionDigits(index) => {
382+
write_parse_error!(formatter, description, index)
383+
},
370384
Self::InvalidLeadingZeros(index) => write_parse_error!(formatter, description, index),
371385
Self::MissingExponent(index) => write_parse_error!(formatter, description, index),
372386
Self::MissingSign(index) => write_parse_error!(formatter, description, index),

lexical-util/src/feature_format.rs

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -586,6 +586,62 @@ impl<const FORMAT: u128> NumberFormat<FORMAT> {
586586
Self::CASE_SENSITIVE_BASE_SUFFIX
587587
}
588588

589+
/// If digits are required before the decimal point with exponent notation.
590+
///
591+
/// See [`required_integer_digits_with_exponent`][Self::required_integer_digits_with_exponent].
592+
pub const REQUIRED_INTEGER_DIGITS_WITH_EXPONENT: bool = from_flag!(FORMAT, REQUIRED_INTEGER_DIGITS_WITH_EXPONENT);
593+
594+
/// Get if digits are required before the decimal point with exponent notation.
595+
///
596+
/// Can only be modified with [`feature`][crate#features] `format`. Defaults
597+
/// to [`false`].
598+
///
599+
/// # Examples
600+
///
601+
/// | Input | Valid? |
602+
/// |:-:|:-:|
603+
/// | `.1e5` | ❌ |
604+
/// | `.e5` | ❌ |
605+
/// | `1.e5` | ✔️ |
606+
/// | `1.0e5` | ✔️ |
607+
///
608+
/// # Used For
609+
///
610+
/// - Parse Float
611+
#[inline(always)]
612+
pub const fn required_integer_digits_with_exponent(&self) -> bool {
613+
Self::REQUIRED_INTEGER_DIGITS_WITH_EXPONENT
614+
}
615+
616+
/// If digits are required after the decimal point with exponent notation,
617+
/// if the decimal point is present.
618+
///
619+
/// See [`required_fraction_digits_with_exponent`][Self::required_fraction_digits_with_exponent].
620+
pub const REQUIRED_FRACTION_DIGITS_WITH_EXPONENT: bool = from_flag!(FORMAT, REQUIRED_FRACTION_DIGITS_WITH_EXPONENT);
621+
622+
/// Get if digits are required after the decimal point with exponent
623+
/// notation, if the decimal point is present.
624+
///
625+
/// Can only be modified with [`feature`][crate#features] `format`. Defaults
626+
/// to [`false`]
627+
///
628+
/// # Examples
629+
///
630+
/// | Input | Valid? |
631+
/// |:-:|:-:|
632+
/// | `.1e5` | ✔️ |
633+
/// | `.e5` | ❌ |
634+
/// | `1.e5` | ❌ |
635+
/// | `1.0e5` | ✔️ |
636+
///
637+
/// # Used For
638+
///
639+
/// - Parse Float
640+
#[inline(always)]
641+
pub const fn required_fraction_digits_with_exponent(&self) -> bool {
642+
Self::REQUIRED_FRACTION_DIGITS_WITH_EXPONENT
643+
}
644+
589645
// DIGIT SEPARATOR FLAGS & MASKS
590646

591647
/// If digit separators are allowed between integer digits.

0 commit comments

Comments
 (0)