From 762094069e1b89f261097580b0b123ee6972b040 Mon Sep 17 00:00:00 2001 From: tormol Date: Sat, 18 May 2019 16:14:46 +0200 Subject: [PATCH 01/24] Fix quickcheck Arbitrary impl never generating DEL --- src/ascii_char.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ascii_char.rs b/src/ascii_char.rs index 9ab55f8..ecc410d 100644 --- a/src/ascii_char.rs +++ b/src/ascii_char.rs @@ -732,7 +732,7 @@ impl Arbitrary for AsciiChar { } 40...99 => { // Completely arbitrary characters - unsafe { AsciiChar::from_unchecked(g.gen_range(0, 0x7F) as u8) } + unsafe { AsciiChar::from_unchecked(g.gen_range(0, 0x80) as u8) } } _ => unreachable!(), } From 7a369e9a51bbcfac8f696abf93d8a6bf75e60389 Mon Sep 17 00:00:00 2001 From: tormol Date: Thu, 11 Jul 2019 21:20:44 +0200 Subject: [PATCH 02/24] Remove no_std docs link --- src/lib.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 58bb10f..4bd2540 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -13,8 +13,6 @@ //! A library that provides ASCII-only string and character types, equivalent to the `char`, `str` //! and `String` types in the standard library. //! -#![cfg_attr(feature = "std", doc="[The documentation for the `core` mode is here](https://tomprogrammer.github.io/rust-ascii/core/ascii/index.html).")] -#![cfg_attr(not(feature = "std"), doc="This is the documentation for `core` mode.")] //! Please refer to the readme file to learn about the different feature modes of this crate. //! //! # Requirements From 1717420e40beef2011645d39338fa24e72f1d2a5 Mon Sep 17 00:00:00 2001 From: tormol Date: Tue, 19 Mar 2019 20:17:18 +0100 Subject: [PATCH 03/24] Test optional dependencies on Travis --- .travis.yml | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index b8ef2fd..a6e16fa 100644 --- a/.travis.yml +++ b/.travis.yml @@ -14,10 +14,12 @@ before_script: script: - | - travis-cargo build && + travis-cargo test -- --no-default-features && travis-cargo test && - travis-cargo build -- --no-default-features && - travis-cargo test -- --no-default-features + travis-cargo test -- --all-features && + rm Cargo.lock && + # no version of quickcheck compiles with -Z minimal-versions, but serde does + travis-cargo --only nightly build -- -Z minimal-versions --features 'serde serde-test' after_success: - | From 716f53b5e1bd9b4074a3902b9da34d48eaa38490 Mon Sep 17 00:00:00 2001 From: tormol Date: Fri, 10 May 2019 19:42:08 +0200 Subject: [PATCH 04/24] Implement Clone, Copy and Eq for ToAsciiCharError --- src/ascii_char.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ascii_char.rs b/src/ascii_char.rs index ecc410d..2921e69 100644 --- a/src/ascii_char.rs +++ b/src/ascii_char.rs @@ -619,7 +619,7 @@ impl_into_partial_eq_ord!{char, AsciiChar::as_char} /// Error returned by `ToAsciiChar`. -#[derive(PartialEq)] +#[derive(Clone, Copy, PartialEq, Eq)] pub struct ToAsciiCharError(()); const ERRORMSG_CHAR: &'static str = "not an ASCII character"; From 0d15921729f1093acb9a7694ef692ba33aad9157 Mon Sep 17 00:00:00 2001 From: tormol Date: Sat, 13 Jul 2019 21:33:20 +0200 Subject: [PATCH 05/24] Implement ToAsciiChar for i8, u16 and u32 i8 is what c_char corresponds to on x86. Together with the impl for u8 this means ToAsciiChar is always implemented for c_char (on any relevant architecture). --- src/ascii_char.rs | 46 ++++++++++++++++++++++++++++++++++++---------- 1 file changed, 36 insertions(+), 10 deletions(-) diff --git a/src/ascii_char.rs b/src/ascii_char.rs index 2921e69..d476b17 100644 --- a/src/ascii_char.rs +++ b/src/ascii_char.rs @@ -675,12 +675,18 @@ impl ToAsciiChar for AsciiChar { impl ToAsciiChar for u8 { #[inline] fn to_ascii_char(self) -> Result { - unsafe { - if self <= 0x7F { - return Ok(self.to_ascii_char_unchecked()); - } - } - Err(ToAsciiCharError(())) + (self as u32).to_ascii_char() + } + #[inline] + unsafe fn to_ascii_char_unchecked(self) -> AsciiChar { + mem::transmute(self) + } +} + +impl ToAsciiChar for i8 { + #[inline] + fn to_ascii_char(self) -> Result { + (self as u32).to_ascii_char() } #[inline] unsafe fn to_ascii_char_unchecked(self) -> AsciiChar { @@ -690,13 +696,33 @@ impl ToAsciiChar for u8 { impl ToAsciiChar for char { #[inline] + fn to_ascii_char(self) -> Result { + (self as u32).to_ascii_char() + } + #[inline] + unsafe fn to_ascii_char_unchecked(self) -> AsciiChar { + (self as u32).to_ascii_char_unchecked() + } +} + +impl ToAsciiChar for u32 { fn to_ascii_char(self) -> Result { unsafe { - if self as u32 <= 0x7F { - return Ok(self.to_ascii_char_unchecked()); + match self { + 0..=127 => Ok(self.to_ascii_char_unchecked()), + _ => Err(ToAsciiCharError(())) } } - Err(ToAsciiCharError(())) + } + #[inline] + unsafe fn to_ascii_char_unchecked(self) -> AsciiChar { + (self as u8).to_ascii_char_unchecked() + } +} + +impl ToAsciiChar for u16 { + fn to_ascii_char(self) -> Result { + (self as u32).to_ascii_char() } #[inline] unsafe fn to_ascii_char_unchecked(self) -> AsciiChar { @@ -758,7 +784,7 @@ mod tests { assert_eq!(generic(A), Ok(A)); assert_eq!(generic(b'A'), Ok(A)); assert_eq!(generic('A'), Ok(A)); - assert!(generic(200).is_err()); + assert!(generic(200u16).is_err()); assert!(generic('λ').is_err()); } From 8a6c7798c202766bd57d70fb8d12739dd68fb9dc Mon Sep 17 00:00:00 2001 From: tormol Date: Thu, 11 Jul 2019 20:51:01 +0200 Subject: [PATCH 06/24] Remove unsound trait impls * impl From<&mut AsciiStr> for &mut [u8] * impl From<&mut AsciiStr> for &mut str They allow writing non-ASCII values to an AsciiStr which when read out as an AsciiChar will produce values outside the valid niche. --- src/ascii_str.rs | 50 +++++++++++++++++++++++++++++------------------- 1 file changed, 30 insertions(+), 20 deletions(-) diff --git a/src/ascii_str.rs b/src/ascii_str.rs index 831e6e1..43acc53 100644 --- a/src/ascii_str.rs +++ b/src/ascii_str.rs @@ -38,13 +38,13 @@ impl AsciiStr { /// Converts `&self` to a `&str` slice. #[inline] pub fn as_str(&self) -> &str { - From::from(self) + unsafe { &*(self as *const AsciiStr as *const str) } } /// Converts `&self` into a byte slice. #[inline] pub fn as_bytes(&self) -> &[u8] { - From::from(self) + unsafe { &*(self as *const AsciiStr as *const[u8]) } } /// Returns the entire string as slice of `AsciiChar`s. @@ -381,22 +381,32 @@ impl AsMut for [AsciiChar] { } } -macro_rules! impl_into { +impl<'a> From<&'a AsciiStr> for &'a [AsciiChar] { + #[inline] + fn from(astr: &AsciiStr) -> &[AsciiChar] { + &astr.slice + } +} +impl<'a> From<&'a mut AsciiStr> for &'a mut [AsciiChar] { + #[inline] + fn from(astr: &mut AsciiStr) -> &mut [AsciiChar] { + &mut astr.slice + } +} +impl<'a> From<&'a AsciiStr> for &'a [u8] { + #[inline] + fn from(astr: &AsciiStr) -> &[u8] { + astr.as_bytes() + } +} +impl<'a> From<&'a AsciiStr> for &'a str { + #[inline] + fn from(astr: &AsciiStr) -> &str { + astr.as_str() + } +} +macro_rules! widen_box { ($wider: ty) => { - impl<'a> From<&'a AsciiStr> for &'a$wider { - #[inline] - fn from(slice: &AsciiStr) -> &$wider { - let ptr = slice as *const AsciiStr as *const $wider; - unsafe { &*ptr } - } - } - impl<'a> From<&'a mut AsciiStr> for &'a mut $wider { - #[inline] - fn from(slice: &mut AsciiStr) -> &mut $wider { - let ptr = slice as *mut AsciiStr as *mut $wider; - unsafe { &mut *ptr } - } - } #[cfg(feature = "std")] impl From> for Box<$wider> { #[inline] @@ -407,9 +417,9 @@ macro_rules! impl_into { } } } -impl_into! {[AsciiChar]} -impl_into! {[u8]} -impl_into! {str} +widen_box! {[AsciiChar]} +widen_box! {[u8]} +widen_box! {str} impl fmt::Display for AsciiStr { #[inline] From 05c57e0ea9367bee523c232bb93dd7a010f1cd7d Mon Sep 17 00:00:00 2001 From: tormol Date: Sat, 13 Jul 2019 04:27:25 +0200 Subject: [PATCH 07/24] Remove quickcheck Updating it to 0.8 is difficult, and removing it makes the crate easier to maintain for Debian. --- .travis.yml | 3 +-- Cargo.toml | 1 - src/ascii_char.rs | 44 -------------------------------------------- src/ascii_string.rs | 25 ------------------------- src/lib.rs | 3 --- 5 files changed, 1 insertion(+), 75 deletions(-) diff --git a/.travis.yml b/.travis.yml index a6e16fa..776598f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -18,8 +18,7 @@ script: travis-cargo test && travis-cargo test -- --all-features && rm Cargo.lock && - # no version of quickcheck compiles with -Z minimal-versions, but serde does - travis-cargo --only nightly build -- -Z minimal-versions --features 'serde serde-test' + travis-cargo --only nightly build -- -Z minimal-versions --all-features after_success: - | diff --git a/Cargo.toml b/Cargo.toml index 3dc880f..20de2fb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,7 +9,6 @@ repository = "https://github.com/tomprogrammer/rust-ascii" version = "0.9.2" [dependencies] -quickcheck = { version = "0.6", optional = true } serde = { version = "1.0.25", optional = true } serde_test = { version = "1.0", optional = true } diff --git a/src/ascii_char.rs b/src/ascii_char.rs index d476b17..b94b687 100644 --- a/src/ascii_char.rs +++ b/src/ascii_char.rs @@ -4,9 +4,6 @@ // which would call the inherent methods if AsciiExt wasn't in scope. #![cfg_attr(feature = "std", allow(deprecated))] -#[cfg(feature = "quickcheck")] -use quickcheck::{Arbitrary, Gen}; - use core::mem; use core::cmp::Ordering; use core::{fmt, char}; @@ -730,47 +727,6 @@ impl ToAsciiChar for u16 { } } -#[cfg(feature = "quickcheck")] -impl Arbitrary for AsciiChar { - fn arbitrary(g: &mut G) -> Self { - let mode = g.gen_range(0, 100); - match mode { - 0...14 => { - // Control characters - unsafe { AsciiChar::from_unchecked(g.gen_range(0, 0x1F) as u8) } - } - 15...39 => { - // Characters often used in programming languages - *g.choose(&[ - AsciiChar::Space, AsciiChar::Tab, AsciiChar::LineFeed, AsciiChar::Tilde, - AsciiChar::Grave, AsciiChar::Exclamation, AsciiChar::At, AsciiChar::Hash, - AsciiChar::Dollar, AsciiChar::Percent, AsciiChar::Ampersand, - AsciiChar::Asterisk, AsciiChar::ParenOpen, AsciiChar::ParenClose, - AsciiChar::UnderScore, AsciiChar::Minus, AsciiChar::Equal, AsciiChar::Plus, - AsciiChar::BracketOpen, AsciiChar::BracketClose, AsciiChar::CurlyBraceOpen, - AsciiChar::CurlyBraceClose, AsciiChar::Colon, AsciiChar::Semicolon, - AsciiChar::Apostrophe, AsciiChar::Quotation, AsciiChar::BackSlash, - AsciiChar::VerticalBar, AsciiChar::Caret, AsciiChar::Comma, AsciiChar::LessThan, - AsciiChar::GreaterThan, AsciiChar::Dot, AsciiChar::Slash, AsciiChar::Question, - AsciiChar::_0, AsciiChar::_1, AsciiChar::_2, AsciiChar::_3, AsciiChar::_3, - AsciiChar::_4 , AsciiChar::_6, AsciiChar::_7, AsciiChar::_8, AsciiChar::_9, - ]).unwrap() - } - 40...99 => { - // Completely arbitrary characters - unsafe { AsciiChar::from_unchecked(g.gen_range(0, 0x80) as u8) } - } - _ => unreachable!(), - } - } - - fn shrink(&self) -> Box> { - Box::new((*self as u8).shrink().filter_map( - |x| AsciiChar::from(x).ok(), - )) - } -} - #[cfg(test)] mod tests { use super::{AsciiChar, ToAsciiChar, ToAsciiCharError}; diff --git a/src/ascii_string.rs b/src/ascii_string.rs index 249ea78..fc50782 100644 --- a/src/ascii_string.rs +++ b/src/ascii_string.rs @@ -9,9 +9,6 @@ use std::str::FromStr; use std::ops::{Deref, DerefMut, Add, AddAssign, Index, IndexMut}; use std::iter::FromIterator; -#[cfg(feature = "quickcheck")] -use quickcheck::{Arbitrary, Gen}; - use ascii_char::AsciiChar; use ascii_str::{AsciiStr, AsAsciiStr, AsAsciiStrError}; @@ -889,28 +886,6 @@ where } } -#[cfg(feature = "quickcheck")] -impl Arbitrary for AsciiString { - fn arbitrary(g: &mut G) -> Self { - let size = { - let s = g.size(); - g.gen_range(0, s) - }; - let mut s = AsciiString::with_capacity(size); - for _ in 0..size { - s.push(AsciiChar::arbitrary(g)); - } - s - } - - fn shrink(&self) -> Box> { - let chars: Vec = self.as_slice().to_vec(); - Box::new(chars.shrink().map( - |x| x.into_iter().collect::(), - )) - } -} - #[cfg(test)] mod tests { use std::str::FromStr; diff --git a/src/lib.rs b/src/lib.rs index 4bd2540..4363c15 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -32,9 +32,6 @@ #[cfg(feature = "std")] extern crate core; -#[cfg(feature = "quickcheck")] -extern crate quickcheck; - #[cfg(feature = "serde")] extern crate serde; From 76c7303a348ce90f67a1253ac6727a4e7edaff74 Mon Sep 17 00:00:00 2001 From: tormol Date: Wed, 4 Apr 2018 12:25:50 +0200 Subject: [PATCH 08/24] Remove (deprecated) AsciiExt --- src/ascii_char.rs | 76 ++++--------------------------------------- src/ascii_str.rs | 66 ------------------------------------- src/free_functions.rs | 2 -- 3 files changed, 6 insertions(+), 138 deletions(-) diff --git a/src/ascii_char.rs b/src/ascii_char.rs index b94b687..e520ca0 100644 --- a/src/ascii_char.rs +++ b/src/ascii_char.rs @@ -1,16 +1,10 @@ #![cfg_attr(rustfmt, rustfmt_skip)] -// #[allow(deprecated)] doesn't silence warnings on the method invocations, -// which would call the inherent methods if AsciiExt wasn't in scope. -#![cfg_attr(feature = "std", allow(deprecated))] - use core::mem; use core::cmp::Ordering; use core::{fmt, char}; #[cfg(feature = "std")] use std::error::Error; -#[cfg(feature = "std")] -use std::ascii::AsciiExt; #[allow(non_camel_case_types)] /// An ASCII character. It wraps a `u8`, with the highest bit always zero. @@ -531,48 +525,6 @@ impl fmt::Debug for AsciiChar { } } -#[cfg(feature = "std")] -impl AsciiExt for AsciiChar { - type Owned = AsciiChar; - - #[inline] - fn is_ascii(&self) -> bool { - true - } - - #[inline] - fn to_ascii_uppercase(&self) -> AsciiChar { - unsafe { - self.as_byte() - .to_ascii_uppercase() - .to_ascii_char_unchecked() - } - } - - #[inline] - fn to_ascii_lowercase(&self) -> AsciiChar { - unsafe { - self.as_byte() - .to_ascii_lowercase() - .to_ascii_char_unchecked() - } - } - - fn eq_ignore_ascii_case(&self, other: &Self) -> bool { - self.as_byte().eq_ignore_ascii_case(&other.as_byte()) - } - - #[inline] - fn make_ascii_uppercase(&mut self) { - *self = self.to_ascii_uppercase(); - } - - #[inline] - fn make_ascii_lowercase(&mut self) { - *self = self.to_ascii_lowercase(); - } -} - impl Default for AsciiChar { fn default() -> AsciiChar { AsciiChar::Null @@ -781,6 +733,12 @@ mod tests { assert_eq!(a.to_ascii_lowercase(), a); assert_eq!(a.to_ascii_uppercase(), A); + let mut mutable = (A,a); + mutable.0.make_ascii_lowercase(); + mutable.1.make_ascii_uppercase(); + assert_eq!(mutable.0, a); + assert_eq!(mutable.1, A); + assert!(LineFeed.eq_ignore_ascii_case(&LineFeed)); assert!(!LineFeed.eq_ignore_ascii_case(&CarriageReturn)); assert!(z.eq_ignore_ascii_case(&Z)); @@ -788,28 +746,6 @@ mod tests { assert!(!Z.eq_ignore_ascii_case(&DEL)); } - #[test] - #[cfg(feature = "std")] - fn ascii_ext() { - #[allow(deprecated)] - use std::ascii::AsciiExt; - assert!(AsciiExt::is_ascii(&Null)); - assert!(AsciiExt::is_ascii(&DEL)); - assert!(AsciiExt::eq_ignore_ascii_case(&a, &A)); - assert!(!AsciiExt::eq_ignore_ascii_case(&A, &At)); - assert_eq!(AsciiExt::to_ascii_lowercase(&A), a); - assert_eq!(AsciiExt::to_ascii_uppercase(&A), A); - assert_eq!(AsciiExt::to_ascii_lowercase(&a), a); - assert_eq!(AsciiExt::to_ascii_uppercase(&a), A); - assert_eq!(AsciiExt::to_ascii_lowercase(&At), At); - assert_eq!(AsciiExt::to_ascii_uppercase(&At), At); - let mut mutable = (A,a); - AsciiExt::make_ascii_lowercase(&mut mutable.0); - AsciiExt::make_ascii_uppercase(&mut mutable.1); - assert_eq!(mutable.0, a); - assert_eq!(mutable.1, A); - } - #[test] #[cfg(feature = "std")] fn fmt_ascii() { diff --git a/src/ascii_str.rs b/src/ascii_str.rs index 43acc53..55c897a 100644 --- a/src/ascii_str.rs +++ b/src/ascii_str.rs @@ -1,17 +1,11 @@ #![cfg_attr(rustfmt, rustfmt_skip)] -// #[allow(deprecated)] doesn't silence warnings on the method invocations, -// which would call the inherent methods if AsciiExt wasn't in scope. -#![cfg_attr(feature = "std", allow(deprecated))] - use core::fmt; use core::ops::{Index, IndexMut, Range, RangeTo, RangeFrom, RangeFull}; use core::slice::{Iter, IterMut}; #[cfg(feature = "std")] use std::error::Error; #[cfg(feature = "std")] -use std::ascii::AsciiExt; -#[cfg(feature = "std")] use std::ffi::CStr; use ascii_char::AsciiChar; @@ -476,47 +470,6 @@ impl IndexMut for AsciiStr { } } -#[cfg(feature = "std")] -impl AsciiExt for AsciiStr { - type Owned = AsciiString; - - #[inline] - fn is_ascii(&self) -> bool { - true - } - - fn to_ascii_uppercase(&self) -> AsciiString { - let mut ascii_string = self.to_ascii_string(); - ascii_string.make_ascii_uppercase(); - ascii_string - } - - fn to_ascii_lowercase(&self) -> AsciiString { - let mut ascii_string = self.to_ascii_string(); - ascii_string.make_ascii_lowercase(); - ascii_string - } - - fn eq_ignore_ascii_case(&self, other: &Self) -> bool { - self.len() == other.len() && - self.chars().zip(other.chars()).all(|(a, b)| { - a.eq_ignore_ascii_case(b) - }) - } - - fn make_ascii_uppercase(&mut self) { - for ascii in self.chars_mut() { - ascii.make_ascii_uppercase(); - } - } - - fn make_ascii_lowercase(&mut self) { - for ascii in self.chars_mut() { - ascii.make_ascii_lowercase(); - } - } -} - impl<'a> IntoIterator for &'a AsciiStr { type Item = &'a AsciiChar; type IntoIter = Chars<'a>; @@ -945,25 +898,6 @@ mod tests { assert_eq!(b.to_ascii_uppercase().as_str(), "A@A"); } - #[test] - #[cfg(feature = "std")] - fn ascii_ext() { - #[allow(deprecated)] - use std::ascii::AsciiExt; - assert!(AsciiExt::is_ascii(<&AsciiStr>::default())); - let mut mutable = String::from("a@AA@a"); - let parts = mutable.split_at_mut(3); - let a = parts.0.as_mut_ascii_str().unwrap(); - let b = parts.1.as_mut_ascii_str().unwrap(); - assert!(AsciiExt::eq_ignore_ascii_case(a, b)); - assert_eq!(AsciiExt::to_ascii_lowercase(a).as_str(), "a@a"); - assert_eq!(AsciiExt::to_ascii_uppercase(b).as_str(), "A@A"); - AsciiExt::make_ascii_uppercase(a); - AsciiExt::make_ascii_lowercase(b); - assert_eq!(a, "A@A"); - assert_eq!(b, "a@a"); - } - #[test] fn chars_iter() { let chars = &[b'h', b'e', b'l', b'l', b'o', b' ', b'w', b'o', b'r', b'l', b'd', b'\0']; diff --git a/src/free_functions.rs b/src/free_functions.rs index 3385d91..b286015 100644 --- a/src/free_functions.rs +++ b/src/free_functions.rs @@ -2,8 +2,6 @@ use ascii_char::{AsciiChar, ToAsciiChar}; -// I would like to require C: AsciiExt, but it's not in core. - /// Terminals use [caret notation](https://en.wikipedia.org/wiki/Caret_notation) /// to display some typed control codes, such as ^D for EOT and ^Z for SUB. /// From 66dd4e2da910e233527df2d1fceca4b65290b3e7 Mon Sep 17 00:00:00 2001 From: tormol Date: Wed, 3 Oct 2018 18:24:20 +0200 Subject: [PATCH 09/24] Newtype character iterators, and make .chars() produce copies * Replace Chars and CharsMut type aliases with custom types. * Add CharsRef. * Make Chars an Item=AsciiChar iterator instead of Item=&'a AsciiChar. * Export Split so that rustdoc documents it. Returning impl trait would make {as,into}_str() methods inaccessible. --- src/ascii_str.rs | 116 ++++++++++++++++++++++++++++++++++++++++------- src/lib.rs | 3 +- tests.rs | 2 +- 3 files changed, 103 insertions(+), 18 deletions(-) diff --git a/src/ascii_str.rs b/src/ascii_str.rs index 55c897a..d57a9cb 100644 --- a/src/ascii_str.rs +++ b/src/ascii_str.rs @@ -145,14 +145,14 @@ impl AsciiStr { /// Returns an iterator over the characters of the `AsciiStr`. #[inline] pub fn chars(&self) -> Chars { - self.slice.iter() + Chars(self.slice.iter()) } /// Returns an iterator over the characters of the `AsciiStr` which allows you to modify the /// value of each `AsciiChar`. #[inline] pub fn chars_mut(&mut self) -> CharsMut { - self.slice.iter_mut() + CharsMut(self.slice.iter_mut()) } /// Returns an iterator over parts of the `AsciiStr` separated by a character. @@ -230,7 +230,7 @@ impl AsciiStr { pub fn eq_ignore_ascii_case(&self, other: &Self) -> bool { self.len() == other.len() && self.chars().zip(other.chars()).all(|(a, b)| { - a.eq_ignore_ascii_case(b) + a.eq_ignore_ascii_case(&b) }) } @@ -470,12 +470,16 @@ impl IndexMut for AsciiStr { } } +/// Produces references for compatibility with `[u8]`. +/// +/// (`str` doesn't implement `IntoIterator` for its references, +/// so there is no compatibility to lose.) impl<'a> IntoIterator for &'a AsciiStr { type Item = &'a AsciiChar; - type IntoIter = Chars<'a>; + type IntoIter = CharsRef<'a>; #[inline] fn into_iter(self) -> Self::IntoIter { - self.chars() + CharsRef(self.as_slice().iter()) } } @@ -488,11 +492,93 @@ impl<'a> IntoIterator for &'a mut AsciiStr { } } -/// An immutable iterator over the characters of an `AsciiStr`. -pub type Chars<'a> = Iter<'a, AsciiChar>; +/// A copying iterator over the characters of an `AsciiStr`. +#[derive(Clone, Debug)] +pub struct Chars<'a>(Iter<'a, AsciiChar>); +impl<'a> Chars<'a> { + /// Returns the ascii string slice with the remaining characters. + pub fn as_str(&self) -> &'a AsciiStr { + self.0.as_slice().into() + } +} +impl<'a> Iterator for Chars<'a> { + type Item = AsciiChar; + #[inline] + fn next(&mut self) -> Option { + self.0.next().cloned() + } + fn size_hint(&self) -> (usize, Option) { + self.0.size_hint() + } +} +impl<'a> DoubleEndedIterator for Chars<'a> { + #[inline] + fn next_back(&mut self) -> Option { + self.0.next_back().cloned() + } +} +impl<'a> ExactSizeIterator for Chars<'a> { + fn len(&self) -> usize { + self.0.len() + } +} /// A mutable iterator over the characters of an `AsciiStr`. -pub type CharsMut<'a> = IterMut<'a, AsciiChar>; +#[derive(Debug)] +pub struct CharsMut<'a>(IterMut<'a, AsciiChar>); +impl<'a> CharsMut<'a> { + /// Returns the ascii string slice with the remaining characters. + pub fn into_str(self) -> &'a mut AsciiStr { + self.0.into_slice().into() + } +} +impl<'a> Iterator for CharsMut<'a> { + type Item = &'a mut AsciiChar; + #[inline] + fn next(&mut self) -> Option<&'a mut AsciiChar> { + self.0.next() + } + fn size_hint(&self) -> (usize, Option) { + self.0.size_hint() + } +} +impl<'a> DoubleEndedIterator for CharsMut<'a> { + #[inline] + fn next_back(&mut self) -> Option<&'a mut AsciiChar> { + self.0.next_back() + } +} +impl<'a> ExactSizeIterator for CharsMut<'a> { + fn len(&self) -> usize { + self.0.len() + } +} + +/// An immutable iterator over the characters of an `AsciiStr`. +#[derive(Clone, Debug)] +pub struct CharsRef<'a>(Iter<'a, AsciiChar>); +impl<'a> CharsRef<'a> { + /// Returns the ascii string slice with the remaining characters. + pub fn as_str(&self) -> &'a AsciiStr { + self.0.as_slice().into() + } +} +impl<'a> Iterator for CharsRef<'a> { + type Item = &'a AsciiChar; + #[inline] + fn next(&mut self) -> Option<&'a AsciiChar> { + self.0.next() + } + fn size_hint(&self) -> (usize, Option) { + self.0.size_hint() + } +} +impl<'a> DoubleEndedIterator for CharsRef<'a> { + #[inline] + fn next_back(&mut self) -> Option<&'a AsciiChar> { + self.0.next_back() + } +} /// An iterator over parts of an `AsciiStr` separated by an `AsciiChar`. /// @@ -508,9 +594,9 @@ impl<'a> Iterator for Split<'a> { fn next(&mut self) -> Option<&'a AsciiStr> { if !self.ended { - let start: &AsciiStr = self.chars.as_slice().into(); + let start: &AsciiStr = self.chars.as_str(); let split_on = self.on; - if let Some(at) = self.chars.position(|&c| c == split_on) { + if let Some(at) = self.chars.position(|c| c == split_on) { Some(&start[..at]) } else { self.ended = true; @@ -524,9 +610,9 @@ impl<'a> Iterator for Split<'a> { impl<'a> DoubleEndedIterator for Split<'a> { fn next_back(&mut self) -> Option<&'a AsciiStr> { if !self.ended { - let start: &AsciiStr = self.chars.as_slice().into(); + let start: &AsciiStr = self.chars.as_str(); let split_on = self.on; - if let Some(at) = self.chars.rposition(|&c| c == split_on) { + if let Some(at) = self.chars.rposition(|c| c == split_on) { Some(&start[at+1..]) } else { self.ended = true; @@ -549,7 +635,7 @@ impl<'a> Iterator for Lines<'a> { fn next(&mut self) -> Option<&'a AsciiStr> { if let Some(idx) = self.string .chars() - .position(|&chr| chr == AsciiChar::LineFeed) + .position(|chr| chr == AsciiChar::LineFeed) { let line = if idx > 0 && self.string[idx - 1] == AsciiChar::CarriageReturn { &self.string[..idx - 1] @@ -902,7 +988,7 @@ mod tests { fn chars_iter() { let chars = &[b'h', b'e', b'l', b'l', b'o', b' ', b'w', b'o', b'r', b'l', b'd', b'\0']; let ascii = AsciiStr::from_ascii(chars).unwrap(); - for (achar, byte) in ascii.chars().zip(chars.iter()) { + for (achar, byte) in ascii.chars().zip(chars.iter().cloned()) { assert_eq!(achar, byte); } } @@ -911,9 +997,7 @@ mod tests { fn chars_iter_mut() { let chars = &mut [b'h', b'e', b'l', b'l', b'o', b' ', b'w', b'o', b'r', b'l', b'd', b'\0']; let ascii = chars.as_mut_ascii_str().unwrap(); - *ascii.chars_mut().next().unwrap() = AsciiChar::H; - assert_eq!(ascii[0], b'H'); } diff --git a/src/lib.rs b/src/lib.rs index 4363c15..467e8a8 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -47,7 +47,8 @@ mod free_functions; mod serialization; pub use ascii_char::{AsciiChar, ToAsciiChar, ToAsciiCharError}; -pub use ascii_str::{AsciiStr, AsAsciiStr, AsMutAsciiStr, AsAsciiStrError, Chars, CharsMut, Lines}; +pub use ascii_str::{AsciiStr, AsAsciiStr, AsMutAsciiStr, AsAsciiStrError}; +pub use ascii_str::{Chars, CharsMut, CharsRef, Lines, Split}; #[cfg(feature = "std")] pub use ascii_string::{AsciiString, IntoAsciiString, FromAsciiError}; pub use free_functions::{caret_encode, caret_decode}; diff --git a/tests.rs b/tests.rs index 76f11d6..3af5e40 100644 --- a/tests.rs +++ b/tests.rs @@ -118,7 +118,7 @@ fn extend_from_iterator() { use ::std::borrow::Cow; let abc = "abc".as_ascii_str().unwrap(); - let mut s = abc.chars().cloned().collect::(); + let mut s = abc.chars().collect::(); assert_eq!(s, abc); s.extend(abc); assert_eq!(s, "abcabc"); From b95403e5a7ade3b513513383da6eb632b9eeb06d Mon Sep 17 00:00:00 2001 From: tormol Date: Sun, 11 Nov 2018 11:42:57 +0100 Subject: [PATCH 10/24] Rename trim_left() and trim_right() to trim_start() and trim_end() Rust is deprecating the old names. --- src/ascii_str.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/ascii_str.rs b/src/ascii_str.rs index d57a9cb..40626c2 100644 --- a/src/ascii_str.rs +++ b/src/ascii_str.rs @@ -195,7 +195,7 @@ impl AsciiStr { /// assert_eq!("white \tspace", example.trim()); /// ``` pub fn trim(&self) -> &Self { - self.trim_right().trim_left() + self.trim_start().trim_end() } /// Returns an ASCII string slice with leading whitespace removed. @@ -204,9 +204,9 @@ impl AsciiStr { /// ``` /// # use ascii::AsciiStr; /// let example = AsciiStr::from_ascii(" \twhite \tspace \t").unwrap(); - /// assert_eq!("white \tspace \t", example.trim_left()); + /// assert_eq!("white \tspace \t", example.trim_start()); /// ``` - pub fn trim_left(&self) -> &Self { + pub fn trim_start(&self) -> &Self { &self[self.chars().take_while(|a| a.is_whitespace()).count()..] } @@ -216,9 +216,9 @@ impl AsciiStr { /// ``` /// # use ascii::AsciiStr; /// let example = AsciiStr::from_ascii(" \twhite \tspace \t").unwrap(); - /// assert_eq!(" \twhite \tspace", example.trim_right()); + /// assert_eq!(" \twhite \tspace", example.trim_end()); /// ``` - pub fn trim_right(&self) -> &Self { + pub fn trim_end(&self) -> &Self { let trimmed = self.chars() .rev() .take_while(|a| a.is_whitespace()) From e62369441a504771a53732af0b06b56d7e91c14e Mon Sep 17 00:00:00 2001 From: tormol Date: Sun, 11 Nov 2018 11:44:33 +0100 Subject: [PATCH 11/24] Implement inclusive range indexing for AsciiStr Cannot use RangeBounds or SliceIndex because it would conflict with Index which returns a different type. --- src/ascii_str.rs | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/src/ascii_str.rs b/src/ascii_str.rs index 40626c2..99f4519 100644 --- a/src/ascii_str.rs +++ b/src/ascii_str.rs @@ -1,7 +1,8 @@ #![cfg_attr(rustfmt, rustfmt_skip)] use core::fmt; -use core::ops::{Index, IndexMut, Range, RangeTo, RangeFrom, RangeFull}; +use core::ops::{Index, IndexMut}; +use core::ops::{Range, RangeTo, RangeFrom, RangeFull, RangeInclusive, RangeToInclusive}; use core::slice::{Iter, IterMut}; #[cfg(feature = "std")] use std::error::Error; @@ -453,6 +454,8 @@ impl_index! { Range } impl_index! { RangeTo } impl_index! { RangeFrom } impl_index! { RangeFull } +impl_index! { RangeInclusive } +impl_index! { RangeToInclusive } impl Index for AsciiStr { type Output = AsciiChar; @@ -943,6 +946,26 @@ mod tests { assert!(default.is_empty()); } + #[test] + fn index() { + let mut arr = [AsciiChar::A, AsciiChar::B, AsciiChar::C, AsciiChar::D]; + let a: &AsciiStr = arr[..].into(); + assert_eq!(a[..].as_slice(), &a.as_slice()[..]); + assert_eq!(a[..4].as_slice(), &a.as_slice()[..4]); + assert_eq!(a[4..].as_slice(), &a.as_slice()[4..]); + assert_eq!(a[2..3].as_slice(), &a.as_slice()[2..3]); + assert_eq!(a[..=3].as_slice(), &a.as_slice()[..=3]); + assert_eq!(a[1..=1].as_slice(), &a.as_slice()[1..=1]); + let mut copy = arr.clone(); + let a_mut: &mut AsciiStr = {&mut arr[..]}.into(); + assert_eq!(a_mut[..].as_mut_slice(), &mut copy[..]); + assert_eq!(a_mut[..2].as_mut_slice(), &mut copy[..2]); + assert_eq!(a_mut[3..].as_mut_slice(), &mut copy[3..]); + assert_eq!(a_mut[4..4].as_mut_slice(), &mut copy[4..4]); + assert_eq!(a_mut[..=0].as_mut_slice(), &mut copy[..=0]); + assert_eq!(a_mut[0..=2].as_mut_slice(), &mut copy[0..=2]); + } + #[test] fn as_str() { let b = b"( ;"; From a82fa95e442201383ef1b4a89ff3bdd17e0a2c57 Mon Sep 17 00:00:00 2001 From: tormol Date: Sat, 13 Jul 2019 04:37:11 +0200 Subject: [PATCH 12/24] Modernize style and use clippy --- .travis.yml | 2 ++ src/ascii_char.rs | 12 ++++++------ src/ascii_str.rs | 4 ++-- src/ascii_string.rs | 40 +++++++++++++++++++++------------------- src/free_functions.rs | 2 +- src/lib.rs | 2 ++ 6 files changed, 34 insertions(+), 28 deletions(-) diff --git a/.travis.yml b/.travis.yml index 776598f..19dc492 100644 --- a/.travis.yml +++ b/.travis.yml @@ -10,6 +10,7 @@ rust: before_script: - | pip install 'travis-cargo<0.2' --user && + rustup component add clippy && export PATH=$HOME/.local/bin:$PATH script: @@ -17,6 +18,7 @@ script: travis-cargo test -- --no-default-features && travis-cargo test && travis-cargo test -- --all-features && + travis-cargo --only stable clippy -- --all-features && rm Cargo.lock && travis-cargo --only nightly build -- -Z minimal-versions --all-features diff --git a/src/ascii_char.rs b/src/ascii_char.rs index e520ca0..84fc793 100644 --- a/src/ascii_char.rs +++ b/src/ascii_char.rs @@ -466,7 +466,7 @@ impl AsciiChar { pub fn as_printable_char(self) -> char { unsafe { match self as u8 { - b' '...b'~' => self.as_char(), + b' '..=b'~' => self.as_char(), 127 => '␡', _ => char::from_u32_unchecked(self as u32 + '␀' as u32), } @@ -488,7 +488,7 @@ impl AsciiChar { pub fn to_ascii_uppercase(&self) -> Self { unsafe { match *self as u8 { - b'a'...b'z' => AsciiChar::from_unchecked(self.as_byte() - (b'a' - b'A')), + b'a'..=b'z' => AsciiChar::from_unchecked(self.as_byte() - (b'a' - b'A')), _ => *self, } } @@ -499,7 +499,7 @@ impl AsciiChar { pub fn to_ascii_lowercase(&self) -> Self { unsafe { match *self as u8 { - b'A'...b'Z' => AsciiChar::from_unchecked(self.as_byte() + (b'a' - b'A')), + b'A'..=b'Z' => AsciiChar::from_unchecked(self.as_byte() + (b'a' - b'A')), _ => *self, } } @@ -571,7 +571,7 @@ impl_into_partial_eq_ord!{char, AsciiChar::as_char} #[derive(Clone, Copy, PartialEq, Eq)] pub struct ToAsciiCharError(()); -const ERRORMSG_CHAR: &'static str = "not an ASCII character"; +const ERRORMSG_CHAR: &str = "not an ASCII character"; #[cfg(not(feature = "std"))] impl ToAsciiCharError { @@ -624,7 +624,7 @@ impl ToAsciiChar for AsciiChar { impl ToAsciiChar for u8 { #[inline] fn to_ascii_char(self) -> Result { - (self as u32).to_ascii_char() + u32::from(self).to_ascii_char() } #[inline] unsafe fn to_ascii_char_unchecked(self) -> AsciiChar { @@ -671,7 +671,7 @@ impl ToAsciiChar for u32 { impl ToAsciiChar for u16 { fn to_ascii_char(self) -> Result { - (self as u32).to_ascii_char() + u32::from(self).to_ascii_char() } #[inline] unsafe fn to_ascii_char_unchecked(self) -> AsciiChar { diff --git a/src/ascii_str.rs b/src/ascii_str.rs index 99f4519..8edb7a7 100644 --- a/src/ascii_str.rs +++ b/src/ascii_str.rs @@ -169,7 +169,7 @@ impl AsciiStr { /// ``` pub fn split(&self, on: AsciiChar) -> Split { Split { - on: on, + on, ended: false, chars: self.chars(), } @@ -684,7 +684,7 @@ impl<'a> DoubleEndedIterator for Lines<'a> { #[derive(Clone, Copy, PartialEq, Eq, Debug)] pub struct AsAsciiStrError(usize); -const ERRORMSG_STR: &'static str = "one or more bytes are not ASCII"; +const ERRORMSG_STR: &str = "one or more bytes are not ASCII"; impl AsAsciiStrError { /// Returns the index of the first non-ASCII byte. diff --git a/src/ascii_string.rs b/src/ascii_string.rs index fc50782..a8e9a88 100644 --- a/src/ascii_string.rs +++ b/src/ascii_string.rs @@ -103,7 +103,7 @@ impl AsciiString { bytes.capacity(), ); mem::forget(bytes); - AsciiString { vec: vec } + AsciiString { vec } } /// Converts anything that can represent a byte buffer into an `AsciiString`. @@ -390,10 +390,6 @@ macro_rules! impl_eq { fn eq(&self, other: &$rhs) -> bool { PartialEq::eq(&**self, &**other) } - #[inline] - fn ne(&self, other: &$rhs) -> bool { - PartialEq::ne(&**self, &**other) - } } } } @@ -424,7 +420,7 @@ impl BorrowMut for AsciiString { impl From> for AsciiString { #[inline] fn from(vec: Vec) -> Self { - AsciiString { vec: vec } + AsciiString { vec } } } @@ -455,7 +451,7 @@ impl<'a> From<&'a AsciiStr> for AsciiString { impl<'a> From<&'a [AsciiChar]> for AsciiString { #[inline] fn from(s: &'a [AsciiChar]) -> AsciiString { - s.into_iter().map(|c| *c).collect() + s.iter().copied().collect() } } @@ -552,15 +548,21 @@ impl fmt::Debug for AsciiString { /// transmission of an error other than that an error occurred. impl fmt::Write for AsciiString { fn write_str(&mut self, s: &str) -> fmt::Result { - let astr = try!(AsciiStr::from_ascii(s).map_err(|_| fmt::Error)); - self.push_str(astr); - Ok(()) + if let Ok(astr) = AsciiStr::from_ascii(s) { + self.push_str(astr); + Ok(()) + } else { + Err(fmt::Error) + } } fn write_char(&mut self, c: char) -> fmt::Result { - let achar = try!(AsciiChar::from(c).map_err(|_| fmt::Error)); - self.push(achar); - Ok(()) + if let Ok(achar) = AsciiChar::from(c) { + self.push(achar); + Ok(()) + } else { + Err(fmt::Error) + } } } @@ -716,8 +718,8 @@ impl Error for FromAsciiError { self.error.description() } /// Always returns an `AsAsciiStrError` - fn cause(&self) -> Option<&Error> { - Some(&self.error as &Error) + fn cause(&self) -> Option<&dyn Error> { + Some(&self.error as &dyn Error) } } @@ -815,7 +817,7 @@ impl IntoAsciiString for CString { // `CString`, so this is safe. CString::from_vec_unchecked(owner) }, - error: error, + error, } }) .map(|mut s| { @@ -840,7 +842,7 @@ impl<'a> IntoAsciiString for &'a CStr { owner: unsafe { CStr::from_ptr(owner.as_ptr() as *const _) }, - error: error, + error, } }) .map(|mut s| { @@ -869,7 +871,7 @@ where .map_err(|FromAsciiError { error, owner }| { FromAsciiError { owner: Cow::Owned(owner), - error: error, + error, } }) } @@ -878,7 +880,7 @@ where .map_err(|FromAsciiError { error, owner }| { FromAsciiError { owner: Cow::Borrowed(owner), - error: error, + error, } }) } diff --git a/src/free_functions.rs b/src/free_functions.rs index b286015..29328ed 100644 --- a/src/free_functions.rs +++ b/src/free_functions.rs @@ -55,7 +55,7 @@ pub fn caret_decode>(c: C) -> Option { // The formula is explained in the Wikipedia article. unsafe { match c.into() { - b'?'...b'_' => Some(AsciiChar::from_unchecked(c.into() ^ 0b0100_0000)), + b'?'..=b'_' => Some(AsciiChar::from_unchecked(c.into() ^ 0b0100_0000)), _ => None, } } diff --git a/src/lib.rs b/src/lib.rs index 467e8a8..870fdfe 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -29,6 +29,8 @@ #![cfg_attr(not(feature = "std"), no_std)] +#![allow(clippy::trivially_copy_pass_by_ref)] // for compatibility with methods on char and u8 + #[cfg(feature = "std")] extern crate core; From 93953910a0e86e44f0b856a8157e99028d265d74 Mon Sep 17 00:00:00 2001 From: tormol Date: Sun, 14 Jul 2019 17:27:44 +0200 Subject: [PATCH 13/24] Constify most of AsciiChar * add const fn AsciiChar::new() * make all is_xxx() const fn * make eq_ignore_ascii_case() and to_ascii_{upper,lower}case() const fn * make as_char() and as_byte() const fn * make #![no_std] ToAsciiCharError::description() const fn methods not made const fn: * AsciiChar::from() require branching (Could add a from_byte() methods using an 256-element array, but that doesn't seem worth it for now) * AsciiChar::from_unchecked() require transmuting or equivalents * make_ascii_{upper,lower}case() require mutating * as_printable_char() doesn't seem useful enough to bother with a [char; 128] --- src/ascii_char.rs | 256 +++++++++++++++++++++++++++++++--------------- 1 file changed, 171 insertions(+), 85 deletions(-) diff --git a/src/ascii_char.rs b/src/ascii_char.rs index 84fc793..6779f0e 100644 --- a/src/ascii_char.rs +++ b/src/ascii_char.rs @@ -296,6 +296,63 @@ impl AsciiChar { ch.to_ascii_char() } + /// Create an `AsciiChar` from a `char`, panicking if it's not ASCII. + /// + /// This function is intended for creating `AsciiChar` values from + /// hardcoded known-good character literals such as `'K'`, `'-'` or `'\0'`, + /// and for use in `const` contexts. + /// Use [`from_ascii()`](#tymethod.from_ascii) instead when you're not + /// certain the character is ASCII. + /// + /// # Examples + /// + /// ``` + /// # use ascii::AsciiChar; + /// assert_eq!(AsciiChar::new('@'), AsciiChar::At); + /// assert_eq!(AsciiChar::new('C').as_char(), 'C'); + /// ``` + /// + /// In a constant: + /// ``` + /// # use ascii::AsciiChar; + /// const SPLIT_ON: AsciiChar = AsciiChar::new(','); + /// ``` + /// + /// This will not compile: + /// ```compile_fail + /// # use ascii::AsciiChar; + /// const BAD: AsciiChar = AsciiChar::new('Ø'); + /// ``` + /// + /// # Panics + /// + /// This function will panic if passed a non-ASCII character. + /// + /// The panic message might not be the most descriptive due to the + /// current limitations of `const fn`. + pub const fn new(ch: char) -> AsciiChar { + use AsciiChar::*; + const ALL: [AsciiChar; 128] = [ + Null, SOH, SOX, ETX, EOT, ENQ, ACK, Bell, + BackSpace, Tab, LineFeed, VT, FF, CarriageReturn, SI, SO, + DLE, DC1, DC2, DC3, DC4, NAK, SYN, ETB, + CAN, EM, SUB, ESC, FS, GS, RS, US, + Space, Exclamation, Quotation, Hash, Dollar, Percent, Ampersand, Apostrophe, + ParenOpen, ParenClose, Asterisk, Plus, Comma, Minus, Dot, Slash, + _0, _1, _2, _3, _4, _5, _6, _7, + _8, _9, Colon, Semicolon, LessThan, Equal, GreaterThan, Question, + At, A, B, C, D, E, F, G, + H, I, J, K, L, M, N, O, + P, Q, R, S, T, U, V, W, + X, Y, Z, BracketOpen, BackSlash, BracketClose, Caret, UnderScore, + Grave, a, b, c, d, e, f, g, + h, i, j, k, l, m, n, o, + p, q, r, s, t, u, v, w, + x, y, z, CurlyBraceOpen, VerticalBar, CurlyBraceClose, Tilde, DEL, + ]; + ALL[ch as usize] + } + /// Constructs an ASCII character from a `char` or `u8` without any checks. #[inline] pub unsafe fn from_unchecked(ch: C) -> Self { @@ -304,75 +361,79 @@ impl AsciiChar { /// Converts an ASCII character into a `u8`. #[inline] - pub fn as_byte(self) -> u8 { + pub const fn as_byte(self) -> u8 { self as u8 } /// Converts an ASCII character into a `char`. #[inline] - pub fn as_char(self) -> char { - self.as_byte() as char + pub const fn as_char(self) -> char { + self as u8 as char } // the following methods are like ctype, and the implementation is inspired by musl + /// Turns uppercase into lowercase, but also modifies '@' and '<'..='_' + const fn to_not_upper(self) -> u8 { + self as u8 | 0b010_0000 + } + /// Check if the character is a letter (a-z, A-Z) #[inline] - pub fn is_alphabetic(self) -> bool { - let c = self.as_byte() | 0b010_0000; // Turns uppercase into lowercase. - c >= b'a' && c <= b'z' + pub const fn is_alphabetic(self) -> bool { + (self.to_not_upper() >= b'a') & (self.to_not_upper() <= b'z') } /// Check if the character is a number (0-9) #[inline] - pub fn is_digit(self) -> bool { - self >= AsciiChar::_0 && self <= AsciiChar::_9 + pub const fn is_digit(self) -> bool { + (self as u8 >= b'0') & (self as u8 <= b'9') } /// Check if the character is a letter or number #[inline] - pub fn is_alphanumeric(self) -> bool { - self.is_alphabetic() || self.is_digit() + pub const fn is_alphanumeric(self) -> bool { + self.is_alphabetic() | self.is_digit() } /// Check if the character is a space or horizontal tab #[inline] - pub fn is_blank(self) -> bool { - self == AsciiChar::Space || self == AsciiChar::Tab + pub const fn is_blank(self) -> bool { + (self as u8 == b' ') | (self as u8 == b'\t') } /// Check if the character is a ' ', '\t', '\n' or '\r' #[inline] - pub fn is_whitespace(self) -> bool { - self.is_blank() || self == AsciiChar::LineFeed || self == AsciiChar::CarriageReturn + pub const fn is_whitespace(self) -> bool { + self.is_blank() | (self as u8 == b'\n') | (self as u8 == b'\r') } /// Check if the character is a control character /// /// # Examples /// ``` - /// use ascii::ToAsciiChar; - /// assert_eq!('\0'.to_ascii_char().unwrap().is_control(), true); - /// assert_eq!('n'.to_ascii_char().unwrap().is_control(), false); - /// assert_eq!(' '.to_ascii_char().unwrap().is_control(), false); - /// assert_eq!('\n'.to_ascii_char().unwrap().is_control(), true); + /// # use ascii::AsciiChar; + /// assert_eq!(AsciiChar::new('\0').is_control(), true); + /// assert_eq!(AsciiChar::new('n').is_control(), false); + /// assert_eq!(AsciiChar::new(' ').is_control(), false); + /// assert_eq!(AsciiChar::new('\n').is_control(), true); /// ``` #[inline] - pub fn is_control(self) -> bool { - self < AsciiChar::Space || self == AsciiChar::DEL + pub const fn is_control(self) -> bool { + ((self as u8) < b' ') | (self as u8 == 127) } /// Checks if the character is printable (except space) /// /// # Examples /// ``` - /// use ascii::ToAsciiChar; - /// assert_eq!('n'.to_ascii_char().unwrap().is_graph(), true); - /// assert_eq!(' '.to_ascii_char().unwrap().is_graph(), false); - /// assert_eq!('\n'.to_ascii_char().unwrap().is_graph(), false); + /// # use ascii::AsciiChar; + /// assert_eq!(AsciiChar::new('n').is_graph(), true); + /// assert_eq!(AsciiChar::new(' ').is_graph(), false); + /// assert_eq!(AsciiChar::new('\n').is_graph(), false); /// ``` #[inline] - pub fn is_graph(self) -> bool { + pub const fn is_graph(self) -> bool { self.as_byte().wrapping_sub(b' ' + 1) < 0x5E } @@ -380,13 +441,13 @@ impl AsciiChar { /// /// # Examples /// ``` - /// use ascii::ToAsciiChar; - /// assert_eq!('n'.to_ascii_char().unwrap().is_print(), true); - /// assert_eq!(' '.to_ascii_char().unwrap().is_print(), true); - /// assert_eq!('\n'.to_ascii_char().unwrap().is_print(), false); + /// # use ascii::AsciiChar; + /// assert_eq!(AsciiChar::new('n').is_print(), true); + /// assert_eq!(AsciiChar::new(' ').is_print(), true); + /// assert_eq!(AsciiChar::new('\n').is_print(), false); /// ``` #[inline] - pub fn is_print(self) -> bool { + pub const fn is_print(self) -> bool { self.as_byte().wrapping_sub(b' ') < 0x5F } @@ -394,13 +455,13 @@ impl AsciiChar { /// /// # Examples /// ``` - /// use ascii::ToAsciiChar; - /// assert_eq!('a'.to_ascii_char().unwrap().is_lowercase(), true); - /// assert_eq!('A'.to_ascii_char().unwrap().is_lowercase(), false); - /// assert_eq!('@'.to_ascii_char().unwrap().is_lowercase(), false); + /// use ascii::AsciiChar; + /// assert_eq!(AsciiChar::new('a').is_lowercase(), true); + /// assert_eq!(AsciiChar::new('A').is_lowercase(), false); + /// assert_eq!(AsciiChar::new('@').is_lowercase(), false); /// ``` #[inline] - pub fn is_lowercase(self) -> bool { + pub const fn is_lowercase(self) -> bool { self.as_byte().wrapping_sub(b'a') < 26 } @@ -408,13 +469,13 @@ impl AsciiChar { /// /// # Examples /// ``` - /// use ascii::ToAsciiChar; - /// assert_eq!('A'.to_ascii_char().unwrap().is_uppercase(), true); - /// assert_eq!('a'.to_ascii_char().unwrap().is_uppercase(), false); - /// assert_eq!('@'.to_ascii_char().unwrap().is_uppercase(), false); + /// # use ascii::AsciiChar; + /// assert_eq!(AsciiChar::new('A').is_uppercase(), true); + /// assert_eq!(AsciiChar::new('a').is_uppercase(), false); + /// assert_eq!(AsciiChar::new('@').is_uppercase(), false); /// ``` #[inline] - pub fn is_uppercase(self) -> bool { + pub const fn is_uppercase(self) -> bool { self.as_byte().wrapping_sub(b'A') < 26 } @@ -422,31 +483,31 @@ impl AsciiChar { /// /// # Examples /// ``` - /// use ascii::ToAsciiChar; - /// assert_eq!('n'.to_ascii_char().unwrap().is_punctuation(), false); - /// assert_eq!(' '.to_ascii_char().unwrap().is_punctuation(), false); - /// assert_eq!('_'.to_ascii_char().unwrap().is_punctuation(), true); - /// assert_eq!('~'.to_ascii_char().unwrap().is_punctuation(), true); + /// # use ascii::AsciiChar; + /// assert_eq!(AsciiChar::new('n').is_punctuation(), false); + /// assert_eq!(AsciiChar::new(' ').is_punctuation(), false); + /// assert_eq!(AsciiChar::new('_').is_punctuation(), true); + /// assert_eq!(AsciiChar::new('~').is_punctuation(), true); /// ``` #[inline] - pub fn is_punctuation(self) -> bool { - self.is_graph() && !self.is_alphanumeric() + pub const fn is_punctuation(self) -> bool { + self.is_graph() & !self.is_alphanumeric() } /// Checks if the character is a valid hex digit /// /// # Examples /// ``` - /// use ascii::ToAsciiChar; - /// assert_eq!('5'.to_ascii_char().unwrap().is_hex(), true); - /// assert_eq!('a'.to_ascii_char().unwrap().is_hex(), true); - /// assert_eq!('F'.to_ascii_char().unwrap().is_hex(), true); - /// assert_eq!('G'.to_ascii_char().unwrap().is_hex(), false); - /// assert_eq!(' '.to_ascii_char().unwrap().is_hex(), false); + /// # use ascii::AsciiChar; + /// assert_eq!(AsciiChar::new('5').is_hex(), true); + /// assert_eq!(AsciiChar::new('a').is_hex(), true); + /// assert_eq!(AsciiChar::new('F').is_hex(), true); + /// assert_eq!(AsciiChar::new('G').is_hex(), false); + /// assert_eq!(AsciiChar::new(' ').is_hex(), false); /// ``` #[inline] - pub fn is_hex(self) -> bool { - self.is_digit() || (self.as_byte() | 0x20u8).wrapping_sub(b'a') < 6 + pub const fn is_hex(self) -> bool { + self.is_digit() | ((self as u8 | 0x20u8).wrapping_sub(b'a') < 6) } /// Unicode has printable versions of the ASCII control codes, like '␛'. @@ -457,11 +518,11 @@ impl AsciiChar { /// /// # Examples /// ``` - /// # use ascii::ToAsciiChar; - /// assert_eq!('\0'.to_ascii_char().unwrap().as_printable_char(), '␀'); - /// assert_eq!('\n'.to_ascii_char().unwrap().as_printable_char(), '␊'); - /// assert_eq!(' '.to_ascii_char().unwrap().as_printable_char(), ' '); - /// assert_eq!('p'.to_ascii_char().unwrap().as_printable_char(), 'p'); + /// # use ascii::AsciiChar; + /// assert_eq!(AsciiChar::new('\0').as_printable_char(), '␀'); + /// assert_eq!(AsciiChar::new('\n').as_printable_char(), '␊'); + /// assert_eq!(AsciiChar::new(' ').as_printable_char(), ' '); + /// assert_eq!(AsciiChar::new('p').as_printable_char(), 'p'); /// ``` pub fn as_printable_char(self) -> char { unsafe { @@ -483,31 +544,43 @@ impl AsciiChar { *self = self.to_ascii_lowercase() } - /// Maps letters `a`...`z` to `A`...`Z` and returns everything else unchanged. + /// Maps letters a-z to A-Z and returns any other character unchanged. + /// + /// # Examples + /// ``` + /// # use ascii::AsciiChar; + /// assert_eq!(AsciiChar::new('u').to_ascii_uppercase().as_char(), 'U'); + /// assert_eq!(AsciiChar::new('U').to_ascii_uppercase().as_char(), 'U'); + /// assert_eq!(AsciiChar::new('2').to_ascii_uppercase().as_char(), '2'); + /// assert_eq!(AsciiChar::new('=').to_ascii_uppercase().as_char(), '='); + /// assert_eq!(AsciiChar::new('[').to_ascii_uppercase().as_char(), '['); + /// ``` #[inline] - pub fn to_ascii_uppercase(&self) -> Self { - unsafe { - match *self as u8 { - b'a'..=b'z' => AsciiChar::from_unchecked(self.as_byte() - (b'a' - b'A')), - _ => *self, - } - } + pub const fn to_ascii_uppercase(&self) -> Self { + [*self, AsciiChar::new((*self as u8 & 0b101_1111) as char)][self.is_lowercase() as usize] } - /// Maps letters `A`...`Z` to `a`...`z` and returns everything else unchanged. + /// Maps letters A-Z to a-z and returns any other character unchanged. + /// + /// # Examples + /// ``` + /// # use ascii::AsciiChar; + /// assert_eq!(AsciiChar::new('U').to_ascii_lowercase().as_char(), 'u'); + /// assert_eq!(AsciiChar::new('u').to_ascii_lowercase().as_char(), 'u'); + /// assert_eq!(AsciiChar::new('2').to_ascii_lowercase().as_char(), '2'); + /// assert_eq!(AsciiChar::new('^').to_ascii_lowercase().as_char(), '^'); + /// assert_eq!(AsciiChar::new('\x7f').to_ascii_lowercase().as_char(), '\x7f'); + /// ``` #[inline] - pub fn to_ascii_lowercase(&self) -> Self { - unsafe { - match *self as u8 { - b'A'..=b'Z' => AsciiChar::from_unchecked(self.as_byte() + (b'a' - b'A')), - _ => *self, - } - } + pub const fn to_ascii_lowercase(&self) -> Self { + [*self, AsciiChar::new(self.to_not_upper() as char)][self.is_uppercase() as usize] } /// Compares two characters case-insensitively. - pub fn eq_ignore_ascii_case(&self, other: &Self) -> bool { - self.to_ascii_lowercase() == other.to_ascii_lowercase() + #[inline] + pub const fn eq_ignore_ascii_case(&self, other: &Self) -> bool { + (self.as_byte() == other.as_byte()) | + (self.is_alphabetic() & (self.to_not_upper() == other.to_not_upper())) } } @@ -577,7 +650,7 @@ const ERRORMSG_CHAR: &str = "not an ASCII character"; impl ToAsciiCharError { /// Returns a description for this error, like `std::error::Error::description`. #[inline] - pub fn description(&self) -> &'static str { + pub const fn description(&self) -> &'static str { ERRORMSG_CHAR } } @@ -696,6 +769,13 @@ mod tests { assert!(generic('λ').is_err()); } + #[test] + fn new_array_is_correct() { + for byte in 0..128u8 { + assert_eq!(AsciiChar::new(byte as char).as_byte(), byte); + } + } + #[test] fn as_byte_and_char() { assert_eq!(A.as_byte(), b'A'); @@ -704,10 +784,12 @@ mod tests { #[test] fn is_digit() { - assert_eq!('0'.to_ascii_char().unwrap().is_digit(), true); - assert_eq!('9'.to_ascii_char().unwrap().is_digit(), true); - assert_eq!('/'.to_ascii_char().unwrap().is_digit(), false); - assert_eq!(':'.to_ascii_char().unwrap().is_digit(), false); + assert_eq!(_0.is_digit(), true); + assert_eq!(_9.is_digit(), true); + assert_eq!(O.is_digit(), false); + assert_eq!(o.is_digit(), false); + assert_eq!(Slash.is_digit(), false); + assert_eq!(Colon.is_digit(), false); } #[test] @@ -743,7 +825,12 @@ mod tests { assert!(!LineFeed.eq_ignore_ascii_case(&CarriageReturn)); assert!(z.eq_ignore_ascii_case(&Z)); assert!(Z.eq_ignore_ascii_case(&z)); + assert!(A.eq_ignore_ascii_case(&a)); + assert!(!K.eq_ignore_ascii_case(&C)); assert!(!Z.eq_ignore_ascii_case(&DEL)); + assert!(!BracketOpen.eq_ignore_ascii_case(&CurlyBraceOpen)); + assert!(!Grave.eq_ignore_ascii_case(&At)); + assert!(!Grave.eq_ignore_ascii_case(&DEL)); } #[test] @@ -754,5 +841,4 @@ mod tests { assert_eq!(format!("{}", LineFeed), "\n"); assert_eq!(format!("{:?}", LineFeed), "'\\n'"); } - } From ffafc2a1673949a85a2b629b1b1641caf2c5e1ee Mon Sep 17 00:00:00 2001 From: tormol Date: Sat, 13 Jul 2019 21:28:50 +0200 Subject: [PATCH 14/24] Rename AsciiChar::from[_unchecked]() to from_ascii[_unchecked]() ... for consistency with str::from_utf8[_unchecked](). --- src/ascii_char.rs | 22 +++++++++++++++++----- src/ascii_str.rs | 2 +- src/ascii_string.rs | 12 ++++++------ src/free_functions.rs | 2 +- src/serialization/ascii_char.rs | 6 +++--- 5 files changed, 28 insertions(+), 16 deletions(-) diff --git a/src/ascii_char.rs b/src/ascii_char.rs index 6779f0e..c860ea7 100644 --- a/src/ascii_char.rs +++ b/src/ascii_char.rs @@ -288,11 +288,11 @@ impl AsciiChar { /// # Example /// ``` /// # use ascii::AsciiChar; - /// let a = AsciiChar::from('g').unwrap(); + /// let a = AsciiChar::from_ascii('g').unwrap(); /// assert_eq!(a.as_char(), 'g'); /// ``` #[inline] - pub fn from(ch: C) -> Result { + pub fn from_ascii(ch: C) -> Result { ch.to_ascii_char() } @@ -301,7 +301,7 @@ impl AsciiChar { /// This function is intended for creating `AsciiChar` values from /// hardcoded known-good character literals such as `'K'`, `'-'` or `'\0'`, /// and for use in `const` contexts. - /// Use [`from_ascii()`](#tymethod.from_ascii) instead when you're not + /// Use [`from_ascii()`](#method.from_ascii) instead when you're not /// certain the character is ASCII. /// /// # Examples @@ -353,9 +353,21 @@ impl AsciiChar { ALL[ch as usize] } - /// Constructs an ASCII character from a `char` or `u8` without any checks. + /// Constructs an ASCII character from a `u8`, `char` or other character + /// type without any checks. + /// + /// # Safety + /// + /// This function is very unsafe as it can create invalid enum + /// discriminants, which instantly creates undefined behavior. + /// (`let _ = AsciiChar::from_ascii_unchecked(200);` alone is UB). + /// + /// The undefined behavior is not just theoretical either: + /// For example, `[0; 128][AsciiChar::from_ascii_unchecked(255) as u8 as usize] = 0` + /// might not panic, creating a buffer overflow, + /// and `Some(AsciiChar::from_ascii_unchecked(128))` might be `None`. #[inline] - pub unsafe fn from_unchecked(ch: C) -> Self { + pub unsafe fn from_ascii_unchecked(ch: C) -> Self { ch.to_ascii_char_unchecked() } diff --git a/src/ascii_str.rs b/src/ascii_str.rs index 8edb7a7..f94d19a 100644 --- a/src/ascii_str.rs +++ b/src/ascii_str.rs @@ -1088,7 +1088,7 @@ mod tests { fn split_equals_str(haystack: &str, needle: char) { let mut strs = haystack.split(needle); let mut asciis = haystack.as_ascii_str().unwrap() - .split(AsciiChar::from(needle).unwrap()) + .split(AsciiChar::from_ascii(needle).unwrap()) .map(|a| a.as_str()); loop { assert_eq!(asciis.size_hint(), strs.size_hint()); diff --git a/src/ascii_string.rs b/src/ascii_string.rs index a8e9a88..6b29a75 100644 --- a/src/ascii_string.rs +++ b/src/ascii_string.rs @@ -225,9 +225,9 @@ impl AsciiString { /// ``` /// # use ascii::{ AsciiChar, AsciiString}; /// let mut s = AsciiString::from_ascii("abc").unwrap(); - /// s.push(AsciiChar::from('1').unwrap()); - /// s.push(AsciiChar::from('2').unwrap()); - /// s.push(AsciiChar::from('3').unwrap()); + /// s.push(AsciiChar::from_ascii('1').unwrap()); + /// s.push(AsciiChar::from_ascii('2').unwrap()); + /// s.push(AsciiChar::from_ascii('3').unwrap()); /// assert_eq!(s, "abc123"); /// ``` #[inline] @@ -330,7 +330,7 @@ impl AsciiString { /// # use ascii::{AsciiChar, AsciiString}; /// let mut s = AsciiString::new(); /// assert!(s.is_empty()); - /// s.push(AsciiChar::from('a').unwrap()); + /// s.push(AsciiChar::from_ascii('a').unwrap()); /// assert!(!s.is_empty()); /// ``` #[inline] @@ -557,7 +557,7 @@ impl fmt::Write for AsciiString { } fn write_char(&mut self, c: char) -> fmt::Result { - if let Ok(achar) = AsciiChar::from(c) { + if let Ok(achar) = AsciiChar::from_ascii(c) { self.push(achar); Ok(()) } else { @@ -909,7 +909,7 @@ mod tests { #[test] fn from_ascii_vec() { - let vec = vec![AsciiChar::from('A').unwrap(), AsciiChar::from('B').unwrap()]; + let vec = vec![AsciiChar::from_ascii('A').unwrap(), AsciiChar::from_ascii('B').unwrap()]; assert_eq!(AsciiString::from(vec), AsciiString::from_str("AB").unwrap()); } diff --git a/src/free_functions.rs b/src/free_functions.rs index 29328ed..4f644a4 100644 --- a/src/free_functions.rs +++ b/src/free_functions.rs @@ -55,7 +55,7 @@ pub fn caret_decode>(c: C) -> Option { // The formula is explained in the Wikipedia article. unsafe { match c.into() { - b'?'..=b'_' => Some(AsciiChar::from_unchecked(c.into() ^ 0b0100_0000)), + b'?'..=b'_' => Some(AsciiChar::from_ascii_unchecked(c.into() ^ 0b0100_0000)), _ => None, } } diff --git a/src/serialization/ascii_char.rs b/src/serialization/ascii_char.rs index 5075721..8f92248 100644 --- a/src/serialization/ascii_char.rs +++ b/src/serialization/ascii_char.rs @@ -23,7 +23,7 @@ impl<'de> Visitor<'de> for AsciiCharVisitor { #[inline] fn visit_char(self, v: char) -> Result { - AsciiChar::from(v).map_err(|_| Error::invalid_value(Unexpected::Char(v), &self)) + AsciiChar::from_ascii(v).map_err(|_| Error::invalid_value(Unexpected::Char(v), &self)) } #[inline] @@ -69,7 +69,7 @@ mod tests { #[cfg(feature = "serde_test")] fn serialize() { use serde_test::{assert_tokens, Token}; - let ascii_char = AsciiChar::from(ASCII_CHAR).unwrap(); + let ascii_char = AsciiChar::from_ascii(ASCII_CHAR).unwrap(); assert_tokens(&ascii_char, &[Token::Char(ASCII_CHAR)]); } @@ -77,7 +77,7 @@ mod tests { #[cfg(feature = "serde_test")] fn deserialize() { use serde_test::{assert_de_tokens, assert_de_tokens_error, Token}; - let ascii_char = AsciiChar::from(ASCII_CHAR).unwrap(); + let ascii_char = AsciiChar::from_ascii(ASCII_CHAR).unwrap(); assert_de_tokens(&ascii_char, &[Token::String(ASCII_STR)]); assert_de_tokens(&ascii_char, &[Token::Str(ASCII_STR)]); assert_de_tokens(&ascii_char, &[Token::BorrowedStr(ASCII_STR)]); From bc0f2c028efd3c935bfd53f9b20c7da097c5049f Mon Sep 17 00:00:00 2001 From: tormol Date: Sun, 14 Jul 2019 18:05:04 +0200 Subject: [PATCH 15/24] Future-proof some methods * De-genericize AsciiStr::new() and from_ascii_unchecked() so that they can hopefully be made const fn sooner (due to needing fewer features). * Remove AsciiStr::new() because I want to make it similar to AsciiChar::new(), but also want to reserve the name for what becomes possible as const fn first. * Make AsciiStr::split() return impl Trait to make changing it to take a Pattern once that is stabilized easier. * Make AsciiStr::lines() return const fn just in case. --- src/ascii_char.rs | 2 +- src/ascii_str.rs | 24 ++++++++---------------- src/lib.rs | 2 +- 3 files changed, 10 insertions(+), 18 deletions(-) diff --git a/src/ascii_char.rs b/src/ascii_char.rs index c860ea7..5203c77 100644 --- a/src/ascii_char.rs +++ b/src/ascii_char.rs @@ -367,7 +367,7 @@ impl AsciiChar { /// might not panic, creating a buffer overflow, /// and `Some(AsciiChar::from_ascii_unchecked(128))` might be `None`. #[inline] - pub unsafe fn from_ascii_unchecked(ch: C) -> Self { + pub unsafe fn from_ascii_unchecked(ch: u8) -> Self { ch.to_ascii_char_unchecked() } diff --git a/src/ascii_str.rs b/src/ascii_str.rs index f94d19a..f9bf0b7 100644 --- a/src/ascii_str.rs +++ b/src/ascii_str.rs @@ -25,11 +25,6 @@ pub struct AsciiStr { } impl AsciiStr { - /// Coerces into an `AsciiStr` slice. - pub fn new + ?Sized>(s: &S) -> &AsciiStr { - s.as_ref() - } - /// Converts `&self` to a `&str` slice. #[inline] pub fn as_str(&self) -> &str { @@ -85,7 +80,7 @@ impl AsciiStr { /// # Examples /// ``` /// # use ascii::AsciiStr; - /// let foo = AsciiStr::from_ascii("foo"); + /// let foo = AsciiStr::from_ascii(b"foo"); /// let err = AsciiStr::from_ascii("Ŋ"); /// assert_eq!(foo.unwrap().as_str(), "foo"); /// assert_eq!(err.unwrap_err().valid_up_to(), 0); @@ -104,15 +99,12 @@ impl AsciiStr { /// # Examples /// ``` /// # use ascii::AsciiStr; - /// let foo = unsafe{ AsciiStr::from_ascii_unchecked("foo") }; + /// let foo = unsafe { AsciiStr::from_ascii_unchecked(&b"foo"[..]) }; /// assert_eq!(foo.as_str(), "foo"); /// ``` #[inline] - pub unsafe fn from_ascii_unchecked(bytes: &B) -> &AsciiStr - where - B: AsRef<[u8]>, - { - bytes.as_ref().as_ascii_str_unchecked() + pub unsafe fn from_ascii_unchecked(bytes: &[u8]) -> &AsciiStr { + bytes.as_ascii_str_unchecked() } /// Returns the number of characters / bytes in this ASCII sequence. @@ -167,7 +159,7 @@ impl AsciiStr { /// .collect::>(); /// assert_eq!(words, ["apple", "banana", "lemon"]); /// ``` - pub fn split(&self, on: AsciiChar) -> Split { + pub fn split(&self, on: AsciiChar) -> impl DoubleEndedIterator { Split { on, ended: false, @@ -181,7 +173,7 @@ impl AsciiStr { /// /// The final line ending is optional. #[inline] - pub fn lines(&self) -> Lines { + pub fn lines(&self) -> impl DoubleEndedIterator { Lines { string: self, } @@ -587,7 +579,7 @@ impl<'a> DoubleEndedIterator for CharsRef<'a> { /// /// This type is created by [`AsciiChar::split()`](struct.AsciiChar.html#method.split). #[derive(Clone, Debug)] -pub struct Split<'a> { +struct Split<'a> { on: AsciiChar, ended: bool, chars: Chars<'a> @@ -629,7 +621,7 @@ impl<'a> DoubleEndedIterator for Split<'a> { /// An iterator over the lines of the internal character array. #[derive(Clone, Debug)] -pub struct Lines<'a> { +struct Lines<'a> { string: &'a AsciiStr, } impl<'a> Iterator for Lines<'a> { diff --git a/src/lib.rs b/src/lib.rs index 870fdfe..f4b9f1a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -50,7 +50,7 @@ mod serialization; pub use ascii_char::{AsciiChar, ToAsciiChar, ToAsciiCharError}; pub use ascii_str::{AsciiStr, AsAsciiStr, AsMutAsciiStr, AsAsciiStrError}; -pub use ascii_str::{Chars, CharsMut, CharsRef, Lines, Split}; +pub use ascii_str::{Chars, CharsMut, CharsRef}; #[cfg(feature = "std")] pub use ascii_string::{AsciiString, IntoAsciiString, FromAsciiError}; pub use free_functions::{caret_encode, caret_decode}; From bedace4ffff6180ea806ce8d9022a59b64235690 Mon Sep 17 00:00:00 2001 From: tormol Date: Sun, 14 Jul 2019 17:38:51 +0200 Subject: [PATCH 16/24] Constify a tiny few AsciiStr methods * as_slice() * as_ptr() * AsAsciiStrError::valid_up_to() * #![no_std] AsAsciiStrError::description() All other methods either require pointer "dereferencing", branching, calling not yet const fn std methods or return impl Trait. --- src/ascii_str.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/ascii_str.rs b/src/ascii_str.rs index f9bf0b7..20dfe3f 100644 --- a/src/ascii_str.rs +++ b/src/ascii_str.rs @@ -39,7 +39,7 @@ impl AsciiStr { /// Returns the entire string as slice of `AsciiChar`s. #[inline] - pub fn as_slice(&self) -> &[AsciiChar] { + pub const fn as_slice(&self) -> &[AsciiChar] { &self.slice } @@ -55,7 +55,7 @@ impl AsciiStr { /// will end up pointing to garbage. Modifying the `AsciiStr` may cause it's buffer to be /// reallocated, which would also make any pointers to it invalid. #[inline] - pub fn as_ptr(&self) -> *const AsciiChar { + pub const fn as_ptr(&self) -> *const AsciiChar { self.as_slice().as_ptr() } @@ -683,13 +683,13 @@ impl AsAsciiStrError { /// /// It is the maximum index such that `from_ascii(input[..index])` would return `Ok(_)`. #[inline] - pub fn valid_up_to(self) -> usize { + pub const fn valid_up_to(self) -> usize { self.0 } #[cfg(not(feature = "std"))] /// Returns a description for this error, like `std::error::Error::description`. #[inline] - pub fn description(&self) -> &'static str { + pub const fn description(&self) -> &'static str { ERRORMSG_STR } } From c41ba9f21a36a56c9ba9c8ca4d55e4a917e2bae2 Mon Sep 17 00:00:00 2001 From: tormol Date: Sun, 14 Jul 2019 21:15:30 +0200 Subject: [PATCH 17/24] Implement more AsRef and genericize Extend and IntoIterator * Implement identity AsRef and AsMut for AsciiStr. * Implement AsRe for AsciiChar. * implement Extend and IntoIterator for any iterable with Item=AsRef. --- src/ascii_str.rs | 21 ++++++++++++++++- src/ascii_string.rs | 56 +++++---------------------------------------- tests.rs | 3 ++- 3 files changed, 28 insertions(+), 52 deletions(-) diff --git a/src/ascii_str.rs b/src/ascii_str.rs index 20dfe3f..e09ad33 100644 --- a/src/ascii_str.rs +++ b/src/ascii_str.rs @@ -3,7 +3,7 @@ use core::fmt; use core::ops::{Index, IndexMut}; use core::ops::{Range, RangeTo, RangeFrom, RangeFull, RangeInclusive, RangeToInclusive}; -use core::slice::{Iter, IterMut}; +use core::slice::{self, Iter, IterMut}; #[cfg(feature = "std")] use std::error::Error; #[cfg(feature = "std")] @@ -355,6 +355,18 @@ impl From> for Box { } } +impl AsRef for AsciiStr { + #[inline] + fn as_ref(&self) -> &AsciiStr { + self + } +} +impl AsMut for AsciiStr { + #[inline] + fn as_mut(&mut self) -> &mut AsciiStr { + self + } +} impl AsRef for [AsciiChar] { #[inline] fn as_ref(&self) -> &AsciiStr { @@ -408,6 +420,13 @@ widen_box! {[AsciiChar]} widen_box! {[u8]} widen_box! {str} +// allows &AsciiChar to be used by generic AsciiString Extend and FromIterator +impl AsRef for AsciiChar { + fn as_ref(&self) -> &AsciiStr { + slice::from_ref(self).into() + } +} + impl fmt::Display for AsciiStr { #[inline] fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { diff --git a/src/ascii_string.rs b/src/ascii_string.rs index 6b29a75..06b36aa 100644 --- a/src/ascii_string.rs +++ b/src/ascii_string.rs @@ -566,65 +566,21 @@ impl fmt::Write for AsciiString { } } -impl FromIterator for AsciiString { - fn from_iter>(iter: I) -> AsciiString { +impl> FromIterator for AsciiString { + fn from_iter>(iter: I) -> AsciiString { let mut buf = AsciiString::new(); buf.extend(iter); buf } } -impl<'a> FromIterator<&'a AsciiStr> for AsciiString { - fn from_iter>(iter: I) -> AsciiString { - let mut buf = AsciiString::new(); - buf.extend(iter); - buf - } -} - -impl<'a> FromIterator> for AsciiString { - fn from_iter>>(iter: I) -> AsciiString { - let mut buf = AsciiString::new(); - buf.extend(iter); - buf - } -} - -impl Extend for AsciiString { - fn extend>(&mut self, iterable: I) { - let iterator = iterable.into_iter(); - let (lower_bound, _) = iterator.size_hint(); - self.reserve(lower_bound); - for ch in iterator { - self.push(ch) - } - } -} - -impl<'a> Extend<&'a AsciiChar> for AsciiString { - fn extend>(&mut self, iter: I) { - self.extend(iter.into_iter().cloned()) - } -} - -impl<'a> Extend<&'a AsciiStr> for AsciiString { - fn extend>(&mut self, iterable: I) { - let iterator = iterable.into_iter(); - let (lower_bound, _) = iterator.size_hint(); - self.reserve(lower_bound); - for s in iterator { - self.push_str(s) - } - } -} - -impl<'a> Extend> for AsciiString { - fn extend>>(&mut self, iterable: I) { +impl> Extend for AsciiString { + fn extend>(&mut self, iterable: I) { let iterator = iterable.into_iter(); let (lower_bound, _) = iterator.size_hint(); self.reserve(lower_bound); - for s in iterator { - self.push_str(&*s); + for item in iterator { + self.push_str(item.as_ref()) } } } diff --git a/tests.rs b/tests.rs index 3af5e40..178ec54 100644 --- a/tests.rs +++ b/tests.rs @@ -137,5 +137,6 @@ fn extend_from_iterator() { } }); s.extend(cows); - assert_eq!(s, "abcabconetwothreeASCIIASCIIASCII"); + s.extend(&[AsciiChar::LineFeed]); + assert_eq!(s, "abcabconetwothreeASCIIASCIIASCII\n"); } From cea27b1fc707fc97929b42caac10f7df6e520a26 Mon Sep 17 00:00:00 2001 From: tormol Date: Sun, 14 Jul 2019 21:22:25 +0200 Subject: [PATCH 18/24] Extend AsAsciiStr to easily convert sub-slices * Add methods slice_ascii(), and get_ascii() to AsAsciiStr * Add methods slice_ascii_mut() to AsMutasciiStr The main use case is avoiding accidentally panicking by slicing a str in the middle of a codepoint with AsciiStr::from_ascii(str[a..=b]) or AsciiChar::from(str[n]). I had hoped to finally use RangeArgument, but almost nothing in std takes it because they instead went for their own unstable and sealed trait (SliceIndex). Converting between them is also impossible without going through trait objects. SliceIndex is actually workable though, albeit with one wart (associated type). Working with mutable slices is hairy as usual. --- src/ascii_str.rs | 280 +++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 236 insertions(+), 44 deletions(-) diff --git a/src/ascii_str.rs b/src/ascii_str.rs index e09ad33..d2e5942 100644 --- a/src/ascii_str.rs +++ b/src/ascii_str.rs @@ -3,7 +3,7 @@ use core::fmt; use core::ops::{Index, IndexMut}; use core::ops::{Range, RangeTo, RangeFrom, RangeFull, RangeInclusive, RangeToInclusive}; -use core::slice::{self, Iter, IterMut}; +use core::slice::{self, Iter, IterMut, SliceIndex}; #[cfg(feature = "std")] use std::error::Error; #[cfg(feature = "std")] @@ -725,60 +725,123 @@ impl Error for AsAsciiStrError { } } -/// Convert slices of bytes to `AsciiStr`. +/// Convert slices of bytes or AsciiChar to `AsciiStr`. +// Could nearly replace this trait with SliceIndex, but its methods isn't even +// on a path for stabilization. pub trait AsAsciiStr { + /// Used to constrain `SliceIndex` + #[doc(hidden)] + type Inner; + /// Convert a subslice to an ASCII slice. + /// + /// Returns `Err` if the range is out of bounds or if not all bytes in the + /// slice are ASCII. The value in the error will be the index of the first + /// non-ASCII byte or the end of the slice. + /// + /// # Examples + /// ``` + /// use ascii::AsAsciiStr; + /// assert!("'zoä'".slice_ascii(..3).is_ok()); + /// assert!("'zoä'".slice_ascii(0..4).is_err()); + /// assert!("'zoä'".slice_ascii(5..=5).is_ok()); + /// assert!("'zoä'".slice_ascii(4..).is_err()); + /// assert!(b"\r\n".slice_ascii(..).is_ok()); + /// ``` + fn slice_ascii(&self, range: R) -> Result<&AsciiStr, AsAsciiStrError> + where R: SliceIndex<[Self::Inner], Output=[Self::Inner]>; + /// Convert to an ASCII slice. + /// + /// # Example + /// ``` + /// use ascii::{AsAsciiStr, AsciiChar}; + /// assert!("ASCII".as_ascii_str().is_ok()); + /// assert!(b"\r\n".as_ascii_str().is_ok()); + /// assert!("'zoä'".as_ascii_str().is_err()); + /// assert!(b"\xff".as_ascii_str().is_err()); + /// assert!([AsciiChar::C][..].as_ascii_str().is_ok()); // infallible + /// ``` + fn as_ascii_str(&self) -> Result<&AsciiStr, AsAsciiStrError> { + self.slice_ascii(..) + } + /// Get a single ASCII character from the slice. + /// + /// Returns `None` if the index is out of bounds or the byte is not ASCII. + /// + /// # Examples + /// ``` + /// use ascii::{AsAsciiStr, AsciiChar}; + /// assert_eq!("'zoä'".get_ascii(4), None); + /// assert_eq!("'zoä'".get_ascii(5), Some(AsciiChar::Apostrophe)); + /// assert_eq!("'zoä'".get_ascii(6), None); + /// ``` + fn get_ascii(&self, index: usize) -> Option { + self.slice_ascii(index..=index).ok().and_then(|str| str.first()) + } /// Convert to an ASCII slice without checking for non-ASCII characters. + /// + /// # Examples + /// unsafe fn as_ascii_str_unchecked(&self) -> &AsciiStr; - /// Convert to an ASCII slice. - fn as_ascii_str(&self) -> Result<&AsciiStr, AsAsciiStrError>; } -/// Convert mutable slices of bytes to `AsciiStr`. -pub trait AsMutAsciiStr { +/// Convert mutable slices of bytes or AsciiChar to `AsciiStr`. +pub trait AsMutAsciiStr: AsAsciiStr { + /// Convert a subslice to an ASCII slice. + fn slice_ascii_mut(&mut self, range: R) -> Result<&mut AsciiStr, AsAsciiStrError> + where R: SliceIndex<[Self::Inner], Output=[Self::Inner]>; + /// Convert to a mutable ASCII slice. + fn as_mut_ascii_str(&mut self) -> Result<&mut AsciiStr, AsAsciiStrError> { + self.slice_ascii_mut(..) + } /// Convert to a mutable ASCII slice without checking for non-ASCII characters. unsafe fn as_mut_ascii_str_unchecked(&mut self) -> &mut AsciiStr; - /// Convert to a mutable ASCII slice. - fn as_mut_ascii_str(&mut self) -> Result<&mut AsciiStr, AsAsciiStrError>; } // These generic implementations mirror the generic implementations for AsRef in core. impl<'a, T: ?Sized> AsAsciiStr for &'a T where T: AsAsciiStr { - #[inline] - fn as_ascii_str(&self) -> Result<&AsciiStr, AsAsciiStrError> { - ::as_ascii_str(*self) + type Inner = ::Inner; + fn slice_ascii(&self, range: R) -> Result<&AsciiStr, AsAsciiStrError> + where R: SliceIndex<[Self::Inner], Output=[Self::Inner]> + { + ::slice_ascii(*self, range) } - - #[inline] unsafe fn as_ascii_str_unchecked(&self) -> &AsciiStr { ::as_ascii_str_unchecked(*self) } } impl<'a, T: ?Sized> AsAsciiStr for &'a mut T where T: AsAsciiStr { - #[inline] - fn as_ascii_str(&self) -> Result<&AsciiStr, AsAsciiStrError> { - ::as_ascii_str(*self) + type Inner = ::Inner; + fn slice_ascii(&self, range: R) -> Result<&AsciiStr, AsAsciiStrError> + where R: SliceIndex<[Self::Inner], Output=[Self::Inner]> + { + ::slice_ascii(*self, range) } - #[inline] unsafe fn as_ascii_str_unchecked(&self) -> &AsciiStr { ::as_ascii_str_unchecked(*self) } } impl<'a, T: ?Sized> AsMutAsciiStr for &'a mut T where T: AsMutAsciiStr { - #[inline] - fn as_mut_ascii_str(&mut self) -> Result<&mut AsciiStr, AsAsciiStrError> { - ::as_mut_ascii_str(*self) + fn slice_ascii_mut(&mut self, range: R) -> Result<&mut AsciiStr, AsAsciiStrError> + where R: SliceIndex<[Self::Inner], Output=[Self::Inner]> + { + ::slice_ascii_mut(*self, range) } - #[inline] unsafe fn as_mut_ascii_str_unchecked(&mut self) -> &mut AsciiStr { ::as_mut_ascii_str_unchecked(*self) } } impl AsAsciiStr for AsciiStr { + type Inner = AsciiChar; + fn slice_ascii(&self, range: R) -> Result<&AsciiStr, AsAsciiStrError> + where R: SliceIndex<[AsciiChar], Output=[AsciiChar]> + { + self.slice.slice_ascii(range) + } #[inline] fn as_ascii_str(&self) -> Result<&AsciiStr, AsAsciiStrError> { Ok(self) @@ -787,11 +850,16 @@ impl AsAsciiStr for AsciiStr { unsafe fn as_ascii_str_unchecked(&self) -> &AsciiStr { self } + #[inline] + fn get_ascii(&self, index: usize) -> Option { + self.slice.get_ascii(index) + } } impl AsMutAsciiStr for AsciiStr { - #[inline] - fn as_mut_ascii_str(&mut self) -> Result<&mut AsciiStr, AsAsciiStrError> { - Ok(self) + fn slice_ascii_mut(&mut self, range: R) -> Result<&mut AsciiStr, AsAsciiStrError> + where R: SliceIndex<[AsciiChar], Output=[AsciiChar]> + { + self.slice.slice_ascii_mut(range) } #[inline] unsafe fn as_mut_ascii_str_unchecked(&mut self) -> &mut AsciiStr { @@ -800,6 +868,15 @@ impl AsMutAsciiStr for AsciiStr { } impl AsAsciiStr for [AsciiChar] { + type Inner = AsciiChar; + fn slice_ascii(&self, range: R) -> Result<&AsciiStr, AsAsciiStrError> + where R: SliceIndex<[AsciiChar], Output=[AsciiChar]> + { + match self.get(range) { + Some(slice) => Ok(slice.into()), + None => Err(AsAsciiStrError(self.len())), + } + } #[inline] fn as_ascii_str(&self) -> Result<&AsciiStr, AsAsciiStrError> { Ok(self.into()) @@ -808,11 +885,20 @@ impl AsAsciiStr for [AsciiChar] { unsafe fn as_ascii_str_unchecked(&self) -> &AsciiStr { self.into() } + #[inline] + fn get_ascii(&self, index: usize) -> Option { + self.get(index).cloned() + } } impl AsMutAsciiStr for [AsciiChar] { - #[inline] - fn as_mut_ascii_str(&mut self) -> Result<&mut AsciiStr, AsAsciiStrError> { - Ok(self.into()) + fn slice_ascii_mut(&mut self, range: R) -> Result<&mut AsciiStr, AsAsciiStrError> + where R: SliceIndex<[AsciiChar], Output=[AsciiChar]> + { + let len = self.len(); + match self.get_mut(range) { + Some(slice) => Ok(slice.into()), + None => Err(AsAsciiStrError(len)), + } } #[inline] unsafe fn as_mut_ascii_str_unchecked(&mut self) -> &mut AsciiStr { @@ -821,10 +907,24 @@ impl AsMutAsciiStr for [AsciiChar] { } impl AsAsciiStr for [u8] { + type Inner = u8; + fn slice_ascii(&self, range: R) -> Result<&AsciiStr, AsAsciiStrError> + where R: SliceIndex<[u8], Output=[u8]> + { + if let Some(slice) = self.get(range) { + slice.as_ascii_str().map_err(|AsAsciiStrError(not_ascii)| { + let offset = slice.as_ptr() as usize - self.as_ptr() as usize; + AsAsciiStrError(offset + not_ascii) + }) + } else { + Err(AsAsciiStrError(self.len())) + } + } fn as_ascii_str(&self) -> Result<&AsciiStr, AsAsciiStrError> { - match self.iter().position(|&b| b > 127) { - Some(index) => Err(AsAsciiStrError(index)), - None => unsafe { Ok(self.as_ascii_str_unchecked()) }, + if self.is_ascii() {// is_ascii is likely optimized + unsafe { Ok(self.as_ascii_str_unchecked()) } + } else { + Err(AsAsciiStrError(self.iter().take_while(|&b| b.is_ascii()).count())) } } #[inline] @@ -834,10 +934,25 @@ impl AsAsciiStr for [u8] { } } impl AsMutAsciiStr for [u8] { + fn slice_ascii_mut(&mut self, range: R) -> Result<&mut AsciiStr, AsAsciiStrError> + where R: SliceIndex<[u8], Output=[u8]> + { + let (ptr, len) = (self.as_ptr(), self.len()); + if let Some(slice) = self.get_mut(range) { + let slice_ptr = slice.as_ptr(); + slice.as_mut_ascii_str().map_err(|AsAsciiStrError(not_ascii)| { + let offset = slice_ptr as usize - ptr as usize; + AsAsciiStrError(offset + not_ascii) + }) + } else { + Err(AsAsciiStrError(len)) + } + } fn as_mut_ascii_str(&mut self) -> Result<&mut AsciiStr, AsAsciiStrError> { - match self.iter().position(|&b| b > 127) { - Some(index) => Err(AsAsciiStrError(index)), - None => unsafe { Ok(self.as_mut_ascii_str_unchecked()) }, + if self.is_ascii() {// is_ascii() is likely optimized + unsafe { Ok(self.as_mut_ascii_str_unchecked()) } + } else { + Err(AsAsciiStrError(self.iter().take_while(|&b| b.is_ascii()).count())) } } #[inline] @@ -848,6 +963,12 @@ impl AsMutAsciiStr for [u8] { } impl AsAsciiStr for str { + type Inner = u8; + fn slice_ascii(&self, range: R) -> Result<&AsciiStr, AsAsciiStrError> + where R: SliceIndex<[u8], Output=[u8]> + { + self.as_bytes().slice_ascii(range) + } fn as_ascii_str(&self) -> Result<&AsciiStr, AsAsciiStrError> { self.as_bytes().as_ascii_str() } @@ -857,6 +978,25 @@ impl AsAsciiStr for str { } } impl AsMutAsciiStr for str { + fn slice_ascii_mut(&mut self, range: R) -> Result<&mut AsciiStr, AsAsciiStrError> + where R: SliceIndex<[u8], Output=[u8]> + { + let (ptr, len) = if let Some(slice) = self.as_bytes().get(range) { + if !slice.is_ascii() { + let offset = slice.as_ptr() as usize - self.as_ptr() as usize; + let not_ascii = slice.iter().take_while(|&b| b.is_ascii()).count(); + return Err(AsAsciiStrError(offset+not_ascii)); + } + (slice.as_ptr(), slice.len()) + } else { + return Err(AsAsciiStrError(self.len())); + }; + unsafe { + let ptr = ptr as *const AsciiChar as *mut AsciiChar; + let slice = core::slice::from_raw_parts_mut(ptr, len); + Ok(slice.into()) + } + } fn as_mut_ascii_str(&mut self) -> Result<&mut AsciiStr, AsAsciiStrError> { match self.bytes().position(|b| b > 127) { Some(index) => Err(AsAsciiStrError(index)), @@ -873,6 +1013,12 @@ impl AsMutAsciiStr for str { /// Note that the trailing null byte will be removed in the conversion. #[cfg(feature = "std")] impl AsAsciiStr for CStr { + type Inner = u8; + fn slice_ascii(&self, range: R) -> Result<&AsciiStr, AsAsciiStrError> + where R: SliceIndex<[u8], Output=[u8]> + { + self.to_bytes().slice_ascii(range) + } #[inline] fn as_ascii_str(&self) -> Result<&AsciiStr, AsAsciiStrError> { self.to_bytes().as_ascii_str() @@ -934,21 +1080,67 @@ mod tests { } #[test] - #[cfg(feature = "std")] fn as_ascii_str() { + macro_rules! err {{$i:expr} => {Err(AsAsciiStrError($i))}} + let s = "abčd"; + let b = s.as_bytes(); + assert_eq!(s.as_ascii_str(), err!(2)); + assert_eq!(b.as_ascii_str(), err!(2)); + let a: &AsciiStr = [AsciiChar::a, AsciiChar::b][..].as_ref(); + assert_eq!(s[..2].as_ascii_str(), Ok(a)); + assert_eq!(b[..2].as_ascii_str(), Ok(a)); + assert_eq!(s.slice_ascii(..2), Ok(a)); + assert_eq!(b.slice_ascii(..2), Ok(a)); + assert_eq!(s.slice_ascii(..=2), err!(2)); + assert_eq!(b.slice_ascii(..=2), err!(2)); + assert_eq!(s.get_ascii(4), Some(AsciiChar::d)); + assert_eq!(b.get_ascii(4), Some(AsciiChar::d)); + assert_eq!(s.get_ascii(3), None); + assert_eq!(b.get_ascii(3), None); + assert_eq!(s.get_ascii(b.len()), None); + assert_eq!(b.get_ascii(b.len()), None); + assert_eq!(a.get_ascii(0), Some(AsciiChar::a)); + assert_eq!(a.get_ascii(a.len()), None); + } + + #[test] + #[cfg(feature = "std")] + fn cstr_as_ascii_str() { + use std::ffi::CStr; + macro_rules! err {{$i:expr} => {Err(AsAsciiStrError($i))}} + let cstr = CStr::from_bytes_with_nul(b"a\xbbcde\xffg\0").unwrap(); + assert_eq!(cstr.as_ascii_str(), err!(1)); + assert_eq!(cstr.slice_ascii(2..), err!(5)); + assert_eq!(cstr.get_ascii(5), None); + assert_eq!(cstr.get_ascii(6), Some(AsciiChar::g)); + assert_eq!(cstr.get_ascii(7), None); + let aslice = &[AsciiChar::X, AsciiChar::Y, AsciiChar::Z, AsciiChar::Null][..]; + let astr: &AsciiStr = aslice.as_ref(); + let cstr = CStr::from_bytes_with_nul(astr.as_bytes()).unwrap(); + assert_eq!(cstr.slice_ascii(..2), Ok(&astr[..2])); + assert_eq!(cstr.as_ascii_str(), Ok(&astr[..3])); + } + + #[test] + #[cfg(feature = "std")] + fn as_mut_ascii_str() { macro_rules! err {{$i:expr} => {Err(AsAsciiStrError($i))}} let mut s: String = "abčd".to_string(); let mut b: Vec = s.clone().into(); - assert_eq!(s.as_str().as_ascii_str(), err!(2)); - assert_eq!(s.as_mut_str().as_mut_ascii_str(), err!(2)); - assert_eq!(b.as_slice().as_ascii_str(), err!(2)); - assert_eq!(b.as_mut_slice().as_mut_ascii_str(), err!(2)); - let mut a = [AsciiChar::a, AsciiChar::b]; - assert_eq!((&s[..2]).as_ascii_str(), Ok((&a[..]).into())); - assert_eq!((&b[..2]).as_ascii_str(), Ok((&a[..]).into())); - let a = Ok((&mut a[..]).into()); - assert_eq!((&mut s[..2]).as_mut_ascii_str(), a); - assert_eq!((&mut b[..2]).as_mut_ascii_str(), a); + let mut first = [AsciiChar::a, AsciiChar::b]; + let mut second = [AsciiChar::d]; + assert_eq!(s.as_mut_ascii_str(), err!(2)); + assert_eq!(b.as_mut_ascii_str(), err!(2)); + assert_eq!(s.slice_ascii_mut(..), err!(2)); + assert_eq!(b.slice_ascii_mut(..), err!(2)); + assert_eq!(s[..2].as_mut_ascii_str(), Ok((&mut first[..]).into())); + assert_eq!(b[..2].as_mut_ascii_str(), Ok((&mut first[..]).into())); + assert_eq!(s.slice_ascii_mut(0..2), Ok((&mut first[..]).into())); + assert_eq!(b.slice_ascii_mut(0..2), Ok((&mut first[..]).into())); + assert_eq!(s.slice_ascii_mut(4..), Ok((&mut second[..]).into())); + assert_eq!(b.slice_ascii_mut(4..), Ok((&mut second[..]).into())); + assert_eq!(s.slice_ascii_mut(4..=10), err!(5)); + assert_eq!(b.slice_ascii_mut(4..=10), err!(5)); } #[test] From 7c43f755252aaa72014c126128c3d0531d56cc90 Mon Sep 17 00:00:00 2001 From: tormol Date: Sun, 14 Jul 2019 20:49:53 +0200 Subject: [PATCH 19/24] Improve AsciiChar compatibility with u8 and char * Change is_whitespace() to also return true for AsciiCHar::VT and ::FF. * Rename a bunch of ctype methods and make them take self by reference: * is_digit() => is_ascii_digit() * is_hex() => is_ascii_hexdigit() + is_control() => is_ascii_control() + is_graphic() => is_ascii_graphic() + is_blank() => is_ascii_blank() + is_print() => is_ascii_printable() * is_punctuation() => is_ascii_punctuation() * Ddd identical _ascii methods when char also has methods without _ascii, except that the _ascii methods take self by reference: * is_ascii_alphabetic() = is_alphabetic() * is_ascii_alphanumeric() = is_alphanumeric() * is_ascii_uppercase() = is_uppercase() * is_ascii_lowercase() = is_lowercase() * Add is_digit() which takes base as parameter. * Add is_ascii_whitespace() which returns true for FF but not VT. --- src/ascii_char.rs | 211 +++++++++++++++++++++++++++++++++++----------- 1 file changed, 161 insertions(+), 50 deletions(-) diff --git a/src/ascii_char.rs b/src/ascii_char.rs index 5203c77..9705fc4 100644 --- a/src/ascii_char.rs +++ b/src/ascii_char.rs @@ -383,7 +383,12 @@ impl AsciiChar { self as u8 as char } - // the following methods are like ctype, and the implementation is inspired by musl + // the following methods are like ctype, and the implementation is inspired by musl. + // The ascii_ methods take self by reference for maximum compatibility + // with the corresponding methods on u8 and char. + // It is bad for both usability and performance, but marking those + // that doesn't have a non-ascii sibling #[inline] should + // make the compiler optimize away the indirection. /// Turns uppercase into lowercase, but also modifies '@' and '<'..='_' const fn to_not_upper(self) -> u8 { @@ -396,28 +401,92 @@ impl AsciiChar { (self.to_not_upper() >= b'a') & (self.to_not_upper() <= b'z') } + /// Check if the character is a letter (a-z, A-Z). + /// + /// This method is identical to [`is_alphabetic()`](#method.is_alphabetic) + pub fn is_ascii_alphabetic(&self) -> bool { + self.is_alphabetic() + } + + /// Check if the character is a digit in the given radix. + /// + /// If the radix is always 10 or 16, + /// [`is_ascii_digit()`](#method.is_ascii_digit) and + /// [`is_ascii_hexdigit()`](#method.is_ascii_hexdigit()) will be faster. + /// + /// # Panics + /// + /// Radixes greater than 36 are not supported and will result in a panic. + pub fn is_digit(self, radix: u32) -> bool { + match (self as u8, radix) { + (b'0'..=b'9', 0..=36) => u32::from(self as u8 - b'0') < radix, + (b'a'..=b'z', 11..=36) => u32::from(self as u8 - b'a') < radix - 10, + (b'A'..=b'Z', 11..=36) => u32::from(self as u8 - b'A') < radix - 10, + (_, 0..=36) => false, + (_, _) => panic!("radixes greater than 36 are not supported"), + } + } + /// Check if the character is a number (0-9) + /// + /// # Examples + /// ``` + /// # use ascii::AsciiChar; + /// assert_eq!(AsciiChar::new('0').is_ascii_digit(), true); + /// assert_eq!(AsciiChar::new('9').is_ascii_digit(), true); + /// assert_eq!(AsciiChar::new('a').is_ascii_digit(), false); + /// assert_eq!(AsciiChar::new('A').is_ascii_digit(), false); + /// assert_eq!(AsciiChar::new('/').is_ascii_digit(), false); + /// ``` #[inline] - pub const fn is_digit(self) -> bool { - (self as u8 >= b'0') & (self as u8 <= b'9') + pub const fn is_ascii_digit(&self) -> bool { + (*self as u8 >= b'0') & (*self as u8 <= b'9') } /// Check if the character is a letter or number #[inline] pub const fn is_alphanumeric(self) -> bool { - self.is_alphabetic() | self.is_digit() + self.is_alphabetic() | self.is_ascii_digit() + } + + /// Check if the character is a letter or number + /// + /// This method is identical to [`is_alphanumeric()`](#method.is_alphanumeric) + pub fn is_ascii_alphanumeric(&self) -> bool { + self.is_alphanumeric() } /// Check if the character is a space or horizontal tab + /// + /// # Examples + /// ``` + /// # use ascii::AsciiChar; + /// assert!(AsciiChar::Space.is_ascii_blank()); + /// assert!(AsciiChar::Tab.is_ascii_blank()); + /// assert!(!AsciiChar::VT.is_ascii_blank()); + /// assert!(!AsciiChar::LineFeed.is_ascii_blank()); + /// assert!(!AsciiChar::CarriageReturn.is_ascii_blank()); + /// assert!(!AsciiChar::FF.is_ascii_blank()); + /// ``` #[inline] - pub const fn is_blank(self) -> bool { + pub const fn is_ascii_blank(self) -> bool { (self as u8 == b' ') | (self as u8 == b'\t') } - /// Check if the character is a ' ', '\t', '\n' or '\r' + /// Check if the character one of ' ', '\t', '\n', '\r', + /// '\0xb' (vertical tab) or '\0xc' (form feed). #[inline] pub const fn is_whitespace(self) -> bool { - self.is_blank() | (self as u8 == b'\n') | (self as u8 == b'\r') + let b = self as u8; + self.is_ascii_blank() | (b == b'\n') | (b == b'\r') | (b == 0x0b) | (b == 0x0c) + } + + /// Check if the character is a ' ', '\t', '\n', '\r' or '\0xc' (form feed). + /// + /// This method is NOT identical to `is_whitespace()`. + #[inline] + pub const fn is_ascii_whitespace(self) -> bool { + self.is_ascii_blank() | (self as u8 == b'\n') | (self as u8 == b'\r') | (self as u8 == 0x0c) } /// Check if the character is a control character @@ -425,14 +494,16 @@ impl AsciiChar { /// # Examples /// ``` /// # use ascii::AsciiChar; - /// assert_eq!(AsciiChar::new('\0').is_control(), true); - /// assert_eq!(AsciiChar::new('n').is_control(), false); - /// assert_eq!(AsciiChar::new(' ').is_control(), false); - /// assert_eq!(AsciiChar::new('\n').is_control(), true); + /// assert_eq!(AsciiChar::new('\0').is_ascii_control(), true); + /// assert_eq!(AsciiChar::new('n').is_ascii_control(), false); + /// assert_eq!(AsciiChar::new(' ').is_ascii_control(), false); + /// assert_eq!(AsciiChar::new('\n').is_ascii_control(), true); + /// assert_eq!(AsciiChar::new('\t').is_ascii_control(), true); + /// assert_eq!(AsciiChar::EOT.is_ascii_control(), true); /// ``` #[inline] - pub const fn is_control(self) -> bool { - ((self as u8) < b' ') | (self as u8 == 127) + pub const fn is_ascii_control(&self) -> bool { + ((*self as u8) < b' ') | (*self as u8 == 127) } /// Checks if the character is printable (except space) @@ -440,12 +511,12 @@ impl AsciiChar { /// # Examples /// ``` /// # use ascii::AsciiChar; - /// assert_eq!(AsciiChar::new('n').is_graph(), true); - /// assert_eq!(AsciiChar::new(' ').is_graph(), false); - /// assert_eq!(AsciiChar::new('\n').is_graph(), false); + /// assert_eq!(AsciiChar::new('n').is_ascii_graphic(), true); + /// assert_eq!(AsciiChar::new(' ').is_ascii_graphic(), false); + /// assert_eq!(AsciiChar::new('\n').is_ascii_graphic(), false); /// ``` #[inline] - pub const fn is_graph(self) -> bool { + pub const fn is_ascii_graphic(&self) -> bool { self.as_byte().wrapping_sub(b' ' + 1) < 0x5E } @@ -454,16 +525,16 @@ impl AsciiChar { /// # Examples /// ``` /// # use ascii::AsciiChar; - /// assert_eq!(AsciiChar::new('n').is_print(), true); - /// assert_eq!(AsciiChar::new(' ').is_print(), true); - /// assert_eq!(AsciiChar::new('\n').is_print(), false); + /// assert_eq!(AsciiChar::new('n').is_ascii_printable(), true); + /// assert_eq!(AsciiChar::new(' ').is_ascii_printable(), true); + /// assert_eq!(AsciiChar::new('\n').is_ascii_printable(), false); /// ``` #[inline] - pub const fn is_print(self) -> bool { + pub const fn is_ascii_printable(&self) -> bool { self.as_byte().wrapping_sub(b' ') < 0x5F } - /// Checks if the character is alphabetic and lowercase + /// Checks if the character is alphabetic and lowercase (a-z). /// /// # Examples /// ``` @@ -477,7 +548,14 @@ impl AsciiChar { self.as_byte().wrapping_sub(b'a') < 26 } - /// Checks if the character is alphabetic and uppercase + /// Checks if the character is alphabetic and lowercase (a-z). + /// + /// This method is identical to [`is_lowercase()`](#method.is_lowercase) + pub fn is_ascii_lowercase(&self) -> bool { + self.is_lowercase() + } + + /// Checks if the character is alphabetic and uppercase (A-Z). /// /// # Examples /// ``` @@ -491,19 +569,26 @@ impl AsciiChar { self.as_byte().wrapping_sub(b'A') < 26 } + /// Checks if the character is alphabetic and uppercase (A-Z). + /// + /// This method is identical to [`is_uppercase()`](#method.is_uppercase) + pub fn is_ascii_uppercase(&self) -> bool { + self.is_uppercase() + } + /// Checks if the character is punctuation /// /// # Examples /// ``` /// # use ascii::AsciiChar; - /// assert_eq!(AsciiChar::new('n').is_punctuation(), false); - /// assert_eq!(AsciiChar::new(' ').is_punctuation(), false); - /// assert_eq!(AsciiChar::new('_').is_punctuation(), true); - /// assert_eq!(AsciiChar::new('~').is_punctuation(), true); + /// assert_eq!(AsciiChar::new('n').is_ascii_punctuation(), false); + /// assert_eq!(AsciiChar::new(' ').is_ascii_punctuation(), false); + /// assert_eq!(AsciiChar::new('_').is_ascii_punctuation(), true); + /// assert_eq!(AsciiChar::new('~').is_ascii_punctuation(), true); /// ``` #[inline] - pub const fn is_punctuation(self) -> bool { - self.is_graph() & !self.is_alphanumeric() + pub const fn is_ascii_punctuation(&self) -> bool { + self.is_ascii_graphic() & !self.is_alphanumeric() } /// Checks if the character is a valid hex digit @@ -511,15 +596,15 @@ impl AsciiChar { /// # Examples /// ``` /// # use ascii::AsciiChar; - /// assert_eq!(AsciiChar::new('5').is_hex(), true); - /// assert_eq!(AsciiChar::new('a').is_hex(), true); - /// assert_eq!(AsciiChar::new('F').is_hex(), true); - /// assert_eq!(AsciiChar::new('G').is_hex(), false); - /// assert_eq!(AsciiChar::new(' ').is_hex(), false); + /// assert_eq!(AsciiChar::new('5').is_ascii_hexdigit(), true); + /// assert_eq!(AsciiChar::new('a').is_ascii_hexdigit(), true); + /// assert_eq!(AsciiChar::new('F').is_ascii_hexdigit(), true); + /// assert_eq!(AsciiChar::new('G').is_ascii_hexdigit(), false); + /// assert_eq!(AsciiChar::new(' ').is_ascii_hexdigit(), false); /// ``` #[inline] - pub const fn is_hex(self) -> bool { - self.is_digit() | ((self as u8 | 0x20u8).wrapping_sub(b'a') < 6) + pub const fn is_ascii_hexdigit(&self) -> bool { + self.is_ascii_digit() | ((*self as u8 | 0x20u8).wrapping_sub(b'a') < 6) } /// Unicode has printable versions of the ASCII control codes, like '␛'. @@ -781,6 +866,12 @@ mod tests { assert!(generic('λ').is_err()); } + #[test] + fn as_byte_and_char() { + assert_eq!(A.as_byte(), b'A'); + assert_eq!(A.as_char(), 'A'); + } + #[test] fn new_array_is_correct() { for byte in 0..128u8 { @@ -789,26 +880,46 @@ mod tests { } #[test] - fn as_byte_and_char() { - assert_eq!(A.as_byte(), b'A'); - assert_eq!(A.as_char(), 'A'); + fn is_all() { + for byte in 0..128u8 { + let ch = byte as char; + let ascii = AsciiChar::new(ch); + assert_eq!(ascii.is_alphabetic(), ch.is_alphabetic()); + assert_eq!(ascii.is_ascii_alphabetic(), ch.is_ascii_alphabetic()); + assert_eq!(ascii.is_alphanumeric(), ch.is_alphanumeric()); + assert_eq!(ascii.is_ascii_alphanumeric(), ch.is_ascii_alphanumeric()); + assert_eq!(ascii.is_digit(8), ch.is_digit(8), "is_digit(8) {:?}", ch); + assert_eq!(ascii.is_digit(10), ch.is_digit(10), "is_digit(10) {:?}", ch); + assert_eq!(ascii.is_digit(16), ch.is_digit(16), "is_digit(16) {:?}", ch); + assert_eq!(ascii.is_digit(36), ch.is_digit(36), "is_digit(36) {:?}", ch); + assert_eq!(ascii.is_ascii_digit(), ch.is_ascii_digit()); + assert_eq!(ascii.is_ascii_hexdigit(), ch.is_ascii_hexdigit()); + assert_eq!(ascii.is_ascii_control(), ch.is_ascii_control()); + assert_eq!(ascii.is_ascii_graphic(), ch.is_ascii_graphic()); + assert_eq!(ascii.is_ascii_punctuation(), ch.is_ascii_punctuation()); + assert_eq!(ascii.is_whitespace(), ch.is_whitespace(), "{:?} ({:#04x})", ch, byte); + assert_eq!(ascii.is_ascii_whitespace(), ch.is_ascii_whitespace(), "{:?} ({:#04x})", ch, byte); + assert_eq!(ascii.is_uppercase(), ch.is_uppercase()); + assert_eq!(ascii.is_ascii_uppercase(), ch.is_ascii_uppercase()); + assert_eq!(ascii.is_lowercase(), ch.is_lowercase()); + assert_eq!(ascii.is_ascii_lowercase(), ch.is_ascii_lowercase()); + assert_eq!(ascii.to_ascii_uppercase(), ch.to_ascii_uppercase()); + assert_eq!(ascii.to_ascii_lowercase(), ch.to_ascii_lowercase()); + } } #[test] - fn is_digit() { - assert_eq!(_0.is_digit(), true); - assert_eq!(_9.is_digit(), true); - assert_eq!(O.is_digit(), false); - assert_eq!(o.is_digit(), false); - assert_eq!(Slash.is_digit(), false); - assert_eq!(Colon.is_digit(), false); + fn is_digit_strange_radixes() { + assert_eq!(AsciiChar::_0.is_digit(0), '0'.is_digit(0)); + assert_eq!(AsciiChar::_0.is_digit(1), '0'.is_digit(1)); + assert_eq!(AsciiChar::_5.is_digit(5), '5'.is_digit(5)); + assert_eq!(AsciiChar::z.is_digit(35), 'z'.is_digit(35)); } #[test] - fn is_control() { - assert_eq!(US.is_control(), true); - assert_eq!(DEL.is_control(), true); - assert_eq!(Space.is_control(), false); + #[should_panic] + fn is_digit_bad_radix() { + AsciiChar::_7.is_digit(37); } #[test] From f6c5e0608f1cc29b673e1274cac9d8d95a83fd3d Mon Sep 17 00:00:00 2001 From: tormol Date: Sun, 14 Jul 2019 23:06:44 +0200 Subject: [PATCH 20/24] Increase minimum supported Rust version and loosen policy for 1.0 --- .travis.yml | 2 +- README.md | 13 ++++++++----- src/lib.rs | 11 +++++++---- 3 files changed, 16 insertions(+), 10 deletions(-) diff --git a/.travis.yml b/.travis.yml index 19dc492..fc07336 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,7 +5,7 @@ rust: - nightly - beta - stable - - 1.9.0 + - 1.36.0 before_script: - | diff --git a/README.md b/README.md index 02b046a..5457ffc 100644 --- a/README.md +++ b/README.md @@ -30,11 +30,14 @@ following dependency declaration in `Cargo.toml`: ascii = { version = "0.9", default-features = false } ``` -## Requirements - -- The minimum supported Rust version is 1.9.0 -- Enabling the quickcheck feature requires Rust 1.12.0 -- Enabling the serde feature requires Rust 1.13.0 +## Minimum supported Rust version + +The minimum supported Rust version for 1.0.\* releases is 1.36.0. +Later 1.y.0 releases might require newer Rust versions, but the three most +recent stable releases at the time of publishing will always be supported. +For example this means that if the current stable Rust version is 1.44 when +ascii 1.1.0 is released, then ascii 1.1.* will not require a newer +Rust version than 1.42. ## History diff --git a/src/lib.rs b/src/lib.rs index f4b9f1a..5b6782a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -15,11 +15,14 @@ //! //! Please refer to the readme file to learn about the different feature modes of this crate. //! -//! # Requirements +//! # Minimum supported Rust version //! -//! - The minimum supported Rust version is 1.9.0 -//! - Enabling the quickcheck feature requires Rust 1.12.0 -//! - Enabling the serde feature requires Rust 1.13.0 +//! The minimum supported Rust version for 1.0.\* releases is 1.36.0. +//! Later 1.y.0 releases might require newer Rust versions, but the three most +//! recent stable releases at the time of publishing will always be supported. +//! For example this means that if the current stable Rust version is 1.44 when +//! ascii 1.1.0 is released, then ascii 1.1.* will not require a newer +//! Rust version than 1.42. //! //! # History //! From b0b407b4cd096a7d4f12da2248cbbdb38d2c0150 Mon Sep 17 00:00:00 2001 From: tormol Date: Tue, 16 Jul 2019 22:37:21 +0200 Subject: [PATCH 21/24] Decrease minimum Rust version to 1.33.0 --- .travis.yml | 2 +- README.md | 6 +++--- src/ascii_str.rs | 18 ++++++++++-------- src/ascii_string.rs | 2 +- src/lib.rs | 6 +++--- 5 files changed, 18 insertions(+), 16 deletions(-) diff --git a/.travis.yml b/.travis.yml index fc07336..03d9d7f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,7 +5,7 @@ rust: - nightly - beta - stable - - 1.36.0 + - 1.33.0 before_script: - | diff --git a/README.md b/README.md index 5457ffc..192abf0 100644 --- a/README.md +++ b/README.md @@ -32,12 +32,12 @@ ascii = { version = "0.9", default-features = false } ## Minimum supported Rust version -The minimum supported Rust version for 1.0.\* releases is 1.36.0. +The minimum Rust version for 1.0.\* releases is 1.33.0. Later 1.y.0 releases might require newer Rust versions, but the three most recent stable releases at the time of publishing will always be supported. -For example this means that if the current stable Rust version is 1.44 when +For example this means that if the current stable Rust version is 1.38 when ascii 1.1.0 is released, then ascii 1.1.* will not require a newer -Rust version than 1.42. +Rust version than 1.36. ## History diff --git a/src/ascii_str.rs b/src/ascii_str.rs index d2e5942..c13ef82 100644 --- a/src/ascii_str.rs +++ b/src/ascii_str.rs @@ -1152,14 +1152,16 @@ mod tests { #[test] fn index() { let mut arr = [AsciiChar::A, AsciiChar::B, AsciiChar::C, AsciiChar::D]; - let a: &AsciiStr = arr[..].into(); - assert_eq!(a[..].as_slice(), &a.as_slice()[..]); - assert_eq!(a[..4].as_slice(), &a.as_slice()[..4]); - assert_eq!(a[4..].as_slice(), &a.as_slice()[4..]); - assert_eq!(a[2..3].as_slice(), &a.as_slice()[2..3]); - assert_eq!(a[..=3].as_slice(), &a.as_slice()[..=3]); - assert_eq!(a[1..=1].as_slice(), &a.as_slice()[1..=1]); - let mut copy = arr.clone(); + { + let a: &AsciiStr = arr[..].into(); + assert_eq!(a[..].as_slice(), &a.as_slice()[..]); + assert_eq!(a[..4].as_slice(), &a.as_slice()[..4]); + assert_eq!(a[4..].as_slice(), &a.as_slice()[4..]); + assert_eq!(a[2..3].as_slice(), &a.as_slice()[2..3]); + assert_eq!(a[..=3].as_slice(), &a.as_slice()[..=3]); + assert_eq!(a[1..=1].as_slice(), &a.as_slice()[1..=1]); + } + let mut copy = arr; let a_mut: &mut AsciiStr = {&mut arr[..]}.into(); assert_eq!(a_mut[..].as_mut_slice(), &mut copy[..]); assert_eq!(a_mut[..2].as_mut_slice(), &mut copy[..2]); diff --git a/src/ascii_string.rs b/src/ascii_string.rs index 06b36aa..5c1aba2 100644 --- a/src/ascii_string.rs +++ b/src/ascii_string.rs @@ -451,7 +451,7 @@ impl<'a> From<&'a AsciiStr> for AsciiString { impl<'a> From<&'a [AsciiChar]> for AsciiString { #[inline] fn from(s: &'a [AsciiChar]) -> AsciiString { - s.iter().copied().collect() + s.iter().cloned().collect() } } diff --git a/src/lib.rs b/src/lib.rs index 5b6782a..08ea644 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -17,12 +17,12 @@ //! //! # Minimum supported Rust version //! -//! The minimum supported Rust version for 1.0.\* releases is 1.36.0. +//! The minimum Rust version for 1.0.\* releases is 1.33.0. //! Later 1.y.0 releases might require newer Rust versions, but the three most //! recent stable releases at the time of publishing will always be supported. -//! For example this means that if the current stable Rust version is 1.44 when +//! For example this means that if the current stable Rust version is 1.38 when //! ascii 1.1.0 is released, then ascii 1.1.* will not require a newer -//! Rust version than 1.42. +//! Rust version than 1.36. //! //! # History //! From 2e566e41c408e57cfd2b337b6b3a268e45936b6a Mon Sep 17 00:00:00 2001 From: tormol Date: Tue, 30 Jul 2019 09:11:20 +0200 Subject: [PATCH 22/24] Mark AsciiStr and AsciiString #[repr(transparent)] --- src/ascii_str.rs | 1 + src/ascii_string.rs | 1 + 2 files changed, 2 insertions(+) diff --git a/src/ascii_str.rs b/src/ascii_str.rs index c13ef82..b56febf 100644 --- a/src/ascii_str.rs +++ b/src/ascii_str.rs @@ -20,6 +20,7 @@ use ascii_string::AsciiString; /// It can be created by a checked conversion from a `str` or `[u8]`, or borrowed from an /// `AsciiString`. #[derive(PartialEq, Eq, PartialOrd, Ord, Hash)] +#[repr(transparent)] pub struct AsciiStr { slice: [AsciiChar], } diff --git a/src/ascii_string.rs b/src/ascii_string.rs index 5c1aba2..3d9fbf3 100644 --- a/src/ascii_string.rs +++ b/src/ascii_string.rs @@ -14,6 +14,7 @@ use ascii_str::{AsciiStr, AsAsciiStr, AsAsciiStrError}; /// A growable string stored as an ASCII encoded buffer. #[derive(Clone, Default, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[repr(transparent)] pub struct AsciiString { vec: Vec, } From 13e36284eb2690b35ac83bc447eb1925623f6033 Mon Sep 17 00:00:00 2001 From: tormol Date: Tue, 20 Aug 2019 22:36:49 +0200 Subject: [PATCH 23/24] Fix inconstistencies * Make methods const fn and #[inline] even if just a different name for another method. * Make all is_ascii_xxx() methods take self by reference. --- src/ascii_char.rs | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/src/ascii_char.rs b/src/ascii_char.rs index 9705fc4..389801f 100644 --- a/src/ascii_char.rs +++ b/src/ascii_char.rs @@ -404,7 +404,8 @@ impl AsciiChar { /// Check if the character is a letter (a-z, A-Z). /// /// This method is identical to [`is_alphabetic()`](#method.is_alphabetic) - pub fn is_ascii_alphabetic(&self) -> bool { + #[inline] + pub const fn is_ascii_alphabetic(&self) -> bool { self.is_alphabetic() } @@ -452,7 +453,8 @@ impl AsciiChar { /// Check if the character is a letter or number /// /// This method is identical to [`is_alphanumeric()`](#method.is_alphanumeric) - pub fn is_ascii_alphanumeric(&self) -> bool { + #[inline] + pub const fn is_ascii_alphanumeric(&self) -> bool { self.is_alphanumeric() } @@ -469,8 +471,8 @@ impl AsciiChar { /// assert!(!AsciiChar::FF.is_ascii_blank()); /// ``` #[inline] - pub const fn is_ascii_blank(self) -> bool { - (self as u8 == b' ') | (self as u8 == b'\t') + pub const fn is_ascii_blank(&self) -> bool { + (*self as u8 == b' ') | (*self as u8 == b'\t') } /// Check if the character one of ' ', '\t', '\n', '\r', @@ -485,8 +487,10 @@ impl AsciiChar { /// /// This method is NOT identical to `is_whitespace()`. #[inline] - pub const fn is_ascii_whitespace(self) -> bool { - self.is_ascii_blank() | (self as u8 == b'\n') | (self as u8 == b'\r') | (self as u8 == 0x0c) + pub const fn is_ascii_whitespace(&self) -> bool { + self.is_ascii_blank() + | (*self as u8 == b'\n') | (*self as u8 == b'\r') + | (*self as u8 == 0x0c/*form feed*/) } /// Check if the character is a control character @@ -551,7 +555,8 @@ impl AsciiChar { /// Checks if the character is alphabetic and lowercase (a-z). /// /// This method is identical to [`is_lowercase()`](#method.is_lowercase) - pub fn is_ascii_lowercase(&self) -> bool { + #[inline] + pub const fn is_ascii_lowercase(&self) -> bool { self.is_lowercase() } @@ -572,7 +577,8 @@ impl AsciiChar { /// Checks if the character is alphabetic and uppercase (A-Z). /// /// This method is identical to [`is_uppercase()`](#method.is_uppercase) - pub fn is_ascii_uppercase(&self) -> bool { + #[inline] + pub const fn is_ascii_uppercase(&self) -> bool { self.is_uppercase() } From b80037e081147b9cb36b2ea6c7ff5a0b46207846 Mon Sep 17 00:00:00 2001 From: tormol Date: Tue, 20 Aug 2019 23:30:27 +0200 Subject: [PATCH 24/24] Only install clippy on travis for targets where it's going to be run Not running clippy on nightly doesn't help much when the before_script fails if the component isn't available today. --- .travis.yml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 03d9d7f..da25de4 100644 --- a/.travis.yml +++ b/.travis.yml @@ -9,8 +9,10 @@ rust: before_script: - | - pip install 'travis-cargo<0.2' --user && - rustup component add clippy && + pip install 'travis-cargo<0.2' --user || exit 1; + if [ "$TRAVIS_RUST_VERSION" == "stable" ]; then + rustup component add clippy || exit 1 + fi export PATH=$HOME/.local/bin:$PATH script: