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..eb1a829 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,24 @@ fn test_time_config_builder() { ); assert_eq!(TimeConfigBuilder::new().build(), TimeConfig::builder().build()); } + +#[test] +fn date_dash_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")); +} + +#[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)); +}