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 | //! Types and helpers to parse the input of the derive macro. |
11 | |
12 | use syn::{Attribute, DeriveInput, Expr, ExprLit, Generics, Ident, Lit, Meta}; |
13 | |
14 | #[derive (Debug, PartialEq)] |
15 | pub(crate) enum GrammarSource { |
16 | File(String), |
17 | Inline(String), |
18 | } |
19 | |
20 | /// Parsed information of the derive and the attributes. |
21 | pub struct ParsedDerive { |
22 | /// The identifier of the deriving struct, union, or enum. |
23 | pub name: Ident, |
24 | /// The generics of the deriving struct, union, or enum. |
25 | pub generics: Generics, |
26 | /// Indicates whether the 'non_exhaustive' attribute is added to the 'Rule' enum. |
27 | pub non_exhaustive: bool, |
28 | } |
29 | |
30 | pub(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 | |
67 | fn get_attribute(attr: &Attribute) -> GrammarSource { |
68 | match &attr.meta { |
69 | Meta::NameValue(name_value: &MetaNameValue) => match &name_value.value { |
70 | Expr::Lit(ExprLit { |
71 | lit: Lit::Str(string: &LitStr), |
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)] |
87 | mod 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 | } |
173 | |