array-mumu 0.2.0-rc.5

Array tools plugin for the Mumu ecosystem
Documentation
// src/prop.rs
//
// array:prop(key?, object?) → value OR partial
//
// Supports full and placeholder-based partial application:
//   • array:prop("k", obj)              → obj["k"] or _
//   • array:prop("k")                   → returns fn(obj) -> obj["k"]
//   • array:prop(_, obj)                → returns fn(key) -> obj[key]
//   • array:prop()                      → returns fn(key, obj) -> obj[key]
//
// Also used for `array:prop_partial` (same bridge).

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

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

fn key_from_value(v: &Value) -> Result<String, String> {
    match v {
        Value::SingleString(s) => Ok(s.clone()),
        Value::StrArray(a) if a.len() == 1 => Ok(a[0].clone()),
        other => Err(format!(
            "array:prop => key must be string or single-element StrArray, got {:?}",
            other
        )),
    }
}

fn do_prop(_interp: &mut Interpreter, key: Value, obj: Value) -> Result<Value, String> {
    let k = key_from_value(&key)?;
    match obj {
        Value::KeyedArray(map) => Ok(map.get(&k).cloned().unwrap_or(Value::Placeholder)),
        other => Err(format!(
            "array:prop => second argument must be a keyed array, got {:?}",
            other
        )),
    }
}

fn make_two_arg_partial(
    key_opt: Option<Value>,
    obj_opt: Option<Value>,
    label: &'static str,
) -> Value {
    use FunctionValue::RustClosure;
    let closure = move |interp: &mut Interpreter, new_args: Vec<Value>| {
        let mut k = key_opt.clone();
        let mut o = obj_opt.clone();

        for arg in new_args {
            if k.is_none() {
                if is_placeholder(&arg) {
                    // remain None -> still waiting for the key
                } else {
                    k = Some(arg);
                }
                continue;
            }
            if o.is_none() {
                if is_placeholder(&arg) {
                    // remain None -> still waiting for the object
                } else {
                    o = Some(arg);
                }
                continue;
            }
            return Err("array:prop => partial => too many arguments".to_string());
        }

        if let (Some(key_v), Some(obj_v)) = (k.clone(), o.clone()) {
            do_prop(interp, key_v, obj_v)
        } else {
            Ok(make_two_arg_partial(k, o, label))
        }
    };

    Value::Function(Box::new(RustClosure(
        label.to_string(),
        Arc::new(Mutex::new(closure)),
        0,
    )))
}

pub fn array_prop_bridge(_interp: &mut Interpreter, args: Vec<Value>) -> Result<Value, String> {
    match args.len() {
        0 => Ok(make_two_arg_partial(None, None, "array-prop-partial")),
        1 => {
            // Only key OR only object was provided; if it's a placeholder, keep both open.
            let a = &args[0];
            if is_placeholder(a) {
                Ok(make_two_arg_partial(None, None, "array-prop-partial"))
            } else if matches!(a, Value::SingleString(_) | Value::StrArray(_)) {
                // Key known → return fn(obj) -> obj[key]
                Ok(make_two_arg_partial(Some(a.clone()), None, "array-prop-partial"))
            } else {
                // Treat single non-key argument as the object → return fn(key) -> obj[key]
                Ok(make_two_arg_partial(None, Some(a.clone()), "array-prop-partial"))
            }
        }
        2 => {
            let key = &args[0];
            let obj = &args[1];
            let key_pl = is_placeholder(key);
            let obj_pl = is_placeholder(obj);

            if key_pl && obj_pl {
                return Ok(make_two_arg_partial(None, None, "array-prop-partial"));
            }
            if key_pl {
                return Ok(make_two_arg_partial(None, Some(obj.clone()), "array-prop-partial"));
            }
            if obj_pl {
                return Ok(make_two_arg_partial(Some(key.clone()), None, "array-prop-partial"));
            }

            // Both concrete → finalize now
            do_prop(_interp, key.clone(), obj.clone())
        }
        n => Err(format!(
            "array:prop expects up to 2 arguments (key?, object?), got {}",
            n
        )),
    }
}