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, _) = 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, _) = peek(is_not(alt((error_sigil, info_sigil))))(input)?;
139  let (input, _) = many0(space_tab)(input)?;
140  let (input, _) = left_parenthesis(input)?;
141  let (input, num) = separated_list1(period,alt((many1(alpha),many1(digit))))(input)?;
142  let (input, _) = right_parenthesis(input)?;
143  let (input, _) = many0(space_tab)(input)?;
144  let (input, text) = paragraph_newline(input)?;
145  let (input, _) = many0(space_tab)(input)?;
146  let (input, _) = whitespace0(input)?;
147  let level: u8 = if num.len() < 3 { 3 } else { num.len() as u8 + 1 };
148  Ok((input, Subtitle{text, level}))
149}
150
151// strong := (asterisk, asterisk), +paragraph-element, (asterisk, asterisk) ;
152pub fn strong(input: ParseString) -> ParseResult<ParagraphElement> {
153  let (input, _) = tuple((asterisk,asterisk))(input)?;
154  let (input, text) = paragraph_element(input)?;
155  let (input, _) = tuple((asterisk,asterisk))(input)?;
156  Ok((input, ParagraphElement::Strong(Box::new(text))))
157}
158
159/// emphasis := asterisk, +paragraph-element, asterisk ;
160pub fn emphasis(input: ParseString) -> ParseResult<ParagraphElement> {
161  let (input, _) = asterisk(input)?;
162  let (input, text) = paragraph_element(input)?;
163  let (input, _) = asterisk(input)?;
164  Ok((input, ParagraphElement::Emphasis(Box::new(text))))
165}
166
167// strikethrough := tilde, +paragraph-element, tilde ;
168pub fn strikethrough(input: ParseString) -> ParseResult<ParagraphElement> {
169  let (input, _) = tilde(input)?;
170  let (input, text) = paragraph_element(input)?;
171  let (input, _) = tilde(input)?;
172  Ok((input, ParagraphElement::Strikethrough(Box::new(text))))
173}
174
175/// underline := underscore, +paragraph-element, underscore ;
176pub fn underline(input: ParseString) -> ParseResult<ParagraphElement> {
177  let (input, _) = underscore(input)?;
178  let (input, text) = paragraph_element(input)?;
179  let (input, _) = underscore(input)?;
180  Ok((input, ParagraphElement::Underline(Box::new(text))))
181}
182
183/// highlight := "!!", +paragraph-element, "!!" ;
184pub fn highlight(input: ParseString) -> ParseResult<ParagraphElement> {
185  let (input, _) = highlight_sigil(input)?;
186  let (input, text) = paragraph_element(input)?;
187  let (input, _) = highlight_sigil(input)?;
188  Ok((input, ParagraphElement::Highlight(Box::new(text))))
189}
190
191// inline-code := grave, +text, grave ; 
192pub fn inline_code(input: ParseString) -> ParseResult<ParagraphElement> {
193  let (input, _) = is_not(grave_codeblock_sigil)(input)?; // prevent matching code fences
194  let (input, _) = grave(input)?;
195  let (input, text) = many0(tuple((is_not(grave),text)))(input)?;
196  let (input, _) = grave(input)?;
197  let mut text = text.into_iter().map(|(_,tkn)| tkn).collect();
198  // return empty token if there's nothing between the graves
199  let mut text = match Token::merge_tokens(&mut text) {
200    Some(t) => t,
201    None => {
202      return Ok((input, ParagraphElement::InlineCode(Token::default())));
203    }
204  };
205  text.kind = TokenKind::Text;
206  Ok((input, ParagraphElement::InlineCode(text)))
207}
208
209// inline-equation := equation-sigil, +text, equation-sigil ;
210pub fn inline_equation(input: ParseString) -> ParseResult<ParagraphElement> {
211  let (input, _) = equation_sigil(input)?;
212  let (input, txt) = many0(tuple((is_not(equation_sigil),alt((backslash,text)))))(input)?;
213  let (input, _) = equation_sigil(input)?;
214  let mut txt = txt.into_iter().map(|(_,tkn)| tkn).collect();
215  let mut eqn = Token::merge_tokens(&mut txt).unwrap();
216  eqn.kind = TokenKind::Text;
217  Ok((input, ParagraphElement::InlineEquation(eqn)))
218}
219
220// hyperlink := "[", +text, "]", "(", +text, ")" ;
221pub fn hyperlink(input: ParseString) -> ParseResult<ParagraphElement> {
222  let (input, _) = left_bracket(input)?;
223  let (input, link_text) = inline_paragraph(input)?;
224  let (input, _) = right_bracket(input)?;
225  let (input, _) = left_parenthesis(input)?;
226  let (input, link) = many1(tuple((is_not(right_parenthesis),text)))(input)?;
227  let (input, _) = right_parenthesis(input)?;
228  let mut tokens = link.into_iter().map(|(_,tkn)| tkn).collect::<Vec<Token>>();
229  let link_merged = Token::merge_tokens(&mut tokens).unwrap();
230  Ok((input, ParagraphElement::Hyperlink((link_text, link_merged))))
231}
232
233// raw-hyperlink := http-prefix, +text ;
234pub fn raw_hyperlink(input: ParseString) -> ParseResult<ParagraphElement> {
235  let (input, _) = peek(http_prefix)(input)?;
236  let (input, address) = many1(tuple((is_not(space), text)))(input)?;
237  let mut tokens = address.into_iter().map(|(_,tkn)| tkn).collect::<Vec<Token>>();
238  let url_token = Token::merge_tokens(&mut tokens).unwrap();
239  let url_paragraph = Paragraph::from_tokens(vec![url_token.clone()]);
240  Ok((input, ParagraphElement::Hyperlink((url_paragraph, url_token))))
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 := "![", *text, "]", "(", +text, ")" , ?option-map ;
273pub fn img(input: ParseString) -> ParseResult<Image> {
274  let (input, _) = img_prefix(input)?;
275  let (input, caption_text) = opt(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: 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, mika_section_open, mika_section_close))),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  println!("Parsing paragraph element at: {:?}", input.peek(0));
351  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)
352}
353
354// paragraph := +paragraph_element ;
355pub fn inline_paragraph(input: ParseString) -> ParseResult<Paragraph> {
356  let (input, _) = peek(paragraph_element)(input)?;
357  let (input, elements) = many1(
358    pair(
359      is_not(new_line),
360      paragraph_element
361    )
362  )(input)?;
363  let elements = elements.into_iter().map(|(_,elem)| elem).collect();
364  Ok((input, Paragraph{elements, error_range: None}))
365}
366
367// paragraph := +paragraph_element ;
368pub fn paragraph(input: ParseString) -> ParseResult<Paragraph> {
369  let (input, _) = peek(paragraph_element)(input)?;
370  let (input, elements) = many1(
371    pair(
372      is_not(alt((null(new_line), null(mika_section_close), null(idea_sigil)))),
373      labelr!(paragraph_element, 
374              |input| recover::<ParagraphElement, _>(input, skip_till_paragraph_element),
375              "Unexpected paragraph element")
376    )
377  )(input)?;
378  let elements = elements.into_iter().map(|(_,elem)| elem).collect();
379  Ok((input, Paragraph{elements, error_range: None}))
380}
381
382// paragraph-newline := +paragraph_element, new_line ;
383pub fn paragraph_newline(input: ParseString) -> ParseResult<Paragraph> {
384  let (input, elements) = paragraph(input)?;
385  let (input, _) = new_line(input)?;
386  Ok((input, elements))
387}
388
389// indented-ordered-list-item := ws, number, ".", +text, new_line*; 
390pub fn ordered_list_item(input: ParseString) -> ParseResult<(Number,Paragraph)> {
391  let (input, number) = number(input)?;
392  let (input, _) = period(input)?;
393  let (input, list_item) = labelr!(paragraph_newline, |input| recover::<Paragraph, _>(input, skip_till_eol), "Expects paragraph as list item")(input)?;
394  Ok((input, (number,list_item)))
395}
396
397// checked-item := "-", ("[", "x", "]"), paragraph ;
398pub fn checked_item(input: ParseString) -> ParseResult<(bool,Paragraph)> {
399  let (input, _) = dash(input)?;
400  let (input, _) = left_bracket(input)?;
401  let (input, _) = alt((tag("x"),tag("✓"),tag("✗")))(input)?;
402  let (input, _) = right_bracket(input)?;
403  let (input, list_item) = labelr!(paragraph_newline, |input| recover::<Paragraph, _>(input, skip_till_eol), "Expects paragraph as list item")(input)?;
404  Ok((input, (true,list_item)))
405}
406
407// unchecked-item := "-", ("[", whitespace0, "]"), paragraph ;
408pub fn unchecked_item(input: ParseString) -> ParseResult<(bool,Paragraph)> {
409  let (input, _) = dash(input)?;
410  let (input, _) = left_bracket(input)?;
411  let (input, _) = whitespace0(input)?;
412  let (input, _) = right_bracket(input)?;
413  let (input, list_item) = labelr!(paragraph_newline, |input| recover::<Paragraph, _>(input, skip_till_eol), "Expects paragraph as list item")(input)?;
414  Ok((input, (false,list_item)))
415}
416
417// check-list-item := checked-item | unchecked-item ;
418pub fn check_list_item(input: ParseString) -> ParseResult<(bool,Paragraph)> {
419  let (input, item) = alt((checked_item, unchecked_item))(input)?;
420  Ok((input, item))
421}
422
423pub fn check_list(mut input: ParseString, level: usize) -> ParseResult<MDList> {
424  let mut items = vec![];
425  loop {
426    // Calculate current line indent
427    let mut indent = 0;
428    let mut current = input.peek(indent);
429    while current == Some(" ") || current == Some("\t") {
430      indent += 1;
431      current = input.peek(indent);
432    }
433    // If indent is less than current level, we are done parsing this list level
434    if indent < level {
435      break;
436    }
437    // Consume whitespace
438    let (next_input, _) = many0(space_tab)(input.clone())?;
439    // Try parsing a checklist item
440    let (next_input, list_item) = match check_list_item(next_input.clone()) {
441      Ok((next_input, list_item)) => (next_input, list_item),
442      Err(err) => {
443        if !items.is_empty() {
444          break;
445        } else {
446          return Err(err);
447        }
448      }
449    };
450    // Look ahead to next line's indent
451    let mut lookahead_indent = 0;
452    let mut current = next_input.peek(lookahead_indent);
453    while current == Some(" ") || current == Some("\t") {
454      lookahead_indent += 1;
455      current = next_input.peek(lookahead_indent);
456    }
457    input = next_input;
458    if lookahead_indent < level {
459      // End of this list level
460      items.push((list_item, None));
461      break;
462    } else if lookahead_indent == level {
463      // Same level, continue
464      items.push((list_item, None));
465      continue;
466    } else {
467      // Nested sublist: parse recursively
468      let (next_input, sublist_md) = sublist(input.clone(), lookahead_indent)?;
469      items.push((list_item, Some(sublist_md)));
470      input = next_input;
471    }
472  }
473  Ok((input, MDList::Check(items)))
474}
475
476
477// unordered_list := +list_item, ?new_line, *whitespace ;
478pub fn unordered_list(mut input: ParseString, level: usize) -> ParseResult<MDList> {
479  let mut items = vec![];
480  loop {
481    let mut indent = 0;
482    let mut current = input.peek(indent);
483    while current == Some(" ") || current == Some("\t") {
484      indent += 1;
485      current = input.peek(indent);
486    }
487    // If indentation is less than the current level, return to parent list
488    if indent < level {
489      return Ok((input, MDList::Unordered(items)));
490    }
491    let (next_input, _) = many0(space_tab)(input.clone())?;
492    // Try to parse a list item
493    let (next_input, list_item) = match unordered_list_item(next_input.clone()) {
494      Ok((next_input, list_item)) => (next_input, list_item),
495      Err(err) => {
496        if !items.is_empty() {
497          return Ok((input, MDList::Unordered(items)));
498        } else {
499          return Err(err);
500        }
501      }
502    };
503    // Look ahead at the next line to determine indent
504    let mut lookahead_indent = 0;
505    let mut current = next_input.peek(lookahead_indent);
506    while current == Some(" ") || current == Some("\t") {
507      lookahead_indent += 1;
508      current = next_input.peek(lookahead_indent);
509    }
510    input = next_input;
511    if lookahead_indent < level {
512      // This is the last item at the current list level
513      items.push((list_item, None));
514      return Ok((input, MDList::Unordered(items)));
515    } else if lookahead_indent == level {
516      // Continue at the same level
517      items.push((list_item, None));
518      continue;
519    } else {
520      // Nested list detected
521      let (next_input, sub) = sublist(input.clone(), lookahead_indent)?;
522      items.push((list_item, Some(sub)));
523      input = next_input;
524    }
525  }
526}
527
528// ordered-list := +ordered-list-item, ?new-line, *whitespace ;
529pub fn ordered_list(mut input: ParseString, level: usize) -> ParseResult<MDList> {
530  let mut items = vec![];
531  loop {
532    let mut indent = 0;
533    let mut current = input.peek(indent);
534    while current == Some(" ") || current == Some("\t") {
535      indent += 1;
536      current = input.peek(indent);
537    }
538    // If indent drops below current level, return to parent
539    if indent < level {
540      let start = items.first()
541        .map(|item: &((Number, Paragraph), Option<MDList>)| item.0.0.clone())
542        .unwrap_or(Number::from_integer(1));
543      return Ok((input, MDList::Ordered(OrderedList { start, items })));
544    }
545    // Consume whitespace
546    let (next_input, _) = many0(space_tab)(input.clone())?;
547    // Try to parse an ordered list item
548    let (next_input, (list_item, _)) = match tuple((ordered_list_item, is_not(tuple((dash, dash)))))(next_input.clone()) {
549      Ok((next_input, res)) => (next_input, res),
550      Err(err) => {
551        if !items.is_empty() {
552          let start = items.first()
553            .map(|((number, _), _)| number.clone())
554            .unwrap_or(Number::from_integer(1));
555          return Ok((input, MDList::Ordered(OrderedList { start, items })));
556        } else {
557          return Err(err);
558        }
559      }
560    };
561
562    // Determine indentation of the next line
563    let mut lookahead_indent = 0;
564    let mut current = next_input.peek(lookahead_indent);
565    while current == Some(" ") || current == Some("\t") {
566      lookahead_indent += 1;
567      current = next_input.peek(lookahead_indent);
568    }
569
570    input = next_input;
571
572    if lookahead_indent < level {
573      items.push((list_item, None));
574      let start = items.first()
575        .map(|((number, _), _)| number.clone())
576        .unwrap_or(Number::from_integer(1));
577      return Ok((input, MDList::Ordered(OrderedList { start, items })));
578    } else if lookahead_indent == level {
579      items.push((list_item, None));
580      continue;
581    } else {
582      // Nested sublist
583      let (next_input, sub) = sublist(input.clone(), lookahead_indent)?;
584      items.push((list_item, Some(sub)));
585      input = next_input;
586    }
587  }
588}
589
590
591
592pub fn sublist(input: ParseString, level: usize) -> ParseResult<MDList> {
593  let (input, list) = match ordered_list(input.clone(), level) {
594    Ok((input, list)) => (input, list),
595    _ => match check_list(input.clone(), level) {
596      Ok((input, list)) => (input, list),
597      _ => match unordered_list(input.clone(), level) {
598        Ok((input, list)) => (input, list),
599        Err(err) => { return Err(err); }
600      }
601    }
602  };
603  Ok((input, list))
604}
605
606// mechdown-list := ordered-list | unordered-list ;
607pub fn mechdown_list(input: ParseString) -> ParseResult<MDList> {
608  let (input, list) = match ordered_list(input.clone(), 0) {
609    Ok((input, list)) => (input, list),
610    _ => match check_list(input.clone(), 0) {
611      Ok((input, list)) => (input, list),
612      _ => match unordered_list(input.clone(), 0) {
613        Ok((input, list)) => (input, list),
614        Err(err) => { return Err(err); }
615      }
616    }
617  };
618  Ok((input, list))
619}
620
621// list_item := dash, <space+>, <paragraph>, new_line* ;
622pub fn unordered_list_item(input: ParseString) -> ParseResult<(Option<Token>,Paragraph)> {
623  let msg1 = "Expects space after dash";
624  let msg2 = "Expects paragraph as list item";
625  let (input, _) = dash(input)?;
626  let (input, bullet) = opt(tuple((left_parenthesis, emoji, right_parenthesis)))(input)?;
627  let (input, _) = labelr!(null(many1(space)), skip_nil, msg1)(input)?;
628  let (input, list_item) = labelr!(paragraph_newline, |input| recover::<Paragraph, _>(input, skip_till_eol), msg2)(input)?;
629  let (input, _) = many0(new_line)(input)?;
630  let bullet = match bullet {
631    Some((_,b,_)) => Some(b),
632    None => None,
633  };
634  Ok((input,  (bullet, list_item)))
635}
636
637// codeblock-sigil := "```" | "~~~" ;
638pub fn codeblock_sigil(input: ParseString) -> ParseResult<fn(ParseString) -> ParseResult<Token>> {
639  let (input, sgl_tkn) = alt((grave_codeblock_sigil, tilde_codeblock_sigil))(input)?;
640  let sgl_cmb = match sgl_tkn.kind {
641    TokenKind::GraveCodeBlockSigil => grave_codeblock_sigil,
642    TokenKind::TildeCodeBlockSigil => tilde_codeblock_sigil,
643    _ => unreachable!(),
644  };
645  Ok((input, sgl_cmb))
646}
647
648//
649pub fn code_block(input: ParseString) -> ParseResult<SectionElement> {
650  let msg1 = "Expects 3 graves to start a code block";
651  let msg2 = "Expects new_line";
652  let msg3 = "Expects 3 graves followed by new_line to terminate a code block";
653  let (input, (end_sgl,r)) = range(codeblock_sigil)(input)?;
654  let (input, _) = many0(space_tab)(input)?;
655  let (input, code_id) = many0(tuple((is_not(left_brace),text)))(input)?;
656  let code_id = code_id.into_iter().map(|(_,tkn)| tkn).collect::<Vec<Token>>();
657  let (input, options) = opt(option_map)(input)?;
658  let (input, _) = many0(space_tab)(input)?;
659  let (input, _) = label!(new_line, msg2)(input)?;
660  let (input, (text,src_range)) = range(many0(nom_tuple((
661    is_not(end_sgl),
662    any,
663  ))))(input)?;
664  let (input, _) = end_sgl(input)?;
665  let (input, _) = whitespace0(input)?;
666  let block_src: Vec<char> = text.into_iter().flat_map(|(_, s)| s.chars().collect::<Vec<char>>()).collect();
667  let code_token = Token::new(TokenKind::CodeBlock, src_range, block_src.clone());
668
669  let code_id = code_id.iter().flat_map(|tkn| tkn.chars.clone().into_iter().collect::<Vec<char>>()).collect::<String>();
670  match code_id.as_str() {
671    "ebnf" => {
672      let ebnf_text = block_src.iter().collect::<String>();
673      match parse_grammar(&ebnf_text) {
674        Ok(grammar_tree) => {return Ok((input, SectionElement::Grammar(grammar_tree)));},
675        Err(err) => {
676          println!("Error parsing EBNF grammar: {:?}", err);
677          todo!();
678        }
679      }
680    }
681    tag => {
682      // if x begins with mec, mech, or 🤖
683      if tag.starts_with("mech") || tag.starts_with("mec") || tag.starts_with("🤖") {
684
685        // get rid of the prefix and then treat the rest of the string after : as an identifier
686        let rest = tag.trim_start_matches("mech").trim_start_matches("mec").trim_start_matches("🤖").trim_start_matches(":");
687        
688        let config = if rest == "" {BlockConfig { namespace_str: "".to_string(), namespace: 0, disabled: false, hidden: false}}
689        else if rest == "disabled" { BlockConfig { namespace_str: "".to_string(), namespace: 0, disabled: true, hidden: false} }
690        else if rest == "hidden" { BlockConfig { namespace_str: "".to_string(), namespace: 0, disabled: false, hidden: true} }
691        else { BlockConfig { namespace_str: rest.to_string(), namespace: hash_str(rest), disabled: false, hidden: false} };
692
693        let mech_src = block_src.iter().collect::<String>();
694        let graphemes = graphemes::init_source(&mech_src);
695        let parse_string = ParseString::new(&graphemes);
696
697        match mech_code(parse_string) {
698          Ok((_, mech_tree)) => {
699            // TODO what if not all the input is parsed? Is that handled?
700            return Ok((input, SectionElement::FencedMechCode(FencedMechCode{code: mech_tree, config, options})));
701          },
702          Err(err) => {
703            return Err(nom::Err::Error(ParseError {
704                cause_range: SourceRange::default(),
705                remaining_input: input,
706                error_detail: ParseErrorDetail {
707                    message: "Generic error parsing Mech code block",
708                    annotation_rngs: Vec::new(),
709                },
710            }));
711          }
712        };
713      } else if tag.starts_with("equation") || tag.starts_with("eq") || tag.starts_with("math") || tag.starts_with("latex") || tag.starts_with("tex") {
714          return Ok((input, SectionElement::Equation(code_token)));
715      } else if tag.starts_with("diagram") || tag.starts_with("chart") || tag.starts_with("mermaid") {
716          return Ok((input, SectionElement::Diagram(code_token)));          
717      } else {
718        // Some other code block, just keep moving although we might want to do something with it later
719      }
720    }
721  } 
722  Ok((input, SectionElement::CodeBlock(code_token)))
723}
724
725pub fn thematic_break(input: ParseString) -> ParseResult<SectionElement> {
726  let (input, _) = many1(asterisk)(input)?;
727  let (input, _) = many0(space_tab)(input)?;
728  let (input, _) = new_line(input)?;
729  Ok((input, SectionElement::ThematicBreak))
730}
731
732// footnote := "[^", +text, "]", ":", ws0, paragraph ;
733pub fn footnote(input: ParseString) -> ParseResult<Footnote> {
734  let (input, _) = footnote_prefix(input)?;
735  let (input, text) = many1(tuple((is_not(right_bracket),text)))(input)?;
736  let (input, _) = right_bracket(input)?;
737  let (input, _) = colon(input)?;
738  let (input, _) = whitespace0(input)?;
739  let (input, paragraph) = many1(paragraph_newline)(input)?;
740  let mut tokens = text.into_iter().map(|(_,tkn)| tkn).collect::<Vec<Token>>();
741  let footnote_text = Token::merge_tokens(&mut tokens).unwrap();
742  let footnote = (footnote_text, paragraph);
743  Ok((input, footnote))
744}
745
746pub fn blank_line(input: ParseString) -> ParseResult<Vec<Token>> {
747  let (input, mut st) = many0(space_tab)(input)?;
748  let (input, n) = new_line(input)?;
749  st.push(n);
750  Ok((input, st))
751}
752
753// question-block := question-sigil, *space, +paragraph ;
754pub fn question_block(input: ParseString) -> ParseResult<SectionElement> {
755  let (input, _) = question_sigil(input)?;
756  let (input, _) = many0(space_tab)(input)?;
757  let (input, paragraphs) = many1(paragraph_newline)(input)?;
758  Ok((input, SectionElement::QuestionBlock(paragraphs)))
759}
760
761// info-block := info-sigil, *space, +paragraph ;
762pub fn info_block(input: ParseString) -> ParseResult<SectionElement> {
763  let (input, _) = info_sigil(input)?;
764  let (input, _) = many0(space_tab)(input)?;
765  let (input, paragraphs) = many1(paragraph_newline)(input)?;
766  Ok((input, SectionElement::InfoBlock(paragraphs)))
767}
768
769// quote-block := quote-sigil, *space, +paragraph ;
770pub fn quote_block(input: ParseString) -> ParseResult<SectionElement> {
771  let (input, _) = peek(is_not(float_sigil))(input)?;
772  let (input, _) = quote_sigil(input)?;
773  let (input, _) = many0(space_tab)(input)?;
774  let (input, paragraphs) = many1(paragraph_newline)(input)?;
775  Ok((input, SectionElement::QuoteBlock(paragraphs)))
776}
777
778// warning-block := warning-sigil, *space, +paragraph ;
779pub fn warning_block(input: ParseString) -> ParseResult<SectionElement> {
780  let (input, _) = peek(is_not(float_sigil))(input)?;
781  let (input, _) = warning_sigil(input)?;
782  let (input, _) = many0(space_tab)(input)?;
783  let (input, paragraphs) = many1(paragraph_newline)(input)?;
784  Ok((input, SectionElement::WarningBlock(paragraphs)))
785}
786
787// success-block := success-sigil, *space, +paragraph ;
788pub fn success_block(input: ParseString) -> ParseResult<SectionElement> {
789  let (input, _) = peek(is_not(float_sigil))(input)?;
790  let (input, _) = alt((success_sigil, success_check_sigil))(input)?;
791  let (input, _) = many0(space_tab)(input)?;
792  let (input, paragraphs) = many1(paragraph_newline)(input)?;
793  Ok((input, SectionElement::SuccessBlock(paragraphs)))
794}
795
796// error-block := error-sigil, *space, +paragraph ;
797pub fn error_block(input: ParseString) -> ParseResult<SectionElement> {
798  let (input, _) = peek(is_not(float_sigil))(input)?;
799  let (input, _) = alt((error_sigil, error_alt_sigil))(input)?;
800  let (input, _) = many0(space_tab)(input)?;
801  let (input, paragraphs) = many1(paragraph_newline)(input)?;
802  Ok((input, SectionElement::ErrorBlock(paragraphs)))
803}
804
805// idea-block := idea-sigil, *space, +paragraph ;
806pub fn idea_block(input: ParseString) -> ParseResult<SectionElement> {
807  let (input, _) = idea_sigil(input)?;
808  let (input, _) = many0(space_tab)(input)?;
809  let (input, paragraphs) = many1(paragraph_newline)(input)?;
810  Ok((input, SectionElement::IdeaBlock(paragraphs)))
811}
812
813// abstract-element := abstract-sigil, *space, +paragraph ;
814pub fn abstract_el(input: ParseString) -> ParseResult<SectionElement> {
815  let (input, _) = abstract_sigil(input)?;
816  let (input, _) = many0(space_tab)(input)?;
817  let (input, paragraphs) = many1(paragraph_newline)(input)?;
818  Ok((input, SectionElement::Abstract(paragraphs)))
819}
820
821// equation := "$$" , +text ;
822pub fn equation(input: ParseString) -> ParseResult<Token> {
823  let (input, _) = equation_sigil(input)?;
824  let (input, mut txt) = many1(alt((backslash,text)))(input)?;
825  let mut eqn = Token::merge_tokens(&mut txt).unwrap();
826  Ok((input, eqn))
827}
828
829// citation := "[", (identifier | number), "]", ":", ws0, paragraph, ws0, ?("(", +text, ")") ;
830pub fn citation(input: ParseString) -> ParseResult<Citation> {
831  let (input, _) = left_bracket(input)?;
832  let (input, mut id) = many1(alphanumeric)(input)?;
833  let (input, _) = right_bracket(input)?;
834  let (input, _) = colon(input)?;
835  let (input, _) = whitespace0(input)?;
836  let (input, txt) = paragraph(input)?;
837  let (input, _) = whitespace0(input)?;
838  let id = Token::merge_tokens(&mut id).unwrap();
839  Ok((input, Citation{id, text: txt}))
840}
841
842// float-sigil := ">>" | "<<" ;
843pub fn float_sigil(input: ParseString) -> ParseResult<FloatDirection> {
844  let (input, d) = alt((float_left, float_right))(input)?;
845  let d = match d.kind {
846    TokenKind::FloatLeft => FloatDirection::Left,
847    TokenKind::FloatRight => FloatDirection::Right,
848    _ => unreachable!(),
849  };
850  Ok((input, d))
851}
852
853// float := float-sigil, section-element ;
854pub fn float(input: ParseString) -> ParseResult<(Box<SectionElement>,FloatDirection)> {
855  let (input, direction) = float_sigil(input)?;
856  let (input, _) = many0(space_tab)(input)?;
857  let (input, el) = section_element(input)?;
858  Ok((input, (Box::new(el), direction)))
859}
860
861// float := float-sigil, section-element ;
862pub fn not_mech_code(input: ParseString) -> ParseResult<()> {
863  let (input, _) = alt((null(question_block), 
864    null(info_block),  
865    null(success_block),
866    null(warning_block),
867    null(error_block),
868    null(idea_block),
869    null(img), 
870    null(float)))(input)?;
871  Ok((input, ()))
872}
873
874// 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 ;
875pub fn section_element(input: ParseString) -> ParseResult<SectionElement> {
876  let parsers: Vec<(&'static str, Box<dyn Fn(ParseString) -> ParseResult<SectionElement>>)> = vec![
877    ("list",            Box::new(|i| mechdown_list(i).map(|(i, lst)| (i, SectionElement::List(lst))))),
878    ("footnote",        Box::new(|i| footnote(i).map(|(i, f)| (i, SectionElement::Footnote(f))))),
879    ("citation",        Box::new(|i| citation(i).map(|(i, c)| (i, SectionElement::Citation(c))))),
880    ("abstract",        Box::new(abstract_el)),
881    ("img",             Box::new(|i| img(i).map(|(i, img)| (i, SectionElement::Image(img))))),
882    ("equation",        Box::new(|i| equation(i).map(|(i, e)| (i, SectionElement::Equation(e))))),
883    ("table",           Box::new(|i| mechdown_table(i).map(|(i, t)| (i, SectionElement::Table(t))))),
884    ("float",           Box::new(|i| float(i).map(|(i, f)| (i, SectionElement::Float(f))))),
885    ("quote_block",     Box::new(quote_block)),
886    ("code_block",      Box::new(code_block)),
887    ("thematic_break",  Box::new(|i| thematic_break(i).map(|(i, _)| (i, SectionElement::ThematicBreak)))),
888    ("subtitle",        Box::new(|i| subtitle(i).map(|(i, s)| (i, SectionElement::Subtitle(s))))),
889    ("question_block",  Box::new(question_block)),
890    ("info_block",      Box::new(info_block)),
891    ("success_block",   Box::new(success_block)),
892    ("warning_block",   Box::new(warning_block)),
893    ("error_block",     Box::new(error_block)),
894    ("idea_block",      Box::new(idea_block)),
895    ("paragraph",       Box::new(|i| paragraph(i).map(|(i, p)| (i, SectionElement::Paragraph(p))))),
896  ];
897
898  alt_best(input, &parsers)
899  
900}
901
902// section := ?ul-subtitle, +section-element ;
903pub fn section(input: ParseString) -> ParseResult<Section> {
904  let (input, subtitle) = opt(ul_subtitle)(input)?;
905
906  let mut elements = vec![];
907
908  let mut new_input = input.clone();
909
910  loop {
911    // Stop if EOF reached
912    if new_input.cursor >= new_input.graphemes.len() {
913      //println!("EOF reached while parsing section");
914      break;
915    }
916
917    // Stop if the next thing is a new section (peek, do not consume)
918    if ul_subtitle(new_input.clone()).is_ok() {
919      //println!("Next section detected, ending current section");
920      break;
921    }
922
923    #[cfg(feature = "mika")]
924    if mika_section_close(new_input.clone()).is_ok() {
925      break;
926    }
927
928    /*let (input, sct_elmnt) = labelr!(
929      section_element,
930      |input| recover::<SectionElement, _>(input, skip_till_eol),
931      "Expected a section element."
932    )(input.clone())?;*/
933
934    //elements.push(sct_elmnt);
935    //let (input, _) = many0(blank_line)(input.clone())?;
936
937    #[cfg(feature = "mika")]
938    match mika(new_input.clone()) {
939      Ok((input, mika)) => {
940        println!("Parsed Mika code block: {:#?}", mika);
941        elements.push(SectionElement::Mika(mika));
942        new_input = input;
943        continue;
944      }
945      Err(e) => {
946        // not mika code, try mech code
947        //return Err(e);
948      }
949    }
950  
951    // check if it's mech_code first, we'll prioritize that
952    match mech_code(new_input.clone()) {
953      Ok((input, mech_tree)) => {
954        elements.push(SectionElement::MechCode(mech_tree));
955        new_input = input;
956        continue;
957      }
958      Err(e) => {
959        // not mech code, try section_element
960        //return Err(e);
961      }
962    }
963
964    println!("Parsing section element at: {:?}", new_input.peek(0));
965    match section_element(new_input.clone()) {
966      Ok((input, element)) => {
967
968        elements.push(element);
969
970        // Skip any blank lines after the element
971        let (input, _) = many0(blank_line)(input.clone())?;
972        new_input = input;
973      }
974      Err(err) => {
975        // Propagate hard errors
976        return Err(err);
977      }
978    }
979  }
980  Ok((new_input, Section { subtitle, elements }))
981}
982
983// body := whitespace0, +(section, eof), eof ;
984pub fn body(input: ParseString) -> ParseResult<Body> {
985  let (mut input, _) = whitespace0(input)?;
986  let mut sections = vec![];
987  let mut new_input = input.clone();
988  loop {
989    if new_input.cursor >= new_input.graphemes.len() {
990      break;
991    }
992    // Try parsing a section
993    match section(new_input.clone()) {
994      Ok((input, sect)) => {
995        //println!("Parsed section: {:#?}", sect);
996        sections.push(sect);
997        new_input = input;
998      }
999      Err(err) => {
1000        return Err(err);
1001      }
1002    }
1003  }
1004  Ok((new_input, Body { sections }))
1005}