array-mumu 0.2.0-rc.5

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

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

/// `array:nth` bridging function with placeholder-based partial usage.
/// - We have 2 parameters: (index, data).
/// - 0 arguments => partial => (index=?, data=?)
/// - 1 argument => if `_`, still missing both; otherwise treat it as `index`
/// - 2 arguments => if no placeholders => do_nth_final; else partial
///
/// The final call returns the nth element of an array or `_` if out of range.
pub fn array_nth_bridge(
    _interp: &mut Interpreter,
    args: Vec<Value>
) -> Result<Value, String> {
    match args.len() {
        // No arguments => partial => (index=?, data=?)
        0 => Ok(make_two_arg_partial(None, None)),

        // One argument => either `_` or a numeric index
        1 => {
            let first = &args[0];
            match first {
                Value::Placeholder => {
                    // index=?, data=?
                    Ok(make_two_arg_partial(None, None))
                }
                val => {
                    // We'll treat it as the index; data=? remains unknown
                    Ok(make_two_arg_partial(Some(val.clone()), None))
                }
            }
        }

        // Two arguments => might be final or partial if placeholders
        2 => {
            let index_val = &args[0];
            let data_val  = &args[1];

            let i_is_pl = matches!(index_val, Value::Placeholder);
            let d_is_pl = matches!(data_val, Value::Placeholder);

            if !i_is_pl && !d_is_pl {
                // no placeholders => do_nth_final
                do_nth_final(index_val.clone(), data_val.clone())
            } else {
                // partial usage
                let maybe_idx = if i_is_pl { None } else { Some(index_val.clone()) };
                let maybe_dat = if d_is_pl { None } else { Some(data_val.clone()) };
                Ok(make_two_arg_partial(maybe_idx, maybe_dat))
            }
        }

        n => Err(format!("array:nth => expected up to 2 arguments, got {}", n)),
    }
}

/// Once we have both index and data as real arguments (no placeholders), do nth logic.
fn do_nth_final(
    index_val: Value,
    data_val: Value
) -> Result<Value, String> {
    // The index must be an i32 or single-element IntArray
    let idx_i32 = match index_val {
        Value::Int(i) => i,
        Value::IntArray(ref arr) if arr.len() == 1 => arr[0],
        other => {
            return Err(format!(
                "array:nth => index must be int or single-element IntArray, got {:?}",
                other
            ))
        }
    };

    match data_val {
        Value::IntArray(xs) => nth_from_int_array(xs, idx_i32),
        Value::StrArray(ss) => nth_from_str_array(ss, idx_i32),
        Value::BoolArray(bb) => nth_from_bool_array(bb, idx_i32),

        // NEW: handle float arrays
        Value::FloatArray(ffs) => nth_from_float_array(ffs, idx_i32),

        other => Err(format!(
            "array:nth => second argument must be an array, got {:?}",
            other
        )),
    }
}

/// Return nth element from an int[] or `_` if out of range.
fn nth_from_int_array(xs: Vec<i32>, idx: i32) -> Result<Value, String> {
    let len = xs.len() as i32;
    let real = if idx < 0 { len + idx } else { idx };
    if real < 0 || real >= len {
        Ok(Value::Placeholder)
    } else {
        Ok(Value::Int(xs[real as usize]))
    }
}

/// Return nth element from a str[] or `_` if out of range.
fn nth_from_str_array(ss: Vec<String>, idx: i32) -> Result<Value, String> {
    let len = ss.len() as i32;
    let real = if idx < 0 { len + idx } else { idx };
    if real < 0 || real >= len {
        Ok(Value::Placeholder)
    } else {
        Ok(Value::SingleString(ss[real as usize].clone()))
    }
}

/// Return nth element from a bool[] or `_` if out of range.
fn nth_from_bool_array(bb: Vec<bool>, idx: i32) -> Result<Value, String> {
    let len = bb.len() as i32;
    let real = if idx < 0 { len + idx } else { idx };
    if real < 0 || real >= len {
        Ok(Value::Placeholder)
    } else {
        Ok(Value::Bool(bb[real as usize]))
    }
}

/// NEW: return nth element from a float[] or `_` if out of range.
fn nth_from_float_array(ffs: Vec<f64>, idx: i32) -> Result<Value, String> {
    let len = ffs.len() as i32;
    let real = if idx < 0 { len + idx } else { idx };
    if real < 0 || real >= len {
        Ok(Value::Placeholder)
    } else {
        Ok(Value::Float(ffs[real as usize]))
    }
}

/// Build a partial closure capturing (index, data). Additional calls fill whichever is missing,
/// eventually calling `do_nth_final`.
fn make_two_arg_partial(
    index_opt: Option<Value>,
    data_opt: Option<Value>
) -> Value {
    Value::Function(Box::new(FunctionValue::RustClosure(
        "array:nth-partial".to_string(),
        Arc::new(Mutex::new(move |_interp: &mut Interpreter, new_args: Vec<Value>| {
            let mut i_current = index_opt.clone();
            let mut d_current = data_opt.clone();

            for arg in new_args {
                // fill index first
                if i_current.is_none() {
                    if matches!(arg, Value::Placeholder) {
                        // remain None
                    } else {
                        i_current = Some(arg);
                    }
                    continue;
                }
                // fill data next
                if d_current.is_none() {
                    if matches!(arg, Value::Placeholder) {
                        // remain None
                    } else {
                        d_current = Some(arg);
                    }
                    continue;
                }
                return Err("array:nth => partial => too many arguments".to_string());
            }

            // if both known => do_nth_final
            if i_current.is_some() && d_current.is_some() {
                do_nth_final(
                    i_current.as_ref().unwrap().clone(),
                    d_current.as_ref().unwrap().clone()
                )
            } else {
                Ok(make_two_arg_partial(i_current, d_current))
            }
        })),
        0,
    )))
}