blue-lang-syntax 0.0.134

blue's surface syntax: lexer, parser, and lowering to the tatara-lisp quoted form.
Documentation
use std::num::NonZeroUsize;

pub const ENV: &str = "BLUE_TEST_STACK";

pub const DEFAULT_MIB: NonZeroUsize = NonZeroUsize::new(64).unwrap();

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Stack {
    Inherit,
    Mib(NonZeroUsize),
}

impl Stack {
    pub fn from_setting(setting: Option<&str>) -> Result<Self, String> {
        match setting.map(str::trim) {
            None | Some("") => Ok(Self::Mib(DEFAULT_MIB)),
            Some("inherit") => Ok(Self::Inherit),
            Some(v) => v.parse::<NonZeroUsize>().map(Self::Mib).map_err(|_| {
                format!("{ENV} is {v:?}; it takes inherit, or a whole number of MiB above 0")
            }),
        }
    }

    pub fn configured() -> Self {
        let setting = match std::env::var(ENV) {
            Ok(v) => Some(v),
            Err(std::env::VarError::NotPresent) => None,
            Err(std::env::VarError::NotUnicode(v)) => {
                panic!("{ENV} is {v:?}; it takes inherit, or a whole number of MiB above 0")
            }
        };
        Self::from_setting(setting.as_deref()).unwrap_or_else(|why| panic!("{why}"))
    }
}

pub fn run<T: Send>(body: impl FnOnce() -> T + Send) -> T {
    run_on(Stack::configured(), body)
}

pub fn run_on<T: Send>(stack: Stack, body: impl FnOnce() -> T + Send) -> T {
    let Stack::Mib(mib) = stack else {
        return body();
    };
    let name = std::thread::current()
        .name()
        .unwrap_or("blue-test-stack")
        .to_string();
    std::thread::scope(|scope| {
        std::thread::Builder::new()
            .name(name)
            .stack_size(mib.get() << 20)
            .spawn_scoped(scope, body)
            .unwrap_or_else(|e| panic!("could not start a thread with a {mib} MiB stack: {e}"))
            .join()
            .unwrap_or_else(|payload| std::panic::resume_unwind(payload))
    })
}

#[cfg(test)]
mod tests {
    use super::{run_on, Stack, DEFAULT_MIB};
    use std::num::NonZeroUsize;

    #[test]
    fn the_setting_is_the_default_inherit_or_a_size_and_nothing_else() {
        assert_eq!(Stack::from_setting(None), Ok(Stack::Mib(DEFAULT_MIB)));
        assert_eq!(Stack::from_setting(Some("")), Ok(Stack::Mib(DEFAULT_MIB)));
        assert_eq!(Stack::from_setting(Some("inherit")), Ok(Stack::Inherit));
        assert_eq!(
            Stack::from_setting(Some("16")),
            Ok(Stack::Mib(NonZeroUsize::new(16).unwrap()))
        );
        for bad in ["0", "-1", "off", "16MiB", "1.5"] {
            let why = Stack::from_setting(Some(bad)).expect_err(bad);
            assert!(
                why.contains("BLUE_TEST_STACK") && why.contains("inherit"),
                "{why}"
            );
        }
    }

    #[test]
    fn a_sized_stack_runs_the_body_on_its_own_thread_named_for_the_caller() {
        let caller = std::thread::current().id();
        let caller_name = std::thread::current().name().map(str::to_string);
        let (id, name) = run_on(Stack::Mib(NonZeroUsize::new(4).unwrap()), || {
            (
                std::thread::current().id(),
                std::thread::current().name().map(str::to_string),
            )
        });
        assert_ne!(id, caller);
        assert_eq!(name, caller_name);
    }

    #[test]
    fn inherit_runs_the_body_on_the_calling_thread() {
        let caller = std::thread::current().id();
        assert_eq!(
            run_on(Stack::Inherit, || std::thread::current().id()),
            caller
        );
    }

    #[test]
    fn a_panic_in_the_body_reaches_the_caller_with_its_message() {
        let caught = std::panic::catch_unwind(|| {
            run_on(Stack::Mib(DEFAULT_MIB), || panic!("the body's own words"))
        })
        .expect_err("the panic must propagate");
        let text = caught
            .downcast_ref::<&str>()
            .map(|s| s.to_string())
            .or_else(|| caught.downcast_ref::<String>().cloned());
        assert_eq!(text.as_deref(), Some("the body's own words"));
    }

    #[test]
    fn a_body_deeper_than_the_harness_stack_completes_on_the_default_stack() {
        fn depth(n: u64) -> u64 {
            let pad = std::hint::black_box([n; 64]);
            if n == 0 {
                0
            } else {
                pad[0].min(1) + depth(n - 1)
            }
        }
        assert_eq!(
            run_on(Stack::Mib(DEFAULT_MIB), || depth(std::hint::black_box(
                20_000
            ))),
            20_000
        );
    }
}