pest_generator/
parse_derive.rs
1use syn::{Attribute, DeriveInput, Expr, ExprLit, Generics, Ident, Lit, Meta};
13
14#[derive(Debug, PartialEq)]
15pub(crate) enum GrammarSource {
16 File(String),
17 Inline(String),
18}
19
20pub struct ParsedDerive {
22 pub name: Ident,
24 pub generics: Generics,
26 pub non_exhaustive: bool,
28}
29
30pub(crate) fn parse_derive(ast: DeriveInput) -> (ParsedDerive, Vec<GrammarSource>) {
31 let name = ast.ident;
32 let generics = ast.generics;
33
34 let grammar: Vec<&Attribute> = ast
35 .attrs
36 .iter()
37 .filter(|attr| {
38 let path = attr.meta.path();
39 path.is_ident("grammar") || path.is_ident("grammar_inline")
40 })
41 .collect();
42
43 if grammar.is_empty() {
44 panic!("a grammar file needs to be provided with the #[grammar = \"PATH\"] or #[grammar_inline = \"GRAMMAR CONTENTS\"] attribute");
45 }
46
47 let mut grammar_sources = Vec::with_capacity(grammar.len());
48 for attr in grammar {
49 grammar_sources.push(get_attribute(attr))
50 }
51
52 let non_exhaustive = ast
53 .attrs
54 .iter()
55 .any(|attr| attr.meta.path().is_ident("non_exhaustive"));
56
57 (
58 ParsedDerive {
59 name,
60 generics,
61 non_exhaustive,
62 },
63 grammar_sources,
64 )
65}
66
67fn get_attribute(attr: &Attribute) -> GrammarSource {
68 match &attr.meta {
69 Meta::NameValue(name_value) => match &name_value.value {
70 Expr::Lit(ExprLit {
71 lit: Lit::Str(string),
72 ..
73 }) => {
74 if name_value.path.is_ident("grammar") {
75 GrammarSource::File(string.value())
76 } else {
77 GrammarSource::Inline(string.value())
78 }
79 }
80 _ => panic!("grammar attribute must be a string"),
81 },
82 _ => panic!("grammar attribute must be of the form `grammar = \"...\"`"),
83 }
84}
85
86#[cfg(test)]
87mod tests {
88 use super::parse_derive;
89 use super::GrammarSource;
90
91 #[test]
92 fn derive_inline_file() {
93 let definition = "
94 #[other_attr]
95 #[grammar_inline = \"GRAMMAR\"]
96 pub struct MyParser<'a, T>;
97 ";
98 let ast = syn::parse_str(definition).unwrap();
99 let (_, filenames) = parse_derive(ast);
100 assert_eq!(filenames, [GrammarSource::Inline("GRAMMAR".to_string())]);
101 }
102
103 #[test]
104 fn derive_ok() {
105 let definition = "
106 #[other_attr]
107 #[grammar = \"myfile.pest\"]
108 pub struct MyParser<'a, T>;
109 ";
110 let ast = syn::parse_str(definition).unwrap();
111 let (parsed_derive, filenames) = parse_derive(ast);
112 assert_eq!(filenames, [GrammarSource::File("myfile.pest".to_string())]);
113 assert!(!parsed_derive.non_exhaustive);
114 }
115
116 #[test]
117 fn derive_multiple_grammars() {
118 let definition = "
119 #[other_attr]
120 #[grammar = \"myfile1.pest\"]
121 #[grammar = \"myfile2.pest\"]
122 pub struct MyParser<'a, T>;
123 ";
124 let ast = syn::parse_str(definition).unwrap();
125 let (_, filenames) = parse_derive(ast);
126 assert_eq!(
127 filenames,
128 [
129 GrammarSource::File("myfile1.pest".to_string()),
130 GrammarSource::File("myfile2.pest".to_string())
131 ]
132 );
133 }
134
135 #[test]
136 fn derive_nonexhaustive() {
137 let definition = "
138 #[non_exhaustive]
139 #[grammar = \"myfile.pest\"]
140 pub struct MyParser<'a, T>;
141 ";
142 let ast = syn::parse_str(definition).unwrap();
143 let (parsed_derive, filenames) = parse_derive(ast);
144 assert_eq!(filenames, [GrammarSource::File("myfile.pest".to_string())]);
145 assert!(parsed_derive.non_exhaustive);
146 }
147
148 #[test]
149 #[should_panic(expected = "grammar attribute must be a string")]
150 fn derive_wrong_arg() {
151 let definition = "
152 #[other_attr]
153 #[grammar = 1]
154 pub struct MyParser<'a, T>;
155 ";
156 let ast = syn::parse_str(definition).unwrap();
157 parse_derive(ast);
158 }
159
160 #[test]
161 #[should_panic(
162 expected = "a grammar file needs to be provided with the #[grammar = \"PATH\"] or #[grammar_inline = \"GRAMMAR CONTENTS\"] attribute"
163 )]
164 fn derive_no_grammar() {
165 let definition = "
166 #[other_attr]
167 pub struct MyParser<'a, T>;
168 ";
169 let ast = syn::parse_str(definition).unwrap();
170 parse_derive(ast);
171 }
172}