#![allow(missing_docs)]
#![allow(clippy::redundant_closure)]
use std::fs::File;
use std::io::Read;
use std::path::{Path, PathBuf};
use nom::branch::alt;
use nom::bytes::complete::{tag, take_till1, take_until};
use nom::character::complete::{char, line_ending, not_line_ending, space0};
use nom::combinator::{map, map_res, opt, rest, verify};
use nom::multi::{fold_many0, many0, many1_count};
use nom::sequence::delimited;
use nom::IResult;
use crate::layout::paragraphs::ligatures::ligature;
use crate::parser::ast::Ast;
use crate::parser::error::{EmptyError, ErrorType, Errors};
use crate::parser::warning::{EmptyWarning, WarningType, Warnings};
use crate::parser::{position, Error, Parsed, Span};
pub fn should_stop(c: char) -> bool {
c == '*' || c == '/' || c == '$' || c == '|'
}
pub fn error(span: Span, ty: ErrorType) -> Ast {
Ast::Error(EmptyError {
position: position(&span),
ty,
})
}
pub fn warning(span: Span, ty: WarningType) -> Ast {
Ast::Warning(EmptyWarning {
position: position(&span),
ty,
})
}
pub fn parse_bold(input: Span) -> IResult<Span, Ast> {
let (input, content) = in_between("*", input)?;
let (_, content) = parse_group(content)?;
Ok((input, Ast::Bold(content)))
}
fn in_between<'a>(pattern: &str, input: Span<'a>) -> IResult<Span<'a>, Span<'a>> {
delimited(tag(pattern), take_until(pattern), tag(pattern))(input)
}
pub fn parse_italic(input: Span) -> IResult<Span, Ast> {
let (input, content) = in_between("/", input)?;
let (_, content) = parse_group(content)?;
Ok((input, Ast::Italic(content)))
}
pub fn parse_inline_math(input: Span) -> IResult<Span, Ast> {
let (input, content) = in_between("$", input)?;
Ok((input, Ast::InlineMath(content.fragment.into())))
}
pub fn parse_delimited(input: Span) -> IResult<Span, Ast> {
alt((parse_bold, parse_italic, parse_inline_math))(input)
}
fn parse_delimited_unmatch_error(input: Span) -> IResult<Span, Ast> {
alt((
map(tag("*"), |x| error(x, ErrorType::UnmatchedStar)),
map(tag("/"), |x| error(x, ErrorType::UnmatchedSlash)),
map(tag("$"), |x| error(x, ErrorType::UnmatchedDollar)),
))(input)
}
pub fn parse_comment(input: Span) -> IResult<Span, Ast> {
let (input, _) = tag("||")(input)?;
let (input, _) = not_line_ending(input)?;
let (input, _) = opt(line_ending)(input)?;
Ok((input, Ast::Newline))
}
pub fn parse_any(input: Span) -> IResult<Span, Ast> {
alt((
map(tag("**"), |x| warning(x, WarningType::ConsecutiveStars)),
parse_comment,
parse_delimited,
parse_delimited_unmatch_error,
map(tag("|"), |_| Ast::Text(String::from("|"))),
map(take_till1(should_stop), |x: Span| {
Ast::Text(ligature(x.fragment))
}),
))(input)
}
pub fn parse_group(input: Span) -> IResult<Span, Vec<Ast>> {
many0(parse_any)(input)
}
pub fn parse_paragraph(input: Span) -> IResult<Span, Ast> {
map(parse_group, Ast::Paragraph)(input)
}
pub fn parse_single_line(input: Span) -> IResult<Span, Vec<Ast>> {
alt((parse_two_lines_error, parse_group))(input)
}
fn parse_two_lines_error(input: Span) -> IResult<Span, Vec<Ast>> {
let (input, _) = not_line_ending(input)?;
let (input, _) = line_ending(input)?;
let (input, span) = not_line_ending(input)?;
Ok((input, vec![error(span, ErrorType::MultipleLinesTitle)]))
}
pub fn parse_title_level(input: Span) -> IResult<Span, usize> {
map(many1_count(char('#')), |nb_hashes| nb_hashes - 1)(input)
}
pub fn parse_title(input: Span) -> IResult<Span, Ast> {
let (input, level) = parse_title_level(input)?;
let (input, _) = space0(input)?;
let (input, content) = parse_single_line(input)?;
Ok((
input,
Ast::Title {
level: level as u8,
children: content,
},
))
}
pub fn get_block(input: Span) -> IResult<Span, Span> {
let take_until_double_line_ending = |i| {
alt((
take_until("\r\n\r\n"),
take_until("\r\n\n"),
take_until("\n\n"),
))(i)
};
let at_least_1_char = verify(rest, |s: &Span| !s.fragment.is_empty());
let (input, mut span) = alt((take_until_double_line_ending, at_least_1_char))(input)?;
let (input, _) = many0(line_ending)(input)?;
span.fragment = span.fragment.trim_end();
Ok((input, span))
}
pub fn parse_block_content(input: Span) -> IResult<Span, Ast> {
alt((parse_title, parse_paragraph))(input)
}
pub fn parse_content(input: &str) -> IResult<Span, Vec<Ast>> {
let parse_block = map_res(get_block, parse_block_content);
fold_many0(parse_block, vec![], |mut content: Vec<_>, (_, block)| {
content.push(block);
content
})(Span::new(input))
}
pub fn parse<P: AsRef<Path>>(path: P) -> Result<Parsed, Error> {
let path = path.as_ref();
let mut file = File::open(&path)?;
let mut content = String::new();
file.read_to_string(&mut content)?;
let elements = match parse_content(&content) {
Ok((_, elements)) => elements,
Err(_) => unreachable!(),
};
let ast = Ast::File(PathBuf::from(path), elements);
let errors = ast.errors();
let warnings = ast.warnings();
if errors.is_empty() {
Ok(Parsed {
ast,
warnings: Warnings {
path: PathBuf::from(&path),
warnings,
content,
},
})
} else {
Err(Error::DexError(Errors {
path: PathBuf::from(&path),
content,
errors,
}))
}
}