Use custom synom parsing.

This commit is contained in:
Jimmy Cuadra 2017-05-13 00:19:14 -07:00
parent 69522626ff
commit bf7189048a
4 changed files with 178 additions and 156 deletions

View File

@ -5,14 +5,18 @@ version = "0.1.0"
[dependencies] [dependencies]
quote = "0.3.15" quote = "0.3.15"
syn = { version = "0.11.11", features = ["full"] } synom = "0.11.3"
[dependencies.ruma-api]
path = "../ruma-api"
[dependencies.hyper] [dependencies.hyper]
git = "https://github.com/hyperium/hyper" git = "https://github.com/hyperium/hyper"
rev = "fed04dfb58e19b408322d4e5ca7474871e848a35" rev = "fed04dfb58e19b408322d4e5ca7474871e848a35"
[dependencies.ruma-api]
path = "../ruma-api"
[dependencies.syn]
features = ["full"]
version = "0.11.11"
[lib] [lib]
proc-macro = true proc-macro = true

View File

@ -1,25 +1,27 @@
#![feature(proc_macro)] #![feature(proc_macro)]
extern crate hyper;
extern crate proc_macro; extern crate proc_macro;
extern crate quote; extern crate quote;
extern crate ruma_api; extern crate ruma_api;
extern crate syn; extern crate syn;
#[macro_use] extern crate synom;
use proc_macro::TokenStream; use proc_macro::TokenStream;
use hyper::Method; use quote::Tokens;
use quote::{ToTokens, Tokens}; use syn::{Expr, Field, Ident, Item};
use syn::{ExprKind, Item, ItemKind, Lit, parse_items};
use parse::{Entry, parse_entries};
mod parse;
#[proc_macro] #[proc_macro]
pub fn ruma_api(input: TokenStream) -> TokenStream { pub fn ruma_api(input: TokenStream) -> TokenStream {
let items = parse_items(&input.to_string()) let entries = parse_entries(&input.to_string()).expect("ruma_api! failed to parse input");
.expect("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 { struct Api {
@ -34,41 +36,19 @@ impl Api {
} }
} }
impl From<Vec<Item>> for Api { impl From<Vec<Entry>> for Api {
fn from(items: Vec<Item>) -> Api { fn from(entries: Vec<Entry>) -> 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");
}
Api { Api {
metadata: metadata.expect("metadata is missing"), metadata: Metadata {
request: request.expect("request is missing"), description: Tokens::new(),
response: response.expect("response is missing"), 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, requires_authentication: Tokens,
} }
impl From<Item> 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; struct Request;
impl From<Item> for Request { impl From<Item> for Request {
@ -197,9 +79,3 @@ impl From<Item> for Response {
// panic!("ruma_api! could not parse Response"); // panic!("ruma_api! could not parse Response");
} }
} }
fn tokens_for<T>(value: T) -> Tokens where T: ToTokens {
let mut tokens = Tokens::new();
value.to_tokens(&mut tokens);
tokens
}

144
src/parse.rs Normal file
View File

@ -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<Field>),
Response(Vec<Field>),
}
named!(pub parse_entries -> Vec<Entry>, 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 }
));

View File

@ -8,18 +8,16 @@ pub mod get_supported_versions {
use ruma_api_macros::ruma_api; use ruma_api_macros::ruma_api;
ruma_api! { ruma_api! {
const METADATA: Metadata = Metadata { metadata: {
description: "Get the versions of the client-server API supported by this homeserver.", description: "Get the versions of the client-server API supported by this homeserver.",
method: Method::Get, method: Method::Get,
name: "api_versions", name: "api_versions",
path: "/_matrix/client/versions", path: "/_matrix/client/versions",
rate_limited: false, rate_limited: false,
requires_authentication: true, requires_authentication: true,
}; },
request: {},
struct Request; response: {
struct Response {
/// A list of Matrix client API protocol versions supported by the homeserver. /// A list of Matrix client API protocol versions supported by the homeserver.
pub versions: Vec<String>, pub versions: Vec<String>,
} }