use std::error::Error;
use std::ffi::CString;
use std::fmt;
use std::mem;
use libc::size_t;
use {Encode, Encoding, Message};
use runtime::{Class, Imp, NO, Object, Sel, self};
#[derive(Clone, PartialEq, Debug)]
pub struct UnequalArgsError {
sel_args: usize,
fn_args: usize,
}
impl fmt::Display for UnequalArgsError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "Selector accepts {} arguments, but function accepts {}",
self.sel_args, self.fn_args)
}
}
impl Error for UnequalArgsError {
fn description(&self) -> &str {
"Selector and function accept unequal numbers of arguments"
}
}
pub trait IntoMethodImp {
type Callee: Message;
type Ret: Encode;
fn argument_encodings() -> Box<[Encoding]>;
fn into_imp(self, sel: Sel) -> Result<Imp, UnequalArgsError>;
}
macro_rules! count_idents {
() => (0);
($a:ident) => (1);
($a:ident, $($b:ident),+) => (1 + count_idents!($($b),*));
}
macro_rules! method_decl_impl {
(-$s:ident, $sp:ty, $($t:ident),*) => (
impl<$s, R $(, $t)*> IntoMethodImp for extern fn($sp, Sel $(, $t)*) -> R
where $s: Message, R: Encode $(, $t: Encode)* {
type Callee = $s;
type Ret = R;
fn argument_encodings() -> Box<[Encoding]> {
Box::new([
<*mut Object>::encode(),
Sel::encode(),
$($t::encode()),*
])
}
fn into_imp(self, sel: Sel) -> Result<Imp, UnequalArgsError> {
let fn_args = 2 + count_idents!($($t),*);
let sel_args = 2 + sel.name().chars().filter(|&c| c == ':').count();
if sel_args == fn_args {
unsafe { Ok(mem::transmute(self)) }
} else {
Err(UnequalArgsError { sel_args: sel_args, fn_args: fn_args })
}
}
}
);
($($t:ident),*) => (
method_decl_impl!(-T, &T, $($t),*);
method_decl_impl!(-T, &mut T, $($t),*);
);
}
method_decl_impl!();
method_decl_impl!(A);
method_decl_impl!(A, B);
method_decl_impl!(A, B, C);
method_decl_impl!(A, B, C, D);
method_decl_impl!(A, B, C, D, E);
method_decl_impl!(A, B, C, D, E, F);
method_decl_impl!(A, B, C, D, E, F, G);
method_decl_impl!(A, B, C, D, E, F, G, H);
method_decl_impl!(A, B, C, D, E, F, G, H, I);
method_decl_impl!(A, B, C, D, E, F, G, H, I, J);
method_decl_impl!(A, B, C, D, E, F, G, H, I, J, K);
method_decl_impl!(A, B, C, D, E, F, G, H, I, J, K, L);
fn method_type_encoding<F>() -> CString where F: IntoMethodImp {
let mut types = F::Ret::encode().as_str().to_string();
types.extend(F::argument_encodings().iter().map(|e| e.as_str()));
CString::new(types).unwrap()
}
pub struct ClassDecl {
cls: *mut Class,
}
impl ClassDecl {
pub fn new(superclass: &Class, name: &str) -> Option<ClassDecl> {
let name = CString::new(name).unwrap();
let cls = unsafe {
runtime::objc_allocateClassPair(superclass, name.as_ptr(), 0)
};
if cls.is_null() {
None
} else {
Some(ClassDecl { cls: cls })
}
}
pub unsafe fn add_method<F>(&mut self, sel: Sel, func: F)
where F: IntoMethodImp<Callee=Object> {
let types = method_type_encoding::<F>();
let imp = match func.into_imp(sel) {
Ok(imp) => imp,
Err(err) => panic!("{}", err),
};
let success = runtime::class_addMethod(self.cls, sel, imp,
types.as_ptr());
assert!(success != NO, "Failed to add method {:?}", sel);
}
pub unsafe fn add_class_method<F>(&mut self, sel: Sel, func: F)
where F: IntoMethodImp<Callee=Class> {
let types = method_type_encoding::<F>();
let imp = match func.into_imp(sel) {
Ok(imp) => imp,
Err(err) => panic!("{}", err),
};
let cls_obj = self.cls as *const Object;
let metaclass = runtime::object_getClass(cls_obj) as *mut Class;
let success = runtime::class_addMethod(metaclass, sel, imp,
types.as_ptr());
assert!(success != NO, "Failed to add class method {:?}", sel);
}
pub fn add_ivar<T>(&mut self, name: &str) where T: Encode {
let c_name = CString::new(name).unwrap();
let encoding = CString::new(T::encode().as_str()).unwrap();
let size = mem::size_of::<T>() as size_t;
let align = mem::align_of::<T>() as u8;
let success = unsafe {
runtime::class_addIvar(self.cls, c_name.as_ptr(), size, align,
encoding.as_ptr())
};
assert!(success != NO, "Failed to add ivar {}", name);
}
pub fn register(self) -> &'static Class {
unsafe {
let cls = self.cls;
runtime::objc_registerClassPair(cls);
mem::forget(self);
&*cls
}
}
}
impl Drop for ClassDecl {
fn drop(&mut self) {
unsafe {
runtime::objc_disposeClassPair(self.cls);
}
}
}
#[cfg(test)]
mod tests {
use runtime::{Object, Sel};
use test_utils;
use super::IntoMethodImp;
#[test]
fn test_custom_class() {
let obj = test_utils::custom_object();
unsafe {
let _: () = msg_send![obj, setFoo:13u32];
let result: u32 = msg_send![obj, foo];
assert!(result == 13);
}
}
#[test]
fn test_class_method() {
let cls = test_utils::custom_class();
unsafe {
let result: u32 = msg_send![cls, classFoo];
assert!(result == 7);
}
}
#[test]
fn test_mismatched_args() {
extern fn wrong_num_args_method(_obj: &Object, _cmd: Sel, _a: i32) { }
let sel = sel!(doSomethingWithFoo:bar:);
let f: extern fn(&Object, Sel, i32) = wrong_num_args_method;
let imp = f.into_imp(sel);
assert!(imp.is_err());
}
}