array-mumu 0.2.0-rc.5

Array tools plugin for the Mumu ecosystem
Documentation
// array/src/key_eq.rs
//
// array:key_eq(value, key, obj) => bool
//   Returns true iff obj[key] exists and equals value (deep equality via Value::PartialEq).
//   Works like Ramda's propEq.
//   Supports partial & placeholder-based partial application.
//
// Examples:
//   hasBrownHair = array:key_eq("brown", "hair")
//   hasBrownHair(kids[1])  // -> true (if kids[1].hair == "brown")
//
//   expect = array:key_eq(_, "age", kid)   // wait for value, then test kid.age == value
//
// Key may be SingleString or StrArray([key]).
// Obj must be KeyedArray.

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),
        Value::StrArray(a) if a.len() == 1 => Ok(a[0].clone()),
        other => Err(format!(
            "array:key_eq => key must be string or single-element StrArray, got {:?}",
            other
        )),
    }
}

fn key_eq_finalize(expected: Value, key_val: Value, obj: Value) -> Result<Value, String> {
    let key = key_from_value(key_val)?;
    match obj {
        Value::KeyedArray(map) => {
            if let Some(found) = map.get(&key) {
                Ok(Value::Bool(found == &expected))
            } else {
                Ok(Value::Bool(false))
            }
        }
        other => Err(format!(
            "array:key_eq => third argument must be a keyed array, got {:?}",
            other
        )),
    }
}

fn make_three_arg_partial(
    expected_opt: Option<Value>,
    key_opt: Option<Value>,
    obj_opt: Option<Value>,
) -> Value {
    use FunctionValue::RustClosure;

    let closure = move |_interp: &mut Interpreter, new_args: Vec<Value>| {
        let mut ev = expected_opt.clone();
        let mut kv = key_opt.clone();
        let mut ov = obj_opt.clone();

        for arg in new_args {
            if ev.is_none() {
                if !is_placeholder(&arg) {
                    ev = Some(arg);
                }
                continue;
            }
            if kv.is_none() {
                if !is_placeholder(&arg) {
                    kv = Some(arg);
                }
                continue;
            }
            if ov.is_none() {
                if !is_placeholder(&arg) {
                    ov = Some(arg);
                }
                continue;
            }
            return Err("array:key_eq => partial => too many arguments".to_string());
        }

        if let (Some(e), Some(k), Some(o)) = (ev.clone(), kv.clone(), ov.clone()) {
            key_eq_finalize(e, k, o)
        } else {
            Ok(make_three_arg_partial(ev, kv, ov))
        }
    };

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

pub fn array_key_eq_bridge(_interp: &mut Interpreter, mut args: Vec<Value>) -> Result<Value, String> {
    match args.len() {
        0 => Ok(make_three_arg_partial(None, None, None)),

        1 => {
            let a = args.remove(0);
            if is_placeholder(&a) {
                Ok(make_three_arg_partial(None, None, None))
            } else {
                Ok(make_three_arg_partial(Some(a), None, None))
            }
        }

        2 => {
            let a = args.remove(0);
            let b = args.remove(0);
            Ok(make_three_arg_partial(
                if is_placeholder(&a) { None } else { Some(a) },
                if is_placeholder(&b) { None } else { Some(b) },
                None,
            ))
        }

        3 => {
            let e = args.remove(0);
            let k = args.remove(0);
            let o = args.remove(0);
            if is_placeholder(&e) || is_placeholder(&k) || is_placeholder(&o) {
                return Ok(make_three_arg_partial(
                    if is_placeholder(&e) { None } else { Some(e) },
                    if is_placeholder(&k) { None } else { Some(k) },
                    if is_placeholder(&o) { None } else { Some(o) },
                ));
            }
            key_eq_finalize(e, k, o)
        }

        n => Err(format!(
            "array:key_eq => expected up to 3 arguments (value, key, object), got {}",
            n
        )),
    }
}