array-mumu 0.2.0-rc.5

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

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

/// The main bridging function for `array:append`.
/// It now supports placeholders the same way as `math:pow`.
/// 
/// Usage examples:
///   array:append(item, array) => final if both arguments are real
///   array:append(_, array)    => partial usage => wait for item
///   array:append(item)        => partial usage => wait for array
///   array:append()            => partial usage => wait for item & array
pub fn array_append_bridge(
    _interp: &mut Interpreter,
    mut args: Vec<Value>
) -> Result<Value, String> {
    match args.len() {
        0 => {
            // No arguments => partial (item=?, list=?)
            Ok(make_append_partial(None, None))
        }
        1 => {
            // One argument => if it's `_`, item=? and list=? remain unknown
            let val = args.remove(0);
            if let Value::Placeholder = val {
                Ok(make_append_partial(None, None))
            } else {
                // item=val, array=?
                Ok(make_append_partial(Some(val), None))
            }
        }
        2 => {
            // Possibly final or partial
            let item_val  = args.remove(0);
            let list_val  = args.remove(0);

            let item_is_pl = matches!(item_val, Value::Placeholder);
            let list_is_pl = matches!(list_val, Value::Placeholder);

            if item_is_pl || list_is_pl {
                // partial usage
                let maybe_item = if item_is_pl { None } else { Some(item_val) };
                let maybe_list = if list_is_pl { None } else { Some(list_val) };
                Ok(make_append_partial(maybe_item, maybe_list))
            } else {
                // both real => do_append
                do_append(item_val, list_val)
            }
        }
        n => {
            Err(format!("array:append => expected up to 2 arguments, got {}", n))
        }
    }
}

/// Creates a partial function storing whichever arguments are known.
/// If both `item_opt` and `list_opt` become real, we do the final append.
fn make_append_partial(
    item_opt: Option<Value>,
    list_opt: Option<Value>,
) -> Value {
    use mumu::parser::types::FunctionValue::RustClosure;

    let closure = move |_interp: &mut Interpreter, new_args: Vec<Value>| {
        let mut item = item_opt.clone();
        let mut lst  = list_opt.clone();

        for arg in new_args {
            // If we haven't got an `item` yet:
            if item.is_none() {
                if let Value::Placeholder = arg {
                    // stay None
                } else {
                    item = Some(arg);
                }
                continue;
            }
            // If we haven't got a `list` yet:
            if lst.is_none() {
                if let Value::Placeholder = arg {
                    // stay None
                } else {
                    lst = Some(arg);
                }
                continue;
            }
            // If we get here, both item & list are already set => extra arg => error
            return Err("array:append => partial => too many arguments".to_string());
        }

        // Check if both are real => do_append or remain partial
        if item.is_some() && lst.is_some() {
            do_append(item.unwrap(), lst.unwrap())
        } else {
            // Return a further partial
            Ok(make_append_partial(item, lst))
        }
    };

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

/// Actually appends `item` to `list_val`.
/// - If `list_val` is an IntArray, then item must be Int.
/// - If `list_val` is StrArray, then item must be SingleString, etc.
/// - If `list_val` is SingleString, item must be SingleString => we do string concatenation.
fn do_append(item: Value, list_val: Value) -> Result<Value, String> {
    match list_val {
        Value::IntArray(xs) => match item {
            Value::Int(i) => {
                let mut new_xs = xs.clone();
                new_xs.push(i);
                Ok(Value::IntArray(new_xs))
            }
            _ => Err("array:append => cannot append non-Int to IntArray".to_string()),
        },
        Value::StrArray(ss) => match item {
            Value::SingleString(s) => {
                let mut new_ss = ss.clone();
                new_ss.push(s);
                Ok(Value::StrArray(new_ss))
            }
            _ => Err("array:append => cannot append non-SingleString to StrArray".to_string()),
        },
        Value::BoolArray(bb) => match item {
            Value::Bool(b) => {
                let mut new_bb = bb.clone();
                new_bb.push(b);
                Ok(Value::BoolArray(new_bb))
            }
            _ => Err("array:append => cannot append non-Bool to BoolArray".to_string()),
        },
        Value::SingleString(st) => match item {
            Value::SingleString(x) => {
                Ok(Value::SingleString(format!("{}{}", st, x)))
            }
            _ => Err("array:append => cannot append non-SingleString to SingleString".to_string()),
        },
        other => Err(format!("array:append => cannot append to {:?}", other)),
    }
}