pipa-js 0.1.1

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

#[test]
fn test_error_shows_stack_trace() {
    let mut rt = JSRuntime::new();
    let mut ctx = rt.new_context();

    let code = r#"
function inner() {
    throw new Error("test error");
}
function outer() {
    inner();
}
outer();
"#;

    let result = eval(&mut ctx, code);
    assert!(result.is_err(), "Expected an error");

    let error_msg = result.unwrap_err();

    assert!(
        error_msg.contains("Uncaught"),
        "Error should start with 'Uncaught': {}",
        error_msg
    );
    assert!(
        error_msg.contains("at"),
        "Error should contain 'at' for stack trace: {}",
        error_msg
    );

    println!("Error output:\n{}", error_msg);
}

#[test]
fn test_type_error_shows_traceback() {
    let mut rt = JSRuntime::new();
    let mut ctx = rt.new_context();

    let code = r#"
function foo() {
    var x = 123;
    x();  // TypeError: x is not a function
}
foo();
"#;

    let result = eval(&mut ctx, code);
    assert!(result.is_err(), "Expected a TypeError");

    let error_msg = result.unwrap_err();
    println!("TypeError output:\n{}", error_msg);

    assert!(
        error_msg.contains("at"),
        "Error should contain stack trace: {}",
        error_msg
    );
}

#[test]
fn test_nested_function_calls_traceback() {
    let mut rt = JSRuntime::new();
    let mut ctx = rt.new_context();

    let code = r#"
function level3() {
    throw "deep error";
}
function level2() {
    level3();
}
function level1() {
    level2();
}
level1();
"#;

    let result = eval(&mut ctx, code);
    assert!(result.is_err());

    let error_msg = result.unwrap_err();
    println!("Nested call error:\n{}", error_msg);

    assert!(
        error_msg.contains("Uncaught"),
        "Should show 'Uncaught': {}",
        error_msg
    );
}

#[test]
fn test_error_in_global_scope() {
    let mut rt = JSRuntime::new();
    let mut ctx = rt.new_context();

    let code = r#"throw "global error";"#;

    let result = eval(&mut ctx, code);
    assert!(result.is_err());

    let error_msg = result.unwrap_err();
    println!("Global error:\n{}", error_msg);

    assert!(error_msg.contains("Uncaught"));
    assert!(error_msg.contains("global error"));
}

#[test]
fn test_traceback_line_numbers_are_accurate() {
    let mut rt = JSRuntime::new();
    let mut ctx = rt.new_context();

    let code = r#"
function inner() {
    var x = 1;
    var y = 2;
    throw new Error("line 5 error");
}
function outer() {
    inner();
}
outer();
"#;

    let result = eval(&mut ctx, code);
    assert!(result.is_err());
    let error_msg = result.unwrap_err();
    println!("Line number accuracy test (stack VM):\n{}", error_msg);

    assert!(
        error_msg.contains("inner"),
        "Should show inner: {}",
        error_msg
    );
}

#[test]
fn test_register_vm_traceback_line_numbers() {
    let mut rt = JSRuntime::new();
    let mut ctx = rt.new_context();

    let code = r#"
function inner() {
    var x = 1;
    var y = 2;
    throw new Error("line 5 error");
}
function outer() {
    inner();
}
return outer();
"#;

    let result = eval_register(&mut ctx, code);
    assert!(result.is_err());
    let error_msg = result.unwrap_err();
    println!("Line number accuracy test (register VM):\n{}", error_msg);

    assert!(
        error_msg.contains(":5:") || error_msg.contains(":6:"),
        "Expected error around line 5, got:\n{}",
        error_msg
    );
}

#[test]
fn test_traceback_shows_full_call_chain() {
    let mut rt = JSRuntime::new();
    let mut ctx = rt.new_context();

    let code = r#"
function a() { b(); }
function b() { c(); }
function c() { d(); }
function d() { throw new Error("deep"); }
a();
"#;

    let result = eval(&mut ctx, code);
    assert!(result.is_err());
    let error_msg = result.unwrap_err();

    assert!(
        error_msg.contains("d"),
        "Should show d in stack: {}",
        error_msg
    );
}

