array-mumu 0.2.0-rc.5

Array tools plugin for the Mumu ecosystem
Documentation
use mumu::parser::types::Value;
use mumu::parser::interpreter::Interpreter;
use super::common::{is_placeholder, make_two_arg_partial};

pub fn array_chunk_bridge(_interp: &mut Interpreter, args: Vec<Value>) -> Result<Value, String> {
    match args.len() {
        0 => Ok(make_two_arg_partial(do_chunk, None, None, "array-transform-chunk-partial")),
        1 => {
            if is_placeholder(&args[0]) {
                Ok(make_two_arg_partial(do_chunk, None, None, "array-transform-chunk-partial"))
            } else {
                Ok(make_two_arg_partial(do_chunk, Some(args[0].clone()), None, "array-transform-chunk-partial"))
            }
        }
        2 => {
            let a1_pl = is_placeholder(&args[0]);
            let a2_pl = is_placeholder(&args[1]);
            if !a1_pl && !a2_pl {
                do_chunk(_interp, args[0].clone(), args[1].clone())
            } else {
                Ok(make_two_arg_partial(
                    do_chunk,
                    if a1_pl { None } else { Some(args[0].clone()) },
                    if a2_pl { None } else { Some(args[1].clone()) },
                    "array-transform-chunk-partial"
                ))
            }
        }
        _ => Err("array:chunk expects 2 arguments (size, array)".to_string()),
    }
}

fn do_chunk(_interp: &mut Interpreter, size: Value, arr: Value) -> Result<Value, String> {
    let sz = match size {
        Value::Int(n) if n > 0 => n as usize,
        Value::IntArray(ref a) if a.len() == 1 && a[0] > 0 => a[0] as usize,
        _ => return Err("array:chunk => first argument must be positive int".to_string()),
    };

    match arr {
        // Numeric arrays can return a true 2D array when all chunk lengths match.
        Value::IntArray(xs) => {
            let rows: Vec<Vec<i32>> = xs.chunks(sz).map(|c| c.to_vec()).collect();
            let all_equal = rows
                .first()
                .map(|r0| rows.iter().all(|r| r.len() == r0.len()))
                .unwrap_or(true);
            if all_equal {
                Ok(Value::Int2DArray(rows))
            } else {
                Ok(Value::MixedArray(rows.into_iter().map(Value::IntArray).collect()))
            }
        }
        Value::FloatArray(xs) => {
            let rows: Vec<Vec<f64>> = xs.chunks(sz).map(|c| c.to_vec()).collect();
            let all_equal = rows
                .first()
                .map(|r0| rows.iter().all(|r| r.len() == r0.len()))
                .unwrap_or(true);
            if all_equal {
                Ok(Value::Float2DArray(rows))
            } else {
                Ok(Value::MixedArray(rows.into_iter().map(Value::FloatArray).collect()))
            }
        }

        // Non-numeric arrays always return MixedArray of same-typed rows.
        Value::StrArray(xs) => {
            let rows = xs.chunks(sz).map(|c| Value::StrArray(c.to_vec())).collect();
            Ok(Value::MixedArray(rows))
        }
        Value::BoolArray(xs) => {
            let rows = xs.chunks(sz).map(|c| Value::BoolArray(c.to_vec())).collect();
            Ok(Value::MixedArray(rows))
        }
        Value::MixedArray(xs) => {
            // Preserve heterogeneity: chunk into MixedArray rows of original values.
            let rows = xs.chunks(sz).map(|c| Value::MixedArray(c.to_vec())).collect();
            Ok(Value::MixedArray(rows))
        }

        other => Err(format!("array:chunk => second argument must be an array, got {:?}", other)),
    }
}