array-mumu 0.2.0-rc.5

Array tools plugin for the Mumu ecosystem
Documentation
// FILE: array/src/search.rs

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

// Partial application helper, modeled after multi.rs

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

fn make_two_arg_partial(
    finalize_fn: fn(&mut Interpreter, Value, Value) -> Result<Value, String>,
    a_opt: Option<Value>,
    b_opt: Option<Value>
) -> Value {
    use FunctionValue::RustClosure;
    let closure = move |interp: &mut Interpreter, new_args: Vec<Value>| {
        let mut a = a_opt.clone();
        let mut b = b_opt.clone();
        for arg in new_args {
            if a.is_none() {
                if !is_placeholder(&arg) { a = Some(arg); }
                continue;
            }
            if b.is_none() {
                if !is_placeholder(&arg) { b = Some(arg); }
                continue;
            }
            return Err("Too many arguments for partial function".to_string());
        }
        if let (Some(aa), Some(bb)) = (a.clone(), b.clone()) {
            finalize_fn(interp, aa, bb)
        } else {
            Ok(make_two_arg_partial(finalize_fn, a, b))
        }
    };
    Value::Function(Box::new(RustClosure(
        "array-search-partial".to_string(),
        Arc::new(Mutex::new(closure)),
        0,
    )))
}

// ----------- FIND -----------

pub fn array_find_bridge(interp: &mut Interpreter, args: Vec<Value>) -> Result<Value, String> {
    match args.len() {
        0 => Ok(make_two_arg_partial(do_find, None, None)),
        1 => {
            if is_placeholder(&args[0]) {
                Ok(make_two_arg_partial(do_find, None, None))
            } else {
                Ok(make_two_arg_partial(do_find, Some(args[0].clone()), None))
            }
        }
        2 => {
            let a1_pl = is_placeholder(&args[0]);
            let a2_pl = is_placeholder(&args[1]);
            if !a1_pl && !a2_pl {
                do_find(interp, args[0].clone(), args[1].clone())
            } else {
                Ok(make_two_arg_partial(
                    do_find,
                    if a1_pl { None } else { Some(args[0].clone()) },
                    if a2_pl { None } else { Some(args[1].clone()) },
                ))
            }
        }
        n => Err(format!("array:find expects up to 2 arguments, got {}", n)),
    }
}

fn do_find(interp: &mut Interpreter, pred: Value, arr: Value) -> Result<Value, String> {
    let pred_fn = match pred {
        Value::Function(fb) => fb,
        _ => return Err("array:find => first argument must be a function".to_string()),
    };
    let iter = match arr {
        Value::IntArray(xs) => xs.into_iter().map(Value::Int).collect(),
        Value::FloatArray(xs) => xs.into_iter().map(Value::Float).collect(),
        Value::BoolArray(xs) => xs.into_iter().map(Value::Bool).collect(),
        Value::StrArray(xs) => xs.into_iter().map(Value::SingleString).collect(),
        Value::MixedArray(xs) => xs,
        _ => return Err("array:find => second argument must be an array".to_string()),
    };
    for item in iter {
        let res = apply_n_ary_function_value(interp, pred_fn.clone(), vec![item.clone()]);
        if let Ok(Value::Bool(true)) = res {
            return Ok(item);
        }
    }
    Ok(Value::Placeholder)
}

// ----------- FINDINDEX -----------

pub fn array_find_index_bridge(interp: &mut Interpreter, args: Vec<Value>) -> Result<Value, String> {
    match args.len() {
        0 => Ok(make_two_arg_partial(do_find_index, None, None)),
        1 => {
            if is_placeholder(&args[0]) {
                Ok(make_two_arg_partial(do_find_index, None, None))
            } else {
                Ok(make_two_arg_partial(do_find_index, Some(args[0].clone()), None))
            }
        }
        2 => {
            let a1_pl = is_placeholder(&args[0]);
            let a2_pl = is_placeholder(&args[1]);
            if !a1_pl && !a2_pl {
                do_find_index(interp, args[0].clone(), args[1].clone())
            } else {
                Ok(make_two_arg_partial(
                    do_find_index,
                    if a1_pl { None } else { Some(args[0].clone()) },
                    if a2_pl { None } else { Some(args[1].clone()) },
                ))
            }
        }
        n => Err(format!("array:findIndex expects up to 2 arguments, got {}", n)),
    }
}

fn do_find_index(interp: &mut Interpreter, pred: Value, arr: Value) -> Result<Value, String> {
    let pred_fn = match pred {
        Value::Function(fb) => fb,
        _ => return Err("array:findIndex => first argument must be a function".to_string()),
    };
    let iter = match arr {
        Value::IntArray(xs) => xs.into_iter().map(Value::Int).collect(),
        Value::FloatArray(xs) => xs.into_iter().map(Value::Float).collect(),
        Value::BoolArray(xs) => xs.into_iter().map(Value::Bool).collect(),
        Value::StrArray(xs) => xs.into_iter().map(Value::SingleString).collect(),
        Value::MixedArray(xs) => xs,
        _ => return Err("array:findIndex => second argument must be an array".to_string()),
    };
    for (idx, item) in iter.into_iter().enumerate() {
        let res = apply_n_ary_function_value(interp, pred_fn.clone(), vec![item]);
        if let Ok(Value::Bool(true)) = res {
            return Ok(Value::Int(idx as i32));
        }
    }
    Ok(Value::Int(-1))
}

// ----------- INCLUDES -----------

