mumu-event 0.1.1

event plugin for the mumu ecosystem
Documentation
// src/timeout.rs

// event/src/timeout.rs

use core_mumu::{
    parser::interpreter::apply::apply_n_ary_function_value,
    parser::types::{Value, FunctionValue},
    Interpreter,
};
use std::sync::{Arc, Mutex};
use std::time::{Duration, Instant};

/// Represents a single scheduled timeout.
#[derive(Clone)]
pub struct TimeoutTask {
    pub deadline: Instant,
    pub callback: Value, // The function to call once time is up
}

/// The bridging function that implements `event:timeout`.
/// - Usage: `event:timeout(ms, callback)` => schedule a timer
/// - Partial usage with placeholders:
///     event:timeout(3000, cb)
///     partial = event:timeout(3000)
///     partial(cb)
///     event:timeout(_, cb)(3000)
pub fn event_timeout_bridge(
    interp: &mut Interpreter,
    args: Vec<Value>
) -> Result<Value, String> {
    let verbose = interp.is_verbose();
    if verbose {
        eprintln!("[event:timeout] => event_timeout_bridge => got {} arg(s): {:?}", args.len(), args);
    }

    match args.len() {
        0 => {
            if verbose {
                eprintln!("[event:timeout] => zero args => returning partial => (ms=?, cb=?)");
            }
            Ok(make_partial(None, None, interp.is_verbose()))
        }
        1 => {
            let val = &args[0];
            if verbose {
                eprintln!("[event:timeout] => single arg => val={:?}", val);
            }
            match val {
                // If `_`, still missing both ms and cb
                Value::Placeholder => {
                    if verbose {
                        eprintln!("[event:timeout] => single arg is Placeholder => partial => (ms=?, cb=?)");
                    }
                    Ok(make_partial(None, None, verbose))
                }
                // If int => interpret it as ms => partial => (ms=val, cb=?)
                Value::Int(_) | Value::IntArray(_) => {
                    if verbose {
                        eprintln!("[event:timeout] => single arg is int => partial => ms known, cb=?");
                    }
                    Ok(make_partial(Some(val.clone()), None, verbose))
                }
                // If function => partial => (ms=?, cb=val)
                Value::Function(_) => {
                    if verbose {
                        eprintln!("[event:timeout] => single arg is Function => partial => ms=?, cb known");
                    }
                    Ok(make_partial(None, Some(val.clone()), verbose))
                }
                other => {
                    let msg = format!(
                        "event:timeout => single argument must be placeholder, int, or function => got: {:?}",
                        other
                    );
                    if verbose {
                        eprintln!("[event:timeout] => ERROR => {}", msg);
                    }
                    Err(msg)
                }
            }
        }
        2 => {
            let ms_val = &args[0];
            let cb_val = &args[1];
            let ms_is_pl = matches!(ms_val, Value::Placeholder);
            let cb_is_pl = matches!(cb_val, Value::Placeholder);

            if verbose {
                eprintln!(
                    "[event:timeout] => 2 args => ms={:?} (placeholder={}), cb={:?} (placeholder={})",
                    ms_val, ms_is_pl, cb_val, cb_is_pl
                );
            }

            if !ms_is_pl && !cb_is_pl {
                if verbose {
                    eprintln!("[event:timeout] => both real => scheduling now");
                }
                schedule_timeout(interp, ms_val.clone(), cb_val.clone())
            } else {
                if verbose {
                    eprintln!("[event:timeout] => partial usage => returning partial");
                }
                let maybe_ms = if ms_is_pl { None } else { Some(ms_val.clone()) };
                let maybe_cb = if cb_is_pl { None } else { Some(cb_val.clone()) };
                Ok(make_partial(maybe_ms, maybe_cb, verbose))
            }
        }
        n => {
            let msg = format!(
                "event:timeout => expected up to 2 arguments, got {}",
                n
            );
            if verbose {
                eprintln!("[event:timeout] => ERROR => {}", msg);
            }
            Err(msg)
        }
    }
}

