1pub mod shared;
7
8pub mod cm_blockquote_parser;
10pub mod cm_fenced_code_block_parser;
12pub mod cm_heading_parser;
14pub mod cm_html_blocks_parser;
16pub mod cm_indented_code_block_parser;
18pub mod cm_link_reference_parser;
20pub mod cm_list_parser;
22pub mod cm_paragraph_parser;
24pub mod cm_thematic_break_parser;
26pub mod gfm_admonitions;
28pub mod gfm_footnote_definition_parser;
30pub mod gfm_table_parser;
32pub mod marco_headerless_table_parser;
34pub mod marco_sliders_parser;
36pub mod marco_tab_blocks_parser;
38#[cfg(feature = "parallel-parse")]
40pub(crate) mod parallel_inline;
41
42pub use shared::{dedent_list_item_content, to_parser_span, to_parser_span_range, GrammarSpan};
44
45use super::ast::Document;
46use crate::grammar::blocks as grammar;
47use crate::parser::ast::{Node, NodeKind};
48use nom::Input;
49
50#[derive(Debug, Clone, PartialEq)]
56enum BlockContextKind {
57 ListItem { content_indent: usize },
60}
61
62#[derive(Debug, Clone)]
64struct BlockContext {
65 kind: BlockContextKind,
66}
67
68impl BlockContext {
69 pub fn new_list_item(content_indent: usize) -> Self {
71 Self {
72 kind: BlockContextKind::ListItem { content_indent },
73 }
74 }
75
76 fn can_continue_at(&self, indent: usize) -> bool {
78 match self.kind {
79 BlockContextKind::ListItem { content_indent } => {
80 indent >= content_indent
82 }
83 }
84 }
85}
86
87struct ParserState {
93 blocks: Vec<BlockContext>,
94 allow_tab_blocks: bool,
95 allow_sliders: bool,
96}
97
98impl ParserState {
99 fn new() -> Self {
100 Self {
101 blocks: Vec::new(),
102 allow_tab_blocks: true,
103 allow_sliders: true,
104 }
105 }
106
107 fn new_with_tab_blocks(allow_tab_blocks: bool) -> Self {
108 Self {
109 blocks: Vec::new(),
110 allow_tab_blocks,
111 allow_sliders: true,
112 }
113 }
114
115 fn new_with_sliders(allow_sliders: bool) -> Self {
116 Self {
117 blocks: Vec::new(),
118 allow_tab_blocks: true,
119 allow_sliders,
120 }
121 }
122
123 pub fn push_block(&mut self, context: BlockContext) {
125 self.blocks.push(context);
126 }
127
128 fn pop_block(&mut self) -> Option<BlockContext> {
130 self.blocks.pop()
131 }
132
133 fn can_continue_at(&self, indent: usize) -> bool {
135 if let Some(context) = self.blocks.last() {
136 context.can_continue_at(indent)
137 } else {
138 false
140 }
141 }
142
143 fn close_blocks_until_indent(&mut self, indent: usize) -> usize {
146 let mut closed = 0;
147
148 while let Some(context) = self.blocks.last() {
150 if context.can_continue_at(indent) {
151 break;
153 } else {
154 self.blocks.pop();
156 closed += 1;
157 }
158 }
159
160 closed
161 }
162}
163
164pub fn parse_blocks(input: &str) -> Result<Document, Box<dyn std::error::Error>> {
170 let mut state = ParserState::new();
171 parse_blocks_internal(input, 0, &mut state)
172}
173
174fn parse_blocks_internal(
176 input: &str,
177 depth: usize,
178 state: &mut ParserState,
179) -> Result<Document, Box<dyn std::error::Error>> {
180 const MAX_DEPTH: usize = 100;
182 if depth > MAX_DEPTH {
183 log::warn!("Maximum recursion depth reached in block parser");
184 return Ok(Document::new());
185 }
186
187 log::debug!(
188 "Block parser input: {} bytes at depth {}, state depth: {}",
189 input.len(),
190 depth,
191 state.blocks.len()
192 );
193
194 let mut nodes = Vec::new();
195 let mut document = Document::new(); let mut remaining = GrammarSpan::new(input);
197
198 #[cfg(feature = "parallel-parse")]
204 let mut pending_leaves: Vec<parallel_inline::PendingLeaf<'_>> = Vec::new();
205
206 let max_iterations = input.lines().count().saturating_mul(8).max(1_000);
210 let mut iteration_count = 0;
211 let mut last_offset = 0;
212
213 while !remaining.fragment().is_empty() {
214 iteration_count += 1;
215 if iteration_count > max_iterations {
216 log::error!(
217 "Block parser exceeded iteration limit ({}) at depth {}",
218 max_iterations,
219 depth
220 );
221 break;
222 }
223
224 let current_offset = remaining.location_offset();
226 if current_offset == last_offset && iteration_count > 1 {
227 log::error!(
228 "Block parser not making progress at offset {}, depth {}",
229 current_offset,
230 depth
231 );
232 use nom::bytes::complete::take;
234 let skip_len = remaining
235 .fragment()
236 .chars()
237 .next()
238 .map(|c| c.len_utf8())
239 .unwrap_or(1);
240 if let Ok((rest, _)) =
241 take::<_, _, nom::error::Error<GrammarSpan>>(skip_len as u32)(remaining)
242 {
243 remaining = rest;
244 last_offset = remaining.location_offset();
245 continue;
246 }
247 break;
248 }
249 last_offset = current_offset;
250
251 let first_line_end = remaining
256 .fragment()
257 .find('\n')
258 .unwrap_or(remaining.fragment().len());
259 let first_line = &remaining.fragment()[..first_line_end];
260
261 if first_line.chars().all(|c| c == ' ' || c == '\t') {
264 let peek_offset = if first_line_end < remaining.fragment().len() {
266 first_line_end + 1
267 } else {
268 first_line_end
269 };
270
271 let mut next_nonblank_indent: Option<usize> = None;
273 let rest_of_input = &remaining.fragment()[peek_offset..];
274
275 for peek_line in rest_of_input.lines() {
276 if !peek_line.trim().is_empty() {
277 let mut indent = 0;
279 for ch in peek_line.chars() {
280 if ch == ' ' {
281 indent += 1;
282 } else if ch == '\t' {
283 indent += 4 - (indent % 4); } else {
285 break;
286 }
287 }
288 next_nonblank_indent = Some(indent);
289 break;
290 }
291 }
292
293 let should_continue = if let Some(next_indent) = next_nonblank_indent {
295 state.can_continue_at(next_indent)
297 } else {
298 false
300 };
301
302 if should_continue {
303 log::debug!(
306 "Blank line: continuing context at indent {:?}",
307 next_nonblank_indent
308 );
309
310 use nom::bytes::complete::take;
311 let skip_len = if first_line_end < remaining.fragment().len() {
312 first_line_end + 1 } else {
314 first_line_end
315 };
316
317 if let Ok((new_remaining, _)) =
318 take::<_, _, nom::error::Error<GrammarSpan>>(skip_len as u32)(remaining)
319 {
320 remaining = new_remaining;
321 continue;
322 } else {
323 break;
324 }
325 } else {
326 if let Some(next_indent) = next_nonblank_indent {
329 let closed = state.close_blocks_until_indent(next_indent);
330 log::debug!(
331 "Blank line: closed {} blocks due to indent {}",
332 closed,
333 next_indent
334 );
335 } else {
336 log::debug!("Blank line: end of input, closing all blocks");
338 while state.pop_block().is_some() {}
339 }
340
341 use nom::bytes::complete::take;
343 let skip_len = if first_line_end < remaining.fragment().len() {
344 first_line_end + 1
345 } else {
346 first_line_end
347 };
348
349 if let Ok((new_remaining, _)) =
350 take::<_, _, nom::error::Error<GrammarSpan>>(skip_len as u32)(remaining)
351 {
352 remaining = new_remaining;
353 continue;
354 } else {
355 break;
356 }
357 }
358 }
359
360 if let Ok((rest, content)) = grammar::html_special_tag(remaining) {
363 nodes.push(cm_html_blocks_parser::parse_html_block(content));
364 remaining = rest;
365 continue;
366 }
367
368 if let Ok((rest, content)) = grammar::html_comment(remaining) {
370 nodes.push(cm_html_blocks_parser::parse_html_block(content));
371 remaining = rest;
372 continue;
373 }
374
375 if let Ok((rest, content)) = grammar::html_processing_instruction(remaining) {
377 nodes.push(cm_html_blocks_parser::parse_html_block(content));
378 remaining = rest;
379 continue;
380 }
381
382 if let Ok((rest, content)) = grammar::html_declaration(remaining) {
384 nodes.push(cm_html_blocks_parser::parse_html_block(content));
385 remaining = rest;
386 continue;
387 }
388
389 if let Ok((rest, content)) = grammar::html_cdata(remaining) {
391 nodes.push(cm_html_blocks_parser::parse_html_block(content));
392 remaining = rest;
393 continue;
394 }
395
396 if let Ok((rest, content)) = grammar::html_block_tag(remaining) {
398 nodes.push(cm_html_blocks_parser::parse_html_block(content));
399 remaining = rest;
400 continue;
401 }
402
403 if let Ok((rest, content)) = grammar::html_complete_tag(remaining) {
406 nodes.push(cm_html_blocks_parser::parse_html_block(content));
407 remaining = rest;
408 continue;
409 } if let Ok((rest, (level, content))) = grammar::heading(remaining) {
411 nodes.push(cm_heading_parser::parse_atx_heading(level, content));
412 remaining = rest;
413 continue;
414 }
415
416 if let Ok((rest, (language, content))) = grammar::fenced_code_block(remaining) {
418 nodes.push(cm_fenced_code_block_parser::parse_fenced_code_block(
419 language, content,
420 ));
421 remaining = rest;
422 continue;
423 }
424
425 if let Ok((rest, content)) = grammar::thematic_break(remaining) {
427 nodes.push(cm_thematic_break_parser::parse_thematic_break(content));
428 remaining = rest;
429 continue;
430 }
431
432 if let Ok((rest, content)) = grammar::blockquote(remaining) {
434 let node =
435 cm_blockquote_parser::parse_blockquote(content, depth, |cleaned, new_depth| {
436 parse_blocks_internal(cleaned, new_depth, state)
437 })?;
438
439 nodes.push(node);
440 remaining = rest;
441 continue;
442 }
443
444 if let Ok((rest, content)) = grammar::indented_code_block(remaining) {
447 nodes.push(cm_indented_code_block_parser::parse_indented_code_block(
448 content,
449 ));
450 remaining = rest;
451 continue;
452 }
453
454 if let Ok((rest, items)) = grammar::list(remaining) {
457 let node = cm_list_parser::parse_list(
458 items,
459 depth,
460 parse_blocks_internal,
461 |content_indent| {
462 let mut item_state = ParserState::new();
463 item_state.push_block(BlockContext::new_list_item(content_indent));
464 item_state
465 },
466 )?;
467
468 nodes.push(node);
469 remaining = rest;
470 continue;
471 }
472
473 if state.allow_sliders {
477 let deck_start = remaining;
478 if let Ok((rest, deck)) = grammar::marco_slide_deck(remaining) {
479 let node = marco_sliders_parser::parse_marco_slide_deck(
480 deck,
481 deck_start,
482 rest,
483 depth,
484 |slide_body, new_depth| {
485 let mut slide_state = ParserState::new_with_sliders(false);
488 parse_blocks_internal(slide_body, new_depth, &mut slide_state)
489 },
490 )?;
491
492 nodes.push(node);
493 remaining = rest;
494 continue;
495 }
496 }
497
498 let full_start = remaining;
501 if let Ok((rest, (level, content))) = grammar::setext_heading(remaining) {
502 let full_end = rest;
503 nodes.push(cm_heading_parser::parse_setext_heading(
504 level, content, full_start, full_end,
505 ));
506 remaining = rest;
507 continue;
508 }
509
510 #[cfg(feature = "parallel-parse")]
513 if depth == 0 {
514 if let Some((rest, node, content)) =
515 gfm_footnote_definition_parser::parse_footnote_definition_shape(remaining)
516 {
517 let node_index = nodes.len();
518 if !content.is_empty() {
519 pending_leaves.push(parallel_inline::PendingLeaf {
520 node_index,
521 nested_child_index: Some(0),
522 segments: vec![parallel_inline::Segment::Pending(
523 parallel_inline::PendingSpan::Owned(content),
524 )],
525 });
526 }
527 nodes.push(node);
528 remaining = rest;
529 continue;
530 }
531 } else if let Some((rest, node)) =
532 gfm_footnote_definition_parser::parse_footnote_definition(remaining)
533 {
534 nodes.push(node);
535 remaining = rest;
536 continue;
537 }
538 #[cfg(not(feature = "parallel-parse"))]
539 if let Some((rest, node)) =
540 gfm_footnote_definition_parser::parse_footnote_definition(remaining)
541 {
542 nodes.push(node);
543 remaining = rest;
544 continue;
545 }
546
547 if let Ok((rest, (label, url, title))) = grammar::link_reference_definition(remaining) {
548 cm_link_reference_parser::parse_link_reference(&mut document, &label, url, title);
549 remaining = rest;
550 continue;
551 }
552
553 let headerless_table_start = remaining;
559 #[cfg(feature = "parallel-parse")]
560 if depth == 0 {
561 if let Ok((rest, table)) = grammar::headerless_table(remaining) {
562 nodes.push(
563 marco_headerless_table_parser::parse_marco_headerless_table_parallel(
564 table,
565 headerless_table_start,
566 rest,
567 ),
568 );
569 remaining = rest;
570 continue;
571 }
572 } else if let Ok((rest, table)) = grammar::headerless_table(remaining) {
573 nodes.push(marco_headerless_table_parser::parse_marco_headerless_table(
574 table,
575 headerless_table_start,
576 rest,
577 ));
578 remaining = rest;
579 continue;
580 }
581 #[cfg(not(feature = "parallel-parse"))]
582 if let Ok((rest, table)) = grammar::headerless_table(remaining) {
583 nodes.push(marco_headerless_table_parser::parse_marco_headerless_table(
584 table,
585 headerless_table_start,
586 rest,
587 ));
588 remaining = rest;
589 continue;
590 }
591
592 let table_start = remaining;
593 #[cfg(feature = "parallel-parse")]
594 if depth == 0 {
595 if let Ok((rest, table)) = grammar::gfm_table(remaining) {
596 nodes.push(gfm_table_parser::parse_gfm_table_parallel(
597 table,
598 table_start,
599 rest,
600 ));
601 remaining = rest;
602 continue;
603 }
604 } else if let Ok((rest, table)) = grammar::gfm_table(remaining) {
605 nodes.push(gfm_table_parser::parse_gfm_table(table, table_start, rest));
606 remaining = rest;
607 continue;
608 }
609 #[cfg(not(feature = "parallel-parse"))]
610 if let Ok((rest, table)) = grammar::gfm_table(remaining) {
611 nodes.push(gfm_table_parser::parse_gfm_table(table, table_start, rest));
612 remaining = rest;
613 continue;
614 }
615
616 if state.allow_tab_blocks {
619 let tab_start = remaining;
620 if let Ok((rest, block)) = grammar::marco_tab_block(remaining) {
621 let node = marco_tab_blocks_parser::parse_marco_tab_block(
622 block,
623 tab_start,
624 rest,
625 depth,
626 |panel, new_depth| {
627 let mut panel_state = ParserState::new_with_tab_blocks(false);
631 parse_blocks_internal(panel, new_depth, &mut panel_state)
632 },
633 )?;
634
635 nodes.push(node);
636 remaining = rest;
637 continue;
638 }
639 }
640
641 if let Some((rest, node)) = parse_extended_definition_list(remaining, depth) {
644 nodes.push(node);
645 remaining = rest;
646 continue;
647 }
648
649 #[cfg(feature = "parallel-parse")]
651 if depth == 0 {
652 if let Ok((rest, content)) = grammar::paragraph(remaining) {
653 let (node, segments) = cm_paragraph_parser::parse_paragraph_shape(content);
654 let node_index = nodes.len();
655 if !segments.is_empty() {
656 pending_leaves.push(parallel_inline::PendingLeaf {
657 node_index,
658 nested_child_index: None,
659 segments,
660 });
661 }
662 nodes.push(node);
663 remaining = rest;
664 continue;
665 }
666 } else if let Ok((rest, content)) = grammar::paragraph(remaining) {
667 nodes.push(cm_paragraph_parser::parse_paragraph(content));
668 remaining = rest;
669 continue;
670 }
671 #[cfg(not(feature = "parallel-parse"))]
672 if let Ok((rest, content)) = grammar::paragraph(remaining) {
673 nodes.push(cm_paragraph_parser::parse_paragraph(content));
674 remaining = rest;
675 continue;
676 }
677
678 log::warn!(
681 "Could not parse block at offset {}, skipping character",
682 remaining.location_offset()
683 );
684 use nom::bytes::complete::take;
685 let skip_len = remaining
686 .fragment()
687 .chars()
688 .next()
689 .map(|c| c.len_utf8())
690 .unwrap_or(1);
691 if let Ok((rest, _)) =
692 take::<_, _, nom::error::Error<GrammarSpan>>(skip_len as u32)(remaining)
693 {
694 remaining = rest;
695 } else {
696 break;
697 }
698 }
699
700 log::info!("Parsed {} blocks", nodes.len());
701
702 #[cfg(feature = "parallel-parse")]
708 parallel_inline::apply_pending_leaves(&mut nodes, pending_leaves);
709
710 document.children = nodes;
712 Ok(document)
713}
714
715fn parse_extended_definition_list<'a>(
736 input: GrammarSpan<'a>,
737 depth: usize,
738) -> Option<(GrammarSpan<'a>, Node)> {
739 let text = input.fragment();
741 if text.is_empty() {
742 return None;
743 }
744
745 const CONTINUATION_INDENT: usize = 2;
746
747 fn line_bounds(s: &str, start: usize) -> (usize, usize, usize) {
748 let rel_end = s[start..].find('\n').map(|i| start + i).unwrap_or(s.len());
750 let next = if rel_end < s.len() {
751 rel_end + 1
752 } else {
753 rel_end
754 };
755 (start, rel_end, next)
756 }
757
758 fn count_indent_columns(line: &str) -> usize {
759 let mut indent = 0usize;
761 for ch in line.chars() {
762 if ch == ' ' {
763 indent += 1;
764 } else if ch == '\t' {
765 indent += 4 - (indent % 4);
766 } else {
767 break;
768 }
769 }
770 indent
771 }
772
773 fn def_marker_content_start(line: &str) -> Option<usize> {
774 let bytes = line.as_bytes();
776 let mut i = 0usize;
777 for _ in 0..3 {
778 if bytes.get(i) == Some(&b' ') {
779 i += 1;
780 } else {
781 break;
782 }
783 }
784
785 if bytes.get(i) != Some(&b':') {
786 return None;
787 }
788 if bytes.get(i + 1) == Some(&b':') {
790 return None;
791 }
792
793 match bytes.get(i + 1) {
795 Some(b' ') | Some(b'\t') => {
796 Some(i + 2)
798 }
799 _ => None,
800 }
801 }
802
803 fn can_start_item_at(text: &str, start: usize) -> bool {
804 if start >= text.len() {
805 return false;
806 }
807 let (_t0s, t0e, t1s) = line_bounds(text, start);
808 let term_line = &text[start..t0e];
809 if term_line.trim().is_empty() {
810 return false;
811 }
812 if t1s >= text.len() {
813 return false;
814 }
815 let (_d0s, d0e, _d1s) = line_bounds(text, t1s);
816 let def_line = &text[t1s..d0e];
817 def_marker_content_start(def_line).is_some()
818 }
819
820 let mut children: Vec<Node> = Vec::new();
822 let mut cursor = 0usize;
823 let mut parsed_any = false;
824 #[cfg(feature = "parallel-parse")]
830 let mut pending_leaves: Vec<parallel_inline::PendingLeaf<'a>> = Vec::new();
831
832 loop {
834 if cursor >= text.len() {
835 break;
836 }
837
838 let (term_start, term_end, after_term) = line_bounds(text, cursor);
840 let term_line = &text[term_start..term_end];
841
842 if term_line.trim().is_empty() {
845 break;
846 }
847
848 if after_term >= text.len() {
850 break;
851 }
852
853 let (def_line_start, def_line_end, _after_def_line) = line_bounds(text, after_term);
854 let first_def_line = &text[def_line_start..def_line_end];
855 if def_marker_content_start(first_def_line).is_none() {
856 break;
857 }
858
859 let term_start_span = input.take_from(term_start);
861 let (term_after_span, term_taken_span) = term_start_span.take_split(term_end - term_start);
862 let term_span = crate::parser::shared::opt_span_range(term_start_span, term_after_span);
863
864 #[cfg(feature = "parallel-parse")]
865 if depth == 0 {
866 let node_index = children.len();
867 if !term_taken_span.fragment().is_empty() {
868 pending_leaves.push(parallel_inline::PendingLeaf {
869 node_index,
870 nested_child_index: None,
871 segments: vec![parallel_inline::Segment::Pending(
872 parallel_inline::PendingSpan::Borrowed(term_taken_span),
873 )],
874 });
875 }
876 children.push(Node {
877 kind: NodeKind::DefinitionTerm,
878 span: term_span,
879 children: Vec::new(),
880 });
881 } else {
882 let term_children =
883 match crate::parser::inlines::parse_inlines_from_span(term_taken_span) {
884 Ok(children) => children,
885 Err(e) => {
886 log::warn!("Failed to parse inline elements in definition term: {}", e);
887 vec![Node {
888 kind: NodeKind::Text(term_taken_span.fragment().to_string()),
889 span: crate::parser::shared::opt_span(term_taken_span),
890 children: Vec::new(),
891 }]
892 }
893 };
894
895 children.push(Node {
896 kind: NodeKind::DefinitionTerm,
897 span: term_span,
898 children: term_children,
899 });
900 }
901 #[cfg(not(feature = "parallel-parse"))]
902 {
903 let term_children =
904 match crate::parser::inlines::parse_inlines_from_span(term_taken_span) {
905 Ok(children) => children,
906 Err(e) => {
907 log::warn!("Failed to parse inline elements in definition term: {}", e);
908 vec![Node {
909 kind: NodeKind::Text(term_taken_span.fragment().to_string()),
910 span: crate::parser::shared::opt_span(term_taken_span),
911 children: Vec::new(),
912 }]
913 }
914 };
915
916 children.push(Node {
917 kind: NodeKind::DefinitionTerm,
918 span: term_span,
919 children: term_children,
920 });
921 }
922
923 cursor = after_term;
925 while cursor < text.len() {
926 let (line_start, line_end, next_line_start) = line_bounds(text, cursor);
927 let line = &text[line_start..line_end];
928
929 let content_start_in_line = match def_marker_content_start(line) {
930 Some(i) => i,
931 None => break,
932 };
933
934 let def_block_start = line_start;
936 let mut def_block_end = next_line_start;
937
938 let mut raw_lines: Vec<&str> = Vec::new();
940 raw_lines.push(&line[content_start_in_line..]);
941
942 let mut scan = next_line_start;
943 while scan < text.len() {
944 let (ls, le, ln) = line_bounds(text, scan);
945 let l = &text[ls..le];
946
947 if def_marker_content_start(l).is_some() {
949 break;
950 }
951
952 if l.trim().is_empty() {
953 let mut look = ln;
956 let mut next_indent: Option<usize> = None;
957 while look < text.len() {
958 let (_pls, ple, pln) = line_bounds(text, look);
959 let pl = &text[look..ple];
960 if !pl.trim().is_empty() {
961 next_indent = Some(count_indent_columns(pl));
962 break;
963 }
964 look = pln;
965 }
966
967 if next_indent.unwrap_or(0) >= CONTINUATION_INDENT {
968 raw_lines.push("");
969 scan = ln;
970 def_block_end = scan;
971 continue;
972 }
973
974 break;
975 }
976
977 let indent = count_indent_columns(l);
978 if indent >= CONTINUATION_INDENT {
979 raw_lines.push(l);
980 scan = ln;
981 def_block_end = scan;
982 continue;
983 }
984
985 break;
986 }
987
988 let raw_body = raw_lines.join("\n");
989 let dedented = dedent_list_item_content(&raw_body, CONTINUATION_INDENT);
990
991 let mut def_state = ParserState::new();
993 def_state.push_block(BlockContext::new_list_item(CONTINUATION_INDENT));
994 let def_children = match parse_blocks_internal(&dedented, depth + 1, &mut def_state) {
995 Ok(doc) => doc.children,
996 Err(e) => {
997 log::warn!("Failed to parse definition description blocks: {}", e);
998 Vec::new()
999 }
1000 };
1001
1002 let dd_start_span = input.take_from(def_block_start);
1003 let dd_end_span = input.take_from(def_block_end);
1004 children.push(Node {
1005 kind: NodeKind::DefinitionDescription,
1006 span: crate::parser::shared::opt_span_range(dd_start_span, dd_end_span),
1007 children: def_children,
1008 });
1009
1010 parsed_any = true;
1011 cursor = def_block_end;
1012 }
1013
1014 let mut scan = cursor;
1016 while scan < text.len() {
1017 let (_ls, le, ln) = line_bounds(text, scan);
1018 let l = &text[scan..le];
1019 if !l.trim().is_empty() {
1020 break;
1021 }
1022 scan = ln;
1023 }
1024
1025 if scan != cursor && can_start_item_at(text, scan) {
1026 cursor = scan;
1027 continue;
1028 }
1029
1030 break;
1031 }
1032
1033 if !parsed_any {
1034 return None;
1035 }
1036
1037 #[cfg(feature = "parallel-parse")]
1038 parallel_inline::apply_pending_leaves(&mut children, pending_leaves);
1039
1040 let (rest, _taken) = input.take_split(cursor);
1041 let span = crate::parser::shared::opt_span_range(input, rest);
1042 Some((
1043 rest,
1044 Node {
1045 kind: NodeKind::DefinitionList,
1046 span,
1047 children,
1048 },
1049 ))
1050}
1051
1052#[cfg(test)]
1053mod tests {
1054 use super::parse_blocks;
1055 use crate::parser::ast::NodeKind;
1056
1057 #[test]
1058 fn smoke_test_block_parser_handles_large_documents() {
1059 let count = 250;
1062 let mut input = String::new();
1063 for i in 0..count {
1064 input.push_str(&format!("Paragraph {i}\n\n"));
1065 }
1066
1067 let doc = parse_blocks(&input).expect("parse_blocks failed");
1068 assert_eq!(doc.children.len(), count);
1069 assert!(matches!(
1070 doc.children.last().unwrap().kind,
1071 NodeKind::Paragraph
1072 ));
1073 }
1074}