#![feature(box_patterns)]
extern crate proc_macro;
use gazebo::prelude::*;
use proc_macro::TokenStream;
use quote::{quote, ToTokens};
use syn::*;
#[proc_macro_attribute]
pub fn starlark_module(attr: TokenStream, input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as ItemFn);
assert!(attr.is_empty());
let visibility = input.vis;
let name = input.sig.ident;
let arg_ty = match input.sig.inputs.first() {
Some(FnArg::Typed(PatType { ty, .. })) if is_mut_globals_builder(ty) => ty,
_ => panic!("Must take on argument of type &mut GlobalsBuilder"),
};
let body = input.block.stmts.map(add_stmt);
let result = quote! {
#visibility fn #name(globals_builder: #arg_ty) {
#( #body )*
}
};
result.into()
}
#[derive(Clone)]
struct Arg<'a> {
attrs: &'a Vec<Attribute>,
ident: &'a PatIdent,
ty: &'a Type,
}
impl<'a> Arg<'a> {
fn new(x: &'a FnArg) -> Self {
match x {
FnArg::Typed(PatType {
attrs,
pat: box Pat::Ident(ident),
ty: box ty,
..
}) => Self { attrs, ident, ty },
arg => panic!("Unexpected argument, {:?}", arg),
}
}
}
fn add_stmt(stmt: &Stmt) -> proc_macro2::TokenStream {
match stmt {
Stmt::Item(Item::Fn(x)) => add_function(x),
Stmt::Item(Item::Const(x)) => add_const(x),
_ => panic!("Can only put constants and functions inside a #[starlark_module]"),
}
}
fn add_const(x: &ItemConst) -> proc_macro2::TokenStream {
let name_str = x.ident.to_string();
let ty = &x.ty;
let value = &x.expr;
quote! {
globals_builder.set::<#ty>(#name_str, #value);
}
}
fn is_attribute_attribute(x: &Attribute) -> bool {
x.path.is_ident("attribute")
}
fn is_attribute_type(x: &Attribute) -> Option<impl ToTokens> {
if x.path.is_ident("starlark_type") {
if let Ok(Meta::List(MetaList { nested, .. })) = x.parse_meta() {
if nested.len() == 1 {
return Some(nested.first().unwrap().clone());
}
}
panic!(
"Couldn't parse attribute `{:?}`. Expected `#[starlark_type(\"my_type\")]`",
x
)
} else {
None
}
}
fn process_attributes(xs: &[Attribute]) -> (bool, Option<impl ToTokens>, Vec<&Attribute>) {
let mut rest = Vec::with_capacity(xs.len());
let mut attribute = false;
let mut typ = None;
for x in xs {
if is_attribute_attribute(x) {
attribute = true;
} else if let Some(t) = is_attribute_type(x) {
typ = Some(t);
} else {
rest.push(x);
}
}
(attribute, typ, rest)
}
fn add_function(func: &ItemFn) -> proc_macro2::TokenStream {
let name = &func.sig.ident;
let name_string = name.to_string();
let name_str = name_string.trim_start_match("r#");
let (is_attribute, has_type, attrs) = process_attributes(&func.attrs);
let return_type = match &func.sig.output {
ReturnType::Default => panic!("Function named '{}' must have a return type", name),
ReturnType::Type(_, x) => quote! {#x},
};
let body = &func.block;
let args = func.sig.inputs.iter().map(Arg::new).collect::<Vec<_>>();
let bind_args = args.map(bind_argument);
let signature = args.map(record_argument);
let setter = if is_attribute {
quote! {
let func = globals_builder.alloc(
starlark::values::function::NativeFunction::new(#name, signature),
);
globals_builder.set(
#name_str,
starlark::values::function::NativeAttribute::new(func),
);
}
} else if let Some(typ) = has_type {
quote! {
static TYPE: starlark::values::ConstFrozenValue =
starlark::values::ConstFrozenValue::new(#typ);
let mut func = starlark::values::function::NativeFunction::new(#name, signature);
func.set_type(&TYPE);
globals_builder.set(#name_str, func);
}
} else {
quote! {
globals_builder.set(
#name_str,
starlark::values::function::NativeFunction::new(#name, signature),
);
}
};
quote! {
#( #attrs )*
#[allow(non_snake_case)] fn #name<'v, 'a, 'a2>(
ctx: &mut starlark::eval::Evaluator<'v, 'a>,
starlark_args: starlark::eval::ParametersParser<'v, 'a2>,
) -> anyhow::Result<starlark::values::Value<'v>> {
fn inner<'v, 'a, 'a2>(
#[allow(unused_variables)]
ctx: &mut starlark::eval::Evaluator<'v, 'a>,
#[allow(unused_mut)]
#[allow(unused_variables)]
mut starlark_args: starlark::eval::ParametersParser<'v, 'a2>,
) -> anyhow::Result<#return_type> {
#[allow(unused_variables)]
let heap = ctx.heap();
#( #bind_args )*
#body
}
match inner(ctx, starlark_args) {
Ok(v) => Ok(ctx.heap().alloc(v)),
Err(e) => Err(e),
}
}
{
#[allow(unused_mut)]
let mut signature = starlark::eval::ParametersSpec::new(#name_str.to_owned());
#( #signature )*
#setter
}
}
}
fn bind_argument(arg: &Arg) -> proc_macro2::TokenStream {
let name = &arg.ident.ident;
let name_str = name.to_string();
let ty = arg.ty;
let default = get_default(arg);
let next = if is_type_option(ty) {
assert!(
default.is_none(),
"Can't have Option argument with a default, for `{}`",
name_str
);
quote! { starlark_args.next_opt(#name_str, ctx.heap())? }
} else if !is_type_value(ty) && default.is_some() {
let default = default.unwrap();
quote! { starlark_args.next_opt(#name_str, ctx.heap())?.unwrap_or(#default) }
} else {
quote! { starlark_args.next(#name_str, ctx.heap())? }
};
let mutability = &arg.ident.mutability;
let attrs = &arg.attrs;
quote! {
#( #attrs )*
let #mutability #name: #ty = #next;
}
}
fn record_argument(arg: &Arg) -> proc_macro2::TokenStream {
let name = &arg.ident.ident;
let mut name_str_full = (if arg.ident.by_ref.is_some() { "$" } else { "" }).to_owned();
name_str_full += &name.to_string();
let name_str = name_str_full.trim_matches('_');
let default = get_default(arg);
match name_str_full.as_str() {
"args" => {
assert!(default.is_none(), "Can't have *args with a default");
quote! {signature.args("args");}
}
"kwargs" => {
assert!(default.is_none(), "Can't have **kwargs with a default");
quote! {signature.kwargs("kwargs");}
}
_ if is_type_option(arg.ty) => {
quote! {signature.optional(#name_str);}
}
_ if default.is_some() => {
let default = default.unwrap();
let ty = arg.ty;
if is_type_value(ty) {
quote! {signature.defaulted(#name_str, globals_builder.alloc(#default));}
} else {
quote! {signature.optional(#name_str);}
}
}
_ => {
quote! {signature.required(#name_str);}
}
}
}
fn is_type_name(x: &Type, name: &str) -> bool {
if let Type::Path(TypePath {
path: Path { segments, .. },
..
}) = x
{
if let Some(seg1) = segments.first() {
return seg1.ident == name;
}
}
false
}
fn get_default<'a>(arg: &'a Arg) -> Option<&'a Pat> {
arg.ident.subpat.as_ref().map(|(_, x)| &**x)
}
fn is_type_option(x: &Type) -> bool {
is_type_name(x, "Option")
}
fn is_type_value(x: &Type) -> bool {
is_type_name(x, "Value")
}
fn is_mut_globals_builder(x: &Type) -> bool {
match x {
Type::Reference(TypeReference {
mutability: Some(_),
elem: x,
..
}) => is_type_name(x, "GlobalsBuilder"),
_ => false,
}
}