use crate::syntax::SyntaxKind;
use rowan::GreenNodeBuilder;
fn is_escapable(ch: char) -> bool {
ch.is_ascii_punctuation() || ch.is_whitespace()
}
pub fn try_parse_escape(text: &str) -> Option<(usize, char, EscapeType)> {
if !text.starts_with('\\') {
return None;
}
if text.len() < 2 {
return None;
}
let next_char = text[1..].chars().next()?;
if !is_escapable(next_char) {
return None;
}
let escape_type = match next_char {
' ' => EscapeType::NonbreakingSpace,
'\n' => EscapeType::HardLineBreak,
_ => EscapeType::Literal,
};
let total_len = 1 + next_char.len_utf8(); Some((total_len, next_char, escape_type))
}
#[derive(Debug, PartialEq, Eq)]
pub enum EscapeType {
Literal, NonbreakingSpace, HardLineBreak, }
pub fn emit_escape(builder: &mut GreenNodeBuilder, ch: char, escape_type: EscapeType) {
match escape_type {
EscapeType::NonbreakingSpace => {
builder.token(SyntaxKind::NONBREAKING_SPACE.into(), "\\ ");
}
EscapeType::HardLineBreak => {
builder.token(SyntaxKind::HARD_LINE_BREAK.into(), "\\\n");
}
EscapeType::Literal => {
let mut s = String::new();
s.push('\\');
s.push(ch);
builder.token(SyntaxKind::ESCAPED_CHAR.into(), &s);
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_escape_asterisk() {
let result = try_parse_escape(r"\*");
assert_eq!(result, Some((2, '*', EscapeType::Literal)));
}
#[test]
fn test_escape_backtick() {
let result = try_parse_escape(r"\`");
assert_eq!(result, Some((2, '`', EscapeType::Literal)));
}
#[test]
fn test_escape_space() {
let result = try_parse_escape(r"\ ");
assert_eq!(result, Some((2, ' ', EscapeType::NonbreakingSpace)));
}
#[test]
fn test_escape_newline() {
let result = try_parse_escape("\\\n");
assert_eq!(result, Some((2, '\n', EscapeType::HardLineBreak)));
}
#[test]
fn test_escape_bracket() {
let result = try_parse_escape(r"\[");
assert_eq!(result, Some((2, '[', EscapeType::Literal)));
}
#[test]
fn test_escape_dollar() {
let result = try_parse_escape(r"\$");
assert_eq!(result, Some((2, '$', EscapeType::Literal)));
}
#[test]
fn test_not_escape_letter() {
let result = try_parse_escape(r"\a");
assert_eq!(result, None);
}
#[test]
fn test_not_escape_at_end() {
let result = try_parse_escape(r"\");
assert_eq!(result, None);
}
#[test]
fn test_escape_all_punctuation() {
for ch in r#"`*_{}[]()>#+-.!"#.chars() {
let input = format!(r"\{}", ch);
let result = try_parse_escape(&input);
assert!(result.is_some(), "Should escape '{}'", ch);
assert_eq!(result.unwrap().1, ch);
}
}
#[test]
fn test_is_escapable() {
assert!(is_escapable('*'));
assert!(is_escapable('`'));
assert!(is_escapable('['));
assert!(is_escapable('!'));
assert!(is_escapable(' '));
assert!(is_escapable('\n'));
assert!(is_escapable('\t'));
assert!(!is_escapable('a'));
assert!(!is_escapable('Z'));
assert!(!is_escapable('5'));
}
}