use crate::{
args::{Arg, ArgInfo},
describe::DocComments,
error::{Error, Result},
flags::{DataType, MethodFlags},
types::Zval,
zend::{ExecuteData, FunctionEntry, ZendType},
};
use std::{ffi::CString, mem, ptr};
#[cfg(not(windows))]
pub type FunctionHandler = extern "C" fn(execute_data: &mut ExecuteData, retval: &mut Zval);
#[cfg(windows)]
pub type FunctionHandler =
extern "vectorcall" fn(execute_data: &mut ExecuteData, retval: &mut Zval);
#[cfg(not(windows))]
type FunctionPointerHandler = extern "C" fn(execute_data: *mut ExecuteData, retval: *mut Zval);
#[cfg(windows)]
type FunctionPointerHandler =
extern "vectorcall" fn(execute_data: *mut ExecuteData, retval: *mut Zval);
#[must_use]
#[derive(Debug)]
pub struct FunctionBuilder<'a> {
pub(crate) name: String,
function: FunctionEntry,
pub(crate) args: Vec<Arg<'a>>,
n_req: Option<usize>,
pub(crate) retval: Option<DataType>,
ret_as_ref: bool,
pub(crate) ret_as_null: bool,
pub(crate) docs: DocComments,
}
impl<'a> FunctionBuilder<'a> {
pub fn new<T: Into<String>>(name: T, handler: FunctionHandler) -> Self {
Self {
name: name.into(),
function: FunctionEntry {
fname: ptr::null(),
handler: Some(unsafe {
mem::transmute::<FunctionHandler, FunctionPointerHandler>(handler)
}),
arg_info: ptr::null(),
num_args: 0,
flags: 0, #[cfg(php84)]
doc_comment: ptr::null(),
#[cfg(php84)]
frameless_function_infos: ptr::null(),
},
args: vec![],
n_req: None,
retval: None,
ret_as_ref: false,
ret_as_null: false,
docs: &[],
}
}
pub fn new_abstract<T: Into<String>>(name: T) -> Self {
Self {
name: name.into(),
function: FunctionEntry {
fname: ptr::null(),
handler: None,
arg_info: ptr::null(),
num_args: 0,
flags: MethodFlags::Abstract.bits(),
#[cfg(php84)]
doc_comment: ptr::null(),
#[cfg(php84)]
frameless_function_infos: ptr::null(),
},
args: vec![],
n_req: None,
retval: None,
ret_as_ref: false,
ret_as_null: false,
docs: &[],
}
}
pub fn constructor(handler: FunctionHandler) -> Self {
Self::new("__construct", handler)
}
pub fn arg(mut self, arg: Arg<'a>) -> Self {
self.args.push(arg);
self
}
pub fn not_required(mut self) -> Self {
self.n_req = Some(self.args.len());
self
}
pub fn returns(mut self, type_: DataType, as_ref: bool, allow_null: bool) -> Self {
self.retval = Some(type_);
self.ret_as_ref = as_ref;
self.ret_as_null = allow_null && type_ != DataType::Void && type_ != DataType::Mixed;
self
}
pub fn docs(mut self, docs: DocComments) -> Self {
self.docs = docs;
self
}
pub fn build(mut self) -> Result<FunctionEntry> {
let mut args = Vec::with_capacity(self.args.len() + 1);
let mut n_req = self.n_req.unwrap_or(self.args.len());
let variadic = self.args.last().is_some_and(|arg| arg.variadic);
if variadic {
self.function.flags |= MethodFlags::Variadic.bits();
n_req = n_req.saturating_sub(1);
}
args.push(ArgInfo {
name: n_req as *const _,
type_: match self.retval {
Some(retval) => {
ZendType::empty_from_type(retval, self.ret_as_ref, false, self.ret_as_null)
.ok_or(Error::InvalidCString)?
}
None => ZendType::empty(false, false),
},
default_value: ptr::null(),
});
args.extend(
self.args
.iter()
.map(Arg::as_arg_info)
.collect::<Result<Vec<_>>>()?,
);
self.function.fname = CString::new(self.name)?.into_raw();
self.function.num_args = (args.len() - 1).try_into()?;
self.function.arg_info = Box::into_raw(args.into_boxed_slice()) as *const ArgInfo;
Ok(self.function)
}
}