Skip to main content

mech_syntax/
mechdown.rs

1#[macro_use]
2use crate::*;
3
4#[cfg(not(feature = "no-std"))] use core::fmt;
5#[cfg(feature = "no-std")] use alloc::fmt;
6#[cfg(feature = "no-std")] use alloc::string::String;
7#[cfg(feature = "no-std")] use alloc::vec::Vec;
8use nom::{
9  IResult,
10  branch::alt,
11  sequence::{tuple as nom_tuple, pair},
12  combinator::{opt, eof, peek},
13  multi::{many1, many_till, many0, separated_list1,separated_list0},
14  bytes::complete::{take_until, take_while},
15  Err,
16  Err::Failure
17};
18
19use std::collections::HashMap;
20use colored::*;
21
22use crate::*;
23
24#[derive(Default)]
25pub struct TitleFrontMatter {
26  pub author: Option<Paragraph>,
27  pub date: Option<Paragraph>,
28  pub hero: Option<SectionElement>,
29  pub kicker: Option<Paragraph>,
30  pub summary: Option<Paragraph>,
31  pub next: Option<Paragraph>,
32  pub previous: Option<Paragraph>,
33}
34
35// Mechdown
36// ============================================================================
37
38// title := +text, new-line, +equal, *(space|tab), *whitespace ;
39pub fn title(input: ParseString) -> ParseResult<Title> {
40  let (input, mut text) = many1(text)(input)?;
41  let (input, _) = new_line(input)?;
42  let (input, _) = many1(equal)(input)?;
43  let (input, _) = whitespace0(input)?;
44  let (input, front_matter) = opt(title_front_matter)(input)?;
45  let mut title = Token::merge_tokens(&mut text).unwrap();
46  title.kind = TokenKind::Title;
47  let front_matter = front_matter.unwrap_or_default();
48  Ok((input, Title{
49    text: title,
50    author: front_matter.author,
51    date: front_matter.date,
52    hero: front_matter.hero,
53    kicker: front_matter.kicker,
54    summary: front_matter.summary,
55    next: front_matter.next,
56    previous: front_matter.previous,
57  }))
58}
59
60pub fn title_front_matter(input: ParseString) -> ParseResult<TitleFrontMatter> {
61  let mut input = input;
62  let mut front_matter = TitleFrontMatter::default();
63
64  while many1(equal)(input.clone()).is_err() {
65    let (next_input, key) = identifier(input.clone())?;
66    let (next_input, _) = many0(space_tab)(next_input)?;
67    let (next_input, _) = colon(next_input)?;
68    let (next_input, _) = many0(space_tab)(next_input)?;
69    let key_name = key.to_string().to_lowercase();
70
71    if key_name == "hero" {
72      if let Ok((next_input, image)) = img(next_input.clone()) {
73        let (next_input, _) = whitespace0(next_input)?;
74        input = next_input;
75        front_matter.hero = Some(SectionElement::Image(image));
76        continue;
77      } else if let Ok((next_input, figure_table)) = figures(next_input.clone()) {
78        let (next_input, _) = whitespace0(next_input)?;
79        input = next_input;
80        front_matter.hero = Some(SectionElement::FigureTable(figure_table));
81        continue;
82      }
83    }
84
85    let (next_input, paragraph) = inline_paragraph(next_input)?;
86    let (next_input, _) = new_line(next_input)?;
87    input = next_input;
88    match key_name.as_str() {
89      "author" => front_matter.author = Some(paragraph),
90      "date" => front_matter.date = Some(paragraph),
91      "kicker" => front_matter.kicker = Some(paragraph),
92      "summary" => front_matter.summary = Some(paragraph),
93      "next" => front_matter.next = Some(paragraph),
94      "previous" => front_matter.previous = Some(paragraph),
95      _ => (),
96    }
97  }
98
99  let (input, _) = many1(equal)(input)?;
100  let (input, _) = whitespace0(input)?;
101  Ok((input, front_matter))
102}
103
104pub struct MarkdownTableHeader {
105  pub header: Vec<(Token, Token)>,
106}
107
108pub fn no_alignment(input: ParseString) -> ParseResult<ColumnAlignment> {
109  let (input, _) = many1(dash)(input)?;
110  Ok((input, ColumnAlignment::Left))
111}
112
113pub fn left_alignment(input: ParseString) -> ParseResult<ColumnAlignment> {
114  let (input, _) = colon(input)?;
115  let (input, _) = many1(dash)(input)?;
116  Ok((input, ColumnAlignment::Left))
117}
118
119pub fn right_alignment(input: ParseString) -> ParseResult<ColumnAlignment> {
120  let (input, _) = many1(dash)(input)?;
121  let (input, _) = colon(input)?;
122  Ok((input, ColumnAlignment::Right))
123}
124
125pub fn center_alignment(input: ParseString) -> ParseResult<ColumnAlignment> {
126  let (input, _) = colon(input)?;
127  let (input, _) = many1(dash)(input)?;
128  let (input, _) = colon(input)?;
129  Ok((input, ColumnAlignment::Center))
130}
131
132pub fn alignment_separator(input: ParseString) -> ParseResult<ColumnAlignment> {
133  let (input, _) = many0(space_tab)(input)?;
134  let (input, separator) = alt((center_alignment, left_alignment, right_alignment, no_alignment))(input)?;
135  let (input, _) = many0(space_tab)(input)?;
136  Ok((input, separator))
137}
138
139pub fn mechdown_table(input: ParseString) -> ParseResult<MarkdownTable> {
140  let (input, _) = whitespace0(input)?;
141  let (input, table) = alt((mechdown_table_with_header, mechdown_table_no_header))(input)?;
142  Ok((input, table))
143}
144
145pub fn mechdown_table_with_header(input: ParseString) -> ParseResult<MarkdownTable> {
146  let (input, (header,alignment)) = mechdown_table_header(input)?;
147  let (input, rows) = many1(mechdown_table_row)(input)?;
148  Ok((input, MarkdownTable{header, rows, alignment}))
149}
150
151pub fn mechdown_table_no_header(input: ParseString) -> ParseResult<MarkdownTable> {
152  let (input, rows) = many1(mechdown_table_row)(input)?;
153  let header = vec![];
154  let alignment = vec![];
155  Ok((input, MarkdownTable{header, rows, alignment}))
156}
157
158pub fn mechdown_table_header(input: ParseString) -> ParseResult<(Vec<Paragraph>,Vec<ColumnAlignment>)> {
159  let (input, _) = whitespace0(input)?;
160  let (input, header) = many1(tuple((bar, tuple((many0(space_tab), inline_paragraph)))))(input)?;
161  let (input, _) = bar(input)?;
162  let (input, _) = whitespace0(input)?;
163  let (input, alignment) = many1(tuple((bar, tuple((many0(space_tab), alignment_separator)))))(input)?;
164  let (input, _) = bar(input)?;
165  let (input, _) = whitespace0(input)?;
166  let column_names: Vec<Paragraph> = header.into_iter().map(|(_,(_,tkn))| tkn).collect();
167  let column_alignments = alignment.into_iter().map(|(_,(_,tkn))| tkn).collect();
168  Ok((input, (column_names,column_alignments)))
169}
170
171pub fn empty_paragraph(input: ParseString) -> ParseResult<Paragraph> {
172  Ok((input, Paragraph{elements: vec![], error_range: None}))
173}
174
175// mechdown_table_row := +(bar, paragraph), bar, *whitespace ;
176pub fn mechdown_table_row(input: ParseString) -> ParseResult<Vec<Paragraph>> {
177  let (input, _) = whitespace0(input)?;
178  let (input, _) = bar(input)?;
179  let (input, row) = many1(tuple((alt((tuple((many0(space_tab), inline_paragraph)),tuple((many1(space_tab), empty_paragraph)))),bar)))(input)?;
180  let (input, _) = whitespace0(input)?;
181  let row = row.into_iter().map(|((_,tkn),_)| tkn).collect();
182  Ok((input, row))
183}
184
185// subtitle := +(digit | alpha), period, *space-tab, paragraph-newline, *space-tab, whitespace* ;
186pub fn ul_subtitle(input: ParseString) -> ParseResult<Subtitle> {
187  let (input, _) = many1((alt((digit_token, alpha_token))))(input)?;
188  let (input, _) = period(input)?;
189  let (input, _) = many0(space_tab)(input)?;
190  let (input, text) = paragraph_newline(input)?;
191  let (input, _) = many1(dash)(input)?;
192  let (input, _) = many0(space_tab)(input)?;
193  let (input, _) = new_line(input)?;
194  let (input, _) = many0(space_tab)(input)?;
195  let (input, _) = whitespace0(input)?;
196  Ok((input, Subtitle{text, level: 2}))
197}
198
199// subtitle := *(space-tab), "(", +(alpha | digit | period), ")", *(space-tab), paragraph-newline, *(space-tab), whitespace* ;
200pub fn subtitle(input: ParseString) -> ParseResult<Subtitle> {
201  let (input, _) = peek(is_not(alt((error_sigil, info_sigil))))(input)?;
202  let (input, _) = many0(space_tab)(input)?;
203  let (input, _) = left_parenthesis(input)?;
204  let (input, num) = separated_list1(period,alt((many1(alpha),many1(digit))))(input)?;
205  let (input, _) = right_parenthesis(input)?;
206  let (input, _) = many0(space_tab)(input)?;
207  let (input, text) = paragraph_newline(input)?;
208  let (input, _) = many0(space_tab)(input)?;
209  let (input, _) = whitespace0(input)?;
210  let level: u8 = if num.len() < 3 { 3 } else { num.len() as u8 + 1 };
211  Ok((input, Subtitle{text, level}))
212}
213
214// strong := (asterisk, asterisk), +paragraph-element, (asterisk, asterisk) ;
215pub fn strong(input: ParseString) -> ParseResult<ParagraphElement> {
216  let (input, _) = tuple((asterisk,asterisk))(input)?;
217  let (input, text) = paragraph_element(input)?;
218  let (input, _) = tuple((asterisk,asterisk))(input)?;
219  Ok((input, ParagraphElement::Strong(Box::new(text))))
220}
221
222/// emphasis := asterisk, +paragraph-element, asterisk ;
223pub fn emphasis(input: ParseString) -> ParseResult<ParagraphElement> {
224  let (input, _) = asterisk(input)?;
225  let (input, text) = paragraph_element(input)?;
226  let (input, _) = asterisk(input)?;
227  Ok((input, ParagraphElement::Emphasis(Box::new(text))))
228}
229
230// strikethrough := tilde, +paragraph-element, tilde ;
231pub fn strikethrough(input: ParseString) -> ParseResult<ParagraphElement> {
232  let (input, _) = tilde(input)?;
233  let (input, text) = paragraph_element(input)?;
234  let (input, _) = tilde(input)?;
235  Ok((input, ParagraphElement::Strikethrough(Box::new(text))))
236}
237
238/// underline := underscore, +paragraph-element, underscore ;
239pub fn underline(input: ParseString) -> ParseResult<ParagraphElement> {
240  let (input, _) = underscore(input)?;
241  let (input, text) = paragraph_element(input)?;
242  let (input, _) = underscore(input)?;
243  Ok((input, ParagraphElement::Underline(Box::new(text))))
244}
245
246/// highlight := "!!", +paragraph-element, "!!" ;
247pub fn highlight(input: ParseString) -> ParseResult<ParagraphElement> {
248  let (input, _) = highlight_sigil(input)?;
249  let (input, text) = paragraph_element(input)?;
250  let (input, _) = highlight_sigil(input)?;
251  Ok((input, ParagraphElement::Highlight(Box::new(text))))
252}
253
254// inline-code := grave, +text, grave ; 
255pub fn inline_code(input: ParseString) -> ParseResult<ParagraphElement> {
256  let (input, _) = is_not(grave_codeblock_sigil)(input)?; // prevent matching code fences
257  let (input, _) = grave(input)?;
258  let (input, text) = many0(tuple((is_not(grave),text)))(input)?;
259  let (input, _) = grave(input)?;
260  let mut text = text.into_iter().map(|(_,tkn)| tkn).collect();
261  // return empty token if there's nothing between the graves
262  let mut text = match Token::merge_tokens(&mut text) {
263    Some(t) => t,
264    None => {
265      return Ok((input, ParagraphElement::InlineCode(Token::default())));
266    }
267  };
268  text.kind = TokenKind::Text;
269  Ok((input, ParagraphElement::InlineCode(text)))
270}
271
272// inline-equation := equation-sigil, +text, equation-sigil ;
273pub fn inline_equation(input: ParseString) -> ParseResult<ParagraphElement> {
274  let (input, _) = equation_sigil(input)?;
275  let (input, txt) = many0(tuple((is_not(equation_sigil),alt((backslash,text)))))(input)?;
276  let (input, _) = equation_sigil(input)?;
277  let mut txt = txt.into_iter().map(|(_,tkn)| tkn).collect();
278  let mut eqn = Token::merge_tokens(&mut txt).unwrap();
279  eqn.kind = TokenKind::Text;
280  Ok((input, ParagraphElement::InlineEquation(eqn)))
281}
282
283// hyperlink := "[", +text, "]", "(", +text, ")" ;
284pub fn hyperlink(input: ParseString) -> ParseResult<ParagraphElement> {
285  let (input, _) = left_bracket(input)?;
286  let (input, link_text) = inline_paragraph(input)?;
287  let (input, _) = right_bracket(input)?;
288  let (input, _) = left_parenthesis(input)?;
289  let (input, link) = many1(tuple((is_not(right_parenthesis),text)))(input)?;
290  let (input, _) = right_parenthesis(input)?;
291  let mut tokens = link.into_iter().map(|(_,tkn)| tkn).collect::<Vec<Token>>();
292  let link_merged = Token::merge_tokens(&mut tokens).unwrap();
293  Ok((input, ParagraphElement::Hyperlink((link_text, link_merged))))
294}
295
296// raw-hyperlink := http-prefix, +text ;
297pub fn raw_hyperlink(input: ParseString) -> ParseResult<ParagraphElement> {
298  let (input, _) = peek(http_prefix)(input)?;
299  let (input, address) = many1(tuple((is_not(space), text)))(input)?;
300  let mut tokens = address.into_iter().map(|(_,tkn)| tkn).collect::<Vec<Token>>();
301  let url_token = Token::merge_tokens(&mut tokens).unwrap();
302  let url_paragraph = Paragraph::from_tokens(vec![url_token.clone()]);
303  Ok((input, ParagraphElement::Hyperlink((url_paragraph, url_token))))
304}
305
306// option-map := "{", whitespace*, mapping*, whitespace*, "}" ;
307pub fn option_map(input: ParseString) -> ParseResult<OptionMap> {
308  let msg = "Expects right bracket '}' to terminate map.";
309  let (input, (_, r)) = range(left_brace)(input)?;
310  let (input, _) = whitespace0(input)?;
311  let (input, elements) = many1(option_mapping)(input)?;
312  let (input, _) = whitespace0(input)?;
313  let (input, _) = label!(right_brace, msg, r)(input)?;
314  Ok((input, OptionMap{elements}))
315}
316
317// option-mapping :=  whitespace*, expression, whitespace*, ":", whitespace*, expression, comma?, whitespace* ;
318pub fn option_mapping(input: ParseString) -> ParseResult<(Identifier, MechString)> {
319  let msg1 = "Unexpected space before colon ':'";
320  let msg2 = "Expects a value";
321  let msg3 = "Expects whitespace or comma followed by whitespace";
322  let msg4 = "Expects whitespace";
323  let (input, _) = whitespace0(input)?;
324  let (input, key) = identifier(input)?;
325  let (input, _) = whitespace0(input)?;
326  let (input, _) = colon(input)?;
327  let (input, _) = whitespace0(input)?;
328  let (input, value) = option_value(input)?;
329  let (input, _) = whitespace0(input)?;
330  let (input, _) = opt(comma)(input)?;
331  let (input, _) = whitespace0(input)?;
332  Ok((input, (key, value)))
333}
334
335// option-value := string | identifier ;
336pub fn option_value(input: ParseString) -> ParseResult<MechString> {
337  if let Ok((input, value)) = string(input.clone()) {
338    return Ok((input, value));
339  }
340  let (input, mut identifier_value) = identifier(input)?;
341  identifier_value.name.kind = TokenKind::String;
342  Ok((input, MechString { text: identifier_value.name }))
343}
344
345// img := "![", *text, "]", "(", +text, ")" , ?option-map ;
346pub fn img(input: ParseString) -> ParseResult<Image> {
347  let (input, _) = img_prefix(input)?;
348  let (input, caption_text) = opt(inline_paragraph)(input)?;
349  let (input, _) = right_bracket(input)?;
350  let (input, _) = left_parenthesis(input)?;
351  let (input, src) = many1(tuple((is_not(right_parenthesis),text)))(input)?;
352  let (input, _) = right_parenthesis(input)?;
353  let (input, style) = opt(option_map)(input)?;
354  let merged_src = Token::merge_tokens(&mut src.into_iter().map(|(_,tkn)| tkn).collect::<Vec<Token>>()).unwrap();
355  Ok((input, Image{src: merged_src, caption: caption_text, style}))
356}
357
358pub fn figure_item(input: ParseString) -> ParseResult<FigureItem> {
359  let (input, image) = img(input)?;
360  let caption = image.caption.unwrap_or(Paragraph { elements: vec![], error_range: None });
361  Ok((input, FigureItem { src: image.src, caption }))
362}
363
364// figures-row := bar, +( *(space|tab), figure-item, *(space|tab), bar ), *whitespace ;
365pub fn figures_row(input: ParseString) -> ParseResult<Vec<FigureItem>> {
366  let (input, _) = whitespace0(input)?;
367  let (input, _) = bar(input)?;
368  let (input, cells) = many1(tuple((many0(space_tab), figure_item, many0(space_tab), bar)))(input)?;
369  let (input, _) = whitespace0(input)?;
370  let row = cells.into_iter().map(|(_, item, _, _)| item).collect();
371  Ok((input, row))
372}
373
374// figures := +figures-row ;
375pub fn figures(input: ParseString) -> ParseResult<FigureTable> {
376  let (input, rows) = many1(figures_row)(input)?;
377  Ok((input, FigureTable { rows }))
378}
379
380// paragraph-text := ¬(img-prefix | http-prefix | left-bracket | tilde | asterisk | underscore | grave | define-operator | bar), +text ;
381pub fn paragraph_text(input: ParseString) -> ParseResult<ParagraphElement> {
382  let (input, elements) = match many1(nom_tuple((is_not(alt((section_sigil, footnote_prefix, highlight_sigil, equation_sigil, img_prefix, http_prefix, left_brace, left_bracket, left_angle, right_bracket, tilde, asterisk, underscore, grave, define_operator, bar, mika_section_open, mika_section_close))),text)))(input) {
383    Ok((input, mut text)) => {
384      let mut text = text.into_iter().map(|(_,tkn)| tkn).collect();
385      let mut text = Token::merge_tokens(&mut text).unwrap();
386      text.kind = TokenKind::Text;
387      (input, ParagraphElement::Text(text))
388    }, 
389    Err(err) => {return Err(err);},
390  };
391  Ok((input, elements))
392}
393
394// eval-inline-mech-code := "{", ws0, expression, ws0, "}" ;`
395pub fn eval_inline_mech_code(input: ParseString) -> ParseResult<ParagraphElement> {
396  let (input, _) = left_brace(input)?;
397  let (input, _) = whitespace0(input)?;
398  let (input, expr) = expression(input)?;
399  let (input, _) = whitespace0(input)?;
400  let (input, _) = right_brace(input)?;
401  Ok((input, ParagraphElement::EvalInlineMechCode(expr)))
402}
403
404// inline-mech-code := "{{", ws0, expression, ws0, "}}" ;`
405pub fn inline_mech_code(input: ParseString) -> ParseResult<ParagraphElement> {
406  let (input, _) = left_brace(input)?;
407  let (input, _) = left_brace(input)?;
408  let (input, _) = whitespace0(input)?;
409  let (input, expr) = mech_code_alt(input)?;
410  let (input, _) = whitespace0(input)?;
411  let (input, _) = right_brace(input)?;
412  let (input, _) = right_brace(input)?;
413  Ok((input, ParagraphElement::InlineMechCode(expr)))
414}
415
416// footnote-reference := "[^", +text, "]" ;
417pub fn footnote_reference(input: ParseString) -> ParseResult<ParagraphElement> {
418  let (input, _) = footnote_prefix(input)?;
419  let (input, text) = many1(tuple((is_not(right_bracket),text)))(input)?;
420  let (input, _) = right_bracket(input)?;
421  let mut tokens = text.into_iter().map(|(_,tkn)| tkn).collect::<Vec<Token>>();
422  let footnote_text = Token::merge_tokens(&mut tokens).unwrap();
423  Ok((input, ParagraphElement::FootnoteReference(footnote_text)))
424}
425
426// reference := "[", +alphanumeric, "]" ;
427pub fn reference(input: ParseString) -> ParseResult<ParagraphElement> {
428  let (input, _) = left_bracket(input)?;
429  let (input, mut txt) = many1(alphanumeric)(input)?;
430  let (input, _) = right_bracket(input)?;
431  let ref_text = Token::merge_tokens(&mut txt).unwrap();
432  Ok((input, ParagraphElement::Reference(ref_text)))
433}
434
435// section_ref := "§" , +(alphanumeric | period) ;
436pub fn section_reference(input: ParseString) -> ParseResult<ParagraphElement> {
437  let (input, _) = section_sigil(input)?;
438  let (input, mut txt) = many1(alt((alphanumeric, period)))(input)?;
439  let section_text = Token::merge_tokens(&mut txt).unwrap();
440  Ok((input, ParagraphElement::SectionReference(section_text)))
441}
442
443// paragraph-element := hyperlink | reference | section-ref | raw-hyperlink | highlight | footnote-reference | inline-mech-code | eval-inline-mech-code | inline-equation | paragraph-text | strong | highlight | emphasis | inline-code | strikethrough | underline ;
444pub fn paragraph_element(input: ParseString) -> ParseResult<ParagraphElement> {
445  alt((hyperlink, reference, section_reference, raw_hyperlink, highlight, footnote_reference, inline_mech_code, eval_inline_mech_code, inline_equation, paragraph_text, strong, highlight, emphasis, inline_code, strikethrough, underline))(input)
446}
447
448// paragraph := +paragraph_element ;
449pub fn inline_paragraph(input: ParseString) -> ParseResult<Paragraph> {
450  let (input, _) = peek(paragraph_element)(input)?;
451  let (input, elements) = many1(
452    pair(
453      is_not(new_line),
454      paragraph_element
455    )
456  )(input)?;
457  let elements = elements.into_iter().map(|(_,elem)| elem).collect();
458  Ok((input, Paragraph{elements, error_range: None}))
459}
460
461// paragraph := +paragraph_element ;
462pub fn paragraph(input: ParseString) -> ParseResult<Paragraph> {
463  let (input, _) = peek(paragraph_element)(input)?;
464  let (input, elements) = many1(
465    pair(
466      is_not(alt((null(new_line), null(mika_section_close), null(idea_sigil)))),
467      labelr!(paragraph_element, 
468              |input| recover::<ParagraphElement, _>(input, skip_till_paragraph_element),
469              "Unexpected paragraph element")
470    )
471  )(input)?;
472  let elements = elements.into_iter().map(|(_,elem)| elem).collect();
473  Ok((input, Paragraph{elements, error_range: None}))
474}
475
476// paragraph-newline := +paragraph_element, new_line ;
477pub fn paragraph_newline(input: ParseString) -> ParseResult<Paragraph> {
478  let (input, elements) = paragraph(input)?;
479  let (input, _) = new_line(input)?;
480  Ok((input, elements))
481}
482
483// indented-ordered-list-item := ws, number, ".", +text, new_line*; 
484pub fn ordered_list_item(input: ParseString) -> ParseResult<(Number,Paragraph)> {
485  let (input, number) = number(input)?;
486  let (input, _) = period(input)?;
487  let (input, list_item) = labelr!(paragraph_newline, |input| recover::<Paragraph, _>(input, skip_till_eol), "Expects paragraph as list item")(input)?;
488  Ok((input, (number,list_item)))
489}
490
491// checked-item := "-", ("[", "x", "]"), paragraph ;
492pub fn checked_item(input: ParseString) -> ParseResult<(bool,Paragraph)> {
493  let (input, _) = dash(input)?;
494  let (input, _) = left_bracket(input)?;
495  let (input, _) = alt((tag("x"),tag("✓"),tag("✗")))(input)?;
496  let (input, _) = right_bracket(input)?;
497  let (input, list_item) = labelr!(paragraph_newline, |input| recover::<Paragraph, _>(input, skip_till_eol), "Expects paragraph as list item")(input)?;
498  Ok((input, (true,list_item)))
499}
500
501// unchecked-item := "-", ("[", whitespace0, "]"), paragraph ;
502pub fn unchecked_item(input: ParseString) -> ParseResult<(bool,Paragraph)> {
503  let (input, _) = dash(input)?;
504  let (input, _) = left_bracket(input)?;
505  let (input, _) = whitespace0(input)?;
506  let (input, _) = right_bracket(input)?;
507  let (input, list_item) = labelr!(paragraph_newline, |input| recover::<Paragraph, _>(input, skip_till_eol), "Expects paragraph as list item")(input)?;
508  Ok((input, (false,list_item)))
509}
510
511// check-list-item := checked-item | unchecked-item ;
512pub fn check_list_item(input: ParseString) -> ParseResult<(bool,Paragraph)> {
513  let (input, item) = alt((checked_item, unchecked_item))(input)?;
514  Ok((input, item))
515}
516
517pub fn check_list(mut input: ParseString, level: usize) -> ParseResult<MDList> {
518  let mut items = vec![];
519  loop {
520    // Calculate current line indent
521    let mut indent = 0;
522    let mut current = input.peek(indent);
523    while current == Some(" ") || current == Some("\t") {
524      indent += 1;
525      current = input.peek(indent);
526    }
527    // If indent is less than current level, we are done parsing this list level
528    if indent < level {
529      break;
530    }
531    // Consume whitespace
532    let (next_input, _) = many0(space_tab)(input.clone())?;
533    // Try parsing a checklist item
534    let (next_input, list_item) = match check_list_item(next_input.clone()) {
535      Ok((next_input, list_item)) => (next_input, list_item),
536      Err(err) => {
537        if !items.is_empty() {
538          break;
539        } else {
540          return Err(err);
541        }
542      }
543    };
544    // Look ahead to next line's indent
545    let mut lookahead_indent = 0;
546    let mut current = next_input.peek(lookahead_indent);
547    while current == Some(" ") || current == Some("\t") {
548      lookahead_indent += 1;
549      current = next_input.peek(lookahead_indent);
550    }
551    input = next_input;
552    if lookahead_indent < level {
553      // End of this list level
554      items.push((list_item, None));
555      break;
556    } else if lookahead_indent == level {
557      // Same level, continue
558      items.push((list_item, None));
559      continue;
560    } else {
561      // Nested sublist: parse recursively
562      let (next_input, sublist_md) = sublist(input.clone(), lookahead_indent)?;
563      items.push((list_item, Some(sublist_md)));
564      input = next_input;
565    }
566  }
567  Ok((input, MDList::Check(items)))
568}
569
570
571// unordered_list := +list_item, ?new_line, *whitespace ;
572pub fn unordered_list(mut input: ParseString, level: usize) -> ParseResult<MDList> {
573  let mut items = vec![];
574  loop {
575    let mut indent = 0;
576    let mut current = input.peek(indent);
577    while current == Some(" ") || current == Some("\t") {
578      indent += 1;
579      current = input.peek(indent);
580    }
581    // If indentation is less than the current level, return to parent list
582    if indent < level {
583      return Ok((input, MDList::Unordered(items)));
584    }
585    let (next_input, _) = many0(space_tab)(input.clone())?;
586    // Try to parse a list item
587    let (next_input, list_item) = match unordered_list_item(next_input.clone()) {
588      Ok((next_input, list_item)) => (next_input, list_item),
589      Err(err) => {
590        if !items.is_empty() {
591          return Ok((input, MDList::Unordered(items)));
592        } else {
593          return Err(err);
594        }
595      }
596    };
597    // Look ahead at the next line to determine indent
598    let mut lookahead_indent = 0;
599    let mut current = next_input.peek(lookahead_indent);
600    while current == Some(" ") || current == Some("\t") {
601      lookahead_indent += 1;
602      current = next_input.peek(lookahead_indent);
603    }
604    input = next_input;
605    if lookahead_indent < level {
606      // This is the last item at the current list level
607      items.push((list_item, None));
608      return Ok((input, MDList::Unordered(items)));
609    } else if lookahead_indent == level {
610      // Continue at the same level
611      items.push((list_item, None));
612      continue;
613    } else {
614      // Nested list detected
615      let (next_input, sub) = sublist(input.clone(), lookahead_indent)?;
616      items.push((list_item, Some(sub)));
617      input = next_input;
618    }
619  }
620}
621
622// ordered-list := +ordered-list-item, ?new-line, *whitespace ;
623pub fn ordered_list(mut input: ParseString, level: usize) -> ParseResult<MDList> {
624  let mut items = vec![];
625  loop {
626    let mut indent = 0;
627    let mut current = input.peek(indent);
628    while current == Some(" ") || current == Some("\t") {
629      indent += 1;
630      current = input.peek(indent);
631    }
632    // If indent drops below current level, return to parent
633    if indent < level {
634      let start = items.first()
635        .map(|item: &((Number, Paragraph), Option<MDList>)| item.0.0.clone())
636        .unwrap_or(Number::from_integer(1));
637      return Ok((input, MDList::Ordered(OrderedList { start, items })));
638    }
639    // Consume whitespace
640    let (next_input, _) = many0(space_tab)(input.clone())?;
641    // Try to parse an ordered list item
642    let (next_input, (list_item, _)) = match tuple((ordered_list_item, is_not(tuple((dash, dash)))))(next_input.clone()) {
643      Ok((next_input, res)) => (next_input, res),
644      Err(err) => {
645        if !items.is_empty() {
646          let start = items.first()
647            .map(|((number, _), _)| number.clone())
648            .unwrap_or(Number::from_integer(1));
649          return Ok((input, MDList::Ordered(OrderedList { start, items })));
650        } else {
651          return Err(err);
652        }
653      }
654    };
655
656    // Determine indentation of the next line
657    let mut lookahead_indent = 0;
658    let mut current = next_input.peek(lookahead_indent);
659    while current == Some(" ") || current == Some("\t") {
660      lookahead_indent += 1;
661      current = next_input.peek(lookahead_indent);
662    }
663
664    input = next_input;
665
666    if lookahead_indent < level {
667      items.push((list_item, None));
668      let start = items.first()
669        .map(|((number, _), _)| number.clone())
670        .unwrap_or(Number::from_integer(1));
671      return Ok((input, MDList::Ordered(OrderedList { start, items })));
672    } else if lookahead_indent == level {
673      items.push((list_item, None));
674      continue;
675    } else {
676      // Nested sublist
677      let (next_input, sub) = sublist(input.clone(), lookahead_indent)?;
678      items.push((list_item, Some(sub)));
679      input = next_input;
680    }
681  }
682}
683
684
685
686pub fn sublist(input: ParseString, level: usize) -> ParseResult<MDList> {
687  let (input, list) = match ordered_list(input.clone(), level) {
688    Ok((input, list)) => (input, list),
689    _ => match check_list(input.clone(), level) {
690      Ok((input, list)) => (input, list),
691      _ => match unordered_list(input.clone(), level) {
692        Ok((input, list)) => (input, list),
693        Err(err) => { return Err(err); }
694      }
695    }
696  };
697  Ok((input, list))
698}
699
700// mechdown-list := ordered-list | unordered-list ;
701pub fn mechdown_list(input: ParseString) -> ParseResult<MDList> {
702  let (input, list) = match ordered_list(input.clone(), 0) {
703    Ok((input, list)) => (input, list),
704    _ => match check_list(input.clone(), 0) {
705      Ok((input, list)) => (input, list),
706      _ => match unordered_list(input.clone(), 0) {
707        Ok((input, list)) => (input, list),
708        Err(err) => { return Err(err); }
709      }
710    }
711  };
712  Ok((input, list))
713}
714
715// list_item := dash, <space+>, <paragraph>, new_line* ;
716pub fn unordered_list_item(input: ParseString) -> ParseResult<(Option<Token>,Paragraph)> {
717  let msg1 = "Expects space after dash";
718  let msg2 = "Expects paragraph as list item";
719  let (input, _) = dash(input)?;
720  let (input, bullet) = opt(tuple((left_parenthesis, emoji, right_parenthesis)))(input)?;
721  let (input, _) = labelr!(null(many1(space)), skip_nil, msg1)(input)?;
722  let (input, list_item) = labelr!(paragraph_newline, |input| recover::<Paragraph, _>(input, skip_till_eol), msg2)(input)?;
723  let (input, _) = many0(new_line)(input)?;
724  let bullet = match bullet {
725    Some((_,b,_)) => Some(b),
726    None => None,
727  };
728  Ok((input,  (bullet, list_item)))
729}
730
731// codeblock-sigil := "```" | "~~~" ;
732pub fn codeblock_sigil(input: ParseString) -> ParseResult<fn(ParseString) -> ParseResult<Token>> {
733  let (input, sgl_tkn) = alt((grave_codeblock_sigil, tilde_codeblock_sigil))(input)?;
734  let sgl_cmb = match sgl_tkn.kind {
735    TokenKind::GraveCodeBlockSigil => grave_codeblock_sigil,
736    TokenKind::TildeCodeBlockSigil => tilde_codeblock_sigil,
737    _ => unreachable!(),
738  };
739  Ok((input, sgl_cmb))
740}
741
742//
743pub fn code_block(input: ParseString) -> ParseResult<SectionElement> {
744  let msg1 = "Expects 3 graves to start a code block";
745  let msg2 = "Expects new_line";
746  let msg3 = "Expects 3 graves followed by new_line to terminate a code block";
747  let (input, (end_sgl,r)) = range(codeblock_sigil)(input)?;
748  let (input, _) = many0(space_tab)(input)?;
749  let (input, code_id) = many0(tuple((is_not(left_brace),text)))(input)?;
750  let code_id = code_id.into_iter().map(|(_,tkn)| tkn).collect::<Vec<Token>>();
751  let (input, options) = opt(option_map)(input)?;
752  let (input, _) = many0(space_tab)(input)?;
753  let (input, _) = label!(new_line, msg2)(input)?;
754  let (input, (text,src_range)) = range(many0(nom_tuple((
755    is_not(end_sgl),
756    any,
757  ))))(input)?;
758  let (input, _) = end_sgl(input)?;
759  let (input, _) = whitespace0(input)?;
760  let block_src: Vec<char> = text.into_iter().flat_map(|(_, s)| s.chars().collect::<Vec<char>>()).collect();
761  let code_token = Token::new(TokenKind::CodeBlock, src_range, block_src.clone());
762
763  let code_id = code_id.iter().flat_map(|tkn| tkn.chars.clone().into_iter().collect::<Vec<char>>()).collect::<String>();
764  match code_id.as_str() {
765    "ebnf" => {
766      let ebnf_text = block_src.iter().collect::<String>();
767      match parse_grammar(&ebnf_text) {
768        Ok(grammar_tree) => {return Ok((input, SectionElement::Grammar(grammar_tree)));},
769        Err(err) => {
770          println!("Error parsing EBNF grammar: {:?}", err);
771          todo!();
772        }
773      }
774    }
775    tag => {
776      // if x begins with mec, mech, or 🤖
777      if tag.starts_with("mech") || tag.starts_with("mec") || tag.starts_with("🤖") {
778
779        // get rid of the prefix and then treat the rest of the string after : as an identifier
780        let rest = tag.trim_start_matches("mech").trim_start_matches("mec").trim_start_matches("🤖").trim_start_matches(":");
781        
782        let mut config = if rest == "" {BlockConfig { namespace_str: "".to_string(), namespace: 0, disabled: false, hidden: false, output: true}}
783        else if rest == "disabled" { BlockConfig { namespace_str: "".to_string(), namespace: 0, disabled: true, hidden: false, output: true} }
784        else if rest == "hidden" { BlockConfig { namespace_str: "".to_string(), namespace: 0, disabled: false, hidden: true, output: true} }
785        else { BlockConfig { namespace_str: rest.to_string(), namespace: hash_str(rest), disabled: false, hidden: false, output: true} };
786
787        if let Some(options_map) = &options {
788          for (key, value) in &options_map.elements {
789            if key.to_string() == "output" {
790              let output_value = value.to_string().trim().trim_matches('"').to_ascii_lowercase();
791              config.output = !matches!(output_value.as_str(), "false" | "no" | "off" | "0");
792            }
793          }
794        }
795
796        let mech_src = block_src.iter().collect::<String>();
797        let graphemes = graphemes::init_source(&mech_src);
798        let parse_string = ParseString::new(&graphemes);
799
800        match mech_code(parse_string) {
801          Ok((_, mech_tree)) => {
802            // TODO what if not all the input is parsed? Is that handled?
803            return Ok((input, SectionElement::FencedMechCode(FencedMechCode{code: mech_tree, config, options})));
804          },
805          Err(err) => {
806            return Err(nom::Err::Error(ParseError {
807                cause_range: SourceRange::default(),
808                remaining_input: input,
809                error_detail: ParseErrorDetail {
810                    message: "Generic error parsing Mech code block",
811                    annotation_rngs: Vec::new(),
812                },
813            }));
814          }
815        };
816      } else if tag.starts_with("equation") || tag.starts_with("eq") || tag.starts_with("math") || tag.starts_with("latex") || tag.starts_with("tex") {
817          return Ok((input, SectionElement::Equation(code_token)));
818      } else if tag.starts_with("diagram") || tag.starts_with("chart") || tag.starts_with("mermaid") {
819          return Ok((input, SectionElement::Diagram(code_token)));          
820      } else {
821        // Some other code block, just keep moving although we might want to do something with it later
822      }
823    }
824  } 
825  Ok((input, SectionElement::CodeBlock(code_token)))
826}
827
828pub fn thematic_break(input: ParseString) -> ParseResult<SectionElement> {
829  let (input, _) = many1(asterisk)(input)?;
830  let (input, _) = many0(space_tab)(input)?;
831  let (input, _) = new_line(input)?;
832  Ok((input, SectionElement::ThematicBreak))
833}
834
835// footnote := "[^", +text, "]", ":", ws0, paragraph ;
836pub fn footnote(input: ParseString) -> ParseResult<Footnote> {
837  let (input, _) = footnote_prefix(input)?;
838  let (input, text) = many1(tuple((is_not(right_bracket),text)))(input)?;
839  let (input, _) = right_bracket(input)?;
840  let (input, _) = colon(input)?;
841  let (input, _) = whitespace0(input)?;
842  let (input, paragraph) = many1(paragraph_newline)(input)?;
843  let mut tokens = text.into_iter().map(|(_,tkn)| tkn).collect::<Vec<Token>>();
844  let footnote_text = Token::merge_tokens(&mut tokens).unwrap();
845  let footnote = (footnote_text, paragraph);
846  Ok((input, footnote))
847}
848
849// prompt := prompt-sigil, *space, +paragraph ;
850pub fn prompt(input: ParseString) -> ParseResult<SectionElement> {
851  let (input, _) = prompt_sigil(input)?;
852  let (input, _) = many0(space_tab)(input)?;
853  let (input, element) = section_element(input)?;
854  Ok((input, SectionElement::Prompt(Box::new(element))))
855}
856
857pub fn blank_line(input: ParseString) -> ParseResult<Vec<Token>> {
858  let (input, mut st) = many0(space_tab)(input)?;
859  let (input, n) = new_line(input)?;
860  st.push(n);
861  Ok((input, st))
862}
863
864// question-block := question-sigil, *space, +paragraph ;
865pub fn question_block(input: ParseString) -> ParseResult<SectionElement> {
866  let (input, _) = question_sigil(input)?;
867  let (input, _) = many0(space_tab)(input)?;
868  let (input, paragraphs) = many1(paragraph_newline)(input)?;
869  Ok((input, SectionElement::QuestionBlock(paragraphs)))
870}
871
872// info-block := info-sigil, *space, +paragraph ;
873pub fn info_block(input: ParseString) -> ParseResult<SectionElement> {
874  let (input, _) = info_sigil(input)?;
875  let (input, _) = many0(space_tab)(input)?;
876  let (input, paragraphs) = many1(paragraph_newline)(input)?;
877  Ok((input, SectionElement::InfoBlock(paragraphs)))
878}
879
880// quote-block := quote-sigil, *space, +paragraph ;
881pub fn quote_block(input: ParseString) -> ParseResult<SectionElement> {
882  let (input, _) = peek(is_not(float_sigil))(input)?;
883  let (input, _) = peek(is_not(prompt_sigil))(input)?;
884  let (input, _) = quote_sigil(input)?;
885  let (input, _) = many0(space_tab)(input)?;
886  let (input, paragraphs) = many1(paragraph_newline)(input)?;
887  Ok((input, SectionElement::QuoteBlock(paragraphs)))
888}
889
890// warning-block := warning-sigil, *space, +paragraph ;
891pub fn warning_block(input: ParseString) -> ParseResult<SectionElement> {
892  let (input, _) = peek(is_not(float_sigil))(input)?;
893  let (input, _) = warning_sigil(input)?;
894  let (input, _) = many0(space_tab)(input)?;
895  let (input, paragraphs) = many1(paragraph_newline)(input)?;
896  Ok((input, SectionElement::WarningBlock(paragraphs)))
897}
898
899// success-block := success-sigil, *space, +paragraph ;
900pub fn success_block(input: ParseString) -> ParseResult<SectionElement> {
901  let (input, _) = peek(is_not(float_sigil))(input)?;
902  let (input, _) = alt((success_sigil, success_check_sigil))(input)?;
903  let (input, _) = many0(space_tab)(input)?;
904  let (input, paragraphs) = many1(paragraph_newline)(input)?;
905  Ok((input, SectionElement::SuccessBlock(paragraphs)))
906}
907
908// error-block := error-sigil, *space, +paragraph ;
909pub fn error_block(input: ParseString) -> ParseResult<SectionElement> {
910  let (input, _) = peek(is_not(float_sigil))(input)?;
911  let (input, _) = alt((error_sigil, error_alt_sigil))(input)?;
912  let (input, _) = many0(space_tab)(input)?;
913  let (input, paragraphs) = many1(paragraph_newline)(input)?;
914  Ok((input, SectionElement::ErrorBlock(paragraphs)))
915}
916
917// idea-block := idea-sigil, *space, +paragraph ;
918pub fn idea_block(input: ParseString) -> ParseResult<SectionElement> {
919  let (input, _) = idea_sigil(input)?;
920  let (input, _) = many0(space_tab)(input)?;
921  let (input, paragraphs) = many1(paragraph_newline)(input)?;
922  Ok((input, SectionElement::IdeaBlock(paragraphs)))
923}
924
925// abstract-element := abstract-sigil, *space, +paragraph ;
926pub fn abstract_el(input: ParseString) -> ParseResult<SectionElement> {
927  let (input, _) = abstract_sigil(input)?;
928  let (input, _) = many0(space_tab)(input)?;
929  let (input, paragraphs) = many1(paragraph_newline)(input)?;
930  Ok((input, SectionElement::Abstract(paragraphs)))
931}
932
933// equation := "$$" , +text ;
934pub fn equation(input: ParseString) -> ParseResult<Token> {
935  let (input, _) = equation_sigil(input)?;
936  let (input, mut txt) = many1(alt((backslash,text)))(input)?;
937  let mut eqn = Token::merge_tokens(&mut txt).unwrap();
938  Ok((input, eqn))
939}
940
941// citation := "[", (identifier | number), "]", ":", ws0, paragraph, ws0, ?("(", +text, ")") ;
942pub fn citation(input: ParseString) -> ParseResult<Citation> {
943  let (input, _) = left_bracket(input)?;
944  let (input, mut id) = many1(alphanumeric)(input)?;
945  let (input, _) = right_bracket(input)?;
946  let (input, _) = colon(input)?;
947  let (input, _) = whitespace0(input)?;
948  let (input, txt) = paragraph(input)?;
949  let (input, _) = whitespace0(input)?;
950  let id = Token::merge_tokens(&mut id).unwrap();
951  Ok((input, Citation{id, text: txt}))
952}
953
954// float-sigil := ">>" | "<<" ;
955pub fn float_sigil(input: ParseString) -> ParseResult<FloatDirection> {
956  let (input, d) = alt((float_left, float_right))(input)?;
957  let d = match d.kind {
958    TokenKind::FloatLeft => FloatDirection::Left,
959    TokenKind::FloatRight => FloatDirection::Right,
960    _ => unreachable!(),
961  };
962  Ok((input, d))
963}
964
965// float := float-sigil, section-element ;
966pub fn float(input: ParseString) -> ParseResult<(Box<SectionElement>,FloatDirection)> {
967  let (input, direction) = float_sigil(input)?;
968  let (input, _) = many0(space_tab)(input)?;
969  let (input, el) = section_element(input)?;
970  Ok((input, (Box::new(el), direction)))
971}
972
973// float := float-sigil, section-element ;
974pub fn not_mech_code(input: ParseString) -> ParseResult<()> {
975  let (input, _) = alt((null(question_block), 
976    null(info_block),  
977    null(success_block),
978    null(warning_block),
979    null(error_block),
980    null(idea_block),
981    null(img), 
982    null(mika_section_close),
983    null(float)))(input)?;
984  Ok((input, ()))
985}
986
987// section-element := mech-code | question-block | info-block | list | footnote | citation | abstract-element | img | figures | equation | table | float | quote-block | code-block | thematic-break | subtitle | paragraph ;
988pub fn section_element(input: ParseString) -> ParseResult<SectionElement> {
989  let parsers: Vec<(&'static str, Box<dyn Fn(ParseString) -> ParseResult<SectionElement>>)> = vec![
990    ("list",            Box::new(|i| mechdown_list(i).map(|(i, lst)| (i, SectionElement::List(lst))))),
991    ("prompt",          Box::new(prompt)),
992    ("footnote",        Box::new(|i| footnote(i).map(|(i, f)| (i, SectionElement::Footnote(f))))),
993    ("citation",        Box::new(|i| citation(i).map(|(i, c)| (i, SectionElement::Citation(c))))),
994    ("abstract",        Box::new(abstract_el)),
995    ("img",             Box::new(|i| img(i).map(|(i, img)| (i, SectionElement::Image(img))))),
996    ("figures",         Box::new(|i| figures(i).map(|(i, f)| (i, SectionElement::FigureTable(f))))),
997    ("equation",        Box::new(|i| equation(i).map(|(i, e)| (i, SectionElement::Equation(e))))),
998    ("table",           Box::new(|i| mechdown_table(i).map(|(i, t)| (i, SectionElement::Table(t))))),
999    ("float",           Box::new(|i| float(i).map(|(i, f)| (i, SectionElement::Float(f))))),
1000    ("quote_block",     Box::new(quote_block)),
1001    ("code_block",      Box::new(code_block)),
1002    ("thematic_break",  Box::new(|i| thematic_break(i).map(|(i, _)| (i, SectionElement::ThematicBreak)))),
1003    ("subtitle",        Box::new(|i| subtitle(i).map(|(i, s)| (i, SectionElement::Subtitle(s))))),
1004    ("question_block",  Box::new(question_block)),
1005    ("info_block",      Box::new(info_block)),
1006    ("success_block",   Box::new(success_block)),
1007    ("warning_block",   Box::new(warning_block)),
1008    ("error_block",     Box::new(error_block)),
1009    ("idea_block",      Box::new(idea_block)),
1010    ("paragraph",       Box::new(|i| paragraph(i).map(|(i, p)| (i, SectionElement::Paragraph(p))))),
1011  ];
1012
1013  alt_best(input, &parsers)
1014  
1015}
1016
1017// section := ?ul-subtitle, +section-element ;
1018pub fn section(input: ParseString) -> ParseResult<Section> {
1019  let (input, subtitle) = opt(ul_subtitle)(input)?;
1020
1021  let mut elements = vec![];
1022
1023  let mut new_input = input.clone();
1024
1025  loop {
1026    // Stop if EOF reached
1027    if new_input.cursor >= new_input.graphemes.len() {
1028      //println!("EOF reached while parsing section");
1029      break;
1030    }
1031
1032    // Stop if the next thing is a new section (peek, do not consume)
1033    if ul_subtitle(new_input.clone()).is_ok() {
1034      //println!("Next section detected, ending current section");
1035      break;
1036    }
1037
1038    #[cfg(feature = "mika")]
1039    if mika_section_close(new_input.clone()).is_ok() {
1040      break;
1041    }
1042
1043    /*let (input, sct_elmnt) = labelr!(
1044      section_element,
1045      |input| recover::<SectionElement, _>(input, skip_till_eol),
1046      "Expected a section element."
1047    )(input.clone())?;*/
1048
1049    //elements.push(sct_elmnt);
1050    //let (input, _) = many0(blank_line)(input.clone())?;
1051
1052    #[cfg(feature = "mika")]
1053    match mika(new_input.clone()) {
1054      Ok((input, mika)) => {
1055        elements.push(SectionElement::Mika(mika));
1056        new_input = input;
1057        continue;
1058      }
1059      Err(e) => {
1060        // not mika code, try mech code
1061        //return Err(e);
1062      }
1063    }
1064  
1065    // check if it's mech_code first, we'll prioritize that
1066    match mech_code(new_input.clone()) {
1067      Ok((input, mech_tree)) => {
1068        elements.push(SectionElement::MechCode(mech_tree));
1069        new_input = input;
1070        continue;
1071      }
1072      Err(e) => {
1073        // not mech code, try section_element
1074        //return Err(e);
1075      }
1076    }
1077
1078    match section_element(new_input.clone()) {
1079      Ok((input, element)) => {
1080
1081        elements.push(element);
1082
1083        // Skip any blank lines after the element
1084        let (input, _) = many0(blank_line)(input.clone())?;
1085        new_input = input;
1086      }
1087      Err(err) => {
1088        // Propagate hard errors
1089        return Err(err);
1090      }
1091    }
1092  }
1093  Ok((new_input, Section { subtitle, elements }))
1094}
1095
1096// body := whitespace0, +(section, eof), eof ;
1097pub fn body(input: ParseString) -> ParseResult<Body> {
1098  let (mut input, _) = whitespace0(input)?;
1099  let mut sections = vec![];
1100  let mut new_input = input.clone();
1101  loop {
1102    if new_input.cursor >= new_input.graphemes.len() {
1103      break;
1104    }
1105    // Try parsing a section
1106    match section(new_input.clone()) {
1107      Ok((input, sect)) => {
1108        //println!("Parsed section: {:#?}", sect);
1109        sections.push(sect);
1110        new_input = input;
1111      }
1112      Err(err) => {
1113        return Err(err);
1114      }
1115    }
1116  }
1117  Ok((new_input, Body { sections }))
1118}