Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
122 changes: 122 additions & 0 deletions generator/src/docs.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
use pest::iterators::Pairs;
use pest_meta::parser::Rule;
use std::collections::HashMap;

#[derive(Debug)]
pub(crate) struct DocComment {
pub grammar_doc: String,

/// HashMap for store all doc_comments for rules.
/// key is rule name, value is doc_comment.
pub line_docs: HashMap<String, String>,
}

/// Consume pairs to matches `Rule::grammar_doc`, `Rule::line_doc` into `DocComment`
///
/// e.g.
///
/// a pest file:
///
/// ```ignore
/// //! This is a grammar doc
/// /// line doc 1
/// /// line doc 2
/// foo = {}
///
/// /// line doc 3
/// bar = {}
/// ```
///
/// Then will get:
///
/// ```ignore
/// grammar_doc = "This is a grammar doc"
/// line_docs = { "foo": "line doc 1\nline doc 2", "bar": "line doc 3" }
/// ```
pub(crate) fn consume(pairs: Pairs<'_, Rule>) -> DocComment {
let mut grammar_doc = String::new();

let mut line_docs: HashMap<String, String> = HashMap::new();
let mut line_doc = String::new();

for pair in pairs {
match pair.as_rule() {
Rule::grammar_doc => {
// grammar_doc > inner_doc
let inner_doc = pair.into_inner().next().unwrap();
grammar_doc.push_str(inner_doc.as_str());
grammar_doc.push('\n');
}
Rule::grammar_rule => {
if let Some(inner) = pair.into_inner().next() {
// grammar_rule > line_doc | identifier
match inner.as_rule() {
Rule::line_doc => {
if let Some(inner_doc) = inner.into_inner().next() {
line_doc.push_str(inner_doc.as_str());
line_doc.push('\n');
}
}
Rule::identifier => {
if !line_doc.is_empty() {
let rule_name = inner.as_str().to_owned();

// Remove last \n
line_doc.pop();
line_docs.insert(rule_name, line_doc.clone());
line_doc.clear();
}
}
_ => (),
}
}
}
_ => (),
}
}

if !grammar_doc.is_empty() {
// Remove last \n
grammar_doc.pop();
}

DocComment {
grammar_doc,
line_docs,
}
}

#[cfg(test)]
mod tests {
use std::collections::HashMap;

use pest_meta::parser;
use pest_meta::parser::Rule;

#[test]
fn test_doc_comment() {
let pairs = match parser::parse(Rule::grammar_rules, include_str!("../tests/test.pest")) {
Ok(pairs) => pairs,
Err(_) => panic!("error parsing tests/test.pest"),
};

let doc_comment = super::consume(pairs);

let mut expected = HashMap::new();
expected.insert("foo".to_owned(), "Matches foo str, e.g.: `foo`".to_owned());
expected.insert(
"bar".to_owned(),
"Matches bar str,\n Indent 2, e.g: `bar` or `foobar`".to_owned(),
);
expected.insert(
"dar".to_owned(),
"Matches dar\nMatch dar description".to_owned(),
);
assert_eq!(expected, doc_comment.line_docs);

assert_eq!(
"A parser for JSON file.\nAnd this is a example for JSON parser.\n\n indent-4-space",
doc_comment.grammar_doc
);
}
}
59 changes: 50 additions & 9 deletions generator/src/generator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,15 @@ use pest::unicode::unicode_property_names;
use pest_meta::ast::*;
use pest_meta::optimizer::*;

