use chumsky::{
IterParser, Parser,
combinator::{IgnoreThen, Not, Rewind},
container::Seq,
error::{RichReason, Simple},
extra::{self, Err, Full},
input::{Input, Stream},
primitive::{Any, Choice, any, choice, empty, end, just, none_of, one_of, todo},
recursive::{Recursive, recursive},
regex::regex,
span::SimpleSpan,
text::{ascii::ident, inline_whitespace, newline, unicode, whitespace},
};
use crate::{
ast::{
Content, FmlCodeBlockValue, FmlFunction, FmlValue, FunctionToken, Section,
Token::{self, CodeInline},
TokenVec,
},
lls,
parser::FML_TOKENS,
};
pub fn esc<'a>() -> impl Parser<'a, &'a str, char, extra::Err<Simple<'a, char>>> + Copy {
just('\\').ignore_then(one_of(FML_TOKENS))
}
pub fn esc_np<'a>() -> impl Parser<'a, &'a str, char, extra::Err<Simple<'a, char>>> + Copy {
just('\\').ignore_then(one_of(FML_TOKENS).rewind())
}
pub fn esc_nl<'a>() -> impl Parser<'a, &'a str, (), extra::Err<Simple<'a, char>>> + Copy {
just('\\').ignore_then(newline())
}
pub fn esc_nl_np<'a>() -> impl Parser<'a, &'a str, (), extra::Err<Simple<'a, char>>> + Copy {
just('\\').ignore_then(newline().rewind())
}
pub fn sol<'a>() -> impl Parser<'a, &'a str, (), extra::Err<Simple<'a, char>>> + Clone {
choice((
regex(r"^").ignored(),
empty().delimited_by(newline(), empty()),
))
}
pub fn nl<'a>() -> impl Parser<'a, &'a str, (), extra::Err<Simple<'a, char>>> + Clone {
choice((newline(), esc_nl()))
}
pub fn parser<'a>() -> impl Parser<'a, &'a str, Content, extra::Err<Simple<'a, char>>> {
let section_sep = choice((
inline_whitespace()
.ignore_then(newline())
.repeated()
.at_least(2),
whitespace().ignore_then(end()),
));
let section = any()
.and_is(section_sep.not())
.repeated()
.at_least(1)
.to_slice();
let section = fml_values()
.try_map(|x, _| {
TokenVec(x)
.try_into()
.map_err(|()| Simple::new(None, SimpleSpan::default()))
})
.nested_in(section)
.map(Section);
section.padded().repeated().collect().map(Content)
}
pub fn fml_values<'a>() -> impl Parser<'a, &'a str, Vec<Token>, extra::Err<Simple<'a, char>>> + Clone
{
fml_value().repeated().at_least(1).collect()
}
pub fn fml_value<'a>() -> impl Parser<'a, &'a str, Token, extra::Err<Simple<'a, char>>> + Clone {
recursive(|fml| {
let fml = || fml.clone();
let mut delimiteds = Recursive::declare();
let mut nestables = Recursive::declare();
let mut headings = Recursive::declare();
delimiteds.define({
let inner = fml().repeated().at_least(1).collect::<Vec<Token>>();
let inner = || inner.clone();
let spoiler = inner().nested_in(delimited_slice!(just("|")));
let bold = inner().nested_in(delimited_slice!(just("*")));
let underlined = inner().nested_in(delimited_slice!(just("__")));
let italic = inner().nested_in(delimited_slice!(just("_")));
let strikethrough = inner().nested_in(delimited_slice!(just("~")));
let typst = delimited_string!(just("$"));
choice((
spoiler.map(Token::CwSpoiler),
bold.map(Token::Bold),
underlined.map(Token::Underline),
italic.map(Token::Italic),
strikethrough.map(Token::Strikethrough),
typst.map(Token::Typst),
))
});
let line_longs = choice((nestables.clone(), headings.clone()));
nestables.define({
let quo_start = just('>').ignore_then(inline_whitespace().at_least(1));
let quote = sol().ignore_then(quo_start).ignore_then(
choice((
just('\\').ignore_then(any().and_is(newline())),
any().and_is(newline()).then_ignore(quo_start),
any().and_is(newline().not()),
))
.repeated()
.at_least(1)
.collect::<String>()
.map(Token::Quote),
);
let list_inner = fml()
.repeated()
.at_least(1)
.collect::<Vec<Token>>()
.map(Token::ListItem);
choice((quote, list_inner.nested_in(lls!(just('-')))))
.then_ignore(line_longs.clone().not().then(newline()).ignored().or_not())
});
let code_styles = choice((code_block(), code_inline()));
let function_body = fml()
.repeated()
.at_least(1)
.collect::<Vec<Token>>()
.nested_in(function_body_pat());
let function = just('#')
.ignore_then(ident())
.then(function_params())
.then(function_body.clone())
.map(
|((name, params), body): ((&str, Option<String>), Vec<Token>)| {
Token::Function(FunctionToken {
name: name.to_string(),
params,
body,
})
},
);
let sup_shorthand = just("^")
.ignore_then(function_body.clone())
.map(Token::Superscript);
let sub_shorthand = just("_").ignore_then(function_body).map(Token::Subscript);
let functions = choice((function, sup_shorthand, sub_shorthand));
let hyperlink_fml = fml()
.repeated()
.at_least(1)
.collect::<Vec<Token>>()
.nested_in(delimited_slice!(just("["), just("]")));
let hyperlink_uri = delimited_string!(just("("), just(")"));
let hyperlink = hyperlink_fml
.then(hyperlink_uri)
.map(|(fml, uri)| Token::Hyperlink { fml, uri });
headings.define({
let inner = choice((
code_styles.clone(),
functions.clone(),
hyperlink.clone(),
delimiteds.clone(),
nestables.clone(),
plain(choice((
code_styles.clone(),
functions.clone(),
hyperlink.clone(),
delimiteds.clone(),
nestables.clone(),
nl().to(Token::Linebreak),
))),
))
.repeated()
.at_least(1)
.collect::<Vec<Token>>();
let inner = || inner.clone();
let head_1 = inner().nested_in(lls!(just('#')));
let head_2 = inner().nested_in(lls!(just("##")));
let head_3 = inner().nested_in(lls!(just("###")));
let sup_line = inner().nested_in(lls!(just("#^")));
let sub_line = inner().nested_in(lls!(just("#!"), just("#_")));
choice((
head_1.map(Token::Heading1),
head_2.map(Token::Heading2),
head_3.map(Token::Heading3),
sup_line.map(Token::SupLine),
sub_line.map(Token::SubLine),
))
.then_ignore(line_longs.not().then(newline()).ignored().or_not())
});
choice((
code_styles.clone(),
functions.clone(),
hyperlink.clone(),
headings.clone(),
nestables.clone(),
delimiteds.clone(),
nl().to(Token::Linebreak),
plain(choice((
code_styles,
functions,
hyperlink,
delimiteds,
headings,
nestables,
nl().to(Token::Linebreak),
))),
))
})
}
pub fn plain<'a>(
break_on: impl Parser<'a, &'a str, Token, Err<Simple<'a, char>>> + Clone,
) -> impl Parser<'a, &'a str, Token, extra::Err<Simple<'a, char>>> + Clone {
let non_style = choice((esc(), any().and_is(break_on.not())));
choice((esc(), any()))
.then(non_style.repeated().collect::<String>())
.map(|(ch, mut s): (char, String)| {
s.insert(0, ch); Token::Text(s)
})
}
pub fn code_inline<'a>() -> impl Parser<'a, &'a str, Token, Err<Simple<'a, char>>> + Clone {
choice((
choice((just(r"\`").ignored(), just("`").ignored(), newline()))
.not()
.rewind(),
just("\\").ignore_then(just("`").rewind()).ignored(),
))
.ignore_then(any())
.repeated()
.at_least(1)
.collect::<String>()
.delimited_by(just('`'), just('`'))
.map(CodeInline)
}
pub fn code_block<'a>() -> impl Parser<'a, &'a str, Token, Err<Simple<'a, char>>> + Clone {
let linebreak = inline_whitespace().ignore_then(newline());
let body_str = choice((
any()
.and_is(
choice((
linebreak,
just(r"\```").ignored(),
whitespace().then(just("```")).ignored(),
))
.not(),
)
.to_slice(),
just('\\').ignore_then(just("```")),
))
.repeated()
.at_least(1)
.collect::<String>()
.map(FmlCodeBlockValue::Str);
let code_block_body = choice((linebreak.to(FmlCodeBlockValue::Linebreak), body_str))
.repeated()
.at_least(1)
.collect::<Vec<_>>();
ident()
.map(ToString::to_string)
.or_not()
.then_ignore(inline_whitespace().then(newline().or_not()))
.then(code_block_body)
.delimited_by(whitespace().then(just("```")), just("```").padded())
.map(|(lang, body)| Token::CodeBlock { lang, body })
}
pub fn function_params<'a>()
-> impl Parser<'a, &'a str, Option<String>, Err<Simple<'a, char>>> + Clone {
any()
.and_is(just('>').not())
.repeated()
.at_least(1)
.collect::<String>()
.delimited_by(just('<'), just('>'))
.or_not()
}
pub fn function_body_pat<'a>() -> impl Parser<'a, &'a str, &'a str, Err<Simple<'a, char>>> + Clone {
recursive(|fbp| {
choice((
any()
.and_is(
choice((
just('\\'),
just('{').padded(),
whitespace().ignore_then(just('}')),
))
.not(),
)
.repeated()
.at_least(1)
.to_slice(),
just('\\').ignore_then(one_of("{}")).to_slice(),
fbp,
))
.repeated()
.at_least(1)
.to_slice()
.delimited_by(just('{').padded(), whitespace().then(just('}')))
})
}
pub fn is_blank(input: &str) -> bool {
if input.len() == 0 {
true
} else {
for c in input.chars() {
if !unicode::is_whitespace(c) {
return false;
}
}
true
}
}