pipa-js 0.1.1

A fast, minimal ES2023 JavaScript runtime built in Rust.
Documentation
use pipa::{JSRuntime, eval};

fn eval_int(code: &str) -> i64 {
    let mut runtime = JSRuntime::new();
    let mut ctx = runtime.new_context();
    let result = eval(&mut ctx, code);
    assert!(result.is_ok(), "JS error: {:?}", result);
    result.unwrap().get_int()
}

fn eval_ok(code: &str) {
    let mut runtime = JSRuntime::new();
    let mut ctx = runtime.new_context();
    let result = eval(&mut ctx, code);
    assert!(result.is_ok(), "JS error: {:?}", result);
}

#[test]
fn test_for_of_array_basic() {
    assert_eq!(
        eval_int("var sum = 0; for (var x of [1, 2, 3]) { sum += x; } sum"),
        6
    );
}

#[test]
fn test_for_of_array_empty() {
    assert_eq!(
        eval_int("var sum = 0; for (var x of []) { sum += x; } sum"),
        0
    );
}

#[test]
fn test_for_of_array_single() {
    assert_eq!(
        eval_int("var sum = 0; for (var x of [42]) { sum += x; } sum"),
        42
    );
}

#[test]
fn test_for_of_array_strings() {
    eval_ok(
        r#"
        var result = "";
        for (var s of ["hello", " ", "world"]) { result += s; }
        if (result !== "hello world") throw new Error("Expected 'hello world', got: " + result);
        "#,
    );
}

#[test]
fn test_for_of_array_with_break() {
    assert_eq!(
        eval_int(
            "var sum = 0; for (var x of [1, 2, 3, 4, 5]) { if (x === 3) break; sum += x; } sum"
        ),
        3
    );
}

#[test]
fn test_for_of_array_with_continue() {
    assert_eq!(
        eval_int(
            "var sum = 0; for (var x of [1, 2, 3, 4, 5]) { if (x === 3) continue; sum += x; } sum"
        ),
        12
    );
}

#[test]
fn test_for_of_array_let_binding() {
    eval_ok(
        r#"
        function test() {
            var results = [];
            for (let x of [10, 20, 30]) {
                results.push(x);
            }
            if (results.length !== 3) throw new Error("length: " + results.length);
            if (results[0] !== 10) throw new Error("results[0]: " + results[0]);
            if (results[1] !== 20) throw new Error("results[1]: " + results[1]);
            if (results[2] !== 30) throw new Error("results[2]: " + results[2]);
        }
        test();
        "#,
    );
}

#[test]
fn test_for_of_string_basic() {
    eval_ok(
        r#"
        var chars = "";
        for (var c of "abc") { chars += c; }
        if (chars !== "abc") throw new Error("Expected 'abc', got: " + chars);
        "#,
    );
}

#[test]
fn test_for_of_string_empty() {
    assert_eq!(
        eval_int(r#"var count = 0; for (var c of "") { count++; } count"#),
        0
    );
}

#[test]
fn test_for_of_string_count() {
    assert_eq!(
        eval_int(r#"var count = 0; for (var c of "hello") { count++; } count"#),
        5
    );
}

#[test]
fn test_for_of_nested() {
    assert_eq!(
        eval_int(
            r#"
        var sum = 0;
        for (var arr of [[1, 2], [3, 4]]) {
            for (var x of arr) {
                sum += x;
            }
        }
        sum
        "#
        ),
        10
    );
}

#[test]
fn test_for_of_existing_var() {
    assert_eq!(
        eval_int("var x = 0; var sum = 0; for (x of [10, 20, 30]) { sum += x; } sum"),
        60
    );
}

#[test]
fn test_for_of_var_after_loop() {
    assert_eq!(eval_int("var x = 0; for (x of [1, 2, 3]) {} x"), 3);
}

#[test]
fn test_super_this_binding() {
    eval_ok(
        r#"
        class Animal {
            constructor(name) { this.name = name; }
        }
        class Dog extends Animal {
            constructor(name) {
                super(name);
                this.type = "dog";
            }
        }
        var d = new Dog("Rex");
        if (d.name !== "Rex") throw new Error("name: " + d.name);
        if (d.type !== "dog") throw new Error("type: " + d.type);
        "#,
    );
}

#[test]
fn test_default_constructor_super() {
    eval_ok(
        r#"
        class Base {
            constructor(x) { this.x = x; }
        }
        class Derived extends Base {
            getX() { return this.x; }
        }
        var d = new Derived(42);
        if (d.getX() !== 42) throw new Error("getX: " + d.getX());
        "#,
    );
}

#[test]
fn test_default_constructor_multi_arg() {
    eval_ok(
        r#"
        class Base {
            constructor(a, b) { this.sum = a + b; }
        }
        class Derived extends Base {}
        var d = new Derived(3, 4);
        if (d.sum !== 7) throw new Error("sum: " + d.sum);
        "#,
    );
}

#[test]
fn test_generator_parses_and_creates_function() {
    eval_ok(
        "var gen = function*() { yield 1; }; if (typeof gen !== 'function') throw new Error('generator not a function');",
    );
}

#[test]
fn test_generator_object_has_next() {
    eval_ok(
        "var gen = function*() { yield 1; }; var g = gen(); if (typeof g.next !== 'function') throw new Error('generator object missing next');",
    );
}