Skip to main content

cdoc_parser/raw/
parser.rs

1use crate::raw::{RawDocument, Reference};
2use cowstr::CowStr;
3use pest::iterators::Pair;
4use pest::Parser;
5use pest_derive::Parser;
6
7#[derive(Parser)]
8#[grammar = "grammars/raw_doc.pest"]
9pub struct RawDocParser;
10
11use crate::code_ast::parse_code_string;
12use crate::code_ast::types::CodeContent;
13use crate::common::Span;
14use crate::raw::{Element, ElementInfo, Parameter, Special, Value};
15use pest::iterators::Pairs;
16use thiserror::Error;
17
18#[derive(Error, Debug)]
19pub enum ParserError {
20    #[error("code cell parsing error")]
21    CodeError(#[from] Box<pest::error::Error<crate::code_ast::Rule>>),
22    #[error("document parsing error")]
23    DocError(#[from] Box<pest::error::Error<Rule>>),
24}
25
26impl RawDocument {
27    fn parse_doc(&mut self, mut pairs: Pairs<Rule>) -> Result<(), ParserError> {
28        let mut elems = pairs.next().expect("no root item").into_inner();
29
30        if let Some(p) = elems.next() {
31            match p.as_rule() {
32                Rule::meta => self.parse_meta(p),
33                _ => {
34                    let el = self.parse_element(p);
35                    self.src.push(el?)
36                }
37            }
38        }
39
40        let elems = self.parse_elements(elems)?;
41        self.src.extend(elems);
42
43        Ok(())
44    }
45
46    fn parse_elements(&mut self, pairs: Pairs<Rule>) -> Result<Vec<ElementInfo>, ParserError> {
47        pairs.map(|p| self.parse_element(p.clone())).collect()
48    }
49
50    fn parse_element(&mut self, pair: Pair<Rule>) -> Result<ElementInfo, ParserError> {
51        let span = Span::from(pair.as_span());
52
53        let element = match pair.as_rule() {
54            Rule::command => self.parse_command(pair)?,
55            Rule::math_block => self.parse_math_block(pair),
56            Rule::code_def => self.parse_code(pair)?,
57            Rule::verbatim => self.parse_verbatim(pair),
58            Rule::src | Rule::string | Rule::body => self.parse_src(pair),
59            _ => unreachable!(),
60        };
61
62        Ok(ElementInfo { element, span })
63    }
64
65    fn parse_src(&mut self, pair: Pair<Rule>) -> Element {
66        let value = pair.as_str();
67        Element::Markdown(value.into())
68    }
69
70    fn parse_command(&mut self, pair: Pair<Rule>) -> Result<Element, ParserError> {
71        let mut inner = pair.into_inner();
72        let name = inner
73            .next()
74            .expect("empty command")
75            .into_inner()
76            .next()
77            .unwrap()
78            .as_span();
79        let name = self.cowstr_from_span(name);
80
81        let mut parameters = vec![];
82        let mut body = None;
83        let mut label = None;
84
85        for elem in inner {
86            match elem.as_rule() {
87                Rule::parameters => parameters = self.parse_parameters(elem.into_inner())?,
88                Rule::body_def => body = Some(self.parse_elements(elem.into_inner())?),
89                Rule::label => {
90                    label = Some(self.cowstr_from_span(elem.into_inner().next().unwrap().as_span()))
91                }
92                _ => unreachable!(),
93            }
94        }
95
96        if let Some(label) = label.clone() {
97            self.references
98                .insert(label, Reference::Command(name.clone(), parameters.clone()));
99        }
100
101        Ok(Element::Special(
102            label,
103            Special::Command {
104                function: name,
105                parameters,
106                body,
107            },
108        ))
109    }
110
111    fn parse_parameters(&mut self, pairs: Pairs<Rule>) -> Result<Vec<Parameter>, ParserError> {
112        pairs
113            .into_iter()
114            .map(|elem| {
115                if let Rule::param = elem.as_rule() {
116                    self.parse_param(elem)
117                } else {
118                    unreachable!()
119                }
120            })
121            .collect()
122    }
123
124    fn parse_param(&mut self, pair: Pair<Rule>) -> Result<Parameter, ParserError> {
125        let span = Span::from(pair.as_span());
126        let mut pairs = pair.into_inner();
127        let first = pairs.next().expect("empty param");
128
129        Ok(if let Rule::key = first.as_rule() {
130            let value = pairs.next().expect("no value");
131            Parameter::with_key(first.as_str(), self.parse_value(value)?, span)
132        } else {
133            Parameter::with_value(self.parse_value(first)?, span)
134        })
135    }
136
137    fn parse_value(&mut self, pair: Pair<Rule>) -> Result<Value, ParserError> {
138        Ok(match pair.as_rule() {
139            Rule::basic_val | Rule::string => Value::String(pair.as_str().into()),
140            Rule::md_val => Value::Content(self.parse_elements(pair.into_inner())?),
141            Rule::flag => Value::Flag(pair.as_str().into()),
142            _ => unreachable!(),
143        })
144    }
145
146    fn parse_math_block(&mut self, pair: Pair<Rule>) -> Element {
147        let (lvl, src, label) = self.block_parser(pair);
148
149        let src = self.parse_math(src);
150
151        if let Some(label) = label.clone() {
152            self.references.insert(label, Reference::Math(src.clone()));
153        }
154
155        Element::Special(
156            label,
157            Special::Math {
158                inner: src,
159                is_block: lvl.len() != 1,
160            },
161        )
162    }
163
164    fn parse_code_attributes(&mut self, pairs: Pairs<Rule>) -> Vec<CowStr> {
165        pairs
166            .into_iter()
167            .map(|elem| {
168                if let Rule::code_param = elem.as_rule() {
169                    self.parse_code_attribute(elem)
170                } else {
171                    unreachable!()
172                }
173            })
174            .collect()
175    }
176
177    fn parse_code_attribute(&mut self, pair: Pair<Rule>) -> CowStr {
178        let mut pairs = pair.into_inner();
179        let first = pairs.next().expect("empty param");
180
181        if let Rule::key = first.as_rule() {
182            let value = pairs.next().expect("no value");
183            // CodeAttr {
184            //     key: Some(first.as_str().to_string()),
185            //     value: value.as_str().to_string(),
186            // }
187            self.cowstr_from_span(value.as_span())
188        } else {
189            // CodeAttr {
190            //     key: None,
191            //     value: first.as_str().to_string(),
192            // }
193            self.cowstr_from_span(first.as_span())
194        }
195    }
196
197    fn parse_code(&mut self, pair: Pair<Rule>) -> Result<Element, ParserError> {
198        let mut inner = pair.into_inner();
199        let lvl = inner.next().expect("missing code_lvl").as_str().to_string();
200
201        let maybe_param = inner.next().expect("missing code_src");
202        let (src_pair, attributes) = if let Rule::code_params = maybe_param.as_rule() {
203            let attributes = self.parse_code_attributes(maybe_param.into_inner());
204            (inner.next().expect("missing code_src"), Some(attributes))
205        } else {
206            (maybe_param, None)
207        };
208
209        let src_span = src_pair.as_span();
210        let src = self.cowstr_from_span(src_span);
211
212        let id = inner.next().map(|val| self.cowstr_from_span(val.as_span()));
213
214        if let Some(label) = id.clone() {
215            self.references.insert(label, Reference::Code(src.clone()));
216        }
217
218        Ok(Element::Special(
219            id,
220            if lvl.len() == 1 {
221                Special::CodeInline { inner: src }
222            } else {
223                let inner = if attributes
224                    .as_ref()
225                    .map(|a| a.contains(&CowStr::from("no_parse")))
226                    .unwrap_or_default()
227                {
228                    CodeContent::Plain(src)
229                } else {
230                    parse_code_string(src)?
231                };
232
233                Special::CodeBlock {
234                    lvl: lvl.len(),
235                    inner,
236                    attributes: attributes.unwrap_or_default(),
237                }
238            },
239        ))
240    }
241
242    fn parse_verbatim(&mut self, pair: Pair<Rule>) -> Element {
243        let value = pair.as_str();
244        Element::Special(
245            None,
246            Special::Verbatim {
247                inner: value.into(),
248            },
249        )
250    }
251
252    fn parse_meta(&mut self, pair: Pair<Rule>) {
253        self.meta = Some(self.cowstr_from_span(pair.as_span()));
254    }
255
256    fn cowstr_from_span(&self, span: pest::Span) -> CowStr {
257        CowStr::from(&self.input[span.start()..span.end()])
258    }
259
260    fn parse_math(&self, pair: Pair<Rule>) -> CowStr {
261        match pair.as_rule() {
262            Rule::math_chars => self.cowstr_from_span(pair.as_span()),
263            Rule::math_block_curly => cowstr::format!(
264                "{{{}}}",
265                pair.into_inner()
266                    .map(|p| self.parse_math(p))
267                    .collect::<CowStr>()
268            ),
269            // | Rule::math_block_bracket
270            // | Rule::math_block_paren
271            Rule::math_body => pair
272                .into_inner()
273                .map(|p| self.parse_math(p))
274                .collect::<CowStr>(),
275            _ => unreachable!(),
276        }
277    }
278
279    fn block_parser<'a>(&'a self, pair: Pair<'a, Rule>) -> (CowStr, Pair<Rule>, Option<CowStr>) {
280        let mut inner = pair.into_inner();
281        let lvl = self.cowstr_from_span(inner.next().expect("missing code_lvl").as_span());
282        let src = inner.next().expect("missing code_src");
283        let id = inner.next().map(|val| self.cowstr_from_span(val.as_span()));
284        (lvl, src, id)
285    }
286}
287
288pub fn parse_to_doc(input: &str) -> Result<RawDocument, ParserError> {
289    let mut doc = RawDocument::new(input);
290    doc.parse_doc(RawDocParser::parse(Rule::top, input).map_err(Box::new)?)?;
291    Ok(doc)
292}
293
294#[cfg(test)]
295mod tests {
296    use crate::code_ast::types::{CodeContent, CodeElem};
297    use crate::common::Span;
298    use crate::raw::{
299        parse_to_doc, Element, ElementInfo, Parameter, RawDocument, Reference, Special, Value,
300    };
301    use cowstr::CowStr;
302    use std::collections::HashMap;
303
304    macro_rules! doc_tests {
305        ($prefix:ident $($name:ident: $value:expr,)*) => {
306        $(
307            paste::item!{
308            #[test]
309            fn [<$prefix _ $name>]() {
310                let (input, expected) = $value;
311                let doc = RawDocument { input: CowStr::from(input), src: expected, meta: None, references: Default::default() };
312                compare(doc, input);
313            }
314            }
315        )*
316        }
317    }
318
319    #[test]
320    fn test_code() {
321        let input = r#"```
322code
323```"#;
324        let expected = RawDocument {
325            src: vec![ElementInfo {
326                element: Element::Special(
327                    None,
328                    Special::CodeBlock {
329                        lvl: 3,
330                        inner: CodeContent::Parsed {
331                            blocks: vec![CodeElem::Src("\ncode\n\n".into())],
332                            meta: Default::default(),
333                            hash: 3750657748055546767,
334                        },
335                        attributes: vec![],
336                    },
337                ),
338                span: Span::new(0, 12),
339            }],
340            input: CowStr::from(input),
341            meta: None,
342            references: Default::default(),
343        };
344
345        compare(expected, input);
346    }
347
348    #[test]
349    fn test_code_param() {
350        let input = r#"```lang, val
351code
352```"#;
353        let expected = RawDocument {
354            src: vec![ElementInfo {
355                element: Element::Special(
356                    None,
357                    Special::CodeBlock {
358                        lvl: 3,
359                        inner: CodeContent::Parsed {
360                            blocks: vec![CodeElem::Src("code\n\n".into())],
361                            meta: Default::default(),
362                            hash: 15492099155864206242,
363                        },
364                        attributes: vec!["lang".into(), "val".into()],
365                    },
366                ),
367                span: Span::new(0, 21),
368            }],
369            input: CowStr::from(input),
370            meta: None,
371            references: Default::default(),
372        };
373
374        compare(expected, input);
375    }
376
377    #[test]
378    fn test_math() {
379        let input = "$inline$";
380        let expected = RawDocument {
381            src: vec![ElementInfo {
382                element: Element::Special(
383                    None,
384                    Special::Math {
385                        is_block: false,
386                        inner: "inline".into(),
387                    },
388                ),
389                span: Span::new(0, 8),
390            }],
391            input: CowStr::from(input),
392            meta: None,
393            references: Default::default(),
394        };
395
396        compare(expected, input);
397    }
398
399    #[test]
400    fn test_verbatim() {
401        let input = "\\{verbatim\\}";
402        let expected = RawDocument {
403            src: vec![ElementInfo {
404                element: Element::Special(
405                    None,
406                    Special::Verbatim {
407                        inner: "verbatim".into(),
408                    },
409                ),
410                span: Span::new(2, 10),
411            }],
412            input: CowStr::from(input),
413            meta: None,
414            references: Default::default(),
415        };
416
417        compare(expected, input);
418    }
419
420    #[test]
421    fn test_src() {
422        let input = "just some stuff {} xx--^*# fsdf";
423        let expected = RawDocument {
424            src: vec![ElementInfo {
425                element: Element::Markdown(input.into()),
426                span: Span::new(0, 31),
427            }],
428            input: CowStr::from(input),
429            meta: None,
430            references: Default::default(),
431        };
432
433        compare(expected, input);
434    }
435
436    #[test]
437    fn test_refs() {
438        let input = "#call|id";
439        let expected = RawDocument {
440            src: vec![ElementInfo {
441                element: Element::Special(
442                    Some("id".into()),
443                    Special::Command {
444                        function: "call".into(),
445                        parameters: vec![],
446                        body: None,
447                    },
448                ),
449                span: Span::new(0, 8),
450            }],
451            input: CowStr::from(input),
452            meta: None,
453            references: HashMap::from([("id".into(), Reference::Command("call".into(), vec![]))]),
454        };
455
456        compare(expected, input);
457    }
458
459    const CMD_WITH_PARAMS_NO_BODY: &str =
460        "#func(basic, \"quoted\", {content}, key=basic, key=\"quoted\", key={content}, :flag)";
461
462    doc_tests! {
463        command
464        no_params_no_body: ("#func",  vec![
465            ElementInfo {
466                element: Element::Special(None, Special::Command {
467                    function: "func".into(),
468                    parameters: vec![],
469                    body: None,
470                }),
471                span: Span::new(0, 5),
472            }
473        ]),
474        with_params_no_body: (CMD_WITH_PARAMS_NO_BODY,  vec![
475            ElementInfo {
476                element: Element::Special(None, Special::Command {
477                    function: "func".into(),
478                    parameters: vec![
479                        Parameter { key: None, value: Value::String("basic".into()), span: Span::new(6, 11) },
480                        Parameter { key: None, value: Value::String("quoted".into()), span: Span::new(13, 21) },
481                        Parameter { key: None, value: Value::Content(vec![
482                            ElementInfo {
483                                element: Element::Markdown("content".into()),
484                                span: Span::new(24, 31)
485                            }
486                        ]), span: Span::new(23, 32) },
487                        Parameter { key: Some("key".into()), value: Value::String("basic".into()), span: Span::new(34, 43) },
488                        Parameter { key: Some("key".into()), value: Value::String("quoted".into()), span: Span::new(45, 57) },
489                        Parameter { key: Some("key".into()), value: Value::Content(vec![
490                            ElementInfo {
491                                element: Element::Markdown("content".into()),
492                                span: Span::new(64, 71)
493                            }
494                        ]), span: Span::new( 59, 72) },
495                        Parameter { key: None, value: Value::Flag("flag".into()), span: Span::new(74, 79) }
496                    ],
497                    body: None,
498                }),
499                span: Span::new(0, 80),
500            }
501        ]),
502        with_params_with_body: ("#func(c){x}", vec![
503            ElementInfo {
504                element: Element::Special(None, Special::Command {
505                    function: "func".into(),
506                    parameters: vec![
507                        Parameter { key: None, value: Value::String("c".into()), span: Span::new(6, 7)}
508                    ],
509                    body: Some(vec![
510                        ElementInfo {
511                            element: Element::Markdown("x".into()),
512                            span: Span::new(9, 10)
513                        }
514                    ])
515                }),
516                span: Span::new(0, 11),
517            }
518        ]),
519        no_params_with_body: ("#func{x}", vec![
520            ElementInfo {
521                element: Element::Special(None, Special::Command {
522                    function: "func".into(),
523                    parameters: vec![],
524                    body: Some(vec![
525                        ElementInfo {
526                            element: Element::Markdown("x".into()),
527                            span: Span::new(6, 7)
528                        }
529                    ])
530                }),
531                span: Span::new(0, 8),
532            }
533        ]),
534        body_nested: ("#func1{#func2}", vec![
535            ElementInfo {
536                element: Element::Special(None, Special::Command {
537                    function: "func1".into(),
538                    parameters: vec![],
539                    body: Some(vec![ElementInfo {
540                            element: Element::Special(None, Special::Command{
541                                function: "func2".into(),
542                                parameters: vec![],
543                                body: None,
544                            }),
545                            span: Span::new(7, 13),
546                        }
547                    ])
548                }),
549                span: Span::new(0, 14),
550            }
551
552        ]),
553    }
554
555    fn compare(expected: RawDocument, input: &str) {
556        let doc = parse_to_doc(input).expect("Parse error");
557
558        assert_eq!(expected, doc);
559    }
560}