diff --git a/Cargo.toml b/Cargo.toml index ffa6086b..debf9c7f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,22 +11,24 @@ repository = "https://github.com/ruma/ruma-api-macros" version = "0.1.0" [dependencies] -quote = "0.3.15" -ruma-api = "0.4.0" -synom = "0.11.3" +quote = "0.5.2" +ruma-api = "0.5.0" [dependencies.syn] +version = "0.13.4" features = ["full"] -version = "0.11.11" + +[dependencies.proc-macro2] +version = "0.3.8" +features = ["nightly"] [dev-dependencies] -futures = "0.1.14" -hyper = "0.11" -serde = "1.0.8" -serde_derive = "1.0.8" -serde_json = "1.0.2" +futures = "0.1.21" +serde = "1.0.45" +serde_derive = "1.0.45" +serde_json = "1.0.17" serde_urlencoded = "0.5.1" -url = "1.5.1" +url = "1.7.0" [lib] proc-macro = true diff --git a/src/api/metadata.rs b/src/api/metadata.rs index e0de6841..5855314a 100644 --- a/src/api/metadata.rs +++ b/src/api/metadata.rs @@ -1,4 +1,5 @@ use quote::{ToTokens, Tokens}; +use syn::synom::Synom; use syn::{Expr, Ident}; #[derive(Debug)] @@ -11,6 +12,55 @@ pub struct Metadata { pub requires_authentication: Tokens, } +impl Synom for Metadata { + named!(parse -> Self, do_parse!( + ident: syn!(Ident) >> + cond_reduce!(ident == "description") >> + punct!(:) >> + description: syn!(Expr) >> + punct!(,) >> + + ident: syn!(Ident) >> + cond_reduce!(ident == "method") >> + punct!(:) >> + method: syn!(Expr) >> + punct!(,) >> + + ident: syn!(Ident) >> + cond_reduce!(ident == "name") >> + punct!(:) >> + name: syn!(Expr) >> + punct!(,) >> + + ident: syn!(Ident) >> + cond_reduce!(ident == "path") >> + punct!(:) >> + path: syn!(Expr) >> + punct!(,) >> + + ident: syn!(Ident) >> + cond_reduce!(ident == "rate_limited") >> + punct!(:) >> + rate_limited: syn!(Expr) >> + punct!(,) >> + + ident: syn!(Ident) >> + cond_reduce!(ident == "requires_authentication") >> + punct!(:) >> + requires_authentication: syn!(Expr) >> + punct!(,) >> + + (Metadata { + description, + method, + name, + path, + rate_limited, + requires_authentication, + }) + )); +} + impl From> for Metadata { fn from(fields: Vec<(Ident, Expr)>) -> Self { let mut description = None; diff --git a/src/api/mod.rs b/src/api/mod.rs index 8ed40e76..c54aaa40 100644 --- a/src/api/mod.rs +++ b/src/api/mod.rs @@ -1,35 +1,39 @@ +use std::convert::{TryFrom, TryInto}; + use quote::{ToTokens, Tokens}; -use syn::{Field, MetaItem}; +use syn::punctuated::Pair; +use syn::synom::Synom; +use syn::{Expr, Field, Ident, Meta}; mod metadata; mod request; mod response; -use parse::Entry; +// use parse::Entry; use self::metadata::Metadata; use self::request::Request; use self::response::Response; -pub fn strip_serde_attrs(field: &Field) -> Field { - let mut field = field.clone(); +// pub fn strip_serde_attrs(field: &Field) -> Field { +// let mut field = field.clone(); - field.attrs = field.attrs.into_iter().filter(|attr| { - let (attr_ident, _) = match attr.value { - MetaItem::List(ref attr_ident, _) => { - (attr_ident, ()) - } - _ => return true, - }; +// field.attrs = field.attrs.into_iter().filter(|attr| { +// let (attr_ident, _) = match attr.value { +// Meta::List(ref attr_ident, _) => { +// (attr_ident, ()) +// } +// _ => return true, +// }; - if attr_ident != "serde" { - return true; - } +// if attr_ident != "serde" { +// return true; +// } - false - }).collect(); +// false +// }).collect(); - field -} +// field +// } #[derive(Debug)] pub struct Api { @@ -38,291 +42,333 @@ pub struct Api { response: Response, } -impl ToTokens for Api { - fn to_tokens(&self, tokens: &mut Tokens) { - let description = &self.metadata.description; - let method = &self.metadata.method; - let name = &self.metadata.name; - let path = &self.metadata.path; - let rate_limited = &self.metadata.rate_limited; - let requires_authentication = &self.metadata.requires_authentication; +impl TryFrom> for Api { + type Error = &'static str; - let request_types = { - let mut tokens = Tokens::new(); - self.request.to_tokens(&mut tokens); - tokens - }; - let response_types = { - let mut tokens = Tokens::new(); - self.response.to_tokens(&mut tokens); - tokens - }; - - let set_request_path = if self.request.has_path_fields() { - let path_str_quoted = path.as_str(); - assert!( - path_str_quoted.starts_with('"') && path_str_quoted.ends_with('"'), - "path needs to be a string literal" - ); - - let path_str = &path_str_quoted[1 .. path_str_quoted.len() - 1]; - - assert!(path_str.starts_with('/'), "path needs to start with '/'"); - assert!( - path_str.chars().filter(|c| *c == ':').count() == self.request.path_field_count(), - "number of declared path parameters needs to match amount of placeholders in path" - ); - - let request_path_init_fields = self.request.request_path_init_fields(); - - let mut tokens = quote! { - let request_path = RequestPath { - #request_path_init_fields - }; - - // This `unwrap()` can only fail when the url is a - // cannot-be-base url like `mailto:` or `data:`, which is not - // the case for our placeholder url. - let mut path_segments = url.path_segments_mut().unwrap(); - }; - - for segment in path_str[1..].split('/') { - tokens.append(quote! { - path_segments.push - }); - - tokens.append("("); - - if segment.starts_with(':') { - tokens.append("&request_path."); - tokens.append(&segment[1..]); - tokens.append(".to_string()"); - } else { - tokens.append("\""); - tokens.append(segment); - tokens.append("\""); - } - - tokens.append(");"); - } - - tokens - } else { - quote! { - url.set_path(metadata.path); - } - }; - - let set_request_query = if self.request.has_query_fields() { - let request_query_init_fields = self.request.request_query_init_fields(); - - quote! { - let request_query = RequestQuery { - #request_query_init_fields - }; - - url.set_query(Some(&::serde_urlencoded::to_string(request_query)?)); - } - } else { - Tokens::new() - }; - - let add_body_to_request = if let Some(field) = self.request.newtype_body_field() { - let field_name = field.ident.as_ref().expect("expected body field to have a name"); - - quote! { - let request_body = RequestBody(request.#field_name); - - hyper_request.set_body(::serde_json::to_vec(&request_body)?); - } - } else if self.request.has_body_fields() { - let request_body_init_fields = self.request.request_body_init_fields(); - - quote! { - let request_body = RequestBody { - #request_body_init_fields - }; - - hyper_request.set_body(::serde_json::to_vec(&request_body)?); - } - } else { - Tokens::new() - }; - - let deserialize_response_body = if let Some(field) = self.response.newtype_body_field() { - let field_type = &field.ty; - let mut tokens = Tokens::new(); - - tokens.append(quote! { - let future_response = hyper_response.body() - .fold::<_, _, Result<_, ::std::io::Error>>(Vec::new(), |mut bytes, chunk| { - bytes.write_all(&chunk)?; - - Ok(bytes) - }) - .map_err(::ruma_api::Error::from) - .and_then(|bytes| { - ::serde_json::from_slice::<#field_type>(bytes.as_slice()) - .map_err(::ruma_api::Error::from) - }) - }); - - tokens.append(".and_then(move |response_body| {"); - - tokens - } else if self.response.has_body_fields() { - let mut tokens = Tokens::new(); - - tokens.append(quote! { - let future_response = hyper_response.body() - .fold::<_, _, Result<_, ::std::io::Error>>(Vec::new(), |mut bytes, chunk| { - bytes.write_all(&chunk)?; - - Ok(bytes) - }) - .map_err(::ruma_api::Error::from) - .and_then(|bytes| { - ::serde_json::from_slice::(bytes.as_slice()) - .map_err(::ruma_api::Error::from) - }) - }); - - tokens.append(".and_then(move |response_body| {"); - - tokens - } else { - let mut tokens = Tokens::new(); - - tokens.append(quote! { - let future_response = ::futures::future::ok(()) - }); - - tokens.append(".and_then(move |_| {"); - - tokens - }; - - let mut closure_end = Tokens::new(); - closure_end.append("});"); - - let extract_headers = if self.response.has_header_fields() { - quote! { - let mut headers = hyper_response.headers().clone(); - } - } else { - Tokens::new() - }; - - let response_init_fields = if self.response.has_fields() { - self.response.init_fields() - } else { - Tokens::new() - }; - - tokens.append(quote! { - #[allow(unused_imports)] - use std::io::Write as _Write; - - #[allow(unused_imports)] - use ::futures::{Future as _Future, Stream as _Stream}; - use ::ruma_api::Endpoint as _RumaApiEndpoint; - - /// The API endpoint. - #[derive(Debug)] - pub struct Endpoint; - - #request_types - - impl ::std::convert::TryFrom for ::hyper::Request { - type Error = ::ruma_api::Error; - - #[allow(unused_mut, unused_variables)] - fn try_from(request: Request) -> Result { - let metadata = Endpoint::METADATA; - - // Use dummy homeserver url which has to be overwritten in - // the calling code. Previously (with hyper::Uri) this was - // not required, but Url::parse only accepts absolute urls. - let mut url = ::url::Url::parse("http://invalid-host-please-change/").unwrap(); - - { #set_request_path } - { #set_request_query } - - let mut hyper_request = ::hyper::Request::new( - metadata.method, - // Every valid URL is a valid URI - url.into_string().parse().unwrap(), - ); - - { #add_body_to_request } - - Ok(hyper_request) - } - } - - #response_types - - impl ::futures::future::FutureFrom<::hyper::Response> for Response { - type Future = Box<_Future>; - type Error = ::ruma_api::Error; - - #[allow(unused_variables)] - fn future_from(hyper_response: ::hyper::Response) - -> Box<_Future> { - #extract_headers - - #deserialize_response_body - - let response = Response { - #response_init_fields - }; - - Ok(response) - #closure_end - - Box::new(future_response) - } - } - - impl ::ruma_api::Endpoint for Endpoint { - type Request = Request; - type Response = Response; - - const METADATA: ::ruma_api::Metadata = ::ruma_api::Metadata { - description: #description, - method: ::hyper::#method, - name: #name, - path: #path, - rate_limited: #rate_limited, - requires_authentication: #requires_authentication, - }; - } - }); - } -} - -impl From> for Api { - fn from(entries: Vec) -> Api { - if entries.len() != 3 { - panic!("ruma_api! expects 3 blocks: metadata, request, and response"); + fn try_from(exprs: Vec) -> Result { + if exprs.len() != 3 { + return Err("ruma_api! expects 3 blocks: metadata, request, and response"); } let mut metadata = None; let mut request = None; let mut response = None; - for entry in entries { - match entry { - Entry::Metadata(fields) => metadata = Some(Metadata::from(fields)), - Entry::Request(fields) => request = Some(Request::from(fields)), - Entry::Response(fields) => response = Some(Response::from(fields)), + for expr in exprs { + let expr = match expr { + Expr::Struct(expr) => expr, + _ => return Err("ruma_api! blocks should use struct syntax"), + }; + + let segments = expr.path.segments; + + if segments.len() != 1 { + return Err("ruma_api! blocks must be one of: metadata, request, or response"); + } + + let Pair::End(last_segment) = segments.last().unwrap(); + + match last_segment.ident.as_ref() { + "metadata" => metadata = Some(expr.try_into()?), + "request" => request = Some(expr.try_into()?), + "response" => response = Some(expr.try_into()?), + _ => return Err("ruma_api! blocks must be one of: metadata, request, or response"), } } - Api { - metadata: metadata.expect("ruma_api! is missing metadata"), - request: request.expect("ruma_api! is missing request"), - response: response.expect("ruma_api! is missing response"), + if metadata.is_none() { + return Err("ruma_api! is missing metadata"); } + + if request.is_none() { + return Err("ruma_api! is missing request"); + } + + if response.is_none() { + return Err("ruma_api! is missing response"); + } + + Ok(Api { + metadata: metadata.unwrap(), + request: request.unwrap(), + response: response.unwrap(), + }) + } } + +pub struct Exprs { + pub inner: Vec, +} + +impl Synom for Exprs { + named!(parse -> Self, do_parse!( + exprs: many0!(syn!(Expr)) >> + (Exprs { + inner: exprs, + }) + )); +} +// impl ToTokens for Api { +// fn to_tokens(&self, tokens: &mut Tokens) { +// let description = &self.metadata.description; +// let method = &self.metadata.method; +// let name = &self.metadata.name; +// let path = &self.metadata.path; +// let rate_limited = &self.metadata.rate_limited; +// let requires_authentication = &self.metadata.requires_authentication; + +// let request_types = { +// let mut tokens = Tokens::new(); +// self.request.to_tokens(&mut tokens); +// tokens +// }; +// let response_types = { +// let mut tokens = Tokens::new(); +// self.response.to_tokens(&mut tokens); +// tokens +// }; + +// let set_request_path = if self.request.has_path_fields() { +// let path_str_quoted = path.as_str(); +// assert!( +// path_str_quoted.starts_with('"') && path_str_quoted.ends_with('"'), +// "path needs to be a string literal" +// ); + +// let path_str = &path_str_quoted[1 .. path_str_quoted.len() - 1]; + +// assert!(path_str.starts_with('/'), "path needs to start with '/'"); +// assert!( +// path_str.chars().filter(|c| *c == ':').count() == self.request.path_field_count(), +// "number of declared path parameters needs to match amount of placeholders in path" +// ); + +// let request_path_init_fields = self.request.request_path_init_fields(); + +// let mut tokens = quote! { +// let request_path = RequestPath { +// #request_path_init_fields +// }; + +// // This `unwrap()` can only fail when the url is a +// // cannot-be-base url like `mailto:` or `data:`, which is not +// // the case for our placeholder url. +// let mut path_segments = url.path_segments_mut().unwrap(); +// }; + +// for segment in path_str[1..].split('/') { +// tokens.append(quote! { +// path_segments.push +// }); + +// tokens.append("("); + +// if segment.starts_with(':') { +// tokens.append("&request_path."); +// tokens.append(&segment[1..]); +// tokens.append(".to_string()"); +// } else { +// tokens.append("\""); +// tokens.append(segment); +// tokens.append("\""); +// } + +// tokens.append(");"); +// } + +// tokens +// } else { +// quote! { +// url.set_path(metadata.path); +// } +// }; + +// let set_request_query = if self.request.has_query_fields() { +// let request_query_init_fields = self.request.request_query_init_fields(); + +// quote! { +// let request_query = RequestQuery { +// #request_query_init_fields +// }; + +// url.set_query(Some(&::serde_urlencoded::to_string(request_query)?)); +// } +// } else { +// Tokens::new() +// }; + +// let add_body_to_request = if let Some(field) = self.request.newtype_body_field() { +// let field_name = field.ident.as_ref().expect("expected body field to have a name"); + +// quote! { +// let request_body = RequestBody(request.#field_name); + +// hyper_request.set_body(::serde_json::to_vec(&request_body)?); +// } +// } else if self.request.has_body_fields() { +// let request_body_init_fields = self.request.request_body_init_fields(); + +// quote! { +// let request_body = RequestBody { +// #request_body_init_fields +// }; + +// hyper_request.set_body(::serde_json::to_vec(&request_body)?); +// } +// } else { +// Tokens::new() +// }; + +// let deserialize_response_body = if let Some(field) = self.response.newtype_body_field() { +// let field_type = &field.ty; +// let mut tokens = Tokens::new(); + +// tokens.append(quote! { +// let future_response = hyper_response.body() +// .fold::<_, _, Result<_, ::std::io::Error>>(Vec::new(), |mut bytes, chunk| { +// bytes.write_all(&chunk)?; + +// Ok(bytes) +// }) +// .map_err(::ruma_api::Error::from) +// .and_then(|bytes| { +// ::serde_json::from_slice::<#field_type>(bytes.as_slice()) +// .map_err(::ruma_api::Error::from) +// }) +// }); + +// tokens.append(".and_then(move |response_body| {"); + +// tokens +// } else if self.response.has_body_fields() { +// let mut tokens = Tokens::new(); + +// tokens.append(quote! { +// let future_response = hyper_response.body() +// .fold::<_, _, Result<_, ::std::io::Error>>(Vec::new(), |mut bytes, chunk| { +// bytes.write_all(&chunk)?; + +// Ok(bytes) +// }) +// .map_err(::ruma_api::Error::from) +// .and_then(|bytes| { +// ::serde_json::from_slice::(bytes.as_slice()) +// .map_err(::ruma_api::Error::from) +// }) +// }); + +// tokens.append(".and_then(move |response_body| {"); + +// tokens +// } else { +// let mut tokens = Tokens::new(); + +// tokens.append(quote! { +// let future_response = ::futures::future::ok(()) +// }); + +// tokens.append(".and_then(move |_| {"); + +// tokens +// }; + +// let mut closure_end = Tokens::new(); +// closure_end.append("});"); + +// let extract_headers = if self.response.has_header_fields() { +// quote! { +// let mut headers = hyper_response.headers().clone(); +// } +// } else { +// Tokens::new() +// }; + +// let response_init_fields = if self.response.has_fields() { +// self.response.init_fields() +// } else { +// Tokens::new() +// }; + +// tokens.append(quote! { +// #[allow(unused_imports)] +// use std::io::Write as _Write; + +// #[allow(unused_imports)] +// use ::futures::{Future as _Future, Stream as _Stream}; +// use ::ruma_api::Endpoint as _RumaApiEndpoint; + +// /// The API endpoint. +// #[derive(Debug)] +// pub struct Endpoint; + +// #request_types + +// impl ::std::convert::TryFrom for ::hyper::Request { +// type Error = ::ruma_api::Error; + +// #[allow(unused_mut, unused_variables)] +// fn try_from(request: Request) -> Result { +// let metadata = Endpoint::METADATA; + +// // Use dummy homeserver url which has to be overwritten in +// // the calling code. Previously (with hyper::Uri) this was +// // not required, but Url::parse only accepts absolute urls. +// let mut url = ::url::Url::parse("http://invalid-host-please-change/").unwrap(); + +// { #set_request_path } +// { #set_request_query } + +// let mut hyper_request = ::hyper::Request::new( +// metadata.method, +// // Every valid URL is a valid URI +// url.into_string().parse().unwrap(), +// ); + +// { #add_body_to_request } + +// Ok(hyper_request) +// } +// } + +// #response_types + +// impl ::futures::future::FutureFrom<::hyper::Response> for Response { +// type Future = Box<_Future>; +// type Error = ::ruma_api::Error; + +// #[allow(unused_variables)] +// fn future_from(hyper_response: ::hyper::Response) +// -> Box<_Future> { +// #extract_headers + +// #deserialize_response_body + +// let response = Response { +// #response_init_fields +// }; + +// Ok(response) +// #closure_end + +// Box::new(future_response) +// } +// } + +// impl ::ruma_api::Endpoint for Endpoint { +// type Request = Request; +// type Response = Response; + +// const METADATA: ::ruma_api::Metadata = ::ruma_api::Metadata { +// description: #description, +// method: ::hyper::#method, +// name: #name, +// path: #path, +// rate_limited: #rate_limited, +// requires_authentication: #requires_authentication, +// }; +// } +// }); +// } +// } + diff --git a/src/api/request.rs b/src/api/request.rs index be9729b4..163e1d31 100644 --- a/src/api/request.rs +++ b/src/api/request.rs @@ -1,5 +1,6 @@ use quote::{ToTokens, Tokens}; -use syn::{Field, MetaItem, NestedMetaItem}; +use syn::synom::Synom; +use syn::{Field, FieldsNamed, Meta, NestedMeta}; use api::strip_serde_attrs; @@ -8,6 +9,15 @@ pub struct Request { fields: Vec, } +impl Synom for Request { + named!(parse -> Self, do_parse!( + fields: syn!(FieldsNamed) >> + (Request { + fields, + }) + )); +} + impl Request { pub fn has_body_fields(&self) -> bool { self.fields.iter().any(|field| field.is_body()) @@ -74,7 +84,7 @@ impl From> for Request { field.attrs = field.attrs.into_iter().filter(|attr| { let (attr_ident, nested_meta_items) = match attr.value { - MetaItem::List(ref attr_ident, ref nested_meta_items) => (attr_ident, nested_meta_items), + Meta::List(ref attr_ident, ref nested_meta_items) => (attr_ident, nested_meta_items), _ => return true, }; @@ -84,9 +94,9 @@ impl From> for Request { for nested_meta_item in nested_meta_items { match *nested_meta_item { - NestedMetaItem::MetaItem(ref meta_item) => { + NestedMeta::Meta(ref meta_item) => { match *meta_item { - MetaItem::Word(ref ident) => { + Meta::Word(ref ident) => { if ident == "body" { has_newtype_body = true; request_field_kind = RequestFieldKind::NewtypeBody; @@ -107,7 +117,7 @@ impl From> for Request { ), } } - NestedMetaItem::Literal(_) => panic!( + NestedMeta::Literal(_) => panic!( "ruma_api! attribute meta item on requests must be: body, header, path, or query" ), } diff --git a/src/api/response.rs b/src/api/response.rs index abe0905e..2fd1782a 100644 --- a/src/api/response.rs +++ b/src/api/response.rs @@ -1,5 +1,5 @@ use quote::{ToTokens, Tokens}; -use syn::{Field, MetaItem, NestedMetaItem}; +use syn::{Field, Meta, NestedMeta}; use api::strip_serde_attrs; @@ -83,7 +83,7 @@ impl From> for Response { field.attrs = field.attrs.into_iter().filter(|attr| { let (attr_ident, nested_meta_items) = match attr.value { - MetaItem::List(ref attr_ident, ref nested_meta_items) => { + Meta::List(ref attr_ident, ref nested_meta_items) => { (attr_ident, nested_meta_items) } _ => return true, @@ -95,9 +95,9 @@ impl From> for Response { for nested_meta_item in nested_meta_items { match *nested_meta_item { - NestedMetaItem::MetaItem(ref meta_item) => { + NestedMeta::Meta(ref meta_item) => { match *meta_item { - MetaItem::Word(ref ident) => { + Meta::Word(ref ident) => { if ident == "body" { has_newtype_body = true; response_field_kind = ResponseFieldKind::NewtypeBody; @@ -114,7 +114,7 @@ impl From> for Response { ), } } - NestedMetaItem::Literal(_) => panic!( + NestedMeta::Literal(_) => panic!( "ruma_api! attribute meta item on responses must be: header" ), } diff --git a/src/lib.rs b/src/lib.rs index 539d8258..2c4341f7 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -4,24 +4,24 @@ //! See the documentation for the `ruma_api!` macro for usage details. #![deny(missing_debug_implementations)] -#![feature(proc_macro)] +#![feature(proc_macro, try_from)] #![recursion_limit="256"] +#![allow(warnings)] extern crate proc_macro; #[macro_use] extern crate quote; extern crate ruma_api; -extern crate syn; -#[macro_use] extern crate synom; +#[macro_use] extern crate syn; use proc_macro::TokenStream; +use std::convert::TryFrom; use quote::{ToTokens, Tokens}; -use api::Api; -use parse::parse_entries; +use api::{Api, Exprs}; mod api; -mod parse; +// mod parse; /// Generates a `ruma_api::Endpoint` from a concise definition. /// @@ -196,13 +196,15 @@ mod parse; /// ``` #[proc_macro] pub fn ruma_api(input: TokenStream) -> TokenStream { - let entries = parse_entries(&input.to_string()).expect("ruma_api! failed to parse input"); - - let api = Api::from(entries); + let exprs: Exprs = syn::parse(input).expect("ruma_api! failed to parse input"); + let api = match Api::try_from(exprs.inner) { + Ok(api) => api, + Err(error) => panic!("{}", error), + }; let mut tokens = Tokens::new(); api.to_tokens(&mut tokens); - tokens.parse().expect("ruma_api! failed to parse output tokens as a TokenStream") + tokens.into() } diff --git a/src/parse.rs b/src/parse.rs index 4b4a0e80..483efb87 100644 --- a/src/parse.rs +++ b/src/parse.rs @@ -6,12 +6,12 @@ use syn::{ Expr, Field, Ident, - MetaItem, - NestedMetaItem, + Meta, + NestedMeta, Visibility, }; -use syn::parse::{expr, ident, lit, ty}; -use synom::space::{block_comment, whitespace}; +// use syn::parse::{expr, ident, lit, ty}; +// use synom::space::{block_comment, whitespace}; #[derive(Debug)] pub enum Entry { @@ -27,27 +27,24 @@ named!(pub parse_entries -> Vec, do_parse!( named!(entry -> Entry, alt!( do_parse!( - keyword!("metadata") >> - punct!("{") >> - fields: many0!(struct_init_field) >> - punct!("}") >> - (Entry::Metadata(fields)) + block_type: syn!(Ident) >> + cond_reduce!(block_type == "metadata") >> + brace_and_fields: braces!(many0!(struct_init_field)) >> + (Entry::Metadata(brace_and_fields.1)) ) | do_parse!( - keyword!("request") >> - punct!("{") >> - fields: terminated_list!(punct!(","), struct_field) >> - punct!("}") >> - (Entry::Request(fields)) + block_type: syn!(Ident) >> + cond_reduce!(block_type == "request") >> + brace_and_fields: braces!(terminated_list!(punct!(","), struct_field)) >> + (Entry::Request(brace_and_fields.1)) ) | do_parse!( - keyword!("response") >> - punct!("{") >> - fields: terminated_list!(punct!(","), struct_field) >> - punct!("}") >> - (Entry::Response(fields)) + block_type: syn!(Ident) >> + cond_reduce!(block_type == "response") >> + brace_and_fields: braces!(terminated_list!(punct!(","), struct_field)) >> + (Entry::Response(brace_and_fields.1)) ) )); @@ -55,9 +52,9 @@ named!(entry -> Entry, alt!( named!(struct_init_field -> (Ident, Expr), do_parse!( ident: ident >> - punct!(":") >> + punct!(:) >> expr: expr >> - punct!(",") >> + punct!(,) >> (ident, expr) )); @@ -65,7 +62,7 @@ named!(struct_field -> Field, do_parse!( attrs: many0!(outer_attr) >> visibility >> id: ident >> - punct!(":") >> + punct!(:) >> ty: ty >> (Field { ident: Some(id), @@ -77,24 +74,24 @@ named!(struct_field -> Field, do_parse!( named!(outer_attr -> Attribute, alt!( do_parse!( - punct!("#") >> - punct!("[") >> - meta_item: meta_item >> - punct!("]") >> + punct!(#) >> + brackets_and_meta_item: brackets!(meta_item) >> (Attribute { style: AttrStyle::Outer, - value: meta_item, + value: brackets_and_meta_item.1, is_sugared_doc: false, }) ) | do_parse!( - punct!("///") >> + punct!(/) >> + punct!(/) >> + punct!(/) >> not!(tag!("/")) >> content: take_until!("\n") >> (Attribute { style: AttrStyle::Outer, - value: MetaItem::NameValue( + value: Meta::NameValue( "doc".into(), format!("///{}", content).into(), ), @@ -108,7 +105,7 @@ named!(outer_attr -> Attribute, alt!( com: block_comment >> (Attribute { style: AttrStyle::Outer, - value: MetaItem::NameValue( + value: Meta::NameValue( "doc".into(), com.into(), ), @@ -117,33 +114,31 @@ named!(outer_attr -> Attribute, alt!( ) )); -named!(meta_item -> MetaItem, alt!( +named!(meta_item -> Meta, alt!( do_parse!( id: ident >> - punct!("(") >> - inner: terminated_list!(punct!(","), nested_meta_item) >> - punct!(")") >> - (MetaItem::List(id, inner)) + parens_and_inner: parens!(terminated_list!(punct!(,), nested_meta_item)) >> + (Meta::List(id, parens_and_inner.1)) ) | do_parse!( name: ident >> - punct!("=") >> + punct!(=) >> value: lit >> - (MetaItem::NameValue(name, value)) + (Meta::NameValue(name, value)) ) | - map!(ident, MetaItem::Word) + map!(ident, Meta::Word) )); -named!(nested_meta_item -> NestedMetaItem, alt!( - meta_item => { NestedMetaItem::MetaItem } +named!(nested_meta_item -> NestedMeta, alt!( + meta_item => { NestedMeta::Meta } | - lit => { NestedMetaItem::Literal } + lit => { NestedMeta::Literal } )); named!(visibility -> Visibility, alt!( - keyword!("pub") => { |_| Visibility::Public } + keyword!(pub) => { |_| Visibility::Public } | epsilon!() => { |_| Visibility::Inherited } ));