From 009d68087de3aa0354008ef8429cc11a1e137f04 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rapha=C3=ABl=20Forment?= Date: Mon, 23 Feb 2026 00:51:01 +0100 Subject: [PATCH] Fix: revert optimizations --- crates/forth/Cargo.toml | 7 -- crates/forth/benches/forth_vm.rs | 201 ------------------------------- crates/forth/src/types.rs | 1 + crates/forth/src/vm.rs | 111 +++++++---------- src/app/scripting.rs | 2 +- src/engine/sequencer.rs | 6 +- src/input/main_page.rs | 35 +++--- src/model/script.rs | 4 +- src/services/stack_preview.rs | 2 +- tests/forth/definitions.rs | 12 +- tests/forth/errors.rs | 2 +- tests/forth/harness.rs | 10 +- tests/forth/list_words.rs | 2 +- tests/forth/midi.rs | 8 +- tests/forth/quotations.rs | 10 +- tests/forth/ramps.rs | 36 +++--- tests/forth/randomness.rs | 42 +++---- tests/forth/stack.rs | 6 +- tests/forth/temporal.rs | 8 +- tests/forth/variables.rs | 6 +- 20 files changed, 134 insertions(+), 377 deletions(-) delete mode 100644 crates/forth/benches/forth_vm.rs diff --git a/crates/forth/Cargo.toml b/crates/forth/Cargo.toml index 6e19892..7e65ef9 100644 --- a/crates/forth/Cargo.toml +++ b/crates/forth/Cargo.toml @@ -15,10 +15,3 @@ desktop = [] rand = "0.8" parking_lot = "0.12" arc-swap = "1" - -[dev-dependencies] -criterion = { version = "0.5", features = ["html_reports"] } - -[[bench]] -name = "forth_vm" -harness = false diff --git a/crates/forth/benches/forth_vm.rs b/crates/forth/benches/forth_vm.rs deleted file mode 100644 index 10b3947..0000000 --- a/crates/forth/benches/forth_vm.rs +++ /dev/null @@ -1,201 +0,0 @@ -use arc_swap::ArcSwap; -use cagire_forth::{Dictionary, ExecutionTrace, Forth, Rng, StepContext, Variables}; -use criterion::{black_box, criterion_group, criterion_main, Criterion}; -use parking_lot::Mutex; -use rand::rngs::StdRng; -use rand::SeedableRng; -use std::collections::HashMap; -use std::sync::Arc; - -fn new_vars() -> Variables { - Arc::new(ArcSwap::from_pointee(HashMap::new())) -} - -fn new_dict() -> Dictionary { - Arc::new(Mutex::new(HashMap::new())) -} - -fn new_rng() -> Rng { - Arc::new(Mutex::new(StdRng::seed_from_u64(42))) -} - -fn new_forth() -> Forth { - Forth::new(new_vars(), new_dict(), new_rng()) -} - -fn ctx() -> StepContext<'static> { - StepContext { - step: 3, - beat: 7.5, - bank: 0, - pattern: 1, - tempo: 120.0, - phase: 0.75, - slot: 0, - runs: 5, - iter: 5, - speed: 1.0, - fill: false, - nudge_secs: 0.0, - cc_access: None, - speed_key: "__speed_0_1__", - mouse_x: 0.5, - mouse_y: 0.5, - mouse_down: 0.0, - } -} - -const SCRIPTS: &[(&str, &str)] = &[ - ("trivial", "60"), - ("drum_hit", "\"kick\" s ."), - ("synth_params", "\"sine\" s 60 note 0.5 amp 2000 lpf ."), - ("cycle_notes", "{ 60 } { 64 } { 67 } 3 cycle note"), - ( - "conditional", - "step 2 mod 0 = if \"kick\" s . else \"hat\" s . then", - ), - ("variables", "beat 4 mod \"myvar\" !"), - ( - "multi_emit", - "\"kick\" s . \"hat\" s 0.7 amp . \"snare\" s 0.5 amp .", - ), - ( - "complex", - "{ 60 } { 64 } { 67 } { 72 } 4 cycle note \"sine\" s 0.01 0.1 0.7 0.3 adsr 2000 lpf .", - ), -]; - -fn bench_evaluate(c: &mut Criterion) { - let mut group = c.benchmark_group("evaluate"); - let step_ctx = ctx(); - - for &(name, script) in SCRIPTS { - group.bench_function(name, |b| { - let mut forth = new_forth(); - b.iter(|| { - let _ = black_box(forth.evaluate(black_box(script), &step_ctx)); - forth.clear_stack(); - }); - }); - } - group.finish(); -} - -fn bench_evaluate_with_trace(c: &mut Criterion) { - let mut group = c.benchmark_group("evaluate_with_trace"); - let step_ctx = ctx(); - - for &(name, script) in SCRIPTS { - group.bench_function(name, |b| { - let mut forth = new_forth(); - let mut trace = ExecutionTrace::default(); - b.iter(|| { - trace.executed_spans.clear(); - trace.selected_spans.clear(); - trace.resolved.clear(); - let _ = black_box(forth.evaluate_with_trace(black_box(script), &step_ctx, &mut trace)); - forth.clear_stack(); - }); - }); - } - group.finish(); -} - -fn bench_trace_overhead(c: &mut Criterion) { - let mut group = c.benchmark_group("trace_overhead"); - let step_ctx = ctx(); - let script = "{ 60 } { 64 } { 67 } { 72 } 4 cycle note \"sine\" s 0.01 0.1 0.7 0.3 adsr 2000 lpf ."; - - group.bench_function("no_trace", |b| { - let mut forth = new_forth(); - b.iter(|| { - let _ = black_box(forth.evaluate(black_box(script), &step_ctx)); - forth.clear_stack(); - }); - }); - - group.bench_function("with_trace", |b| { - let mut forth = new_forth(); - let mut trace = ExecutionTrace::default(); - b.iter(|| { - trace.executed_spans.clear(); - trace.selected_spans.clear(); - trace.resolved.clear(); - let _ = black_box(forth.evaluate_with_trace(black_box(script), &step_ctx, &mut trace)); - forth.clear_stack(); - }); - }); - group.finish(); -} - -fn bench_tick_simulation(c: &mut Criterion) { - let mut group = c.benchmark_group("tick_simulation"); - - let patterns: &[(&str, &str)] = &[ - ("drum_pattern", "\"kick\" s ."), - ("melodic_pattern", "{ 60 } { 64 } { 67 } { 72 } 4 cycle note \"sine\" s 0.5 amp ."), - ("generative_pattern", "1 8 rand note \"pluck\" s 0.3 0.8 rand amp ."), - ("conditional_pattern", "step 4 mod 0 = if \"kick\" s . else step 2 mod 0 = if \"hat\" s 0.5 amp . then then"), - ]; - - for &(name, script) in patterns { - group.bench_function(name, |b| { - let mut forth = new_forth(); - let step_ctx = ctx(); - let mut trace = ExecutionTrace::default(); - b.iter(|| { - trace.executed_spans.clear(); - trace.selected_spans.clear(); - trace.resolved.clear(); - let _ = black_box(forth.evaluate_with_trace(black_box(script), &step_ctx, &mut trace)); - forth.clear_stack(); - }); - }); - } - group.finish(); -} - -fn bench_throughput(c: &mut Criterion) { - let mut group = c.benchmark_group("throughput"); - - let medium_scripts: &[&str] = &[ - "\"kick\" s .", - "\"hat\" s 0.5 amp .", - "\"snare\" s 0.8 amp 1000 lpf .", - "{ 60 } { 64 } { 67 } 3 cycle note \"sine\" s .", - "\"bass\" s 36 note 0.7 amp .", - "{ 48 } { 55 } { 60 } { 64 } 4 cycle note \"pad\" s 0.3 amp 800 lpf .", - "\"pluck\" s 72 note 0.4 amp 3000 lpf .", - "step 4 mod 0 = if \"kick\" s . then", - ]; - - group.bench_function("8_patterns_one_step", |b| { - let mut forths: Vec = (0..8).map(|_| new_forth()).collect(); - let step_ctx = ctx(); - let mut trace = ExecutionTrace::default(); - b.iter(|| { - for (i, forth) in forths.iter_mut().enumerate() { - trace.executed_spans.clear(); - trace.selected_spans.clear(); - trace.resolved.clear(); - let _ = black_box(forth.evaluate_with_trace( - black_box(medium_scripts[i]), - &step_ctx, - &mut trace, - )); - forth.clear_stack(); - } - }); - }); - group.finish(); -} - -criterion_group!( - benches, - bench_evaluate, - bench_evaluate_with_trace, - bench_trace_overhead, - bench_tick_simulation, - bench_throughput, -); -criterion_main!(benches); diff --git a/crates/forth/src/types.rs b/crates/forth/src/types.rs index a68fe02..82003dc 100644 --- a/crates/forth/src/types.rs +++ b/crates/forth/src/types.rs @@ -76,6 +76,7 @@ pub type VariablesMap = HashMap; pub type Variables = Arc>; pub type Dictionary = Arc>>>; pub type Rng = Arc>; +pub type Stack = Mutex>; pub(super) type CmdSnapshot<'a> = (Option<&'a Value>, &'a [(&'static str, Value)]); #[derive(Clone, Debug)] diff --git a/crates/forth/src/vm.rs b/crates/forth/src/vm.rs index 7d764ae..ecad6a6 100644 --- a/crates/forth/src/vm.rs +++ b/crates/forth/src/vm.rs @@ -1,5 +1,6 @@ //! Stack-based Forth interpreter with audio command generation. +use parking_lot::Mutex; use rand::rngs::StdRng; use rand::{Rng as RngTrait, SeedableRng}; use std::borrow::Cow; @@ -9,45 +10,43 @@ use std::sync::Arc; use super::compiler::compile_script; use super::ops::Op; use super::types::{ - CmdRegister, Dictionary, ExecutionTrace, ResolvedValue, Rng, SourceSpan, StepContext, Value, - Variables, VariablesMap, + CmdRegister, Dictionary, ExecutionTrace, ResolvedValue, Rng, SourceSpan, Stack, StepContext, + Value, Variables, VariablesMap, }; pub struct Forth { - stack: Vec, + stack: Stack, vars: Variables, dict: Dictionary, rng: Rng, - cache: HashMap>, } impl Forth { pub fn new(vars: Variables, dict: Dictionary, rng: Rng) -> Self { Self { - stack: Vec::new(), + stack: Mutex::new(Vec::new()), vars, dict, rng, - cache: HashMap::new(), } } pub fn stack(&self) -> Vec { - self.stack.clone() + self.stack.lock().clone() } - pub fn clear_stack(&mut self) { - self.stack.clear(); + pub fn clear_stack(&self) { + self.stack.lock().clear(); } - pub fn evaluate(&mut self, script: &str, ctx: &StepContext) -> Result, String> { + pub fn evaluate(&self, script: &str, ctx: &StepContext) -> Result, String> { let (outputs, var_writes) = self.evaluate_impl(script, ctx, None)?; self.apply_var_writes(var_writes); Ok(outputs) } pub fn evaluate_with_trace( - &mut self, + &self, script: &str, ctx: &StepContext, trace: &mut ExecutionTrace, @@ -58,7 +57,7 @@ impl Forth { } pub fn evaluate_raw( - &mut self, + &self, script: &str, ctx: &StepContext, trace: &mut ExecutionTrace, @@ -78,7 +77,7 @@ impl Forth { } fn evaluate_impl( - &mut self, + &self, script: &str, ctx: &StepContext, trace: Option<&mut ExecutionTrace>, @@ -87,35 +86,26 @@ impl Forth { return Err("empty script".into()); } - let ops = if let Some(cached) = self.cache.get(script) { - Arc::clone(cached) - } else { - let compiled = compile_script(script, &self.dict)?; - let ops: Arc<[Op]> = Arc::from(compiled); - self.cache.insert(script.to_owned(), Arc::clone(&ops)); - ops - }; + let ops = compile_script(script, &self.dict)?; self.execute(&ops, ctx, trace) } fn execute( - &mut self, + &self, ops: &[Op], ctx: &StepContext, trace: Option<&mut ExecutionTrace>, ) -> Result<(Vec, HashMap), String> { + let mut stack = self.stack.lock(); let mut outputs: Vec = Vec::with_capacity(8); let mut cmd = CmdRegister::new(); let vars_snapshot = self.vars.load_full(); - let mut var_writes: Vec<(String, Value)> = Vec::new(); + let mut var_writes: HashMap = HashMap::new(); - Self::execute_ops( - &self.rng, - &self.dict, - &mut self.cache, + self.execute_ops( ops, ctx, - &mut self.stack, + &mut stack, &mut outputs, &mut cmd, trace, @@ -123,15 +113,13 @@ impl Forth { &mut var_writes, )?; - let var_map: HashMap = var_writes.into_iter().collect(); - Ok((outputs, var_map)) + Ok((outputs, var_writes)) } #[allow(clippy::too_many_arguments)] + #[allow(clippy::only_used_in_recursion)] fn execute_ops( - rng: &Rng, - dict: &Dictionary, - cache: &mut HashMap>, + &self, ops: &[Op], ctx: &StepContext, stack: &mut Vec, @@ -139,14 +127,11 @@ impl Forth { cmd: &mut CmdRegister, trace: Option<&mut ExecutionTrace>, vars_snapshot: &VariablesMap, - var_writes: &mut Vec<(String, Value)>, + var_writes: &mut HashMap, ) -> Result<(), String> { let mut pc = 0; let trace_cell = std::cell::RefCell::new(trace); let var_writes_cell = std::cell::RefCell::new(Some(var_writes)); - let rng_ref = rng; - let dict_ref = dict; - let cache_cell = std::cell::RefCell::new(Some(cache)); let run_quotation = |quot: Value, stack: &mut Vec, @@ -163,12 +148,7 @@ impl Forth { let mut trace_opt = trace_cell.borrow_mut().take(); let mut var_writes_guard = var_writes_cell.borrow_mut(); let vw = var_writes_guard.as_mut().expect("var_writes taken"); - let mut cache_guard = cache_cell.borrow_mut(); - let c = cache_guard.as_mut().expect("cache taken"); - Self::execute_ops( - rng_ref, - dict_ref, - c, + self.execute_ops( "_ops, ctx, stack, @@ -178,7 +158,6 @@ impl Forth { vars_snapshot, vw, )?; - drop(cache_guard); drop(var_writes_guard); *trace_cell.borrow_mut() = trace_opt; Ok(()) @@ -376,9 +355,9 @@ impl Forth { ensure(stack, count)?; let start = stack.len() - count; let slice = &mut stack[start..]; - let mut rng_guard = rng.lock(); + let mut rng = self.rng.lock(); for i in (1..slice.len()).rev() { - let j = rng_guard.gen_range(0..=i); + let j = rng.gen_range(0..=i); slice.swap(i, j); } } @@ -641,10 +620,7 @@ impl Forth { let vw = var_writes_cell.borrow(); let vw_ref = vw.as_ref().expect("var_writes taken"); let val = vw_ref - .iter() - .rev() - .find(|(k, _)| k == name) - .map(|(_, v)| v) + .get(name) .or_else(|| vars_snapshot.get(name)) .cloned() .unwrap_or(Value::Int(0, None)); @@ -659,7 +635,7 @@ impl Forth { .borrow_mut() .as_mut() .expect("var_writes taken") - .push((name, val)); + .insert(name, val); } Op::SetKeep => { let name = pop(stack)?; @@ -669,7 +645,7 @@ impl Forth { .borrow_mut() .as_mut() .expect("var_writes taken") - .push((name, val)); + .insert(name, val); } Op::GetContext(name) => { @@ -704,7 +680,7 @@ impl Forth { } else { (*b_i, *a_i) }; - let val = rng.lock().gen_range(lo..=hi); + let val = self.rng.lock().gen_range(lo..=hi); record_resolved(&trace_cell, *word_span, ResolvedValue::Int(val)); stack.push(Value::Int(val, None)); } @@ -715,7 +691,7 @@ impl Forth { let val = if (hi - lo).abs() < f64::EPSILON { lo } else { - rng.lock().gen_range(lo..hi) + self.rng.lock().gen_range(lo..hi) }; record_resolved(&trace_cell, *word_span, ResolvedValue::Float(val)); stack.push(Value::Float(val, None)); @@ -729,7 +705,7 @@ impl Forth { return Err("exprand requires positive values".into()); } let (lo, hi) = if lo <= hi { (lo, hi) } else { (hi, lo) }; - let u: f64 = rng.lock().gen(); + let u: f64 = self.rng.lock().gen(); let val = lo * (hi / lo).powf(u); record_resolved(&trace_cell, *word_span, ResolvedValue::Float(val)); stack.push(Value::Float(val, None)); @@ -741,14 +717,14 @@ impl Forth { return Err("logrand requires positive values".into()); } let (lo, hi) = if lo <= hi { (lo, hi) } else { (hi, lo) }; - let u: f64 = rng.lock().gen(); + let u: f64 = self.rng.lock().gen(); let val = hi * (lo / hi).powf(u); record_resolved(&trace_cell, *word_span, ResolvedValue::Float(val)); stack.push(Value::Float(val, None)); } Op::Seed => { let s = pop_int(stack)?; - *rng.lock() = StdRng::seed_from_u64(s as u64); + *self.rng.lock() = StdRng::seed_from_u64(s as u64); } Op::Cycle(word_span) | Op::PCycle(word_span) => { @@ -775,7 +751,7 @@ impl Forth { if count == 0 { return Err("choose count must be > 0".into()); } - let idx = rng.lock().gen_range(0..count); + let idx = self.rng.lock().gen_range(0..count); if let Some(span) = word_span { if stack.len() >= count { let start = stack.len() - count; @@ -832,7 +808,7 @@ impl Forth { if total <= 0.0 { return Err("wchoose: total weight must be > 0".into()); } - let threshold: f64 = rng.lock().gen::() * total; + let threshold: f64 = self.rng.lock().gen::() * total; let mut cumulative = 0.0; let mut selected_idx = count - 1; for (i, &w) in weights.iter().enumerate() { @@ -850,7 +826,7 @@ impl Forth { Op::ChanceExec(word_span) | Op::ProbExec(word_span) => { let threshold = pop_float(stack)?; let quot = pop(stack)?; - let val: f64 = rng.lock().gen(); + let val: f64 = self.rng.lock().gen(); let limit = match &ops[pc] { Op::ChanceExec(_) => threshold, _ => threshold / 100.0, @@ -863,7 +839,7 @@ impl Forth { } Op::Coin(word_span) => { - let val: f64 = rng.lock().gen(); + let val: f64 = self.rng.lock().gen(); let result = val < 0.5; record_resolved(&trace_cell, *word_span, ResolvedValue::Bool(result)); stack.push(Value::Int(if result { 1 } else { 0 }, None)); @@ -1003,7 +979,7 @@ impl Forth { .borrow_mut() .as_mut() .expect("var_writes taken") - .push(("__tempo__".to_string(), Value::Float(clamped, None))); + .insert("__tempo__".to_string(), Value::Float(clamped, None)); } Op::SetSpeed => { @@ -1013,7 +989,7 @@ impl Forth { .borrow_mut() .as_mut() .expect("var_writes taken") - .push((ctx.speed_key.to_string(), Value::Float(clamped, None))); + .insert(ctx.speed_key.to_string(), Value::Float(clamped, None)); } Op::Loop => { @@ -1175,7 +1151,7 @@ impl Forth { .borrow_mut() .as_mut() .expect("var_writes taken") - .push(("i".to_string(), Value::Int(i, None))); + .insert("i".to_string(), Value::Int(i, None)); run_quotation(quot.clone(), stack, outputs, cmd)?; } } @@ -1411,12 +1387,7 @@ impl Forth { } Op::Forget => { let name = pop(stack)?; - dict.lock().remove(name.as_str()?); - cache_cell - .borrow_mut() - .as_mut() - .expect("cache taken") - .clear(); + self.dict.lock().remove(name.as_str()?); } } pc += 1; diff --git a/src/app/scripting.rs b/src/app/scripting.rs index 7f3eab7..29b68c3 100644 --- a/src/app/scripting.rs +++ b/src/app/scripting.rs @@ -122,7 +122,7 @@ impl App { } pub fn execute_script_oneshot( - &mut self, + &self, script: &str, link: &LinkState, audio_tx: &arc_swap::ArcSwap>, diff --git a/src/engine/sequencer.rs b/src/engine/sequencer.rs index 0cbd3a3..15cfc6c 100644 --- a/src/engine/sequencer.rs +++ b/src/engine/sequencer.rs @@ -872,8 +872,6 @@ impl SequencerState { } } - let mut trace = ExecutionTrace::default(); - for (_id, active) in self.audio_state.active_patterns.iter_mut() { let Some(pattern) = self.pattern_cache.get(active.bank, active.pattern) else { continue; @@ -939,9 +937,7 @@ impl SequencerState { mouse_down, }; if let Some(script) = resolved_script { - trace.executed_spans.clear(); - trace.selected_spans.clear(); - trace.resolved.clear(); + let mut trace = ExecutionTrace::default(); if let Ok(cmds) = self .script_engine .evaluate_with_trace(script, &ctx, &mut trace) diff --git a/src/input/main_page.rs b/src/input/main_page.rs index 9d9262b..45bfe22 100644 --- a/src/input/main_page.rs +++ b/src/input/main_page.rs @@ -147,26 +147,23 @@ pub(super) fn handle_main_page(ctx: &mut InputContext, key: KeyEvent, ctrl: bool } } KeyCode::Char('r') if ctrl => { - let script = ctx - .app - .current_edit_pattern() - .resolve_script(ctx.app.editor_ctx.step) - .filter(|s| !s.trim().is_empty()) - .map(|s| s.to_owned()); - if let Some(script) = script { - match ctx - .app - .execute_script_oneshot(&script, ctx.link, ctx.audio_tx) - { - Ok(()) => ctx + let pattern = ctx.app.current_edit_pattern(); + if let Some(script) = pattern.resolve_script(ctx.app.editor_ctx.step) { + if !script.trim().is_empty() { + match ctx .app - .ui - .flash("Executed", 100, crate::state::FlashKind::Info), - Err(e) => ctx.app.ui.flash( - &format!("Error: {e}"), - 200, - crate::state::FlashKind::Error, - ), + .execute_script_oneshot(script, ctx.link, ctx.audio_tx) + { + Ok(()) => ctx + .app + .ui + .flash("Executed", 100, crate::state::FlashKind::Info), + Err(e) => ctx.app.ui.flash( + &format!("Error: {e}"), + 200, + crate::state::FlashKind::Error, + ), + } } } } diff --git a/src/model/script.rs b/src/model/script.rs index 2b7a097..477dd92 100644 --- a/src/model/script.rs +++ b/src/model/script.rs @@ -11,12 +11,12 @@ impl ScriptEngine { } } - pub fn evaluate(&mut self, script: &str, ctx: &StepContext) -> Result, String> { + pub fn evaluate(&self, script: &str, ctx: &StepContext) -> Result, String> { self.forth.evaluate(script, ctx) } pub fn evaluate_with_trace( - &mut self, + &self, script: &str, ctx: &StepContext, trace: &mut ExecutionTrace, diff --git a/src/services/stack_preview.rs b/src/services/stack_preview.rs index 94881af..48cd293 100644 --- a/src/services/stack_preview.rs +++ b/src/services/stack_preview.rs @@ -43,7 +43,7 @@ pub fn update_cache(editor_ctx: &EditorContext) { let vars = Arc::new(ArcSwap::from_pointee(HashMap::new())); let dict = Arc::new(Mutex::new(HashMap::new())); let rng = Arc::new(Mutex::new(StdRng::seed_from_u64(42))); - let mut engine = ScriptEngine::new(vars, dict, rng); + let engine = ScriptEngine::new(vars, dict, rng); let ctx = StepContext { step: 0, diff --git a/tests/forth/definitions.rs b/tests/forth/definitions.rs index b795c58..26eb5f4 100644 --- a/tests/forth/definitions.rs +++ b/tests/forth/definitions.rs @@ -29,7 +29,7 @@ fn word_with_param() { #[test] fn word_available_across_evaluations() { - let mut f = forth(); + let f = forth(); let ctx = default_ctx(); f.evaluate(": hi 42 ;", &ctx).unwrap(); f.evaluate("hi", &ctx).unwrap(); @@ -38,7 +38,7 @@ fn word_available_across_evaluations() { #[test] fn word_defined_in_one_forth_available_in_same() { - let mut f = forth(); + let f = forth(); let ctx = default_ctx(); f.evaluate(": ten 10 ;", &ctx).unwrap(); f.clear_stack(); @@ -104,7 +104,7 @@ fn define_word_with_sound() { #[test] fn define_word_with_conditional() { - let mut f = forth(); + let f = forth(); let ctx = default_ctx(); f.evaluate(": maybe-double dup 5 gt if 2 * then ;", &ctx).unwrap(); f.clear_stack(); @@ -117,7 +117,7 @@ fn define_word_with_conditional() { #[test] fn forget_removes_word() { - let mut f = forth(); + let f = forth(); let ctx = default_ctx(); f.evaluate(": double 2 * ;", &ctx).unwrap(); f.evaluate("5 double", &ctx).unwrap(); @@ -135,7 +135,7 @@ fn forget_removes_word() { #[test] fn forget_nonexistent_is_noop() { - let mut f = forth(); + let f = forth(); let ctx = default_ctx(); f.evaluate("\"nosuchword\" forget", &ctx).unwrap(); f.evaluate("42", &ctx).unwrap(); @@ -144,7 +144,7 @@ fn forget_nonexistent_is_noop() { #[test] fn forget_and_redefine() { - let mut f = forth(); + let f = forth(); let ctx = default_ctx(); f.evaluate(": foo 10 ;", &ctx).unwrap(); f.evaluate("foo", &ctx).unwrap(); diff --git a/tests/forth/errors.rs b/tests/forth/errors.rs index cfe784b..6d44e32 100644 --- a/tests/forth/errors.rs +++ b/tests/forth/errors.rs @@ -65,7 +65,7 @@ fn conditional_based_on_step() { #[test] fn accumulator() { - let mut f = forth(); + let f = forth(); let ctx = default_ctx(); f.evaluate(r#"0 !acc"#, &ctx).unwrap(); for _ in 0..5 { diff --git a/tests/forth/harness.rs b/tests/forth/harness.rs index 3f39e88..3ef379b 100644 --- a/tests/forth/harness.rs +++ b/tests/forth/harness.rs @@ -55,13 +55,13 @@ pub fn forth_seeded(seed: u64) -> Forth { } pub fn run(script: &str) -> Forth { - let mut f = forth(); + let f = forth(); f.evaluate(script, &default_ctx()).unwrap(); f } pub fn run_ctx(script: &str, ctx: &StepContext) -> Forth { - let mut f = forth(); + let f = forth(); f.evaluate(script, ctx).unwrap(); f } @@ -124,7 +124,7 @@ pub fn expect_floats_close(script: &str, expected: f64, epsilon: f64) { } pub fn expect_error(script: &str, expected_substr: &str) { - let mut f = forth(); + let f = forth(); let result = f.evaluate(script, &default_ctx()); assert!(result.is_err(), "expected error for '{}'", script); let err = result.unwrap_err(); @@ -137,14 +137,14 @@ pub fn expect_error(script: &str, expected_substr: &str) { } pub fn expect_outputs(script: &str, count: usize) -> Vec { - let mut f = forth(); + let f = forth(); let outputs = f.evaluate(script, &default_ctx()).unwrap(); assert_eq!(outputs.len(), count, "expected {} outputs", count); outputs } pub fn run_with_trace(script: &str) -> (Forth, ExecutionTrace) { - let mut f = forth(); + let f = forth(); let mut trace = ExecutionTrace::default(); f.evaluate_with_trace(script, &default_ctx(), &mut trace) .unwrap(); diff --git a/tests/forth/list_words.rs b/tests/forth/list_words.rs index 532f16b..b710665 100644 --- a/tests/forth/list_words.rs +++ b/tests/forth/list_words.rs @@ -2,7 +2,7 @@ use super::harness::*; #[test] fn choose_from_stack() { - let mut f = forth(); + let f = forth(); let ctx = default_ctx(); f.evaluate("1 2 3 3 choose", &ctx).unwrap(); let val = stack_int(&f); diff --git a/tests/forth/midi.rs b/tests/forth/midi.rs index e74cae8..4a9fe99 100644 --- a/tests/forth/midi.rs +++ b/tests/forth/midi.rs @@ -32,7 +32,7 @@ fn test_midi_cc_with_channel() { #[test] fn test_ccval_returns_zero_without_cc_memory() { - let mut f = forth(); + let f = forth(); let ctx = default_ctx(); let outputs = f.evaluate("1 1 ccval", &ctx).unwrap(); assert!(outputs.is_empty()); @@ -50,7 +50,7 @@ fn test_ccval_reads_from_cc_memory() { cc_memory.set_cc(0, 0, 1, 64); // device 0, channel 1 (0-indexed), CC 1, value 64 cc_memory.set_cc(0, 5, 74, 127); // device 0, channel 6 (0-indexed), CC 74, value 127 - let mut f = forth(); + let f = forth(); let ctx = StepContext { cc_access: Some(&cc_memory as &dyn CcAccess), ..default_ctx() @@ -316,7 +316,7 @@ fn test_midi_note_long_duration() { #[test] fn test_midi_note_duration_with_tempo() { use crate::harness::{ctx_with, forth}; - let mut f = forth(); + let f = forth(); // At tempo=60, step_duration = 60/60/4/1 = 0.25 seconds let ctx = ctx_with(|c| c.tempo = 60.0); let outputs = f.evaluate("60 note m.", &ctx).unwrap(); @@ -326,7 +326,7 @@ fn test_midi_note_duration_with_tempo() { #[test] fn test_midi_note_duration_with_speed() { use crate::harness::{ctx_with, forth}; - let mut f = forth(); + let f = forth(); // At tempo=120 speed=2, step_duration = 60/120/4/2 = 0.0625 seconds let ctx = ctx_with(|c| c.speed = 2.0); let outputs = f.evaluate("60 note m.", &ctx).unwrap(); diff --git a/tests/forth/quotations.rs b/tests/forth/quotations.rs index 41de2f0..2287300 100644 --- a/tests/forth/quotations.rs +++ b/tests/forth/quotations.rs @@ -3,7 +3,7 @@ use super::harness::*; #[test] fn quotation_on_stack() { // Quotation should be pushable to stack - let mut f = forth(); + let f = forth(); let result = f.evaluate("{ 1 2 + }", &default_ctx()); assert!(result.is_ok()); } @@ -79,7 +79,7 @@ fn quotation_with_emit() { #[test] fn quotation_skips_emit() { // When false, . should not fire - let mut f = forth(); + let f = forth(); let outputs = f .evaluate(r#""kick" s { . } 0 ?"#, &default_ctx()) .unwrap(); @@ -107,7 +107,7 @@ fn every_with_quotation_integration() { // { 2 distort } 2 every — on even iterations, distort is applied for iter in 0..4 { let ctx = ctx_with(|c| c.iter = iter); - let mut f = forth(); + let f = forth(); let outputs = f .evaluate(r#""kick" s { 2 distort } 2 every ."#, &ctx) .unwrap(); @@ -132,7 +132,7 @@ fn every_with_quotation_integration() { #[test] fn bjork_with_sound() { let ctx = ctx_with(|c| c.runs = 2); // position 2 is a hit for (3,8) - let mut f = forth(); + let f = forth(); let outputs = f .evaluate(r#""kick" s { 2 distort } 3 8 bjork ."#, &ctx) .unwrap(); @@ -158,7 +158,7 @@ fn when_and_unless_complementary() { // Using iter mod + ?/!? for if-else behavior (every no longer pushes bool) for iter in 0..4 { let ctx = ctx_with(|c| c.iter = iter); - let mut f = forth(); + let f = forth(); let outputs = f .evaluate( r#""kick" s { 2 distort } iter 2 mod 0 = ? { 4 distort } iter 2 mod 0 = !? ."#, diff --git a/tests/forth/ramps.rs b/tests/forth/ramps.rs index be81855..a3dd744 100644 --- a/tests/forth/ramps.rs +++ b/tests/forth/ramps.rs @@ -3,7 +3,7 @@ use super::harness::*; #[test] fn ramp_at_beat_zero() { let ctx = ctx_with(|c| c.beat = 0.0); - let mut f = forth(); + let f = forth(); f.evaluate("1.0 2.0 ramp", &ctx).unwrap(); let val = stack_float(&f); assert!((val - 0.0).abs() < 1e-9, "expected 0.0, got {}", val); @@ -12,7 +12,7 @@ fn ramp_at_beat_zero() { #[test] fn ramp_linear() { let ctx = ctx_with(|c| c.beat = 0.5); - let mut f = forth(); + let f = forth(); f.evaluate("1.0 1.0 ramp", &ctx).unwrap(); let val = stack_float(&f); assert!((val - 0.5).abs() < 1e-9, "expected 0.5, got {}", val); @@ -21,7 +21,7 @@ fn ramp_linear() { #[test] fn ramp_quadratic() { let ctx = ctx_with(|c| c.beat = 0.5); - let mut f = forth(); + let f = forth(); f.evaluate("1.0 2.0 ramp", &ctx).unwrap(); let val = stack_float(&f); assert!((val - 0.25).abs() < 1e-9, "expected 0.25, got {}", val); @@ -30,7 +30,7 @@ fn ramp_quadratic() { #[test] fn ramp_sqrt() { let ctx = ctx_with(|c| c.beat = 0.25); - let mut f = forth(); + let f = forth(); f.evaluate("1.0 0.5 ramp", &ctx).unwrap(); let val = stack_float(&f); assert!((val - 0.5).abs() < 1e-9, "expected 0.5, got {}", val); @@ -39,7 +39,7 @@ fn ramp_sqrt() { #[test] fn ramp_wraps() { let ctx = ctx_with(|c| c.beat = 1.5); - let mut f = forth(); + let f = forth(); f.evaluate("1.0 1.0 ramp", &ctx).unwrap(); let val = stack_float(&f); assert!((val - 0.5).abs() < 1e-9, "expected 0.5, got {}", val); @@ -48,7 +48,7 @@ fn ramp_wraps() { #[test] fn ramp_freq_half() { let ctx = ctx_with(|c| c.beat = 1.0); - let mut f = forth(); + let f = forth(); f.evaluate("0.5 1.0 ramp", &ctx).unwrap(); let val = stack_float(&f); assert!((val - 0.5).abs() < 1e-9, "expected 0.5, got {}", val); @@ -62,7 +62,7 @@ fn ramp_underflow() { #[test] fn range_mid() { let ctx = default_ctx(); - let mut f = forth(); + let f = forth(); f.evaluate("0.5 100.0 200.0 range", &ctx).unwrap(); let val = stack_float(&f); assert!((val - 150.0).abs() < 1e-9, "expected 150.0, got {}", val); @@ -71,7 +71,7 @@ fn range_mid() { #[test] fn range_at_zero() { let ctx = default_ctx(); - let mut f = forth(); + let f = forth(); f.evaluate("0.0 100.0 200.0 range", &ctx).unwrap(); let val = stack_float(&f); assert!((val - 100.0).abs() < 1e-9, "expected 100.0, got {}", val); @@ -80,7 +80,7 @@ fn range_at_zero() { #[test] fn range_at_one() { let ctx = default_ctx(); - let mut f = forth(); + let f = forth(); f.evaluate("1.0 100.0 200.0 range", &ctx).unwrap(); let val = stack_float(&f); assert!((val - 200.0).abs() < 1e-9, "expected 200.0, got {}", val); @@ -94,7 +94,7 @@ fn range_underflow() { #[test] fn linramp() { let ctx = ctx_with(|c| c.beat = 0.5); - let mut f = forth(); + let f = forth(); f.evaluate("1.0 linramp", &ctx).unwrap(); let val = stack_float(&f); assert!((val - 0.5).abs() < 1e-9, "expected 0.5, got {}", val); @@ -103,7 +103,7 @@ fn linramp() { #[test] fn expramp() { let ctx = ctx_with(|c| c.beat = 0.5); - let mut f = forth(); + let f = forth(); f.evaluate("1.0 expramp", &ctx).unwrap(); let val = stack_float(&f); let expected = 0.5_f64.powf(3.0); @@ -113,7 +113,7 @@ fn expramp() { #[test] fn logramp() { let ctx = ctx_with(|c| c.beat = 0.5); - let mut f = forth(); + let f = forth(); f.evaluate("1.0 logramp", &ctx).unwrap(); let val = stack_float(&f); let expected = 0.5_f64.powf(0.3); @@ -123,7 +123,7 @@ fn logramp() { #[test] fn ramp_with_range() { let ctx = ctx_with(|c| c.beat = 0.5); - let mut f = forth(); + let f = forth(); f.evaluate("1.0 1.0 ramp 200.0 800.0 range", &ctx).unwrap(); let val = stack_float(&f); assert!((val - 500.0).abs() < 1e-9, "expected 500.0, got {}", val); @@ -132,7 +132,7 @@ fn ramp_with_range() { #[test] fn perlin_deterministic() { let ctx = ctx_with(|c| c.beat = 2.7); - let mut f = forth(); + let f = forth(); f.evaluate("1.0 perlin", &ctx).unwrap(); let val1 = stack_float(&f); f.evaluate("1.0 perlin", &ctx).unwrap(); @@ -144,7 +144,7 @@ fn perlin_deterministic() { fn perlin_in_range() { for i in 0..100 { let ctx = ctx_with(|c| c.beat = i as f64 * 0.1); - let mut f = forth(); + let f = forth(); f.evaluate("1.0 perlin", &ctx).unwrap(); let val = stack_float(&f); assert!(val >= 0.0 && val <= 1.0, "perlin out of range: {}", val); @@ -155,7 +155,7 @@ fn perlin_in_range() { fn perlin_varies() { let ctx1 = ctx_with(|c| c.beat = 0.5); let ctx2 = ctx_with(|c| c.beat = 1.5); - let mut f = forth(); + let f = forth(); f.evaluate("1.0 perlin", &ctx1).unwrap(); let val1 = stack_float(&f); f.evaluate("1.0 perlin", &ctx2).unwrap(); @@ -165,7 +165,7 @@ fn perlin_varies() { #[test] fn perlin_smooth() { - let mut f = forth(); + let f = forth(); let mut prev = 0.0; for i in 0..100 { let ctx = ctx_with(|c| c.beat = i as f64 * 0.01); @@ -181,7 +181,7 @@ fn perlin_smooth() { #[test] fn perlin_with_range() { let ctx = ctx_with(|c| c.beat = 1.3); - let mut f = forth(); + let f = forth(); f.evaluate("1.0 perlin 200.0 800.0 range", &ctx).unwrap(); let val = stack_float(&f); assert!(val >= 200.0 && val <= 800.0, "perlin+range out of bounds: {}", val); diff --git a/tests/forth/randomness.rs b/tests/forth/randomness.rs index c066a52..46fa6e8 100644 --- a/tests/forth/randomness.rs +++ b/tests/forth/randomness.rs @@ -3,7 +3,7 @@ use cagire::forth::ResolvedValue; #[test] fn rand_in_range() { - let mut f = forth_seeded(12345); + let f = forth_seeded(12345); f.evaluate("0 10 rand", &default_ctx()).unwrap(); let val = stack_float(&f); assert!(val >= 0.0 && val < 10.0, "rand {} not in [0, 10)", val); @@ -11,8 +11,8 @@ fn rand_in_range() { #[test] fn rand_deterministic() { - let mut f1 = forth_seeded(99); - let mut f2 = forth_seeded(99); + let f1 = forth_seeded(99); + let f2 = forth_seeded(99); f1.evaluate("0 100 rand", &default_ctx()).unwrap(); f2.evaluate("0 100 rand", &default_ctx()).unwrap(); assert_eq!(f1.stack(), f2.stack()); @@ -20,16 +20,16 @@ fn rand_deterministic() { #[test] fn seed_resets() { - let mut f1 = forth_seeded(1); + let f1 = forth_seeded(1); f1.evaluate("42 seed 0 100 rand", &default_ctx()).unwrap(); - let mut f2 = forth_seeded(999); + let f2 = forth_seeded(999); f2.evaluate("42 seed 0 100 rand", &default_ctx()).unwrap(); assert_eq!(f1.stack(), f2.stack()); } #[test] fn coin_binary() { - let mut f = forth_seeded(42); + let f = forth_seeded(42); f.evaluate("coin", &default_ctx()).unwrap(); let val = stack_int(&f); assert!(val == 0 || val == 1); @@ -51,7 +51,7 @@ fn chance_one() { #[test] fn choose_from_list() { - let mut f = forth_seeded(42); + let f = forth_seeded(42); f.evaluate("10 20 30 3 choose", &default_ctx()).unwrap(); let val = stack_int(&f); assert!(val == 10 || val == 20 || val == 30); @@ -109,7 +109,7 @@ fn mtof_ftom_roundtrip() { #[test] fn exprand_in_range() { - let mut f = forth_seeded(12345); + let f = forth_seeded(12345); f.evaluate("1.0 100.0 exprand", &default_ctx()).unwrap(); let val = stack_float(&f); assert!(val >= 1.0 && val <= 100.0, "exprand {} not in [1, 100]", val); @@ -117,8 +117,8 @@ fn exprand_in_range() { #[test] fn exprand_deterministic() { - let mut f1 = forth_seeded(99); - let mut f2 = forth_seeded(99); + let f1 = forth_seeded(99); + let f2 = forth_seeded(99); f1.evaluate("1.0 100.0 exprand", &default_ctx()).unwrap(); f2.evaluate("1.0 100.0 exprand", &default_ctx()).unwrap(); assert_eq!(f1.stack(), f2.stack()); @@ -126,8 +126,8 @@ fn exprand_deterministic() { #[test] fn exprand_swapped_args() { - let mut f1 = forth_seeded(42); - let mut f2 = forth_seeded(42); + let f1 = forth_seeded(42); + let f2 = forth_seeded(42); f1.evaluate("1.0 100.0 exprand", &default_ctx()).unwrap(); f2.evaluate("100.0 1.0 exprand", &default_ctx()).unwrap(); assert_eq!(f1.stack(), f2.stack()); @@ -142,7 +142,7 @@ fn exprand_requires_positive() { #[test] fn logrand_in_range() { - let mut f = forth_seeded(12345); + let f = forth_seeded(12345); f.evaluate("1.0 100.0 logrand", &default_ctx()).unwrap(); let val = stack_float(&f); assert!(val >= 1.0 && val <= 100.0, "logrand {} not in [1, 100]", val); @@ -150,8 +150,8 @@ fn logrand_in_range() { #[test] fn logrand_deterministic() { - let mut f1 = forth_seeded(99); - let mut f2 = forth_seeded(99); + let f1 = forth_seeded(99); + let f2 = forth_seeded(99); f1.evaluate("1.0 100.0 logrand", &default_ctx()).unwrap(); f2.evaluate("1.0 100.0 logrand", &default_ctx()).unwrap(); assert_eq!(f1.stack(), f2.stack()); @@ -159,8 +159,8 @@ fn logrand_deterministic() { #[test] fn logrand_swapped_args() { - let mut f1 = forth_seeded(42); - let mut f2 = forth_seeded(42); + let f1 = forth_seeded(42); + let f2 = forth_seeded(42); f1.evaluate("1.0 100.0 logrand", &default_ctx()).unwrap(); f2.evaluate("100.0 1.0 logrand", &default_ctx()).unwrap(); assert_eq!(f1.stack(), f2.stack()); @@ -214,7 +214,7 @@ fn bounce_underflow() { #[test] fn wchoose_all_weight_one_item() { for _ in 0..10 { - let mut f = forth_seeded(42); + let f = forth_seeded(42); f.evaluate("10 0.0 20 1.0 2 wchoose", &default_ctx()) .unwrap(); assert_eq!(stack_int(&f), 20); @@ -223,8 +223,8 @@ fn wchoose_all_weight_one_item() { #[test] fn wchoose_deterministic() { - let mut f1 = forth_seeded(99); - let mut f2 = forth_seeded(99); + let f1 = forth_seeded(99); + let f2 = forth_seeded(99); f1.evaluate("60 0.6 64 0.3 67 0.1 3 wchoose", &default_ctx()) .unwrap(); f2.evaluate("60 0.6 64 0.3 67 0.1 3 wchoose", &default_ctx()) @@ -249,7 +249,7 @@ fn wchoose_negative_weight() { #[test] fn wchoose_quotation() { - let mut f = forth_seeded(42); + let f = forth_seeded(42); f.evaluate("{ 10 } 0.0 { 20 } 1.0 2 wchoose", &default_ctx()) .unwrap(); assert_eq!(stack_int(&f), 20); diff --git a/tests/forth/stack.rs b/tests/forth/stack.rs index 54cf788..6d88844 100644 --- a/tests/forth/stack.rs +++ b/tests/forth/stack.rs @@ -137,7 +137,7 @@ fn over2_underflow() { #[test] fn stack_persists() { - let mut f = forth(); + let f = forth(); let ctx = default_ctx(); f.evaluate("1 2 3", &ctx).unwrap(); assert_eq!(f.stack(), vec![int(1), int(2), int(3)]); @@ -147,7 +147,7 @@ fn stack_persists() { #[test] fn clear_stack() { - let mut f = forth(); + let f = forth(); f.evaluate("1 2 3", &default_ctx()).unwrap(); f.clear_stack(); assert!(f.stack().is_empty()); @@ -180,7 +180,7 @@ fn rev_underflow() { #[test] fn shuffle_preserves_elements() { - let mut f = forth(); + let f = forth(); f.evaluate("1 2 3 4 4 shuffle", &default_ctx()).unwrap(); let mut stack: Vec = f .stack() diff --git a/tests/forth/temporal.rs b/tests/forth/temporal.rs index ff24027..a41f104 100644 --- a/tests/forth/temporal.rs +++ b/tests/forth/temporal.rs @@ -98,7 +98,7 @@ fn dur_is_step_duration() { fn cycle_picks_by_runs() { for runs in 0..4 { let ctx = ctx_with(|c| c.runs = runs); - let mut f = forth(); + let f = forth(); let outputs = f.evaluate(r#""kick" s { . } { } 2 cycle"#, &ctx).unwrap(); if runs % 2 == 0 { assert_eq!(outputs.len(), 1, "runs={}: emit should be picked", runs); @@ -112,7 +112,7 @@ fn cycle_picks_by_runs() { fn pcycle_picks_by_iter() { for iter in 0..4 { let ctx = ctx_with(|c| c.iter = iter); - let mut f = forth(); + let f = forth(); let outputs = f.evaluate(r#""kick" s { . } { } 2 pcycle"#, &ctx).unwrap(); if iter % 2 == 0 { assert_eq!(outputs.len(), 1, "iter={}: emit should be picked", iter); @@ -126,7 +126,7 @@ fn pcycle_picks_by_iter() { fn cycle_with_sounds() { for runs in 0..3 { let ctx = ctx_with(|c| c.runs = runs); - let mut f = forth(); + let f = forth(); let outputs = f.evaluate( r#"{ "kick" s . } { "hat" s . } { "snare" s . } 3 cycle"#, &ctx @@ -194,7 +194,7 @@ fn clear_resets_at_deltas() { fn at_records_selected_spans() { use cagire::forth::ExecutionTrace; - let mut f = forth(); + let f = forth(); let mut trace = ExecutionTrace::default(); let script = r#"0 0.5 0.75 at "kick" s ."#; f.evaluate_with_trace(script, &default_ctx(), &mut trace).unwrap(); diff --git a/tests/forth/variables.rs b/tests/forth/variables.rs index 85e5ac9..97c7a50 100644 --- a/tests/forth/variables.rs +++ b/tests/forth/variables.rs @@ -12,7 +12,7 @@ fn fetch_nonexistent() { #[test] fn persistence_across_evals() { - let mut f = forth(); + let f = forth(); let ctx = default_ctx(); f.evaluate(r#"10 !counter"#, &ctx).unwrap(); f.clear_stack(); @@ -50,7 +50,7 @@ fn set_keep() { #[test] fn set_keep_stores() { - let mut f = forth(); + let f = forth(); let ctx = default_ctx(); f.evaluate(r#"42 ,x"#, &ctx).unwrap(); f.clear_stack(); @@ -60,7 +60,7 @@ fn set_keep_stores() { #[test] fn set_keep_chain() { - let mut f = forth(); + let f = forth(); let ctx = default_ctx(); f.evaluate(r#"10 ,a ,b"#, &ctx).unwrap(); f.clear_stack();