umka 0.1.0

high level bindings for umka
Documentation
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>>,
    // result: Option<Box<[u8]>>,
}

enum Arg<'a> {
    Int(i64),
    UInt(u64),
    // Ptr(...) TODO: figure this one out
    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![],
            // result: None,
        }
    }

    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 ptr_arg(&mut self, ...) -> &mut Self {
    //     todo!();
    //     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)
        }
    }
}

// if we set a string arg and umka writes to that ptr, we're into UB.
// TODO: look deeper into this to make sure it's actually safe.
#[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>();
        }
    }
}