From 8f859b70e772b238cecb2fac9de4c3d6caa26213 Mon Sep 17 00:00:00 2001 From: BuildTools Date: Wed, 9 Jun 2021 01:00:44 -0500 Subject: [PATCH 1/2] Fail tinystrN! on inputs with escapse and Document+Test --- macros/src/lib.rs | 3 ++ src/macros.rs | 89 +++++++++++++++++++++++++++++++++++++++++++---- 2 files changed, 85 insertions(+), 7 deletions(-) diff --git a/macros/src/lib.rs b/macros/src/lib.rs index 7868a12..374ab95 100644 --- a/macros/src/lib.rs +++ b/macros/src/lib.rs @@ -12,6 +12,9 @@ fn get_value_from_token_stream(input: TokenStream) -> String { if !val.starts_with('"') && !val.ends_with('"') { panic!("Expected a string literal; found {:?}", input); } + if val.contains('\\') { + panic!("Backslash escapes are not supported"); + } (&val[1..val.len() - 1]).to_string() } diff --git a/src/macros.rs b/src/macros.rs index 2ff218c..e430306 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 string escapes: /// https://doc.rust-lang.org/reference/tokens.html#string-literals +/// https://doc.rust-lang.org/stable/reference/tokens.html#ascii-escapes /// -/// # Example +/// # Examples /// /// ``` /// use tinystr::{tinystr4, TinyStr4}; @@ -12,6 +13,30 @@ /// let s2: TinyStr4 = "abc".parse().unwrap(); /// assert_eq!(S1, s2); /// ``` +/// +/// ```compile_fail +/// # use tinystr::tinystr4; +/// // These will fail to compile +/// tinystr4!("\x41"); +/// tinystr4!("\t\n"); +/// tinystr4!("\'\""); +/// tinystr4!("\\\\"); +/// tinystr4!("\u{41}"); +/// ``` +/// ``` +/// use tinystr::TinyStr4; +/// // These will work as expected +/// let s1: TinyStr4 = "\x41".parse().unwrap(); +/// assert_eq!(s1, "A"); +/// let s2: TinyStr4 = "\t\n".parse().unwrap(); +/// assert_eq!(s2, "\t\n"); +/// let s3: TinyStr4 = "\'\"".parse().unwrap(); +/// assert_eq!(s3, "\'\""); +/// let s4: TinyStr4 = "\\\\".parse().unwrap(); +/// assert_eq!(s4, "\\\\"); +/// let s5: TinyStr4 = "\u{41}".parse().unwrap(); +/// assert_eq!(s5, "A"); +/// ``` #[macro_export] macro_rules! tinystr4 { ($s:literal) => { @@ -29,10 +54,11 @@ fn test_tinystr4() { /// 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 string escapes: /// https://doc.rust-lang.org/reference/tokens.html#string-literals +/// https://doc.rust-lang.org/stable/reference/tokens.html#ascii-escapes /// -/// # Example +/// # Examples /// /// ``` /// use tinystr::{tinystr8, TinyStr8}; @@ -41,6 +67,30 @@ fn test_tinystr4() { /// let s2: TinyStr8 = "abcdefg".parse().unwrap(); /// assert_eq!(S1, s2); /// ``` +/// +/// ```compile_fail +/// # use tinystr::tinystr8; +/// // These will fail to compile +/// tinystr8!("\x41"); +/// tinystr8!("\t\n"); +/// tinystr8!("\'\""); +/// tinystr8!("\\\\"); +/// tinystr8!("\u{41}"); +/// ``` +/// ``` +/// use tinystr::TinyStr8; +/// // These will work as expected +/// let s1: TinyStr8 = "\x41".parse().unwrap(); +/// assert_eq!(s1, "A"); +/// let s2: TinyStr8 = "\t\n".parse().unwrap(); +/// assert_eq!(s2, "\t\n"); +/// let s3: TinyStr8 = "\'\"".parse().unwrap(); +/// assert_eq!(s3, "\'\""); +/// let s4: TinyStr8 = "\\\\".parse().unwrap(); +/// assert_eq!(s4, "\\\\"); +/// let s5: TinyStr8 = "\u{41}".parse().unwrap(); +/// assert_eq!(s5, "A"); +/// ``` #[macro_export] macro_rules! tinystr8 { ($s:literal) => { @@ -56,12 +106,13 @@ fn test_tinystr8() { assert_eq!(X1, 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 string escapes: /// https://doc.rust-lang.org/reference/tokens.html#string-literals +/// https://doc.rust-lang.org/stable/reference/tokens.html#ascii-escapes /// -/// # Example +/// # Examples /// /// ``` /// use tinystr::{tinystr16, TinyStr16}; @@ -70,6 +121,30 @@ fn test_tinystr8() { /// let s2: TinyStr16 = "longer-string".parse().unwrap(); /// assert_eq!(S1, s2); /// ``` +/// +/// ```compile_fail +/// # use tinystr::tinystr16; +/// // These will fail to compile +/// tinystr16!("\x41"); +/// tinystr16!("\t\n"); +/// tinystr16!("\'\""); +/// tinystr16!("\\\\"); +/// tinystr16!("\u{41}"); +/// ``` +/// ``` +/// use tinystr::TinyStr16; +/// // These will work as expected +/// let s1: TinyStr16 = "\x41".parse().unwrap(); +/// assert_eq!(s1, "A"); +/// let s2: TinyStr16 = "\t\n".parse().unwrap(); +/// assert_eq!(s2, "\t\n"); +/// let s3: TinyStr16 = "\'\"".parse().unwrap(); +/// assert_eq!(s3, "\'\""); +/// let s4: TinyStr16 = "\\\\".parse().unwrap(); +/// assert_eq!(s4, "\\\\"); +/// let s5: TinyStr16 = "\u{41}".parse().unwrap(); +/// assert_eq!(s5, "A"); +/// ``` #[macro_export] macro_rules! tinystr16 { ($s:literal) => { From ee55e97814f8e407526dbd325f86fbe18a29b32e Mon Sep 17 00:00:00 2001 From: BuildTools Date: Thu, 10 Jun 2021 11:09:26 -0500 Subject: [PATCH 2/2] Parse escapes in Proc macro If a string with escape sequences is passed any macro this will parse the escapes. This will parse all escapes listed in the rust reference except for \u{XXXX} Unicode escapes because non ascii is not supported All in the name of not adding syn to Cargo.toml --- macros/src/lib.rs | 40 ++++++++++++++++++++-- src/macros.rs | 86 +++++++++++------------------------------------ 2 files changed, 58 insertions(+), 68 deletions(-) diff --git a/macros/src/lib.rs b/macros/src/lib.rs index 374ab95..73ac14f 100644 --- a/macros/src/lib.rs +++ b/macros/src/lib.rs @@ -13,9 +13,45 @@ fn get_value_from_token_stream(input: TokenStream) -> String { panic!("Expected a string literal; found {:?}", input); } if val.contains('\\') { - panic!("Backslash escapes are not supported"); + // 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() } - (&val[1..val.len() - 1]).to_string() } #[proc_macro] diff --git a/src/macros.rs b/src/macros.rs index e430306..4e3a9ae 100644 --- a/src/macros.rs +++ b/src/macros.rs @@ -1,8 +1,8 @@ /// Macro to create a const TinyStr4, validated with zero runtime cost. /// -/// The argument must be a string literal without string escapes: +/// 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#ascii-escapes +/// https://doc.rust-lang.org/stable/reference/tokens.html#unicode-escapes /// /// # Examples /// @@ -16,27 +16,10 @@ /// /// ```compile_fail /// # use tinystr::tinystr4; -/// // These will fail to compile -/// tinystr4!("\x41"); -/// tinystr4!("\t\n"); -/// tinystr4!("\'\""); -/// tinystr4!("\\\\"); +/// // This will fail to compile /// tinystr4!("\u{41}"); /// ``` -/// ``` -/// use tinystr::TinyStr4; -/// // These will work as expected -/// let s1: TinyStr4 = "\x41".parse().unwrap(); -/// assert_eq!(s1, "A"); -/// let s2: TinyStr4 = "\t\n".parse().unwrap(); -/// assert_eq!(s2, "\t\n"); -/// let s3: TinyStr4 = "\'\"".parse().unwrap(); -/// assert_eq!(s3, "\'\""); -/// let s4: TinyStr4 = "\\\\".parse().unwrap(); -/// assert_eq!(s4, "\\\\"); -/// let s5: TinyStr4 = "\u{41}".parse().unwrap(); -/// assert_eq!(s5, "A"); -/// ``` + #[macro_export] macro_rules! tinystr4 { ($s:literal) => { @@ -47,16 +30,18 @@ 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 without string escapes: +/// 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#ascii-escapes +/// https://doc.rust-lang.org/stable/reference/tokens.html#unicode-escapes /// /// # Examples /// @@ -70,27 +55,10 @@ fn test_tinystr4() { /// /// ```compile_fail /// # use tinystr::tinystr8; -/// // These will fail to compile -/// tinystr8!("\x41"); -/// tinystr8!("\t\n"); -/// tinystr8!("\'\""); -/// tinystr8!("\\\\"); +/// // This will fail to compile /// tinystr8!("\u{41}"); /// ``` -/// ``` -/// use tinystr::TinyStr8; -/// // These will work as expected -/// let s1: TinyStr8 = "\x41".parse().unwrap(); -/// assert_eq!(s1, "A"); -/// let s2: TinyStr8 = "\t\n".parse().unwrap(); -/// assert_eq!(s2, "\t\n"); -/// let s3: TinyStr8 = "\'\"".parse().unwrap(); -/// assert_eq!(s3, "\'\""); -/// let s4: TinyStr8 = "\\\\".parse().unwrap(); -/// assert_eq!(s4, "\\\\"); -/// let s5: TinyStr8 = "\u{41}".parse().unwrap(); -/// assert_eq!(s5, "A"); -/// ``` + #[macro_export] macro_rules! tinystr8 { ($s:literal) => { @@ -104,14 +72,15 @@ 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 TinyStr16, validated with zero runtime cost. /// -/// The argument must be a string literal without string escapes: +/// 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#ascii-escapes -/// +/// https://doc.rust-lang.org/stable/reference/tokens.html#unicode-escapes /// # Examples /// /// ``` @@ -124,27 +93,10 @@ fn test_tinystr8() { /// /// ```compile_fail /// # use tinystr::tinystr16; -/// // These will fail to compile -/// tinystr16!("\x41"); -/// tinystr16!("\t\n"); -/// tinystr16!("\'\""); -/// tinystr16!("\\\\"); +/// // This will fail to compile /// tinystr16!("\u{41}"); /// ``` -/// ``` -/// use tinystr::TinyStr16; -/// // These will work as expected -/// let s1: TinyStr16 = "\x41".parse().unwrap(); -/// assert_eq!(s1, "A"); -/// let s2: TinyStr16 = "\t\n".parse().unwrap(); -/// assert_eq!(s2, "\t\n"); -/// let s3: TinyStr16 = "\'\"".parse().unwrap(); -/// assert_eq!(s3, "\'\""); -/// let s4: TinyStr16 = "\\\\".parse().unwrap(); -/// assert_eq!(s4, "\\\\"); -/// let s5: TinyStr16 = "\u{41}".parse().unwrap(); -/// assert_eq!(s5, "A"); -/// ``` + #[macro_export] macro_rules! tinystr16 { ($s:literal) => { @@ -202,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); }