mumu-flow 0.1.0

Stream tranform tools plugin for the Lava language
Documentation
use std::sync::{Arc, Mutex};
use mumu::parser::interpreter::{Interpreter, DynamicFnInfo};
use mumu::parser::interpreter::apply_n_ary_function_value;
use mumu::parser::types::{Value, FunctionValue, InkIteratorKind};

pub fn register_flow_to_array(interp: &mut Interpreter) {
    let f = Arc::new(Mutex::new(flow_to_array_bridge_fn));
    let info = DynamicFnInfo::new(f, true);
    interp.register_dynamic_function_ex("flow:to_array", info);

    interp.set_variable(
        "flow:to_array",
        Value::Function(Box::new(FunctionValue::Named("flow:to_array".to_string())))
    );
}

fn flow_to_array_bridge_fn(interp: &mut Interpreter, mut args: Vec<Value>) -> Result<Value, String> {
    if args.len() != 1 {
        return Err(format!("flow:to_array => expected exactly 1 argument, got {}", args.len()));
    }

    let source_val = args.remove(0);
    match source_val {
        Value::InkIterator(handle) => {
            let mut output_values = Vec::new();
            match &handle.kind {
                InkIteratorKind::Core(state_arc) => {
                    loop {
                        let item_res = {
                            let mut guard = state_arc.lock().map_err(|_| "flow:to_array => InkIterator lock error".to_string())?;
                            if guard.done || guard.current >= guard.end {
                                None
                            } else {
                                let item = guard.current;
                                guard.current += 1;
                                Some(Value::Int(item as i32))
                            }
                        };
                        match item_res {
                            Some(v) => output_values.push(v),
                            None => break,
                        }
                    }
                    coerce_to_single_array(output_values)
                }
                InkIteratorKind::Plugin(plugin_arc) => {
                    let mut plugin = plugin_arc.lock().map_err(|_| "flow:to_array => plugin lock error".to_string())?;
                    loop {
                        match plugin.next_value() {
                            Ok(val) => output_values.push(val),
                            Err(e) if e == "NO_MORE_DATA" => break,
                            Err(e) => return Err(e),
                        }
                    }
                    coerce_to_single_array(output_values)
                }
            }
        }
        Value::InkTransform(fb) => {
            let mut output_values = Vec::new();
            loop {
                match apply_n_ary_function_value(interp, fb.clone(), vec![]) {
                    Ok(next_val) => output_values.push(next_val),
                    Err(e) => {
                        if e == "NO_MORE_DATA" {
                            break;
                        } else {
                            return Err(e);
                        }
                    }
                }
            }
            coerce_to_single_array(output_values)
        }
        other => Err(format!("flow:to_array => argument must be InkIterator or InkTransform, got {:?}", other)),
    }
}

fn coerce_to_single_array(items: Vec<Value>) -> Result<Value, String> {
    if items.is_empty() {
        return Ok(Value::IntArray(vec![]));
    }

    let all_int = items.iter().all(|v| matches!(v, Value::Int(_)));
    if all_int {
        let mut temp = Vec::with_capacity(items.len());
        for v in items {
            if let Value::Int(i) = v {
                temp.push(i);
            }
        }
        return Ok(Value::IntArray(temp));
    }

    let all_float = items.iter().all(|v| matches!(v, Value::Float(_)));
    if all_float {
        let mut temp = Vec::with_capacity(items.len());
        for v in items {
            if let Value::Float(f) = v {
                temp.push(f);
            }
        }
        return Ok(Value::FloatArray(temp));
    }

    let all_str = items.iter().all(|v| matches!(v, Value::SingleString(_)));
    if all_str {
        let mut temp = Vec::with_capacity(items.len());
        for v in items {
            if let Value::SingleString(s) = v {
                temp.push(s);
            }
        }
        return Ok(Value::StrArray(temp));
    }

    Err("flow:to_array => array has mixed/unhandled types; cannot unify".to_string())
}