use std::{
ffi::{CStr, CString, c_void},
mem::zeroed,
ptr,
};
pub struct FunctionCallBuilder<'a> {
umka: &'a crate::Umka,
name: CString,
module: Option<CString>,
args: Vec<Arg<'a>>,
}
enum Arg<'a> {
Int(i64),
UInt(u64),
Real(f64),
Real32(f32),
Str(&'a CStr),
}
#[derive(Debug, thiserror::Error)]
pub enum FunctionCallError {
#[error("a function called \"{0}\" could not be found")]
LookupFailed(String),
#[error("too many arguments for function call")]
TooManyArgs,
#[error("{}", .0.as_ref().map_or_else(
|| "generic function call error".to_string(),
ToString::to_string
))]
CallFailed(Option<crate::ErrorDetails>),
}
impl<'a> FunctionCallBuilder<'a> {
pub(crate) const fn new(umka: &'a crate::Umka, name: CString) -> Self {
Self {
umka,
name,
module: None,
args: vec![],
}
}
pub fn in_module(&mut self, module_name: &'static str) -> Result<&mut Self, crate::Error> {
let module_name = CString::new(module_name).map_err(crate::Error::NullByte)?;
self.module = Some(module_name);
Ok(self)
}
pub fn int_arg(&mut self, val: i64) -> &mut Self {
self.args.push(Arg::Int(val));
self
}
pub fn uint_arg(&mut self, val: u64) -> &mut Self {
self.args.push(Arg::UInt(val));
self
}
pub fn real_arg(&mut self, val: f64) -> &mut Self {
self.args.push(Arg::Real(val));
self
}
pub fn real32_arg(&mut self, val: f32) -> &mut Self {
self.args.push(Arg::Real32(val));
self
}
pub fn str_arg(&mut self, val: &'a CStr) -> &mut Self {
self.args.push(Arg::Str(val));
self
}
pub fn call_int(&mut self) -> Result<i64, FunctionCallError> {
Ok(unsafe { (*self.call()?.result).intVal })
}
pub fn call_uint(&mut self) -> Result<u64, FunctionCallError> {
Ok(unsafe { (*self.call()?.result).uintVal })
}
pub fn call_real(&mut self) -> Result<f64, FunctionCallError> {
Ok(unsafe { (*self.call()?.result).realVal })
}
pub fn call_str(&mut self) -> Result<&'a CStr, FunctionCallError> {
Ok(unsafe { CStr::from_ptr((*self.call()?.result).ptrVal as *const i8) })
}
pub fn call_void(&mut self) -> Result<(), FunctionCallError> {
unsafe {
self.call()?;
}
Ok(())
}
unsafe fn call(&mut self) -> Result<umka_sys::UmkaFuncContext, FunctionCallError> {
unsafe {
let module_name = self.module.as_ref().map_or(ptr::null(), |s| s.as_ptr());
let func_name = self.name.as_ptr();
let mut ctx: umka_sys::UmkaFuncContext = zeroed();
if !umka_sys::umkaGetFunc(self.umka.0, module_name, func_name, &raw mut ctx) {
let name = self.name.to_string_lossy().to_string();
return Err(FunctionCallError::LookupFailed(name));
}
for (idx, arg) in self.args.iter().enumerate() {
let idx = i32::try_from(idx).map_err(|_| FunctionCallError::TooManyArgs)?;
set_arg(
self.umka.0,
&mut *umka_sys::umkaGetParam(ctx.params, idx),
arg,
);
}
let code = umka_sys::umkaCall(self.umka.0, &raw mut ctx);
if code != 0 {
return Err(FunctionCallError::CallFailed(self.umka.error_details()));
}
Ok(ctx)
}
}
}
#[allow(clippy::as_ptr_cast_mut)]
unsafe fn set_arg(umka: *mut umka_sys::Umka, param: &mut umka_sys::UmkaStackSlot, arg: &Arg) {
match arg {
Arg::Int(v) => param.intVal = *v,
Arg::UInt(v) => param.uintVal = *v,
Arg::Real(v) => param.realVal = *v,
Arg::Real32(v) => param.real32Val = *v,
Arg::Str(v) => {
let str = unsafe { umka_sys::umkaMakeStr(umka, v.as_ptr()) };
param.ptrVal = str.cast::<c_void>();
}
}
}