From 3b416f47b3ab7fced54b0a1874149d163b2c8ef0 Mon Sep 17 00:00:00 2001 From: Corey Farwell Date: Thu, 19 Nov 2015 22:45:14 -0500 Subject: [PATCH] Implement 'url!(..)' macro Fixes #136 --- .gitignore | 2 + plugin/Cargo.toml | 11 ++++ plugin/src/lib.rs | 149 ++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 162 insertions(+) create mode 100644 plugin/Cargo.toml create mode 100644 plugin/src/lib.rs diff --git a/.gitignore b/.gitignore index 7cbe84a5..9be95037 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,5 @@ /target /Cargo.lock /.cargo/config +/plugin/target +/plugin/Cargo.lock diff --git a/plugin/Cargo.toml b/plugin/Cargo.toml new file mode 100644 index 00000000..4cf679ad --- /dev/null +++ b/plugin/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "url_plugin" +version = "0.1.0" +authors = ["Corey Farwell "] + +[lib] +name = "url_plugin" +plugin = true + +[dependencies.url] +path = "../" diff --git a/plugin/src/lib.rs b/plugin/src/lib.rs new file mode 100644 index 00000000..cf675b63 --- /dev/null +++ b/plugin/src/lib.rs @@ -0,0 +1,149 @@ +#![feature(plugin_registrar, quote, rustc_private)] +#![crate_type="dylib"] + +extern crate rustc; +extern crate syntax; +extern crate url; + +use rustc::plugin::Registry; +use std::error::Error; +use syntax::ast::{TokenTree, ExprLit, LitStr, Expr}; +use syntax::codemap::Span; +use syntax::ext::base::{ExtCtxt, MacResult, MacEager, DummyResult}; +use syntax::ext::build::AstBuilder; +use syntax::fold::Folder; +use syntax::parse; +use syntax::parse::token::InternedString; +use url::{Url, Host, RelativeSchemeData, SchemeData}; + +#[plugin_registrar] +pub fn plugin_registrar(reg: &mut Registry) { + reg.register_macro("url", expand_url); +} + +fn expand_url(cx: &mut ExtCtxt, sp: Span, tts: &[TokenTree]) + -> Box { + let mut parser = parse::new_parser_from_tts(cx.parse_sess(), cx.cfg(), tts.to_vec()); + let query_expr = cx.expander().fold_expr(parser.parse_expr().unwrap()); + + // Ensure a str literal was passed to the macro + let query = match parse_str_lit(&query_expr) { + Some(query) => query, + None => { + cx.span_err(query_expr.span, "'url!' expected string literal"); + return DummyResult::any(sp) + }, + }; + + // Parse the str literal + let Url {scheme, scheme_data, query, fragment} = match Url::parse(&query) { + Ok(url) => url, + Err(error) => { + cx.span_err(query_expr.span, error.description()); + return DummyResult::any(sp) + } + }; + + let scheme_data_expr = cx.expr_scheme_data(sp, scheme_data); + let query_expr = cx.expr_option_string(sp, query); + let fragment_expr = cx.expr_option_string(sp, fragment); + + let url_expr = quote_expr!(cx, { + ::url::Url { + scheme: $scheme.to_owned(), + scheme_data: $scheme_data_expr, + query: $query_expr, + fragment: $fragment_expr, + } + }); + + MacEager::expr(url_expr) +} + +fn parse_str_lit(e: &Expr) -> Option { + if let ExprLit(ref lit) = e.node { + if let LitStr(ref s, _) = lit.node { + return Some(s.clone()); + } + } + None +} + +trait ExtCtxtHelpers { + fn expr_scheme_data(&self, sp: Span, scheme_data: SchemeData) -> syntax::ptr::P; + fn expr_option_string(&self, sp: Span, string: Option) -> syntax::ptr::P; + fn expr_option_u16(&self, sp: Span, unsigned: Option) -> syntax::ptr::P; + fn expr_host(&self, sp: Span, host: Host) -> syntax::ptr::P; + fn expr_slice_u16(&self, sp: Span, unsigned: &[u16]) -> syntax::ptr::P; + fn expr_vec_string(&self, sp: Span, strings: Vec) -> syntax::ptr::P; +} + +impl<'a> ExtCtxtHelpers for ExtCtxt<'a> { + fn expr_scheme_data(&self, sp: Span, scheme_data: SchemeData) -> syntax::ptr::P { + match scheme_data { + SchemeData::Relative( + RelativeSchemeData {username, password, host, port, default_port, path}) => + { + let password_expr = self.expr_option_string(sp, password); + let host_expr = self.expr_host(sp, host); + let port_expr = self.expr_option_u16(sp, port); + let default_port_expr = self.expr_option_u16(sp, default_port); + let path_expr = self.expr_vec_string(sp, path); + + quote_expr!(self, + ::url::SchemeData::Relative( + ::url::RelativeSchemeData { + username: $username.to_owned(), + password: $password_expr, + host: $host_expr, + port: $port_expr, + default_port: $default_port_expr, + path: $path_expr.to_owned(), + } + )) + }, + SchemeData::NonRelative(ref scheme_data) => { + quote_expr!(self, ::url::SchemeData::NonRelative($scheme_data.to_owned())) + }, + } + } + + fn expr_option_string(&self, sp: Span, string: Option) -> syntax::ptr::P { + match string { + Some(string) => quote_expr!(self, Some($string.to_owned())), + None => self.expr_none(sp), + } + } + + fn expr_option_u16(&self, sp: Span, unsigned: Option) -> syntax::ptr::P { + match unsigned { + Some(unsigned) => quote_expr!(self, Some($unsigned)), + None => self.expr_none(sp), + } + } + + fn expr_host(&self, sp: Span, host: Host) -> syntax::ptr::P { + match host { + Host::Domain(domain) => quote_expr!(self, ::url::Host::Domain(String::from($domain))), + Host::Ipv6(address) => { + let pieces_expr = self.expr_slice_u16(sp, &address.pieces); + quote_expr!(self, + url::Host::Ipv6( + ::url::Ipv6Address { + pieces: $pieces_expr.to_owned() + } + )) + }, + } + } + + fn expr_slice_u16(&self, sp: Span, unsigned: &[u16]) -> syntax::ptr::P { + let unsigned = unsigned.iter().map(|p| quote_expr!(self, $p)).collect(); + self.expr_vec_slice(sp, unsigned) + } + + fn expr_vec_string(&self, sp: Span, strings: Vec) -> syntax::ptr::P { + let strings = strings.iter().map(|p| quote_expr!(self, $p.to_owned())).collect(); + self.expr_vec(sp, strings) + } +}