math-mumu 0.2.0-rc.4

Math functions plugin for the MuMu / Lava language
Documentation
// src/lib.rs

use mumu::{
    parser::interpreter::{DynamicFnInfo, Interpreter},
    parser::types::{FunctionValue, Value},
};
use std::sync::{Arc, Mutex};

#[cfg(not(target_arch = "wasm32"))]
use std::ffi::{c_void, CStr};

mod arb;
mod sqrt;
mod unary;
mod partials;
mod rotate_point;
mod srand;

use arb::math_arb_bridge;
use sqrt::math_sqrt_bridge;
use unary::*;
use partials::*;
use rotate_point::math_rotate_point_bridge;
use srand::math_srand_bridge;

/// Maximum depth for value_to_string to prevent stack overflow.
const VALUE_TO_STRING_RECURSION_LIMIT: usize = 32;

/// Recursion-protected conversion of `Value` to string.
/// Falls back to "<recursion>" if nesting is too deep.
pub fn value_to_string(val: &Value) -> String {
    value_to_string_with_depth(val, 0)
}

fn value_to_string_with_depth(val: &Value, depth: usize) -> String {
    if depth > VALUE_TO_STRING_RECURSION_LIMIT {
        return "<recursion>".to_string();
    }
    match val {
        Value::Int(x) => x.to_string(),
        Value::IntArray(xs) => format!("{:?}", xs),
        Value::Int2DArray(rows) => format!("{:?}", rows),
        Value::Float(f) => f.to_string(),
        Value::FloatArray(ff) => format!("{:?}", ff),
        Value::Float2DArray(rows) => format!("{:?}", rows),
        Value::StrArray(ss) => format!("{:?}", ss),
        Value::KeyedArray(map) => {
            let mut s = String::from("{ ");
            let mut first = true;
            for (k, v) in map.iter() {
                if !first {
                    s.push_str(", ");
                }
                first = false;
                s.push_str(k);
                s.push_str(": ");
                s.push_str(&value_to_string_with_depth(v, depth + 1));
            }
            s.push_str(" }");
            s
        }
        // NEW: pretty-print KeyedError to keep output friendly
        Value::KeyedError(map) => {
            let msg = map.get("message").and_then(|v| {
                if let Value::SingleString(s) = v { Some(s.as_str()) } else { None }
            }).unwrap_or("");
            let line = map.get("line").and_then(|v| {
                if let Value::Int(i) = v { Some(*i) } else { None }
            }).unwrap_or(0);
            let col = map.get("col").and_then(|v| {
                if let Value::Int(i) = v { Some(*i) } else { None }
            }).unwrap_or(0);

            if !msg.is_empty() {
                if line > 0 || col > 0 {
                    format!("Error{{ line:{}, col:{}, message:\"{}\" }}", line, col, msg)
                } else {
                    format!("Error{{ message:\"{}\" }}", msg)
                }
            } else {
                format!("Error{{{} keys… }}", map.len())
            }
        }
        Value::Function(_) => "[Function]".to_string(),
        Value::Bool(b) => b.to_string(),
        Value::BoolArray(bb) => format!("{:?}", bb),
        Value::Placeholder => "_".to_string(),
        Value::SingleString(s) => format!("\"{}\"", s), // always print strings in quotes
        Value::Long(l) => l.to_string(),
        Value::Stream(sh) => format!("<Stream id={}, label={}>", sh.stream_id, sh.label),
        Value::Iterator(_) => "[Iterator]".to_string(),
        Value::Tensor(_) => "[Tensor]".to_string(),
        Value::MixedArray(items) => {
            let inner = items
                .iter()
                .map(|v| value_to_string_with_depth(v, depth + 1))
                .collect::<Vec<_>>()
                .join(", ");
            format!("[{}]", inner)
        }
        Value::Ref(arc_mutex) => {
            // Avoid infinite loop on self-referential structures
            value_to_string_with_depth(&arc_mutex.lock().unwrap(), depth + 1)
        }
        Value::Undefined => "undefined".to_string(),
        Value::Regex(rx) => format!("Regex(/{}{}/)", rx.pattern, rx.flags),
    }
}

