• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // pest. The Elegant Parser
2 // Copyright (c) 2018 Dragoș Tiselice
3 //
4 // Licensed under the Apache License, Version 2.0
5 // <LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0> or the MIT
6 // license <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
7 // option. All files in the project carrying such notice may not be copied,
8 // modified, or distributed except according to those terms.
9 
10 #![doc(
11     html_root_url = "https://docs.rs/pest_derive",
12     html_logo_url = "https://raw.githubusercontent.com/pest-parser/pest/master/pest-logo.svg",
13     html_favicon_url = "https://raw.githubusercontent.com/pest-parser/pest/master/pest-logo.svg"
14 )]
15 #![warn(missing_docs, rust_2018_idioms, unused_qualifications)]
16 #![recursion_limit = "256"]
17 //! # pest generator
18 //!
19 //! This crate generates code from ASTs (which is used in the `pest_derive` crate).
20 
21 #[macro_use]
22 extern crate quote;
23 
24 use std::env;
25 use std::fs::File;
26 use std::io::{self, Read};
27 use std::path::Path;
28 
29 use generator::generate;
30 use proc_macro2::TokenStream;
31 use syn::DeriveInput;
32 
33 #[macro_use]
34 mod macros;
35 
36 #[cfg(feature = "export-internal")]
37 pub mod docs;
38 #[cfg(not(feature = "export-internal"))]
39 mod docs;
40 
41 #[cfg(feature = "export-internal")]
42 pub mod generator;
43 #[cfg(not(feature = "export-internal"))]
44 mod generator;
45 
46 #[cfg(feature = "export-internal")]
47 pub mod parse_derive;
48 #[cfg(not(feature = "export-internal"))]
49 mod parse_derive;
50 
51 use crate::parse_derive::{parse_derive, GrammarSource};
52 use pest_meta::parser::{self, rename_meta_rule, Rule};
53 use pest_meta::{optimizer, unwrap_or_report, validator};
54 
55 /// Processes the derive/proc macro input and generates the corresponding parser based
56 /// on the parsed grammar. If `include_grammar` is set to true, it'll generate an explicit
57 /// "include_str" statement (done in pest_derive, but turned off in the local bootstrap).
derive_parser(input: TokenStream, include_grammar: bool) -> TokenStream58 pub fn derive_parser(input: TokenStream, include_grammar: bool) -> TokenStream {
59     let ast: DeriveInput = syn::parse2(input).unwrap();
60     let (parsed_derive, contents) = parse_derive(ast);
61 
62     // Grammar presented in a view of a string.
63     let mut data = String::new();
64     let mut paths = vec![];
65 
66     for content in contents {
67         let (_data, _path) = match content {
68             GrammarSource::File(ref path) => {
69                 let root = env::var("CARGO_MANIFEST_DIR").unwrap_or_else(|_| ".".into());
70 
71                 // Check whether we can find a file at the path relative to the CARGO_MANIFEST_DIR
72                 // first.
73                 //
74                 // If we cannot find the expected file over there, fallback to the
75                 // `CARGO_MANIFEST_DIR/src`, which is the old default and kept for convenience
76                 // reasons.
77                 // TODO: This could be refactored once `std::path::absolute()` get's stabilized.
78                 // https://doc.rust-lang.org/std/path/fn.absolute.html
79                 let path = if Path::new(&root).join(path).exists() {
80                     Path::new(&root).join(path)
81                 } else {
82                     Path::new(&root).join("src/").join(path)
83                 };
84 
85                 let file_name = match path.file_name() {
86                     Some(file_name) => file_name,
87                     None => panic!("grammar attribute should point to a file"),
88                 };
89 
90                 let data = match read_file(&path) {
91                     Ok(data) => data,
92                     Err(error) => panic!("error opening {:?}: {}", file_name, error),
93                 };
94                 (data, Some(path.clone()))
95             }
96             GrammarSource::Inline(content) => (content, None),
97         };
98 
99         data.push_str(&_data);
100         if let Some(path) = _path {
101             paths.push(path);
102         }
103     }
104 
105     // `Rule::grammar_rules` is taken from meta/srd/parser.rs.
106     let pairs = match parser::parse(Rule::grammar_rules, &data) {
107         Ok(pairs) => pairs,
108         Err(error) => panic!("error parsing \n{}", error.renamed_rules(rename_meta_rule)),
109     };
110 
111     let defaults = unwrap_or_report(validator::validate_pairs(pairs.clone()));
112     let doc_comment = docs::consume(pairs.clone());
113     let ast = unwrap_or_report(parser::consume_rules(pairs));
114     let optimized = optimizer::optimize(ast);
115 
116     generate(
117         parsed_derive,
118         paths,
119         optimized,
120         defaults,
121         &doc_comment,
122         include_grammar,
123     )
124 }
125 
read_file<P: AsRef<Path>>(path: P) -> io::Result<String>126 fn read_file<P: AsRef<Path>>(path: P) -> io::Result<String> {
127     let mut file = File::open(path.as_ref())?;
128     let mut string = String::new();
129     file.read_to_string(&mut string)?;
130     Ok(string)
131 }
132 
133 #[cfg(test)]
134 mod tests {
135 
136     #[doc = "Matches dar\n\nMatch dar description\n"]
137     #[test]
test_generate_doc()138     fn test_generate_doc() {
139         let input = quote! {
140             #[derive(Parser)]
141             #[non_exhaustive]
142             #[grammar = "../tests/test.pest"]
143             pub struct TestParser;
144         };
145 
146         let token = super::derive_parser(input, true);
147 
148         let expected = quote! {
149             #[doc = "A parser for JSON file.\nAnd this is a example for JSON parser.\n\n    indent-4-space\n"]
150             #[allow(dead_code, non_camel_case_types, clippy::upper_case_acronyms)]
151             #[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
152             #[non_exhaustive]
153             pub enum Rule {
154                 #[doc = "Matches foo str, e.g.: `foo`"]
155                 r#foo,
156                 #[doc = "Matches bar str\n\n  Indent 2, e.g: `bar` or `foobar`"]
157                 r#bar,
158                 r#bar1,
159                 #[doc = "Matches dar\n\nMatch dar description\n"]
160                 r#dar
161             }
162         };
163 
164         assert!(
165             token.to_string().contains(expected.to_string().as_str()),
166             "{}\n\nExpected to contains:\n{}",
167             token,
168             expected
169         );
170     }
171 }
172