pub fn generate(
use crate::docs::DocComment;

pub(crate) fn generate(
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This because of themod generator is not public.

So I changed it to pub(crate).

name: Ident,
generics: &Generics,
path: Option<PathBuf>,
rules: Vec<OptimizedRule>,
defaults: Vec<&str>,
doc_comment: &DocComment,
include_grammar: bool,
) -> TokenStream {
let uses_eoi = defaults.iter().any(|name| *name == "EOI");
Expand All @@ -36,7 +39,7 @@ pub fn generate(
} else {
quote!()
};
let rule_enum = generate_enum(&rules, uses_eoi);
let rule_enum = generate_enum(&rules, doc_comment, uses_eoi);
let patterns = generate_patterns(&rules, uses_eoi);
let skip = generate_skip(&rules);

Expand Down Expand Up @@ -181,10 +184,25 @@ fn generate_include(name: &Ident, path: &str) -> TokenStream {
}
}

fn generate_enum(rules: &[OptimizedRule], uses_eoi: bool) -> TokenStream {
let rules = rules.iter().map(|rule| format_ident!("r#{}", rule.name));
fn generate_enum(rules: &[OptimizedRule], doc_comment: &DocComment, uses_eoi: bool) -> TokenStream {
let rules = rules.iter().map(|rule| {
let rule_name = format_ident!("r#{}", rule.name);

match doc_comment.line_docs.get(&rule.name) {
Some(doc) => quote! {
#[doc = #doc]
#rule_name
},
None => quote! {
#rule_name
},
}
});

let grammar_doc = &doc_comment.grammar_doc;
if uses_eoi {
quote! {
#[doc = #grammar_doc]
#[allow(dead_code, non_camel_case_types, clippy::upper_case_acronyms)]
#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub enum Rule {
Expand All @@ -194,6 +212,7 @@ fn generate_enum(rules: &[OptimizedRule], uses_eoi: bool) -> TokenStream {
}
} else {
quote! {
#[doc = #grammar_doc]
#[allow(dead_code, non_camel_case_types, clippy::upper_case_acronyms)]
#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub enum Rule {
Expand All @@ -208,6 +227,7 @@ fn generate_patterns(rules: &[OptimizedRule], uses_eoi: bool) -> TokenStream {
.iter()
.map(|rule| {
let rule = format_ident!("r#{}", rule.name);

quote! {
Rule::#rule => rules::#rule(state)
}
Expand Down Expand Up @@ -657,10 +677,11 @@ fn option_type() -> TokenStream {

#[cfg(test)]
mod tests {
use proc_macro2::Span;

use super::*;

use proc_macro2::Span;
use std::collections::HashMap;

#[test]
fn rule_enum_simple() {
let rules = vec![OptimizedRule {
Expand All @@ -669,12 +690,22 @@ mod tests {
expr: OptimizedExpr::Ident("g".to_owned()),
}];

let mut line_docs = HashMap::new();
line_docs.insert("f".to_owned(), "This is rule comment".to_owned());

let doc_comment = &DocComment {
grammar_doc: "Rule doc\nhello".to_owned(),
line_docs,
};

assert_eq!(
generate_enum(&rules, false).to_string(),
generate_enum(&rules, doc_comment, false).to_string(),
quote! {
#[doc = "Rule doc\nhello"]
#[allow(dead_code, non_camel_case_types, clippy::upper_case_acronyms)]
#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub enum Rule {
#[doc = "This is rule comment"]
r#f
}
}
Expand Down Expand Up @@ -957,7 +988,7 @@ mod tests {
}

#[test]
fn generate_complete() {
fn test_generate_complete() {
let name = Ident::new("MyParser", Span::call_site());
let generics = Generics::default();

Expand All @@ -974,22 +1005,32 @@ mod tests {
},
];

let mut line_docs = HashMap::new();
line_docs.insert("if".to_owned(), "If statement".to_owned());

let doc_comment = &DocComment {
line_docs,
grammar_doc: "This is Rule doc\nThis is second line".to_owned(),
};

let defaults = vec!["ANY"];
let result = result_type();
let box_ty = box_type();
let mut current_dir = std::env::current_dir().expect("Unable to get current directory");
current_dir.push("test.pest");
let test_path = current_dir.to_str().expect("path contains invalid unicode");
assert_eq!(
generate(name, &generics, Some(PathBuf::from("test.pest")), rules, defaults, true).to_string(),
generate(name, &generics, Some(PathBuf::from("test.pest")), rules, defaults, doc_comment, true).to_string(),
quote! {
#[allow(non_upper_case_globals)]
const _PEST_GRAMMAR_MyParser: &'static str = include_str!(#test_path);

#[doc = "This is Rule doc\nThis is second line"]
#[allow(dead_code, non_camel_case_types, clippy::upper_case_acronyms)]
#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub enum Rule {
r#a,
#[doc = "If statement"]
r#if
}

Expand Down
46 changes: 45 additions & 1 deletion generator/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ use syn::{Attribute, DeriveInput, Generics, Ident, Lit, Meta};

#[macro_use]
mod macros;
mod docs;
mod generator;

use pest_meta::parser::{self, rename_meta_rule, Rule};
Expand Down Expand Up @@ -91,10 +92,19 @@ pub fn derive_parser(input: TokenStream, include_grammar: bool) -> TokenStream {
};

let defaults = unwrap_or_report(validator::validate_pairs(pairs.clone()));
let doc_comment = docs::consume(pairs.clone());
let ast = unwrap_or_report(parser::consume_rules(pairs));
let optimized = optimizer::optimize(ast);

generator::generate(name, &generics, path, optimized, defaults, include_grammar)
generator::generate(
name,
&generics,
path,
optimized,
defaults,
&doc_comment,
include_grammar,
)
}

fn read_file<P: AsRef<Path>>(path: P) -> io::Result<String> {
Expand Down Expand Up @@ -225,4 +235,38 @@ mod tests {
let ast = syn::parse_str(definition).unwrap();
parse_derive(ast);
}

#[test]
fn test_generate_doc() {
let input = quote! {
#[derive(Parser)]
#[grammar = "../tests/test.pest"]
pub struct TestParser;
};

let token = super::derive_parser(input, true);

let expected = quote! {
#[doc = "A parser for JSON file.\nAnd this is a example for JSON parser.\n\n indent-4-space"]
#[allow(dead_code, non_camel_case_types, clippy::upper_case_acronyms)]
#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]

pub enum Rule {
#[doc = "Matches foo str, e.g.: `foo`"]
r#foo,
#[doc = "Matches bar str,\n Indent 2, e.g: `bar` or `foobar`"]
r#bar,
r#bar1,
#[doc = "Matches dar\nMatch dar description"]
r#dar
}
};

assert!(
token.to_string().contains(expected.to_string().as_str()),
"{}\n\nExpected to contains:\n{}",
token,
expected
);
}
}
20 changes: 20 additions & 0 deletions generator/tests/test.pest
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
//! A parser for JSON file.
//! And this is a example for JSON parser.
//!
//! indent-4-space

/// Matches foo str, e.g.: `foo`
foo = { "foo" }

/// Matches bar str,
/// Indent 2, e.g: `bar` or `foobar`

bar = { "bar" | "foobar" }

bar1 = { "bar1" }

/// Matches dar

/// Match dar description

dar = { "da" }
5 changes: 5 additions & 0 deletions grammars/src/grammars/json.pest
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,13 @@
// option. All files in the project carrying such notice may not be copied,
// modified, or distributed except according to those terms.

//! A parser for JSON file.
//!
//! And this is a example for JSON parser.
json = { SOI ~ (object | array) ~ EOI }

/// Matches object, e.g.: `{ "foo": "bar" }`
/// Foobar
object = { "{" ~ pair ~ ("," ~ pair)* ~ "}" | "{" ~ "}" }
pair = { string ~ ":" ~ value }

Expand Down
Loading