From bf7189048a5354bf516e8c28ca565596eb75399e Mon Sep 17 00:00:00 2001 From: Jimmy Cuadra Date: Sat, 13 May 2017 00:19:14 -0700 Subject: [PATCH] Use custom synom parsing. --- Cargo.toml | 12 ++- src/lib.rs | 168 +++++---------------------------------- src/parse.rs | 144 +++++++++++++++++++++++++++++++++ tests/ruma_api_macros.rs | 10 +-- 4 files changed, 178 insertions(+), 156 deletions(-) create mode 100644 src/parse.rs diff --git a/Cargo.toml b/Cargo.toml index 62b3d253..8b16922b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,14 +5,18 @@ version = "0.1.0" [dependencies] quote = "0.3.15" -syn = { version = "0.11.11", features = ["full"] } - -[dependencies.ruma-api] -path = "../ruma-api" +synom = "0.11.3" [dependencies.hyper] git = "https://github.com/hyperium/hyper" rev = "fed04dfb58e19b408322d4e5ca7474871e848a35" +[dependencies.ruma-api] +path = "../ruma-api" + +[dependencies.syn] +features = ["full"] +version = "0.11.11" + [lib] proc-macro = true diff --git a/src/lib.rs b/src/lib.rs index 0727d79d..3ad4079a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,25 +1,27 @@ #![feature(proc_macro)] -extern crate hyper; extern crate proc_macro; extern crate quote; extern crate ruma_api; extern crate syn; +#[macro_use] extern crate synom; use proc_macro::TokenStream; -use hyper::Method; -use quote::{ToTokens, Tokens}; -use syn::{ExprKind, Item, ItemKind, Lit, parse_items}; +use quote::Tokens; +use syn::{Expr, Field, Ident, Item}; + +use parse::{Entry, parse_entries}; + +mod parse; #[proc_macro] pub fn ruma_api(input: TokenStream) -> TokenStream { - let items = parse_items(&input.to_string()) - .expect("failed to parse input"); + let entries = parse_entries(&input.to_string()).expect("ruma_api! failed to parse input"); - let api = Api::from(items); + let api = Api::from(entries); - api.output().parse().expect("failed to parse output") + api.output().parse().expect("ruma_api! failed to parse output as a TokenStream") } struct Api { @@ -34,41 +36,19 @@ impl Api { } } -impl From> for Api { - fn from(items: Vec) -> Api { - if items.len() != 3 { - panic!("ruma_api! expects exactly three items: const METADATA, struct Request, and struct Response"); - } - - let mut metadata = None; - let mut request = None; - let mut response = None; - - for item in items { - match &item.ident.to_string()[..] { - "METADATA" => metadata = Some(Metadata::from(item)), - "Request" => request = Some(Request::from(item)), - "Response" => response = Some(Response::from(item)), - other => panic!("ruma_api! found unexpected item: {}", other), - } - } - - if metadata.is_none() { - panic!("ruma_api! requires item: const METADATA"); - } - - if request.is_none() { - panic!("ruma_api! requires item: struct Request"); - } - - if response.is_none() { - panic!("ruma_api! requires item: struct Response"); - } - +impl From> for Api { + fn from(entries: Vec) -> Api { Api { - metadata: metadata.expect("metadata is missing"), - request: request.expect("request is missing"), - response: response.expect("response is missing"), + 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, } } } @@ -82,104 +62,6 @@ struct Metadata { requires_authentication: Tokens, } -impl From for Metadata { - fn from(item: Item) -> Self { - let expr = match item.node { - ItemKind::Const(_, expr) => expr, - _ => panic!("ruma_api! expects METADATA to be a const item"), - }; - - let field_values = match expr.node { - ExprKind::Struct(_, field_values, _) => field_values, - _ => panic!("ruma_api! expects METADATA to be a Metadata struct"), - }; - - 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_value in field_values { - match &field_value.ident.to_string()[..] { - "description" => { - match field_value.expr.node { - ExprKind::Lit(Lit::Str(value, _)) => description = Some(tokens_for(value)), - _ => panic!("ruma_api! expects metadata description to be a &'static str"), - } - } - "method" => { - match field_value.expr.node { - ExprKind::Path(_, value) => method = Some(tokens_for(value)), - _ => panic!("ruma_api! expects metadata method to be a path"), - } - } - "name" => { - match field_value.expr.node { - ExprKind::Lit(Lit::Str(value, _)) => name = Some(tokens_for(value)), - _ => panic!("ruma_api! expects metadata name to be a &'static str"), - } - } - "path" => { - match field_value.expr.node { - ExprKind::Lit(Lit::Str(value, _)) => path = Some(tokens_for(value)), - _ => panic!("ruma_api! expects metadata path to be a &'static str"), - } - } - "rate_limited" => { - match field_value.expr.node { - ExprKind::Lit(Lit::Bool(value)) => rate_limited = Some(tokens_for(value)), - _ => panic!("ruma_api! expects metadata rate_limited to be a bool"), - } - } - "requires_authentication" => { - match field_value.expr.node { - ExprKind::Lit(Lit::Bool(value)) => { - requires_authentication = Some(tokens_for(value)); - } - _ => panic!("ruma_api! expects metadata requires_authentication to be a bool"), - } - } - other => panic!("ruma_api! found unexpected metadata field: {}", other), - } - } - - if description.is_none() { - panic!("ruma_api! metadata requires field: description"); - } - - if method.is_none() { - panic!("ruma_api! metadata requires field: method"); - } - - if name.is_none() { - panic!("ruma_api! metadata requires field: name"); - } - - if path.is_none() { - panic!("ruma_api! metadata requires field: path"); - } - - if rate_limited.is_none() { - panic!("ruma_api! metadata requires field: rate_limited"); - } - - if requires_authentication.is_none() { - panic!("ruma_api! metadata requires field: requires_authentication"); - } - - Metadata { - description: description.expect("description is missing"), - method: method.expect("method is missing"), - name: name.expect("name is missing"), - path: path.expect("path is missing"), - rate_limited: rate_limited.expect("rate limited is missing"), - requires_authentication: requires_authentication.expect("requires_authentication is missing"), - } - } -} - struct Request; impl From for Request { @@ -197,9 +79,3 @@ impl From for Response { // panic!("ruma_api! could not parse Response"); } } - -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 new file mode 100644 index 00000000..b9651276 --- /dev/null +++ b/src/parse.rs @@ -0,0 +1,144 @@ +use syn::{ + Attribute, + AttrStyle, + Expr, + Field, + Ident, + Item, + Lit, + MetaItem, + NestedMetaItem, + StrStyle, + Token, + TokenTree, + Visibility, +}; +use syn::parse::{expr, ident, lit, ty}; +use synom::space::{block_comment, whitespace}; + +pub enum Entry { + Metadata(Vec<(Ident, Expr)>), + Request(Vec), + Response(Vec), +} + +named!(pub parse_entries -> Vec, do_parse!( + entries: many0!(entry) >> + (entries) +)); + +named!(entry -> Entry, alt!( + do_parse!( + keyword!("metadata") >> + punct!(":") >> + punct!("{") >> + fields: many0!(struct_init_field) >> + (Entry::Metadata(fields)) + ) + | + do_parse!( + keyword!("request") >> + punct!(":") >> + punct!("{") >> + fields: many0!(struct_field) >> + (Entry::Request(fields)) + ) + | + do_parse!( + keyword!("response") >> + punct!(":") >> + punct!("{") >> + fields: many0!(struct_field) >> + (Entry::Response(fields)) + ) +)); + +// Everything below copy/pasted from syn 0.11.11. + +named!(struct_init_field -> (Ident, Expr), do_parse!( + ident: ident >> + punct!(":") >> + expr: expr >> + punct!(",") >> + (ident, expr) +)); + +named!(struct_field -> Field, do_parse!( + attrs: many0!(outer_attr) >> + id: ident >> + punct!(":") >> + ty: ty >> + (Field { + ident: Some(id), + vis: Visibility::Public, + attrs: attrs, + ty: ty, + }) +)); + +named!(outer_attr -> Attribute, alt!( + do_parse!( + punct!("#") >> + punct!("[") >> + meta_item: meta_item >> + punct!("]") >> + (Attribute { + style: AttrStyle::Outer, + value: meta_item, + is_sugared_doc: false, + }) + ) + | + do_parse!( + punct!("///") >> + not!(tag!("/")) >> + content: take_until!("\n") >> + (Attribute { + style: AttrStyle::Outer, + value: MetaItem::NameValue( + "doc".into(), + format!("///{}", content).into(), + ), + is_sugared_doc: true, + }) + ) + | + do_parse!( + option!(whitespace) >> + peek!(tuple!(tag!("/**"), not!(tag!("*")))) >> + com: block_comment >> + (Attribute { + style: AttrStyle::Outer, + value: MetaItem::NameValue( + "doc".into(), + com.into(), + ), + is_sugared_doc: true, + }) + ) +)); + +named!(meta_item -> MetaItem, alt!( + do_parse!( + id: ident >> + punct!("(") >> + inner: terminated_list!(punct!(","), nested_meta_item) >> + punct!(")") >> + (MetaItem::List(id, inner)) + ) + | + do_parse!( + name: ident >> + punct!("=") >> + value: lit >> + (MetaItem::NameValue(name, value)) + ) + | + map!(ident, MetaItem::Word) +)); + +named!(nested_meta_item -> NestedMetaItem, alt!( + meta_item => { NestedMetaItem::MetaItem } + | + lit => { NestedMetaItem::Literal } +)); diff --git a/tests/ruma_api_macros.rs b/tests/ruma_api_macros.rs index 881989a7..1464de06 100644 --- a/tests/ruma_api_macros.rs +++ b/tests/ruma_api_macros.rs @@ -8,18 +8,16 @@ pub mod get_supported_versions { use ruma_api_macros::ruma_api; ruma_api! { - const METADATA: Metadata = 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, - }; - - struct Request; - - struct Response { + }, + request: {}, + response: { /// A list of Matrix client API protocol versions supported by the homeserver. pub versions: Vec, }