use nom::{branch::alt, bytes::complete::{tag, take_until}, character::complete::{multispace0, multispace1, not_line_ending}, multi::separated_list0, IResult, Parser};
use crate::parser::doc::StofParseError;
pub fn doc_comment(input: &str) -> IResult<&str, String, StofParseError> {
let (input, _) = whitespace(input)?;
let (input, inner) = separated_list0(multispace0, alt((parse_block_doc_comment, parse_single_line_doc_comment))).parse(input)?;
let mut comments = String::default();
for inner in inner { comments.push_str(inner); }
let (input, _) = whitespace(input)?;
let mut filtered = String::default();
for mut line in comments.split('\n') {
line = line.trim();
if line.starts_with('*') {
line = line.trim_start_matches('*').trim(); }
filtered.push_str(&format!("{line}\n")); }
filtered = filtered.trim().into();
Ok((input, filtered))
}
pub fn whitespace(input: &str) -> IResult<&str, &str, StofParseError> {
let mut rest = input;
while let Ok(res) = alt((
parse_block_comment,
parse_single_line_comment,
multispace1
)).parse(rest) {
rest = res.0;
}
Ok((rest, ""))
}
pub fn whitespace_fail(input: &str) -> IResult<&str, &str, StofParseError> {
let mut rest = input;
let mut success = false;
while let Ok(res) = alt((
parse_block_comment,
parse_single_line_comment,
multispace1
)).parse(rest) {
rest = res.0;
success = true;
}
if !success {
return Err(nom::Err::Error(StofParseError::from("no whitespace present")));
}
Ok((rest, ""))
}
pub(self) fn parse_single_line_comment(input: &str) -> IResult<&str, &str, StofParseError> {
let (input, _) = tag("//").parse(input)?;
if input.starts_with('/') {
return Err(nom::Err::Error(StofParseError::from("single line comment cannot be a doc comment")));
}
let (input, out) = not_line_ending(input)?;
Ok((input, out))
}
pub(self) fn parse_block_comment(input: &str) -> IResult<&str, &str, StofParseError> {
let (input, _) = tag("/*").parse(input)?;
if input.starts_with('*') || input.starts_with('!') {
return Err(nom::Err::Error(StofParseError::from("block comment cannot be a doc comment")));
}
let (input, _) = take_until("*/").parse(input)?;
let (input, out) = tag("*/").parse(input)?;
Ok((input, out))
}
pub(self) fn parse_single_line_doc_comment(input: &str) -> IResult<&str, &str, StofParseError> {
let (input, _) = tag("///").parse(input)?;
let (input, out) = not_line_ending(input)?;
Ok((input, out))
}
pub(self) fn parse_block_doc_comment(input: &str) -> IResult<&str, &str, StofParseError> {
let (input, _) = tag("/**").parse(input)?;
let (input, out) = take_until("*/").parse(input)?;
let (input, _) = tag("*/").parse(input)?;
Ok((input, out))
}
pub fn parse_inner_doc_comment(input: &str) -> IResult<&str, String, StofParseError> {
let (input, _) = tag("/*!").parse(input)?;
let (input, out) = take_until("*/").parse(input)?;
let (input, _) = tag("*/").parse(input)?;
let mut filtered = String::default();
for mut line in out.split('\n') {
line = line.trim();
if line.starts_with('*') {
line = line.trim_start_matches('*').trim(); }
filtered.push_str(&format!("{line}\n")); }
filtered = filtered.trim().into();
Ok((input, filtered))
}
#[cfg(test)]
mod tests {
use crate::parser::whitespace::{doc_comment, parse_block_comment, parse_single_line_comment, whitespace};
#[test]
fn single_line_comment() {
let res = parse_single_line_comment("// This is a comment\n").unwrap();
assert_eq!(res.0, "\n");
}
#[test]
fn block_comment() {
let res = parse_block_comment(r#"/*
* This is a block comment!
* With many lines.
*/hello"#).unwrap();
assert_eq!(res.0, "hello");
}
#[test]
fn whitespace_test() {
let res = whitespace(r#"
// This is a line comment.
/*
* This is a block comment.
*/
/* This is another block. */
hello"#).unwrap();
assert_eq!(res.0, "hello");
}
#[test]
fn doc_comments() {
let res = doc_comment(r#"
/**
* This is a doc comment!
*
* With many lines.
*/
/// This is a line doc comment.
hello"#).unwrap();
assert_eq!(res.0, "hello");
assert_eq!(res.1, r#"This is a doc comment!
With many lines.
This is a line doc comment."#);
}
}