use crate::parser::utils::{IResult, LocatedSpan};
use nom::{
branch::alt,
bytes::complete::{is_not, take_while_m_n},
character::complete::{char, multispace1},
combinator::{map, map_opt, map_res, value, verify},
multi::fold_many0,
sequence::{delimited, preceded},
};
fn unicode(input: LocatedSpan) -> IResult<char> {
let hex = take_while_m_n(1, 6, |c: char| c.is_ascii_hexdigit());
let delimited_hex = preceded(char('u'), delimited(char('{'), hex, char('}')));
let u32 = map_res(delimited_hex, |hex: LocatedSpan| {
u32::from_str_radix(*hex, 16)
});
map_opt(u32, |value| std::char::from_u32(value))(input)
}
fn escaped_char(input: LocatedSpan) -> IResult<StringFragment> {
preceded(
char('\\'),
alt((
map(unicode, StringFragment::EscapedChar),
value(StringFragment::EscapedChar('\n'), char('n')),
value(StringFragment::EscapedChar('\r'), char('r')),
value(StringFragment::EscapedChar('\t'), char('t')),
value(StringFragment::EscapedChar('\\'), char('\\')),
value(StringFragment::EscapedChar('"'), char('"')),
value(StringFragment::Literal("\u{1B}*"), char('*')),
value(StringFragment::Literal("\u{1B}?"), char('?')),
)),
)(input)
}
fn escaped_whitespace(input: LocatedSpan) -> IResult<LocatedSpan> {
preceded(char('\\'), multispace1)(input)
}
fn literal(input: LocatedSpan) -> IResult<LocatedSpan> {
verify(is_not("\"\\"), |s: &LocatedSpan| !s.is_empty())(input)
}
#[derive(Debug, Clone, Copy, PartialEq)]
enum StringFragment<'a> {
Literal(&'a str),
EscapedChar(char),
EscapedWS,
}
fn fragment(input: LocatedSpan) -> IResult<StringFragment> {
alt((
map(literal, |res: LocatedSpan| StringFragment::Literal(*res)),
escaped_char,
value(StringFragment::EscapedWS, escaped_whitespace),
))(input)
}
pub(crate) fn string(input: LocatedSpan) -> IResult<String> {
let build_string = fold_many0(fragment, String::new, |mut string, fragment| {
match fragment {
StringFragment::Literal(s) => string.push_str(s),
StringFragment::EscapedChar(c) => string.push(c),
StringFragment::EscapedWS => {}
}
string
});
delimited(char('"'), build_string, char('"'))(input)
}
#[cfg(test)]
mod tests {
use super::string;
use crate::parser::utils::{span, unwrap_span};
#[test]
fn test_string() {
assert_eq!(
string(span("\"test\"")).map(unwrap_span),
Ok(("", ("test".to_string())))
);
assert_eq!(
string(span("\"a b\"")).map(unwrap_span),
Ok(("", ("a b".to_string())))
);
assert_eq!(
string(span("\"\\\"\"")).map(unwrap_span),
Ok(("", ("\"".to_string())))
);
assert_eq!(
string(span("\"\\\\\"\"")).map(unwrap_span),
Ok(("\"", ("\\".to_string())))
);
assert_eq!(
string(span("\"\\n\"")).map(unwrap_span),
Ok(("", ("\n".to_string())))
);
}
}