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,
    InkIteratorHandle,
    InkIteratorKind,
};

pub fn register_flow_trans(interp: &mut Interpreter) {
    let bridging = Arc::new(Mutex::new(flow_trans_bridge_fn));
    let info = DynamicFnInfo::new(bridging, true);
    interp.register_dynamic_function_ex("flow:trans", info);

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

fn flow_trans_bridge_fn(_intp: &mut Interpreter, args: Vec<Value>) -> Result<Value, String> {
    match args.len() {
        0 => Ok(make_partial(None, None)),
        1 => {
            if is_placeholder(&args[0]) {
                Ok(make_partial(None, None))
            } else if let Value::Function(fb) = &args[0] {
                Ok(make_partial(Some(fb.clone()), None))
            } else {
                Err(format!("flow:trans => first arg must be Function or '_', got {:?}", args[0]))
            }
        }
        2 => {
            let first = &args[0];
            let second = &args[1];
            if !is_placeholder(first) && !is_placeholder(second) {
                let func_box = match first {
                    Value::Function(fb) => fb.clone(),
                    other => return Err(format!("flow:trans => first param must be a Function, got {:?}", other)),
                };
                match second {
                    Value::InkIterator(h) => Ok(build_transform_from_iter(func_box, h.clone())),
                    Value::InkTransform(tfb) => Ok(build_transform_chain(func_box, tfb.clone())),
                    other => Err(format!("flow:trans => second param must be InkIterator, InkTransform, or '_', got {:?}", other)),
                }
            } else {
                let maybe_func = if is_placeholder(first) {
                    None
                } else if let Value::Function(fb) = first {
                    Some(fb.clone())
                } else {
                    None
                };
                let maybe_source = if is_placeholder(second) {
                    None
                } else {
                    Some(second.clone())
                };
                if maybe_func.is_some() && maybe_source.is_some() {
                    let f = maybe_func.unwrap();
                    let s = maybe_source.unwrap();
                    match s {
                        Value::InkIterator(h) => return Ok(build_transform_from_iter(f, h)),
                        Value::InkTransform(tf) => return Ok(build_transform_chain(f, tf)),
                        other => return Err(format!("flow:trans => second param must be InkIterator/InkTransform or '_', got {:?}", other)),
                    }
                } else {
                    Ok(make_partial(maybe_func, maybe_source))
                }
            }
        }
        n => Err(format!("flow:trans => expected up to 2 arguments, got {}", n)),
    }
}

fn make_partial(func_opt: Option<Box<FunctionValue>>, source_opt: Option<Value>) -> Value {
    use mumu::parser::types::FunctionValue::RustClosure;
    let st = TransPartialState { func: func_opt, source: source_opt };

    Value::Function(Box::new(RustClosure(
        "flow:trans-partial".to_string(),
        Arc::new(Mutex::new(move |_intp: &mut Interpreter, new_args: Vec<Value>| {
            let mut f_cur = st.func.clone();
            let mut s_cur = st.source.clone();

            for arg in &new_args {
                if f_cur.is_none() {
                    if is_placeholder(arg) {
                        // remain None
                    } else if let Value::Function(fb) = arg {
                        f_cur = Some(fb.clone());
                    } else {
                        return Err(format!("flow:trans => first param must be Function or '_', got {:?}", arg));
                    }
                    continue;
                }
                if s_cur.is_none() {
                    if is_placeholder(arg) {
                        // remain None
                    } else {
                        s_cur = Some(arg.clone());
                    }
                    continue;
                }
                return Err("flow:trans => partial => too many arguments".to_string());
            }

            if let Some(fb) = f_cur.clone() {
                if let Some(src) = s_cur.clone() {
                    match src {
                        Value::InkIterator(h) => return Ok(build_transform_from_iter(fb, h)),
                        Value::InkTransform(tf) => return Ok(build_transform_chain(fb, tf)),
                        other => return Err(format!("flow:trans => second param must be InkIterator or InkTransform, got {:?}", other)),
                    }
                }
            }

            Ok(make_partial(f_cur, s_cur))
        })),
        0
    )))
}

fn build_transform_from_iter(func: Box<FunctionValue>, iter: InkIteratorHandle) -> Value {
    let env = Arc::new(TransEnvIter {
        user_func: func,
        source_iter: iter,
    });

    use mumu::parser::types::FunctionValue::RustClosure;
    let clos = RustClosure(
        "flow:InkTransform".to_string(),
        Arc::new(Mutex::new(move |intp: &mut Interpreter, _args: Vec<Value>| {
            match &env.source_iter.kind {
                InkIteratorKind::Core(state_arc) => {
                    let mut guard = state_arc.lock()
                        .map_err(|_| "flow:trans => InkIterator lock error".to_string())?;
                    if guard.done || guard.current >= guard.end {
                        guard.done = true;
                        return Err("NO_MORE_DATA".to_string());
                    }
                    let item_val = Value::Int(guard.current);
                    guard.current += 1;
                    if guard.current >= guard.end {
                        guard.done = true;
                    }
                    drop(guard);

                    let result = apply_n_ary_function_value(intp, env.user_func.clone(), vec![item_val])?;
                    Ok(result)
                }
                InkIteratorKind::Plugin(_) => {
                    Err("flow:trans => plugin iterators not supported yet".to_string())
                }
            }
        })),
        0
    );
    Value::InkTransform(Box::new(clos))
}

fn build_transform_chain(func: Box<FunctionValue>, prev_tf: Box<FunctionValue>) -> Value {
    let env = Arc::new(ChainEnv {
        user_func: func,
        prev_transform: prev_tf,
    });

    use mumu::parser::types::FunctionValue::RustClosure;
    let clos = RustClosure(
        "flow:InkTransformChain".to_string(),
        Arc::new(Mutex::new(move |intp: &mut Interpreter, _args: Vec<Value>| {
            let item = apply_n_ary_function_value(intp, env.prev_transform.clone(), vec![]);

            match item {
                Ok(val) => {
                    apply_n_ary_function_value(intp, env.user_func.clone(), vec![val])
                }
                Err(e) => Err(e),
            }
        })),
        0
    );
    Value::InkTransform(Box::new(clos))
}

#[derive(Clone)]
struct TransPartialState {
    func: Option<Box<FunctionValue>>,
    source: Option<Value>,
}

#[derive(Clone)]
struct TransEnvIter {
    user_func: Box<FunctionValue>,
    source_iter: InkIteratorHandle,
}

#[derive(Clone)]
struct ChainEnv {
    user_func: Box<FunctionValue>,
    prev_transform: Box<FunctionValue>,
}

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