use nom::IResult;
use nom::branch::alt;
use nom::bytes::complete::{is_not, take_while_m_n};
use nom::character::complete::{char, multispace1};
use nom::combinator::{map, map_opt, map_res, value, verify};
use nom::multi::fold_many0;
use nom::sequence::{delimited, preceded};
fn parse_unicode(input: &str) -> IResult<&str, char> {
let parse_hex = take_while_m_n(1, 6, |c: char| c.is_ascii_hexdigit());
let parse_delimited_hex = preceded(char('u'), delimited(char('{'), parse_hex, char('}')));
let parse_u32 = map_res(parse_delimited_hex, move |hex| u32::from_str_radix(hex, 16));
map_opt(parse_u32, std::char::from_u32)(input)
}
fn parse_escaped_char(input: &str) -> IResult<&str, char> {
preceded(
char('\\'),
alt((
parse_unicode,
value('\n', char('n')),
value('\r', char('r')),
value('\t', char('t')),
value('\u{08}', char('b')),
value('\u{0C}', char('f')),
value('\\', char('\\')),
value('/', char('/')),
value('"', char('"')),
value('\'', char('\'')),
)),
)(input)
}
fn parse_literal(sep: char, input: &str) -> IResult<&str, &str> {
let not_quote_slash = if sep == '\'' {
is_not("\'\\")
} else {
is_not("\"\\")
};
verify(not_quote_slash, |s: &str| !s.is_empty())(input)
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum StringFragment {
Literal(usize),
EscapedChar,
EscapedWS,
}
impl StringFragment {
const fn len(&self) -> usize {
match self {
Self::Literal(s) => *s,
Self::EscapedChar => 2,
Self::EscapedWS => 0,
}
}
}
fn parse_fragment(sep: char, input: &str) -> IResult<&str, StringFragment> {
alt((
map(
|x| parse_literal(sep, x),
|x| StringFragment::Literal(x.len()),
),
map(parse_escaped_char, |_| StringFragment::EscapedChar),
value(StringFragment::EscapedWS, preceded(char('\\'), multispace1)),
))(input)
}
pub fn parse_string_literal(
sep: char,
) -> impl for<'a> Fn(&'a str) -> IResult<&'a str, Vec<&'a str>> {
move |input| {
let build_string = fold_many0(
|x| parse_fragment(sep, x),
|| 2,
|len, frag| frag.len() + len,
);
let offset = delimited(char(sep), build_string, char(sep));
let (input, lit) = map(offset, |x| &input[..x])(input)?;
Ok((input, lit.split('\n').collect()))
}
}