1 //! Checked strings containing Unicode identifiers according to the 2 //! [Unicode Standard Annex #31](https://www.unicode.org/reports/tr31/). 3 //! 4 //! # Examples 5 //! 6 //! ```rust 7 //! use strck::{IntoCk, ident::unicode::UnicodeIdent}; 8 //! 9 //! assert!("foo".ck::<UnicodeIdent>().is_ok()); 10 //! assert!("struct".ck::<UnicodeIdent>().is_ok()); 11 //! assert!("Москва".ck::<UnicodeIdent>().is_ok()); 12 //! assert!("東京".ck::<UnicodeIdent>().is_ok()); 13 //! 14 //! assert!("_identifier".ck::<UnicodeIdent>().is_err()); 15 //! assert!("r#try".ck::<UnicodeIdent>().is_err()); 16 //! assert!("".ck::<UnicodeIdent>().is_err()); 17 //! ``` 18 //! 19 //! # Aliases 20 //! 21 //! This module exposes [`Ident`] and [`IdentBuf`], which alias `Ck<UnicodeIdent>` 22 //! and `Check<UnicodeIdent>` respectively. These aliases are preferred to keep 23 //! type signatures succinct. 24 //! 25 //! These are also exported under the root, and can be accessed as 26 //! `strck_ident::Ident` and `strck_ident::IdentBuf`. 27 use crate::{Check, Ck, Invariant}; 28 use core::fmt; 29 30 /// An [`Invariant`] for unicode identifiers according to 31 /// [Unicode Standard Annex #31](https://www.unicode.org/reports/tr31/). 32 /// 33 /// # Invariants 34 /// 35 /// * The string is nonempty. 36 /// * The first character is XID_Start. 37 /// * Any following characters are XID_Continue. 38 #[derive(Clone, Debug)] 39 pub struct UnicodeIdent; 40 41 /// Borrowed checked string containing a Unicode identifier. 42 /// 43 /// See [`UnicodeIdent`] for more details. 44 pub type Ident = Ck<UnicodeIdent>; 45 46 /// Owned checked string containing a Unicode identifier. 47 /// 48 /// See [`UnicodeIdent`] for more details. 49 pub type IdentBuf<B = String> = Check<UnicodeIdent, B>; 50 51 /// The error type returned from checking invariants of [`UnicodeIdent`]. 52 #[derive(Debug, Copy, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)] 53 pub enum Error { 54 /// Empty string. 55 Empty, 56 57 /// The first character isn't XID_Start. 58 Start(char), 59 60 /// A trailing character isn't XID_Continue. 61 Continue(char), 62 } 63 64 impl std::error::Error for Error {} 65 66 impl fmt::Display for Error { fmt(&self, f: &mut fmt::Formatter) -> fmt::Result67 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 68 match self { 69 Error::Empty => f.pad("empty"), 70 Error::Start(ch) => write!(f, "invalid start '{ch}'"), 71 Error::Continue(ch) => write!(f, "invalid continue '{ch}'"), 72 } 73 } 74 } 75 76 impl Invariant for UnicodeIdent { 77 type Error = Error; 78 check(slice: &str) -> Result<(), Self::Error>79 fn check(slice: &str) -> Result<(), Self::Error> { 80 let mut chars = slice.chars(); 81 let start = chars.next().ok_or(Error::Empty)?; 82 83 if !unicode_ident::is_xid_start(start) { 84 return Err(Error::Start(start)); 85 } 86 87 for ch in chars { 88 if !unicode_ident::is_xid_continue(ch) { 89 return Err(Error::Continue(ch)); 90 } 91 } 92 Ok(()) 93 } 94 } 95 96 #[cfg(test)] 97 mod tests { 98 use super::{Error, UnicodeIdent}; 99 use crate::IntoCk; 100 101 #[test] test_invalid()102 fn test_invalid() { 103 assert_eq!("".ck::<UnicodeIdent>().unwrap_err(), Error::Empty); 104 assert_eq!("12345".ck::<UnicodeIdent>().unwrap_err(), Error::Start('1')); 105 assert_eq!( 106 "_foo".ck::<UnicodeIdent>().unwrap_err(), 107 Error::Start('') 108 ); 109 assert_eq!( 110 "foo_".ck::<UnicodeIdent>().unwrap_err(), 111 Error::Continue('') 112 ); 113 assert_eq!( 114 "hello.there".ck::<UnicodeIdent>().unwrap_err(), 115 Error::Continue('.') 116 ); 117 assert_eq!( 118 "\\as2mkf".ck::<UnicodeIdent>().unwrap_err(), 119 Error::Start('\\') 120 ); 121 assert_eq!( 122 "the book".ck::<UnicodeIdent>().unwrap_err(), 123 Error::Continue(' ') 124 ); 125 assert_eq!(" book".ck::<UnicodeIdent>().unwrap_err(), Error::Start(' ')); 126 assert_eq!("\n".ck::<UnicodeIdent>().unwrap_err(), Error::Start('\n')); 127 assert_eq!( 128 "_underscore".ck::<UnicodeIdent>().unwrap_err(), 129 Error::Start('_') 130 ); 131 assert_eq!( 132 "r#try".ck::<UnicodeIdent>().unwrap_err(), 133 Error::Continue('#') 134 ); 135 } 136 137 #[test] test_valid()138 fn test_valid() { 139 assert!("a2345".ck::<UnicodeIdent>().is_ok()); 140 assert!("foo".ck::<UnicodeIdent>().is_ok()); 141 assert!("snake_case".ck::<UnicodeIdent>().is_ok()); 142 assert!("impl".ck::<UnicodeIdent>().is_ok()); 143 assert!("岡林".ck::<UnicodeIdent>().is_ok()); 144 } 145 } 146