array-mumu 0.2.0-rc.5

Array tools plugin for the Mumu ecosystem
Documentation
// array/src/each.rs

use mumu::{
    Interpreter,
    Value,
};
use crate::apply::apply_n_ary_function_value;
use std::sync::{Arc, Mutex};

/// `array:each` takes 2 arguments: (fn, data).
/// If either argument is `_`, we do partial usage. Once both are known, we iterate.
///
/// The iteration calls `(fn)(element)` ignoring the return. We do not produce a
/// new array, but return the original array unchanged (like Ramda's `forEach`).
///
/// Now supports MixedArray as well!
///
/// Usage examples:
///   array:each(slog, [1,2,3])
///   partial = array:each(_, [1,2,3]); partial(slog)
///   array:each(slog, MixedArray([ ... ]))
pub fn array_each_bridge(
    interp: &mut Interpreter,
    args: Vec<Value>
) -> Result<Value, String> {
    match args.len() {
        0 => {
            // no arguments => partial => (fn=?, data=?)
            Ok(make_two_arg_partial(None, None))
        }
        1 => {
            // 1 argument => if `_`, both unknown; else store in slot #1
            let val = &args[0];
            if matches!(val, Value::Placeholder) {
                Ok(make_two_arg_partial(None, None))
            } else {
                Ok(make_two_arg_partial(Some(val.clone()), None))
            }
        }
        2 => {
            // possibly final or partial
            let a = &args[0];
            let b = &args[1];
            let a_is_pl = matches!(a, Value::Placeholder);
            let b_is_pl = matches!(b, Value::Placeholder);

            if a_is_pl || b_is_pl {
                // partial
                let a_opt = if a_is_pl { None } else { Some(a.clone()) };
                let b_opt = if b_is_pl { None } else { Some(b.clone()) };
                Ok(make_two_arg_partial(a_opt, b_opt))
            } else {
                // both real => do_each
                do_each(interp, &args[0], &args[1])
            }
        }
        n => Err(format!("array:each => expected up to 2 arguments, got {}", n)),
    }
}

/// Once we have the function & data, do the iteration. 
/// Now supports MixedArray.
/// - For each item, we call `(fn)(item)` ignoring the return value.
/// - We return the original array unchanged.
fn do_each(
    interp: &mut Interpreter,
    func_val: &Value,
    data_val: &Value
) -> Result<Value, String> {
    let f_boxed = match func_val {
        Value::Function(fb) => fb.clone(),
        other => return Err(format!("array:each => first arg must be Function(...), got {:?}", other)),
    };

    match data_val {
        Value::IntArray(xs) => {
            for &x in xs {
                let _ = apply_n_ary_function_value(interp, f_boxed.clone(), vec![Value::Int(x)])?;
            }
            Ok(Value::IntArray(xs.clone()))
        }
        Value::StrArray(ss) => {
            for s in ss {
                let _ = apply_n_ary_function_value(interp, f_boxed.clone(), vec![Value::SingleString(s.clone())])?;
            }
            Ok(Value::StrArray(ss.clone()))
        }
        Value::BoolArray(bb) => {
            for &b in bb {
                let _ = apply_n_ary_function_value(interp, f_boxed.clone(), vec![Value::Bool(b)])?;
            }
            Ok(Value::BoolArray(bb.clone()))
        }
        Value::FloatArray(ff) => {
            for &f in ff {
                let _ = apply_n_ary_function_value(interp, f_boxed.clone(), vec![Value::Float(f)])?;
            }
            Ok(Value::FloatArray(ff.clone()))
        }
        Value::MixedArray(items) => {
            for item in items {
                let _ = apply_n_ary_function_value(interp, f_boxed.clone(), vec![item.clone()])?;
            }
            Ok(Value::MixedArray(items.clone()))
        }
        other => Err(format!("array:each => second arg must be array (including MixedArray), got {:?}", other)),
    }
}

/// Builds a partial function capturing (fn_opt, data_opt).
/// Each new argument tries to fill the first None slot (unless `_`).
/// If both are known => do_each.
fn make_two_arg_partial(
    fn_opt: Option<Value>,
    data_opt: Option<Value>
) -> Value {
    use mumu::parser::types::FunctionValue::RustClosure;

    let closure = move |interp: &mut Interpreter, new_args: Vec<Value>| {
        let mut f_cur = fn_opt.clone();
        let mut d_cur = data_opt.clone();

        for arg in new_args {
            // fill slot #1 => function
            if f_cur.is_none() {
                if matches!(arg, Value::Placeholder) {
                    // remain None
                } else {
                    f_cur = Some(arg);
                }
                continue;
            }
            // fill slot #2 => data
            if d_cur.is_none() {
                if matches!(arg, Value::Placeholder) {
                    // remain None
                } else {
                    d_cur = Some(arg);
                }
                continue;
            }
            return Err("array:each => partial => too many arguments".to_string());
        }

        // if both known => do_each
        if f_cur.is_some() && d_cur.is_some() {
            do_each(
                interp,
                f_cur.as_ref().unwrap(),
                d_cur.as_ref().unwrap()
            )
        } else {
            // still partial
            Ok(make_two_arg_partial(f_cur, d_cur))
        }
    };

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