diff --git a/macros/src/lib.rs b/macros/src/lib.rs index 7868a12..73ac14f 100644 --- a/macros/src/lib.rs +++ b/macros/src/lib.rs @@ -12,7 +12,46 @@ fn get_value_from_token_stream(input: TokenStream) -> String { if !val.starts_with('"') && !val.ends_with('"') { panic!("Expected a string literal; found {:?}", input); } - (&val[1..val.len() - 1]).to_string() + if val.contains('\\') { + // Note: If this proc macro has been called then rustc parsed the string as valid + // including escapes. Jut try to call this macro with a invalid string + let mut val: &str = &val[1..val.len() - 1]; + let mut buf = String::with_capacity(val.len()); + while !val.is_empty() { + // 01234 + // \xXX + // \n + match val.find('\\') { + Some(mut index) => { + buf.push_str(&val[0..index]); + match val.as_bytes()[index + 1] { + b'n' => buf.push('\n'), + b'r' => buf.push('\r'), + b't' => buf.push('\t'), + b'\\' => buf.push('\\'), + b'0' => buf.push('\0'), + b'\'' => buf.push('\''), + b'"' => buf.push('"'), + b'u' => panic!("Unicode Escapes not supported with this macro"), + b'x' => { + buf.push(char::from( + u8::from_str_radix(&val[index + 2..index + 4], 16).unwrap(), + )); + // Hex escapes are longer so bump the pointer + index += 2; + } + _ => unreachable!(), + } + val = &val[index + 2..]; + } + None => break, + } + } + buf.push_str(val); + buf + } else { + (&val[1..val.len() - 1]).to_string() + } } #[proc_macro] diff --git a/src/macros.rs b/src/macros.rs index 2ff218c..4e3a9ae 100644 --- a/src/macros.rs +++ b/src/macros.rs @@ -1,9 +1,10 @@ /// Macro to create a const TinyStr4, validated with zero runtime cost. /// -/// The argument must be a string literal: +/// The argument must be a string literal without Unicode Escapes: /// https://doc.rust-lang.org/reference/tokens.html#string-literals +/// https://doc.rust-lang.org/stable/reference/tokens.html#unicode-escapes /// -/// # Example +/// # Examples /// /// ``` /// use tinystr::{tinystr4, TinyStr4}; @@ -12,6 +13,13 @@ /// let s2: TinyStr4 = "abc".parse().unwrap(); /// assert_eq!(S1, s2); /// ``` +/// +/// ```compile_fail +/// # use tinystr::tinystr4; +/// // This will fail to compile +/// tinystr4!("\u{41}"); +/// ``` + #[macro_export] macro_rules! tinystr4 { ($s:literal) => { @@ -22,17 +30,20 @@ macro_rules! tinystr4 { #[test] fn test_tinystr4() { use crate::TinyStr4; - const X1: TinyStr4 = tinystr4!("foo"); - let x2: TinyStr4 = "foo".parse().unwrap(); + const X1: TinyStr4 = tinystr4!("foo\n"); + let x2: TinyStr4 = "foo\n".parse().unwrap(); assert_eq!(X1, x2); + const X2: TinyStr4 = tinystr4!("\r\t\\\""); + assert_eq!("\r\t\\\"", &*X2); } /// Macro to create a const TinyStr8, validated with zero runtime cost. /// -/// The argument must be a string literal: +/// The argument must be a string literal without Unicode escapes: /// https://doc.rust-lang.org/reference/tokens.html#string-literals +/// https://doc.rust-lang.org/stable/reference/tokens.html#unicode-escapes /// -/// # Example +/// # Examples /// /// ``` /// use tinystr::{tinystr8, TinyStr8}; @@ -41,6 +52,13 @@ fn test_tinystr4() { /// let s2: TinyStr8 = "abcdefg".parse().unwrap(); /// assert_eq!(S1, s2); /// ``` +/// +/// ```compile_fail +/// # use tinystr::tinystr8; +/// // This will fail to compile +/// tinystr8!("\u{41}"); +/// ``` + #[macro_export] macro_rules! tinystr8 { ($s:literal) => { @@ -54,14 +72,16 @@ fn test_tinystr8() { const X1: TinyStr8 = tinystr8!("barbaz"); let x2: TinyStr8 = "barbaz".parse().unwrap(); assert_eq!(X1, x2); + const X2: TinyStr8 = tinystr8!("\r\n\t\\\'\"\x41"); + assert_eq!("\r\n\t\\\'\"\x41", &*X2); } -/// Macro to create a const TinyStr8, validated with zero runtime cost. +/// Macro to create a const TinyStr16, validated with zero runtime cost. /// -/// The argument must be a string literal: +/// The argument must be a string literal without Unicode escapes: /// https://doc.rust-lang.org/reference/tokens.html#string-literals -/// -/// # Example +/// https://doc.rust-lang.org/stable/reference/tokens.html#unicode-escapes +/// # Examples /// /// ``` /// use tinystr::{tinystr16, TinyStr16}; @@ -70,6 +90,13 @@ fn test_tinystr8() { /// let s2: TinyStr16 = "longer-string".parse().unwrap(); /// assert_eq!(S1, s2); /// ``` +/// +/// ```compile_fail +/// # use tinystr::tinystr16; +/// // This will fail to compile +/// tinystr16!("\u{41}"); +/// ``` + #[macro_export] macro_rules! tinystr16 { ($s:literal) => { @@ -127,4 +154,6 @@ fn test_tinystr16() { const X1: TinyStr16 = tinystr16!("metamorphosis"); let x2: TinyStr16 = "metamorphosis".parse().unwrap(); assert_eq!(X1, x2); + const X2: TinyStr16 = tinystr16!("\r\n\t\\\'\"\x41"); + assert_eq!("\r\n\t\\\'\"\x41", &*X2); }