Tests for the Rhai (%rhaidef, %rhaiset, %rhaiget, %rhaiexpr) and Python/monty (%pydef, %pyset, %pyget) scripting back-ends, including persistent store tests.

Rhai scripting (test_rhaidef.rs)

// <<test rhaidef>>=
// crates/weaveback-macro/src/evaluator/tests/test_rhaidef.rs

use crate::evaluator::{EvalConfig, Evaluator, eval_string};

fn evaluator() -> Evaluator {
    Evaluator::new(EvalConfig::default())
}

#[test]
fn test_basic_arithmetic() {
    // Body must be wrapped in %{ %} so weaveback doesn't parse parentheses as arg lists
    let mut ev = evaluator();
    let src = r#"%rhaidef(double, x, %{(parse_int(x) * 2).to_string()%})
%double(21)"#;
    let result = eval_string(src, None, &mut ev).expect("eval failed");
    assert_eq!(result.trim(), "42");
}

#[test]
fn test_helper_function_in_body() {
    let mut ev = evaluator();
    let src = r#"%rhaidef(factorial, n, %{
fn fact(k) { if k <= 1 { 1 } else { k * fact(k - 1) } }
fact(parse_int(n)).to_string()
%})
%factorial(5)"#;
    let result = eval_string(src, None, &mut ev).expect("eval failed");
    assert_eq!(result.trim(), "120");
}

#[test]
fn test_variable_capture_from_scope() {
    // Outer weaveback scope variables are injected into Rhai scope
    let mut ev = evaluator();
    let src = r#"%set(greeting, Hello)
%rhaidef(greet, name, %{
let g = greeting;
let n = name;
g + ", " + n + "!"
%})
%greet(Rhai)"#;
    let result = eval_string(src, None, &mut ev).expect("eval failed");
    assert_eq!(result.trim(), "Hello, Rhai!");
}

#[test]
fn test_hex_formatting() {
    let mut ev = evaluator();
    let src = r#"%rhaidef(as_hex, n, %{to_hex(parse_int(n))%})
%as_hex(255)"#;
    let result = eval_string(src, None, &mut ev).expect("eval failed");
    assert_eq!(result.trim(), "0xFF");
}

#[test]
fn test_error_propagation() {
    let mut ev = evaluator();
    // @@@ is not valid Rhai syntax
    let src = r#"%rhaidef(broken, x, %{@@@%})
%broken(foo)"#;
    let result = eval_string(src, None, &mut ev);
    assert!(result.is_err(), "expected error from bad Rhai code");
}

// --- store tests ---

#[test]
fn test_rhaiset_rhaiget_roundtrip() {
    let mut ev = evaluator();
    let src = r#"%rhaiset(color, red)
%rhaiget(color)"#;
    let result = eval_string(src, None, &mut ev).expect("eval failed");
    assert_eq!(result.trim(), "red");
}

#[test]
fn test_store_counter_auto_writeback() {
    // The script mutates `counter` directly; the store write-back persists it.
    let mut ev = evaluator();
    let src = r#"%rhaiset(counter, 0)
%rhaidef(tick, %{
  counter += 1;
  counter.to_string()
%})
%tick()
%tick()
%tick()"#;
    let result = eval_string(src, None, &mut ev).expect("eval failed");
    let values: Vec<&str> = result.split_whitespace().collect();
    assert_eq!(values, ["1", "2", "3"]);
}

#[test]
fn test_store_integer_type_preserved() {
    // %rhaiset auto-parses numeric strings, so arithmetic works natively
    let mut ev = evaluator();
    let src = r#"%rhaiset(n, 10)
%rhaidef(double_n, %{
  n *= 2;
  n.to_string()
%})
%double_n()
%double_n()"#;
    let result = eval_string(src, None, &mut ev).expect("eval failed");
    let values: Vec<&str> = result.split_whitespace().collect();
    // First call: n=10 → *2 = 20; second call: n=20 → *2 = 40
    assert_eq!(values, ["20", "40"]);
}

#[test]
fn test_store_map_tree() {
    let mut ev = evaluator();
    let src = r#"%rhaiexpr(root, #{})
%rhaidef(build_tree, %{
  root = #{
    name: "root",
    children: [
      #{ name: "a", children: [] },
      #{ name: "b", children: [] }
    ]
  };
  ""
%})
%rhaidef(child_count, %{
  root.children.len().to_string()
%})
%build_tree()
%child_count()"#;
    let result = eval_string(src, None, &mut ev).expect("eval failed");
    assert_eq!(result.trim(), "2");
}

