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