Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Parse escape sequeces in macros #47

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 40 additions & 1 deletion macros/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down
49 changes: 39 additions & 10 deletions src/macros.rs
Original file line number Diff line number Diff line change
@@ -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};
Expand All @@ -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) => {
Expand All @@ -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};
Expand All @@ -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) => {
Expand All @@ -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};
Expand All @@ -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) => {
Expand Down Expand Up @@ -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);
}