1use pest::iterators::Pairs;
2use pest_meta::parser::Rule;
3use std::collections::HashMap;
4
5#[derive(Debug)]
6pub(crate) struct DocComment {
7 pub grammar_doc: String,
8
9 /// HashMap for store all doc_comments for rules.
10 /// key is rule name, value is doc_comment.
11 pub line_docs: HashMap<String, String>,
12}
13
14/// Consume pairs to matches `Rule::grammar_doc`, `Rule::line_doc` into `DocComment`
15///
16/// e.g.
17///
18/// a pest file:
19///
20/// ```ignore
21/// //! This is a grammar doc
22/// /// line doc 1
23/// /// line doc 2
24/// foo = {}
25///
26/// /// line doc 3
27/// bar = {}
28/// ```
29///
30/// Then will get:
31///
32/// ```ignore
33/// grammar_doc = "This is a grammar doc"
34/// line_docs = { "foo": "line doc 1\nline doc 2", "bar": "line doc 3" }
35/// ```
36pub(crate) fn consume(pairs: Pairs<'_, Rule>) -> DocComment {
37 let mut grammar_doc = String::new();
38
39 let mut line_docs: HashMap<String, String> = HashMap::new();
40 let mut line_doc = String::new();
41
42 for pair in pairs {
43 match pair.as_rule() {
44 Rule::grammar_doc => {
45 // grammar_doc > inner_doc
46 let inner_doc = pair.into_inner().next().unwrap();
47 grammar_doc.push_str(inner_doc.as_str());
48 grammar_doc.push('\n');
49 }
50 Rule::grammar_rule => {
51 if let Some(inner) = pair.into_inner().next() {
52 // grammar_rule > line_doc | identifier
53 match inner.as_rule() {
54 Rule::line_doc => {
55 if let Some(inner_doc) = inner.into_inner().next() {
56 line_doc.push_str(inner_doc.as_str());
57 line_doc.push('\n');
58 }
59 }
60 Rule::identifier => {
61 if !line_doc.is_empty() {
62 let rule_name = inner.as_str().to_owned();
63
64 // Remove last \n
65 line_doc.pop();
66 line_docs.insert(rule_name, line_doc.clone());
67 line_doc.clear();
68 }
69 }
70 _ => (),
71 }
72 }
73 }
74 _ => (),
75 }
76 }
77
78 if !grammar_doc.is_empty() {
79 // Remove last \n
80 grammar_doc.pop();
81 }
82
83 DocComment {
84 grammar_doc,
85 line_docs,
86 }
87}
88
89#[cfg(test)]
90mod tests {
91 use std::collections::HashMap;
92
93 use pest_meta::parser;
94 use pest_meta::parser::Rule;
95
96 #[test]
97 fn test_doc_comment() {
98 let pairs = match parser::parse(Rule::grammar_rules, include_str!("../tests/test.pest")) {
99 Ok(pairs) => pairs,
100 Err(_) => panic!("error parsing tests/test.pest"),
101 };
102
103 let doc_comment = super::consume(pairs);
104
105 let mut expected = HashMap::new();
106 expected.insert("foo".to_owned(), "Matches foo str, e.g.: `foo`".to_owned());
107 expected.insert(
108 "bar".to_owned(),
109 "Matches bar str\n\n Indent 2, e.g: `bar` or `foobar`".to_owned(),
110 );
111 expected.insert(
112 "dar".to_owned(),
113 "Matches dar\n\nMatch dar description\n".to_owned(),
114 );
115 assert_eq!(expected, doc_comment.line_docs);
116
117 assert_eq!(
118 "A parser for JSON file.\nAnd this is a example for JSON parser.\n\n indent-4-space\n",
119 doc_comment.grammar_doc
120 );
121 }
122
123 #[test]
124 fn test_empty_grammar_doc() {
125 assert!(parser::parse(Rule::grammar_rules, "//!").is_ok());
126 assert!(parser::parse(Rule::grammar_rules, "///").is_ok());
127 assert!(parser::parse(Rule::grammar_rules, "//").is_ok());
128 assert!(parser::parse(Rule::grammar_rules, "/// Line Doc").is_ok());
129 assert!(parser::parse(Rule::grammar_rules, "//! Grammar Doc").is_ok());
130 assert!(parser::parse(Rule::grammar_rules, "// Comment").is_ok());
131 }
132}
133