mumu-array 0.1.2

Array tools plugin for the MuMu/Lava language
Documentation
// array/src/apply.rs

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

/// The bridging function for `array:apply(fn, arr)`.
/// If both arguments are real, we call `fn(...arrElements)`.
/// Otherwise, we return a partial function capturing whichever is known.
pub fn array_apply_bridge(
    interp: &mut Interpreter,
    mut args: Vec<Value>
) -> Result<Value, String> {
    match args.len() {
        0 => {
            // No arguments => partial => (fn=?, arr=?)
            Ok(make_apply_partial(None, None))
        }
        1 => {
            // Could be a function, an array, or `_`
            let first = args.remove(0);
            match first {
                Value::Placeholder => {
                    // still missing both => partial
                    Ok(make_apply_partial(None, None))
                }
                Value::SingleString(s) if s == "_" => {
                    // partial => missing both
                    Ok(make_apply_partial(None, None))
                }
                Value::StrArray(ref arr) if arr.len() == 1 && arr[0] == "_" => {
                    // partial => missing both
                    Ok(make_apply_partial(None, None))
                }
                other => {
                    // if it's a function => store it as someFn
                    // or if it's an array => store it as someArr
                    Ok(make_apply_partial(Some(other), None))
                }
            }
        }
        2 => {
            // Possibly final or partial with underscores
            let a1 = args.remove(0);
            let a2 = args.remove(0);

            let first_is_placeholder = match &a1 {
                Value::Placeholder => true,
                Value::SingleString(s) if s == "_" => true,
                Value::StrArray(ss) if ss.len()==1 && ss[0] == "_" => true,
                _ => false,
            };
            let second_is_placeholder = match &a2 {
                Value::Placeholder => true,
                Value::SingleString(s) if s == "_" => true,
                Value::StrArray(ss) if ss.len()==1 && ss[0] == "_" => true,
                _ => false,
            };

            if first_is_placeholder || second_is_placeholder {
                // partial usage
                let first_opt = if first_is_placeholder { None } else { Some(a1) };
                let second_opt = if second_is_placeholder { None } else { Some(a2) };
                Ok(make_apply_partial(first_opt, second_opt))
            } else {
                // both real => do final apply
                do_apply(interp, a1, a2)
            }
        }
        n => Err(format!("array:apply => expected up to 2 arguments, got {}", n)),
    }
}

/// Creates a partial function capturing whichever arguments are known (function or array).
fn make_apply_partial(fn_opt: Option<Value>, arr_opt: Option<Value>) -> Value {
    use mumu::parser::types::FunctionValue::RustClosure;

    let closure = move |interp: &mut Interpreter, new_args: Vec<Value>| {
        let mut current_fn = fn_opt.clone();
        let mut current_arr = arr_opt.clone();

        for arg in new_args {
            // fill function if missing
            if current_fn.is_none() {
                if matches!(arg, Value::Placeholder) {
                    // remain None
                } else {
                    current_fn = Some(arg);
                }
                continue;
            }
            // fill array if missing
            if current_arr.is_none() {
                if matches!(arg, Value::Placeholder) {
                    // remain None
                } else {
                    current_arr = Some(arg);
                }
                continue;
            }
            return Err("array:apply => partial => too many arguments".to_string());
        }

        if current_fn.is_some() && current_arr.is_some() {
            do_apply(interp, current_fn.unwrap(), current_arr.unwrap())
        } else {
            // still partial
            Ok(make_apply_partial(current_fn, current_arr))
        }
    };

    Value::Function(Box::new(RustClosure(
        "array:apply-partial".to_string(),
        Arc::new(Mutex::new(closure)),
        0,
    )))
}

/// The final operation if both arguments are known
/// - first must be a function
/// - second must be an array (int[], str[], bool[], or float[])
fn do_apply(interp: &mut Interpreter, fn_val: Value, arr_val: Value) -> Result<Value, String> {
    // 1) parse the function
    let func_box = match fn_val {
        Value::Function(fb) => fb,
        other => {
            return Err(format!("array:apply => first argument must be Function(...), got {:?}", other));
        }
    };

    // 2) parse the array => gather elements as arguments
    let elements = match arr_val {
        Value::IntArray(xs) => xs.into_iter().map(Value::Int).collect(),
        Value::StrArray(ss) => ss.into_iter().map(Value::SingleString).collect(),
        Value::BoolArray(bb) => bb.into_iter().map(Value::Bool).collect(),
        Value::FloatArray(ff) => ff.into_iter().map(Value::Float).collect(),
        Value::Placeholder => {
            return Err("array:apply => second argument cannot be just '_'; it must be an actual array".to_string());
        }
        other => {
            return Err(format!("array:apply => second argument must be an array, got {:?}", other));
        }
    };

    // 3) call the function with each array element as a separate argument
    apply_n_ary_function_value(interp, func_box, elements)
}