use rand::rngs::StdRng; use rand::SeedableRng; use cagire::model::forth::{Dictionary, Forth, Rng, StepContext, Value, Variables}; use std::collections::HashMap; use std::sync::{Arc, Mutex}; pub fn default_ctx() -> StepContext { StepContext { step: 0, beat: 0.0, pattern: 0, tempo: 120.0, phase: 0.0, slot: 0, runs: 0, iter: 0, speed: 1.0, fill: false, } } pub fn ctx_with(f: impl FnOnce(&mut StepContext)) -> StepContext { let mut ctx = default_ctx(); f(&mut ctx); ctx } pub fn new_vars() -> Variables { Arc::new(Mutex::new(HashMap::new())) } pub fn new_dict() -> Dictionary { Arc::new(Mutex::new(HashMap::new())) } pub fn seeded_rng(seed: u64) -> Rng { Arc::new(Mutex::new(StdRng::seed_from_u64(seed))) } pub fn forth() -> Forth { Forth::new(new_vars(), new_dict(), seeded_rng(42)) } pub fn forth_seeded(seed: u64) -> Forth { Forth::new(new_vars(), new_dict(), seeded_rng(seed)) } pub fn run(script: &str) -> Forth { let f = forth(); f.evaluate(script, &default_ctx()).unwrap(); f } pub fn run_ctx(script: &str, ctx: &StepContext) -> Forth { let f = forth(); f.evaluate(script, ctx).unwrap(); f } pub fn stack_top(f: &Forth) -> Value { f.stack().pop().expect("stack empty") } pub fn stack_int(f: &Forth) -> i64 { match stack_top(f) { Value::Int(i, _) => i, other => panic!("expected Int, got {:?}", other), } } pub fn stack_float(f: &Forth) -> f64 { match stack_top(f) { Value::Float(x, _) => x, Value::Int(i, _) => i as f64, other => panic!("expected number, got {:?}", other), } } pub fn expect_stack(script: &str, expected: &[Value]) { let f = run(script); let stack = f.stack(); assert_eq!(stack, expected, "script: {}", script); } pub fn expect_int(script: &str, expected: i64) { expect_stack(script, &[Value::Int(expected, None)]); } pub fn expect_float(script: &str, expected: f64) { let f = run(script); let stack = f.stack(); assert_eq!(stack.len(), 1, "expected single value on stack"); let val = stack_float(&f); assert!( (val - expected).abs() < 1e-9, "expected {}, got {}", expected, val ); } pub fn expect_floats_close(script: &str, expected: f64, epsilon: f64) { let f = run(script); let val = stack_float(&f); assert!( (val - expected).abs() < epsilon, "expected ~{}, got {}", expected, val ); } pub fn expect_error(script: &str, expected_substr: &str) { let f = forth(); let result = f.evaluate(script, &default_ctx()); assert!(result.is_err(), "expected error for '{}'", script); let err = result.unwrap_err(); assert!( err.contains(expected_substr), "error '{}' does not contain '{}'", err, expected_substr ); } pub fn expect_outputs(script: &str, count: usize) -> Vec { let f = forth(); let outputs = f.evaluate(script, &default_ctx()).unwrap(); assert_eq!(outputs.len(), count, "expected {} outputs", count); outputs } #[allow(dead_code)] pub fn expect_output_contains(script: &str, substr: &str) { let outputs = expect_outputs(script, 1); assert!( outputs[0].contains(substr), "output '{}' does not contain '{}'", outputs[0], substr ); }