finx 0.1.0

A fast, lightweight embeddable scripting language
Documentation
#[cfg(test)]
mod tests {
    use std::rc::Rc;

    use finx::{Finx, Value};

    // Helper to capture stdout for assertions
    fn run_and_capture(source: &str) -> Vec<String> {
        let mut engine = Finx::new();
        engine.execute(source).expect("Execution failed");
        engine.get_output()
    }

    #[test]
    fn test_arithmetic_and_comparison() {
        let src = r#"
            print(1 + 2);
            print(10 - 3);
            print(2 * 4);
            print(20 / 5);
            print(3 == 3);
            print(3 != 4);
            print(5 < 10);
            print(5 <= 5);
            print(7 > 2);
            print(7 >= 7);
        "#;
        let out = run_and_capture(src);
        assert_eq!(
            out,
            [
                "3", "7", "8", "4", "true", "true", "true", "true", "true", "true"
            ]
        );
    }

    #[test]
    fn test_shadowing_and_reassignment() {
        let src = r#"
            let x = 1;
            print(x); // 1
            fn f() {
                let x = 2;
                print(x); // 2
                x = 3;
                print(x); // 3
            }
            f();
            print(x); // 1
            x = 42;
            print(x); // 42
        "#;
        let out = run_and_capture(src);
        assert_eq!(out, ["1", "2", "3", "1", "42"]);
    }

    #[test]
    fn test_closures_and_upvalues() {
        let src = r#"
            fn make_adder(x) {
                fn adder(y) {
                    return x + y;
                }
                return adder;
            }
            let add10 = make_adder(10);
            print(add10(5)); // 15
            let add42 = make_adder(42);
            print(add42(1)); // 43
        "#;
        let out = run_and_capture(src);
        assert_eq!(out, ["15", "43"]);
    }

    #[test]
    fn test_if_else_and_nested_if() {
        let src = r#"
            fn test(a, b) {
                if a > b {
                    if a > 10 {
                        print("a is largest and > 10");
                    } else {
                        print("a is largest but <= 10");
                    }
                } else {
                    print("b is largest or equal");
                }
            }
            test(15, 5);
            test(8, 8);
        "#;
        let out = run_and_capture(src);
        assert_eq!(out, ["a is largest and > 10", "b is largest or equal"]);
    }

    #[test]
    fn test_block_expressions() {
        let src = r#"
            let z = {
                let t = 7;
                t + 1
            };
            print(z); // 8
        "#;
        let out = run_and_capture(src);
        assert_eq!(out, ["8"]);
    }

    #[test]
    fn test_higher_order_and_recursion() {
        let src = r#"
            fn fact(n) {
                if n <= 1 {
                    return 1;
                } else {
                    return n * fact(n - 1);
                }
            }

            print(fact(5)); // 120

            fn twice(f, x) {
                return f(f(x));
            }
            fn inc(y) { return y + 1; }
            print(twice(inc, 3)); // 5
        "#;
        let out = run_and_capture(src);
        assert_eq!(out, ["120", "5"]);
    }

    #[test]
    fn test_counter_closure_mutation() {
        let src = r#"
            fn counter() {
                let count = 0;
                fn inc() {
                    count = count + 1;
                    print(count);
                }
                return inc;
            }
            let c = counter();
            c(); // 1
            c(); // 2
            c(); // 3
        "#;
        let out = run_and_capture(src);
        assert_eq!(out, ["1", "2", "3"]);
    }

    #[test]
    fn test_if_closure_capture() {
        let src = r#"
            fn test_if_closure(x) {
                if x > 0 {
                    let y = x * 2;
                    fn show() { print(y); }
                    show();
                } else {
                    let y = -x;
                    fn show() { print(y); }
                    show();
                }
            }
            test_if_closure(5);  // 10
            test_if_closure(-3); // 3
        "#;
        let out = run_and_capture(src);
        assert_eq!(out, ["10", "3"]);
    }

    #[test]
    fn test_native_functions() {
        // Create an engine and register some native functions
        let mut engine = Finx::new(); // Use with_defaults to get print function

        // Register a simple math function
        engine.register_function_ptr(
            "native_add",
            |args| {
                if args.len() != 2 {
                    return Value::Null;
                }
                if let (Some(a), Some(b)) = (args[0].as_num(), args[1].as_num()) {
                    Value::Number(a + b)
                } else {
                    Value::Null
                }
            },
            2,
        );

        // Register a string manipulation function
        engine.register_function_ptr(
            "native_upper",
            |args| {
                if args.len() != 1 {
                    return Value::Null;
                }
                if let Some(s) = args[0].as_str() {
                    Value::Str(Rc::new(s.to_uppercase()))
                } else {
                    Value::Null
                }
            },
            1,
        );

        // Register a math function with more complex logic
        engine.register_function_ptr(
            "native_factorial",
            |args| {
                if args.len() != 1 {
                    return Value::Null;
                }
                if let Some(n) = args[0].as_num() {
                    if n < 0.0 || n.fract() != 0.0 {
                        return Value::Null;
                    }
                    let n = n as u64;
                    let mut result = 1u64;
                    for i in 2..=n {
                        result *= i;
                    }
                    Value::Number(result as f64)
                } else {
                    Value::Null
                }
            },
            1,
        );

        // Test the native functions
        let src = r#"
            let result1 = native_add(5, 3);
            print(result1); // Should print 8
            
            let result2 = native_upper("hello world");
            print(result2); // Should print "HELLO WORLD"
            
            let result3 = native_factorial(5);
            print(result3); // Should print 120
        "#;

        engine.execute(src).expect("Execution failed");
        let output = engine.get_output();
        assert_eq!(output, ["8", "HELLO WORLD", "120"]);
    }
    #[test]
    fn test_native_function_error_handling() {
        let mut engine = Finx::new(); // Use with_defaults for consistency

        // Register a function that expects exactly 2 arguments
        engine.register_function_ptr(
            "test_func",
            |args| {
                if args.len() != 2 {
                    return Value::Null;
                }
                Value::Number(42.0)
            },
            2,
        );

        // Test with wrong number of arguments - this should return an error at runtime
        let src = r#"
            test_func(1); // Wrong number of arguments
        "#;

        // This should return an error due to argument count mismatch
        let result = engine.execute(src);
        assert!(result.is_err());
    }
}