Skip to content

Commit

Permalink
Add support for requiring integer or fraction digits with exponents.
Browse files Browse the repository at this point in the history
This patches support for Rust literals as well as other formats.
  • Loading branch information
Alexhuszagh committed Jan 12, 2025
1 parent 0ffc65c commit 0cad692
Show file tree
Hide file tree
Showing 8 changed files with 410 additions and 24 deletions.
15 changes: 15 additions & 0 deletions lexical-parse-float/src/parse.rs
Original file line number Diff line number Diff line change
Expand Up @@ -567,6 +567,8 @@ pub fn parse_number<'a, const FORMAT: u128, const IS_PARTIAL: bool>(
});
let mut n_digits = byte.current_count() - start.current_count();
#[cfg(feature = "format")]
let n_before_dot = n_digits;
#[cfg(feature = "format")]
if format.required_integer_digits() && n_digits == 0 {
return Err(Error::EmptyInteger(byte.cursor()));
}
Expand Down Expand Up @@ -697,6 +699,19 @@ pub fn parse_number<'a, const FORMAT: u128, const IS_PARTIAL: bool>(
if format.no_exponent_without_fraction() && fraction_digits.is_none() {
return Err(Error::ExponentWithoutFraction(byte.cursor() - 1));
}

// We require digits before the dot, but we have none.
if format.required_integer_digits_with_exponent() && n_before_dot == 0 {
return Err(Error::ExponentWithoutIntegerDigits(byte.cursor() - 1));
}

// We require digits after the dot, but we have none.
if format.required_fraction_digits_with_exponent()
&& fraction_digits.is_some()
&& n_after_dot == 0
{
return Err(Error::ExponentWithoutFractionDigits(byte.cursor() - 1));
}
}

let is_negative_exponent = parse_exponent_sign(&mut byte)?;
Expand Down
40 changes: 40 additions & 0 deletions lexical-parse-float/tests/api_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -912,6 +912,46 @@ fn f64_no_exponent_without_fraction_test() {
assert!(f64::from_lexical_with_options::<F2>(b"3e7", &OPTIONS).is_err());
}

#[test]
#[cfg(feature = "format")]
fn f64_required_integer_digits_with_exponent() {
const F1: u128 = rebuild(format::STANDARD)
.required_mantissa_digits(false)
.required_integer_digits_with_exponent(true)
.build_strict();
const OPTIONS: Options = Options::new();
assert!(f64::from_lexical_with_options::<F1>(b"1e5", &OPTIONS).is_ok());
assert!(f64::from_lexical_with_options::<F1>(b".1e5", &OPTIONS).is_err());
assert!(f64::from_lexical_with_options::<F1>(b".e5", &OPTIONS).is_err());
assert!(f64::from_lexical_with_options::<F1>(b"1.e5", &OPTIONS).is_ok());
assert!(f64::from_lexical_with_options::<F1>(b"1.0e5", &OPTIONS).is_ok());

const F2: u128 = rebuild(F1).required_fraction_digits(true).build_strict();
assert!(f64::from_lexical_with_options::<F2>(b"1e5", &OPTIONS).is_ok());
assert!(f64::from_lexical_with_options::<F2>(b"1.e5", &OPTIONS).is_err());
assert!(f64::from_lexical_with_options::<F2>(b"1.0e5", &OPTIONS).is_ok());
}

#[test]
#[cfg(feature = "format")]
fn f64_required_fraction_digits_with_exponent() {
const F1: u128 = rebuild(format::STANDARD)
.required_mantissa_digits(false)
.required_fraction_digits_with_exponent(true)
.build_strict();
const OPTIONS: Options = Options::new();
assert!(f64::from_lexical_with_options::<F1>(b"1e5", &OPTIONS).is_ok());
assert!(f64::from_lexical_with_options::<F1>(b".1e5", &OPTIONS).is_ok());
assert!(f64::from_lexical_with_options::<F1>(b".e5", &OPTIONS).is_err());
assert!(f64::from_lexical_with_options::<F1>(b"1.e5", &OPTIONS).is_err());
assert!(f64::from_lexical_with_options::<F1>(b"1.0e5", &OPTIONS).is_ok());

const F2: u128 = rebuild(F1).required_fraction_digits(true).build_strict();
assert!(f64::from_lexical_with_options::<F2>(b"1e5", &OPTIONS).is_ok());
assert!(f64::from_lexical_with_options::<F2>(b"1.e5", &OPTIONS).is_err());
assert!(f64::from_lexical_with_options::<F2>(b"1.0e5", &OPTIONS).is_ok());
}

