WIP simplify
This commit is contained in:
@@ -51,22 +51,6 @@ fn string_with_spaces() {
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn list_count() {
|
||||
let f = run("[ 1 2 3 ]");
|
||||
assert_eq!(stack_int(&f), 3);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn list_empty() {
|
||||
expect_int("[ ]", 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn list_preserves_values() {
|
||||
expect_int("[ 10 20 ] drop +", 30);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn conditional_based_on_step() {
|
||||
let ctx0 = ctx_with(|c| c.step = 0);
|
||||
|
||||
@@ -1,124 +1,106 @@
|
||||
use super::harness::*;
|
||||
|
||||
#[test]
|
||||
fn choose_word_from_list() {
|
||||
// 1 2 [ + - ] choose: picks + or -, applies to 1 2
|
||||
// With seed 42, choose picks one deterministically
|
||||
fn choose_from_stack() {
|
||||
let f = forth();
|
||||
let ctx = default_ctx();
|
||||
f.evaluate("1 2 [ + - ] choose", &ctx).unwrap();
|
||||
f.evaluate("1 2 3 3 choose", &ctx).unwrap();
|
||||
let val = stack_int(&f);
|
||||
assert!(val == 3 || val == -1, "expected 3 or -1, got {}", val);
|
||||
assert!(val >= 1 && val <= 3, "expected 1, 2, or 3, got {}", val);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cycle_word_from_list() {
|
||||
// At runs=0, picks first word (dup)
|
||||
fn cycle_by_runs() {
|
||||
let ctx = ctx_with(|c| c.runs = 0);
|
||||
let f = run_ctx("5 < dup nip >", &ctx);
|
||||
assert_eq!(stack_int(&f), 5); // dup leaves 5 5, but stack check takes top
|
||||
|
||||
// At runs=1, picks second word (2 *)
|
||||
let f = forth();
|
||||
let ctx = ctx_with(|c| c.runs = 1);
|
||||
f.evaluate(": double 2 * ; 5 < dup double >", &ctx).unwrap();
|
||||
let f = run_ctx("10 20 30 3 cycle", &ctx);
|
||||
assert_eq!(stack_int(&f), 10);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn user_word_in_list() {
|
||||
let f = forth();
|
||||
let ctx = ctx_with(|c| c.runs = 0);
|
||||
f.evaluate(": add3 3 + ; : add5 5 + ; 10 < add3 add5 >", &ctx).unwrap();
|
||||
assert_eq!(stack_int(&f), 13); // runs=0 picks add3
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn user_word_in_list_second() {
|
||||
let f = forth();
|
||||
let ctx = ctx_with(|c| c.runs = 1);
|
||||
f.evaluate(": add3 3 + ; : add5 5 + ; 10 < add3 add5 >", &ctx).unwrap();
|
||||
assert_eq!(stack_int(&f), 15); // runs=1 picks add5
|
||||
}
|
||||
let f = run_ctx("10 20 30 3 cycle", &ctx);
|
||||
assert_eq!(stack_int(&f), 20);
|
||||
|
||||
#[test]
|
||||
fn values_in_list_still_work() {
|
||||
// Numbers inside lists should still push as values (not quotations)
|
||||
let ctx = ctx_with(|c| c.runs = 0);
|
||||
let f = run_ctx("< 10 20 30 >", &ctx);
|
||||
assert_eq!(stack_int(&f), 10);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn values_in_list_cycle() {
|
||||
let ctx = ctx_with(|c| c.runs = 2);
|
||||
let f = run_ctx("< 10 20 30 >", &ctx);
|
||||
let f = run_ctx("10 20 30 3 cycle", &ctx);
|
||||
assert_eq!(stack_int(&f), 30);
|
||||
|
||||
let ctx = ctx_with(|c| c.runs = 3);
|
||||
let f = run_ctx("10 20 30 3 cycle", &ctx);
|
||||
assert_eq!(stack_int(&f), 10);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pcycle_by_iter() {
|
||||
let ctx = ctx_with(|c| c.iter = 0);
|
||||
let f = run_ctx("10 20 30 3 pcycle", &ctx);
|
||||
assert_eq!(stack_int(&f), 10);
|
||||
|
||||
let ctx = ctx_with(|c| c.iter = 1);
|
||||
let f = run_ctx("10 20 30 3 pcycle", &ctx);
|
||||
assert_eq!(stack_int(&f), 20);
|
||||
|
||||
let ctx = ctx_with(|c| c.iter = 2);
|
||||
let f = run_ctx("10 20 30 3 pcycle", &ctx);
|
||||
assert_eq!(stack_int(&f), 30);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mixed_values_and_words() {
|
||||
// Values stay as values, words become quotations
|
||||
// [ 10 20 ] choose just picks a number
|
||||
let f = forth();
|
||||
let ctx = default_ctx();
|
||||
f.evaluate("[ 10 20 ] choose", &ctx).unwrap();
|
||||
let val = stack_int(&f);
|
||||
assert!(val == 10 || val == 20, "expected 10 or 20, got {}", val);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn word_with_sound_params() {
|
||||
let f = forth();
|
||||
fn cycle_with_quotations() {
|
||||
let ctx = ctx_with(|c| c.runs = 0);
|
||||
let outputs = f.evaluate(
|
||||
": myverb 0.5 verb ; \"sine\" s 440 freq < myverb > .",
|
||||
&ctx
|
||||
).unwrap();
|
||||
assert_eq!(outputs.len(), 1);
|
||||
assert!(outputs[0].contains("verb/0.5"), "expected verb/0.5 in {}", outputs[0]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn arithmetic_word_in_list() {
|
||||
// 3 4 [ + ] choose -> picks + (only option), applies to 3 4 = 7
|
||||
let ctx = ctx_with(|c| c.runs = 0);
|
||||
let f = run_ctx("3 4 < + >", &ctx);
|
||||
assert_eq!(stack_int(&f), 7);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pcycle_word_from_list() {
|
||||
let ctx = ctx_with(|c| c.iter = 0);
|
||||
let f = run_ctx("10 << dup 2 * >>", &ctx);
|
||||
// iter=0 picks dup: 10 10
|
||||
let f = run_ctx("5 { dup } { 2 * } 2 cycle", &ctx);
|
||||
let stack = f.stack();
|
||||
assert_eq!(stack.len(), 2);
|
||||
assert_eq!(stack_int(&f), 5);
|
||||
|
||||
let ctx = ctx_with(|c| c.runs = 1);
|
||||
let f = run_ctx("5 { dup } { 2 * } 2 cycle", &ctx);
|
||||
assert_eq!(stack_int(&f), 10);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pcycle_word_second() {
|
||||
let ctx = ctx_with(|c| c.iter = 1);
|
||||
let f = run_ctx("10 << dup 2 * >>", &ctx);
|
||||
// iter=1 picks "2 *" — but wait, each token is its own element
|
||||
// so << dup 2 * >> has 3 elements: {dup}, 2, {*}
|
||||
// iter=1 picks element index 1 which is value 2
|
||||
assert_eq!(stack_int(&f), 2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn multi_op_quotation_in_list() {
|
||||
// Use { } for multi-op quotations inside lists
|
||||
fn cycle_executes_quotation() {
|
||||
let ctx = ctx_with(|c| c.runs = 0);
|
||||
let f = run_ctx("10 < { 2 * } { 3 + } >", &ctx);
|
||||
assert_eq!(stack_int(&f), 20); // runs=0 picks {2 *}
|
||||
let f = run_ctx("10 { 3 + } { 5 + } 2 cycle", &ctx);
|
||||
assert_eq!(stack_int(&f), 13);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn multi_op_quotation_second() {
|
||||
let ctx = ctx_with(|c| c.runs = 1);
|
||||
let f = run_ctx("10 < { 2 * } { 3 + } >", &ctx);
|
||||
assert_eq!(stack_int(&f), 13); // runs=1 picks {3 +}
|
||||
fn dupn_basic() {
|
||||
// 5 3 dupn -> 5 5 5, then + + -> 15
|
||||
expect_int("5 3 dupn + +", 15);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn dupn_alias() {
|
||||
expect_int("5 3 ! + +", 15);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tcycle_creates_cycle_list() {
|
||||
let outputs = expect_outputs(r#"0.0 at 60 64 67 3 tcycle note sine s ."#, 1);
|
||||
assert!(outputs[0].contains("note/60"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tcycle_with_multiple_emits() {
|
||||
let f = forth();
|
||||
let ctx = default_ctx();
|
||||
let outputs = f.evaluate(r#"0 0.5 2 at 60 64 2 tcycle note sine s ."#, &ctx).unwrap();
|
||||
assert_eq!(outputs.len(), 2);
|
||||
assert!(outputs[0].contains("note/60"));
|
||||
assert!(outputs[1].contains("note/64"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cycle_zero_count_error() {
|
||||
expect_error("1 2 3 0 cycle", "cycle count must be > 0");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn choose_zero_count_error() {
|
||||
expect_error("1 2 3 0 choose", "choose count must be > 0");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tcycle_zero_count_error() {
|
||||
expect_error("1 2 3 0 tcycle", "tcycle count must be > 0");
|
||||
}
|
||||
|
||||
@@ -42,6 +42,13 @@ fn get_sounds(outputs: &[String]) -> Vec<String> {
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn get_param(outputs: &[String], param: &str) -> Vec<f64> {
|
||||
outputs
|
||||
.iter()
|
||||
.map(|o| parse_params(o).get(param).copied().unwrap_or(0.0))
|
||||
.collect()
|
||||
}
|
||||
|
||||
const EPSILON: f64 = 1e-9;
|
||||
|
||||
fn approx_eq(a: f64, b: f64) -> bool {
|
||||
@@ -95,29 +102,29 @@ fn dur_is_step_duration() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cycle_picks_by_step() {
|
||||
fn cycle_picks_by_runs() {
|
||||
for runs in 0..4 {
|
||||
let ctx = ctx_with(|c| c.runs = runs);
|
||||
let f = forth();
|
||||
let outputs = f.evaluate(r#""kick" s < . _ >"#, &ctx).unwrap();
|
||||
let outputs = f.evaluate(r#""kick" s { . } { } 2 cycle"#, &ctx).unwrap();
|
||||
if runs % 2 == 0 {
|
||||
assert_eq!(outputs.len(), 1, "runs={}: . should be picked", runs);
|
||||
assert_eq!(outputs.len(), 1, "runs={}: emit should be picked", runs);
|
||||
} else {
|
||||
assert_eq!(outputs.len(), 0, "runs={}: _ should be picked", runs);
|
||||
assert_eq!(outputs.len(), 0, "runs={}: no-op should be picked", runs);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pcycle_picks_by_pattern() {
|
||||
fn pcycle_picks_by_iter() {
|
||||
for iter in 0..4 {
|
||||
let ctx = ctx_with(|c| c.iter = iter);
|
||||
let f = forth();
|
||||
let outputs = f.evaluate(r#""kick" s << . _ >>"#, &ctx).unwrap();
|
||||
let outputs = f.evaluate(r#""kick" s { . } { } 2 pcycle"#, &ctx).unwrap();
|
||||
if iter % 2 == 0 {
|
||||
assert_eq!(outputs.len(), 1, "iter={}: . should be picked", iter);
|
||||
assert_eq!(outputs.len(), 1, "iter={}: emit should be picked", iter);
|
||||
} else {
|
||||
assert_eq!(outputs.len(), 0, "iter={}: _ should be picked", iter);
|
||||
assert_eq!(outputs.len(), 0, "iter={}: no-op should be picked", iter);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -127,7 +134,10 @@ fn cycle_with_sounds() {
|
||||
for runs in 0..3 {
|
||||
let ctx = ctx_with(|c| c.runs = runs);
|
||||
let f = forth();
|
||||
let outputs = f.evaluate(r#"< { "kick" s . } { "hat" s . } { "snare" s . } >"#, &ctx).unwrap();
|
||||
let outputs = f.evaluate(
|
||||
r#"{ "kick" s . } { "hat" s . } { "snare" s . } 3 cycle"#,
|
||||
&ctx
|
||||
).unwrap();
|
||||
assert_eq!(outputs.len(), 1, "runs={}: expected 1 output", runs);
|
||||
let sounds = get_sounds(&outputs);
|
||||
let expected = ["kick", "hat", "snare"][runs % 3];
|
||||
@@ -154,3 +164,107 @@ fn emit_n_negative_error() {
|
||||
let result = f.evaluate(r#""kick" s -1 .!"#, &default_ctx());
|
||||
assert!(result.is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn at_single_delta() {
|
||||
let outputs = expect_outputs(r#"0.5 at "kick" s ."#, 1);
|
||||
let deltas = get_deltas(&outputs);
|
||||
let step_dur = 0.125;
|
||||
assert!(approx_eq(deltas[0], 0.5 * step_dur), "expected delta at 0.5 of step, got {}", deltas[0]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn at_list_deltas() {
|
||||
let outputs = expect_outputs(r#"0 0.5 2 at "kick" s ."#, 2);
|
||||
let deltas = get_deltas(&outputs);
|
||||
let step_dur = 0.125;
|
||||
assert!(approx_eq(deltas[0], 0.0), "expected delta 0, got {}", deltas[0]);
|
||||
assert!(approx_eq(deltas[1], 0.5 * step_dur), "expected delta at 0.5 of step, got {}", deltas[1]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn at_three_deltas() {
|
||||
let outputs = expect_outputs(r#"0 0.33 0.67 3 at "kick" s ."#, 3);
|
||||
let deltas = get_deltas(&outputs);
|
||||
let step_dur = 0.125;
|
||||
assert!(approx_eq(deltas[0], 0.0), "expected delta 0");
|
||||
assert!((deltas[1] - 0.33 * step_dur).abs() < 0.001, "expected delta at 0.33 of step");
|
||||
assert!((deltas[2] - 0.67 * step_dur).abs() < 0.001, "expected delta at 0.67 of step");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn at_persists_across_emits() {
|
||||
let outputs = expect_outputs(r#"0 0.5 2 at "kick" s . "hat" s ."#, 4);
|
||||
let sounds = get_sounds(&outputs);
|
||||
assert_eq!(sounds, vec!["kick", "kick", "hat", "hat"]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tcycle_basic() {
|
||||
let outputs = expect_outputs(r#"0 0.5 0.75 3 at 60 64 67 3 tcycle note sine s ."#, 3);
|
||||
let notes = get_param(&outputs, "note");
|
||||
assert_eq!(notes, vec![60.0, 64.0, 67.0]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tcycle_wraps() {
|
||||
let outputs = expect_outputs(r#"0 0.33 0.67 3 at 60 64 2 tcycle note sine s ."#, 3);
|
||||
let notes = get_param(&outputs, "note");
|
||||
assert_eq!(notes, vec![60.0, 64.0, 60.0]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tcycle_with_sound() {
|
||||
let outputs = expect_outputs(r#"0 0.5 2 at kick hat 2 tcycle s ."#, 2);
|
||||
let sounds = get_sounds(&outputs);
|
||||
assert_eq!(sounds, vec!["kick", "hat"]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tcycle_multiple_params() {
|
||||
let outputs = expect_outputs(r#"0 0.5 0.75 3 at 60 64 67 3 tcycle note 0.5 1.0 2 tcycle gain sine s ."#, 3);
|
||||
let notes = get_param(&outputs, "note");
|
||||
let gains = get_param(&outputs, "gain");
|
||||
assert_eq!(notes, vec![60.0, 64.0, 67.0]);
|
||||
assert_eq!(gains, vec![0.5, 1.0, 0.5]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn at_reset_with_zero() {
|
||||
let outputs = expect_outputs(r#"0 0.5 2 at "kick" s . 0.0 at "hat" s ."#, 3);
|
||||
let sounds = get_sounds(&outputs);
|
||||
assert_eq!(sounds, vec!["kick", "kick", "hat"]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tcycle_records_selected_spans() {
|
||||
use cagire::forth::ExecutionTrace;
|
||||
|
||||
let f = forth();
|
||||
let mut trace = ExecutionTrace::default();
|
||||
let script = r#"0 0.5 2 at kick hat 2 tcycle s ."#;
|
||||
f.evaluate_with_trace(script, &default_ctx(), &mut trace).unwrap();
|
||||
|
||||
// Should have 4 selected spans:
|
||||
// - 2 for at deltas (0 and 0.5)
|
||||
// - 2 for tcycle sound values (kick and hat)
|
||||
assert_eq!(trace.selected_spans.len(), 4, "expected 4 selected spans (2 at + 2 tcycle)");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn at_records_selected_spans() {
|
||||
use cagire::forth::ExecutionTrace;
|
||||
|
||||
let f = forth();
|
||||
let mut trace = ExecutionTrace::default();
|
||||
let script = r#"0 0.5 0.75 3 at "kick" s ."#;
|
||||
f.evaluate_with_trace(script, &default_ctx(), &mut trace).unwrap();
|
||||
|
||||
// Should have 6 selected spans: 3 for at deltas + 3 for sound (one per emit)
|
||||
assert_eq!(trace.selected_spans.len(), 6, "expected 6 selected spans (3 at + 3 sound)");
|
||||
|
||||
// Verify at delta spans (even indices: 0, 2, 4)
|
||||
assert_eq!(&script[trace.selected_spans[0].start..trace.selected_spans[0].end], "0");
|
||||
assert_eq!(&script[trace.selected_spans[2].start..trace.selected_spans[2].end], "0.5");
|
||||
assert_eq!(&script[trace.selected_spans[4].start..trace.selected_spans[4].end], "0.75");
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user