#[test]
fn test_register_vm_traceback_full_call_chain() {
    let mut rt = JSRuntime::new();
    let mut ctx = rt.new_context();

    let code = r#"
function outer() {
    function a() { return b(); }
    function b() { return c(); }
    function c() { return d(); }
    function d() { throw new Error("deep"); }
    return a();
}
return outer();
"#;

    let result = eval_register(&mut ctx, code);
    assert!(result.is_err());
    let error_msg = result.unwrap_err();

    assert!(
        error_msg.contains("d"),
        "Should show d in stack: {}",
        error_msg
    );
    assert!(
        error_msg.contains("c"),
        "Should show c in stack: {}",
        error_msg
    );
    assert!(
        error_msg.contains("b"),
        "Should show b in stack: {}",
        error_msg
    );
    assert!(
        error_msg.contains("a"),
        "Should show a in stack: {}",
        error_msg
    );
    assert!(
        error_msg.contains("outer"),
        "Should show outer in stack: {}",
        error_msg
    );
}

use pipa::compiler::ast::BlockStatement;
use pipa::compiler::codegen::CodeGenerator;
use pipa::compiler::parser::Parser;
use pipa::runtime::vm::VM;

fn eval_register(ctx: &mut pipa::JSContext, code: &str) -> Result<pipa::JSValue, String> {
    let ast = Parser::new(code).parse()?;
    let mut codegen = CodeGenerator::new();
    let block = BlockStatement {
        body: ast.body,
        lines: ast.lines,
    };
    let (rb, _) = codegen.compile_function(&[], &block, ctx)?;
    let mut vm = VM::new();
    let outcome = vm.execute(ctx, &rb)?;
    match outcome {
        pipa::runtime::vm::ExecutionOutcome::Complete(v) => Ok(v),
        pipa::runtime::vm::ExecutionOutcome::Yield(v) => Ok(v),
    }
}

#[test]
fn test_register_vm_error_traceback() {
    let mut rt = JSRuntime::new();
    let mut ctx = rt.new_context();

    let code = r#"
function inner() {
    throw new Error("register vm error");
}
function outer() {
    inner();
}
return outer();
"#;

    let result = eval_register(&mut ctx, code);
    assert!(result.is_err(), "Expected an error in register VM");

    let error_msg = result.unwrap_err();
    println!("Register VM error output:\n{}", error_msg);

    assert!(
        error_msg.contains("Uncaught"),
        "Error should start with 'Uncaught': {}",
        error_msg
    );
    assert!(
        error_msg.contains("at"),
        "Error should contain 'at' for stack trace: {}",
        error_msg
    );
}

#[test]
fn test_register_vm_type_error_traceback() {
    let mut rt = JSRuntime::new();
    let mut ctx = rt.new_context();

    let code = r#"
function foo() {
    var x = 123;
    x();
}
return foo();
"#;

    let result = eval_register(&mut ctx, code);
    assert!(result.is_err(), "Expected a TypeError in register VM");

    let error_msg = result.unwrap_err();
    println!("Register VM TypeError output:\n{}", error_msg);

    assert!(
        error_msg.contains("at"),
        "Error should contain stack trace: {}",
        error_msg
    );
}

#[test]
fn test_register_vm_nested_calls_traceback() {
    let mut rt = JSRuntime::new();
    let mut ctx = rt.new_context();

    let code = r#"
function level3() {
    throw "deep error";
}
function level2() {
    level3();
}
function level1() {
    level2();
}
return level1();
"#;

    let result = eval_register(&mut ctx, code);
    assert!(result.is_err());

    let error_msg = result.unwrap_err();
    println!("Register VM nested call error:\n{}", error_msg);

    assert!(
        error_msg.contains("Uncaught"),
        "Should show 'Uncaught': {}",
        error_msg
    );
    assert!(
        error_msg.contains("level3"),
        "Should show level3 in stack: {}",
        error_msg
    );
    assert!(
        error_msg.contains("level2"),
        "Should show level2 in stack: {}",
        error_msg
    );
    assert!(
        error_msg.contains("level1"),
        "Should show level1 in stack: {}",
        error_msg
    );
}