netidx-bscript 0.28.2

An incremental scripting language for netidx
Documentation
use crate::{
    deftype, expr::Expr, stdfn::CachedVals, Apply, BuiltIn, BuiltInInitFn, Ctx, Event,
    ExecCtx, Node, UserEvent,
};
use netidx::subscriber::Value;
use netidx_netproto::valarray::ValArray;
use rand::{rng, seq::SliceRandom, Rng};
use smallvec::{smallvec, SmallVec};
use std::sync::Arc;

#[derive(Debug)]
struct Rand {
    args: CachedVals,
}

impl<C: Ctx, E: UserEvent> BuiltIn<C, E> for Rand {
    const NAME: &str = "rand";
    deftype!("rand", "fn<'a: [Int, Float]>(?#start:'a, ?#end:'a, #trigger:Any) -> 'a");

    fn init(_: &mut ExecCtx<C, E>) -> BuiltInInitFn<C, E> {
        Arc::new(|_, _, _, from, _| Ok(Box::new(Rand { args: CachedVals::new(from) })))
    }
}

impl<C: Ctx, E: UserEvent> Apply<C, E> for Rand {
    fn update(
        &mut self,
        ctx: &mut ExecCtx<C, E>,
        from: &mut [Node<C, E>],
        event: &mut Event<E>,
    ) -> Option<Value> {
        macro_rules! gen_cases {
            ($start:expr, $end:expr, $($typ:ident),+) => {
                match ($start, $end) {
                    $(
                        (Value::$typ(start), Value::$typ(end)) if start < end => {
                            Some(Value::$typ(rng().random_range(*start..*end)))
                        }
                    ),+
                    _ => None
                }
            };
        }
        let up = self.args.update(ctx, from, event);
        if up {
            match &self.args.0[..] {
                [Some(start), Some(end), Some(_)] => gen_cases!(
                    start, end, F32, F64, I32, I64, Z32, Z64, U32, U64, V32, V64
                ),
                _ => None,
            }
        } else {
            None
        }
    }
}

#[derive(Debug)]
struct Pick;

impl<C: Ctx, E: UserEvent> BuiltIn<C, E> for Pick {
    const NAME: &str = "rand_pick";
    deftype!("rand", "fn(Array<'a>) -> 'a");

    fn init(_: &mut ExecCtx<C, E>) -> BuiltInInitFn<C, E> {
        Arc::new(|_, _, _, _, _| Ok(Box::new(Pick)))
    }
}

impl<C: Ctx, E: UserEvent> Apply<C, E> for Pick {
    fn update(
        &mut self,
        ctx: &mut ExecCtx<C, E>,
        from: &mut [Node<C, E>],
        event: &mut Event<E>,
    ) -> Option<Value> {
        from[0].update(ctx, event).and_then(|a| match a {
            Value::Array(a) if a.len() > 0 => {
                Some(a[rng().random_range(0..a.len())].clone())
            }
            _ => None,
        })
    }
}

#[derive(Debug)]
struct Shuffle(SmallVec<[Value; 32]>);

impl<C: Ctx, E: UserEvent> BuiltIn<C, E> for Shuffle {
    const NAME: &str = "rand_shuffle";
    deftype!("rand", "fn(Array<'a>) -> Array<'a>");

    fn init(_: &mut ExecCtx<C, E>) -> BuiltInInitFn<C, E> {
        Arc::new(|_, _, _, _, _| Ok(Box::new(Shuffle(smallvec![]))))
    }
}

impl<C: Ctx, E: UserEvent> Apply<C, E> for Shuffle {
    fn update(
        &mut self,
        ctx: &mut ExecCtx<C, E>,
        from: &mut [Node<C, E>],
        event: &mut Event<E>,
    ) -> Option<Value> {
        from[0].update(ctx, event).and_then(|a| match a {
            Value::Array(a) => {
                self.0.extend(a.iter().cloned());
                self.0.shuffle(&mut rng());
                Some(Value::Array(ValArray::from_iter_exact(self.0.drain(..))))
            }
            _ => None,
        })
    }
}

const MOD: &str = r#"
pub mod rand {
    /// generate a random number between #start and #end (exclusive)
    /// every time #trigger updates. If start and end are not specified,
    /// they default to 0.0 and 1.0
    pub let rand = |#start = 0.0, #end = 1.0, #trigger| 'rand;

    /// pick a random element from the array and return it. Update
    /// each time the array updates. If the array is empty return
    /// nothing.
    pub let pick = |a| 'rand_pick;

    /// return a shuffled copy of a
    pub let shuffle = |a| 'rand_shuffle
}
"#;

pub fn register<C: Ctx, E: UserEvent>(ctx: &mut ExecCtx<C, E>) -> Expr {
    ctx.register_builtin::<Rand>().unwrap();
    ctx.register_builtin::<Pick>().unwrap();
    ctx.register_builtin::<Shuffle>().unwrap();
    MOD.parse().unwrap()
}