use super::shared::GrammarSpan;
use crate::parser::ast::{Node, NodeKind};
use crate::parser::shared::{
opt_span, parse_diagrams_enabled, parse_math_enabled, track_positions_enabled,
ParseOptionsGuard,
};
use rayon::prelude::*;
pub(crate) enum PendingSpan<'a> {
Borrowed(GrammarSpan<'a>),
Owned(String),
}
pub(crate) enum Segment<'a> {
Literal(Node),
Pending(PendingSpan<'a>),
}
pub(crate) struct PendingLeaf<'a> {
pub(crate) node_index: usize,
pub(crate) nested_child_index: Option<usize>,
pub(crate) segments: Vec<Segment<'a>>,
}
pub(crate) fn apply_pending_leaves(nodes: &mut [Node], leaves: Vec<PendingLeaf<'_>>) {
if leaves.is_empty() {
return;
}
let mut targets: Vec<(usize, Option<usize>)> = Vec::with_capacity(leaves.len());
let mut segment_lists: Vec<Vec<Segment<'_>>> = Vec::with_capacity(leaves.len());
for leaf in leaves {
targets.push((leaf.node_index, leaf.nested_child_index));
segment_lists.push(leaf.segments);
}
let resolved = resolve_segmented_batch(segment_lists);
for ((node_index, nested_child_index), children) in targets.into_iter().zip(resolved) {
match nested_child_index {
None => nodes[node_index].children = children,
Some(child_index) => nodes[node_index].children[child_index].children = children,
}
}
}
const PARALLEL_THRESHOLD: usize = 4;
pub(crate) fn resolve_pending_batch(items: Vec<PendingSpan<'_>>) -> Vec<Vec<Node>> {
if items.len() < PARALLEL_THRESHOLD {
return items.iter().map(resolve_one).collect();
}
let track = track_positions_enabled();
let math = parse_math_enabled();
let diagrams = parse_diagrams_enabled();
items
.par_iter()
.map(|item| {
let _guard = ParseOptionsGuard::new(track, math, diagrams);
resolve_one(item)
})
.collect()
}
fn resolve_one(item: &PendingSpan<'_>) -> Vec<Node> {
match item {
PendingSpan::Borrowed(span) => {
match crate::parser::inlines::parse_inlines_from_span(*span) {
Ok(children) => children,
Err(e) => {
log::warn!("Failed to parse inline elements: {}", e);
vec![Node {
kind: NodeKind::Text(span.fragment().to_string()),
span: opt_span(*span),
children: Vec::new(),
}]
}
}
}
PendingSpan::Owned(text) => match crate::parser::inlines::parse_inlines(text) {
Ok(children) => children,
Err(_) => vec![Node {
kind: NodeKind::Text(text.clone()),
span: None,
children: Vec::new(),
}],
},
}
}
pub(crate) fn resolve_segmented_batch(paragraphs: Vec<Vec<Segment<'_>>>) -> Vec<Vec<Node>> {
enum Slot {
Literal(Node),
Resolved,
}
let mut pending_spans: Vec<PendingSpan<'_>> = Vec::new();
let plans: Vec<Vec<Slot>> = paragraphs
.into_iter()
.map(|segments| {
segments
.into_iter()
.map(|segment| match segment {
Segment::Literal(node) => Slot::Literal(node),
Segment::Pending(span) => {
pending_spans.push(span);
Slot::Resolved
}
})
.collect()
})
.collect();
let mut resolved = resolve_pending_batch(pending_spans).into_iter();
plans
.into_iter()
.map(|slots| {
let mut children = Vec::new();
for slot in slots {
match slot {
Slot::Literal(node) => children.push(node),
Slot::Resolved => children.extend(resolved.next().unwrap_or_default()),
}
}
children
})
.collect()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn rayon_par_iter_collect_preserves_order() {
let input: Vec<usize> = (0..500).collect();
let doubled: Vec<usize> = input.par_iter().map(|n| n * 2).collect();
let expected: Vec<usize> = input.iter().map(|n| n * 2).collect();
assert_eq!(doubled, expected);
}
#[test]
fn resolve_pending_batch_preserves_order_and_content() {
let texts = ["alpha", "beta", "gamma", "delta", "epsilon"];
let items: Vec<PendingSpan> = texts
.iter()
.map(|t| PendingSpan::Owned(t.to_string()))
.collect();
let results = resolve_pending_batch(items);
assert_eq!(results.len(), texts.len());
for (result, text) in results.iter().zip(texts.iter()) {
let NodeKind::Text(got) = &result[0].kind else {
panic!("expected a single Text node for plain input {text:?}");
};
assert_eq!(got, text);
}
}
#[test]
fn resolve_segmented_batch_interleaves_literals_and_pending() {
let literal = |label: &str| Node {
kind: NodeKind::Text(label.to_string()),
span: None,
children: Vec::new(),
};
let paragraphs = vec![
vec![
Segment::Literal(literal("before")),
Segment::Pending(PendingSpan::Owned("middle".to_string())),
Segment::Literal(literal("after")),
],
vec![Segment::Pending(PendingSpan::Owned("solo".to_string()))],
];
let resolved = resolve_segmented_batch(paragraphs);
assert_eq!(resolved.len(), 2);
assert!(matches!(&resolved[0][0].kind, NodeKind::Text(t) if t == "before"));
assert!(matches!(&resolved[0].last().unwrap().kind, NodeKind::Text(t) if t == "after"));
let middle_text: String = resolved[0][1..resolved[0].len() - 1]
.iter()
.filter_map(|n| match &n.kind {
NodeKind::Text(t) => Some(t.as_str()),
_ => None,
})
.collect();
assert_eq!(middle_text, "middle");
assert!(matches!(&resolved[1][0].kind, NodeKind::Text(t) if t == "solo"));
}
}