const ESCAPED_OPEN: &str = "\x00ESC_OPEN\x00";
const ESCAPED_CLOSE: &str = "\x00ESC_CLOSE\x00";
pub fn process_escapes(input: &str) -> String {
let mut result = String::with_capacity(input.len());
let mut chars = input.chars().peekable();
while let Some(ch) = chars.next() {
match ch {
'\\' => {
if let Some(&next) = chars.peek() {
if next == '{' {
let mut temp = chars.clone();
temp.next();
if temp.peek() == Some(&'{') {
chars.next(); chars.next(); result.push_str(ESCAPED_OPEN);
let mut depth = 1;
while let Some(c) = chars.next() {
if c == '{' && chars.peek() == Some(&'{') {
depth += 1;
result.push(c);
result.push(chars.next().unwrap());
} else if c == '}' && chars.peek() == Some(&'}') {
depth -= 1;
if depth == 0 {
chars.next(); result.push_str(ESCAPED_CLOSE);
break;
}
result.push(c);
result.push(chars.next().unwrap());
} else {
result.push(c);
}
}
continue;
} else if temp.peek() == Some(&'\\') {
temp.next(); if temp.peek() == Some(&'{') {
chars.next(); chars.next(); chars.next(); result.push_str(ESCAPED_OPEN);
let mut depth = 1;
while let Some(c) = chars.next() {
if c == '{' && chars.peek() == Some(&'{') {
depth += 1;
result.push(c);
result.push(chars.next().unwrap());
} else if c == '}' && chars.peek() == Some(&'}') {
depth -= 1;
if depth == 0 {
chars.next(); result.push_str(ESCAPED_CLOSE);
break;
}
result.push(c);
result.push(chars.next().unwrap());
} else {
result.push(c);
}
}
continue;
}
}
} else if next == '\\' {
let mut temp = chars.clone();
temp.next(); if temp.peek() == Some(&'{') {
temp.next(); if temp.peek() == Some(&'{') {
chars.next(); result.push('\\');
continue;
}
}
}
}
result.push(ch);
}
'{' => {
if let Some(&next) = chars.peek()
&& next == '\\'
{
let mut temp = chars.clone();
temp.next(); if temp.peek() == Some(&'{') {
chars.next(); chars.next(); result.push_str(ESCAPED_OPEN);
let mut depth = 1;
while let Some(c) = chars.next() {
if c == '{' && chars.peek() == Some(&'{') {
depth += 1;
result.push(c);
result.push(chars.next().unwrap());
} else if c == '}' && chars.peek() == Some(&'}') {
depth -= 1;
if depth == 0 {
chars.next(); result.push_str(ESCAPED_CLOSE);
break;
}
result.push(c);
result.push(chars.next().unwrap());
} else {
result.push(c);
}
}
continue;
}
}
result.push(ch);
}
_ => result.push(ch),
}
}
result
}
pub fn restore_escaped_braces(input: &str) -> String {
input
.replace(ESCAPED_OPEN, "{{")
.replace(ESCAPED_CLOSE, "}}")
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_backslash_escape() {
let escaped = process_escapes(r"\{{expr}}");
let restored = restore_escaped_braces(&escaped);
assert_eq!(restored, "{{expr}}");
}
#[test]
fn test_brace_escape() {
let escaped = process_escapes(r"{\{expr}}");
let restored = restore_escaped_braces(&escaped);
assert_eq!(restored, "{{expr}}");
}
#[test]
fn test_double_brace_escape() {
let escaped = process_escapes(r"\{\{3 + 8}}");
let restored = restore_escaped_braces(&escaped);
assert_eq!(restored, "{{3 + 8}}");
}
#[test]
fn test_escaped_backslash() {
let escaped = process_escapes(r"\\{{expr}}");
assert_eq!(escaped, r"\{{expr}}");
let restored = restore_escaped_braces(&escaped);
assert_eq!(restored, r"\{{expr}}");
}
#[test]
fn test_mixed_escapes() {
let input = r"{{8 - 10}} \{{ \{{50 + 50}} / \{{10 * 5}} }}";
let escaped = process_escapes(input);
assert!(escaped.contains("{{8 - 10}}"));
assert!(escaped.contains(ESCAPED_OPEN));
let restored = restore_escaped_braces(&escaped);
assert!(restored.contains("{{"));
}
#[test]
fn test_no_escape() {
let input = "{{10 + 5}}";
let escaped = process_escapes(input);
assert_eq!(escaped, input);
let restored = restore_escaped_braces(&escaped);
assert_eq!(restored, input);
}
#[test]
fn test_partial_escape() {
let input = r"\{single";
let escaped = process_escapes(input);
assert_eq!(escaped, input);
}
#[test]
fn test_placeholders_not_in_normal_text() {
let input = "normal {{expr}} text";
let escaped = process_escapes(input);
assert!(!escaped.contains(ESCAPED_OPEN));
assert!(!escaped.contains(ESCAPED_CLOSE));
}
}