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