pest_generator/
parse_derive.rs

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
12use 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
20/// Parsed information of the derive and the attributes.
21pub 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
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}