fuel_pest_generator/
lib.rs1#![doc(html_root_url = "https://docs.rs/pest_derive")]
11#![recursion_limit = "256"]
12
13extern crate pest;
14extern crate pest_meta;
15
16extern crate proc_macro;
17extern crate proc_macro2;
18#[macro_use]
19extern crate quote;
20extern crate syn;
21
22use std::env;
23use std::fs::File;
24use std::io::{self, Read};
25use std::path::Path;
26
27use proc_macro2::TokenStream;
28use syn::{Attribute, DeriveInput, Generics, Ident, Lit, Meta};
29
30#[macro_use]
31mod macros;
32mod generator;
33
34use pest_meta::parser::{self, Rule};
35use pest_meta::{optimizer, unwrap_or_report, validator};
36
37pub fn derive_parser(input: TokenStream, include_grammar: bool) -> TokenStream {
38 let ast: DeriveInput = syn::parse2(input).unwrap();
39 let (name, generics, content) = parse_derive(ast);
40
41 let (data, path) = match content {
42 GrammarSource::File(path) => {
43 let root = env::var("CARGO_MANIFEST_DIR").unwrap_or_else(|_| ".".into());
44 let full_path = Path::new(&root).join("src/").join(&path);
45 let file_name = match full_path.file_name() {
46 Some(file_name) => file_name,
47 None => panic!("grammar attribute should point to a file"),
48 };
49
50 let data = match read_file(&full_path) {
51 Ok(data) => data,
52 Err(error) => panic!("error opening {:?}: {}", file_name, error),
53 };
54 (data, Some(path))
55 }
56 GrammarSource::Inline(content) => (content, None),
57 };
58
59 let pairs = match parser::parse(Rule::grammar_rules, data.into()) {
60 Ok(pairs) => pairs,
61 Err(error) => panic!(
62 "error parsing \n{}",
63 error.renamed_rules(|rule| match *rule {
64 Rule::grammar_rule => "rule".to_owned(),
65 Rule::_push => "PUSH".to_owned(),
66 Rule::assignment_operator => "`=`".to_owned(),
67 Rule::silent_modifier => "`_`".to_owned(),
68 Rule::atomic_modifier => "`@`".to_owned(),
69 Rule::compound_atomic_modifier => "`$`".to_owned(),
70 Rule::non_atomic_modifier => "`!`".to_owned(),
71 Rule::opening_brace => "`{`".to_owned(),
72 Rule::closing_brace => "`}`".to_owned(),
73 Rule::opening_brack => "`[`".to_owned(),
74 Rule::closing_brack => "`]`".to_owned(),
75 Rule::opening_paren => "`(`".to_owned(),
76 Rule::positive_predicate_operator => "`&`".to_owned(),
77 Rule::negative_predicate_operator => "`!`".to_owned(),
78 Rule::sequence_operator => "`&`".to_owned(),
79 Rule::choice_operator => "`|`".to_owned(),
80 Rule::optional_operator => "`?`".to_owned(),
81 Rule::repeat_operator => "`*`".to_owned(),
82 Rule::repeat_once_operator => "`+`".to_owned(),
83 Rule::comma => "`,`".to_owned(),
84 Rule::closing_paren => "`)`".to_owned(),
85 Rule::quote => "`\"`".to_owned(),
86 Rule::insensitive_string => "`^`".to_owned(),
87 Rule::range_operator => "`..`".to_owned(),
88 Rule::single_quote => "`'`".to_owned(),
89 other_rule => format!("{:?}", other_rule),
90 })
91 ),
92 };
93
94 let defaults = unwrap_or_report(validator::validate_pairs(pairs.clone()));
95 let defaults = defaults.iter().map(|span| span.as_str()).collect();
96 let ast = unwrap_or_report(parser::consume_rules(pairs));
97 let optimized = optimizer::optimize(ast);
98
99 generator::generate(name, &generics, path, optimized, defaults, include_grammar)
100}
101
102fn read_file<P: AsRef<Path>>(path: P) -> io::Result<String> {
103 let mut file = File::open(path.as_ref())?;
104 let mut string = String::new();
105 file.read_to_string(&mut string)?;
106 Ok(string)
107}
108
109#[derive(Debug, PartialEq)]
110enum GrammarSource {
111 File(String),
112 Inline(String),
113}
114
115fn parse_derive(ast: DeriveInput) -> (Ident, Generics, GrammarSource) {
116 let name = ast.ident;
117 let generics = ast.generics;
118
119 let grammar: Vec<&Attribute> = ast
120 .attrs
121 .iter()
122 .filter(|attr| match attr.parse_meta() {
123 Ok(Meta::NameValue(name_value)) => {
124 name_value.path.is_ident("grammar") || name_value.path.is_ident("grammar_inline")
125 }
126 _ => false,
127 })
128 .collect();
129
130 let argument = match grammar.len() {
131 0 => panic!("a grammar file needs to be provided with the #[grammar = \"PATH\"] or #[grammar_inline = \"GRAMMAR CONTENTS\"] attribute"),
132 1 => get_attribute(grammar[0]),
133 _ => panic!("only 1 grammar file can be provided"),
134 };
135
136 (name, generics, argument)
137}
138
139fn get_attribute(attr: &Attribute) -> GrammarSource {
140 match attr.parse_meta() {
141 Ok(Meta::NameValue(name_value)) => match name_value.lit {
142 Lit::Str(string) => {
143 if name_value.path.is_ident("grammar") {
144 GrammarSource::File(string.value())
145 } else {
146 GrammarSource::Inline(string.value())
147 }
148 }
149 _ => panic!("grammar attribute must be a string"),
150 },
151 _ => panic!("grammar attribute must be of the form `grammar = \"...\"`"),
152 }
153}
154
155#[cfg(test)]
156mod tests {
157 use super::parse_derive;
158 use super::GrammarSource;
159 use syn;
160
161 #[test]
162 fn derive_inline_file() {
163 let definition = "
164 #[other_attr]
165 #[grammar_inline = \"GRAMMAR\"]
166 pub struct MyParser<'a, T>;
167 ";
168 let ast = syn::parse_str(definition).unwrap();
169 let (_, _, filename) = parse_derive(ast);
170 assert_eq!(filename, GrammarSource::Inline("GRAMMAR".to_string()));
171 }
172
173 #[test]
174 fn derive_ok() {
175 let definition = "
176 #[other_attr]
177 #[grammar = \"myfile.pest\"]
178 pub struct MyParser<'a, T>;
179 ";
180 let ast = syn::parse_str(definition).unwrap();
181 let (_, _, filename) = parse_derive(ast);
182 assert_eq!(filename, GrammarSource::File("myfile.pest".to_string()));
183 }
184
185 #[test]
186 #[should_panic(expected = "only 1 grammar file can be provided")]
187 fn derive_multiple_grammars() {
188 let definition = "
189 #[other_attr]
190 #[grammar = \"myfile1.pest\"]
191 #[grammar = \"myfile2.pest\"]
192 pub struct MyParser<'a, T>;
193 ";
194 let ast = syn::parse_str(definition).unwrap();
195 parse_derive(ast);
196 }
197
198 #[test]
199 #[should_panic(expected = "grammar attribute must be a string")]
200 fn derive_wrong_arg() {
201 let definition = "
202 #[other_attr]
203 #[grammar = 1]
204 pub struct MyParser<'a, T>;
205 ";
206 let ast = syn::parse_str(definition).unwrap();
207 parse_derive(ast);
208 }
209}