Refactored parsing code into its own module.

This commit is contained in:
Sergio Benitez 2016-08-08 18:08:59 -07:00
parent 3a89cb8e2b
commit c7b1eebd20
6 changed files with 253 additions and 158 deletions

View File

@ -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<u16>,
}
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);

View File

@ -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);
}

View File

@ -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<Item> {
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<FnDecl> = match item.node {
ItemKind::Fn(ref decl, _, _, _, _, _) => decl,
_ => bad_item("other")
};
span(fn_decl, item.span)
}
fn expect_list(&self) -> &'a Vec<P<MetaItem>> {
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<Spanned<&'s str>> {
// 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<Method>,
pub path: KVSpanned<String>,
pub form: Option<KVSpanned<String>>,
}
pub trait RouteDecoratorExt {
fn bad_method(&self, sp: Span, message: &str);
fn parse_method(&self, default: Method) -> Spanned<Method>;
fn parse_route(&self, known_method: Option<Spanned<Method>>) -> 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<Method> {
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<Spanned<Method>>) -> 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: \"<param>\"")
.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
}
}
}

View File

@ -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<Method>,
path: KVSpanned<String>,
form: Option<KVSpanned<String>>,
}
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<Item>, Spanned<&'a FnDecl>) {
// `annotated` is the AST object for the annotated item.
let item: &P<Item> = 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<FnDecl> = 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<Method>.
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: \"<param>\"")
.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<Spanned<&'a str>> {
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 = &params.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<String>)
-> Vec<Spanned<&'a str>> {
pub fn extract_params_from_kv<'a>(parser: &MetaItemParser,
params: &'a KVSpanned<String>) -> Vec<Spanned<&'a str>> {
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<Item=&'a Spanned<&'a str>>>(ecx: &ExtCtxt,
declared_params: T, fn_decl: &Spanned<&FnDecl>)
-> Vec<UserParam> {
@ -273,15 +133,16 @@ fn method_variant_to_expr(ecx: &ExtCtxt, method: Method) -> P<Expr> {
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());

View File

@ -23,7 +23,7 @@ pub fn routes_macro(ecx: &mut ExtCtxt, _sp: Span, args: &[TokenTree])
// Build up the P<Expr> for each path.
let path_exprs: Vec<P<Expr>> = 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.

View File

@ -37,7 +37,7 @@ pub fn append_ident<T: ToString>(ident: &Ident, other: T) -> Ident {
}
#[inline]
pub fn wrap_span<T>(t: T, span: Span) -> Spanned<T> {
pub fn span<T>(t: T, span: Span) -> Spanned<T> {
Spanned {
span: span,
node: t,
@ -95,7 +95,7 @@ impl<T> KVSpanned<T> {
}
}
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<MetaItem>])
-> HashMap<&'b str, KVSpanned<&'b str>> {
let mut seen = HashSet::new();
@ -160,9 +160,21 @@ pub fn token_separate<T: ToTokens>(ecx: &ExtCtxt, things: &[T],
pub trait MetaItemExt {
fn expect_list<'a>(&'a self, ecx: &ExtCtxt, msg: &str) -> &'a Vec<P<MetaItem>>;
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<P<MetaItem>> {
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 {