use super::harness::*; use cagire::forth::Value; #[test] fn define_and_use_word() { expect_int(": double 2 * ; 5 double", 10); } #[test] fn define_word_with_multiple_ops() { expect_int(": triple dup dup + + ; 3 triple", 9); } #[test] fn define_word_using_another_user_word() { expect_int(": double 2 * ; : quad double double ; 3 quad", 12); } #[test] fn redefine_word_overwrites() { expect_int(": foo 10 ; : foo 20 ; foo", 20); } #[test] fn word_with_param() { let outputs = expect_outputs(": loud 0.9 gain ; \"kick\" s loud .", 1); assert!(outputs[0].contains("gain/0.9")); } #[test] fn word_available_across_evaluations() { let f = forth(); let ctx = default_ctx(); f.evaluate(": hi 42 ;", &ctx).unwrap(); f.evaluate("hi", &ctx).unwrap(); assert_eq!(stack_int(&f), 42); } #[test] fn word_defined_in_one_forth_available_in_same() { let f = forth(); let ctx = default_ctx(); f.evaluate(": ten 10 ;", &ctx).unwrap(); f.clear_stack(); f.evaluate("ten ten +", &ctx).unwrap(); assert_eq!(stack_int(&f), 20); } #[test] fn unknown_word_becomes_string() { expect_str("nosuchword", "nosuchword"); } #[test] fn missing_semicolon_errors() { expect_error(": foo 10", "missing ';'"); } #[test] fn missing_name_errors() { expect_error(":", "expected word name"); } #[test] fn unexpected_semicolon_errors() { expect_error(";", "unexpected ;"); } #[test] fn apply_executes_quotation() { expect_int("5 { 2 * } apply", 10); } #[test] fn apply_with_stack_ops() { expect_int("3 4 { + } apply", 7); } #[test] fn apply_empty_stack_errors() { expect_error("apply", "stack underflow"); } #[test] fn apply_non_quotation_errors() { expect_error("42 apply", "expected quotation"); } #[test] fn apply_nested() { expect_int("2 { { 3 * } apply } apply", 6); } #[test] fn define_word_containing_quotation() { expect_int(": dbl { 2 * } apply ; 7 dbl", 14); } #[test] fn define_word_with_sound() { let outputs = expect_outputs(": kick \"kick\" s . ; kick", 1); assert!(outputs[0].contains("sound/kick")); } #[test] fn define_word_with_conditional() { let f = forth(); let ctx = default_ctx(); f.evaluate(": maybe-double dup 5 gt if 2 * then ;", &ctx).unwrap(); f.clear_stack(); f.evaluate("3 maybe-double", &ctx).unwrap(); assert_eq!(stack_int(&f), 3); f.clear_stack(); f.evaluate("10 maybe-double", &ctx).unwrap(); assert_eq!(stack_int(&f), 20); } #[test] fn forget_removes_word() { let f = forth(); let ctx = default_ctx(); f.evaluate(": double 2 * ;", &ctx).unwrap(); f.evaluate("5 double", &ctx).unwrap(); assert_eq!(stack_int(&f), 10); f.clear_stack(); f.evaluate("\"double\" forget", &ctx).unwrap(); f.evaluate("double", &ctx).unwrap(); let stack = f.stack(); assert_eq!(stack.len(), 1); match &stack[0] { Value::Str(s, _) => assert_eq!(s.as_ref(), "double"), other => panic!("expected Str, got {:?}", other), } } #[test] fn forget_nonexistent_is_noop() { let f = forth(); let ctx = default_ctx(); f.evaluate("\"nosuchword\" forget", &ctx).unwrap(); f.evaluate("42", &ctx).unwrap(); assert_eq!(stack_int(&f), 42); } #[test] fn forget_and_redefine() { let f = forth(); let ctx = default_ctx(); f.evaluate(": foo 10 ;", &ctx).unwrap(); f.evaluate("foo", &ctx).unwrap(); assert_eq!(stack_int(&f), 10); f.clear_stack(); f.evaluate("\"foo\" forget", &ctx).unwrap(); f.evaluate(": foo 20 ;", &ctx).unwrap(); f.evaluate("foo", &ctx).unwrap(); assert_eq!(stack_int(&f), 20); }