#![no_std]
#![forbid(unsafe_code)]
//! # gnucobol-rs-exec
//!
//! Procedure Division **control-flow combinators** with cobc-faithful iteration semantics — the part of an
//! executor that is byte-deterministic and provable. gnucobol-rs is deliberately *not* a Procedure Division
//! executor; the `GNURUST.PROCEDURE.FLOW.ATLAS.1` court *observed* what each control statement does, and this
//! crate encodes those observed semantics as `no_std` combinators (pinned in tests to the court's counts:
//! `PERFORM 3 TIMES` → 3, `PERFORM VARYING 1 BY 1 UNTIL > 4` → body 4×, index ends at 5).
//!
//! Condition evaluation (`IF`, `EVALUATE`, `LEVEL-88`) is supplied by the caller — typically composing the
//! oracle-proven `gnucobol-rs` core — so these combinators stay pure and `no_std`.
//!
//! ```
//! let mut n = 0;
//! gnucobol_rs_exec::perform_times(3, || n += 1);
//! assert_eq!(n, 3);
//! assert_eq!(gnucobol_rs_exec::perform_varying(1, 1, 4, |_| {}), 5); // index ends past the bound
//! ```

/// `PERFORM n TIMES` — run `body` exactly `n` times.
pub fn perform_times(n: usize, mut body: impl FnMut()) {
    for _ in 0..n {
        body();
    }
}

/// `PERFORM UNTIL cond` (the default `WITH TEST BEFORE`) — evaluate `cond` *before* each iteration; run
/// `body` while it is false. Returns the number of iterations performed.
pub fn perform_until(mut cond: impl FnMut() -> bool, mut body: impl FnMut()) -> usize {
    let mut n = 0;
    while !cond() {
        body();
        n += 1;
    }
    n
}

/// `PERFORM ... WITH TEST AFTER UNTIL cond` — run `body`, *then* test; always runs at least once. Returns
/// the number of iterations performed.
pub fn perform_until_after(mut cond: impl FnMut() -> bool, mut body: impl FnMut()) -> usize {
    let mut n = 0;
    loop {
        body();
        n += 1;
        if cond() {
            break;
        }
    }
    n
}

/// `PERFORM VARYING idx FROM `from` BY `by` UNTIL idx > `until_gt`` — run `body(idx)` for each value while
/// `idx <= until_gt`, incrementing by `by`. Returns the FINAL index value, which per cobc is the first
/// value that failed the `UNTIL` test (e.g. `FROM 1 BY 1 UNTIL > 4` runs body for 1..=4 and returns 5).
pub fn perform_varying(from: i64, by: i64, until_gt: i64, mut body: impl FnMut(i64)) -> i64 {
    let mut i = from;
    while i <= until_gt {
        body(i);
        i += by;
    }
    i
}

/// `EVALUATE` — return the 0-based index of the first `WHEN` arm whose guard is true, or `None` for
/// `WHEN OTHER` / no match. (The guards are evaluated left to right; the caller builds them, typically over
/// the oracle-proven core comparisons.)
pub fn evaluate(arms: &[bool]) -> Option<usize> {
    arms.iter().position(|&b| b)
}

#[cfg(test)]
mod tests {
    use super::*;
    use core::cell::Cell;

    #[test]
    fn perform_times_matches_court_count() {
        let n = Cell::new(0);
        perform_times(3, || n.set(n.get() + 1)); // PROCEDURE.FLOW: perform_times = 3
        assert_eq!(n.get(), 3);
    }

    #[test]
    fn perform_varying_body_count_and_end_value() {
        let body = Cell::new(0);
        // PERFORM VARYING I FROM 1 BY 1 UNTIL I > 4
        let end = perform_varying(1, 1, 4, |_| body.set(body.get() + 1));
        assert_eq!(body.get(), 4); // varying_body = 004
        assert_eq!(end, 5); // varying_ends = 005
    }

    #[test]
    fn perform_until_is_test_before_after_distinct() {
        // already-true condition: TEST BEFORE runs 0 times, TEST AFTER runs once.
        assert_eq!(perform_until(|| true, || {}), 0);
        assert_eq!(perform_until_after(|| true, || {}), 1);
        // count down from 5 to 0: 5 iterations either way.
        let c = Cell::new(5);
        assert_eq!(perform_until(|| c.get() == 0, || c.set(c.get() - 1)), 5);
    }

    #[test]
    fn evaluate_picks_the_first_true_arm() {
        assert_eq!(evaluate(&[false, true, true]), Some(1)); // eval = branch 2 (1-based) = index 1
        assert_eq!(evaluate(&[false, false]), None); // WHEN OTHER
    }
}
