flow-mumu 0.1.4

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

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

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

fn flow_slice_bridge_fn(_interp: &mut Interpreter, args: Vec<Value>) -> Result<Value, String> {
    make_slice_partial(args)
}

fn make_slice_partial(existing_args: Vec<Value>) -> Result<Value, String> {
    use mumu::parser::types::FunctionValue::RustClosure;

    let store = Arc::new(SlicePartialState { arg_list: existing_args });

    let clos = RustClosure(
        "flow:slice-partial".to_string(),
        Arc::new(Mutex::new(move |interp: &mut Interpreter, new_args: Vec<Value>| {
            let mut combined = store.arg_list.clone();
            for na in &new_args {
                combined.push(na.clone());
            }
            finalize_or_partial(interp, combined)
        })),
        0
    );

    Ok(Value::Function(Box::new(clos)))
}

fn finalize_or_partial(_interp: &mut Interpreter, combined: Vec<Value>) -> Result<Value, String> {
    if combined.len() < 3 {
        return make_slice_partial(combined);
    }
    if combined.len() > 3 {
        return Err(format!("flow:slice => expected up to 3 arguments, got {}", combined.len()));
    }

    let skip_val = &combined[0];
    let count_val = &combined[1];
    let source_val = &combined[2];

    if is_placeholder(skip_val) || is_placeholder(count_val) || is_placeholder(source_val) {
        return make_slice_partial(combined);
    }

    let skip_int = match skip_val {
        Value::Int(i) => *i,
        _ => return Err(format!("flow:slice => 'skip' must be int or placeholder, got {:?}", skip_val)),
    };
    let count_int = match count_val {
        Value::Int(i) => *i,
        _ => return Err(format!("flow:slice => 'count' must be int or placeholder, got {:?}", count_val)),
    };

    match source_val {
        Value::Iterator(h) => Ok(Value::Function(build_slice_transform(
            skip_int,
            count_int,
            SourceKind::Iterator(h.clone()),
        ))),
        Value::Function(fb) => Ok(Value::Function(build_slice_transform(
            skip_int,
            count_int,
            SourceKind::Transform(fb.clone()),
        ))),
        _ => Err(format!(
            "flow:slice => third arg must be Iterator or transform Function, got {:?}",
            source_val
        )),
    }
}

enum SourceKind {
    Iterator(IteratorHandle),
    Transform(Box<FunctionValue>),
}

fn build_slice_transform(skip: i32, count: i32, source: SourceKind) -> Box<FunctionValue> {
    use mumu::parser::types::FunctionValue::RustClosure;

    let data = Arc::new(Mutex::new(SliceData {
        skip_so_far: 0,
        taken_so_far: 0,
        skip_limit: skip.max(0),
        take_limit: count.max(0),
        source_kind: source,
        done: false,
    }));

    Box::new(RustClosure(
        "flow:slice-transform".to_string(),
        Arc::new(Mutex::new(move |interp: &mut Interpreter, _args: Vec<Value>| {
            let mut locked = data.lock().map_err(|_| "flow:slice => lock error".to_string())?;
            if locked.done || locked.taken_so_far >= locked.take_limit {
                return Err("NO_MORE_DATA".to_string());
            }

            // Skip phase
            while locked.skip_so_far < locked.skip_limit {
                let reached_end = match &mut locked.source_kind {
                    SourceKind::Iterator(handle) => {
                        match &mut handle.kind {
                            IteratorKind::Core(state_arc) => {
                                let mut g = state_arc
                                    .lock()
                                    .map_err(|_| "flow:slice => Iterator lock error".to_string())?;
                                if g.done || g.current >= g.end {
                                    true
                                } else {
                                    g.current += 1;
                                    if g.current >= g.end {
                                        g.done = true;
                                    }
                                    false
                                }
                            }
                            IteratorKind::Plugin(plugin_arc) => {
                                let mut plugin = plugin_arc
                                    .lock()
                                    .map_err(|_| "flow:slice => plugin lock error".to_string())?;
                                match plugin.next_value() {
                                    Ok(_) => false,
                                    Err(e) if e == "NO_MORE_DATA" => true,
                                    Err(e) => return Err(e),
                                }
                            }
                        }
                    }
                    SourceKind::Transform(fb) => {
                        match apply_n_ary_function_value(interp, fb.clone(), vec![]) {
                            Ok(_) => false,
                            Err(e) if e == "NO_MORE_DATA" => true,
                            Err(e) => return Err(e),
                        }
                    }
                };

                if reached_end {
                    locked.done = true;
                    return Err("NO_MORE_DATA".to_string());
                }

                locked.skip_so_far += 1;
            }

            // Take next value
            let next_val = match &mut locked.source_kind {
                SourceKind::Iterator(handle) => match &mut handle.kind {
                    IteratorKind::Core(state_arc) => {
                        let mut g = state_arc
                            .lock()
                            .map_err(|_| "flow:slice => Iterator lock error".to_string())?;
                        if g.done || g.current >= g.end {
                            None
                        } else {
                            let cur_item = g.current;
                            g.current += 1;
                            if g.current >= g.end {
                                g.done = true;
                            }
                            Some(Value::Int(cur_item))
                        }
                    }
                    IteratorKind::Plugin(plugin_arc) => {
                        let mut plugin = plugin_arc
                            .lock()
                            .map_err(|_| "flow:slice => plugin lock error".to_string())?;
                        match plugin.next_value() {
                            Ok(v) => Some(v),
                            Err(e) if e == "NO_MORE_DATA" => None,
                            Err(e) => return Err(e),
                        }
                    }
                },
                SourceKind::Transform(fb) => {
                    match apply_n_ary_function_value(interp, fb.clone(), vec![]) {
                        Ok(v) => Some(v),
                        Err(e) if e == "NO_MORE_DATA" => None,
                        Err(e) => return Err(e),
                    }
                }
            };

            match next_val {
                None => {
                    locked.done = true;
                    Err("NO_MORE_DATA".to_string())
                }
                Some(val) => {
                    locked.taken_so_far += 1;
                    if locked.taken_so_far >= locked.take_limit {
                        locked.done = true;
                    }
                    Ok(val)
                }
            }
        })),
        0,
    ))
}

struct SliceData {
    skip_so_far: i32,
    taken_so_far: i32,
    skip_limit: i32,
    take_limit: i32,
    source_kind: SourceKind,
    done: bool,
}

struct SlicePartialState {
    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,
    }
}