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::types::{Value, FunctionValue};
use mumu::parser::interpreter::apply_n_ary_function_value;

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

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

fn flow_compose_bridge_fn(_intp: &mut Interpreter, args: Vec<Value>) -> Result<Value, String> {
    if args.is_empty() {
        Ok(make_compose_multi_partial(vec![]))
    } else {
        let mut placeholders_found = false;
        let mut all_fns = true;

        for v in &args {
            if is_placeholder(v) {
                placeholders_found = true;
            } else if !matches!(v, Value::Function(_)) {
                all_fns = false;
            }
        }

        if placeholders_found || !all_fns {
            Ok(make_compose_multi_partial(args))
        } else {
            Ok(build_final_composition(args))
        }
    }
}

fn make_compose_multi_partial(existing_args: Vec<Value>) -> Value {
    use mumu::parser::types::FunctionValue::RustClosure;

    let st = ComposeMultiPartialState { arg_list: existing_args };

    Value::Function(Box::new(RustClosure(
        "flow:compose-multi-partial".to_string(),
        Arc::new(Mutex::new(move |_intp: &mut Interpreter, new_args: Vec<Value>| {
            let mut combined = st.arg_list.clone();
            for na in &new_args {
                combined.push(na.clone());
            }

            let mut placeholders_found = false;
            let mut all_fns = true;
            for v in &combined {
                if is_placeholder(v) {
                    placeholders_found = true;
                } else if !matches!(v, Value::Function(_)) {
                    all_fns = false;
                }
            }

            if placeholders_found || !all_fns {
                Ok(make_compose_multi_partial(combined))
            } else {
                Ok(build_final_composition(combined))
            }
        })),
        0
    )))
}

fn build_final_composition(fn_args: Vec<Value>) -> Value {
    let chain_env = Arc::new(ComposeMultiEnv { chain: fn_args });

    make_multi_final(chain_env)
}

fn make_multi_final(chain_env: Arc<ComposeMultiEnv>) -> Value {
    use mumu::parser::types::FunctionValue::RustClosure;

    let clos = RustClosure(
        "flow:compose-final-nary".to_string(),
        Arc::new(Mutex::new(move |_intp: &mut Interpreter, incoming: Vec<Value>| {
            if incoming.is_empty() {
                return Ok(make_multi_final(Arc::clone(&chain_env)));
            }
            if incoming.len() > 1 {
                return Err(format!("flow:compose => final => got {} args, expected 0 or 1", incoming.len()));
            }
            let data_val = incoming[0].clone();
            let mut cur_val = data_val;
            for funcv in chain_env.chain.iter().rev() {
                if let Value::Function(fb) = funcv {
                    cur_val = apply_n_ary_function_value(_intp, fb.clone(), vec![cur_val])?;
                } else {
                    return Err(format!("flow:compose => in final chain, got non-function: {:?}", funcv));
                }
            }
            Ok(cur_val)
        })),
        0
    );
    Value::Function(Box::new(clos))
}

#[derive(Clone)]
struct ComposeMultiEnv {
    chain: Vec<Value>,
}

#[derive(Clone)]
struct ComposeMultiPartialState {
    arg_list: Vec<Value>,
}

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