// Copyright 2016 Kyle Mayes
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
use std::collections::{HashMap};
use rustc_plugin::{Registry};
use syntax::print::pprust;
use syntax::ast::{Attribute, Expr, Ident, Item, ItemKind, Visibility};
use syntax::codemap::{Span, Spanned};
use syntax::ext::base::{ExtCtxt, DummyResult, MacEager, MacResult};
use syntax::ptr::{P};
use syntax::tokenstream::{TokenTree};
use syntax::util::small_vector::{SmallVector};
use expr::{ToExpr};
//================================================
// Functions
//================================================
/// Strips the visibility and attributes from a function and appends `_` to the name.
fn strip_function(
context: &ExtCtxt, function: P<Item>
) -> (P<Item>, Ident, Option<Ident>, Vec<Attribute>) {
let ident = function.ident;
let visibility = if function.vis == Visibility::Public {
Some(context.ident_of("pub"))
} else {
None
};
let attributes = function.attrs.clone();
let function = function.map(|mut f| {
f.ident = context.ident_of(&format!("{}_", ident.name));
f.vis = Visibility::Inherited;
f.attrs = vec![];
f
});
(function, ident, visibility, attributes)
}
/// Returns a function that parse arguments according to the supplied specification.
fn expand_parse_fn(
context: &ExtCtxt, span: Span, name: Ident, specification: &Specification
) -> P<Item> {
let expr = specification.to_expr(context, span);
quote_item!(context,
#[allow(non_snake_case)]
fn parse(
context: &::syntax::ext::base::ExtCtxt, arguments: &[::syntax::tokenstream::TokenTree]
) -> ::easy_plugin::PluginResult<$name> {
let specification = $expr;
::easy_plugin::parse_arguments(context, arguments, &specification).map(|a| $name::extract(&a))
}
).unwrap()
}
/// Returns an expression that attempts to parse plugin arguments.
fn expand_parse_expr(context: &ExtCtxt, expr: P<Expr>) -> P<Expr> {
quote_expr!(context,
match $expr {
Ok(result) => result,
Err((subspan, message)) => {
let span = if subspan == ::syntax::codemap::DUMMY_SP {
span
} else {
subspan
};
context.span_err(span, &message);
::syntax::ext::base::DummyResult::any(span)
},
}
)
}
fn expand_easy_plugin_(
context: &mut ExtCtxt, span: Span, arguments: &[TokenTree]
) -> PluginResult<Box<MacResult + 'static>> {
// Build the argument specification.
let mut specifications = HashMap::new();
let variant = parse_specification_string(r#"
struct Variant {
$name:ident {
$($tt:tt)*
}
}
"#, &specifications).unwrap();
specifications.insert("Variant".into(), variant);
let definition = parse_specification_string(r#"
enum Definition {
Enum {
enum $name:ident {
$($variant:$Variant), + $(,)*
}
},
Struct {
struct $name:ident {
$($tt:tt)*
}
},
}
"#, &specifications).unwrap();
specifications.insert("Definition".into(), definition);
let specification = parse_specification_string(r#"
struct Arguments {
$($definition:$Definition)+
$function:item
}
"#, &specifications).unwrap();
// Extract the arguments.
let matches = try!(parse_arguments(context, arguments, &specification));
let definitions = matches.get_sequence("definition").to_arguments_vec(|a| a);
let function = matches.get::<P<Item>>("function");
// Parse the specification enums and structs.
let mut specifications = HashMap::new();
for definition in definitions {
let name = definition.get::<Spanned<Ident>>("name").node.name.as_str().to_string();
if definition.variant == Some(0) {
let mut variants = vec![];
for arguments in definition.get_sequence("variant").to_arguments_vec(|a| a) {
let name = arguments.get::<Spanned<Ident>>("name");
let tts = arguments.get_sequence("tt").to_vec::<TokenTree>();
let specifiers = try!(parse_specifiers(&tts, &specifications));
variants.push(Variant::new(name.node.name.as_str().to_string(), specifiers));
}
specifications.insert(name.clone(), Specification::Enum(Enum::new(name, variants)));
} else {
let tts = definition.get_sequence("tt").to_vec::<TokenTree>();
let specifiers = try!(parse_specifiers(&tts, &specifications));
specifications.insert(name.clone(), Specification::Struct(Struct::new(name, specifiers)));
}
}
// Generate the specification enums and structs.
let items = specifications.iter().flat_map(|(n, e)| {
let name = context.ident_of(n);
match *e {
Specification::Enum(ref enum_) =>
ast::expand_enum_items(context, name, &enum_.variants).into_iter(),
Specification::Struct(ref struct_) =>
ast::expand_struct_items(context, name, &struct_.specification).into_iter(),
}
}).collect::<Vec<_>>();
// Determine the primary argument specification.
let (name, specification) = match function.node {
ItemKind::Fn(ref decl, _, _, _, _, _) if decl.inputs.len() == 3 => {
let ty = pprust::ty_to_string(&decl.inputs[2].ty);
match specifications.get(&ty) {
Some(arguments) => (ty, arguments),
_ => return decl.inputs[2].ty.to_error("expected specification"),
}
},
_ => return function.to_error("expected plugin function"),
};
// Generate the plugin function.
let (function, identifier, visibility, attributes) = strip_function(context, function);
let expr = quote_expr!(context, |a| ${function.ident}(context, span, a));
let expr = quote_expr!(context, parse(context, arguments).and_then($expr));
let item = quote_item!(context,
#[allow(non_camel_case_types)]
$($attributes)*
$visibility fn $identifier(
context: &mut ::syntax::ext::base::ExtCtxt,
span: ::syntax::codemap::Span,
arguments: &[::syntax::tokenstream::TokenTree],
) -> Box<::syntax::ext::base::MacResult> {
$($items)*
${expand_parse_fn(context, span, context.ident_of(&name), specification)}
$function
${expand_parse_expr(context, expr)}
}
).unwrap();
Ok(MacEager::items(SmallVector::one(item)))
}
fn expand_easy_plugin<'cx>(
context: &'cx mut ExtCtxt, span: Span, arguments: &[TokenTree]
) -> Box<MacResult + 'cx> {
match expand_easy_plugin_(context, span, arguments) {
Ok(result) => result,
Err((span, message)) => {
context.span_err(span, &message);
DummyResult::any(span)
},
}
}
/// Add `easy_plugin!` to the supplied registry.
#[cfg(feature="syntex")]
pub fn plugin_registrar(registry: &mut Registry) {
registry.add_macro("easy_plugin", expand_easy_plugin);
}
/// Expand the supplied source file into the supplied destination file using `easy_plugin!`.
#[cfg(feature="syntex")]
pub fn expand<S: AsRef<std::path::Path>, D: AsRef<std::path::Path>>(
source: S, destination: D
) -> Result<(), rustc_plugin::Error> {
let mut registry = Registry::new();
plugin_registrar(&mut registry);
registry.expand("", source.as_ref(), destination.as_ref())
}
#[cfg(not(feature="syntex"))]
#[doc(hidden)]
#[plugin_registrar]
pub fn plugin_registrar(registry: &mut Registry) {
registry.register_macro("easy_plugin", expand_easy_plugin);
}