#[test]
fn test_store_array_accumulation() {
    let mut ev = evaluator();
    let src = r#"%rhaiexpr(items, [])
%rhaidef(push_item, x, %{
  items.push(x);
  items.len().to_string()
%})
%push_item(apple)
%push_item(banana)
%push_item(cherry)"#;
    let result = eval_string(src, None, &mut ev).expect("eval failed");
    let counts: Vec<&str> = result.split_whitespace().collect();
    assert_eq!(counts, ["1", "2", "3"]);
}

#[test]
fn test_store_shadowed_by_weaveback_scope() {
    // An weaveback %set variable does NOT override a same-named store key —
    // store takes priority so scripts see the persistent value.
    let mut ev = evaluator();
    let src = r#"%rhaiset(x, store_val)
%set(x, weaveback_val)
%rhaidef(get_x, %{x%})
%get_x()"#;
    let result = eval_string(src, None, &mut ev).expect("eval failed");
    assert_eq!(result.trim(), "store_val");
}
// @

Python scripting (test_pydef.rs)

// <<test pydef>>=
// crates/weaveback-macro/src/evaluator/tests/test_pydef.rs

mod pydef_tests {
    use crate::evaluator::{EvalConfig, Evaluator, eval_string};

    fn evaluator() -> Evaluator {
        Evaluator::new(EvalConfig::default())
    }

    #[test]
    fn test_double() {
        let mut ev = evaluator();
        let src = r#"%pydef(double, x, %{str(int(x) * 2)%})
%double(21)"#;
        let result = eval_string(src, None, &mut ev).expect("eval failed");
        assert_eq!(result.trim(), "42");
    }

    #[test]
    fn test_offset() {
        let mut ev = evaluator();
        let src = r#"%pydef(offset, base, size, %{
str(int(base) + int(size))
%})
%offset(256, 64)"#;
        let result = eval_string(src, None, &mut ev).expect("eval failed");
        assert_eq!(result.trim(), "320");
    }

    #[test]
    fn test_greet() {
        let mut ev = evaluator();
        let src = r#"%pydef(greet, name, %{
"Hello, " + name + "!"
%})
%greet(world)"#;
        let result = eval_string(src, None, &mut ev).expect("eval failed");
        assert_eq!(result.trim(), "Hello, world!");
    }

    #[test]
    fn test_only_declared_params_visible() {
        let mut ev = evaluator();
        let src = r#"%set(secret, hidden)
%pydef(echo, x, %{x%})
%echo(visible)"#;
        let result = eval_string(src, None, &mut ev).expect("eval failed");
        assert_eq!(result.trim(), "visible");
    }

    #[test]
    fn test_error_propagation() {
        let mut ev = evaluator();
        let src = r#"%pydef(broken, x, %{1 / 0%})
%broken(foo)"#;
        let result = eval_string(src, None, &mut ev);
        assert!(result.is_err(), "expected error from division by zero");
    }

    // --- store tests ---

    #[test]
    fn test_pyset_pyget_roundtrip() {
        let mut ev = evaluator();
        let src = r#"%pyset(color, red)
%pyget(color)"#;
        let result = eval_string(src, None, &mut ev).expect("eval failed");
        assert_eq!(result.trim(), "red");
    }

    #[test]
    fn test_store_persists_across_calls() {
        let mut ev = evaluator();
        let src = r#"%pyset(counter, 0)
%pydef(increment, %{str(int(counter) + 1)%})
%pyset(counter, %increment())
%pyset(counter, %increment())
%pyget(counter)"#;
        let result = eval_string(src, None, &mut ev).expect("eval failed");
        assert_eq!(result.trim(), "2");
    }

    #[test]
    fn test_store_visible_in_script() {
        let mut ev = evaluator();
        let src = r#"%pyset(prefix, item_)
%pydef(tagged, name, %{prefix + name%})
%tagged(count)"#;
        let result = eval_string(src, None, &mut ev).expect("eval failed");
        assert_eq!(result.trim(), "item_count");
    }

    #[test]
    fn test_param_shadows_store_key() {
        let mut ev = evaluator();
        let src = r#"%pyset(x, store_value)
%pydef(identity, x, %{x%})
%identity(param_value)"#;
        let result = eval_string(src, None, &mut ev).expect("eval failed");
        assert_eq!(result.trim(), "param_value");
    }

    #[test]
    fn test_running_sum() {
        let mut ev = evaluator();
        let src = r#"%pyset(total, 0)
%pydef(add, n, %{str(int(total) + int(n))%})
%pyset(total, %add(10))
%pyset(total, %add(20))
%pyset(total, %add(12))
%pyget(total)"#;
        let result = eval_string(src, None, &mut ev).expect("eval failed");
        assert_eq!(result.trim(), "42");
    }
}
// @