#![feature(proc_macro)]
#![allow(unused_imports)]
#![feature(catch_expr)]
#![feature(type_ascription)]
use std::iter;
use std::env;
extern crate intercom_common;
use intercom_common::idents;
use intercom_common::utils;
use intercom_common::ast_converters::*;
use intercom_common::error::MacroError;
use intercom_common::methodinfo::ComMethodInfo;
extern crate proc_macro;
use proc_macro::{TokenStream, LexError};
extern crate syn;
#[macro_use]
extern crate quote;
use syn::*;
fn lib_name() -> String {
env::var( "CARGO_PKG_NAME" )
.expect( "Could not resolve package name. \
Ensure CARGO_PKG_NAME environment variable is defined." )
}
#[proc_macro_attribute]
pub fn com_interface(
attr: TokenStream,
tokens: TokenStream,
) -> TokenStream
{
match expand_com_interface( &attr, tokens ) {
Ok(t) => t,
Err(e) => error(e, &attr),
}
}
#[proc_macro_attribute]
pub fn com_impl(
attr: TokenStream,
tokens: TokenStream,
) -> TokenStream
{
match expand_com_impl( &attr, tokens ) {
Ok(t) => t,
Err(e) => error(e, &attr),
}
}
#[proc_macro_attribute]
pub fn com_class(
attr: TokenStream,
tokens: TokenStream,
) -> TokenStream
{
match expand_com_class( &attr, tokens ) {
Ok(t) => t,
Err(e) => error(e, &attr),
}
}
#[proc_macro_attribute]
pub fn com_library(
attr: TokenStream,
tokens: TokenStream,
) -> TokenStream
{
match expand_com_library( &attr, tokens ) {
Ok(t) => t,
Err(e) => error(e, &attr),
}
}
fn expand_com_interface(
attr_tokens: &TokenStream,
item_tokens: TokenStream,
) -> Result<TokenStream, MacroError>
{
let ( mut output, attr, item ) =
utils::parse_inputs( "com_interface", &attr_tokens, &item_tokens )?;
let ( itf_ident, fns, itf_type ) =
utils::get_ident_and_fns( &item )
.ok_or( "[com_interface(IID:&str)] must be applied to trait \
or struct impl" )?;
let iid_ident = idents::iid( &itf_ident );
let vtable_ident = idents::vtable_struct( &itf_ident );
let crate_name = lib_name();
let attr_params = utils::get_parameters( &attr );
let iid_guid = attr_params
.as_ref()
.and_then( |ref params| params.first() )
.ok_or( "[com_interface(IID:&str)] must specify an IID".to_owned() )
.and_then( |f| utils::parameter_to_guid(
f, &crate_name, itf_ident.as_ref(), "IID"
) )?
.ok_or( "Interfaces must specify IID" )?;
let base = attr_params
.as_ref()
.and_then( |p| p.get( 1 ) )
.and_then( |p| p.get_ident().ok() );
let iid_tokens = utils::get_guid_tokens( &iid_guid );
let iid_doc = format!( "`{}` interface ID.", itf_ident );
output.push( quote!(
#[doc = #iid_doc]
#[allow(non_upper_case_globals)]
pub const #iid_ident : ::intercom::IID = #iid_tokens;
) );
let mut fields = vec![];
if base != Some( Ident::from( "NO_BASE" ) ) {
let vtbl = match base {
None => quote!( ::intercom::IUnknownVtbl ),
Some( ident ) => {
let vtbl = idents::vtable_struct( &ident );
quote!( #vtbl )
}
};
fields.push( quote!( pub __base : #vtbl, ) );
}
let mut impls = vec![];
for ( method_ident, method_sig ) in fns {
do catch {
let method_info = ComMethodInfo::new(
&method_ident, &method_sig.decl ).ok()?;
let ( args, _ ) =
utils::get_method_args( method_sig )?;
let arg_tokens = utils::flatten( args.iter() );
let ret_ty = method_info.returnhandler.com_ty();
fields.push( quote!(
pub #method_ident :
unsafe extern "stdcall" fn( #arg_tokens ) -> #ret_ty,
) );
let impl_args = method_info.args.iter().map( |ca| {
let name = &ca.name;
let ty = &ca.ty;
quote!( #name : #ty )
} );
let out_arg_declarations = method_info.returnhandler.com_out_args()
.iter()
.map( |ca| {
let ident = &ca.name;
let ty = &ca.handler.com_ty();
let default = ca.handler.default_value();
quote!( let mut #ident : #ty = #default; )
} ).collect::<Vec<_>>();
let in_params = method_info.args
.iter()
.map( |ca| {
let param = ca.handler.rust_to_com( &ca.name );
quote!( #param )
} ).collect::<Vec<_>>();
let out_params = method_info.returnhandler.com_out_args()
.iter()
.map( |ca| {
let name = &ca.name;
quote!( &mut #name )
} ).collect::<Vec<_>>();
let params = iter::once( quote!( comptr ) )
.chain( in_params )
.chain( out_params );
let return_ident = Ident::from( "__result" );
let return_statement = method_info
.returnhandler
.com_to_rust_return( &return_ident );
let self_arg = method_info.rust_self_arg;
let return_ty = method_info.rust_return_ty;
impls.push( quote!(
fn #method_ident( #self_arg, #( #impl_args ),* ) -> #return_ty {
let comptr = ::intercom::ComItf::ptr( self );
let vtbl = comptr as *const *const #vtable_ident;
unsafe {
#( #out_arg_declarations )*;
let #return_ident = ((**vtbl).#method_ident)( #( #params ),* );
#return_statement
}
}
) );
Some(())
};
}
let field_tokens = utils::flatten( fields.iter() );
output.push( quote!(
#[allow(non_camel_case_types)]
#[repr(C)]
#[doc(hidden)]
pub struct #vtable_ident { #field_tokens }
) );
if itf_type == utils::InterfaceType::Trait {
output.push( quote!(
impl #itf_ident for ::intercom::ComItf< #itf_ident > {
#( #impls )*
}
) );
}
Ok( utils::tokens_to_tokenstream( item_tokens, output )? )
}
fn expand_com_impl(
attr_tokens: &TokenStream,
item_tokens: TokenStream,
) -> Result<TokenStream, MacroError>
{
let ( mut output, _, item ) =
utils::parse_inputs( "com_impl", &attr_tokens, &item_tokens )?;
let ( itf_ident_opt, struct_ident, fns )
= utils::get_impl_data( &item )
.ok_or( "[com_impl] must be applied to an impl" )?;
let itf_ident = itf_ident_opt.unwrap_or( struct_ident );
let vtable_struct_ident = idents::vtable_struct( &itf_ident );
let vtable_instance_ident = idents::vtable_instance( &struct_ident, &itf_ident );
let vtable_offset = idents::vtable_offset(
&struct_ident,
&itf_ident );
let query_interface_ident = idents::method_impl(
&struct_ident, &itf_ident, "query_interface" );
output.push( quote!(
#[allow(non_snake_case)]
#[doc(hidden)]
pub unsafe extern "stdcall" fn #query_interface_ident(
self_vtable : ::intercom::RawComPtr,
riid : ::intercom::REFIID,
out : *mut ::intercom::RawComPtr
) -> ::intercom::HRESULT
{
::intercom::ComBox::< #struct_ident >::query_interface(
&mut *(( self_vtable as usize - #vtable_offset() ) as *mut _ ),
riid,
out )
}
) );
let add_ref_ident = idents::method_impl(
&struct_ident, &itf_ident, "add_ref" );
output.push( quote!(
#[allow(non_snake_case)]
#[allow(dead_code)]
#[doc(hidden)]
pub unsafe extern "stdcall" fn #add_ref_ident(
self_vtable : ::intercom::RawComPtr
) -> u32 {
::intercom::ComBox::< #struct_ident >::add_ref(
&mut *(( self_vtable as usize - #vtable_offset() ) as *mut _ ) )
}
) );
let release_ident = idents::method_impl(
&struct_ident, &itf_ident, "release" );
output.push( quote!(
#[allow(non_snake_case)]
#[allow(dead_code)]
#[doc(hidden)]
pub unsafe extern "stdcall" fn #release_ident(
self_vtable : ::intercom::RawComPtr
) -> u32 {
::intercom::ComBox::< #struct_ident >::release_ptr(
( self_vtable as usize - #vtable_offset() ) as *mut _ )
}
) );
let mut vtable_fields = vec![
quote!(
__base : ::intercom::IUnknownVtbl {
query_interface : #query_interface_ident,
add_ref : #add_ref_ident,
release : #release_ident,
},
) ];
for ( method_ident, method_sig ) in fns {
do catch {
let method_impl_ident = idents::method_impl(
&struct_ident,
&itf_ident,
&method_ident.as_ref() );
let method_info = ComMethodInfo::new(
&method_ident, &method_sig.decl ).ok()?;
let ( args, params ) =
utils::get_method_args( method_sig )?;
let return_ident = Ident::from( "__result" );
let return_statement = method_info
.returnhandler
.rust_to_com_return( &return_ident );
let arg_tokens = utils::flatten( args.iter() );
let param_tokens = utils::flatten( params.iter() );
let ret_ty = method_info.returnhandler.com_ty();
output.push( quote!(
#[allow(non_snake_case)]
#[allow(dead_code)]
#[doc(hidden)]
pub unsafe extern "stdcall" fn #method_impl_ident(
#arg_tokens
) -> #ret_ty {
let self_combox = ( self_vtable as usize - #vtable_offset() )
as *mut ::intercom::ComBox< #struct_ident >;
let #return_ident = (*self_combox).#method_ident( #param_tokens );
#return_statement
}
) );
vtable_fields.push( quote!( #method_ident : #method_impl_ident, ) );
Some(())
};
}
let vtable_field_tokens = utils::flatten( vtable_fields.iter() );
output.push( quote!(
#[allow(non_upper_case_globals)]
const #vtable_instance_ident : #vtable_struct_ident
= #vtable_struct_ident { #vtable_field_tokens };
) );
Ok( utils::tokens_to_tokenstream( item_tokens, output )? )
}
fn expand_com_class(
attr_tokens: &TokenStream,
item_tokens: TokenStream,
) -> Result<TokenStream, MacroError>
{
let ( mut output, attr, item ) =
utils::parse_inputs( "com_class", &attr_tokens, &item_tokens )?;
let struct_ident = utils::get_struct_ident_from_annotatable( &item );
let isupporterrorinfo_ident = Ident::from( "ISupportErrorInfo".to_owned() );
let crate_name = lib_name();
let ( clsid_guid, itfs ) = utils::get_parameters( &attr )
.as_ref()
.and_then( |ref params| params.split_first() )
.ok_or( "[com_class(IID, itfs...)] must specify an IID".to_owned() )
.and_then( |( f, itfs )| {
let guid = utils::parameter_to_guid(
f, &crate_name, struct_ident.as_ref(), "CLSID" )?;
Ok( ( guid,
( itfs.into_iter()
.map( |i|
i.get_ident()
.or( Err( "Invalid interface" ) ))
.collect() : Result<Vec<Ident>, &'static str> )?
) )
} )?;
let mut query_interface_match_arms = vec![
quote!(
::intercom::IID_IUnknown =>
( &vtables._ISupportErrorInfo )
as *const &::intercom::ISupportErrorInfoVtbl
as *mut &::intercom::ISupportErrorInfoVtbl
as ::intercom::RawComPtr
),
quote!(
::intercom::IID_ISupportErrorInfo =>
( &vtables._ISupportErrorInfo )
as *const &::intercom::ISupportErrorInfoVtbl
as *mut &::intercom::ISupportErrorInfoVtbl
as ::intercom::RawComPtr
) ];
let mut support_error_info_match_arms = vec![] ;
let isupporterrorinfo_vtable_instance_ident =
idents::vtable_instance( &struct_ident, &isupporterrorinfo_ident );
let mut vtable_list_field_decls = vec![
quote!( _ISupportErrorInfo : &'static ::intercom::ISupportErrorInfoVtbl ) ];
let mut vtable_list_field_values = vec![
quote!( _ISupportErrorInfo : &#isupporterrorinfo_vtable_instance_ident ) ];
for itf in itfs {
let offset_ident = idents::vtable_offset( &struct_ident, &itf );
let iid_ident = idents::iid( &itf );
let vtable_struct_ident = idents::vtable_struct( &itf );
let vtable_instance_ident = idents::vtable_instance( &struct_ident, &itf );
output.push( quote!(
#[inline(always)]
#[allow(non_snake_case)]
fn #offset_ident() -> usize {
unsafe {
&::intercom::ComBox::< #struct_ident >::null_vtable().#itf
as *const _ as usize
}
}
) );
vtable_list_field_decls.push(
quote!( #itf : &'static #vtable_struct_ident ) );
vtable_list_field_values.push(
quote!( #itf : &#vtable_instance_ident ) );
query_interface_match_arms.push( quote!(
self::#iid_ident => &vtables.#itf
as *const &#vtable_struct_ident
as *mut &#vtable_struct_ident
as ::intercom::RawComPtr
) );
support_error_info_match_arms.push( quote!(
self::#iid_ident => true
) );
}
output.push( quote!(
#[allow(non_upper_case_globals)]
const #isupporterrorinfo_vtable_instance_ident
: ::intercom::ISupportErrorInfoVtbl
= ::intercom::ISupportErrorInfoVtbl {
__base : ::intercom::IUnknownVtbl {
query_interface
: ::intercom::ComBox::< #struct_ident >
::query_interface_ptr,
add_ref
: ::intercom::ComBox::< #struct_ident >
::add_ref_ptr,
release
: ::intercom::ComBox::< #struct_ident >
::release_ptr,
},
interface_supports_error_info
: ::intercom::ComBox::< #struct_ident >
::interface_supports_error_info_ptr,
};
) );
let vtable_list_ident = idents::vtable_list( &struct_ident );
output.push( quote!(
#[allow(non_snake_case)]
#[doc(hidden)]
pub struct #vtable_list_ident {
#( #vtable_list_field_decls ),*
}
) );
output.push( quote!(
impl ::intercom::CoClass for #struct_ident {
type VTableList = #vtable_list_ident;
fn create_vtable_list() -> Self::VTableList {
#vtable_list_ident {
#( #vtable_list_field_values ),*
}
}
fn query_interface(
vtables : &Self::VTableList,
riid : ::intercom::REFIID,
) -> ::intercom::ComResult< ::intercom::RawComPtr > {
if riid.is_null() { return Err( ::intercom::E_NOINTERFACE ) }
Ok( match *unsafe { &*riid } {
#( #query_interface_match_arms ),*,
_ => return Err( ::intercom::E_NOINTERFACE )
} )
}
fn interface_supports_error_info(
riid : ::intercom::REFIID
) -> bool
{
match *unsafe { &*riid } {
#( #support_error_info_match_arms ),*,
_ => false
}
}
}
) );
let clsid_ident = idents::clsid( &struct_ident );
if let Some( ref guid ) = clsid_guid {
let clsid_guid_tokens = utils::get_guid_tokens( guid );
let clsid_doc = format!( "`{}` class ID.", struct_ident );
let clsid_const = quote!(
#[allow(non_upper_case_globals)]
#[doc = #clsid_doc ]
const #clsid_ident : ::intercom::CLSID = #clsid_guid_tokens;
);
output.push( clsid_const );
}
Ok( utils::tokens_to_tokenstream( item_tokens, output )? )
}
fn expand_com_library(
attr_tokens: &TokenStream,
item_tokens: TokenStream,
) -> Result<TokenStream, MacroError>
{
let mut output = vec![];
let ( _, coclasses ) = utils::parse_com_lib_tokens(
&lib_name(), attr_tokens )?;
let mut match_arms = vec![];
for class_name in coclasses {
let struct_ident = Ident::from( class_name );
let clsid_name = idents::clsid( &struct_ident );
match_arms.push( quote!(
self::#clsid_name =>
Ok( Box::into_raw( ::intercom::ComBox::new(
#struct_ident::new()
) ) as ::intercom::RawComPtr ),
) );
}
let match_arm_tokens = utils::flatten( match_arms.iter() );
let dll_get_class_object = quote!(
#[no_mangle]
#[allow(non_snake_case)]
#[allow(dead_code)]
#[doc(hidden)]
pub unsafe extern "stdcall" fn DllGetClassObject(
rclsid : ::intercom::REFCLSID,
riid : ::intercom::REFIID,
pout : *mut ::intercom::RawComPtr
) -> ::intercom::HRESULT
{
let mut combox = ::intercom::ComBox::new(
::intercom::ClassFactory::new( rclsid, | clsid | {
match *clsid {
#match_arm_tokens
_ => Err( ::intercom::E_NOINTERFACE ),
}
} ) );
::intercom::ComBox::query_interface(
combox.as_mut(),
riid,
pout );
Box::into_raw( combox );
::intercom::S_OK
}
);
output.push( dll_get_class_object );
Ok( utils::tokens_to_tokenstream( item_tokens, output )? )
}
fn error<E,T>(
e: E,
_attr: &T
) -> !
where MacroError: From<E>
{
panic!( "{}", MacroError::from( e ).msg )
}