/// Register all `math:*` bridges into the provided interpreter.
/// This is used by embedded/static (WASM) builds and can also be used by hosts.
pub fn register_all(interp: &mut Interpreter) {
    macro_rules! reg {
        ($name:expr, $f:expr) => {{
            let func = Arc::new(Mutex::new($f));
            interp.register_dynamic_function_ex($name, DynamicFnInfo::new(func.clone(), false));
            interp.set_variable($name, Value::Function(Box::new(FunctionValue::Named($name.into()))));
        }};
    }

    // ---- Math Arithmetic (partials) ----
    reg!("math:plus",     math_plus_bridge);
    reg!("math:subtract", math_subtract_bridge);
    reg!("math:multiply", math_multiply_bridge);
    reg!("math:divide",   math_divide_bridge);
    reg!("math:pow",      math_pow_bridge);
    reg!("math:mod",      math_mod_bridge);

    // ---- Multi-argument math ----
    reg!("math:atan2",    math_atan2_bridge);
    reg!("math:imul",     math_imul_bridge);
    reg!("math:max",      math_max_bridge);
    reg!("math:min",      math_min_bridge);
    reg!("math:hypot",    math_hypot_bridge);

    // ---- Single-argument math (unary.rs) ----
    reg!("math:abs",      math_abs_bridge);
    reg!("math:acos",     math_acos_bridge);
    reg!("math:acosh",    math_acosh_bridge);
    reg!("math:asin",     math_asin_bridge);
    reg!("math:asinh",    math_asinh_bridge);
    reg!("math:atan",     math_atan_bridge);
    reg!("math:atanh",    math_atanh_bridge);
    reg!("math:cbrt",     math_cbrt_bridge);
    reg!("math:ceil",     math_ceil_bridge);
    reg!("math:clz32",    math_clz32_bridge);
    reg!("math:cos",      math_cos_bridge);
    reg!("math:cosh",     math_cosh_bridge);
    reg!("math:exp",      math_exp_bridge);
    reg!("math:expm1",    math_expm1_bridge);
    reg!("math:floor",    math_floor_bridge);
    reg!("math:fround",   math_fround_bridge);
    reg!("math:log",      math_log_bridge);
    reg!("math:log10",    math_log10_bridge);
    reg!("math:log1p",    math_log1p_bridge);
    reg!("math:log2",     math_log2_bridge);
    reg!("math:round",    math_round_bridge);
    reg!("math:sign",     math_sign_bridge);
    reg!("math:sin",      math_sin_bridge);
    reg!("math:sinh",     math_sinh_bridge);
    reg!("math:tan",      math_tan_bridge);
    reg!("math:tanh",     math_tanh_bridge);
    reg!("math:trunc",    math_trunc_bridge);

    // ---- Sqrt & arbitrary precision (arb) ----
    reg!("math:sqrt",     math_sqrt_bridge);
    reg!("math:arb",      math_arb_bridge);

    // ---- 3-arg math (rotate_point) ----
    reg!("math:rotate_point", math_rotate_point_bridge);

    // ---- Random seedable generator (srand) ----
    reg!("math:srand",    math_srand_bridge);
}

/// Host/dynamic loader entrypoint (used when the plugin is loaded via extend()).
/// This symbol must NOT be present in WASM builds to avoid duplicate symbols.
#[cfg(not(target_arch = "wasm32"))]
#[no_mangle]
pub unsafe extern "C" fn Cargo_lock(
    interp_ptr: *mut c_void,
    extra_str: *const c_void,
) -> i32 {
    let interp = &mut *(interp_ptr as *mut Interpreter);

    let verbose = interp.is_verbose();
    if verbose && !extra_str.is_null() {
        let c_str = CStr::from_ptr(extra_str as *const i8);
        eprintln!(
            "(math) cargo_lock => bridging => extra arg='{}'",
            c_str.to_string_lossy()
        );
    }
    if verbose {
        eprintln!("(math) => registering math plugin bridges …");
    }

    // Register everything via the shared function.
    register_all(interp);

    if verbose {
        eprintln!("(math) cargo_lock => done registering all bridging functions.");
    }

    0
}