pub fn escape_ascii(input: &str) -> Result<String, std::string::FromUtf8Error> {
if input.len() < 1 {
return Ok(String::new());
}
let mut v = Vec::from(input);
let mut i = 0;
while i < v.len() - 1 {
if v[i] == '\\' as u8 {
if is_simple_escape(v[i + 1] as char) {
v.remove(i);
v[i] = char_to_escape_sequence(v[i] as char) as u8;
} else if is_complex_escape(v[i + 1] as char) {
if v[i + 1] == 'x' as u8 {
v.remove(i);
v.remove(i);
let sixteens = ascii_to_hex(v.remove(i));
let ones = ascii_to_hex(*v.get(i).unwrap());
v[i] = (sixteens << 4) | ones;
}
}
}
i += 1;
}
String::from_utf8(v)
}
pub fn escape_bytes(input: &str) -> Result<String, std::string::FromUtf8Error> {
escape_ascii(input)
}
pub fn escape_unicode(_input: &str) -> Result<String, std::string::FromUtf8Error> {
unimplemented!("`escape_unicode` is not yet implemented");
}
pub fn escape_quotes(_input: &str) -> Result<String, std::string::FromUtf8Error> {
unimplemented!("`escape_quotes` is not yet implemented");
}
fn char_to_escape_sequence(chr: char) -> char {
match chr {
'n' => '\n',
't' => '\t',
'r' => '\r',
'\\' => '\\',
'0' => '\0',
_ => chr,
}
}
fn is_simple_escape(chr: char) -> bool {
match chr {
'n' | 't' | 'r' | '\\' | '0' => true,
_ => false,
}
}
fn is_complex_escape(chr: char) -> bool {
match chr {
'x' | 'u' => true,
_ => false,
}
}
fn ascii_to_hex(x: u8) -> u8 {
match x as char {
'0' => 0,
'1' => 1,
'2' => 2,
'3' => 3,
'4' => 4,
'5' => 5,
'6' => 6,
'7' => 7,
'8' => 8,
'9' => 9,
'a' | 'A' => 10,
'b' | 'B' => 11,
'c' | 'C' => 12,
'd' | 'D' => 13,
'e' | 'E' => 14,
'f' | 'F' => 15,
_ => panic!("expected hex value"),
}
}
#[cfg(test)]
mod tests {
use super::*;
mod test_process_escapes {
use super::*;
#[test]
fn test_newline() {
assert_eq!(
String::from("hello\nworld"),
escape_ascii(r#"hello\nworld"#).unwrap()
);
}
#[test]
fn test_carriage_return() {
assert_eq!(
String::from("hello\rworld"),
escape_ascii(r#"hello\rworld"#).unwrap()
);
}
#[test]
fn test_tab() {
assert_eq!(
String::from("hello\tworld"),
escape_ascii(r#"hello\tworld"#).unwrap()
);
}
#[test]
fn test_backslash() {
assert_eq!(
String::from("hello\\world"),
escape_ascii(r#"hello\\world"#).unwrap()
);
}
#[test]
fn test_null() {
assert_eq!(
String::from("hello\0world"),
escape_ascii(r#"hello\0world"#).unwrap()
);
}
#[test]
fn test_ascii_byte() {
assert_eq!(
String::from("hello\x20world"),
escape_ascii(r#"hello\x20world"#).unwrap()
);
}
#[test]
fn test_newline_bytes() {
assert_eq!(
String::from("hello\nworld"),
escape_bytes(r#"hello\nworld"#).unwrap()
);
}
#[test]
fn test_carriage_return_bytes() {
assert_eq!(
String::from("hello\rworld"),
escape_bytes(r#"hello\rworld"#).unwrap()
);
}
#[test]
fn test_tab_bytes() {
assert_eq!(
String::from("hello\tworld"),
escape_bytes(r#"hello\tworld"#).unwrap()
);
}
#[test]
fn test_backslash_bytes() {
assert_eq!(
String::from("hello\\world"),
escape_bytes(r#"hello\\world"#).unwrap()
);
}
#[test]
fn test_null_bytes() {
assert_eq!(
String::from("hello\0world"),
escape_bytes(r#"hello\0world"#).unwrap()
);
}
#[test]
fn test_non_ascii_byte() {
assert_eq!(
String::from("hello\x7fworld"),
escape_bytes(r#"hello\x7fworld"#).unwrap()
);
}
#[test]
fn test_unicode_u7fff() {
assert_eq!(
String::from("Hello\u{7fff}world"),
escape_unicode(r#"Hello\u{7fff}world"#).unwrap()
);
}
#[test]
fn test_unicode_crab_emoji() {
assert_eq!(
String::from("Hello🦀world"),
escape_unicode(r#"Hello\u{1f980}world"#).unwrap()
);
}
#[test]
fn test_escape_at_end() {
assert_eq!(
String::from("Hello world\n"),
escape_ascii(r#"Hello world\n"#).unwrap()
);
}
#[test]
fn test_complex_escape_at_end() {
assert_eq!(
String::from("Hello world\x20"),
escape_ascii(r#"Hello world\x20"#).unwrap()
);
}
#[test]
fn test_trailing_backslash() {
assert_eq!(
String::from("Hello world\\"),
escape_ascii(r#"Hello world\"#).unwrap()
);
}
}
mod test_char_to_escape_sequence {
use super::*;
#[test]
fn test_escape_n() {
assert_eq!('\n', char_to_escape_sequence('n'));
}
#[test]
fn test_escape_t() {
assert_eq!('\t', char_to_escape_sequence('t'));
}
#[test]
fn test_escape_r() {
assert_eq!('\r', char_to_escape_sequence('r'));
}
#[test]
fn test_escape_backslash() {
assert_eq!('\\', char_to_escape_sequence('\\'));
}
#[test]
fn test_escape_0() {
assert_eq!('\0', char_to_escape_sequence('0'));
}
#[test]
fn test_esacpe_x() {
assert_eq!('\x7f', char_to_escape_sequence(0x7f as char));
}
}
mod is_simple_escape_tests {
use super::*;
#[test]
fn test_escape_n() {
assert!(is_simple_escape('n'));
}
#[test]
fn test_escape_t() {
assert!(is_simple_escape('t'));
}
#[test]
fn test_escape_r() {
assert!(is_simple_escape('r'));
}
#[test]
fn test_escape_backslash() {
assert!(is_simple_escape('\\'));
}
#[test]
fn test_escape_0() {
assert!(is_simple_escape('0'));
}
}
}