WIP simplify

This commit is contained in:
2026-01-29 09:38:41 +01:00
parent f1f1b28b31
commit 4d0d837e14
11 changed files with 434 additions and 291 deletions

View File

@@ -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");
}