├── .gitignore ├── Cargo.toml ├── LICENSE-APACHE ├── LICENSE-MIT ├── README.md └── src ├── error_macros.rs ├── fields.rs ├── lib.rs ├── tests.rs └── variants.rs /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | Cargo.lock 3 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "derive-syn-parse" 3 | version = "0.2.0" 4 | authors = ["sharnoff "] 5 | license = "MIT OR Apache-2.0" 6 | description = "Derive macro for `syn::parse::Parse`" 7 | repository = "https://github.com/sharnoff/derive-syn-parse" 8 | categories = ["development-tools::procedural-macro-helpers"] 9 | edition = "2018" 10 | 11 | [lib] 12 | proc-macro = true 13 | 14 | [features] 15 | full = ["syn/full"] 16 | 17 | [dependencies] 18 | syn = { version = "2", features = ["derive", "parsing"] } 19 | quote = "1" 20 | proc-macro2 = "1" 21 | 22 | # Testing relies on being able to compare the output of the macros, which is why we need a 23 | # different version of syn 24 | [dev-dependencies] 25 | syn = { version = "2", features = ["full", "extra-traits"] } 26 | -------------------------------------------------------------------------------- /LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Permission is hereby granted, free of charge, to any 2 | person obtaining a copy of this software and associated 3 | documentation files (the "Software"), to deal in the 4 | Software without restriction, including without 5 | limitation the rights to use, copy, modify, merge, 6 | publish, distribute, sublicense, and/or sell copies of 7 | the Software, and to permit persons to whom the Software 8 | is furnished to do so, subject to the following 9 | conditions: 10 | 11 | The above copyright notice and this permission notice 12 | shall be included in all copies or substantial portions 13 | of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 16 | ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 17 | TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 18 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 19 | SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 20 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 22 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 23 | DEALINGS IN THE SOFTWARE. 24 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![crates.io](https://img.shields.io/crates/v/derive-syn-parse.svg)](https://crates.io/crates/derive-syn-parse) 2 | [![docs.rs](https://docs.rs/derive-syn-parse/badge.svg)](https://docs.rs/derive-syn-parse) 3 | 4 | # `derive-syn-parse`: A derive macro for `syn`'s `Parse` trait 5 | 6 | This is a fairly straightforward derive macro that produces an implementation `syn::parse::Parse` 7 | for the type it's applied to. 8 | 9 | A common pattern when writing custom `syn` parsers is repeating `: input.parse()?` for 10 | each field in the output. This crate's `#[derive(Parse)]` handles that for you, with some helpful 11 | extra customization. 12 | 13 | ## Usage 14 | 15 | Using this crate is as simple as adding it to your 'Cargo.toml' and importing the derive macro: 16 | 17 | ```toml 18 | # Cargo.toml 19 | 20 | [dependencies] 21 | derive-syn-parse = "0.2.0" 22 | ``` 23 | 24 | ```rust 25 | // your_file.rs 26 | use derive_syn_parse::Parse; 27 | 28 | #[derive(Parse)] 29 | struct CustomParsable { 30 | // ... 31 | } 32 | ``` 33 | 34 | The derived implementation of `Parse` will always parse in the order that the fields are given. 35 | Detailed information about the various field attributes available is given in the 36 | [crate documentation](https://docs.rs/derive-syn-parse). 37 | 38 | This crate is primarily intended for users who are already making heavy use of `syn` and wish to 39 | reduce the amount of boilerplate code required. 40 | 41 | ## Motivation 42 | 43 | When writing rust code that makes heavy use of `syn`'s parsing functionality, we often end up 44 | writing things like: 45 | ```rust 46 | use syn::parse::{Parse, ParseStream}; 47 | use syn::{Ident, Token, Type}; 48 | 49 | // A simplified struct field 50 | // 51 | // x: i32 52 | struct MyField { 53 | ident: Ident, 54 | colon_token: Token![:], 55 | ty: Type, 56 | } 57 | 58 | impl Parse for MyField { 59 | fn parse(input: ParseStream) -> syn::Result { 60 | Ok(MyField { 61 | ident: input.parse()?, 62 | colon_token: input.parse()?, 63 | ty: input.parse()?, 64 | }) 65 | } 66 | } 67 | ``` 68 | This is really repetitive! Ideally, we'd like to just `#[derive(Parse)]` and have it work. And 69 | so we can! (for the most part) Adding `#[derive(Parse)]` to the previous struct produces an 70 | equivalent implementation of `Parse`: 71 | ```rust 72 | use syn::{Ident, Token, Type}; 73 | use derive_syn_parse::Parse; 74 | 75 | #[derive(Parse)] 76 | struct MyField { 77 | ident: Ident, 78 | colon_token: Token![:], 79 | ty: Type, 80 | } 81 | ``` 82 | 83 | Of course, there are more complicated cases. But - even though they're complicated, many of them are 84 | still covered by the various advanced features provided! For more information, see the 85 | [crate documentation](https://docs.rs/derive-syn-parse). 86 | -------------------------------------------------------------------------------- /src/error_macros.rs: -------------------------------------------------------------------------------- 1 | //! Macros for producing errors within the derive macro 2 | 3 | macro_rules! invalid_input_kind { 4 | ($arm:expr) => {{ 5 | return syn::Error::new( 6 | $arm.span(), 7 | "`#[derive(Parse)]` is only available on structs", 8 | ) 9 | .to_compile_error(); 10 | }}; 11 | } 12 | 13 | // Handle a `syn::Result` inside of a function that returns `proc_macro::TokenStream` by turning it 14 | // into a compile error if it's an error 15 | macro_rules! handle_syn_result { 16 | ( 17 | @default_impl_from($generics_intro:ident, $ident:ident, $generics_args:ident, $where_clause:ident), 18 | $result:expr 19 | ) => {{ 20 | let res: syn::Result<_> = $result; 21 | match res { 22 | Ok(value) => value, 23 | Err(e) => { 24 | let mut ts = quote! { 25 | impl #$generics_intro ::syn::parse::Parse for #$ident #$generics_args #$where_clause { 26 | fn parse(input: ::syn::parse::ParseStream) -> ::syn::Result { 27 | unimplemented!("failed to derive `Parse`") 28 | } 29 | } 30 | }; 31 | ts.extend(e.to_compile_error()); 32 | return ts; 33 | } 34 | } 35 | }}; 36 | 37 | ($result:expr) => {{ 38 | let res: syn::Result<_> = $result; 39 | match res { 40 | Err(e) => return e.to_compile_error().into(), 41 | Ok(value) => value, 42 | } 43 | }}; 44 | } 45 | -------------------------------------------------------------------------------- /src/fields.rs: -------------------------------------------------------------------------------- 1 | //! Handling for generating a `Parse` implementation using fields 2 | 3 | use proc_macro2::{Span, TokenStream}; 4 | use quote::{format_ident, quote, quote_spanned, ToTokens}; 5 | use std::convert::{TryFrom, TryInto}; 6 | use syn::parse::{Parse, ParseStream}; 7 | use syn::spanned::Spanned; 8 | use syn::{AttrStyle, Attribute, Expr, Fields, Ident, Path, Result, Token, Type}; 9 | 10 | pub(crate) fn generate_fn_body( 11 | base_tyname: &impl ToTokens, 12 | fields: Fields, 13 | with_return: bool, 14 | ) -> Result { 15 | let initialize_self = initialize_type_or_variant(base_tyname, &fields); 16 | let parse_fields = fields 17 | .into_iter() 18 | .enumerate() 19 | .map(parse_field) 20 | .collect::>>()?; 21 | 22 | let maybe_return = match with_return { 23 | true => Token![return](Span::call_site()).into_token_stream(), 24 | false => TokenStream::new(), 25 | }; 26 | 27 | Ok(quote! { 28 | #( #parse_fields )* 29 | 30 | #maybe_return Ok(#initialize_self) 31 | }) 32 | } 33 | 34 | enum FieldAttr { 35 | Inside(Ident), 36 | Tree(TreeKind), 37 | Call(Expr), 38 | ParseTerminated(Path), 39 | Peek(PeekAttr), 40 | Prefix(NeighborAttr), 41 | Postfix(NeighborAttr), 42 | } 43 | 44 | enum TreeKind { 45 | Paren, 46 | Bracket, 47 | Brace, 48 | } 49 | 50 | enum ParseMethod { 51 | Tree(TreeKind, Span), 52 | Call(Expr), 53 | ParseTerminated(Path), 54 | Default, 55 | } 56 | 57 | enum PeekAttr { 58 | Peek(Expr), 59 | PeekWith(Expr), 60 | ParseIf(Expr), 61 | } 62 | 63 | // An attribute that's either a #[prefix] or #[postfix] directive. These can either be 64 | // free-standing (i.e. discarded immediately after parsing) or named - saved for later parsing. 65 | // 66 | // They can additionally be specified as inside a particular token stream, so the full syntax is: 67 | // 68 | // "#[prefix(" [ "as" ] [ "in" ] ")]" 69 | struct NeighborAttr { 70 | parse_ty: Type, 71 | maybe_named: Option, 72 | maybe_inside: Option, 73 | } 74 | 75 | impl Parse for NeighborAttr { 76 | fn parse(input: ParseStream) -> syn::Result { 77 | let parse_ty = input.parse()?; 78 | let as_token: Option = input.parse()?; 79 | let maybe_named = match as_token.is_some() { 80 | true => Some(input.parse()?), 81 | false => None, 82 | }; 83 | 84 | let in_token: Option = input.parse()?; 85 | let maybe_inside = match in_token.is_some() { 86 | true => Some(input.parse()?), 87 | false => None, 88 | }; 89 | 90 | Ok(NeighborAttr { 91 | parse_ty, 92 | maybe_named, 93 | maybe_inside, 94 | }) 95 | } 96 | } 97 | 98 | impl ParseMethod { 99 | fn is_default(&self) -> bool { 100 | matches!(self, Self::Default) 101 | } 102 | } 103 | 104 | struct FieldAttrs { 105 | prefix: Vec, 106 | postfix: Vec, 107 | inside: Option, 108 | parse_method: ParseMethod, 109 | peek: Option, 110 | } 111 | 112 | struct ParseField { 113 | required_var_defs: Option, 114 | parse_expr: TokenStream, 115 | pre_parse: TokenStream, 116 | post_parse: TokenStream, 117 | } 118 | 119 | // This needs to return tokenstreams because tuple structs use integer indices as field names 120 | // 121 | // For example: We'd end up writing the following 122 | // Ok(Self { 123 | // 0: _field_0, 124 | // 1: _field_1, 125 | // ... 126 | // }) 127 | // 128 | // Otherwise, we would totally just return a list of identifiers 129 | fn initialize_type_or_variant(name: &impl ToTokens, fields: &syn::Fields) -> TokenStream { 130 | use syn::Fields::{Named, Unit, Unnamed}; 131 | 132 | match fields { 133 | Unit => name.to_token_stream(), 134 | Named(fields) => { 135 | let iter = fields.named.iter().map(|f| { 136 | f.ident 137 | .as_ref() 138 | .expect("named field was unnamed! the impossible is now possible!") 139 | }); 140 | quote! { 141 | #name { #( #iter, )* } 142 | } 143 | } 144 | Unnamed(fields) => { 145 | let iter = fields 146 | .unnamed 147 | .iter() 148 | .enumerate() 149 | .map(|(i, f)| field_name_for_idx(i, f.span())); 150 | quote! { 151 | #name( #( #iter, )* ) 152 | } 153 | } 154 | } 155 | } 156 | 157 | fn field_name_for_idx(idx: usize, span: Span) -> Ident { 158 | format_ident!("_field_{}", idx, span = span) 159 | } 160 | 161 | fn parse_field((idx, field): (usize, syn::Field)) -> Result { 162 | let span = field.span(); 163 | 164 | let assigned_name = field.ident.unwrap_or_else(|| field_name_for_idx(idx, span)); 165 | 166 | let attrs = (field.attrs) 167 | .into_iter() 168 | .filter_map(try_as_field_attr) 169 | .collect::>>()? 170 | .try_into()?; 171 | 172 | let ParseField { 173 | required_var_defs, 174 | parse_expr, 175 | pre_parse, 176 | post_parse, 177 | } = handle_field_attrs(&assigned_name, field.ty.span(), attrs); 178 | 179 | // convert the Option to an iterator, so we can declare variables conditionally: 180 | let required_var_defs = required_var_defs.into_iter(); 181 | let field_ty = field.ty; 182 | Ok(quote_spanned! { 183 | span=> 184 | #( let #required_var_defs; )* 185 | #pre_parse 186 | let #assigned_name: #field_ty = #parse_expr; 187 | #post_parse 188 | }) 189 | } 190 | 191 | fn try_as_field_attr(attr: Attribute) -> Option> { 192 | let name = attr.path().get_ident()?.to_string(); 193 | let span = attr.span(); 194 | 195 | macro_rules! expect_outer_attr { 196 | ($name:literal) => {{ 197 | if let AttrStyle::Inner(_) = attr.style { 198 | return Some(Err(syn::Error::new( 199 | span, 200 | concat!( 201 | "the `#[", 202 | $name, 203 | "]` parsing attribute can only be used as an outer attribute" 204 | ), 205 | ))); 206 | } 207 | }}; 208 | } 209 | 210 | #[rustfmt::skip] 211 | macro_rules! expect_no_attr_args { 212 | ($name:expr) => {{ 213 | use syn::Meta::*; 214 | match attr.meta { 215 | List(args) => return Some(Err(syn::Error::new( 216 | span, 217 | format!("the `#[{}]` parsing attribute does not expect arguments {:?}", $name, args.tokens.to_string()), 218 | ))), 219 | NameValue(val) => return Some(Err(syn::Error::new( 220 | span, 221 | format!("the `#[{}]` parsing attribute does not expect value {:?}", $name, val.value.to_token_stream().to_string()), 222 | ))), 223 | _ => {} 224 | } 225 | }}; 226 | } 227 | 228 | macro_rules! expect_parenthesis { 229 | ($attr_lit:literal) => {{ 230 | use syn::{MacroDelimiter::*, Meta}; 231 | match attr.meta { 232 | Meta::List(ref ml) => match ml.delimiter { 233 | Paren(_) => {} 234 | Brace(_) => { 235 | return Some(Err(syn::Error::new( 236 | span, 237 | concat!( 238 | "expected parenthesis in `#[", 239 | $attr_lit, 240 | "(...)]`, found braces" 241 | ), 242 | ))) 243 | } 244 | Bracket(_) => { 245 | return Some(Err(syn::Error::new( 246 | span, 247 | concat!( 248 | "expected parenthesis in `#[", 249 | $attr_lit, 250 | "(...)]`, found brackets" 251 | ), 252 | ))) 253 | } 254 | }, 255 | _ => { 256 | return Some(Err(syn::Error::new( 257 | span, 258 | concat!("expected parenthesis in `#[", $attr_lit, "(...)]`"), 259 | ))) 260 | } 261 | } 262 | }}; 263 | } 264 | match name.as_str() { 265 | "inside" => { 266 | expect_outer_attr!("inside(...)"); 267 | expect_parenthesis!("inside"); 268 | Some( 269 | attr.parse_args() 270 | .map(move |id| (FieldAttr::Inside(id), span)), 271 | ) 272 | } 273 | "call" => { 274 | expect_outer_attr!("call(...)"); 275 | expect_parenthesis!("call"); 276 | Some(attr.parse_args().map(move |id| (FieldAttr::Call(id), span))) 277 | } 278 | "parse_terminated" => { 279 | expect_outer_attr!("parse_terminated(...)"); 280 | expect_parenthesis!("parse_terminated"); 281 | Some( 282 | attr.parse_args() 283 | .map(move |id| (FieldAttr::ParseTerminated(id), span)), 284 | ) 285 | } 286 | "paren" => { 287 | expect_outer_attr!("paren"); 288 | expect_no_attr_args!("paren"); 289 | Some(Ok((FieldAttr::Tree(TreeKind::Paren), span))) 290 | } 291 | "bracket" => { 292 | expect_outer_attr!("bracket"); 293 | expect_no_attr_args!("bracket"); 294 | Some(Ok((FieldAttr::Tree(TreeKind::Bracket), span))) 295 | } 296 | "brace" => { 297 | expect_outer_attr!("brace"); 298 | expect_no_attr_args!("brace"); 299 | Some(Ok((FieldAttr::Tree(TreeKind::Brace), span))) 300 | } 301 | "peek" => { 302 | expect_outer_attr!("peek(...)"); 303 | expect_parenthesis!("peek"); 304 | Some( 305 | attr.parse_args() 306 | .map(move |id| (FieldAttr::Peek(PeekAttr::Peek(id)), span)), 307 | ) 308 | } 309 | "peek_with" => { 310 | expect_outer_attr!("peek_with(...)"); 311 | expect_parenthesis!("peek_with"); 312 | Some( 313 | attr.parse_args() 314 | .map(move |id| (FieldAttr::Peek(PeekAttr::PeekWith(id)), span)), 315 | ) 316 | } 317 | "parse_if" => { 318 | expect_outer_attr!("parse_if(...)"); 319 | expect_parenthesis!("parse_if"); 320 | Some( 321 | attr.parse_args() 322 | .map(move |id| (FieldAttr::Peek(PeekAttr::ParseIf(id)), span)), 323 | ) 324 | } 325 | "prefix" => { 326 | expect_outer_attr!("prefix(...)"); 327 | expect_parenthesis!("prefix"); 328 | Some( 329 | attr.parse_args() 330 | .map(move |id| (FieldAttr::Prefix(id), span)), 331 | ) 332 | } 333 | "postfix" => { 334 | expect_outer_attr!("postifx(...)"); 335 | expect_parenthesis!("postfix"); 336 | Some( 337 | attr.parse_args() 338 | .map(move |id| (FieldAttr::Postfix(id), span)), 339 | ) 340 | } 341 | _ => None, 342 | } 343 | } 344 | 345 | impl TryFrom> for FieldAttrs { 346 | type Error = syn::Error; 347 | 348 | fn try_from(vec: Vec<(FieldAttr, Span)>) -> Result { 349 | use FieldAttr::{Call, Inside, ParseTerminated, Peek, Postfix, Prefix, Tree}; 350 | 351 | let mut inside = None; 352 | let mut peek = None; 353 | let mut parse_method = ParseMethod::Default; 354 | let mut prefix = Vec::new(); 355 | let mut postfix = Vec::new(); 356 | 357 | for (attr, span) in vec { 358 | match attr { 359 | Tree(_) | Call(_) | ParseTerminated(_) if !parse_method.is_default() => { 360 | return Err(syn::Error::new(span, "parsing method specified twice")); 361 | } 362 | Inside(_) if inside.is_some() => { 363 | return Err(syn::Error::new( 364 | span, 365 | "containing parse stream is specified twice", 366 | )); 367 | } 368 | Peek(_) if peek.is_some() => { 369 | return Err(syn::Error::new(span, "peeking can only be specified once")); 370 | } 371 | 372 | Call(expr) => parse_method = ParseMethod::Call(expr), 373 | ParseTerminated(path) => parse_method = ParseMethod::ParseTerminated(path), 374 | Tree(kind) => parse_method = ParseMethod::Tree(kind, span), 375 | Inside(name) => inside = Some(name), 376 | Peek(p) => peek = Some(p), 377 | 378 | Prefix(_) if inside.is_some() => { 379 | return Err(syn::Error::new( 380 | span, 381 | "`#[prefix]` cannot be used after `#[inside]`. Perhaps try `#[prefix( in )]`?", 382 | )); 383 | } 384 | 385 | Postfix(_) if inside.is_some() => { 386 | return Err(syn::Error::new( 387 | span, 388 | "`#[postfix]` cannot be used after `#[inside]`. Perhaps try `#[prefix( in )]`?", 389 | )); 390 | } 391 | 392 | Prefix(attr) => prefix.push(attr), 393 | Postfix(attr) => postfix.push(attr), 394 | } 395 | } 396 | 397 | Ok(FieldAttrs { 398 | prefix, 399 | postfix, 400 | inside, 401 | parse_method, 402 | peek, 403 | }) 404 | } 405 | } 406 | 407 | fn handle_field_attrs(field_name: &Ident, ty_span: Span, attrs: FieldAttrs) -> ParseField { 408 | use ParseMethod::{Call, Default, ParseTerminated, Tree}; 409 | 410 | let input_source = attrs 411 | .inside 412 | .as_ref() 413 | .map(tree_name) 414 | .unwrap_or_else(crate::parse_input); 415 | 416 | let required_var_defs; 417 | let mut parse_expr; 418 | 419 | match attrs.parse_method { 420 | Default => { 421 | required_var_defs = None; 422 | parse_expr = quote_spanned! { ty_span=> #input_source.parse()? }; 423 | } 424 | Call(expr) => { 425 | required_var_defs = None; 426 | parse_expr = quote_spanned!(expr.span()=> { 427 | let result: ::syn::Result<_> = (#expr)(&#input_source); 428 | result? 429 | }); 430 | } 431 | ParseTerminated(path) => { 432 | required_var_defs = None; 433 | parse_expr = quote_spanned! { path.span()=> #input_source.parse_terminated(#path)? }; 434 | } 435 | Tree(tree_kind, span) => { 436 | required_var_defs = Some(tree_name(field_name)); 437 | 438 | let macro_name = tree_kind.macro_name(); 439 | let tree_name = tree_name(field_name); 440 | parse_expr = quote_spanned! { span=> ::syn::#macro_name!(#tree_name in #input_source) }; 441 | } 442 | } 443 | 444 | if let Some(p) = attrs.peek { 445 | parse_expr = match p { 446 | PeekAttr::Peek(expr) => quote_spanned!(expr.span()=> match #input_source.peek(#expr) { 447 | true => Some(#parse_expr), 448 | false => None, 449 | }), 450 | PeekAttr::PeekWith(expr) => quote_spanned!(expr.span()=> match (#expr)(#input_source) { 451 | true => Some(#parse_expr), 452 | false => None, 453 | }), 454 | PeekAttr::ParseIf(expr) => quote_spanned!(expr.span()=> match #expr { 455 | true => Some(#parse_expr), 456 | false => None, 457 | }), 458 | }; 459 | } 460 | 461 | let neighbor_map = |na: NeighborAttr| -> TokenStream { 462 | let assigned_name = na 463 | .maybe_named 464 | .unwrap_or_else(|| Ident::new("_", Span::call_site())); 465 | let source = na 466 | .maybe_inside 467 | .as_ref() 468 | .map(tree_name) 469 | .unwrap_or_else(crate::parse_input); 470 | let parse_ty = na.parse_ty; 471 | quote! { 472 | let #assigned_name: #parse_ty = #source.parse()?; 473 | } 474 | }; 475 | 476 | let pre_parse = attrs.prefix.into_iter().map(neighbor_map).collect(); 477 | let post_parse = attrs.postfix.into_iter().map(neighbor_map).collect(); 478 | 479 | ParseField { 480 | required_var_defs, 481 | parse_expr, 482 | pre_parse, 483 | post_parse, 484 | } 485 | } 486 | 487 | fn tree_name(field_name: &Ident) -> Ident { 488 | format_ident!("__{}_backing_token_stream", field_name) 489 | } 490 | 491 | impl TreeKind { 492 | // Gives the name of the syn macro that corresponds to attempting parse the next token as a 493 | // certain group 494 | fn macro_name(&self) -> Ident { 495 | let span = Span::call_site(); 496 | match self { 497 | TreeKind::Paren => Ident::new("parenthesized", span), 498 | TreeKind::Bracket => Ident::new("bracketed", span), 499 | TreeKind::Brace => Ident::new("braced", span), 500 | } 501 | } 502 | } 503 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Derive macro for [`syn::parse::Parse`] 2 | //! 3 | //! A common pattern when writing custom `syn` parsers is repeating `: input.parse()?` for 4 | //! each field in the output. `#[derive(Parse)]` handles that for you, with some extra helpful 5 | //! customization. 6 | //! 7 | //! ## Usage 8 | //! 9 | //! Using this crate is as simple as adding it to your 'Cargo.toml' and importing the derive macro: 10 | //! 11 | //! ```toml 12 | //! # Cargo.toml 13 | //! 14 | //! [dependencies] 15 | //! derive-syn-parse = "0.2.0" 16 | //! ``` 17 | //! 18 | //! ``` 19 | //! // your_file.rs 20 | //! use derive_syn_parse::Parse; 21 | //! 22 | //! #[derive(Parse)] 23 | //! struct CustomParsable { 24 | //! // ... 25 | //! } 26 | //! ``` 27 | //! 28 | //! The derived implementation of `Parse` always parses in the order that the fields are given. 29 | //! **Note that deriving `Parse` is also available on enums.** For more information, see the 30 | //! [dedicated section](#enum-parsing). 31 | //! 32 | //! This crate is intended for users who are already making heavy use of `syn`. 33 | //! 34 | //! ## Motivation 35 | //! 36 | //! When writing rust code that makes heavy use of `syn`'s parsing functionality, we often end up 37 | //! writing things like: 38 | //! ``` 39 | //! use syn::parse::{Parse, ParseStream}; 40 | //! use syn::{Ident, Token, Type}; 41 | //! 42 | //! // A simplified struct field 43 | //! // 44 | //! // x: i32 45 | //! struct MyField { 46 | //! ident: Ident, 47 | //! colon_token: Token![:], 48 | //! ty: Type, 49 | //! } 50 | //! 51 | //! impl Parse for MyField { 52 | //! fn parse(input: ParseStream) -> syn::Result { 53 | //! Ok(MyField { 54 | //! ident: input.parse()?, 55 | //! colon_token: input.parse()?, 56 | //! ty: input.parse()?, 57 | //! }) 58 | //! } 59 | //! } 60 | //! ``` 61 | //! This is really repetitive! Ideally, we'd like to just `#[derive(Parse)]` and have it work. And 62 | //! so we can! (for the most part) Adding `#[derive(Parse)]` to the previous struct produces an 63 | //! equivalent implementation of `Parse`: 64 | //! ``` 65 | //! use syn::{Ident, Token, Type}; 66 | //! use derive_syn_parse::Parse; 67 | //! 68 | //! #[derive(Parse)] 69 | //! struct MyField { 70 | //! ident: Ident, 71 | //! colon_token: Token![:], 72 | //! ty: Type, 73 | //! } 74 | //! ``` 75 | //! 76 | //! Of course, there are more complicated cases. This is mainly covered below in the 'Advanced 77 | //! Usage' section. 78 | //! 79 | //! ## Advanced Usage 80 | //! 81 | //! There are a few different facilities provided here, including: 82 | //! * Enum variant parsing, 83 | //! * Conditional field parsing, 84 | //! * Parsing within token trees (parens, brackets, etc.), 85 | //! * And much more! 86 | //! 87 | //! Each of the below sections can be expanded to view detailed information about how to use a 88 | //! particular component. Be warned - each section assumes a fair amount of knowledge about the relevant 89 | //! `syn` features. 90 | // 91 | // 92 | // ---------- SECTION: Enum Parsing ---------- 93 | // 94 | //!
Enum parsing 95 | //! 96 | //! Parsing enums is a complex feature. When writing manual implementations of 97 | //! `Parse`, it doesn't come up as often, but there are also typically *many* ways to do it: 98 | //! `syn` provides both forking the `ParseBuffer` *and* peeking to handle this, with the suggestion that 99 | //! peeking be preferred. 100 | //! 101 | //! This library does not support forking; it tends to suffer from poor error messages and general 102 | //! inefficiency. That being said, manual implementations of `Parse` can and should be written in the 103 | //! rare cases when this library is insufficient. 104 | //! 105 | //! We support peeking in a couple differnet ways - with the `#[peek]` and `#[peek_with]` attributes. 106 | //! One peeking attribute is required for each `enum` variant. The general syntax tends to look like: 107 | //! 108 | //! ```text 109 | //! #[peek($TYPE, name = $NAME)] 110 | //! ``` 111 | //! and 112 | //! ```text 113 | //! #[peek_with($EXPR, name = $NAME)] 114 | //! ``` 115 | //! The name is provided in order to construct useful error messages when input doesn't match any of the 116 | //! variants. 117 | //! 118 | //! These essentially translate to: 119 | //! ``` 120 | //! if input.peek($TYPE) { 121 | //! // parse variant 122 | //! } else { 123 | //! // parse other variants 124 | //! } 125 | //! ``` 126 | //! and 127 | //! ``` 128 | //! if ($EXPR)(input) { 129 | //! // parse variant 130 | //! } else { 131 | //! // parse other variants 132 | //! } 133 | //! ``` 134 | //!
135 | // 136 | // 137 | // ---------- SECTION: Token Trees ---------- 138 | // 139 | //!
Token Trees (parens, brackets, braces) 140 | //! 141 | //! If derive macros had access to type information, we could auto-detect when a field contains any 142 | //! of `syn::token::{Paren, Bracket, Brace}`. Unfortunately, we can't - and these don't implement 143 | //! `Parse`, so they each have their own special attribute to mark them: `#[paren]`, `#[bracket]`, 144 | //! and `#[brace]`, respectively. 145 | //! 146 | //! These are typically used with the `#[inside]` attribute, which indicates that a field should be 147 | //! parsed inside a particular named token tree. This might look like: 148 | //! 149 | //! ``` 150 | //! use derive_syn_parse::Parse; 151 | //! use syn::{Ident, token, Expr}; 152 | //! 153 | //! // Parses a simple function call - something like 154 | //! // 155 | //! // so_long(and_thanks + for_all * the_fish) 156 | //! #[derive(Parse)] 157 | //! struct SingleArgFn { 158 | //! name: Ident, 159 | //! #[paren] 160 | //! arg_paren: token::Paren, 161 | //! #[inside(arg_paren)] 162 | //! arg: Expr, 163 | //! } 164 | //! ``` 165 | //! 166 | //! The `#[inside]` attribute can - of course - be repeated with multiple token trees, though this 167 | //! may not necessarily produce the most readable type definitions. 168 | //! 169 | //! For reference, the above code produces an implementation equivalent to: 170 | //! ``` 171 | //! # use syn::{Ident, token, Expr}; 172 | //! # struct SingleArgFn { name: Ident, arg_paren: token::Paren, arg: Expr } 173 | //! 174 | //! use syn::parse::{Parse, ParseStream}; 175 | //! 176 | //! impl Parse for SingleArgFn { 177 | //! fn parse(input: ParseStream) -> syn::Result { 178 | //! let paren; 179 | //! Ok(SingleArgFn { 180 | //! name: input.parse()?, 181 | //! arg_paren: syn::parenthesized!(paren in input), 182 | //! arg: paren.parse()?, 183 | //! }) 184 | //! } 185 | //! } 186 | //! ``` 187 | //! 188 | //!
189 | // 190 | // 191 | // ---------- SECTION: Custom parsing (call, parse_terminated) ---------- 192 | // 193 | //!
Custom parse functions (#[call], #[parse_terminated]) 194 | //! 195 | //! Not every type worth parsing implements `Parse`, but we still might want to parse them - things 196 | //! like [`Vec`] or any [`Punctuated<_, _>`] type. In these cases, the available 197 | //! attributes mirror the methods on [`ParseBuffer`]. 198 | //! 199 | //! [`ParseBuffer`]: syn::parse::ParseBuffer 200 | //! 201 | //! For `#[parse_terminated]`, there aren't any parameters that can be specified - it's common 202 | //! enough that it's provided for those `Punctuated` fields. 203 | //! 204 | //! Alternatively, `#[call]` has the syntax `#[call( EXPR )]`, where `EXPR` is *any expression* 205 | //! implementing `FnOnce(ParseBuffer) -> syn::Result`. Typically, this might be something like: 206 | //! ``` 207 | //! use syn::{Attribute, Ident, Token}; 208 | //! 209 | //! // Parses a unit struct with attributes. 210 | //! // 211 | //! // #[derive(Copy, Clone)] 212 | //! // struct S; 213 | //! #[derive(Parse)] 214 | //! struct UnitStruct { 215 | //! #[call(Attribute::parse_outer)] 216 | //! attrs: Vec, 217 | //! struct_token: Token![struct], 218 | //! name: Ident, 219 | //! semi_token: Token![;], 220 | //! } 221 | //! ``` 222 | //! 223 | //! Unlike with [`ParseBuffer::call`], which only accepts functions that are 224 | //! `fn(ParseStream) -> syn::Result`, `#[call]` allows any expression that we can call with the 225 | //! `ParseBuffer`. So one could - hypothetically - implement `#[parse_if]` with this: 226 | //! ``` 227 | //! struct Foo { 228 | //! a: Option]>, 229 | //! #[call(|inp| match &a { Some(_) => Ok(Some(inp.parse()?)), None => Ok(None) })] 230 | //! b: Option, 231 | //! } 232 | //! ``` 233 | //! Though it's probably best to just use `#[parse_if]` :) 234 | //! 235 | //! [`Vec`]: syn::Attribute 236 | //! [`Punctuated<_, _>`]: syn::punctuated::Punctuated 237 | //! [`ParseBuffer::call`]: syn::parse::ParseBuffer 238 | //! 239 | //!
240 | // 241 | // 242 | // ---------- SECTION: Conditional field parsing ---------- 243 | // 244 | //!
Conditional field parsing (#[parse_if], #[peek]) 245 | //! 246 | //! When implementing `Parse` for structs, it is occasionally the case that certain fields are 247 | //! optional - or should only be parsed under certain circumstances. There are attributes for that! 248 | //! 249 | //! Say we want to parse enums with the following, different syntax: 250 | //! 251 | //! ``` 252 | //! enum Foo { 253 | //! Bar: Baz, 254 | //! Qux, 255 | //! } 256 | //! ``` 257 | //! where the equivalent Rust code would be: 258 | //! 259 | //! ``` 260 | //! enum Foo { 261 | //! Bar(Baz), 262 | //! Qux, 263 | //! } 264 | //! ``` 265 | //! There’s two ways we could parse the variants here – either with a colon and following type or 266 | //! with no colon or type. To handle this, we can write: 267 | //! 268 | //! ``` 269 | //! #[derive(Parse)] 270 | //! struct Variant { 271 | //! name: Ident, 272 | //! // `syn` already supports optional parsing of simple tokens 273 | //! colon: Option, 274 | //! // We only want to parse the trailing type if there's a colon: 275 | //! #[parse_if(colon.is_some())] 276 | //! ty: Option, 277 | //! } 278 | //! ``` 279 | //! Note that in this case, `ty` must be an `Option`. In addition to conditional parsing based on 280 | //! the values of what’s already been parsed, we can also peek - just as described above in the 281 | //! section on parsing enums. The only difference here is that we do not need to provide a name for 282 | //! the optional field. We could have equally implemented the above as: 283 | //! 284 | //! ``` 285 | //! #[derive(Parse)] 286 | //! struct Variant { 287 | //! name: Ident, 288 | //! #[peek(Token![:])] 289 | //! ty: Option, 290 | //! } 291 | //! 292 | //! #[derive(Parse)] 293 | //! struct VariantType { 294 | //! colon: Token![:], 295 | //! ty: Type, 296 | //! } 297 | //! ``` 298 | //! 299 | //!
300 | // 301 | // 302 | // ---------- SECTION: Prefix & postfix ---------- 303 | // 304 | //!
Temporary parses: Prefix & postfix 305 | //! 306 | //! A common pattern that sometimes occurs when deriving `Parse` implementations is to have many 307 | //! unused punctuation fields - imagine a hypothetical implementation of field parsing with default 308 | //! values: 309 | //! 310 | //! ``` 311 | //! // A field with default values, parsing something like: 312 | //! // 313 | //! // foo: Bar = Bar::new() 314 | //! #[derive(Parse)] 315 | //! struct Field { 316 | //! ident: Ident, 317 | //! colon: Token![:], 318 | //! ty: Type, 319 | //! eq: Option, 320 | //! #[parse_if(eq.is_some())] 321 | //! expr: Option, 322 | //! } 323 | //! ``` 324 | //! 325 | //! Here, there's a couple fields that probably won't be used later - both `colon` and `eq`. We can 326 | //! elimitate both of these with the `#[prefix]` attribute: 327 | //! 328 | //! ``` 329 | //! // A field with default values, parsing something like: 330 | //! // 331 | //! // foo: Bar = Bar::new() 332 | //! #[derive(Parse)] 333 | //! struct Field { 334 | //! ident: Ident, 335 | //! #[prefix(Token![:])] 336 | //! ty: Type, 337 | //! #[prefix(Option as eq)] 338 | //! #[parse_if(eq.is_some())] 339 | //! expr: Option, 340 | //! } 341 | //! ``` 342 | //! 343 | //! We can use `"as "` to give a temporary name to the value - including it as a parsed 344 | //! value that can be referenced in other parsing clauses, but without adding it as a struct field. 345 | //! 346 | //! There's *also* a `#[postfix]` attribute, which operates very similarly to `#[prefix]`, but 347 | //! exists to allow unused fields at the end of the struct. In general, `#[postfix]` tends to be 348 | //! pretty tricky to read, so it's generally preferable to use `#[prefix]` to keep the field 349 | //! ordering the same as the parse order. 350 | //! 351 | //! In some cases, we might want to have both a field and its prefix parsed inside some other token 352 | //! tree. Like the following contrived example: 353 | //! 354 | //! ``` 355 | //! use syn::*; 356 | //! 357 | //! // Parses.... something. Who knows if this is useful... :P 358 | //! // 359 | //! // (=> x + 2) 360 | //! #[derive(Parse)] 361 | //! struct Funky { 362 | //! #[paren] 363 | //! paren: token::Paren, 364 | //! #[inside(paren)] 365 | //! r_arrow: Token![=>], 366 | //! #[inside(paren)] 367 | //! expr: Expr, 368 | //! } 369 | //! ``` 370 | //! 371 | //! To remove the unused `r_arrow` field here, we have an other extra piece we can add: 372 | //! `"in" "`. 373 | //! 374 | //! ``` 375 | //! #[derive(Parse)] 376 | //! struct Funky { 377 | //! #[paren] 378 | //! paren: token::Paren, 379 | //! #[prefix(Token![=>] in paren)] 380 | //! #[inside(paren)] 381 | //! expr: Expr, 382 | //! } 383 | //! ``` 384 | //! 385 | //! Note that attempting to write the `#[inside]` before `#[prefix]` is forbidden; it's less clear 386 | //! what the expected behavior there should be. 387 | //! 388 | //! Finally, when combining both `"as" ` and `"in" `, they should come in that 389 | //! order - e.g. `#[prefix(Foo as bar in baz)]`. 390 | //! 391 | //!
392 | 393 | use proc_macro2::{Span, TokenStream}; 394 | use quote::{quote, quote_spanned, ToTokens}; 395 | use syn::spanned::Spanned; 396 | use syn::{parse_macro_input, Data, DeriveInput, Ident, Result}; 397 | 398 | #[macro_use] 399 | mod error_macros; 400 | 401 | mod fields; 402 | #[cfg(test)] 403 | mod tests; 404 | mod variants; 405 | 406 | #[rustfmt::skip] 407 | #[proc_macro_derive( 408 | Parse, 409 | attributes( 410 | paren, bracket, brace, 411 | inside, 412 | call, parse_terminated, 413 | peek, peek_with, 414 | parse_if, 415 | prefix, postfix, 416 | ) 417 | )] 418 | pub fn derive_parse(item: proc_macro::TokenStream) -> proc_macro::TokenStream { 419 | let input = parse_macro_input!(item as DeriveInput); 420 | derive_parse_internal(input).into() 421 | } 422 | 423 | // Pulled into a separate function so we can test it 424 | pub(crate) fn derive_parse_internal(input: DeriveInput) -> TokenStream { 425 | // The generic parameters following `impl` 426 | let mut generics_intro = TokenStream::new(); 427 | // The generic arguments following the name of the type 428 | let mut generics_args = TokenStream::new(); 429 | 430 | let where_clause = input.generics.where_clause; 431 | 432 | let generic_params: Vec<_> = input.generics.params.into_iter().collect(); 433 | if !generic_params.is_empty() { 434 | let generics_intros: Vec<_> = handle_syn_result! { 435 | generic_params.iter() 436 | .map(require_impl_parse_if_type) 437 | .collect() 438 | }; 439 | generics_intro = quote!( < #( #generics_intros ),* > ); 440 | let generics_args_list: Vec<_> = generic_params.into_iter().map(convert_to_arg).collect(); 441 | generics_args = quote!( < #( #generics_args_list ),* > ); 442 | } 443 | 444 | let ident = input.ident; 445 | 446 | let parse_impl = match input.data { 447 | Data::Union(u) => invalid_input_kind!(u.union_token), 448 | Data::Struct(s) => handle_syn_result! { 449 | @default_impl_from(generics_intro, ident, generics_args, where_clause), 450 | fields::generate_fn_body(&ident, s.fields, false) 451 | }, 452 | Data::Enum(e) => handle_syn_result! { 453 | @default_impl_from(generics_intro, ident, generics_args, where_clause), 454 | variants::generate_impl(e.variants.into_iter()) 455 | }, 456 | }; 457 | 458 | let parse_input = parse_input(); 459 | quote!( 460 | impl #generics_intro ::syn::parse::Parse for #ident #generics_args #where_clause { 461 | fn parse(#parse_input: ::syn::parse::ParseStream) -> ::syn::Result { 462 | #parse_impl 463 | } 464 | } 465 | ) 466 | } 467 | 468 | // Produces the tokens for the generic parameter, adding `+ syn::parse::Parse` 469 | fn require_impl_parse_if_type(param: &syn::GenericParam) -> Result { 470 | use syn::GenericParam::Type; 471 | use syn::TypeParam; 472 | 473 | let TypeParam { 474 | attrs, 475 | ident, 476 | colon_token, 477 | bounds, 478 | eq_token, 479 | default, 480 | } = match param { 481 | Type(t) => t, 482 | param => return Ok(param.to_token_stream()), 483 | }; 484 | 485 | // If we have `struct Foo`, we need to add `: Parse`, but 486 | // if we have `struct Foo`, we need to add `+ Parse` 487 | let parse_bound = if colon_token.is_some() { 488 | quote_spanned! { 489 | ident.span()=> 490 | + ::syn::parse::Parse 491 | } 492 | } else { 493 | quote_spanned! { 494 | ident.span()=> 495 | : ::syn::parse::Parse 496 | } 497 | }; 498 | 499 | Ok(quote! { 500 | #( #attrs )* 501 | #ident #colon_token #bounds #parse_bound #eq_token #default 502 | }) 503 | } 504 | 505 | fn convert_to_arg(param: syn::GenericParam) -> TokenStream { 506 | use syn::GenericParam::{Const, Lifetime, Type}; 507 | 508 | match param { 509 | Type(ty) => ty.ident.to_token_stream(), 510 | Lifetime(lifetime) => lifetime.to_token_stream(), 511 | Const(con) => { 512 | let ident = &con.ident; 513 | quote_spanned!(con.span()=> { #ident }) 514 | } 515 | } 516 | } 517 | 518 | // A helper macro to give the identifier used to represent the ParseStream used as input to the 519 | // macro 520 | fn parse_input() -> Ident { 521 | Ident::new("__parse_input", Span::call_site()) 522 | } 523 | -------------------------------------------------------------------------------- /src/tests.rs: -------------------------------------------------------------------------------- 1 | use quote::ToTokens; 2 | use syn::{parse2, parse_str, DeriveInput, ItemImpl}; 3 | 4 | macro_rules! test_all { 5 | ( 6 | $( 7 | $test_name:ident: { 8 | $($input:tt)* 9 | } => { 10 | $($output:tt)* 11 | }; 12 | )* 13 | ) => { 14 | $( 15 | #[test] 16 | fn $test_name() { 17 | let input: DeriveInput = parse_str(stringify!($($input)*)).expect("failed to parse input as `DeriveInput`"); 18 | let expected_str = stringify!($($output)*); 19 | let expected: ItemImpl = parse_str(expected_str).expect("failed to parse expected output as `ItemImpl`"); 20 | 21 | let output_tokens = crate::derive_parse_internal(input); 22 | let output: ItemImpl = parse2(output_tokens.clone()) 23 | .unwrap_or_else(|err| panic!( 24 | "failed to parse output as `ItemImpl`: {}\noutput_tokens = {:?}", 25 | err, 26 | output_tokens.to_string(), 27 | )); 28 | 29 | if output != expected { 30 | panic!( 31 | "output != expected\noutput = {:?},\nexpected = {:?}", 32 | output_tokens.to_string(), 33 | expected.to_token_stream().to_string(), 34 | ) 35 | } 36 | } 37 | )* 38 | } 39 | } 40 | 41 | test_all! { 42 | simple_input: { 43 | struct Foo { 44 | bar: Bar, 45 | baz: Baz, 46 | } 47 | } => { 48 | impl ::syn::parse::Parse for Foo { 49 | fn parse(__parse_input: ::syn::parse::ParseStream) -> ::syn::Result { 50 | let bar: Bar = __parse_input.parse()?; 51 | let baz: Baz = __parse_input.parse()?; 52 | 53 | Ok(Foo { 54 | bar, 55 | baz, 56 | }) 57 | } 58 | } 59 | }; 60 | generic_struct: { 61 | struct Foo 62 | where ::Qux: Quack, 63 | { 64 | bar: B, 65 | baz: Baz, 66 | quacker: Q, 67 | } 68 | } => { 69 | impl ::syn::parse::Parse for Foo 70 | where ::Qux: Quack, 71 | { 72 | fn parse(__parse_input: ::syn::parse::ParseStream) -> ::syn::Result { 73 | let bar: B = __parse_input.parse()?; 74 | let baz: Baz = __parse_input.parse()?; 75 | let quacker: Q = __parse_input.parse()?; 76 | 77 | Ok(Foo { 78 | bar, 79 | baz, 80 | quacker, 81 | }) 82 | } 83 | } 84 | }; 85 | simple_attrs: { 86 | struct Foo { 87 | bar: Bar, 88 | #[paren] 89 | paren: syn::token::Paren, 90 | #[inside(paren)] 91 | baz: Baz, 92 | } 93 | } => { 94 | impl ::syn::parse::Parse for Foo { 95 | fn parse(__parse_input: ::syn::parse::ParseStream) -> ::syn::Result { 96 | let bar: Bar = __parse_input.parse()?; 97 | 98 | let __paren_backing_token_stream; 99 | let paren: syn::token::Paren = 100 | ::syn::parenthesized!(__paren_backing_token_stream in __parse_input); 101 | let baz: Baz = 102 | __paren_backing_token_stream.parse()?; 103 | 104 | Ok(Foo { 105 | bar, 106 | paren, 107 | baz, 108 | }) 109 | } 110 | } 111 | }; 112 | nested_attrs: { 113 | struct Foo { 114 | bar: Bar, 115 | #[bracket] 116 | fst: syn::token::Bracket, 117 | #[inside(fst)] 118 | #[brace] 119 | snd: syn::token::Brace, 120 | #[inside(snd)] 121 | baz: Baz, 122 | } 123 | } => { 124 | impl ::syn::parse::Parse for Foo { 125 | fn parse(__parse_input: ::syn::parse::ParseStream) -> ::syn::Result { 126 | let bar: Bar = __parse_input.parse()?; 127 | 128 | let __fst_backing_token_stream; 129 | let fst: syn::token::Bracket = 130 | ::syn::bracketed!(__fst_backing_token_stream in __parse_input); 131 | 132 | let __snd_backing_token_stream; 133 | let snd: syn::token::Brace = 134 | ::syn::braced!(__snd_backing_token_stream in __fst_backing_token_stream); 135 | 136 | let baz: Baz = __snd_backing_token_stream.parse()?; 137 | 138 | Ok(Foo { 139 | bar, 140 | fst, 141 | snd, 142 | baz, 143 | }) 144 | } 145 | } 146 | }; 147 | struct_peek: { 148 | struct Foo { 149 | bar: Bar, 150 | #[peek_with(|p| !p.is_empty())] 151 | baz: Baz, 152 | } 153 | } => { 154 | impl ::syn::parse::Parse for Foo { 155 | fn parse(__parse_input: ::syn::parse::ParseStream) -> ::syn::Result { 156 | let bar: Bar = __parse_input.parse()?; 157 | 158 | let baz: Baz = match (|p| !p.is_empty())(__parse_input) { 159 | true => Some(__parse_input.parse()?), 160 | false => None, 161 | }; 162 | 163 | Ok(Foo { 164 | bar, 165 | baz, 166 | }) 167 | } 168 | } 169 | }; 170 | parse_if_peek: { 171 | struct Foo { 172 | at_symbol: Option, 173 | #[parse_if(at_symbol.is_some())] 174 | name: Ident, 175 | } 176 | } => { 177 | impl ::syn::parse::Parse for Foo { 178 | fn parse(__parse_input: ::syn::parse::ParseStream) -> ::syn::Result { 179 | let at_symbol: Option = __parse_input.parse()?; 180 | 181 | let name: Ident = match at_symbol.is_some() { 182 | true => Some(__parse_input.parse()?), 183 | false => None, 184 | }; 185 | 186 | Ok(Foo { 187 | at_symbol, 188 | name, 189 | }) 190 | } 191 | } 192 | }; 193 | simple_prefix: { 194 | struct Field { 195 | name: Ident, 196 | #[prefix(Token![:])] 197 | ty: Type, 198 | } 199 | } => { 200 | impl ::syn::parse::Parse for Field { 201 | fn parse(__parse_input: ::syn::parse::ParseStream) -> ::syn::Result { 202 | let name: Ident = __parse_input.parse()?; 203 | let _: Token![:] = __parse_input.parse()?; 204 | let ty: Type = __parse_input.parse()?; 205 | 206 | Ok(Field { 207 | name, 208 | ty, 209 | }) 210 | } 211 | } 212 | }; 213 | simple_postfix: { 214 | struct Field { 215 | #[postfix(Token![:])] 216 | name: Ident, 217 | ty: Type, 218 | } 219 | } => { 220 | impl ::syn::parse::Parse for Field { 221 | fn parse(__parse_input: ::syn::parse::ParseStream) -> ::syn::Result { 222 | let name: Ident = __parse_input.parse()?; 223 | let _: Token![:] = __parse_input.parse()?; 224 | let ty: Type = __parse_input.parse()?; 225 | 226 | Ok(Field { 227 | name, 228 | ty, 229 | }) 230 | } 231 | } 232 | }; 233 | prefix_as_parse_if: { 234 | struct Field { 235 | name: Ident, 236 | #[prefix(Token![:])] 237 | ty: Type, 238 | #[prefix(Option as eq_token)] 239 | #[parse_if(eq_token.is_some())] 240 | value: Option, 241 | } 242 | } => { 243 | impl ::syn::parse::Parse for Field { 244 | fn parse(__parse_input: ::syn::parse::ParseStream) -> ::syn::Result { 245 | let name: Ident = __parse_input.parse()?; 246 | let _: Token![:] = __parse_input.parse()?; 247 | let ty: Type = __parse_input.parse()?; 248 | let eq_token: Option = __parse_input.parse()?; 249 | let value: Option = match eq_token.is_some() { 250 | true => Some(__parse_input.parse()?), 251 | false => None, 252 | }; 253 | 254 | Ok(Field { 255 | name, 256 | ty, 257 | value, 258 | }) 259 | } 260 | } 261 | }; 262 | prefix_inside: { 263 | // Something like `(=> x)` 264 | struct Foo { 265 | #[paren] 266 | paren: token::Paren, 267 | #[prefix(Token![=>] in paren)] 268 | #[inside(paren)] 269 | ident: Ident, 270 | } 271 | } => { 272 | impl ::syn::parse::Parse for Foo { 273 | fn parse(__parse_input: ::syn::parse::ParseStream) -> ::syn::Result { 274 | let __paren_backing_token_stream; 275 | let paren: token::Paren = 276 | ::syn::parenthesized!(__paren_backing_token_stream in __parse_input); 277 | 278 | let _: Token![=>] = __paren_backing_token_stream.parse()?; 279 | let ident: Ident = __paren_backing_token_stream.parse()?; 280 | 281 | Ok(Foo { 282 | paren, 283 | ident, 284 | }) 285 | } 286 | } 287 | }; 288 | basic_peeks: { 289 | // Either a literal `fn` token, or some generic identifier 290 | enum FnOrIdent { 291 | #[peek(token::Fn, name = "function")] 292 | Fn(token::Fn), 293 | #[peek_with(|_| true, name = "identifier")] 294 | Ident(Ident), 295 | } 296 | } => { 297 | impl ::syn::parse::Parse for FnOrIdent { 298 | fn parse (__parse_input: ::syn::parse::ParseStream) -> ::syn:: Result { 299 | if __parse_input.peek(token::Fn) { 300 | let _field_0: token::Fn = __parse_input.parse()?; 301 | return Ok(Self::Fn(_field_0,)); 302 | } 303 | 304 | if (|_| true)(__parse_input) { 305 | let _field_0: Ident = __parse_input.parse()?; 306 | return Ok(Self::Ident(_field_0,)); 307 | } 308 | 309 | Err (__parse_input.error("expected either function or identifier")) 310 | } 311 | } 312 | }; 313 | calls: { 314 | struct Calls { 315 | #[call(Attribute::parse_outer)] 316 | attrs: Vec, 317 | #[call(|_| Ok(3))] 318 | x: u32, 319 | #[call(|_| Ok(x + 1))] 320 | y: u32, 321 | } 322 | } => { 323 | impl ::syn::parse::Parse for Calls { 324 | fn parse(__parse_input: ::syn::parse::ParseStream) -> ::syn::Result { 325 | let attrs: Vec = { 326 | let result: ::syn::Result<_> = (Attribute::parse_outer)(&__parse_input); 327 | result? 328 | }; 329 | 330 | let x: u32 = { 331 | let result: ::syn::Result<_> = (|_| Ok(3))(&__parse_input); 332 | result? 333 | }; 334 | 335 | let y: u32 = { 336 | let result: ::syn::Result<_> = (|_| Ok(x + 1))(&__parse_input); 337 | result? 338 | }; 339 | 340 | Ok(Calls { attrs, x, y, }) 341 | } 342 | } 343 | }; 344 | } 345 | -------------------------------------------------------------------------------- /src/variants.rs: -------------------------------------------------------------------------------- 1 | //! Handling for generating a `Parse` implementation from `enum` variants 2 | 3 | use crate::fields::generate_fn_body; 4 | use proc_macro2::{Span, TokenStream}; 5 | use quote::{quote, quote_spanned}; 6 | use syn::parse::{Parse, ParseStream}; 7 | use syn::spanned::Spanned; 8 | use syn::{Attribute, Expr, Ident, LitStr, Result, Token, Variant}; 9 | 10 | pub(crate) fn generate_impl( 11 | variants: impl ExactSizeIterator, 12 | ) -> Result { 13 | // generate each `peek` and corresponding inner implementation 14 | 15 | if variants.len() == 0 { 16 | return Err(syn::Error::new( 17 | Span::call_site(), 18 | "cannot derive `Parse` for an empty `enum`", 19 | )); 20 | } 21 | 22 | let input_source = crate::parse_input(); 23 | 24 | let mut names = Vec::new(); 25 | let conditional_return_if_peek_success = variants 26 | .into_iter() 27 | .map(|var| { 28 | let (name, if_condition) = impl_for_variant(&input_source, var)?; 29 | names.push(name); 30 | Ok(if_condition) 31 | }) 32 | .collect::>>()?; 33 | 34 | let error_msg = implemented_error_msg(names); 35 | 36 | Ok(quote! { 37 | #( #conditional_return_if_peek_success )* 38 | 39 | Err(#input_source.error(#error_msg)) 40 | }) 41 | } 42 | 43 | fn implemented_error_msg(names: Vec) -> String { 44 | let one_of = match names.len() { 45 | 1 => "", 46 | 2 => "either ", 47 | _ => "one of ", 48 | }; 49 | 50 | let name_list = match names.len() { 51 | 0 => unreachable!(), 52 | 1 => names[0].value(), 53 | 2 => format!("{} or {}", names[0].value(), names[1].value()), 54 | _ => { 55 | let middle = names[1..names.len() - 1] 56 | .iter() 57 | .map(|name| format!(", {}", name.value())) 58 | .collect::(); 59 | format!( 60 | "{}{}, or {}", 61 | names[0].value(), 62 | middle, 63 | names.last().unwrap().value() 64 | ) 65 | } 66 | }; 67 | 68 | format!("expected {}{}", one_of, name_list) 69 | } 70 | 71 | enum VariantAttr { 72 | Peek(PeekInfo), 73 | PeekWith(PeekInfo), 74 | } 75 | 76 | mod kwd { 77 | syn::custom_keyword!(name); 78 | } 79 | 80 | struct PeekInfo { 81 | expr: Expr, 82 | _comma: Token![,], 83 | _name_token: kwd::name, 84 | _eq: Token![=], 85 | name: LitStr, 86 | } 87 | 88 | // If successful, the first element in the tuple is the name to use to refer to the variant in 89 | // error messages. The second element is an `if` expression that looks something like: 90 | // 91 | // if $input_source.peek($peek_value) { 92 | // Ok(Self::$variant_name { 93 | // $( $field: $input_source.parse()?, )* 94 | // }) 95 | // } 96 | fn impl_for_variant(input_source: &Ident, variant: Variant) -> Result<(LitStr, TokenStream)> { 97 | use VariantAttr::{Peek, PeekWith}; 98 | 99 | let variant_span = variant.span(); 100 | 101 | let diagnositc_name: LitStr; 102 | let peek_expr = match extract_single_attr(variant_span, variant.attrs)? { 103 | Peek(PeekInfo { expr, name, .. }) => { 104 | diagnositc_name = name; 105 | quote_spanned! { 106 | expr.span()=> 107 | #input_source.peek(#expr) 108 | } 109 | } 110 | PeekWith(PeekInfo { expr, name, .. }) => { 111 | diagnositc_name = name; 112 | quote_spanned! { 113 | expr.span()=> 114 | (#expr)(#input_source) 115 | } 116 | } 117 | }; 118 | 119 | let ident = variant.ident; 120 | let variant_path = quote!( Self::#ident ); 121 | let parse_implementation = generate_fn_body(&variant_path, variant.fields, true)?; 122 | 123 | let output = quote! { 124 | if #peek_expr { 125 | #parse_implementation; 126 | } 127 | }; 128 | 129 | Ok((diagnositc_name, output)) 130 | } 131 | 132 | fn extract_single_attr(variant_span: Span, attrs: Vec) -> Result { 133 | let mut attrs: Vec<_> = attrs 134 | .into_iter() 135 | .filter_map(try_as_variant_attr) 136 | .collect::>()?; 137 | 138 | match attrs.len() { 139 | 0 => Err(syn::Error::new( 140 | variant_span, 141 | "enum variants must have `#[peek(..)]` or `#[peek_with(..)]` to derive `Parse`", 142 | )), 143 | 1 => Ok(attrs.remove(0)), 144 | _ => Err(syn::Error::new( 145 | variant_span, 146 | "more than one peeking attribute is disallowed; please use `#[peek_with(..)]` for a custom function", 147 | )), 148 | } 149 | } 150 | 151 | fn try_as_variant_attr(attr: Attribute) -> Option> { 152 | let name = attr.path().get_ident()?.to_string(); 153 | 154 | match name.as_str() { 155 | "peek" => Some(attr.parse_args().map(VariantAttr::Peek)), 156 | "peek_with" => Some(attr.parse_args().map(VariantAttr::PeekWith)), 157 | _ => None, 158 | } 159 | } 160 | 161 | //////////////////////////////////////////// 162 | // Boilerplate `Parse` implementations 🙃 // 163 | //////////////////////////////////////////// 164 | 165 | impl Parse for PeekInfo { 166 | fn parse(input: ParseStream) -> Result { 167 | Ok(PeekInfo { 168 | expr: input.parse()?, 169 | _comma: input.parse()?, 170 | _name_token: input.parse()?, 171 | _eq: input.parse()?, 172 | name: input.parse()?, 173 | }) 174 | } 175 | } 176 | --------------------------------------------------------------------------------