array-mumu 0.2.0-rc.5

Array tools plugin for the Mumu ecosystem
Documentation
// FILE: array/src/map.rs

use crate::apply::do_map;
use mumu::{
    parser::types::FunctionValue::RustClosure,
    parser::types::Value,
    Interpreter,
};
use std::sync::{Arc, Mutex};

/// `array:map` bridging function with placeholder-based partial application.
/// If user passes a Tensor as the data, we raise an error that Tensors aren't supported.
pub fn array_map_bridge(
    interp: &mut Interpreter,
    mut args: Vec<Value>
) -> Result<Value, String> {
    match args.len() {
        0 => Ok(make_two_arg_partial(None, None)),

        1 => {
            let first = args.remove(0);
            match first {
                Value::Placeholder => Ok(make_two_arg_partial(None, None)),
                Value::Function(_) => Ok(make_two_arg_partial(Some(first), None)),
                other => Ok(make_two_arg_partial(None, Some(other))),
            }
        }

        2 => {
            let v1 = args.remove(0);
            let v2 = args.remove(0);

            let v1_is_pl = matches!(v1, Value::Placeholder);
            let v2_is_pl = matches!(v2, Value::Placeholder);

            if !v1_is_pl && !v2_is_pl {
                do_map_final(interp, &v1, &v2)
            } else {
                let maybe_func = if v1_is_pl { None } else { Some(v1) };
                let maybe_data = if v2_is_pl { None } else { Some(v2) };
                Ok(make_two_arg_partial(maybe_func, maybe_data))
            }
        }

        n => Err(format!("array:map => expected up to 2 arguments, got {}", n)),
    }
}

fn do_map_final(
    interp: &mut Interpreter,
    func_val: &Value,
    data_val: &Value
) -> Result<Value, String> {
    // Check that first is indeed a Function
    let func_boxed = match func_val {
        Value::Function(fb) => fb,
        other => return Err(format!(
            "array:map => first argument must be a Function(...), got {:?}",
            other
        )),
    };

    // If the data is a Tensor, raise an error
    if let Value::Tensor(_) = data_val {
        return Err("array:map => does not support Tensors yet".to_string());
    }

    // Otherwise, delegate to the do_map helper
    do_map(interp, func_boxed, data_val.clone())
}

fn make_two_arg_partial(
    func_opt: Option<Value>,
    data_opt: Option<Value>
) -> Value {
    Value::Function(Box::new(RustClosure(
        "array:map-partial".to_string(),
        Arc::new(Mutex::new(move |interp: &mut Interpreter, new_args: Vec<Value>| {
            let mut f_current = func_opt.clone();
            let mut d_current = data_opt.clone();

            for arg in new_args {
                if f_current.is_none() {
                    if matches!(arg, Value::Placeholder) {
                        // remain None
                    } else {
                        f_current = Some(arg);
                    }
                    continue;
                }
                if d_current.is_none() {
                    if matches!(arg, Value::Placeholder) {
                        // remain None
                    } else {
                        d_current = Some(arg);
                    }
                    continue;
                }
                return Err("array:map => partial => too many arguments".to_string());
            }

            if f_current.is_some() && d_current.is_some() {
                do_map_final(
                    interp,
                    f_current.as_ref().unwrap(),
                    d_current.as_ref().unwrap()
                )
            } else {
                Ok(make_two_arg_partial(f_current, d_current))
            }
        })),
        0,
    )))
}