1 // pest. The Elegant Parser
2 // Copyright (c) 2018 DragoČ™ Tiselice
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.
10 #![doc(html_root_url = "https://docs.rs/pest_derive")]
11 #![recursion_limit = "256"]
14 extern crate pest_meta
;
16 extern crate proc_macro
;
17 extern crate proc_macro2
;
24 use std
::io
::{self, Read}
;
27 use proc_macro2
::TokenStream
;
28 use syn
::{Attribute, DeriveInput, Generics, Ident, Lit, Meta}
;
34 use pest_meta
::parser
::{self, Rule}
;
35 use pest_meta
::{optimizer, unwrap_or_report, validator}
;
37 pub 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
);
41 let (data
, path
) = match content
{
42 GrammarSource
::File(ref path
) => {
43 let root
= env
::var("CARGO_MANIFEST_DIR").unwrap_or_else(|_
| ".".into());
44 let path
= Path
::new(&root
).join("src/").join(&path
);
45 let file_name
= match path
.file_name() {
46 Some(file_name
) => file_name
,
47 None
=> panic
!("grammar attribute should point to a file"),
50 let data
= match read_file(&path
) {
52 Err(error
) => panic
!("error opening {:?}: {}", file_name
, error
),
54 (data
, Some(path
.clone()))
56 GrammarSource
::Inline(content
) => (content
, None
),
59 let pairs
= match parser
::parse(Rule
::grammar_rules
, &data
) {
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
),
94 let defaults
= unwrap_or_report(validator
::validate_pairs(pairs
.clone()));
95 let ast
= unwrap_or_report(parser
::consume_rules(pairs
));
96 let optimized
= optimizer
::optimize(ast
);
98 generator
::generate(name
, &generics
, path
, optimized
, defaults
, include_grammar
)
101 fn read_file
<P
: AsRef
<Path
>>(path
: P
) -> io
::Result
<String
> {
102 let mut file
= File
::open(path
.as_ref())?
;
103 let mut string
= String
::new();
104 file
.read_to_string(&mut string
)?
;
108 #[derive(Debug, PartialEq)]
114 fn parse_derive(ast
: DeriveInput
) -> (Ident
, Generics
, GrammarSource
) {
115 let name
= ast
.ident
;
116 let generics
= ast
.generics
;
118 let grammar
: Vec
<&Attribute
> = ast
121 .filter(|attr
| match attr
.interpret_meta() {
122 Some(Meta
::NameValue(name_value
)) => {
123 (name_value
.ident
== "grammar" || name_value
.ident
== "grammar_inline")
129 let argument
= match grammar
.len() {
130 0 => panic
!("a grammar file needs to be provided with the #[grammar = \"PATH\"] or #[grammar_inline = \"GRAMMAR CONTENTS\"] attribute"),
131 1 => get_attribute(grammar
[0]),
132 _
=> panic
!("only 1 grammar file can be provided"),
135 (name
, generics
, argument
)
138 fn get_attribute(attr
: &Attribute
) -> GrammarSource
{
139 match attr
.interpret_meta() {
140 Some(Meta
::NameValue(name_value
)) => match name_value
.lit
{
141 Lit
::Str(string
) => {
142 if name_value
.ident
== "grammar" {
143 GrammarSource
::File(string
.value())
145 GrammarSource
::Inline(string
.value())
148 _
=> panic
!("grammar attribute must be a string"),
150 _
=> panic
!("grammar attribute must be of the form `grammar = \"...\"`"),
156 use super::parse_derive
;
157 use super::GrammarSource
;
161 fn derive_inline_file() {
164 #[grammar_inline = \"GRAMMAR\"]
165 pub struct MyParser<'a, T>;
167 let ast
= syn
::parse_str(definition
).unwrap();
168 let (_
, _
, filename
) = parse_derive(ast
);
169 assert_eq
!(filename
, GrammarSource
::Inline("GRAMMAR".to_string()));
176 #[grammar = \"myfile.pest\"]
177 pub struct MyParser<'a, T>;
179 let ast
= syn
::parse_str(definition
).unwrap();
180 let (_
, _
, filename
) = parse_derive(ast
);
181 assert_eq
!(filename
, GrammarSource
::File("myfile.pest".to_string()));
185 #[should_panic(expected = "only 1 grammar file can be provided")]
186 fn derive_multiple_grammars() {
189 #[grammar = \"myfile1.pest\"]
190 #[grammar = \"myfile2.pest\"]
191 pub struct MyParser<'a, T>;
193 let ast
= syn
::parse_str(definition
).unwrap();
198 #[should_panic(expected = "grammar attribute must be a string")]
199 fn derive_wrong_arg() {
203 pub struct MyParser<'a, T>;
205 let ast
= syn
::parse_str(definition
).unwrap();