From f53fae027df56540a95e596b324eb50f77ce1103 Mon Sep 17 00:00:00 2001 From: Alex Huszagh Date: Sun, 12 Jan 2025 11:30:23 -0600 Subject: [PATCH] Add logic for formats that may support only a few conversions. This adds the following number format flags: - `supports_parsing_integers` - `supports_parsing_floats` - `supports_writing_integers` - `supports_writing_floats` If an operation is not supported with the format feature, then the code panics or returns an error immediately, which will always be resolved at compile time. --- lexical-parse-float/src/api.rs | 12 ++- lexical-parse-float/tests/api_tests.rs | 32 ++++++ lexical-parse-integer/src/api.rs | 9 +- lexical-parse-integer/tests/api_tests.rs | 44 ++++++++ lexical-util/src/error.rs | 6 ++ lexical-util/src/feature_format.rs | 72 +++++++++++++ lexical-util/src/format_builder.rs | 122 ++++++++++++++++++++++- lexical-util/src/format_flags.rs | 25 ++++- lexical-util/src/not_feature_format.rs | 120 ++++++++++++++++++---- lexical-write-float/src/api.rs | 7 +- lexical-write-float/tests/api_tests.rs | 31 +++++- lexical-write-integer/src/api.rs | 15 ++- lexical-write-integer/tests/api_tests.rs | 30 ++++++ 13 files changed, 492 insertions(+), 33 deletions(-) diff --git a/lexical-parse-float/src/api.rs b/lexical-parse-float/src/api.rs index 330cf811..e82a635a 100644 --- a/lexical-parse-float/src/api.rs +++ b/lexical-parse-float/src/api.rs @@ -51,7 +51,9 @@ macro_rules! float_from_lexical { ) -> lexical_util::result::Result { let format = NumberFormat::<{ FORMAT }> {}; - if !format.is_valid() { + if !format.supports_parsing_floats() { + return Err(Error::Unsupported); + } else if !format.is_valid() { return Err(format.error()); } else if !is_valid_options_punctuation(FORMAT, options.exponent(), options.decimal_point()) { return Err(Error::InvalidPunctuation); @@ -65,6 +67,14 @@ macro_rules! float_from_lexical { options: &Self::Options, ) -> lexical_util::result::Result<(Self, usize)> { + let format = NumberFormat::<{ FORMAT }> {}; + if !format.supports_parsing_floats() { + return Err(Error::Unsupported); + } else if !format.is_valid() { + return Err(format.error()); + } else if !is_valid_options_punctuation(FORMAT, options.exponent(), options.decimal_point()) { + return Err(Error::InvalidPunctuation); + } Self::parse_partial::(bytes, options) } } diff --git a/lexical-parse-float/tests/api_tests.rs b/lexical-parse-float/tests/api_tests.rs index e7302499..eabf3f56 100644 --- a/lexical-parse-float/tests/api_tests.rs +++ b/lexical-parse-float/tests/api_tests.rs @@ -1275,3 +1275,35 @@ fn issue68_test() { assert_eq!(f32::INFINITY, f32::from_lexical_with_options::(hex, &OPTIONS).unwrap()); assert_eq!(f64::INFINITY, f64::from_lexical_with_options::(hex, &OPTIONS).unwrap()); } + +#[test] +#[cfg(feature = "format")] +fn unsupported_test() { + const FORMAT: u128 = NumberFormatBuilder::new().supports_parsing_floats(false).build_strict(); + const OPTIONS: Options = Options::new(); + + let float = "12345.0"; + let value = f64::from_lexical_with_options::(float.as_bytes(), &OPTIONS); + assert_eq!(value, Err(Error::Unsupported)); + + let value = f64::from_lexical_partial_with_options::(float.as_bytes(), &OPTIONS); + assert_eq!(value, Err(Error::Unsupported)); +} + +#[test] +#[cfg(feature = "format")] +fn supported_test() { + const FORMAT: u128 = NumberFormatBuilder::new() + .supports_parsing_integers(false) + .supports_writing_integers(false) + .supports_writing_floats(false) + .build_strict(); + const OPTIONS: Options = Options::new(); + + let float = "12345.0"; + let value = f64::from_lexical_with_options::(float.as_bytes(), &OPTIONS); + assert_eq!(value, Ok(12345.0)); + + let value = f64::from_lexical_partial_with_options::(float.as_bytes(), &OPTIONS); + assert_eq!(value, Ok((12345.0, 7))); +} diff --git a/lexical-parse-integer/src/api.rs b/lexical-parse-integer/src/api.rs index 44e0c335..0d8efd29 100644 --- a/lexical-parse-integer/src/api.rs +++ b/lexical-parse-integer/src/api.rs @@ -2,6 +2,7 @@ #![doc(hidden)] +use lexical_util::error::Error; use lexical_util::format::{NumberFormat, STANDARD}; use lexical_util::{from_lexical, from_lexical_with_options}; @@ -42,7 +43,9 @@ macro_rules! integer_from_lexical { ) -> lexical_util::result::Result { let format = NumberFormat::<{ FORMAT }> {}; - if !format.is_valid() { + if !format.supports_parsing_integers() { + return Err(Error::Unsupported); + } else if !format.is_valid() { return Err(format.error()); } Self::parse_complete::(bytes, options) @@ -55,7 +58,9 @@ macro_rules! integer_from_lexical { ) -> lexical_util::result::Result<(Self, usize)> { let format = NumberFormat::<{ FORMAT }> {}; - if !format.is_valid() { + if !format.supports_parsing_integers() { + return Err(Error::Unsupported); + } else if !format.is_valid() { return Err(format.error()); } Self::parse_partial::(bytes, options) diff --git a/lexical-parse-integer/tests/api_tests.rs b/lexical-parse-integer/tests/api_tests.rs index 4c25c8b5..1ca88aef 100644 --- a/lexical-parse-integer/tests/api_tests.rs +++ b/lexical-parse-integer/tests/api_tests.rs @@ -357,3 +357,47 @@ fn base_prefix_and_suffix_test() { assert!(i32::from_lexical_with_options::(b"+h", &OPTIONS).is_err()); assert!(i32::from_lexical_with_options::(b"+0x", &OPTIONS).is_err()); } + +#[test] +#[cfg(feature = "format")] +fn unsupported_test() { + const FORMAT: u128 = NumberFormatBuilder::new().supports_parsing_integers(false).build_strict(); + const OPTIONS: Options = Options::new(); + + let integer = "12345"; + let value = i64::from_lexical_with_options::(integer.as_bytes(), &OPTIONS); + assert_eq!(value, Err(Error::Unsupported)); + + let value = i64::from_lexical_partial_with_options::(integer.as_bytes(), &OPTIONS); + assert_eq!(value, Err(Error::Unsupported)); + + let value = u64::from_lexical_with_options::(integer.as_bytes(), &OPTIONS); + assert_eq!(value, Err(Error::Unsupported)); + + let value = u64::from_lexical_partial_with_options::(integer.as_bytes(), &OPTIONS); + assert_eq!(value, Err(Error::Unsupported)); +} + +#[test] +#[cfg(feature = "format")] +fn supported_test() { + const FORMAT: u128 = NumberFormatBuilder::new() + .supports_parsing_floats(false) + .supports_writing_integers(false) + .supports_writing_floats(false) + .build_strict(); + const OPTIONS: Options = Options::new(); + + let integer = "12345"; + let value = i64::from_lexical_with_options::(integer.as_bytes(), &OPTIONS); + assert_eq!(value, Ok(12345)); + + let value = i64::from_lexical_partial_with_options::(integer.as_bytes(), &OPTIONS); + assert_eq!(value, Ok((12345, 5))); + + let value = u64::from_lexical_with_options::(integer.as_bytes(), &OPTIONS); + assert_eq!(value, Ok(12345)); + + let value = u64::from_lexical_partial_with_options::(integer.as_bytes(), &OPTIONS); + assert_eq!(value, Ok((12345, 5))); +} diff --git a/lexical-util/src/error.rs b/lexical-util/src/error.rs index e871c70c..a0ac0192 100644 --- a/lexical-util/src/error.rs +++ b/lexical-util/src/error.rs @@ -92,6 +92,8 @@ pub enum Error { InvalidConsecutiveExponentDigitSeparator, /// Invalid flags were set without the format feature. InvalidFlags, + /// If the operation is unsupported. + Unsupported, // OPTION ERRORS /// Invalid NaN string: must start with an `n` character. @@ -184,6 +186,7 @@ impl Error { Self::InvalidConsecutiveFractionDigitSeparator => "'enabled consecutive digit separators in the fraction without setting a valid location'", Self::InvalidConsecutiveExponentDigitSeparator => "'enabled consecutive digit separators in the exponent without setting a valid location'", Self::InvalidFlags => "'invalid flags enabled without the format feature'", + Self::Unsupported => "the desired operation is unsupported for this format", // OPTION ERRORS Self::InvalidNanString => "'NaN string must started with `n`'", @@ -249,6 +252,7 @@ impl Error { Self::InvalidConsecutiveFractionDigitSeparator => None, Self::InvalidConsecutiveExponentDigitSeparator => None, Self::InvalidFlags => None, + Self::Unsupported => None, // OPTION ERRORS Self::InvalidNanString => None, @@ -314,6 +318,7 @@ impl Error { InvalidConsecutiveExponentDigitSeparator ); is_error_type!(is_invalid_flags, InvalidFlags); + is_error_type!(is_unsupported, Unsupported); is_error_type!(is_invalid_nan_string, InvalidNanString); is_error_type!(is_nan_string_too_long, NanStringTooLong); is_error_type!(is_invalid_inf_string, InvalidInfString); @@ -411,6 +416,7 @@ impl fmt::Display for Error { format_message!(formatter, description) }, Self::InvalidFlags => format_message!(formatter, description), + Self::Unsupported => format_message!(formatter, description), // OPTION ERRORS Self::InvalidNanString => options_message!(formatter, description), diff --git a/lexical-util/src/feature_format.rs b/lexical-util/src/feature_format.rs index eeb7f7bc..133e4a64 100644 --- a/lexical-util/src/feature_format.rs +++ b/lexical-util/src/feature_format.rs @@ -642,6 +642,78 @@ impl NumberFormat { Self::REQUIRED_FRACTION_DIGITS_WITH_EXPONENT } + /// If the format supports parsing integers. + /// + /// See [`supports_parsing_integers`][Self::supports_parsing_integers]. + pub const SUPPORTS_PARSING_INTEGERS: bool = from_flag!(FORMAT, SUPPORTS_PARSING_INTEGERS); + + /// Get if the format supports parsing integers. + /// + /// Can only be modified with [`feature`][crate#features] `format`. Defaults + /// to [`true`]. + /// + /// # Used For + /// + /// - Parse Integer + #[inline(always)] + pub const fn supports_parsing_integers(&self) -> bool { + Self::SUPPORTS_PARSING_INTEGERS + } + + /// If the format supports parsing floats. + /// + /// See [`supports_parsing_floats`][Self::supports_parsing_floats]. + pub const SUPPORTS_PARSING_FLOATS: bool = from_flag!(FORMAT, SUPPORTS_PARSING_FLOATS); + + /// Get if the format supports parsing floats. + /// + /// Can only be modified with [`feature`][crate#features] `format`. Defaults + /// to [`true`]. + /// + /// # Used For + /// + /// - Parse Float + #[inline(always)] + pub const fn supports_parsing_floats(&self) -> bool { + Self::SUPPORTS_PARSING_FLOATS + } + + /// If the format supports writing integers. + /// + /// See [`supports_writing_integers`][Self::supports_writing_integers]. + pub const SUPPORTS_WRITING_INTEGERS: bool = from_flag!(FORMAT, SUPPORTS_WRITING_INTEGERS); + + /// Get if the format supports writing integers. + /// + /// Can only be modified with [`feature`][crate#features] `format`. Defaults + /// to [`true`]. + /// + /// # Used For + /// + /// - Write Integer + #[inline(always)] + pub const fn supports_writing_integers(&self) -> bool { + Self::SUPPORTS_WRITING_INTEGERS + } + + /// If the format supports writing floats. + /// + /// See [`supports_writing_floats`][Self::supports_writing_floats]. + pub const SUPPORTS_WRITING_FLOATS: bool = from_flag!(FORMAT, SUPPORTS_WRITING_FLOATS); + + /// Get if the format supports writing floats. + /// + /// Can only be modified with [`feature`][crate#features] `format`. Defaults + /// to [`true`]. + /// + /// # Used For + /// + /// - Write Float + #[inline(always)] + pub const fn supports_writing_floats(&self) -> bool { + Self::SUPPORTS_WRITING_FLOATS + } + // DIGIT SEPARATOR FLAGS & MASKS /// If digit separators are allowed between integer digits. diff --git a/lexical-util/src/format_builder.rs b/lexical-util/src/format_builder.rs index dc85a238..55799dc1 100644 --- a/lexical-util/src/format_builder.rs +++ b/lexical-util/src/format_builder.rs @@ -306,6 +306,10 @@ const fn unwrap_or_zero(option: OptionU8) -> u8 { [`required_integer_digits_with_exponent`]: Self::required_integer_digits_with_exponent\n [`required_fraction_digits_with_exponent`]: Self::required_fraction_digits_with_exponent\n [`case_sensitive_exponent`]: Self::case_sensitive_exponent\n +[`supports_parsing_integers`]: Self::supports_parsing_integers\n +[`supports_parsing_floats`]: Self::supports_parsing_floats\n +[`supports_writing_integers`]: Self::supports_writing_integers\n +[`supports_writing_floats`]: Self::supports_writing_floats\n [`integer_internal_digit_separator`]: Self::integer_internal_digit_separator\n [`fraction_internal_digit_separator`]: Self::fraction_internal_digit_separator\n [`exponent_internal_digit_separator`]: Self::exponent_internal_digit_separator\n @@ -339,8 +343,12 @@ const fn unwrap_or_zero(option: OptionU8) -> u8 { [`no_integer_leading_zeros`]: https://github.com/Alexhuszagh/rust-lexical/blob/c6c5052/lexical-util/src/format_builder.rs#L741\n [`no_float_leading_zeros`]: https://github.com/Alexhuszagh/rust-lexical/blob/c6c5052/lexical-util/src/format_builder.rs#L749\n [`required_exponent_notation`]: https://github.com/Alexhuszagh/rust-lexical/blob/c6c5052/lexical-util/src/format_builder.rs#L757\n -[`required_integer_digits_with_exponent`]: TODO\n -[`required_fraction_digits_with_exponent`]: TODO\n +[`required_integer_digits_with_exponent`]: https://github.com/Alexhuszagh/rust-lexical/blob/0cad692/lexical-util/src/format_builder.rs#L1129\n +[`required_fraction_digits_with_exponent`]: https://github.com/Alexhuszagh/rust-lexical/blob/0cad692/lexical-util/src/format_builder.rs#L1149\n +[`supports_parsing_integers`]: TODO\n +[`supports_parsing_floats`]: TODO\n +[`supports_writing_integers`]: TODO\n +[`supports_writing_floats`]: TODO\n [`case_sensitive_exponent`]: https://github.com/Alexhuszagh/rust-lexical/blob/c6c5052/lexical-util/src/format_builder.rs#L765\n [`integer_internal_digit_separator`]: https://github.com/Alexhuszagh/rust-lexical/blob/c6c5052/lexical-util/src/format_builder.rs#L793\n [`fraction_internal_digit_separator`]: https://github.com/Alexhuszagh/rust-lexical/blob/c6c5052/lexical-util/src/format_builder.rs#L805\n @@ -396,6 +404,10 @@ pub struct NumberFormatBuilder { no_integer_leading_zeros: bool, no_float_leading_zeros: bool, required_exponent_notation: bool, + supports_parsing_integers: bool, + supports_parsing_floats: bool, + supports_writing_integers: bool, + supports_writing_floats: bool, case_sensitive_exponent: bool, case_sensitive_base_prefix: bool, case_sensitive_base_suffix: bool, @@ -454,6 +466,12 @@ impl NumberFormatBuilder { /// `false` /// - [`required_integer_digits_with_exponent`][Self::required_integer_digits_with_exponent] -`false` /// - [`required_fraction_digits_with_exponent`][Self::required_fraction_digits_with_exponent] -`false` + /// - [`supports_parsing_integers`][Self::supports_parsing_integers] - + /// `true` + /// - [`supports_parsing_floats`][Self::supports_parsing_floats] - `true` + /// - [`supports_writing_integers`][Self::supports_writing_integers] - + /// `true` + /// - [`supports_writing_floats`][Self::supports_writing_floats] - `true` /// - [`case_sensitive_exponent`][Self::get_case_sensitive_exponent] - /// `false` /// - [`case_sensitive_base_prefix`][Self::get_case_sensitive_base_prefix] - @@ -503,6 +521,10 @@ impl NumberFormatBuilder { case_sensitive_base_suffix: false, required_integer_digits_with_exponent: false, required_fraction_digits_with_exponent: false, + supports_parsing_integers: true, + supports_parsing_floats: true, + supports_writing_integers: true, + supports_writing_floats: true, integer_internal_digit_separator: false, fraction_internal_digit_separator: false, exponent_internal_digit_separator: false, @@ -1150,6 +1172,46 @@ impl NumberFormatBuilder { self.required_fraction_digits_with_exponent } + /// Get if the format supports parsing integers. + /// + /// # Used For + /// + /// - Parse Integer + #[inline(always)] + pub fn get_supports_parsing_integers(&self) -> bool { + self.supports_parsing_integers + } + + /// Get if the format supports parsing floats. + /// + /// # Used For + /// + /// - Parse Float + #[inline(always)] + pub fn get_supports_parsing_floats(&self) -> bool { + self.supports_parsing_floats + } + + /// Get if the format supports writing integers. + /// + /// # Used For + /// + /// - Write Integer + #[inline(always)] + pub fn get_supports_writing_integers(&self) -> bool { + self.supports_writing_integers + } + + /// Get if the format supports writing floats. + /// + /// # Used For + /// + /// - Write Float + #[inline(always)] + pub fn get_supports_writing_floats(&self) -> bool { + self.supports_writing_floats + } + /// Get if digit separators are allowed between integer digits. /// /// This will not consider an input of only the digit separator @@ -2616,6 +2678,54 @@ impl NumberFormatBuilder { self } + /// Set if the format supports parsing integers. + /// + /// # Used For + /// + /// - Parse Integer + #[inline(always)] + #[cfg(feature = "format")] + pub const fn supports_parsing_integers(mut self, flag: bool) -> Self { + self.supports_parsing_integers = flag; + self + } + + /// Set if the format supports parsing floats. + /// + /// # Used For + /// + /// - Parse Float + #[inline(always)] + #[cfg(feature = "format")] + pub const fn supports_parsing_floats(mut self, flag: bool) -> Self { + self.supports_parsing_floats = flag; + self + } + + /// Set if the format supports writing integers. + /// + /// # Used For + /// + /// - Write Integer + #[inline(always)] + #[cfg(feature = "format")] + pub const fn supports_writing_integers(mut self, flag: bool) -> Self { + self.supports_writing_integers = flag; + self + } + + /// Set if the format supports writing floats. + /// + /// # Used For + /// + /// - Write Float + #[inline(always)] + #[cfg(feature = "format")] + pub const fn supports_writing_floats(mut self, flag: bool) -> Self { + self.supports_writing_floats = flag; + self + } + /// Set if digit separators are allowed between integer digits. /// /// This will not consider an input of only the digit separator @@ -3382,6 +3492,10 @@ impl NumberFormatBuilder { self.case_sensitive_base_suffix, CASE_SENSITIVE_BASE_SUFFIX ; self.required_integer_digits_with_exponent, REQUIRED_INTEGER_DIGITS_WITH_EXPONENT ; self.required_fraction_digits_with_exponent, REQUIRED_FRACTION_DIGITS_WITH_EXPONENT ; + self.supports_parsing_integers, SUPPORTS_PARSING_INTEGERS ; + self.supports_parsing_floats, SUPPORTS_PARSING_FLOATS ; + self.supports_writing_integers, SUPPORTS_WRITING_INTEGERS ; + self.supports_writing_floats, SUPPORTS_WRITING_FLOATS ; self.integer_internal_digit_separator, INTEGER_INTERNAL_DIGIT_SEPARATOR ; self.fraction_internal_digit_separator, FRACTION_INTERNAL_DIGIT_SEPARATOR ; self.exponent_internal_digit_separator, EXPONENT_INTERNAL_DIGIT_SEPARATOR ; @@ -3483,6 +3597,10 @@ impl NumberFormatBuilder { format, REQUIRED_FRACTION_DIGITS_WITH_EXPONENT ), + supports_parsing_integers: has_flag!(format, SUPPORTS_PARSING_INTEGERS), + supports_parsing_floats: has_flag!(format, SUPPORTS_PARSING_FLOATS), + supports_writing_integers: has_flag!(format, SUPPORTS_WRITING_INTEGERS), + supports_writing_floats: has_flag!(format, SUPPORTS_WRITING_FLOATS), integer_internal_digit_separator: has_flag!(format, INTEGER_INTERNAL_DIGIT_SEPARATOR), fraction_internal_digit_separator: has_flag!(format, FRACTION_INTERNAL_DIGIT_SEPARATOR), exponent_internal_digit_separator: has_flag!(format, EXPONENT_INTERNAL_DIGIT_SEPARATOR), diff --git a/lexical-util/src/format_flags.rs b/lexical-util/src/format_flags.rs index 4cf51f22..2b93a75e 100644 --- a/lexical-util/src/format_flags.rs +++ b/lexical-util/src/format_flags.rs @@ -25,7 +25,7 @@ //! //! 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 //! +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ -//! |e/P|e/S|I/E|F/E| | +//! |e/P|e/S|I/E|F/E|p/I|p/F|w/I|w/F| | //! +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ //! //! 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 @@ -60,6 +60,10 @@ //! e/S = Case-sensitive base suffix. //! I/E = Require integer digits with exponent. //! F/E = Require fraction digits with exponent. +//! p/I = The format supports parsing integers. +//! p/F = The format supports parsing floats. +//! w/I = The format supports writing integers. +//! w/F = The format supports writing floats. //! //! Digit Separator Flags: //! I/I = Integer internal digit separator. @@ -345,6 +349,18 @@ pub const REQUIRED_INTEGER_DIGITS_WITH_EXPONENT: u128 = 1 << 18; /// notation, if the decimal point is present. pub const REQUIRED_FRACTION_DIGITS_WITH_EXPONENT: u128 = 1 << 19; +/// If the format supports parsing integers. +pub const SUPPORTS_PARSING_INTEGERS: u128 = 1 << 20; + +/// If the format supports parsing floats. +pub const SUPPORTS_PARSING_FLOATS: u128 = 1 << 21; + +/// If the format supports parsing integers. +pub const SUPPORTS_WRITING_INTEGERS: u128 = 1 << 22; + +/// If the format supports parsing floats. +pub const SUPPORTS_WRITING_FLOATS: u128 = 1 << 23; + // Non-digit separator flags. const _: () = assert!(REQUIRED_INTEGER_DIGITS == 1); check_subsequent_flags!(REQUIRED_INTEGER_DIGITS, REQUIRED_FRACTION_DIGITS); @@ -367,6 +383,10 @@ check_subsequent_flags!(CASE_SENSITIVE_EXPONENT, CASE_SENSITIVE_BASE_PREFIX); check_subsequent_flags!(CASE_SENSITIVE_BASE_PREFIX, CASE_SENSITIVE_BASE_SUFFIX); check_subsequent_flags!(CASE_SENSITIVE_BASE_SUFFIX, REQUIRED_INTEGER_DIGITS_WITH_EXPONENT); check_subsequent_flags!(REQUIRED_INTEGER_DIGITS_WITH_EXPONENT, REQUIRED_FRACTION_DIGITS_WITH_EXPONENT); +check_subsequent_flags!(REQUIRED_FRACTION_DIGITS_WITH_EXPONENT, SUPPORTS_PARSING_INTEGERS); +check_subsequent_flags!(SUPPORTS_PARSING_INTEGERS, SUPPORTS_PARSING_FLOATS); +check_subsequent_flags!(SUPPORTS_PARSING_FLOATS, SUPPORTS_WRITING_INTEGERS); +check_subsequent_flags!(SUPPORTS_WRITING_INTEGERS, SUPPORTS_WRITING_FLOATS); // DIGIT SEPARATOR FLAGS & MASKS // ----------------------------- @@ -553,6 +573,9 @@ pub const FLAG_MASK: u128 = /// omitting those that are handled prior. This limits the /// number of match paths required to determine the correct /// interface. +/// +/// Note that this is mostly a legacy constant, since we do +/// constant evaluation which is always at compile time. #[doc(hidden)] pub const INTERFACE_FLAG_MASK: u128 = REQUIRED_DIGITS | diff --git a/lexical-util/src/not_feature_format.rs b/lexical-util/src/not_feature_format.rs index 406d1ee7..ab83bf23 100644 --- a/lexical-util/src/not_feature_format.rs +++ b/lexical-util/src/not_feature_format.rs @@ -43,28 +43,32 @@ use crate::format_flags as flags; /// 19. [`case_sensitive_base_suffix`][NumberFormat::case_sensitive_base_suffix] /// 20. [`required_integer_digits_with_exponent`][NumberFormat::required_integer_digits_with_exponent] /// 21. [`required_fraction_digits_with_exponent`][NumberFormat::required_fraction_digits_with_exponent] -/// 22. [`integer_internal_digit_separator`][NumberFormat::integer_internal_digit_separator] -/// 23. [`fraction_internal_digit_separator`][NumberFormat::fraction_internal_digit_separator] -/// 24. [`exponent_internal_digit_separator`][NumberFormat::exponent_internal_digit_separator] -/// 25. [`internal_digit_separator`][NumberFormat::internal_digit_separator] -/// 26. [`integer_leading_digit_separator`][NumberFormat::integer_leading_digit_separator] -/// 27. [`fraction_leading_digit_separator`][NumberFormat::fraction_leading_digit_separator] -/// 28. [`exponent_leading_digit_separator`][NumberFormat::exponent_leading_digit_separator] -/// 29. [`leading_digit_separator`][NumberFormat::leading_digit_separator] -/// 30. [`integer_trailing_digit_separator`][NumberFormat::integer_trailing_digit_separator] -/// 31. [`fraction_trailing_digit_separator`][NumberFormat::fraction_trailing_digit_separator] -/// 32. [`exponent_trailing_digit_separator`][NumberFormat::exponent_trailing_digit_separator] -/// 33. [`trailing_digit_separator`][NumberFormat::trailing_digit_separator] -/// 34. [`integer_consecutive_digit_separator`][NumberFormat::integer_consecutive_digit_separator] -/// 35. [`fraction_consecutive_digit_separator`][NumberFormat::fraction_consecutive_digit_separator] -/// 36. [`exponent_consecutive_digit_separator`][NumberFormat::exponent_consecutive_digit_separator] -/// 37. [`consecutive_digit_separator`][NumberFormat::consecutive_digit_separator] -/// 38. [`special_digit_separator`][NumberFormat::special_digit_separator] -/// 39. [`digit_separator`][NumberFormat::digit_separator] -/// 40. [`base_prefix`][NumberFormat::base_prefix] -/// 41. [`base_suffix`][NumberFormat::base_suffix] -/// 42. [`exponent_base`][NumberFormat::exponent_base] -/// 43. [`exponent_radix`][NumberFormat::exponent_radix] +/// 22. [`supports_parsing_integers`][NumberFormat::supports_parsing_integers] +/// 23. [`supports_parsing_floats`][NumberFormat::supports_parsing_floats] +/// @4. [`supports_writing_integers`][NumberFormat::supports_writing_integers] +/// 25. [`supports_writing_floats`][NumberFormat::supports_writing_floats] +/// 26. [`integer_internal_digit_separator`][NumberFormat::integer_internal_digit_separator] +/// 27. [`fraction_internal_digit_separator`][NumberFormat::fraction_internal_digit_separator] +/// 28. [`exponent_internal_digit_separator`][NumberFormat::exponent_internal_digit_separator] +/// 29. [`internal_digit_separator`][NumberFormat::internal_digit_separator] +/// 30. [`integer_leading_digit_separator`][NumberFormat::integer_leading_digit_separator] +/// 31. [`fraction_leading_digit_separator`][NumberFormat::fraction_leading_digit_separator] +/// 32. [`exponent_leading_digit_separator`][NumberFormat::exponent_leading_digit_separator] +/// 33. [`leading_digit_separator`][NumberFormat::leading_digit_separator] +/// 34. [`integer_trailing_digit_separator`][NumberFormat::integer_trailing_digit_separator] +/// 35. [`fraction_trailing_digit_separator`][NumberFormat::fraction_trailing_digit_separator] +/// 36. [`exponent_trailing_digit_separator`][NumberFormat::exponent_trailing_digit_separator] +/// 37. [`trailing_digit_separator`][NumberFormat::trailing_digit_separator] +/// 38. [`integer_consecutive_digit_separator`][NumberFormat::integer_consecutive_digit_separator] +/// 39. [`fraction_consecutive_digit_separator`][NumberFormat::fraction_consecutive_digit_separator] +/// 40. [`exponent_consecutive_digit_separator`][NumberFormat::exponent_consecutive_digit_separator] +/// 41. [`consecutive_digit_separator`][NumberFormat::consecutive_digit_separator] +/// 42. [`special_digit_separator`][NumberFormat::special_digit_separator] +/// 43. [`digit_separator`][NumberFormat::digit_separator] +/// 44. [`base_prefix`][NumberFormat::base_prefix] +/// 45. [`base_suffix`][NumberFormat::base_suffix] +/// 46. [`exponent_base`][NumberFormat::exponent_base] +/// 47. [`exponent_radix`][NumberFormat::exponent_radix] /// /// This should always be constructed via [`NumberFormatBuilder`]. /// See [`NumberFormatBuilder`] for the fields for the packed struct. @@ -649,6 +653,78 @@ impl NumberFormat { Self::REQUIRED_FRACTION_DIGITS_WITH_EXPONENT } + /// If the format supports parsing integers. + /// + /// See [`supports_parsing_integers`][Self::supports_parsing_integers]. + pub const SUPPORTS_PARSING_INTEGERS: bool = true; + + /// Get if the format supports parsing integers. + /// + /// Can only be modified with [`feature`][crate#features] `format`. Defaults + /// to [`true`]. + /// + /// # Used For + /// + /// - Parse Integer + #[inline(always)] + pub const fn supports_parsing_integers(&self) -> bool { + Self::SUPPORTS_PARSING_INTEGERS + } + + /// If the format supports parsing floats. + /// + /// See [`supports_parsing_floats`][Self::supports_parsing_floats]. + pub const SUPPORTS_PARSING_FLOATS: bool = true; + + /// Get if the format supports parsing floats. + /// + /// Can only be modified with [`feature`][crate#features] `format`. Defaults + /// to [`true`]. + /// + /// # Used For + /// + /// - Parse Float + #[inline(always)] + pub const fn supports_parsing_floats(&self) -> bool { + Self::SUPPORTS_PARSING_FLOATS + } + + /// If the format supports writing integers. + /// + /// See [`supports_writing_integers`][Self::supports_writing_integers]. + pub const SUPPORTS_WRITING_INTEGERS: bool = true; + + /// Get if the format supports writing integers. + /// + /// Can only be modified with [`feature`][crate#features] `format`. Defaults + /// to [`true`]. + /// + /// # Used For + /// + /// - Write Integer + #[inline(always)] + pub const fn supports_writing_integers(&self) -> bool { + Self::SUPPORTS_WRITING_INTEGERS + } + + /// If the format supports writing floats. + /// + /// See [`supports_writing_floats`][Self::supports_writing_floats]. + pub const SUPPORTS_WRITING_FLOATS: bool = true; + + /// Get if the format supports writing floats. + /// + /// Can only be modified with [`feature`][crate#features] `format`. Defaults + /// to [`true`]. + /// + /// # Used For + /// + /// - Write Float + #[inline(always)] + pub const fn supports_writing_floats(&self) -> bool { + Self::SUPPORTS_WRITING_FLOATS + } + // DIGIT SEPARATOR FLAGS & MASKS // If digit separators are allowed between integer digits. diff --git a/lexical-write-float/src/api.rs b/lexical-write-float/src/api.rs index 56b03d49..0718ec7f 100644 --- a/lexical-write-float/src/api.rs +++ b/lexical-write-float/src/api.rs @@ -4,9 +4,10 @@ #[cfg(feature = "f16")] use lexical_util::bf16::bf16; +use lexical_util::error::Error; #[cfg(feature = "f16")] use lexical_util::f16::f16; -use lexical_util::format::STANDARD; +use lexical_util::format::{NumberFormat, STANDARD}; use lexical_util::{to_lexical, to_lexical_with_options}; use crate::options::Options; @@ -38,6 +39,10 @@ macro_rules! float_to_lexical { options: &Self::Options, ) -> &'a mut [u8] { + let format = NumberFormat::<{ FORMAT }> {}; + if !format.supports_writing_floats() { + core::panic!("{}", Error::Unsupported.description()); + } let count = self.write_float::<{ FORMAT }>(bytes, &options); &mut bytes[..count] } diff --git a/lexical-write-float/tests/api_tests.rs b/lexical-write-float/tests/api_tests.rs index 45ed9fd2..982a31ef 100644 --- a/lexical-write-float/tests/api_tests.rs +++ b/lexical-write-float/tests/api_tests.rs @@ -1,4 +1,6 @@ use lexical_util::constants::BUFFER_SIZE; +#[cfg(any(feature = "format", feature = "power-of-two"))] +use lexical_util::format::NumberFormatBuilder; use lexical_util::format::STANDARD; use lexical_write_float::{Options, ToLexical, ToLexicalWithOptions}; @@ -64,8 +66,6 @@ fn invalid_inf_test() { fn hex_test() { use core::num; - use lexical_util::format::NumberFormatBuilder; - const BASE16_2_10: u128 = NumberFormatBuilder::new() .mantissa_radix(16) .exponent_base(num::NonZeroU8::new(2)) @@ -78,3 +78,30 @@ fn hex_test() { let result = float.to_lexical_with_options::(&mut buffer, &HEX_OPTIONS); assert_eq!(result, b"3.039^12"); } + +#[test] +#[should_panic] +#[cfg(feature = "format")] +fn unsupported_test() { + const FORMAT: u128 = NumberFormatBuilder::new().supports_writing_floats(false).build_strict(); + const OPTIONS: Options = Options::new(); + + let mut buffer = [b'\x00'; BUFFER_SIZE]; + let float = 12345.0f64; + _ = float.to_lexical_with_options::(&mut buffer, &OPTIONS); +} + +#[test] +#[cfg(feature = "format")] +fn supported_test() { + const FORMAT: u128 = NumberFormatBuilder::new() + .supports_parsing_integers(false) + .supports_parsing_floats(false) + .supports_writing_integers(false) + .build_strict(); + const OPTIONS: Options = Options::new(); + + let mut buffer = [b'\x00'; BUFFER_SIZE]; + let float = 12345.0f64; + assert_eq!(b"12345.0", float.to_lexical_with_options::(&mut buffer, &OPTIONS)); +} diff --git a/lexical-write-integer/src/api.rs b/lexical-write-integer/src/api.rs index 08f58534..351bb15e 100644 --- a/lexical-write-integer/src/api.rs +++ b/lexical-write-integer/src/api.rs @@ -2,6 +2,7 @@ #![doc(hidden)] +use lexical_util::error::Error; use lexical_util::format::{NumberFormat, STANDARD}; use lexical_util::num::SignedInteger; use lexical_util::{to_lexical, to_lexical_with_options}; @@ -93,7 +94,12 @@ macro_rules! unsigned_to_lexical { ) -> &'a mut [u8] { _ = options; - assert!(NumberFormat::<{ FORMAT }> {}.is_valid()); + let format = NumberFormat::<{ FORMAT }> {}; + if !format.supports_writing_integers() { + core::panic!("{}", Error::Unsupported.description()); + } else if !format.is_valid() { + core::panic!("{}", format.error().description()); + } let len = unsigned::<$t, FORMAT>(self, bytes); &mut bytes[..len] } @@ -128,7 +134,12 @@ macro_rules! signed_to_lexical { ) -> &'a mut [u8] { _ = options; - assert!(NumberFormat::<{ FORMAT }> {}.is_valid()); + let format = NumberFormat::<{ FORMAT }> {}; + if !format.supports_writing_integers() { + core::panic!("{}", Error::Unsupported.description()); + } else if !format.is_valid() { + core::panic!("{}", format.error().description()); + } let len = signed::<$signed, $unsigned, FORMAT>(self, bytes); &mut bytes[..len] } diff --git a/lexical-write-integer/tests/api_tests.rs b/lexical-write-integer/tests/api_tests.rs index c2239be0..45179702 100644 --- a/lexical-write-integer/tests/api_tests.rs +++ b/lexical-write-integer/tests/api_tests.rs @@ -215,6 +215,36 @@ fn options_radix_test() { assert_eq!(b"A8", 128u8.to_lexical_with_options::<{ FORMAT }>(&mut buffer, &OPTIONS)); } +#[test] +#[should_panic] +#[cfg(feature = "format")] +fn unsupported_test() { + const FORMAT: u128 = NumberFormatBuilder::new().supports_writing_integers(false).build_strict(); + const OPTIONS: Options = Options::new(); + + let mut buffer = [b'\x00'; BUFFER_SIZE]; + let integer = 12345i64; + _ = integer.to_lexical_with_options::(&mut buffer, &OPTIONS); +} + +#[test] +#[cfg(feature = "format")] +fn supported_test() { + const FORMAT: u128 = NumberFormatBuilder::new() + .supports_parsing_integers(false) + .supports_parsing_floats(false) + .supports_writing_floats(false) + .build_strict(); + const OPTIONS: Options = Options::new(); + + let mut buffer = [b'\x00'; BUFFER_SIZE]; + let integer = 12345i64; + assert_eq!(b"12345", integer.to_lexical_with_options::(&mut buffer, &OPTIONS)); + + let integer = 12345u64; + assert_eq!(b"12345", integer.to_lexical_with_options::(&mut buffer, &OPTIONS)); +} + fn roundtrip(x: T) -> T where T: Roundtrip,