flow-mumu 0.1.4

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

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

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

fn flow_concat_bridge(_interp: &mut Interpreter, args: Vec<Value>) -> Result<Value, String> {
    match args.len() {
        0 => Ok(make_partial(None, None)),
        1 => {
            let a = &args[0];
            if is_placeholder(a) {
                Ok(make_partial(None, None))
            } else {
                Ok(make_partial(Some(a.clone()), None))
            }
        }
        2 => {
            let a = &args[0];
            let b = &args[1];
            let a_pl = is_placeholder(a);
            let b_pl = is_placeholder(b);
            if !a_pl && !b_pl {
                concat_finalize(a.clone(), b.clone())
            } else {
                Ok(make_partial(
                    if a_pl { None } else { Some(a.clone()) },
                    if b_pl { None } else { Some(b.clone()) },
                ))
            }
        }
        n => Err(format!("flow:concat => expected up to 2 arguments, got {}", n)),
    }
}

fn make_partial(a_opt: Option<Value>, b_opt: Option<Value>) -> Value {
    use mumu::parser::types::FunctionValue::RustClosure;

    Value::Function(Box::new(RustClosure(
        "flow:concat-partial".to_string(),
        Arc::new(Mutex::new(move |_interp: &mut Interpreter, new_args: Vec<Value>| {
            let mut a = a_opt.clone();
            let mut b = b_opt.clone();
            for v in new_args {
                if a.is_none() {
                    if !is_placeholder(&v) { a = Some(v); }
                    continue;
                }
                if b.is_none() {
                    if !is_placeholder(&v) { b = Some(v); }
                    continue;
                }
                return Err("flow:concat => partial => too many arguments".to_string());
            }
            if a.is_some() && b.is_some() {
                concat_finalize(a.unwrap(), b.unwrap())
            } else {
                Ok(make_partial(a, b))
            }
        })),
        0,
    )))
}

fn concat_finalize(a: Value, b: Value) -> Result<Value, String> {
    let tf_a = to_transform(a)?;
    let tf_b = to_transform(b)?;

    use mumu::parser::types::FunctionValue::RustClosure;
    let state = Arc::new(Mutex::new(ConcatState {
        first_done: false,
        first: tf_a,
        second: tf_b,
    }));

    let clos = RustClosure(
        "flow:concat".to_string(),
        Arc::new(Mutex::new(move |interp: &mut Interpreter, _args: Vec<Value>| {
            let mut st = state.lock().map_err(|_| "flow:concat => state lock".to_string())?;

            if !st.first_done {
                let res = apply_n_ary_function_value(interp, st.first.clone(), vec![]);
                match res {
                    Ok(v) => return Ok(v),
                    Err(e) if e == "NO_MORE_DATA" => {
                        st.first_done = true;
                    }
                    Err(e) => return Err(e),
                }
            }

            let res2 = apply_n_ary_function_value(interp, st.second.clone(), vec![]);
            match res2 {
                Ok(v) => Ok(v),
                Err(e) => Err(e),
            }
        })),
        0,
    );

    // Return a zero-arg transform function (no dedicated InkTransform type anymore)
    Ok(Value::Function(Box::new(clos)))
}

struct ConcatState {
    first_done: bool,
    first: Box<FunctionValue>,
    second: Box<FunctionValue>,
}