array-mumu 0.2.0-rc.5

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

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

fn is_placeholder(v: &Value) -> bool {
    match v {
        Value::Placeholder => true,
        Value::SingleString(s) if s == "_" => true,
        Value::StrArray(ss) if ss.len() == 1 && ss[0] == "_" => true,
        _ => false,
    }
}

pub fn array_join_bridge(
    _interp: &mut Interpreter,
    args: Vec<Value>
) -> Result<Value, String> {
    match args.len() {
        0 => Ok(make_partial(None, None)),
        1 => {
            let a = &args[0];
            if is_placeholder(a) {
                Ok(make_partial(None, None))
            } else {
                Ok(make_partial(Some(a.clone()), None))
            }
        }
        2 => {
            let sep_is_pl = is_placeholder(&args[0]);
            let arr_is_pl = is_placeholder(&args[1]);
            if !sep_is_pl && !arr_is_pl {
                join_finalize(args[0].clone(), args[1].clone())
            } else {
                Ok(make_partial(
                    if sep_is_pl { None } else { Some(args[0].clone()) },
                    if arr_is_pl { None } else { Some(args[1].clone()) },
                ))
            }
        }
        n => Err(format!("array:join expects up to 2 arguments (separator, array), got {}", n)),
    }
}

fn make_partial(sep_opt: Option<Value>, arr_opt: Option<Value>) -> Value {
    let closure = move |_interp: &mut Interpreter, call_args: Vec<Value>| {
        let mut sep = sep_opt.clone();
        let mut arr = arr_opt.clone();

        for arg in call_args {
            if sep.is_none() {
                if is_placeholder(&arg) { /* remain None */ }
                else { sep = Some(arg); }
                continue;
            }
            if arr.is_none() {
                if is_placeholder(&arg) { /* remain None */ }
                else { arr = Some(arg); }
                continue;
            }
            return Err("array:join partial: too many arguments".to_string());
        }

        if sep.is_some() && arr.is_some() {
            join_finalize(sep.unwrap(), arr.unwrap())
        } else {
            Ok(make_partial(sep, arr))
        }
    };

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

fn join_finalize(sep_val: Value, arr_val: Value) -> Result<Value, String> {
    // Accept separator as SingleString or StrArray of len 1
    let sep = match sep_val {
        Value::SingleString(s) => s,
        Value::StrArray(ref arr) if arr.len() == 1 => arr[0].clone(),
        other => return Err(format!("array:join => first argument must be string separator, got {:?}", other)),
    };

    // Accept "array" argument as StrArray, SingleString, or MixedArray of strings
    match &arr_val {
        Value::StrArray(arr) if arr.is_empty() => return Ok(Value::SingleString("".to_string())),
        Value::MixedArray(arr) if arr.is_empty() => return Ok(Value::SingleString("".to_string())),
        _ => {}
    }

    match arr_val {
        Value::StrArray(arr) => Ok(Value::SingleString(arr.join(&sep))),
        Value::SingleString(s) => Ok(Value::SingleString(s)),
        Value::MixedArray(items) => {
            // Accept MixedArray if all elements are SingleString
            let mut strs = Vec::with_capacity(items.len());
            for v in items {
                match v {
                    Value::SingleString(s) => strs.push(s),
                    _ => return Err("array:join => second argument must be all strings (StrArray, string, or MixedArray of strings)".to_string()),
                }
            }
            Ok(Value::SingleString(strs.join(&sep)))
        }
        // Also support the degenerate case: joining an empty array (any type)
        Value::IntArray(arr) if arr.is_empty() => Ok(Value::SingleString("".to_string())),
        Value::BoolArray(arr) if arr.is_empty() => Ok(Value::SingleString("".to_string())),
        other => Err(format!("array:join => second argument must be StrArray, string, or MixedArray of strings, got {:?}", other)),
    }
}