rssn 0.2.9

A comprehensive scientific computing library for Rust, aiming for feature parity with NumPy and SymPy.
Documentation
//! Handle-based FFI API for compute cache module.

use std::ffi::CStr;
use std::ffi::CString;
use std::os::raw::c_char;
use std::sync::Arc;

use crate::compute::cache::ComputationResultCache;
use crate::compute::cache::ParsingCache;
use crate::symbolic::core::Expr;

// --- ParsingCache ---

/// Creates a new `ParsingCache`.
/// The caller is responsible for freeing the memory using `rssn_parsing_cache_free`.
#[unsafe(no_mangle)]
pub extern "C" fn rssn_parsing_cache_new() -> *mut ParsingCache {
    Box::into_raw(Box::new(ParsingCache::new()))
}

/// Frees a `ParsingCache`.
#[unsafe(no_mangle)]
pub extern "C" fn rssn_parsing_cache_free(cache: *mut ParsingCache) {
    if cache.is_null() {
        return;
    }

    unsafe {
        let _ = Box::from_raw(cache);
    }
}

/// Clears a `ParsingCache`.
#[unsafe(no_mangle)]
pub extern "C" fn rssn_parsing_cache_clear(cache: *mut ParsingCache) {
    if cache.is_null() {
        return;
    }

    unsafe {
        (*cache).clear();
    }
}

/// Retrieves an expression from the `ParsingCache`.
///
/// Returns a pointer to the Expr (Arc`<Expr>` with incremented refcount), or null if not found.
/// The caller is responsible for freeing the returned Expr (using the appropriate Expr free function).
#[unsafe(no_mangle)]
pub extern "C" fn rssn_parsing_cache_get(
    cache: *mut ParsingCache,
    input: *const c_char,
) -> *mut Expr {
    if cache.is_null() || input.is_null() {
        return std::ptr::null_mut();
    }

    unsafe {
        let input_str = match CStr::from_ptr(input).to_str() {
            | Ok(s) => s,
            | Err(_) => return std::ptr::null_mut(),
        };

        match (*cache).get(input_str) {
            | Some(expr) => {
                // Return a raw pointer to the inner Expr, but we need to keep the Arc alive?
                // Actually, usually we pass Arc<Expr> across FFI as *const Expr if it's borrowed,
                // or we need a way to pass ownership.
                // For simplicity here, let's clone the inner Expr to a Box if we want to return a standalone pointer,
                // OR we return a pointer to an Arc<Expr>?
                //
                // If we return *mut Expr, we lose the Arc context unless we re-wrap it.
                // Given Expr is usually used inside Arc, maybe we should return *mut Expr and let the caller wrap it?
                // But Expr::Dag contains Arc<DagNode>.
                //
                // Let's assume for now we return a Box<Expr> that is a clone of the cached Expr.
                // This means we are giving a new copy to the caller.
                Box::into_raw(Box::new((*expr).clone()))
            },
            | None => std::ptr::null_mut(),
        }
    }
}

/// Stores an expression in the `ParsingCache`.
/// The expr pointer is cloned (deep copy of the structure, but DAG nodes are shared).
#[unsafe(no_mangle)]
pub extern "C" fn rssn_parsing_cache_set(
    cache: *mut ParsingCache,
    input: *const c_char,
    expr: *const Expr,
) {
    if cache.is_null() || input.is_null() || expr.is_null() {
        return;
    }

    unsafe {
        let input_str = match CStr::from_ptr(input).to_str() {
            | Ok(s) => s.to_string(),
            | Err(_) => return,
        };

        let expr_arc = Arc::new((*expr).clone());

        (*cache).set(input_str, expr_arc);
    }
}

// --- ComputationResultCache ---

/// Creates a new `ComputationResultCache`.
/// The caller is responsible for freeing the memory using `rssn_computation_result_cache_free`.
#[unsafe(no_mangle)]
pub extern "C" fn rssn_computation_result_cache_new() -> *mut ComputationResultCache {
    Box::into_raw(Box::new(ComputationResultCache::new()))
}

/// Frees a `ComputationResultCache`.
#[unsafe(no_mangle)]
pub extern "C" fn rssn_computation_result_cache_free(cache: *mut ComputationResultCache) {
    if cache.is_null() {
        return;
    }

    unsafe {
        let _ = Box::from_raw(cache);
    }
}

/// Clears a `ComputationResultCache`.
#[unsafe(no_mangle)]
pub extern "C" fn rssn_computation_result_cache_clear(cache: *mut ComputationResultCache) {
    if cache.is_null() {
        return;
    }

    unsafe {
        (*cache).clear();
    }
}

/// Retrieves a value from the `ComputationResultCache`.
/// Returns a C string (char*) which must be freed by the caller using `rssn_free_string`.
/// Returns null if not found.
#[unsafe(no_mangle)]
pub extern "C" fn rssn_computation_result_cache_get(
    cache: *mut ComputationResultCache,
    expr: *const Expr,
) -> *mut c_char {
    if cache.is_null() || expr.is_null() {
        return std::ptr::null_mut();
    }

    unsafe {
        // We need to construct an Arc<Expr> to query the cache, but we only have a raw pointer.
        // The cache key is Arc<Expr>.
        // We can create a temporary Arc for the lookup if we clone the Expr.
        let expr_arc = Arc::new((*expr).clone());

        match (*cache).get(&expr_arc) {
            | Some(value) => {
                match CString::new(value) {
                    | Ok(c_str) => c_str.into_raw(),
                    | Err(_) => std::ptr::null_mut(),
                }
            },
            | None => std::ptr::null_mut(),
        }
    }
}

/// Stores a value in the `ComputationResultCache`.
#[unsafe(no_mangle)]
pub extern "C" fn rssn_computation_result_cache_set(
    cache: *mut ComputationResultCache,
    expr: *const Expr,
    value: *const c_char,
) {
    if cache.is_null() || expr.is_null() || value.is_null() {
        return;
    }

    unsafe {
        let value_str = match CStr::from_ptr(value).to_str() {
            | Ok(s) => s.to_string(),
            | Err(_) => return,
        };

        let expr_arc = Arc::new((*expr).clone());

        (*cache).set(expr_arc, value_str);
    }
}