/// Creates a partial function capturing the known `ms` or `cb`.
/// The closure will fill in whichever is still missing and eventually schedule the timeout.
fn make_partial(
    ms_opt: Option<Value>,
    cb_opt: Option<Value>,
    verbose: bool
) -> Value {
    Value::Function(Box::new(FunctionValue::RustClosure(
        "event:timeout-partial".to_string(),
        Arc::new(Mutex::new(move |interp: &mut Interpreter, new_args: Vec<Value>| {
            let mut ms_current = ms_opt.clone();
            let mut cb_current = cb_opt.clone();
            let local_verbose = interp.is_verbose() || verbose;

            if local_verbose {
                eprintln!(
                    "[event:timeout-partial] => invoked => got {} new_args => {:?}",
                    new_args.len(),
                    new_args
                );
                eprintln!(
                    "[event:timeout-partial] => so far => ms_current={:?}, cb_current={:?}",
                    ms_current, cb_current
                );
            }

            for arg in new_args {
                if ms_current.is_none() {
                    if matches!(arg, Value::Placeholder) {
                        if local_verbose {
                            eprintln!("[event:timeout-partial] => new arg is placeholder => ms remains None");
                        }
                    } else {
                        if local_verbose {
                            eprintln!(
                                "[event:timeout-partial] => filling ms_current with {:?}",
                                arg
                            );
                        }
                        ms_current = Some(arg);
                    }
                    continue;
                }
                if cb_current.is_none() {
                    if matches!(arg, Value::Placeholder) {
                        if local_verbose {
                            eprintln!(
                                "[event:timeout-partial] => new arg is placeholder => cb remains None"
                            );
                        }
                    } else {
                        if local_verbose {
                            eprintln!(
                                "[event:timeout-partial] => filling cb_current with {:?}",
                                arg
                            );
                        }
                        cb_current = Some(arg);
                    }
                    continue;
                }
                let msg = "event:timeout => partial => too many arguments".to_string();
                if local_verbose {
                    eprintln!("[event:timeout-partial] => ERROR => {}", msg);
                }
                return Err(msg);
            }

            if ms_current.is_some() && cb_current.is_some() {
                if local_verbose {
                    eprintln!("[event:timeout-partial] => both ms and cb known => scheduling now");
                }
                schedule_timeout(
                    interp,
                    ms_current.as_ref().unwrap().clone(),
                    cb_current.as_ref().unwrap().clone()
                )
            } else {
                if local_verbose {
                    eprintln!("[event:timeout-partial] => still partial => returning closure");
                }
                Ok(make_partial(ms_current, cb_current, local_verbose))
            }
        })),
        0,
    )))
}

/// Once we have a real (non-placeholder) `ms_val` and `cb_val`,
/// we parse the ms, store the callback, and push a new TimeoutTask.
fn schedule_timeout(
    interp: &mut Interpreter,
    ms_val: Value,
    cb_val: Value
) -> Result<Value, String> {
    let verbose = interp.is_verbose();
    if verbose {
        eprintln!(
            "[event:timeout] => schedule_timeout => ms_val={:?}, cb_val={:?}",
            ms_val, cb_val
        );
    }

    // parse ms => must be an int or single-element IntArray
    let ms_i32 = match ms_val {
        Value::Int(i) => i,
        Value::IntArray(ref arr) if arr.len() == 1 => arr[0],
        other => {
            let msg = format!("event:timeout => 'ms' must be an integer, got {:?}", other);
            if verbose {
                eprintln!("[event:timeout] => schedule_timeout => ERROR => {}", msg);
            }
            return Err(msg);
        }
    };
    if ms_i32 < 0 {
        let msg = "event:timeout => 'ms' must be >= 0".to_string();
        if verbose {
            eprintln!("[event:timeout] => schedule_timeout => ERROR => {}", msg);
        }
        return Err(msg);
    }

    // parse cb => must be a function
    let cb_func = match cb_val {
        Value::Function(_) => cb_val,
        other => {
            let msg = format!(
                "event:timeout => second argument must be a Function(...), got {:?}",
                other
            );
            if verbose {
                eprintln!("[event:timeout] => schedule_timeout => ERROR => {}", msg);
            }
            return Err(msg);
        }
    };

    // Create a TimeoutTask
    let now = Instant::now();
    let deadline = now + Duration::from_millis(ms_i32 as u64);

    if verbose {
        eprintln!(
            "[event:timeout] => schedule_timeout => now={:?}, deadline={:?}, ms_i32={}",
            now, deadline, ms_i32
        );
    }

    let task = TimeoutTask {
        deadline,
        callback: cb_func,
    };

    // Push the new task
    crate::push_timeout_task(task, interp);

    if verbose {
        eprintln!(
            "[event:timeout] => schedule_timeout => done => returning Bool(true)"
        );
    }
    Ok(Value::Bool(true))
}

/// Helper to invoke the callback with zero arguments, ignoring the result.
pub fn execute_callback(interp: &mut Interpreter, cb_val: &Value) -> Result<(), String> {
    let verbose = interp.is_verbose();
    if verbose {
        eprintln!(
            "[event:timeout] => execute_callback => about to call function with 0 args => val={:?}",
            cb_val
        );
    }

    if let Value::Function(cb_func) = cb_val {
        let result = apply_n_ary_function_value(interp, cb_func.clone(), vec![]);
        if let Err(e) = &result {
            if verbose {
                eprintln!(
                    "[event:timeout] => execute_callback => ERROR when calling callback => {}",
                    e
                );
            }
        }
        result.map(|_ignored| ())
    } else {
        let msg = "execute_callback => value is not a function".to_string();
        if verbose {
            eprintln!("[event:timeout] => execute_callback => ERROR => {}", msg);
        }
        Err(msg)
    }
}