diff --git a/macros/src/error_decorator.rs b/macros/src/error_decorator.rs index 3a42aa3d..545da59c 100644 --- a/macros/src/error_decorator.rs +++ b/macros/src/error_decorator.rs @@ -1,8 +1,7 @@ use utils::*; +use meta_item_parser::MetaItemParser; use super::{CATCH_STRUCT_PREFIX, CATCH_FN_PREFIX}; -use route_decorator::get_fn_decl; - use syntax::codemap::{Span}; use syntax::ast::{MetaItem}; use syntax::ext::base::{Annotatable, ExtCtxt}; @@ -16,7 +15,7 @@ struct Params { code: KVSpanned, } -fn get_error_params(ecx: &mut ExtCtxt, meta_item: &MetaItem) -> Params { +fn get_error_params(ecx: &ExtCtxt, meta_item: &MetaItem) -> Params { // Ensure we've been supplied with a k = v meta item. Error out if not. let params = meta_item.expect_list(ecx, "Bad use. Expected: #[error(...)]"); @@ -49,7 +48,9 @@ fn get_error_params(ecx: &mut ExtCtxt, meta_item: &MetaItem) -> Params { pub fn error_decorator(ecx: &mut ExtCtxt, sp: Span, meta_item: &MetaItem, annotated: &Annotatable, push: &mut FnMut(Annotatable)) { - let (item, _fn_decl) = get_fn_decl(ecx, sp, annotated); + let parser = MetaItemParser::new(ecx, meta_item, annotated, &sp); + let item = parser.expect_item(); + let error_params = get_error_params(ecx, meta_item); debug!("Error parameters are: {:?}", error_params); diff --git a/macros/src/lib.rs b/macros/src/lib.rs index 225e2a30..37b467dc 100644 --- a/macros/src/lib.rs +++ b/macros/src/lib.rs @@ -13,6 +13,7 @@ mod errors_macro; mod route_decorator; mod error_decorator; mod derive_form; +mod meta_item_parser; use rustc_plugin::Registry; use syntax::ext::base::SyntaxExtension; @@ -39,4 +40,6 @@ pub fn plugin_registrar(reg: &mut Registry) { SyntaxExtension::MultiDecorator(Box::new(from_form_derive))); reg.register_macro("routes", routes_macro); reg.register_macro("errors", errors_macro); + +// reg.register_macro("GET", get_macro); } diff --git a/macros/src/meta_item_parser.rs b/macros/src/meta_item_parser.rs new file mode 100644 index 00000000..ad088801 --- /dev/null +++ b/macros/src/meta_item_parser.rs @@ -0,0 +1,211 @@ +use std::str::FromStr; + +use syntax::ext::base::{Annotatable, ExtCtxt}; +use syntax::ast::{Item, ItemKind, MetaItem, FnDecl}; +use syntax::codemap::{Span, Spanned, BytePos}; +use syntax::ptr::P; + +use utils::*; +use rocket::Method; + +pub struct MetaItemParser<'a, 'c: 'a> { + attr_name: &'a str, + ctxt: &'a ExtCtxt<'c>, + meta_item: &'a MetaItem, + annotated: &'a Annotatable, + span: Span +} + +pub struct ParamIter<'s, 'a, 'c: 'a> { + ctxt: &'a ExtCtxt<'c>, + span: Span, + string: &'s str +} + +impl<'a, 'c> MetaItemParser<'a, 'c> { + pub fn new(ctxt: &'a ExtCtxt<'c>, meta_item: &'a MetaItem, + annotated: &'a Annotatable, span: &'a Span) -> MetaItemParser<'a, 'c> { + MetaItemParser { + attr_name: meta_item.name(), + ctxt: ctxt, + meta_item: meta_item, + annotated: annotated, + span: span.clone(), + } + } + + fn bad_item(&self, expected: &str, got: &str, sp: Span) -> ! { + let msg_a = format!("Expected a {} item...", expected); + let msg_b = format!("...but found a {} item instead.", got); + self.ctxt.span_err(self.span, msg_a.as_str()); + self.ctxt.span_fatal(sp, msg_b.as_str()) + } + + + pub fn expect_item(&self) -> &'a P { + let bad_item = |name: &str, sp: Span| self.bad_item("regular", name, sp); + + match *self.annotated { + Annotatable::Item(ref item) => item, + Annotatable::TraitItem(ref item) => bad_item("trait", item.span), + Annotatable::ImplItem(ref item) => bad_item("impl", item.span) + } + } + + pub fn expect_fn_decl(&self) -> Spanned<&'a FnDecl> { + let item = self.expect_item(); + let bad_item = |name: &str| self.bad_item("fn_decl", name, item.span); + + let fn_decl: &P = match item.node { + ItemKind::Fn(ref decl, _, _, _, _, _) => decl, + _ => bad_item("other") + }; + + span(fn_decl, item.span) + } + + fn expect_list(&self) -> &'a Vec> { + let msg = format!("Bad use. Expected: #[{}(...)]", self.attr_name); + self.meta_item.expect_list(self.ctxt, msg.as_str()) + } + + pub fn iter_params<'s>(&self, from: &Spanned<&'s str>) -> ParamIter<'s, 'a, 'c> { + ParamIter { + ctxt: self.ctxt, + span: from.span, + string: from.node + } + } +} + +impl<'s, 'a, 'c> Iterator for ParamIter<'s, 'a, 'c> { + type Item = Spanned<&'s str>; + + fn next(&mut self) -> Option> { + // Find the start and end indexes for the next parameter, if any. + let (start, end) = match (self.string.find('<'), self.string.find('>')) { + (Some(i), Some(j)) => (i, j), + _ => return None + }; + + // Ensure we found a valid parameter. + if end <= start { + self.ctxt.span_err(self.span, "Parameter list is malformed."); + return None; + } + + // Calculate the parameter and the span for the parameter. + let param = &self.string[(start + 1)..end]; + let mut param_span = self.span; + param_span.lo = self.span.lo + BytePos(start as u32); + param_span.hi = self.span.lo + BytePos((end + 1) as u32); + + // Check for nonemptiness and that the characters are correct. + if param.len() == 0 { + self.ctxt.span_err(param_span, "Parameter names cannot be empty."); + None + } else if param.contains(|c: char| !c.is_alphanumeric()) { + self.ctxt.span_err(param_span, "Parameters must be alphanumeric."); + None + } else { + self.string = &self.string[(end + 1)..]; + self.span.lo = self.span.lo + BytePos((end + 1) as u32); + Some(span(param, param_span)) + } + } +} + +pub struct RouteParams { + pub method: Spanned, + pub path: KVSpanned, + pub form: Option>, +} + +pub trait RouteDecoratorExt { + fn bad_method(&self, sp: Span, message: &str); + fn parse_method(&self, default: Method) -> Spanned; + fn parse_route(&self, known_method: Option>) -> RouteParams; +} + +impl<'a, 'c> RouteDecoratorExt for MetaItemParser<'a, 'c> { + fn bad_method(&self, sp: Span, message: &str) { + let message = format!("{} Valid methods are: [GET, PUT, POST, DELETE, \ + OPTIONS, HEAD, TRACE, CONNECT, PATCH]", message); + self.ctxt.span_err(sp, message.as_str()); + } + + fn parse_method(&self, default: Method) -> Spanned { + let params = self.expect_list(); + if params.len() < 1 { + self.bad_method(self.span, "HTTP method parameter is missing."); + self.ctxt.span_fatal(self.span, "At least 2 arguments are required."); + } + + // Get the method and the rest of the k = v params. + let method_param = params.first().unwrap(); + + // Check that the method parameter is a word (i.e, not a list, k/v pair). + if !method_param.is_word() { + self.bad_method(method_param.span, + "Expected a valid HTTP method at this position."); + return dummy_span(default); + } + + // Parse the method from the string. If bad, error and return default. + Method::from_str(method_param.name()).ok().map_or_else(|| { + let message = format!("{} is not a valid method.", method_param.name()); + self.bad_method(method_param.span, message.as_str()); + dummy_span(default) + }, |method| span(method, method_param.span)) + } + + // Parses the MetaItem derived from the route(...) macro. + fn parse_route(&self, known_method: Option>) -> RouteParams { + let list = self.expect_list(); + let (method, kv_params) = match known_method { + Some(method) => (method, &list[..]), + None => (self.parse_method(Method::Get), list.split_first().unwrap().1) + }; + + // Now grab all of the required and optional parameters. + let req: [&'static str; 1] = ["path"]; + let opt: [&'static str; 1] = ["form"]; + let kv_pairs = get_key_values(self.ctxt, self.meta_item.span, + &req, &opt, kv_params); + + // Ensure we have a path, just to keep parsing and generating errors. + let path = kv_pairs.get("path").map_or(KVSpanned::dummy("/".to_string()), |s| { + s.clone().map(String::from) + }); + + // If there's a form parameter, ensure method is POST. + let form = kv_pairs.get("form").map_or(None, |f| { + if method.node != Method::Post { + self.ctxt.span_err(f.p_span, "Use of `form` requires POST method..."); + let message = format!("...but {} was found instead.", method.node); + self.ctxt.span_err(method.span, message.as_str()); + } + + if !(f.node.starts_with('<') && f.node.ends_with('>')) { + self.ctxt.struct_span_err(f.p_span, + "`form` cannot contain arbitrary text") + .help("`form` must be exactly one parameter: \"\"") + .emit(); + } + + if f.node.chars().filter(|c| *c == '<' || *c == '>').count() != 2 { + self.ctxt.span_err(f.p_span, + "`form` must contain exactly one parameter"); + } + + Some(f.clone().map(String::from)) + }); + + RouteParams { + method: method, + path: path, + form: form + } + } + +} diff --git a/macros/src/route_decorator.rs b/macros/src/route_decorator.rs index 5aa208cc..5bfea030 100644 --- a/macros/src/route_decorator.rs +++ b/macros/src/route_decorator.rs @@ -1,11 +1,11 @@ use super::{ROUTE_STRUCT_PREFIX, ROUTE_FN_PREFIX}; use utils::*; +use meta_item_parser::{MetaItemParser, RouteDecoratorExt}; -use std::str::FromStr; use std::collections::HashMap; use syntax::codemap::{Span, BytePos, /* DUMMY_SP, */ Spanned}; -use syntax::ast::{Stmt, Item, Expr, ItemKind, MetaItem, MetaItemKind, FnDecl}; +use syntax::ast::{Stmt, Expr, MetaItem, FnDecl}; use syntax::ext::base::{Annotatable, ExtCtxt}; use syntax::ptr::P; use syntax::print::pprust::{item_to_string, stmt_to_string}; @@ -16,156 +16,16 @@ use rocket::Method; #[allow(dead_code)] const DEBUG: bool = true; -struct Params { - method: Spanned, - path: KVSpanned, - form: Option>, -} - -fn bad_item_fatal(ecx: &mut ExtCtxt, dec_sp: Span, i_sp: Span) -> ! { - ecx.span_err(dec_sp, "This decorator cannot be used on non-functions..."); - ecx.span_fatal(i_sp, "...but it was used on the item below.") -} - -fn bad_method_err(ecx: &mut ExtCtxt, dec_sp: Span, message: &str) -> Method { - let message = format!("{} Valid methods are: [GET, PUT, POST, DELETE, \ - OPTIONS, HEAD, TRACE, CONNECT, PATCH]", message); - ecx.span_err(dec_sp, message.as_str()); - Method::Get -} - -pub fn get_fn_decl<'a>(ecx: &mut ExtCtxt, sp: Span, annotated: &'a Annotatable) - -> (&'a P, Spanned<&'a FnDecl>) { - // `annotated` is the AST object for the annotated item. - let item: &P = match *annotated { - Annotatable::Item(ref item) => item, - Annotatable::TraitItem(ref item) => bad_item_fatal(ecx, sp, item.span), - Annotatable::ImplItem(ref item) => bad_item_fatal(ecx, sp, item.span) - }; - - let fn_decl: &P = match item.node { - ItemKind::Fn(ref decl, _, _, _, _, _) => decl, - _ => bad_item_fatal(ecx, sp, item.span) - }; - - (item, wrap_span(&*fn_decl, item.span)) -} - -// Parses the MetaItem derived from the route(...) macro. -fn parse_route(ecx: &mut ExtCtxt, meta_item: &MetaItem) -> Params { - // Ensure we've been supplied with a k = v meta item. Error out if not. - let params = meta_item.expect_list(ecx, "Bad use. Expected: #[route(...)]"); - if params.len() < 1 { - bad_method_err(ecx, meta_item.span, "HTTP method parameter is missing."); - ecx.span_fatal(meta_item.span, "At least 2 arguments are required."); - } - - // Get the method and the rest of the k = v params. - let (method_param, kv_params) = params.split_first().unwrap(); - - // Ensure method parameter is valid. If it's not, issue an error but use - // "GET" to continue parsing. method :: Spanned. - let method = if let MetaItemKind::Word(ref word) = method_param.node { - let method = Method::from_str(word).unwrap_or_else(|_| { - let message = format!("{} is not a valid method.", word); - bad_method_err(ecx, method_param.span, message.as_str()) - }); - - Spanned { span: method_param.span, node: method } - } else { - let method = bad_method_err(ecx, method_param.span, "Invalid parameter. \ - Expected a valid HTTP method at this position."); - dummy_span(method) - }; - - // Now grab all of the required and optional parameters. - let req: [&'static str; 1] = ["path"]; - let opt: [&'static str; 1] = ["form"]; - let kv_pairs = get_key_values(ecx, meta_item.span, &req, &opt, kv_params); - - // Ensure we have a path, just to keep parsing and generating errors. - let path = kv_pairs.get("path").map_or(KVSpanned::dummy("/".to_string()), |s| { - s.clone().map(String::from) - }); - - // If there's a form parameter, ensure method is POST. - let form = kv_pairs.get("form").map_or(None, |f| { - if method.node != Method::Post { - ecx.span_err(f.p_span, "Use of `form` requires a POST method..."); - let message = format!("...but {} was found instead.", method.node); - ecx.span_err(method_param.span, message.as_str()); - } - - if !(f.node.starts_with('<') && f.node.ends_with('>')) { - ecx.struct_span_err(f.p_span, "`form` cannot contain arbitrary text") - .help("`form` must be exactly one parameter: \"\"") - .emit(); - } - - if f.node.chars().filter(|c| *c == '<' || *c == '>').count() != 2 { - ecx.span_err(f.p_span, "`form` must contain exactly one parameter"); - } - - Some(f.clone().map(String::from)) - }); - - Params { - method: method, - path: path, - form: form - } -} - -// TODO: Put something like this in the library. Maybe as an iterator? -pub fn extract_params<'a>(ecx: &ExtCtxt, params: &Spanned<&'a str>) - -> Vec> { - let mut output_params = vec![]; - let bad_match_err = "Parameter string is malformed."; - - let mut start = 0; - let mut matching = false; - for (i, c) in params.node.char_indices() { - match c { - '<' if !matching => { - matching = true; - start = i; - }, - '>' if matching => { - matching = false; - - let mut param_span = params.span; - param_span.lo = params.span.lo + BytePos(start as u32); - param_span.hi = params.span.lo + BytePos((i + 1) as u32); - - if i > start + 1 { - let param_name = ¶ms.node[(start + 1)..i]; - output_params.push(wrap_span(param_name, param_span)) - } else { - ecx.span_err(param_span, "Parameter names cannot be empty."); - } - }, - '<' if matching => ecx.span_err(params.span, bad_match_err), - '>' if !matching => ecx.span_err(params.span, bad_match_err), - _ => { /* ... */ } - } - } - - output_params -} - -pub fn extract_params_from_kv<'a>(ecx: &ExtCtxt, params: &'a KVSpanned) - -> Vec> { +pub fn extract_params_from_kv<'a>(parser: &MetaItemParser, + params: &'a KVSpanned) -> Vec> { let mut param_span = params.v_span; param_span.lo = params.v_span.lo + BytePos(1); - extract_params(ecx, &Spanned { - span: param_span, - node: &*params.node - }) + let spanned = span(&*params.node, param_span); + parser.iter_params(&spanned).collect() } // Analyzes the declared parameters against the function declaration. Returns -// two vectors. The first is the set of parameters declared by the user, and -// the second is the set of parameters not declared by the user. +// a vector of all of the parameters in the order the user wants them. fn get_fn_params<'a, T: Iterator>>(ecx: &ExtCtxt, declared_params: T, fn_decl: &Spanned<&FnDecl>) -> Vec { @@ -273,15 +133,16 @@ fn method_variant_to_expr(ecx: &ExtCtxt, method: Method) -> P { pub fn route_decorator(ecx: &mut ExtCtxt, sp: Span, meta_item: &MetaItem, annotated: &Annotatable, push: &mut FnMut(Annotatable)) { // Get the encompassing item and function declaration for the annotated func. - let (item, fn_decl) = get_fn_decl(ecx, sp, annotated); + let parser = MetaItemParser::new(ecx, meta_item, annotated, &sp); + let (item, fn_decl) = (parser.expect_item(), parser.expect_fn_decl()); // Parse and retrieve all of the parameters of the route. - let route = parse_route(ecx, meta_item); + let route = parser.parse_route(None); // Get a list of the user declared parameters in `path` and `form`. - let path_params = extract_params_from_kv(ecx, &route.path); + let path_params = extract_params_from_kv(&parser, &route.path); let form_thing = route.form.unwrap_or_default(); // Default is empty string. - let form_params = extract_params_from_kv(ecx, &form_thing); + let form_params = extract_params_from_kv(&parser, &form_thing); // Ensure the params match the function declaration and return the params. let all_params = path_params.iter().chain(form_params.iter()); diff --git a/macros/src/routes_macro.rs b/macros/src/routes_macro.rs index ad74b1aa..c357ca67 100644 --- a/macros/src/routes_macro.rs +++ b/macros/src/routes_macro.rs @@ -23,7 +23,7 @@ pub fn routes_macro(ecx: &mut ExtCtxt, _sp: Span, args: &[TokenTree]) // Build up the P for each path. let path_exprs: Vec> = paths.iter().map(|p| { - quote_expr!(ecx, rocket::Route::from(&$p)) + quote_expr!(ecx, ::rocket::Route::from(&$p)) }).collect(); // Now put them all in one vector and return the thing. diff --git a/macros/src/utils.rs b/macros/src/utils.rs index 75670fea..e2d2be0f 100644 --- a/macros/src/utils.rs +++ b/macros/src/utils.rs @@ -37,7 +37,7 @@ pub fn append_ident(ident: &Ident, other: T) -> Ident { } #[inline] -pub fn wrap_span(t: T, span: Span) -> Spanned { +pub fn span(t: T, span: Span) -> Spanned { Spanned { span: span, node: t, @@ -95,7 +95,7 @@ impl KVSpanned { } } -pub fn get_key_values<'b>(ecx: &mut ExtCtxt, sp: Span, required: &[&str], +pub fn get_key_values<'b>(ecx: &ExtCtxt, sp: Span, required: &[&str], optional: &[&str], kv_params: &'b [P]) -> HashMap<&'b str, KVSpanned<&'b str>> { let mut seen = HashSet::new(); @@ -160,9 +160,21 @@ pub fn token_separate(ecx: &ExtCtxt, things: &[T], pub trait MetaItemExt { fn expect_list<'a>(&'a self, ecx: &ExtCtxt, msg: &str) -> &'a Vec>; fn expect_word<'a>(&'a self, ecx: &ExtCtxt, msg: &str) -> &'a str; + fn is_word(&self) -> bool; + fn name<'a>(&'a self) -> &'a str; } impl MetaItemExt for MetaItem { + fn name<'a>(&'a self) -> &'a str { + let interned_name = match self.node { + MetaItemKind::Word(ref s) => s, + MetaItemKind::List(ref s, _) => s, + MetaItemKind::NameValue(ref s, _) => s + }; + + &*interned_name + } + fn expect_list<'a>(&'a self, ecx: &ExtCtxt, msg: &str) -> &'a Vec> { match self.node { MetaItemKind::List(_, ref params) => params, @@ -176,6 +188,13 @@ impl MetaItemExt for MetaItem { _ => ecx.span_fatal(self.span, msg) } } + + fn is_word(&self) -> bool { + match self.node { + MetaItemKind::Word(_) => true, + _ => false + } + } } pub trait PatExt {