use crate::parser::events::Event;
use crate::parser::lexer::{TokKind, Token};
use crate::syntax::SyntaxKind;
fn is_inline_markup(kind: &TokKind) -> bool {
matches!(kind, TokKind::RoxygenMdDelim | TokKind::RoxygenMdBracket)
}
pub(super) fn resolve_emphasis(tokens: &[Token], events: &mut Vec<Event>) {
let has_markup = events
.iter()
.any(|e| matches!(e, Event::Tok(i) if is_inline_markup(&tokens[*i].kind)));
if !has_markup {
return;
}
let mut out = Vec::with_capacity(events.len());
let mut run: Vec<usize> = Vec::new();
for ev in std::mem::take(events) {
match ev {
Event::Tok(i) => run.push(i),
other => {
flush_run(tokens, &mut run, &mut out);
out.push(other);
}
}
}
flush_run(tokens, &mut run, &mut out);
*events = out;
}
fn flush_run(tokens: &[Token], run: &mut Vec<usize>, out: &mut Vec<Event>) {
if run.is_empty() {
return;
}
if !run.iter().any(|&i| is_inline_markup(&tokens[i].kind)) {
out.extend(run.drain(..).map(Event::Tok));
return;
}
resolve_run(tokens, run, None, None, out);
run.clear();
}
fn resolve_run(
tokens: &[Token],
run: &[usize],
before: Option<char>,
after: Option<char>,
out: &mut Vec<Event>,
) {
let mut arena = Arena::build(tokens, run, before, after);
arena.process_emphasis();
arena.emit(out);
}
struct Node {
data: NodeData,
prev: Option<usize>,
next: Option<usize>,
first_child: Option<usize>,
last_child: Option<usize>,
}
enum NodeData {
Token(usize),
Delim(String),
Emph {
strong: bool,
open: String,
close: String,
},
Link {
open: String,
close: String,
body: Vec<Event>,
},
}
struct Delim {
node: usize,
ch: u8,
length: usize,
orig: usize,
can_open: bool,
can_close: bool,
prev: Option<usize>,
next: Option<usize>,
active: bool,
}
struct Arena {
nodes: Vec<Node>,
delims: Vec<Delim>,
head: Option<usize>,
tail: Option<usize>,
last_delim: Option<usize>,
}
impl Arena {
fn build(tokens: &[Token], run: &[usize], before: Option<char>, after: Option<char>) -> Arena {
let mut arena = Arena {
nodes: Vec::new(),
delims: Vec::new(),
head: None,
tail: None,
last_delim: None,
};
let neighbor = |p: usize, leading: bool| -> Option<char> {
if leading {
run.get(p + 1)
.map_or(after, |&j| edge_char(&tokens[j], true))
} else {
match p.checked_sub(1) {
Some(q) => edge_char(&tokens[run[q]], false),
None => before,
}
}
};
let roles = match_brackets(tokens, run);
let mut p = 0;
while p < run.len() {
match &roles[p] {
BracketRole::MatchedOpener {
closer,
after,
close_text,
} => {
let open = tokens[run[p]].text.clone();
let close = close_text.clone();
let (closer, after_p) = (*closer, *after);
let mut body = Vec::new();
resolve_run(
tokens,
&run[p + 1..closer],
open.chars().next_back(),
Some(']'),
&mut body,
);
arena.push_node(NodeData::Link { open, close, body });
p = after_p;
continue;
}
BracketRole::LiteralBracket => {
arena.push_node(NodeData::Delim(tokens[run[p]].text.clone()));
p += 1;
continue;
}
BracketRole::Consumed => {
p += 1;
continue;
}
BracketRole::Other => {}
}
let tok = &tokens[run[p]];
if tok.kind == TokKind::RoxygenMdDelim {
let ch = tok.text.as_bytes()[0];
let len = tok.text.len(); let (can_open, can_close) = flanking(ch, neighbor(p, false), neighbor(p, true));
let node = arena.push_node(NodeData::Delim(tok.text.clone()));
arena.push_delim(node, ch, len, can_open, can_close);
} else {
arena.push_node(NodeData::Token(run[p]));
}
p += 1;
}
arena
}
fn push_node(&mut self, data: NodeData) -> usize {
let id = self.nodes.len();
self.nodes.push(Node {
data,
prev: self.tail,
next: None,
first_child: None,
last_child: None,
});
if let Some(t) = self.tail {
self.nodes[t].next = Some(id);
} else {
self.head = Some(id);
}
self.tail = Some(id);
id
}
fn push_delim(&mut self, node: usize, ch: u8, length: usize, can_open: bool, can_close: bool) {
let id = self.delims.len();
self.delims.push(Delim {
node,
ch,
length,
orig: length,
can_open,
can_close,
prev: self.last_delim,
next: None,
active: true,
});
if let Some(t) = self.last_delim {
self.delims[t].next = Some(id);
}
self.last_delim = Some(id);
}
fn process_emphasis(&mut self) {
let mut openers_bottom = [[None; 3]; 2];
let mut closer = self.last_delim;
while let Some(c) = closer {
match self.delims[c].prev {
Some(p) => closer = Some(p),
None => break,
}
}
while let Some(c) = closer {
if !self.delims[c].active {
closer = self.delims[c].next;
continue;
}
if !self.delims[c].can_close {
closer = self.delims[c].next;
continue;
}
let cc = self.delims[c].ch;
let ci = if cc == b'*' { 0 } else { 1 };
let bound = openers_bottom[ci][self.delims[c].length % 3];
let mut opener = self.delims[c].prev;
let mut opener_found = false;
while let Some(o) = opener {
if Some(o) == bound {
break;
}
if self.delims[o].active && self.delims[o].can_open && self.delims[o].ch == cc {
let odd_match = (self.delims[c].can_open || self.delims[o].can_close)
&& !self.delims[c].orig.is_multiple_of(3)
&& (self.delims[o].orig + self.delims[c].orig).is_multiple_of(3);
if !odd_match {
opener_found = true;
break;
}
}
opener = self.delims[o].prev;
}
if opener_found {
let o = opener.unwrap();
let use_delims = if self.delims[c].length >= 2 && self.delims[o].length >= 2 {
2
} else {
1
};
self.delims[o].length -= use_delims;
self.delims[c].length -= use_delims;
let opener_inl = self.delims[o].node;
let closer_inl = self.delims[c].node;
let open_str = self.shorten_from_end(opener_inl, use_delims);
let close_str = self.shorten_from_start(closer_inl, use_delims);
let emph = self.push_emph(use_delims == 2, open_str, close_str);
self.wrap_between(opener_inl, closer_inl, emph);
self.remove_delims_between(o, c);
if self.delims[o].length == 0 {
self.unlink(opener_inl);
self.remove_delim(o);
}
if self.delims[c].length == 0 {
let next = self.delims[c].next;
self.unlink(closer_inl);
self.remove_delim(c);
closer = next;
}
} else {
openers_bottom[ci][self.delims[c].length % 3] = self.delims[c].prev;
let next = self.delims[c].next;
if !self.delims[c].can_open {
self.remove_delim(c);
}
closer = next;
}
}
}
fn push_emph(&mut self, strong: bool, open: String, close: String) -> usize {
let id = self.nodes.len();
self.nodes.push(Node {
data: NodeData::Emph {
strong,
open,
close,
},
prev: None,
next: None,
first_child: None,
last_child: None,
});
id
}
fn shorten_from_end(&mut self, node: usize, use_delims: usize) -> String {
let NodeData::Delim(s) = &mut self.nodes[node].data else {
unreachable!("delimiter stack node is not a Delim")
};
let cut = s.len() - use_delims;
s.split_off(cut)
}
fn shorten_from_start(&mut self, node: usize, use_delims: usize) -> String {
let NodeData::Delim(s) = &mut self.nodes[node].data else {
unreachable!("delimiter stack node is not a Delim")
};
let removed = s[..use_delims].to_string();
s.drain(..use_delims);
removed
}
fn wrap_between(&mut self, opener_inl: usize, closer_inl: usize, emph: usize) {
let mut tmp = self.nodes[opener_inl].next;
while let Some(t) = tmp {
if t == closer_inl {
break;
}
let next = self.nodes[t].next;
self.unlink(t);
self.append_child(emph, t);
tmp = next;
}
self.insert_after(opener_inl, emph);
}
fn unlink(&mut self, node: usize) {
let prev = self.nodes[node].prev;
let next = self.nodes[node].next;
match prev {
Some(p) => self.nodes[p].next = next,
None => self.head = next,
}
match next {
Some(n) => self.nodes[n].prev = prev,
None => self.tail = prev,
}
self.nodes[node].prev = None;
self.nodes[node].next = None;
}
fn append_child(&mut self, parent: usize, child: usize) {
self.nodes[child].prev = self.nodes[parent].last_child;
self.nodes[child].next = None;
match self.nodes[parent].last_child {
Some(l) => self.nodes[l].next = Some(child),
None => self.nodes[parent].first_child = Some(child),
}
self.nodes[parent].last_child = Some(child);
}
fn insert_after(&mut self, anchor: usize, node: usize) {
let next = self.nodes[anchor].next;
self.nodes[node].prev = Some(anchor);
self.nodes[node].next = next;
self.nodes[anchor].next = Some(node);
match next {
Some(n) => self.nodes[n].prev = Some(node),
None => self.tail = Some(node),
}
}
fn remove_delim(&mut self, d: usize) {
if !self.delims[d].active {
return;
}
self.delims[d].active = false;
let prev = self.delims[d].prev;
let next = self.delims[d].next;
if let Some(p) = prev {
self.delims[p].next = next;
}
match next {
Some(n) => self.delims[n].prev = prev,
None => self.last_delim = prev,
}
}
fn remove_delims_between(&mut self, opener: usize, closer: usize) {
let mut d = self.delims[opener].next;
while let Some(cur) = d {
if cur == closer {
break;
}
let next = self.delims[cur].next;
self.remove_delim(cur);
d = next;
}
}
fn emit(&self, out: &mut Vec<Event>) {
let mut cur = self.head;
while let Some(n) = cur {
self.emit_node(n, out);
cur = self.nodes[n].next;
}
}
fn emit_node(&self, n: usize, out: &mut Vec<Event>) {
match &self.nodes[n].data {
NodeData::Token(idx) => out.push(Event::Tok(*idx)),
NodeData::Delim(s) => {
if !s.is_empty() {
out.push(Event::Leaf(SyntaxKind::ROXYGEN_MD_DELIM, s.clone()));
}
}
NodeData::Emph {
strong,
open,
close,
} => {
let kind = if *strong {
SyntaxKind::ROXYGEN_MD_STRONG
} else {
SyntaxKind::ROXYGEN_MD_EMPH
};
out.push(Event::Start(kind));
out.push(Event::Leaf(SyntaxKind::ROXYGEN_MD_DELIM, open.clone()));
let mut child = self.nodes[n].first_child;
while let Some(c) = child {
self.emit_node(c, out);
child = self.nodes[c].next;
}
out.push(Event::Leaf(SyntaxKind::ROXYGEN_MD_DELIM, close.clone()));
out.push(Event::Finish);
}
NodeData::Link { open, close, body } => {
out.push(Event::Start(SyntaxKind::ROXYGEN_MD_LINK));
out.push(Event::Leaf(SyntaxKind::ROXYGEN_MD_DELIM, open.clone()));
out.extend(body.iter().cloned());
out.push(Event::Leaf(SyntaxKind::ROXYGEN_MD_DELIM, close.clone()));
out.push(Event::Finish);
}
}
}
}
fn is_bracket_open(text: &str) -> bool {
text.starts_with('[') || text.starts_with('!')
}
enum BracketRole {
MatchedOpener {
closer: usize,
after: usize,
close_text: String,
},
LiteralBracket,
Consumed,
Other,
}
fn match_brackets(tokens: &[Token], run: &[usize]) -> Vec<BracketRole> {
let mut roles = Vec::with_capacity(run.len());
roles.resize_with(run.len(), || BracketRole::Other);
let mut stack: Vec<(usize, bool)> = Vec::new();
let mut q = 0;
while q < run.len() {
let tok = &tokens[run[q]];
if tok.kind != TokKind::RoxygenMdBracket {
q += 1;
continue;
}
if is_bracket_open(&tok.text) {
stack.push((q, true));
roles[q] = BracketRole::LiteralBracket; q += 1;
continue;
}
let Some((o_pos, active)) = stack.pop() else {
roles[q] = BracketRole::LiteralBracket;
q += 1;
continue;
};
if !active {
roles[q] = BracketRole::LiteralBracket;
q += 1;
continue;
}
match classify_closer(tokens, run, o_pos, q) {
Some((close_text, after)) => {
roles[o_pos] = BracketRole::MatchedOpener {
closer: q,
after,
close_text,
};
for role in &mut roles[q..after] {
*role = BracketRole::Consumed;
}
for e in stack.iter_mut() {
e.1 = false;
}
q = after;
}
None => {
roles[q] = BracketRole::LiteralBracket;
q += 1;
}
}
}
roles
}
fn classify_closer(
tokens: &[Token],
run: &[usize],
o_pos: usize,
closer_q: usize,
) -> Option<(String, usize)> {
let close_tok = &tokens[run[closer_q]];
if close_tok.text != "]" {
return Some((close_tok.text.clone(), closer_q + 1));
}
if let Some((label, after)) = neutral_ref_label(tokens, run, closer_q + 1) {
return Some((format!("][{label}]"), after));
}
if let Some(&j) = run.get(closer_q + 1)
&& tokens[j].kind == TokKind::RoxygenMdLink
{
return Some((format!("]{}", tokens[j].text), closer_q + 2));
}
interior_bracket_free(tokens, run, o_pos, closer_q).then(|| ("]".to_string(), closer_q + 1))
}
fn neutral_ref_label(
tokens: &[Token],
run: &[usize],
label_open: usize,
) -> Option<(String, usize)> {
let open = run.get(label_open).map(|&j| &tokens[j])?;
if open.kind != TokKind::RoxygenMdBracket || !open.text.starts_with('[') {
return None;
}
let mut label = String::new();
let mut k = label_open + 1;
while let Some(&j) = run.get(k) {
let tok = &tokens[j];
if tok.kind == TokKind::RoxygenMdBracket {
return (tok.text == "]").then_some((label, k + 1));
}
label.push_str(&tok.text);
k += 1;
}
None
}
fn interior_bracket_free(tokens: &[Token], run: &[usize], o_pos: usize, closer_q: usize) -> bool {
run[o_pos + 1..closer_q]
.iter()
.all(|&j| !tokens[j].text.bytes().any(|b| matches!(b, b'[' | b']')))
}
fn flanking(ch: u8, before: Option<char>, after: Option<char>) -> (bool, bool) {
let before_ws = is_ws(before);
let after_ws = is_ws(after);
let before_punct = is_punct(before);
let after_punct = is_punct(after);
let left_flanking = !after_ws && (!after_punct || before_ws || before_punct);
let right_flanking = !before_ws && (!before_punct || after_ws || after_punct);
match ch {
b'_' => (
left_flanking && (!right_flanking || before_punct),
right_flanking && (!left_flanking || after_punct),
),
_ => (left_flanking, right_flanking),
}
}
fn edge_char(tok: &Token, leading: bool) -> Option<char> {
if tok.kind == TokKind::RoxygenMarker {
return Some(' ');
}
if tok.kind == TokKind::RoxygenRdMacro {
return Some(if leading { 'x' } else { '-' });
}
if leading {
tok.text.chars().next()
} else {
tok.text.chars().next_back()
}
}
fn is_ws(c: Option<char>) -> bool {
c.is_none_or(char::is_whitespace)
}
fn is_punct(c: Option<char>) -> bool {
c.is_some_and(|c| c.is_ascii_punctuation())
}