From d0a35341a2db1197232dc31fd5481d69ace8ea8b Mon Sep 17 00:00:00 2001 From: Jimmy Cuadra Date: Sat, 13 May 2017 01:16:44 -0700 Subject: [PATCH] Use three block form for the macro, fix some bugs, construct metadata tokens. --- src/lib.rs | 91 ++++++++++++++++++++++++++++++++-------- src/parse.rs | 27 +++++++++--- tests/ruma_api_macros.rs | 10 +++-- 3 files changed, 101 insertions(+), 27 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 3ad4079a..e8d5119b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -8,7 +8,7 @@ extern crate syn; use proc_macro::TokenStream; -use quote::Tokens; +use quote::{ToTokens, Tokens}; use syn::{Expr, Field, Ident, Item}; use parse::{Entry, parse_entries}; @@ -38,17 +38,26 @@ impl Api { impl From> for Api { fn from(entries: Vec) -> Api { + if entries.len() != 3 { + panic!("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)), + } + } + Api { - metadata: Metadata { - description: Tokens::new(), - method: Tokens::new(), - name: Tokens::new(), - path: Tokens::new(), - rate_limited: Tokens::new(), - requires_authentication: Tokens::new(), - }, - request: Request, - response: Response, + metadata: metadata.expect("ruma_api! is missing metadata"), + request: request.expect("ruma_api! is missing request"), + response: response.expect("ruma_api! is missing response"), } } } @@ -62,20 +71,68 @@ struct Metadata { requires_authentication: Tokens, } +impl From> for Metadata { + fn from(fields: Vec<(Ident, Expr)>) -> Self { + let mut description = None; + let mut method = None; + let mut name = None; + let mut path = None; + let mut rate_limited = None; + let mut requires_authentication = None; + + for field in fields { + let (identifier, expression) = field; + + if identifier == Ident::new("description") { + description = Some(tokens_for(expression)); + } else if identifier == Ident::new("method") { + method = Some(tokens_for(expression)); + } else if identifier == Ident::new("name") { + name = Some(tokens_for(expression)); + } else if identifier == Ident::new("path") { + path = Some(tokens_for(expression)); + } else if identifier == Ident::new("rate_limited") { + rate_limited = Some(tokens_for(expression)); + } else if identifier == Ident::new("requires_authentication") { + requires_authentication = Some(tokens_for(expression)); + } else { + panic!("ruma_api! metadata included unexpected field: {}", identifier); + } + } + + Metadata { + description: description.expect("ruma_api! metadata is missing description"), + method: method.expect("ruma_api! metadata is missing method"), + name: name.expect("ruma_api! metadata is missing name"), + path: path.expect("ruma_api! metadata is missing path"), + rate_limited: rate_limited.expect("ruma_api! metadata is missing rate_limited"), + requires_authentication: requires_authentication + .expect("ruma_api! metadata is missing requires_authentication"), + } + } +} + struct Request; -impl From for Request { - fn from(item: Item) -> Self { +impl From> for Request { + fn from(fields: Vec) -> Self { Request - // panic!("ruma_api! could not parse Request"); } } struct Response; -impl From for Response { - fn from(item: Item) -> Self { +impl From> for Response { + fn from(fields: Vec) -> Self { Response - // panic!("ruma_api! could not parse Response"); } } + +/// Helper method for turning a value into tokens. +fn tokens_for(value: T) -> Tokens where T: ToTokens { + let mut tokens = Tokens::new(); + + value.to_tokens(&mut tokens); + + tokens +} diff --git a/src/parse.rs b/src/parse.rs index b9651276..03cc5ffe 100644 --- a/src/parse.rs +++ b/src/parse.rs @@ -16,6 +16,7 @@ use syn::{ use syn::parse::{expr, ident, lit, ty}; use synom::space::{block_comment, whitespace}; +#[derive(Debug)] pub enum Entry { Metadata(Vec<(Ident, Expr)>), Request(Vec), @@ -30,25 +31,25 @@ named!(pub parse_entries -> Vec, do_parse!( named!(entry -> Entry, alt!( do_parse!( keyword!("metadata") >> - punct!(":") >> punct!("{") >> fields: many0!(struct_init_field) >> + punct!("}") >> (Entry::Metadata(fields)) ) | do_parse!( keyword!("request") >> - punct!(":") >> punct!("{") >> - fields: many0!(struct_field) >> + fields: terminated_list!(punct!(","), struct_field) >> + punct!("}") >> (Entry::Request(fields)) ) | do_parse!( keyword!("response") >> - punct!(":") >> punct!("{") >> - fields: many0!(struct_field) >> + fields: terminated_list!(punct!(","), struct_field) >> + punct!("}") >> (Entry::Response(fields)) ) )); @@ -63,14 +64,22 @@ named!(struct_init_field -> (Ident, Expr), do_parse!( (ident, expr) )); +named!(pub struct_like_body -> Vec, do_parse!( + punct!("{") >> + fields: terminated_list!(punct!(","), struct_field) >> + punct!("}") >> + (fields) +)); + named!(struct_field -> Field, do_parse!( attrs: many0!(outer_attr) >> + vis: visibility >> id: ident >> punct!(":") >> ty: ty >> (Field { ident: Some(id), - vis: Visibility::Public, + vis: Visibility::Public, // Ignore declared visibility, always make fields public attrs: attrs, ty: ty, }) @@ -142,3 +151,9 @@ named!(nested_meta_item -> NestedMetaItem, alt!( | lit => { NestedMetaItem::Literal } )); + +named!(visibility -> Visibility, alt!( + keyword!("pub") => { |_| Visibility::Public } + | + epsilon!() => { |_| Visibility::Inherited } +)); diff --git a/tests/ruma_api_macros.rs b/tests/ruma_api_macros.rs index 1464de06..b65a5aca 100644 --- a/tests/ruma_api_macros.rs +++ b/tests/ruma_api_macros.rs @@ -8,16 +8,18 @@ pub mod get_supported_versions { use ruma_api_macros::ruma_api; ruma_api! { - metadata: { + metadata { description: "Get the versions of the client-server API supported by this homeserver.", method: Method::Get, name: "api_versions", path: "/_matrix/client/versions", rate_limited: false, requires_authentication: true, - }, - request: {}, - response: { + } + + request {} + + response { /// A list of Matrix client API protocol versions supported by the homeserver. pub versions: Vec, }