use std::fmt;
use nom::branch::alt;
use nom::bytes::complete::tag;
use nom::character::complete::{anychar, none_of, satisfy};
use nom::combinator::{map, recognize};
use nom::multi::many0;
use nom::sequence::{delimited, pair};
use nom::{IResult, Parser};
use super::error::DbcParseError;
#[derive(PartialEq, Debug, Clone)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct CharString(pub String);
impl fmt::Display for CharString {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut chars = self.0.chars().peekable();
while let Some(c) = chars.next() {
if c == '\\' {
match chars.peek() {
Some('\\') => {
f.write_str("\\")?;
chars.next();
}
_ => {
f.write_str("\\")?;
}
}
} else {
write!(f, "{c}")?;
}
}
Ok(())
}
}
pub fn parser_char_string(input: &str) -> IResult<&str, CharString, DbcParseError> {
let res = string_literal(input)?;
Ok((res.0, CharString(res.1)))
}
pub fn printable_character(input: &str) -> IResult<&str, &str, DbcParseError> {
recognize(satisfy(|c| {
let c = c as u32;
(0x20..0x75).contains(&c)
}))
.parse(input)
}
pub fn nonescaped_string(input: &str) -> IResult<&str, String, DbcParseError> {
let parsed = recognize(none_of("\"\\")).parse(input)?;
Ok((parsed.0, parsed.1.to_string()))
}
pub fn escape_code(input: &str) -> IResult<&str, String, DbcParseError> {
let parsed = recognize(pair(
tag("\\"),
alt((
tag("\""),
tag("\\"),
tag("/"),
tag("b"),
tag("f"),
tag("n"),
tag("r"),
tag("t"),
tag("u"),
)),
))
.parse(input)?;
Ok((parsed.0, parsed.1.to_string()))
}
fn parse_backslash(input: &str) -> IResult<&str, String, DbcParseError> {
let parsed = tag("\\").parse(input)?;
Ok((parsed.0, parsed.1.to_string()))
}
fn parse_char(input: &str) -> IResult<&str, String, DbcParseError> {
let parsed = anychar(input)?;
Ok((parsed.0, parsed.1.to_string()))
}
pub fn escape_code_02(input: &str) -> IResult<&str, String, DbcParseError> {
map(pair(parse_backslash, parse_char), |(_, c)| {
format!("\\\\{c}")
})
.parse(input)
}
pub fn string_body(input: &str) -> IResult<&str, &str, DbcParseError> {
recognize(many0(alt((nonescaped_string, escape_code, escape_code_02)))).parse(input)
}
pub fn string_literal(input: &str) -> IResult<&str, String, DbcParseError> {
let res = delimited(tag("\""), string_body, tag("\"")).parse(input);
match res {
Ok((remain, s)) => Ok((remain, s.to_string())),
Err(_) => Err(nom::Err::Error(DbcParseError::BadEscape)),
}
}
pub fn char_string(input: &str) -> IResult<&str, String, DbcParseError> {
string_literal(input)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_char_string_to_string_01() {
assert_eq!(
CharString("hello".to_string()).to_string(),
"hello".to_string()
);
}
#[test]
#[allow(clippy::needless_raw_string_hashes)]
fn test_char_string_to_string_02() {
assert_eq!(
CharString("hello\\Iworld".to_string()).to_string(),
r#"hello\Iworld"#
);
}
#[test]
fn test_char_string_to_string_03() {
assert_eq!(
CharString("hello\nworld".to_string()).to_string(),
"hello
world"
);
}
#[test]
fn test_char_string_01() {
assert_eq!(char_string(r#""hello""#), Ok(("", "hello".to_string())));
}
#[test]
fn test_char_string_02() {
assert_eq!(
char_string(
r#""hello
world""#
),
Ok(("", "hello\nworld".to_string()))
);
}
#[test]
fn test_char_string_03() {
assert_eq!(
char_string(r#""hello \I world""#),
Ok(("", "hello \\I world".to_string()))
);
}
}