use nom::branch::alt;
use nom::bytes::complete::{is_not, tag};
use nom::character::complete::{anychar, multispace1};
use nom::error::ParseError;
use nom::multi::{many0, many1, many_till};
use nom::sequence::preceded;
use nom::IResult;
pub trait NeedsWhitespaceStringExt {
fn needs_pre_ws(&self) -> bool;
fn needs_post_ws(&self) -> bool;
}
impl NeedsWhitespaceStringExt for str {
fn needs_pre_ws(&self) -> bool {
self.chars()
.next()
.map(|x| {
x.is_ascii_alphanumeric()
|| x == '-'
|| x == '_'
|| x == '%'
|| x == '+'
|| x == '"'
|| x == '\''
|| x == '('
})
.unwrap_or_default()
}
fn needs_post_ws(&self) -> bool {
self.chars()
.last()
.map(|x| {
x.is_ascii_alphanumeric()
|| x == '"'
|| x == '\''
|| x == '-'
|| x == '_'
|| x == '%'
|| x == '+'
|| x == ')'
})
.unwrap_or_default()
}
}
pub fn trim_whitespace(s: &str, f: &mut std::fmt::Formatter<'_>) {
let mut last_alpha = false;
s.split_whitespace().for_each(|w| {
if last_alpha && w.needs_pre_ws() {
write!(f, " ").unwrap();
}
last_alpha = w.needs_post_ws();
write!(f, "{}", w).unwrap();
});
}
fn parse_comment<'a, E>(input: &'a str) -> IResult<&'a str, (), E>
where
E: ParseError<&'a str>,
{
ignore(preceded(tag("//"), many0(is_not("\r\n"))))(input)
}
fn parse_multi_comment<'a, E>(input: &'a str) -> IResult<&'a str, (), E>
where
E: ParseError<&'a str>,
{
ignore(preceded(tag("/*"), many_till(anychar, tag("*/"))))(input)
}
fn ignore<'a, T, E, F>(mut f: F) -> impl FnMut(&'a str) -> IResult<&'a str, (), E>
where
F: FnMut(&'a str) -> IResult<&'a str, T, E>,
{
move |input| {
let (input, _) = f(input)?;
Ok((input, ()))
}
}
pub fn comment0<'a, E>(input: &'a str) -> IResult<&'a str, (), E>
where
E: ParseError<&'a str>,
{
let (input, _) = many0(alt((
ignore(multispace1),
parse_comment,
parse_multi_comment,
)))(input)?;
Ok((input, ()))
}
pub fn comment1<'a, E>(input: &'a str) -> IResult<&'a str, (), E>
where
E: ParseError<&'a str>,
{
ignore(many1(alt((
ignore(multispace1),
parse_comment,
parse_multi_comment,
))))(input)
}
pub fn sep0<'a, E>(input: &'a str) -> IResult<&'a str, (), E>
where
E: ParseError<&'a str>,
{
ignore(many0(alt((comment1, ignore(tag(";"))))))(input)
}
#[cfg(test)]
mod tests {
use std::assert_matches::assert_matches;
use super::*;
#[test]
fn test_multiline_comment() {
assert_matches!(
comment0::<()>(
"
/*
* test
*/"
),
Ok(("", ()))
)
}
#[test]
fn test_forward_slash() {
assert_matches!(comment0::<()>("// test"), Ok(("", ())))
}
#[test]
fn test_semicolons() {
assert_matches!(comment0::<()>("/* test; test */"), Ok(("", ())))
}
}