1use std::ops::Range;
20
21use crate::{
22 Constructs, syntax,
23 syntax::unicode::{is_punctuation, is_whitespace},
24};
25
26use super::{PN, Tokenizer};
27
28#[derive(Clone, Copy)]
29pub struct Delim {
30 pub node: usize,
31 pub marker: u8,
32 pub remaining: usize,
34 pub can_open: bool,
35 pub can_close: bool,
36}
37
38pub struct Formed {
41 pub r: Range<usize>,
42 pub marker: u8,
43 pub len: u8,
44}
45
46impl Tokenizer<'_, '_> {
47 pub(crate) fn delimiter_run(&mut self) {
48 let bytes = self.t.as_bytes();
49 let marker = bytes[self.pos];
50 let start = self.pos;
51 let len = syntax::run_len(&bytes[start..], marker);
52 let prev = self.t[..start].chars().next_back();
53 let next = self.t[start + len..].chars().next();
54 let run = Run::new(marker, len, prev, next, self.constructs);
55 self.flush_text(start);
56 self.nodes.push(PN::Text(start..start + len));
57 if run.valid {
61 self.first_delim_tilde.get_or_insert(marker == b'~');
62 }
63 if run.can_open || run.can_close {
64 self.delims.push(Delim {
65 node: self.nodes.len() - 1,
66 marker,
67 remaining: len,
68 can_open: run.can_open,
69 can_close: run.can_close,
70 });
71 }
72 self.pos = start + len;
73 self.text_start = self.pos;
74 }
75
76 pub(crate) fn process_emphasis(&mut self, bottom: usize) {
79 resolve(&mut self.nodes, &mut self.delims[bottom..], true, &mut self.formed);
80 }
81
82 pub(crate) fn process_emphasis_final(&mut self) {
84 let tilde_first = self.first_delim_tilde.unwrap_or(false);
85 resolve(&mut self.nodes, &mut self.delims, tilde_first, &mut self.formed);
86 }
87}
88
89fn resolve(nodes: &mut [PN], delims: &mut [Delim], tilde_first: bool, formed: &mut Vec<Formed>) {
90 let order = if tilde_first { [true, false] } else { [false, true] };
91 for tilde_pass in order {
92 if delims.iter().any(|d| d.remaining > 0 && d.can_close && (d.marker == b'~') == tilde_pass)
93 {
94 pass(nodes, delims, tilde_pass, formed);
95 }
96 }
97}
98
99#[expect(clippy::cast_possible_truncation)] fn pass(nodes: &mut [PN], delims: &mut [Delim], tilde_pass: bool, formed: &mut Vec<Formed>) {
102 let mut openers_bottom = [[[0; 2]; 3]; 2];
105 let mut tilde_bottom = [0; 3];
108 let mut closer = 0;
109 while closer < delims.len() {
110 let c = &delims[closer];
111 if (c.marker == b'~') != tilde_pass || !c.can_close || c.remaining == 0 {
112 closer += 1;
113 continue;
114 }
115 let class = (usize::from(c.marker == b'_'), c.remaining % 3, usize::from(c.can_open));
116 let scan_bottom = if tilde_pass {
117 tilde_bottom[c.remaining]
118 } else {
119 openers_bottom[class.0][class.1][class.2]
120 };
121
122 let mut opener = None;
123 let mut i = closer;
124 while i > scan_bottom {
125 i -= 1;
126 let o = &delims[i];
127 if o.marker != c.marker || !o.can_open || o.remaining == 0 {
128 continue;
129 }
130 if tilde_pass {
131 if o.remaining == c.remaining {
132 opener = Some(i);
133 break;
134 }
135 continue;
136 }
137 let forbidden = (o.can_close || c.can_open)
141 && !c.remaining.is_multiple_of(3)
142 && (o.remaining + c.remaining).is_multiple_of(3);
143 if !forbidden {
144 opener = Some(i);
145 break;
146 }
147 }
148 let Some(opener) = opener else {
149 if tilde_pass {
150 tilde_bottom[c.remaining] = closer;
151 } else {
152 openers_bottom[class.0][class.1][class.2] = closer;
153 }
154 if !delims[closer].can_open {
155 delims[closer].remaining = 0;
156 }
157 closer += 1;
158 continue;
159 };
160
161 let use_n = if tilde_pass {
162 delims[closer].remaining
163 } else if delims[opener].remaining >= 2 && delims[closer].remaining >= 2 {
164 2
165 } else {
166 1
167 };
168 let opener_node = delims[opener].node;
169 let closer_node = delims[closer].node;
170
171 let between = &mut delims[opener + 1..closer];
175 if between.iter().any(|d| d.remaining > 0 && (d.marker == b'~') != tilde_pass) {
176 for d in between.iter_mut() {
177 if (d.marker == b'~') == tilde_pass {
178 d.remaining = 0;
179 }
180 }
181 resolve(nodes, between, true, formed);
182 }
183 for d in between.iter_mut() {
184 d.remaining = 0;
185 }
186
187 let o_end = text_range(nodes, opener_node).end;
190 text_range(nodes, opener_node).end = o_end - use_n;
191 let c_start = text_range(nodes, closer_node).start;
192 text_range(nodes, closer_node).start = c_start + use_n;
193 formed.push(Formed {
194 r: o_end - use_n..c_start + use_n,
195 marker: delims[opener].marker,
196 len: use_n as u8,
197 });
198
199 delims[opener].remaining -= use_n;
200 delims[closer].remaining -= use_n;
201 if !tilde_pass && delims[opener].remaining > 0 {
208 let m = usize::from(delims[opener].marker == b'_');
209 for b in openers_bottom[m].iter_mut().flatten() {
210 *b = (*b).min(opener);
211 }
212 }
213 if delims[closer].remaining == 0 {
214 closer += 1;
215 }
216 }
217}
218
219fn text_range(nodes: &mut [PN], node: usize) -> &mut Range<usize> {
220 match &mut nodes[node] {
221 PN::Text(r) => r,
222 _ => unreachable!("delimiter nodes are text runs"),
223 }
224}
225
226pub fn build(nodes: impl IntoIterator<Item = PN>, formed: &mut Vec<Formed>) -> Vec<PN> {
229 formed.sort_unstable_by(|a, b| a.r.start.cmp(&b.r.start).then(b.r.end.cmp(&a.r.end)));
231 let mut pending = formed.drain(..).peekable();
232 let mut out = Vec::new();
233 let mut stack: Vec<(Formed, Vec<PN>)> = Vec::new();
234 let close = |stack: &mut Vec<(Formed, Vec<PN>)>, out: &mut Vec<PN>| {
235 let (pair, children) = stack.pop().expect("a pair is open");
236 let node = PN::Emph { marker: pair.marker, len: pair.len, children, r: pair.r };
237 stack.last_mut().map_or(out, |(_, children)| children).push(node);
238 };
239 for node in nodes {
240 let at = node.start();
241 while stack.last().is_some_and(|(pair, _)| pair.r.end <= at) {
243 close(&mut stack, &mut out);
244 }
245 while pending.peek().is_some_and(|pair| pair.r.start <= at) {
246 stack.push((pending.next().expect("peeked"), Vec::new()));
247 }
248 if matches!(&node, PN::Text(r) if r.is_empty()) {
249 continue;
250 }
251 stack.last_mut().map_or(&mut out, |(_, children)| children).push(node);
252 }
253 debug_assert_eq!(pending.len(), 0, "every pair starts before the last node");
254 while !stack.is_empty() {
255 close(&mut stack, &mut out);
256 }
257 out
258}
259
260#[derive(Clone, Copy, Debug, PartialEq, Eq)]
263pub struct Run {
264 pub marker: u8,
265 pub len: usize,
266 pub can_open: bool,
267 pub can_close: bool,
268 pub valid: bool,
270}
271
272impl Run {
273 pub fn new(
275 marker: u8,
276 len: usize,
277 prev: Option<char>,
278 next: Option<char>,
279 constructs: &Constructs,
280 ) -> Self {
281 let min = if constructs.gfm_strikethrough_single_tilde { 1 } else { 2 };
284 let valid = marker != b'~' || (min..=2).contains(&len);
285 let (can_open, can_close) = if valid {
286 classify(marker, prev, next, constructs.gfm_strikethrough)
287 } else {
288 (false, false)
289 };
290 Self { marker, len, can_open, can_close, valid }
291 }
292}
293
294#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
296pub struct Pair {
297 pub opener: usize,
298 pub closer: usize,
299 pub strong: bool,
301}
302
303pub fn pairs(runs: &[Run], in_link: bool) -> Vec<Pair> {
313 let tilde_first = in_link || runs.iter().find(|r| r.valid).is_some_and(|r| r.marker == b'~');
314 let mut nodes = Vec::with_capacity(runs.len() * 2);
317 let mut delims = Vec::new();
318 let mut starts = Vec::with_capacity(runs.len());
319 let mut pos = 0;
320 for run in runs {
321 if !nodes.is_empty() {
322 nodes.push(PN::Text(pos..pos + 1));
323 pos += 1;
324 }
325 starts.push(pos);
326 if run.can_open || run.can_close {
327 delims.push(Delim {
328 node: nodes.len(),
329 marker: run.marker,
330 remaining: run.len,
331 can_open: run.can_open,
332 can_close: run.can_close,
333 });
334 }
335 nodes.push(PN::Text(pos..pos + run.len));
336 pos += run.len;
337 }
338 let mut formed = Vec::new();
339 resolve(&mut nodes, &mut delims, tilde_first, &mut formed);
340 let run_at = |offset: usize| starts.partition_point(|&s| s <= offset) - 1;
341 let mut out: Vec<Pair> = formed
342 .iter()
343 .map(|pair| Pair {
344 opener: run_at(pair.r.start),
345 closer: run_at(pair.r.end - 1),
346 strong: pair.marker != b'~' && pair.len == 2,
347 })
348 .collect();
349 out.sort_unstable();
350 out
351}
352
353pub fn classify(marker: u8, prev: Option<char>, next: Option<char>, tilde: bool) -> (bool, bool) {
367 let is_marker = |c: Option<char>| {
368 marker != b'~' && (matches!(c, Some('*' | '_')) || (tilde && c == Some('~')))
369 };
370 let before = group(prev);
371 let after = group(next);
372
373 let open = after == 0 || (after == 2 && before != 0) || is_marker(next);
374 let close = before == 0 || (before == 2 && after != 0) || is_marker(prev);
375 if marker == b'_' {
376 (open && (before != 0 || !close), close && (after != 0 || !open))
377 } else {
378 (open, close)
379 }
380}
381
382fn group(c: Option<char>) -> u8 {
387 match c {
388 None => 1,
389 Some(c) if is_whitespace(c) => 1,
390 Some(c) if is_punctuation(c) => 2,
391 Some(_) => 0,
392 }
393}
394
395#[cfg(test)]
396mod tests {
397 use super::*;
398
399 fn run(marker: u8, len: usize, prev: Option<char>, next: Option<char>) -> Run {
400 Run::new(marker, len, prev, next, &Constructs::markdown())
401 }
402
403 #[test]
404 fn pairs_nested_and_rule_of_three() {
405 let runs = [run(b'*', 3, None, Some('b')), run(b'*', 3, Some('b'), None)];
407 assert_eq!(
408 pairs(&runs, false),
409 vec![
410 Pair { opener: 0, closer: 1, strong: false },
411 Pair { opener: 0, closer: 1, strong: true }
412 ]
413 );
414 let runs = [
416 run(b'*', 1, None, Some('a')),
417 run(b'*', 2, Some('a'), Some('b')),
418 run(b'*', 1, Some('b'), None),
419 ];
420 assert_eq!(pairs(&runs, false), vec![Pair { opener: 0, closer: 2, strong: false }]);
421 let runs = [
423 run(b'*', 2, None, Some('a')),
424 run(b'_', 1, Some(' '), Some('*')),
425 run(b'*', 2, Some('_'), Some('b')),
426 run(b'*', 2, Some('b'), Some('_')),
427 run(b'_', 1, Some('*'), Some(' ')),
428 run(b'*', 2, Some('c'), None),
429 ];
430 assert_eq!(
431 pairs(&runs, false),
432 vec![
433 Pair { opener: 0, closer: 2, strong: true },
434 Pair { opener: 3, closer: 5, strong: true }
435 ]
436 );
437 }
438
439 #[test]
440 fn pairs_family_order() {
441 let runs = [
443 run(b'~', 2, None, Some('*')),
444 run(b'*', 1, Some('~'), Some('a')),
445 run(b'~', 2, Some('a'), Some('*')),
446 run(b'*', 1, Some('~'), None),
447 ];
448 assert_eq!(pairs(&runs, true), vec![Pair { opener: 0, closer: 2, strong: false }]);
449 }
450}