#[test]
#[cfg(feature = "format")]
fn f64_no_leading_zeros_test() {
Expand Down
14 changes: 14 additions & 0 deletions lexical-util/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,10 @@ pub enum Error {
MissingExponentSign(usize),
/// Exponent was present without fraction component.
ExponentWithoutFraction(usize),
/// Exponent was present without any digits in the integer component.
ExponentWithoutIntegerDigits(usize),
/// Exponent was present without any digits in the fraction component.
ExponentWithoutFractionDigits(usize),
/// Integer or integer component of float had invalid leading zeros.
InvalidLeadingZeros(usize),
/// No exponent with required exponent notation.
Expand Down Expand Up @@ -154,6 +158,8 @@ impl Error {
Self::InvalidPositiveExponentSign(_) => "'invalid `+` sign in exponent'",
Self::MissingExponentSign(_) => "'missing required `+/-` sign for exponent'",
Self::ExponentWithoutFraction(_) => "'invalid float containing exponent without fraction'",
Self::ExponentWithoutIntegerDigits(_) => "'invalid float containing exponent without integer digits'",
Self::ExponentWithoutFractionDigits(_) => "'invalid float containing exponent without fraction digits'",
Self::InvalidLeadingZeros(_) => "'invalid number with leading zeros before digits'",
Self::MissingExponent(_) => "'missing required exponent'",
Self::MissingSign(_) => "'missing required `+/-` sign for integer'",
Expand Down Expand Up @@ -217,6 +223,8 @@ impl Error {
Self::InvalidPositiveExponentSign(index) => Some(index),
Self::MissingExponentSign(index) => Some(index),
Self::ExponentWithoutFraction(index) => Some(index),
Self::ExponentWithoutIntegerDigits(index) => Some(index),
Self::ExponentWithoutFractionDigits(index) => Some(index),
Self::InvalidLeadingZeros(index) => Some(index),
Self::MissingExponent(index) => Some(index),
Self::MissingSign(index) => Some(index),
Expand Down Expand Up @@ -367,6 +375,12 @@ impl fmt::Display for Error {
Self::ExponentWithoutFraction(index) => {
write_parse_error!(formatter, description, index)
},
Self::ExponentWithoutIntegerDigits(index) => {
write_parse_error!(formatter, description, index)
},
Self::ExponentWithoutFractionDigits(index) => {
write_parse_error!(formatter, description, index)
},
Self::InvalidLeadingZeros(index) => write_parse_error!(formatter, description, index),
Self::MissingExponent(index) => write_parse_error!(formatter, description, index),
Self::MissingSign(index) => write_parse_error!(formatter, description, index),
Expand Down
56 changes: 56 additions & 0 deletions lexical-util/src/feature_format.rs
Original file line number Diff line number Diff line change
Expand Up @@ -586,6 +586,62 @@ impl<const FORMAT: u128> NumberFormat<FORMAT> {
Self::CASE_SENSITIVE_BASE_SUFFIX
}

/// If digits are required before the decimal point with exponent notation.
///
/// See [`required_integer_digits_with_exponent`][Self::required_integer_digits_with_exponent].
pub const REQUIRED_INTEGER_DIGITS_WITH_EXPONENT: bool = from_flag!(FORMAT, REQUIRED_INTEGER_DIGITS_WITH_EXPONENT);

/// Get if digits are required before the decimal point with exponent notation.
///
/// Can only be modified with [`feature`][crate#features] `format`. Defaults
/// to [`false`].
///
/// # Examples
///
/// | Input | Valid? |
/// |:-:|:-:|
/// | `.1e5` | ❌ |
/// | `.e5` | ❌ |
/// | `1.e5` | ✔️ |
/// | `1.0e5` | ✔️ |
///
/// # Used For
///
/// - Parse Float
#[inline(always)]
pub const fn required_integer_digits_with_exponent(&self) -> bool {
Self::REQUIRED_INTEGER_DIGITS_WITH_EXPONENT
}

/// If digits are required after the decimal point with exponent notation,
/// if the decimal point is present.
///
/// See [`required_fraction_digits_with_exponent`][Self::required_fraction_digits_with_exponent].
pub const REQUIRED_FRACTION_DIGITS_WITH_EXPONENT: bool = from_flag!(FORMAT, REQUIRED_FRACTION_DIGITS_WITH_EXPONENT);

/// Get if digits are required after the decimal point with exponent
/// notation, if the decimal point is present.
///
/// Can only be modified with [`feature`][crate#features] `format`. Defaults
/// to [`false`]
///
/// # Examples
///
/// | Input | Valid? |
/// |:-:|:-:|
/// | `.1e5` | ✔️ |
/// | `.e5` | ❌ |
/// | `1.e5` | ❌ |
/// | `1.0e5` | ✔️ |
///
/// # Used For
///
/// - Parse Float
#[inline(always)]
pub const fn required_fraction_digits_with_exponent(&self) -> bool {
Self::REQUIRED_FRACTION_DIGITS_WITH_EXPONENT
}

// DIGIT SEPARATOR FLAGS & MASKS

/// If digit separators are allowed between integer digits.
Expand Down
Loading

0 comments on commit 0cad692

Please sign in to comment.