use super::ast::Inline;
pub(super) fn parse_inlines(input: &str) -> Vec<Inline> {
let mut out = Vec::new();
let mut text = String::new();
let mut i = 0;
while i < input.len() {
let rest = &input[i..];
if let Some(c) = rest.strip_prefix('`')
&& let Some(close) = c.find('`')
{
flush(&mut text, &mut out);
out.push(Inline::Code(c[..close].to_string()));
i += 1 + close + 1;
continue;
}
if !rest.starts_with("$$")
&& let Some(c) = rest.strip_prefix('$')
&& let Some(close) = c.find('$')
&& close > 0
{
flush(&mut text, &mut out);
out.push(Inline::Math(c[..close].to_string()));
i += 1 + close + 1;
continue;
}
if let Some(span) = paired(rest, "**") {
flush(&mut text, &mut out);
out.push(Inline::Bold(parse_inlines(span)));
i += span.len() + 4;
continue;
}
if let Some(span) = paired_word_bounded(input, i, rest, "__") {
flush(&mut text, &mut out);
out.push(Inline::Bold(parse_inlines(span)));
i += span.len() + 4;
continue;
}
if let Some(span) = paired(rest, "~~") {
flush(&mut text, &mut out);
out.push(Inline::Strike(parse_inlines(span)));
i += span.len() + 4;
continue;
}
if let Some(span) = paired(rest, "*") {
flush(&mut text, &mut out);
out.push(Inline::Italic(parse_inlines(span)));
i += span.len() + 2;
continue;
}
if let Some(span) = paired_word_bounded(input, i, rest, "_") {
flush(&mut text, &mut out);
out.push(Inline::Italic(parse_inlines(span)));
i += span.len() + 2;
continue;
}
if rest.starts_with('[')
&& let Some(link) = parse_link(rest)
{
let (content, url, consumed) = link;
flush(&mut text, &mut out);
out.push(Inline::Link {
content: parse_inlines(content),
url: url.to_string(),
});
i += consumed;
continue;
}
let ch = rest.chars().next().unwrap();
text.push(ch);
i += ch.len_utf8();
}
flush(&mut text, &mut out);
out
}
fn flush(text: &mut String, out: &mut Vec<Inline>) {
if !text.is_empty() {
out.push(Inline::Text(std::mem::take(text)));
}
}
fn paired_word_bounded<'a>(input: &str, i: usize, rest: &'a str, delim: &str) -> Option<&'a str> {
if input[..i]
.chars()
.next_back()
.is_some_and(char::is_alphanumeric)
{
return None;
}
let span = paired(rest, delim)?;
let after_close = &rest[delim.len() + span.len() + delim.len()..];
if after_close
.chars()
.next()
.is_some_and(char::is_alphanumeric)
{
return None;
}
Some(span)
}
fn paired<'a>(rest: &'a str, delim: &str) -> Option<&'a str> {
let after = rest.strip_prefix(delim)?;
let close = after.find(delim)?;
if close == 0 {
return None;
}
Some(&after[..close])
}
fn parse_link(rest: &str) -> Option<(&str, &str, usize)> {
let mid = rest.find("](")?;
let end = rest[mid + 2..].find(')')?;
let text = &rest[1..mid];
let url = &rest[mid + 2..mid + 2 + end];
if url.is_empty() {
return None;
}
Some((text, url, mid + 2 + end + 1))
}