From 7831ec164362e62d0b6d591a31aaf98b13e9a5d5 Mon Sep 17 00:00:00 2001 From: David Hewitt Date: Mon, 6 Nov 2023 11:28:17 +0000 Subject: [PATCH 1/4] no longer accept '-' or '+' as valid numeric strings --- src/numbers.rs | 58 +++++++++++++++++++++----------------------------- tests/main.rs | 28 ++++++++++++++++++++++-- 2 files changed, 50 insertions(+), 36 deletions(-) diff --git a/src/numbers.rs b/src/numbers.rs index 85a4190..29b24ec 100644 --- a/src/numbers.rs +++ b/src/numbers.rs @@ -2,35 +2,28 @@ /// /// This is around 2x faster than using `str::parse::()` pub fn int_parse_str(s: &str) -> Option { - fast_parse_int(s.bytes()) + int_parse_bytes(s.as_bytes()) } /// Parse bytes as an int. pub fn int_parse_bytes(s: &[u8]) -> Option { - fast_parse_int(s.iter().copied()) -} + let (neg, first_digit, digits) = match s { + [b'-', first, digits @ ..] => (true, first, digits), + [b'+', first, digits @ ..] | [first, digits @ ..] => (false, first, digits), + _ => return None, + }; -pub fn fast_parse_int(mut bytes: I) -> Option -where - I: Iterator, -{ - let mut result: i64 = 0; - let neg = match bytes.next() { - Some(b'-') => true, - Some(b'+') => false, - Some(c) if c.is_ascii_digit() => { - result = (c & 0x0f) as i64; - false - } + let mut result = match first_digit { + b'0' => 0, + b'1'..=b'9' => (first_digit & 0x0f) as i64, _ => return None, }; - for digit in bytes { + for digit in digits { + result = result.checked_mul(10)?; match digit { - b'0'..=b'9' => { - result = result.checked_mul(10)?; - result = result.checked_add((digit & 0x0f) as i64)? - } + b'0' => {} + b'1'..=b'9' => result = result.checked_add((digit & 0x0f) as i64)?, _ => return None, } } @@ -58,30 +51,27 @@ impl IntFloat { /// /// This is around 2x faster than using `str::parse::()` pub fn float_parse_str(s: &str) -> IntFloat { - fast_parse_float(s.bytes()) + float_parse_bytes(s.as_bytes()) } /// Parse bytes as an float. pub fn float_parse_bytes(s: &[u8]) -> IntFloat { - fast_parse_float(s.iter().copied()) -} + let (neg, first_digit, digits) = match s { + [b'-', first, digits @ ..] => (true, first, digits), + [b'+', first, digits @ ..] | [first, digits @ ..] => (false, first, digits), + _ => return IntFloat::Err, + }; -pub fn fast_parse_float(mut bytes: I) -> IntFloat -where - I: Iterator, -{ - let mut int_part: i64 = 0; - let neg = match bytes.next() { - Some(b'-') => true, - Some(c) if c.is_ascii_digit() => { - int_part = (c & 0x0f) as i64; - false - } + let mut int_part = match first_digit { + b'0' => 0, + b'1'..=b'9' => (first_digit & 0x0f) as i64, _ => return IntFloat::Err, }; let mut found_dot = false; + let mut bytes = digits.iter().copied(); + for digit in bytes.by_ref() { match digit { b'0'..=b'9' => { diff --git a/tests/main.rs b/tests/main.rs index 50a9df9..effc3e7 100644 --- a/tests/main.rs +++ b/tests/main.rs @@ -5,8 +5,8 @@ use chrono::{Datelike, FixedOffset as ChronoFixedOffset, NaiveDate, NaiveDateTim use strum::EnumMessage; use speedate::{ - float_parse_str, int_parse_str, Date, DateTime, Duration, MicrosecondsPrecisionOverflowBehavior, ParseError, Time, - TimeConfig, TimeConfigBuilder, + float_parse_bytes, float_parse_str, int_parse_bytes, int_parse_str, Date, DateTime, Duration, IntFloat, + MicrosecondsPrecisionOverflowBehavior, ParseError, Time, TimeConfig, TimeConfigBuilder, }; /// macro for expected values @@ -1348,3 +1348,27 @@ fn test_time_config_builder() { ); assert_eq!(TimeConfigBuilder::new().build(), TimeConfig::builder().build()); } + +#[test] +fn date_dash_err() { + let error = match Date::parse_str("-") { + Ok(_) => panic!("unexpectedly valid"), + Err(e) => e, + }; + assert_eq!(error, ParseError::TooShort); + assert_eq!(error.to_string(), "too_short"); + assert_eq!(error.get_documentation(), Some("input is too short")); +} + +#[test] +fn number_dash_err() { + assert!(int_parse_str("-").is_none()); + assert!(int_parse_str("+").is_none()); + assert!(int_parse_bytes(b"-").is_none()); + assert!(int_parse_bytes(b"+").is_none()); + + assert!(matches!(float_parse_str("-"), IntFloat::Err)); + assert!(matches!(float_parse_str("+"), IntFloat::Err)); + assert!(matches!(float_parse_bytes(b"-"), IntFloat::Err)); + assert!(matches!(float_parse_bytes(b"+"), IntFloat::Err)); +} From a8e8ef30447f6f74d0c8e02bf41e268e26587378 Mon Sep 17 00:00:00 2001 From: David Hewitt Date: Mon, 6 Nov 2023 11:41:11 +0000 Subject: [PATCH 2/4] unwrap error Co-authored-by: Samuel Colvin --- tests/main.rs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/tests/main.rs b/tests/main.rs index effc3e7..d39d795 100644 --- a/tests/main.rs +++ b/tests/main.rs @@ -1351,10 +1351,7 @@ fn test_time_config_builder() { #[test] fn date_dash_err() { - let error = match Date::parse_str("-") { - Ok(_) => panic!("unexpectedly valid"), - Err(e) => e, - }; + let error = match Date::parse_str("-").unwrap_err(); assert_eq!(error, ParseError::TooShort); assert_eq!(error.to_string(), "too_short"); assert_eq!(error.get_documentation(), Some("input is too short")); From a96c38a792d3f5535e5f364064e0941b1ffbc270 Mon Sep 17 00:00:00 2001 From: David Hewitt Date: Mon, 6 Nov 2023 11:42:10 +0000 Subject: [PATCH 3/4] Update tests/main.rs --- tests/main.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/main.rs b/tests/main.rs index d39d795..eb1a829 100644 --- a/tests/main.rs +++ b/tests/main.rs @@ -1351,7 +1351,7 @@ fn test_time_config_builder() { #[test] fn date_dash_err() { - let error = match Date::parse_str("-").unwrap_err(); + let error = Date::parse_str("-").unwrap_err(); assert_eq!(error, ParseError::TooShort); assert_eq!(error.to_string(), "too_short"); assert_eq!(error.get_documentation(), Some("input is too short")); From dc717e4724b31dd88ee2fcc3721c6773fb820129 Mon Sep 17 00:00:00 2001 From: Samuel Colvin Date: Mon, 6 Nov 2023 12:38:57 +0000 Subject: [PATCH 4/4] bump