iocaine 2.2.0

The deadliest poison known to AI
Documentation
// SPDX-FileCopyrightText: 2025 Gergely Nagy
// SPDX-FileContributor: Gergely Nagy
//
// SPDX-License-Identifier: MIT

use minijinja::{
    Error, ErrorKind, State,
    value::{Kwargs, Value},
};
use rand_pcg::Pcg64;
use rand_seeder::Seeder;

#[derive(Debug)]
pub struct GobbledyGook {
    pub generator: Pcg64,
    pub min: u32,
    pub max: u32,
}

impl GobbledyGook {
    pub fn from(state: &State, options: &Kwargs, fallback_group: &str) -> Result<Self, Error> {
        let group = options
            .get::<Option<String>>("group")?
            .unwrap_or_else(|| fallback_group.to_owned());

        let temp = &format!("rng_pos_{group}");
        let counter = state
            .get_temp(temp)
            .unwrap_or_else(|| Value::from(0usize))
            .as_usize()
            .ok_or_else(|| Error::new(ErrorKind::CannotDeserialize, temp.to_owned()))?;

        let pos = options.get::<Option<usize>>("pos")?.unwrap_or(counter);
        if pos >= counter {
            state.set_temp(temp, Value::from(pos + 1));
        }

        let static_seed = state
            .lookup("static_seed")
            .ok_or_else(|| Error::new(ErrorKind::CannotDeserialize, "static_seed"))?;
        let static_seed = static_seed
            .as_str()
            .ok_or_else(|| Error::new(ErrorKind::CannotDeserialize, "static_seed"))?;

        let rng = Seeder::from(format!("iocaine://{static_seed}/{group}#{pos}")).into_rng();

        Ok(Self {
            generator: rng,
            min: options.get::<Option<u32>>("min")?.unwrap_or(1),
            max: options.get("max")?,
        })
    }
}

#[cfg(test)]
mod test {
    use super::*;
    use minijinja::{
        Environment, State,
        value::{Kwargs, Value},
    };
    use rand::Rng;

    fn prepare(state: &State, options: Vec<(&str, Value)>) -> GobbledyGook {
        let options = Kwargs::from_iter(options);

        GobbledyGook::from(state, &options, "test").unwrap()
    }

    #[test]
    fn test_pos_auto_advance() {
        let mut env = Environment::new();
        env.add_global("static_seed", Value::from(""));
        let state = env.empty_state();

        let mut rng1 = prepare(&state, [("max", Value::from(42))].to_vec());
        let n1 = rng1.generator.random_range(1..=42);

        let mut rng2 = prepare(&state, [("max", Value::from(42))].to_vec());
        let n2 = rng2.generator.random_range(1..=42);

        assert_ne!(n1, n2);
    }

    #[test]
    fn test_pos_explicit_same() {
        let mut env = Environment::new();
        env.add_global("static_seed", Value::from(""));
        let state = env.empty_state();

        let mut rng1 = prepare(
            &state,
            [("max", Value::from(42)), ("pos", Value::from(69))].to_vec(),
        );
        let n1 = rng1.generator.random_range(1..=42);

        let mut rng2 = prepare(
            &state,
            [("max", Value::from(42)), ("pos", Value::from(69))].to_vec(),
        );
        let n2 = rng2.generator.random_range(1..=42);

        assert_eq!(n1, n2);
    }

    #[test]
    fn test_pos_explicit_different() {
        let mut env = Environment::new();
        env.add_global("static_seed", Value::from(""));
        let state = env.empty_state();

        let mut rng1 = prepare(
            &state,
            [("max", Value::from(42)), ("pos", Value::from(69))].to_vec(),
        );
        let n1 = rng1.generator.random_range(1..=42);

        let mut rng2 = prepare(
            &state,
            [("max", Value::from(42)), ("pos", Value::from(42))].to_vec(),
        );
        let n2 = rng2.generator.random_range(1..=42);

        assert_ne!(n1, n2);
    }

    #[test]
    fn test_pos_mixed_lookback() {
        let mut env = Environment::new();
        env.add_global("static_seed", Value::from(""));
        let state = env.empty_state();

        let mut rng1 = prepare(&state, [("max", Value::from(42))].to_vec());
        let n1 = rng1.generator.random_range(1..=42);

        let mut rng2 = prepare(
            &state,
            [("max", Value::from(42)), ("pos", Value::from(0))].to_vec(),
        );
        let n2 = rng2.generator.random_range(1..=42);

        let mut rng3 = prepare(&state, [("max", Value::from(42))].to_vec());
        let n3 = rng3.generator.random_range(1..=42);

        assert_eq!(n1, n2);
        assert_ne!(n3, n1);
        assert_ne!(n3, n2);
    }

    #[test]
    fn test_pos_mixed_current() {
        let mut env = Environment::new();
        env.add_global("static_seed", Value::from(""));
        let state = env.empty_state();

        let mut rng1 = prepare(
            &state,
            [("max", Value::from(42)), ("pos", Value::from(0))].to_vec(),
        );
        let n1 = rng1.generator.random_range(1..=42);

        let mut rng2 = prepare(&state, [("max", Value::from(42))].to_vec());
        let n2 = rng2.generator.random_range(1..=42);

        let mut rng3 = prepare(&state, [("max", Value::from(42))].to_vec());
        let n3 = rng3.generator.random_range(1..=42);

        assert_ne!(n1, n2);
        assert_ne!(n3, n1);
        assert_ne!(n3, n2);
    }

    #[test]
    fn test_pos_mixed_lookahead() {
        let mut env = Environment::new();
        env.add_global("static_seed", Value::from(""));
        let state = env.empty_state();

        let mut rng1 = prepare(
            &state,
            [("max", Value::from(42)), ("pos", Value::from(1))].to_vec(),
        );
        let n1 = rng1.generator.random_range(1..=42);

        let mut rng2 = prepare(&state, [("max", Value::from(42))].to_vec());
        let n2 = rng2.generator.random_range(1..=42);

        let mut rng3 = prepare(&state, [("max", Value::from(42))].to_vec());
        let n3 = rng3.generator.random_range(1..=42);

        assert_ne!(n1, n2);
        assert_ne!(n2, n3);
        assert_ne!(n1, n3);
    }
}