pub fn array_includes_bridge(_interp: &mut Interpreter, args: Vec<Value>) -> Result<Value, String> {
    match args.len() {
        0 => Ok(make_two_arg_partial(do_includes, None, None)),
        1 => Ok(make_two_arg_partial(do_includes, Some(args[0].clone()), None)),
        2 => {
            let a1_pl = is_placeholder(&args[0]);
            let a2_pl = is_placeholder(&args[1]);
            if !a1_pl && !a2_pl {
                do_includes(_interp, args[0].clone(), args[1].clone())
            } else {
                Ok(make_two_arg_partial(
                    do_includes,
                    if a1_pl { None } else { Some(args[0].clone()) },
                    if a2_pl { None } else { Some(args[1].clone()) },
                ))
            }
        }
        n => Err(format!("array:includes expects up to 2 arguments, got {}", n)),
    }
}

fn do_includes(_interp: &mut Interpreter, item: Value, arr: Value) -> Result<Value, String> {
    let iter = match arr {
        Value::IntArray(xs) => xs.into_iter().map(Value::Int).collect(),
        Value::FloatArray(xs) => xs.into_iter().map(Value::Float).collect(),
        Value::BoolArray(xs) => xs.into_iter().map(Value::Bool).collect(),
        Value::StrArray(xs) => xs.into_iter().map(Value::SingleString).collect(),
        Value::MixedArray(xs) => xs,
        _ => return Err("array:includes => second argument must be an array".to_string()),
    };
    for v in iter {
        if v == item {
            return Ok(Value::Bool(true));
        }
    }
    Ok(Value::Bool(false))
}

// ----------- EVERY / ALL -----------

pub fn array_every_bridge(interp: &mut Interpreter, args: Vec<Value>) -> Result<Value, String> {
    match args.len() {
        0 => Ok(make_two_arg_partial(do_every, None, None)),
        1 => {
            if is_placeholder(&args[0]) {
                Ok(make_two_arg_partial(do_every, None, None))
            } else {
                Ok(make_two_arg_partial(do_every, Some(args[0].clone()), None))
            }
        }
        2 => {
            let a1_pl = is_placeholder(&args[0]);
            let a2_pl = is_placeholder(&args[1]);
            if !a1_pl && !a2_pl {
                do_every(interp, args[0].clone(), args[1].clone())
            } else {
                Ok(make_two_arg_partial(
                    do_every,
                    if a1_pl { None } else { Some(args[0].clone()) },
                    if a2_pl { None } else { Some(args[1].clone()) },
                ))
            }
        }
        n => Err(format!("array:every expects up to 2 arguments, got {}", n)),
    }
}

fn do_every(interp: &mut Interpreter, pred: Value, arr: Value) -> Result<Value, String> {
    let pred_fn = match pred {
        Value::Function(fb) => fb,
        _ => return Err("array:every => first argument must be a function".to_string()),
    };
    let iter = match arr {
        Value::IntArray(xs) => xs.into_iter().map(Value::Int).collect(),
        Value::FloatArray(xs) => xs.into_iter().map(Value::Float).collect(),
        Value::BoolArray(xs) => xs.into_iter().map(Value::Bool).collect(),
        Value::StrArray(xs) => xs.into_iter().map(Value::SingleString).collect(),
        Value::MixedArray(xs) => xs,
        _ => return Err("array:every => second argument must be an array".to_string()),
    };
    for v in iter {
        let res = apply_n_ary_function_value(interp, pred_fn.clone(), vec![v]);
        if !matches!(res, Ok(Value::Bool(true))) {
            return Ok(Value::Bool(false));
        }
    }
    Ok(Value::Bool(true))
}

// ----------- SOME / ANY -----------

pub fn array_some_bridge(interp: &mut Interpreter, args: Vec<Value>) -> Result<Value, String> {
    match args.len() {
        0 => Ok(make_two_arg_partial(do_some, None, None)),
        1 => {
            if is_placeholder(&args[0]) {
                Ok(make_two_arg_partial(do_some, None, None))
            } else {
                Ok(make_two_arg_partial(do_some, Some(args[0].clone()), None))
            }
        }
        2 => {
            let a1_pl = is_placeholder(&args[0]);
            let a2_pl = is_placeholder(&args[1]);
            if !a1_pl && !a2_pl {
                do_some(interp, args[0].clone(), args[1].clone())
            } else {
                Ok(make_two_arg_partial(
                    do_some,
                    if a1_pl { None } else { Some(args[0].clone()) },
                    if a2_pl { None } else { Some(args[1].clone()) },
                ))
            }
        }
        n => Err(format!("array:some expects up to 2 arguments, got {}", n)),
    }
}

fn do_some(interp: &mut Interpreter, pred: Value, arr: Value) -> Result<Value, String> {
    let pred_fn = match pred {
        Value::Function(fb) => fb,
        _ => return Err("array:some => first argument must be a function".to_string()),
    };
    let iter = match arr {
        Value::IntArray(xs) => xs.into_iter().map(Value::Int).collect(),
        Value::FloatArray(xs) => xs.into_iter().map(Value::Float).collect(),
        Value::BoolArray(xs) => xs.into_iter().map(Value::Bool).collect(),
        Value::StrArray(xs) => xs.into_iter().map(Value::SingleString).collect(),
        Value::MixedArray(xs) => xs,
        _ => return Err("array:some => second argument must be an array".to_string()),
    };
    for v in iter {
        let res = apply_n_ary_function_value(interp, pred_fn.clone(), vec![v]);
        if matches!(res, Ok(Value::Bool(true))) {
            return Ok(Value::Bool(true));
        }
    }
    Ok(Value::Bool(false))
}