easy-plugin 0.11.8

A compiler plugin that makes it easier to write compiler plugins.
// 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);
}