use crate::{
ast::*,
parser::{
Stream,
functions::{macros::*, styles::*},
},
};
use winnow::{
Result,
ascii::*,
combinator::{repeat, *},
error::{ContextError, ParserError, StrContext},
prelude::*,
token::*,
};
pub fn delimiteds<'i>(input: &mut Stream<'i>) -> Result<FmlValue> {
alt((
code_inline,
bold,
italic,
strikethrough,
underlined,
typst,
content_warning,
rich_text,
))
.parse_next(input)
}
macro_rules! delim {
($token:expr, $res:expr, $input:expr) => {{
let scope: String = delimited_esc!($token).parse_next($input)?;
inner_fml(&scope, $input.state.clone()).map($res)
}};
}
fn bold<'i>(input: &mut Stream<'i>) -> Result<FmlValue> {
delim!("*", FmlValue::Bold, input)
}
fn italic<'i>(input: &mut Stream<'i>) -> Result<FmlValue> {
delim!("%", FmlValue::Italic, input)
}
fn strikethrough<'i>(input: &mut Stream<'i>) -> Result<FmlValue> {
delim!("~", FmlValue::Strikethrough, input)
}
fn underlined<'i>(input: &mut Stream<'i>) -> Result<FmlValue> {
delim!("_", FmlValue::Underline, input)
}
fn typst<'i>(input: &mut Stream<'i>) -> Result<FmlValue> {
delimited_esc!("$").parse_next(input).map(FmlValue::Typst)
}
fn code_inline<'i>(input: &mut Stream<'i>) -> Result<FmlValue> {
delimited("`", enot!("`"), "`")
.verify(|x: &str| !x.trim().is_empty())
.parse_next(input)
.map(FmlValue::CodeInline)
}
fn parse_modifiers<'i>(
input: &mut Stream<'i>,
skip_paren: bool,
) -> (Option<String>, Option<String>, Option<String>) {
let mut paren: Option<String> = None;
let mut angled: Option<String> = None;
let mut curly: Option<String> = None;
for _ in 0..3 {
if paren.is_none() && skip_paren.not() {
if let Ok(val) = delimited_esc!("(", ")").map_err(|()| ()).parse_next(input) {
paren = Some(val);
}
}
if angled.is_none() {
if let Ok(val) = delimited_esc!("<", ">").map_err(|()| ()).parse_next(input) {
angled = Some(val);
}
}
if curly.is_none() {
if let Ok(val) = delimited_esc!("{", "}").map_err(|()| ()).parse_next(input) {
curly = Some(val);
}
}
}
(paren, angled, curly)
}
pub fn content_warning<'i>(input: &mut Stream<'i>) -> Result<FmlValue> {
let content_str: String = delimited_esc!("|").parse_next(input)?;
let vals = inner_fml(&content_str, input.state.clone())?;
let (reason, fg_color, bg_color) = parse_modifiers(input, false);
let reason = reason.unwrap_or("Spoilers".to_string());
let res = FmlValue::ContentWarning { reason, body: vals };
let res = wrap_in_colors(res, fg_color, bg_color);
Ok(res)
}
pub fn rich_text<'i>(input: &mut Stream<'i>) -> Result<FmlValue> {
fn rich_text_body<'i>(input: &mut Stream<'i>) -> Result<&'i str> {
let escapes = || ('\\', alt(('[', ']', '\\')));
let body_pt = slice_till!(1.., alt((escapes().take(), not!(escapes(), "[", "]"))));
let body = alt((body_pt, alt((rich_text.take(), "["))));
delimited("[", body, "]").parse_next(input)
}
let body = rich_text_body.parse_next(input)?;
let in_hyperlink = input.state.is_in_hyperlink;
let (url, fg_color, bg_color) = parse_modifiers(input, in_hyperlink);
if url.is_none() && fg_color.is_none() && bg_color.is_none() {
return fail.parse_next(input);
}
let vals = {
let mut state = input.state.clone();
state.is_in_hyperlink = url.is_some();
inner_fml(&body, state)?
};
Ok(if let Some(uri) = url {
let res = FmlValue::Hyperlink { fml: vals, uri };
wrap_in_colors(res, fg_color, bg_color)
} else {
if let Some(color) = fg_color {
let mut res = FmlValue::ColorFg(FmlColor { color, body: vals });
if let Some(color) = bg_color {
res = FmlValue::ColorBg(FmlColor {
color,
body: vec![res].into(),
});
}
res
} else {
if let Some(color) = bg_color {
FmlValue::ColorBg(FmlColor { color, body: vals })
} else {
unreachable!() }
}
})
}