diff --git a/crates/oxide_cli/tests/cli_e2e_tests.rs b/crates/oxide_cli/tests/cli_e2e_tests.rs new file mode 100644 index 00000000..88545b36 --- /dev/null +++ b/crates/oxide_cli/tests/cli_e2e_tests.rs @@ -0,0 +1,120 @@ +use std::process::Command; + +fn run_eval(js: &str) -> std::process::Output { + Command::new("cargo") + .args(["run", "--", "eval", js]) + .output() + .expect("failed to run oxide eval") +} + +#[test] +fn cli_e2e_arithmetic_precedence() { + let output = run_eval("1 + 2 * 3"); + assert!(output.status.success()); + assert_eq!(String::from_utf8_lossy(&output.stdout).trim(), "7"); +} + +#[test] +fn cli_e2e_arithmetic_infinity() { + let output = run_eval("1 / 0"); + assert!(output.status.success()); + assert_eq!(String::from_utf8_lossy(&output.stdout).trim(), "Infinity"); +} + +#[test] +fn cli_e2e_comparison_gt() { + let output = run_eval("5 > 3"); + assert!(output.status.success()); + assert_eq!(String::from_utf8_lossy(&output.stdout).trim(), "true"); +} + +#[test] +fn cli_e2e_comparison_nan() { + let output = run_eval("0 == 1"); + assert!(output.status.success()); + assert_eq!(String::from_utf8_lossy(&output.stdout).trim(), "false"); +} + +#[test] +fn cli_e2e_coercion_not_empty_string() { + let output = run_eval("!''"); + assert!(output.status.success()); + assert_eq!(String::from_utf8_lossy(&output.stdout).trim(), "true"); +} + +#[test] +fn cli_e2e_coercion_empty_string_falsy() { + let output = run_eval("if ('') { 1 } else { 2 }"); + assert!(output.status.success()); + assert_eq!(String::from_utf8_lossy(&output.stdout).trim(), "2"); +} + +#[test] +fn cli_e2e_var_binding_declaration() { + let output = run_eval("var x = 1; var y = 2; x + y"); + assert!(output.status.success()); + assert_eq!(String::from_utf8_lossy(&output.stdout).trim(), "3"); +} + +#[test] +fn cli_e2e_var_binding_auto_global() { + let output = run_eval("x = 5; x"); + assert!(output.status.success()); + assert_eq!(String::from_utf8_lossy(&output.stdout).trim(), "5"); +} + +#[test] +fn cli_e2e_control_flow_if_else() { + let output = run_eval("if (true) { 1 } else { 2 }"); + assert!(output.status.success()); + assert_eq!(String::from_utf8_lossy(&output.stdout).trim(), "1"); +} + +#[test] +fn cli_e2e_control_flow_for_loop() { + let output = run_eval("for (i=0; i<3; i=i+1) { } i"); + assert!(output.status.success()); + assert_eq!(String::from_utf8_lossy(&output.stdout).trim(), "3"); +} + +#[test] +fn cli_e2e_logical_and_short_circuit() { + let output = run_eval("var x=0; false && (x=5); x"); + assert!(output.status.success()); + assert_eq!(String::from_utf8_lossy(&output.stdout).trim(), "0"); +} + +#[test] +fn cli_e2e_logical_or_fallback() { + let output = run_eval("0 || 42"); + assert!(output.status.success()); + assert_eq!(String::from_utf8_lossy(&output.stdout).trim(), "42"); +} + +#[test] +fn cli_e2e_object_property_read() { + let output = run_eval("({a:1}).a"); + assert!(output.status.success()); + assert_eq!(String::from_utf8_lossy(&output.stdout).trim(), "1"); +} + +#[test] +fn cli_e2e_object_missing_property() { + let output = run_eval("({a:1}).b"); + assert!(output.status.success()); + assert_eq!(String::from_utf8_lossy(&output.stdout).trim(), "undefined"); +} + +#[test] +fn cli_e2e_syntax_error() { + let output = run_eval("function("); + assert!(!output.status.success()); + assert!(!String::from_utf8_lossy(&output.stderr).trim().is_empty()); +} + +#[test] +fn cli_e2e_control_flow_undefined_result() { + let output = run_eval("while (false) { }"); + assert!(output.status.success()); + assert_eq!(String::from_utf8_lossy(&output.stdout).trim(), "undefined"); +} diff --git a/crates/oxide_vm/tests/arithmetic_tests.rs b/crates/oxide_vm/tests/arithmetic_tests.rs new file mode 100644 index 00000000..fc9ff6a7 --- /dev/null +++ b/crates/oxide_vm/tests/arithmetic_tests.rs @@ -0,0 +1,65 @@ +use oxide_compiler::compiler::Compiler; +use oxide_parser::Allocator; +use oxide_vm::vm::Vm; + +fn eval(source: &str) -> String { + let allocator = Allocator::default(); + let program = match oxide_parser::parse(&allocator, source) { + Ok(p) => p, + Err(e) => return format!("parse error: {}", e[0].message), + }; + let module = match Compiler::new().compile(&program) { + Ok(m) => m, + Err(e) => return format!("compile error: {e}"), + }; + let mut vm = Vm::new(); + match vm.run(&module) { + Ok(result) => format!("{result}"), + Err(e) => format!("vm error: {e}"), + } +} + +#[test] +fn eval_arithmetic_add() { + assert_eq!(eval("1 + 2"), "3"); +} + +#[test] +fn eval_arithmetic_sub() { + assert_eq!(eval("5 - 3"), "2"); +} + +#[test] +fn eval_arithmetic_mul() { + assert_eq!(eval("3 * 4"), "12"); +} + +#[test] +fn eval_arithmetic_div() { + assert_eq!(eval("10 / 2"), "5"); +} + +#[test] +fn eval_arithmetic_mod() { + assert_eq!(eval("7 % 3"), "1"); +} + +#[test] +fn eval_precedence() { + assert_eq!(eval("1 * 2 + 3"), "5"); +} + +#[test] +fn eval_negation() { + assert_eq!(eval("-5"), "-5"); +} + +#[test] +fn eval_division_by_zero() { + assert_eq!(eval("1 / 0"), "Infinity"); +} + +#[test] +fn eval_string_concat() { + assert_eq!(eval("1 + 2 + 3"), "6"); +} diff --git a/crates/oxide_vm/tests/bench_tests.rs b/crates/oxide_vm/tests/bench_tests.rs new file mode 100644 index 00000000..df948882 --- /dev/null +++ b/crates/oxide_vm/tests/bench_tests.rs @@ -0,0 +1,37 @@ +use oxide_compiler::compiler::Compiler; +use oxide_parser::Allocator; +use oxide_vm::vm::Vm; + +#[test] +fn bench_1m_property_reads() { + use std::time::Instant; + + let allocator = Allocator::default(); + let source = "({a: 1, b: 2, c: 3, d: 4, e: 5}).e"; + let program = oxide_parser::parse(&allocator, source).expect("parse failed"); + let module = Compiler::new().compile(&program).expect("compile failed"); + + let mut vm = Vm::new(); + vm.run(&module).expect("vm run failed"); + for _ in 0..100 { + vm.rerun().ok(); + } + + const N: usize = 1_000_000; + let start = Instant::now(); + for _ in 0..N { + vm.rerun().ok(); + } + let elapsed = start.elapsed(); + let ns_per = elapsed.as_nanos() as f64 / N as f64; + println!( + "1M IC property reads: {:.2}ms ({:.0} ns/read)", + elapsed.as_secs_f64() * 1000.0, + ns_per + ); + if cfg!(debug_assertions) { + println!(" (debug build -- skipping timing assertion)"); + } else { + assert!(ns_per < 500.0, "{} ns/read exceeds 500ns target", ns_per); + } +} diff --git a/crates/oxide_vm/tests/coercion_tests.rs b/crates/oxide_vm/tests/coercion_tests.rs new file mode 100644 index 00000000..e1d38c8e --- /dev/null +++ b/crates/oxide_vm/tests/coercion_tests.rs @@ -0,0 +1,35 @@ +use oxide_compiler::compiler::Compiler; +use oxide_parser::Allocator; +use oxide_vm::vm::Vm; + +fn eval(source: &str) -> String { + let allocator = Allocator::default(); + let program = match oxide_parser::parse(&allocator, source) { + Ok(p) => p, + Err(e) => return format!("parse error: {}", e[0].message), + }; + let module = match Compiler::new().compile(&program) { + Ok(m) => m, + Err(e) => return format!("compile error: {e}"), + }; + let mut vm = Vm::new(); + match vm.run(&module) { + Ok(result) => format!("{result}"), + Err(e) => format!("vm error: {e}"), + } +} + +#[test] +fn eval_coercion_null_equals_null() { + assert_eq!(eval("null == null"), "true"); +} + +#[test] +fn eval_coercion_bool_equals_int() { + assert_eq!(eval("false == 0"), "true"); +} + +#[test] +fn eval_not_falsy() { + assert_eq!(eval("!0"), "true"); +} diff --git a/crates/oxide_vm/tests/comparison_tests.rs b/crates/oxide_vm/tests/comparison_tests.rs new file mode 100644 index 00000000..815d91ef --- /dev/null +++ b/crates/oxide_vm/tests/comparison_tests.rs @@ -0,0 +1,60 @@ +use oxide_compiler::compiler::Compiler; +use oxide_parser::Allocator; +use oxide_vm::vm::Vm; + +fn eval(source: &str) -> String { + let allocator = Allocator::default(); + let program = match oxide_parser::parse(&allocator, source) { + Ok(p) => p, + Err(e) => return format!("parse error: {}", e[0].message), + }; + let module = match Compiler::new().compile(&program) { + Ok(m) => m, + Err(e) => return format!("compile error: {e}"), + }; + let mut vm = Vm::new(); + match vm.run(&module) { + Ok(result) => format!("{result}"), + Err(e) => format!("vm error: {e}"), + } +} + +#[test] +fn eval_comparison_eq_true() { + assert_eq!(eval("1 == 1"), "true"); +} + +#[test] +fn eval_comparison_eq_false() { + assert_eq!(eval("1 == 2"), "false"); +} + +#[test] +fn eval_neq() { + assert_eq!(eval("1 != 2"), "true"); +} + +#[test] +fn eval_comparison_lt() { + assert_eq!(eval("3 < 4"), "true"); +} + +#[test] +fn eval_comparison_gt() { + assert_eq!(eval("4 > 5"), "false"); +} + +#[test] +fn eval_comparison_lte() { + assert_eq!(eval("3 <= 3"), "true"); +} + +#[test] +fn eval_comparison_gte() { + assert_eq!(eval("5 >= 4"), "true"); +} + +#[test] +fn eval_string_number_eq() { + assert_eq!(eval("5 == 5"), "true"); +} diff --git a/crates/oxide_vm/tests/control_flow_tests.rs b/crates/oxide_vm/tests/control_flow_tests.rs new file mode 100644 index 00000000..ba843158 --- /dev/null +++ b/crates/oxide_vm/tests/control_flow_tests.rs @@ -0,0 +1,144 @@ +use oxide_compiler::compiler::Compiler; +use oxide_parser::Allocator; +use oxide_vm::vm::Vm; + +fn eval(source: &str) -> String { + let allocator = Allocator::default(); + let program = match oxide_parser::parse(&allocator, source) { + Ok(p) => p, + Err(e) => return format!("parse error: {}", e[0].message), + }; + let module = match Compiler::new().compile(&program) { + Ok(m) => m, + Err(e) => return format!("compile error: {e}"), + }; + let mut vm = Vm::new(); + match vm.run(&module) { + Ok(result) => format!("{result}"), + Err(e) => format!("vm error: {e}"), + } +} + +#[test] +fn eval_if_true() { + assert_eq!(eval("if (true) { 1 } else { 2 }"), "1"); +} + +#[test] +fn eval_if_false() { + assert_eq!(eval("if (false) { 1 } else { 2 }"), "2"); +} + +#[test] +fn eval_if_without_else_true() { + assert_eq!(eval("if (true) { 42 }"), "42"); +} + +#[test] +fn eval_if_without_else_false() { + assert_eq!(eval("if (false) { 42 }"), "undefined"); +} + +#[test] +fn eval_dangling_else() { + assert_eq!(eval("if (true) { if (false) { 1 } else { 2 } }"), "2"); +} + +#[test] +fn eval_while_zero_iterations() { + assert_eq!(eval("var x = 0; while (false) { x = 1; } x"), "0"); +} + +#[test] +fn eval_while_multi_iterations() { + assert_eq!(eval("var i = 0; while (i < 3) { i = i + 1; } i"), "3"); +} + +#[test] +fn eval_while_result_undefined() { + assert_eq!(eval("while (false) { 1; }"), "undefined"); +} + +#[test] +fn eval_for_basic() { + assert_eq!( + eval("var r = 0; for (i = 0; i < 3; i = i + 1) { r = r + i; } r"), + "3" + ); +} + +#[test] +fn eval_for_no_test() { + assert_eq!( + eval("var r = 0; for (i = 0; ; i = i + 1) { if (i >= 3) { break; } r = r + i; } r"), + "3" + ); +} + +#[test] +fn eval_for_no_init_update() { + assert_eq!(eval("var i = 0; for (; i < 3; ) { i = i + 1; } i"), "3"); +} + +#[test] +fn eval_ternary_true() { + assert_eq!(eval("true ? 1 : 2"), "1"); +} + +#[test] +fn eval_ternary_false() { + assert_eq!(eval("false ? 1 : 2"), "2"); +} + +#[test] +fn eval_ternary_nested() { + assert_eq!(eval("true ? (false ? 1 : 2) : 3"), "2"); +} + +#[test] +fn eval_break_in_while() { + assert_eq!( + eval("var i = 0; while (true) { i = i + 1; if (i >= 3) { break; } } i"), + "3" + ); +} + +#[test] +fn eval_continue_in_while() { + assert_eq!( + eval("var i = 0; var r = 0; while (i < 5) { i = i + 1; if (i == 3) { continue; } r = r + 1; } r"), + "4" + ); +} + +#[test] +fn eval_break_in_nested_loop() { + assert_eq!( + eval("var x = 0; while (true) { while (true) { x = x + 1; if (x >= 3) { break; } } break; } x"), + "3" + ); +} + +#[test] +fn eval_break_in_for() { + assert_eq!( + eval("var r = 0; for (i = 0; i < 10; i = i + 1) { if (i >= 3) { break; } r = r + i; } r"), + "3" + ); +} + +#[test] +fn eval_continue_in_for() { + assert_eq!( + eval("var r = 0; for (i = 0; i < 5; i = i + 1) { if (i == 3) { continue; } r = r + i; } r"), + "7" + ); +} + +#[test] +fn eval_control_flow_complex() { + assert_eq!( + eval("var r = 0; for (i = 0; i < 5; i = i + 1) { if (i == 2) { continue; } r = r + i; } r"), + "8" + ); +} diff --git a/crates/oxide_vm/tests/logical_tests.rs b/crates/oxide_vm/tests/logical_tests.rs new file mode 100644 index 00000000..20717ad0 --- /dev/null +++ b/crates/oxide_vm/tests/logical_tests.rs @@ -0,0 +1,80 @@ +use oxide_compiler::compiler::Compiler; +use oxide_parser::Allocator; +use oxide_vm::vm::Vm; + +fn eval(source: &str) -> String { + let allocator = Allocator::default(); + let program = match oxide_parser::parse(&allocator, source) { + Ok(p) => p, + Err(e) => return format!("parse error: {}", e[0].message), + }; + let module = match Compiler::new().compile(&program) { + Ok(m) => m, + Err(e) => return format!("compile error: {e}"), + }; + let mut vm = Vm::new(); + match vm.run(&module) { + Ok(result) => format!("{result}"), + Err(e) => format!("vm error: {e}"), + } +} + +#[test] +fn eval_not_true() { + assert_eq!(eval("!true"), "false"); +} + +#[test] +fn eval_not_false() { + assert_eq!(eval("!false"), "true"); +} + +#[test] +fn eval_not_zero() { + assert_eq!(eval("!0"), "true"); +} + +#[test] +fn eval_not_string() { + assert_eq!(eval("!'hello'"), "false"); +} + +#[test] +fn eval_and_short_circuit() { + assert_eq!(eval("var x = 0; false && (x = 5); x"), "0"); +} + +#[test] +fn eval_and_truthy() { + assert_eq!(eval("1 && 2"), "2"); +} + +#[test] +fn eval_and_falsy_first() { + assert_eq!(eval("0 && 2"), "0"); +} + +#[test] +fn eval_or_short_circuit() { + assert_eq!(eval("var x = 0; true || (x = 5); x"), "0"); +} + +#[test] +fn eval_or_truthy_first() { + assert_eq!(eval("1 || 2"), "1"); +} + +#[test] +fn eval_or_falsy_both() { + assert_eq!(eval("0 || false"), "false"); +} + +#[test] +fn eval_and_chain() { + assert_eq!(eval("1 && 2 && 3"), "3"); +} + +#[test] +fn eval_or_chain() { + assert_eq!(eval("0 || false || 42"), "42"); +} diff --git a/crates/oxide_vm/tests/object_tests.rs b/crates/oxide_vm/tests/object_tests.rs new file mode 100644 index 00000000..3f6494e8 --- /dev/null +++ b/crates/oxide_vm/tests/object_tests.rs @@ -0,0 +1,44 @@ +use oxide_compiler::compiler::Compiler; +use oxide_parser::Allocator; +use oxide_vm::vm::Vm; + +fn eval(source: &str) -> String { + let allocator = Allocator::default(); + let program = match oxide_parser::parse(&allocator, source) { + Ok(p) => p, + Err(e) => return format!("parse error: {}", e[0].message), + }; + let module = match Compiler::new().compile(&program) { + Ok(m) => m, + Err(e) => return format!("compile error: {e}"), + }; + let mut vm = Vm::new(); + match vm.run(&module) { + Ok(result) => format!("{result}"), + Err(e) => format!("vm error: {e}"), + } +} + +#[test] +fn object_create_and_read() { + let allocator = Allocator::default(); + let source = "({a:1})"; + let program = oxide_parser::parse(&allocator, source).expect("parse failed"); + let module = Compiler::new().compile(&program).expect("compile failed"); + let mut vm = Vm::new(); + let obj = vm.run(&module).expect("vm run failed"); + assert!(obj.is_object()); + let obj_ref = unsafe { &*obj.as_js_object_ptr() }; + assert_eq!(obj_ref.prop_count(), 1, "object should have 1 property"); + assert!(obj_ref.get_inline_prop(0).is_int()); +} + +#[test] +fn eval_object_property_read() { + assert_eq!(eval("({a:1,b:2}).b"), "2"); +} + +#[test] +fn eval_object_missing_property() { + assert_eq!(eval("({a:1}).b"), "undefined"); +} diff --git a/crates/oxide_vm/tests/regression_tests.rs b/crates/oxide_vm/tests/regression_tests.rs new file mode 100644 index 00000000..e51c5ae1 --- /dev/null +++ b/crates/oxide_vm/tests/regression_tests.rs @@ -0,0 +1,39 @@ +use oxide_compiler::compiler::Compiler; +use oxide_parser::Allocator; +use oxide_vm::vm::Vm; + +fn eval(source: &str) -> String { + let allocator = Allocator::default(); + let program = match oxide_parser::parse(&allocator, source) { + Ok(p) => p, + Err(e) => return format!("parse error: {}", e[0].message), + }; + let module = match Compiler::new().compile(&program) { + Ok(m) => m, + Err(e) => return format!("compile error: {e}"), + }; + let mut vm = Vm::new(); + match vm.run(&module) { + Ok(result) => format!("{result}"), + Err(e) => format!("vm error: {e}"), + } +} + +#[test] +fn regression_to_boolean_empty_string() { + assert_eq!(eval("!''"), "true", "NOT empty string should be true"); + assert_eq!( + eval("if ('') { 1 } else { 2 }"), + "2", + "empty string should be falsy" + ); +} + +#[test] +fn regression_continue_in_for() { + assert_eq!( + eval("var r=0; for(i=0;i<3;i=i+1){if(i==1)continue;r=r+i;}r"), + "2", + "continue should skip iteration body" + ); +} diff --git a/crates/oxide_vm/tests/var_binding_tests.rs b/crates/oxide_vm/tests/var_binding_tests.rs new file mode 100644 index 00000000..466a0be9 --- /dev/null +++ b/crates/oxide_vm/tests/var_binding_tests.rs @@ -0,0 +1,40 @@ +use oxide_compiler::compiler::Compiler; +use oxide_parser::Allocator; +use oxide_vm::vm::Vm; + +fn eval(source: &str) -> String { + let allocator = Allocator::default(); + let program = match oxide_parser::parse(&allocator, source) { + Ok(p) => p, + Err(e) => return format!("parse error: {}", e[0].message), + }; + let module = match Compiler::new().compile(&program) { + Ok(m) => m, + Err(e) => return format!("compile error: {e}"), + }; + let mut vm = Vm::new(); + match vm.run(&module) { + Ok(result) => format!("{result}"), + Err(e) => format!("vm error: {e}"), + } +} + +#[test] +fn eval_var_declaration() { + assert_eq!(eval("var x = 42"), "42"); +} + +#[test] +fn eval_multi_stmt_last_value() { + assert_eq!(eval("1; 2; 3"), "3"); +} + +#[test] +fn eval_reassignment() { + assert_eq!(eval("var x = 1; x = 2; x"), "2"); +} + +#[test] +fn eval_multi_var_declaration() { + assert_eq!(eval("var x = 1; var y = 2; x + y"), "3"); +} diff --git a/crates/oxide_vm/tests/vm_tests.rs b/crates/oxide_vm/tests/vm_tests.rs deleted file mode 100644 index e2b10913..00000000 --- a/crates/oxide_vm/tests/vm_tests.rs +++ /dev/null @@ -1,341 +0,0 @@ -use oxide_compiler::compiler::Compiler; -use oxide_parser::Allocator; -use oxide_types::value::JsValue; -use oxide_vm::vm::Vm; - -fn eval(source: &str) -> String { - let allocator = Allocator::default(); - let program = match oxide_parser::parse(&allocator, source) { - Ok(p) => p, - Err(e) => return format!("parse error: {}", e[0].message), - }; - let module = match Compiler::new().compile(&program) { - Ok(m) => m, - Err(e) => return format!("compile error: {e}"), - }; - let mut vm = Vm::new(); - match vm.run(&module) { - Ok(result) => format!("{result}"), - Err(e) => format!("vm error: {e}"), - } -} - -fn eval_val(source: &str) -> JsValue { - let allocator = Allocator::default(); - let program = oxide_parser::parse(&allocator, source).expect("parse failed"); - let module = Compiler::new().compile(&program).expect("compile failed"); - let mut vm = Vm::new(); - vm.run(&module).expect("vm run failed") -} - -#[test] -fn object_create_and_read() { - let obj = eval_val("({a:1})"); - assert!(obj.is_object()); - let obj_ref = unsafe { &*obj.as_js_object_ptr() }; - assert_eq!(obj_ref.prop_count(), 1, "object should have 1 property"); - assert!(obj_ref.get_inline_prop(0).is_int()); -} - -#[test] -fn bench_1m_property_reads() { - use std::time::Instant; - - let allocator = Allocator::default(); - let source = "({a: 1, b: 2, c: 3, d: 4, e: 5}).e"; - let program = oxide_parser::parse(&allocator, source).expect("parse failed"); - let module = Compiler::new().compile(&program).expect("compile failed"); - - let mut vm = Vm::new(); - vm.run(&module).expect("vm run failed"); - for _ in 0..100 { - vm.rerun().ok(); - } - - const N: usize = 1_000_000; - let start = Instant::now(); - for _ in 0..N { - vm.rerun().ok(); - } - let elapsed = start.elapsed(); - let ns_per = elapsed.as_nanos() as f64 / N as f64; - println!( - "1M IC property reads: {:.2}ms ({:.0} ns/read)", - elapsed.as_secs_f64() * 1000.0, - ns_per - ); - if cfg!(debug_assertions) { - println!(" (debug build -- skipping timing assertion)"); - } else { - assert!(ns_per < 500.0, "{} ns/read exceeds 500ns target", ns_per); - } -} - -#[test] -fn eval_arithmetic_add() { - assert_eq!(eval("1 + 2"), "3"); -} - -#[test] -fn eval_arithmetic_sub() { - assert_eq!(eval("5 - 3"), "2"); -} - -#[test] -fn eval_arithmetic_mul() { - assert_eq!(eval("3 * 4"), "12"); -} - -#[test] -fn eval_arithmetic_div() { - assert_eq!(eval("10 / 2"), "5"); -} - -#[test] -fn eval_arithmetic_mod() { - assert_eq!(eval("7 % 3"), "1"); -} - -#[test] -fn eval_comparison_eq_true() { - assert_eq!(eval("1 == 1"), "true"); -} - -#[test] -fn eval_comparison_eq_false() { - assert_eq!(eval("1 == 2"), "false"); -} - -#[test] -fn eval_neq() { - assert_eq!(eval("1 != 2"), "true"); -} - -#[test] -fn eval_comparison_lt() { - assert_eq!(eval("3 < 4"), "true"); -} - -#[test] -fn eval_comparison_gt() { - assert_eq!(eval("4 > 5"), "false"); -} - -#[test] -fn eval_comparison_lte() { - assert_eq!(eval("3 <= 3"), "true"); -} - -#[test] -fn eval_comparison_gte() { - assert_eq!(eval("5 >= 4"), "true"); -} - -#[test] -fn eval_coercion_null_equals_null() { - assert_eq!(eval("null == null"), "true"); -} - -#[test] -fn eval_coercion_bool_equals_int() { - assert_eq!(eval("false == 0"), "true"); -} - -#[test] -fn eval_precedence() { - assert_eq!(eval("1 * 2 + 3"), "5"); -} - -#[test] -fn eval_negation() { - assert_eq!(eval("-5"), "-5"); -} - -#[test] -fn eval_division_by_zero() { - assert_eq!(eval("1 / 0"), "Infinity"); -} - -#[test] -fn eval_multi_stmt_last_value() { - assert_eq!(eval("1; 2; 3"), "3"); -} - -#[test] -fn eval_var_declaration() { - assert_eq!(eval("var x = 42"), "42"); -} - -#[test] -fn eval_if_true() { - assert_eq!(eval("if (true) { 1 } else { 2 }"), "1"); -} - -#[test] -fn eval_if_false() { - assert_eq!(eval("if (false) { 1 } else { 2 }"), "2"); -} - -#[test] -fn eval_if_without_else_true() { - assert_eq!(eval("if (true) { 42 }"), "42"); -} - -#[test] -fn eval_if_without_else_false() { - assert_eq!(eval("if (false) { 42 }"), "undefined"); -} - -#[test] -fn eval_dangling_else() { - assert_eq!(eval("if (true) { if (false) { 1 } else { 2 } }"), "2"); -} - -#[test] -fn eval_while_zero_iterations() { - assert_eq!(eval("var x = 0; while (false) { x = 1; } x"), "0"); -} - -#[test] -fn eval_while_multi_iterations() { - assert_eq!(eval("var i = 0; while (i < 3) { i = i + 1; } i"), "3"); -} - -#[test] -fn eval_while_result_undefined() { - assert_eq!(eval("while (false) { 1; }"), "undefined"); -} - -#[test] -fn eval_for_basic() { - assert_eq!( - eval("var r = 0; for (i = 0; i < 3; i = i + 1) { r = r + i; } r"), - "3" - ); -} - -#[test] -fn eval_for_no_test() { - assert_eq!( - eval("var r = 0; for (i = 0; ; i = i + 1) { if (i >= 3) { break; } r = r + i; } r"), - "3" - ); -} - -#[test] -fn eval_for_no_init_update() { - assert_eq!(eval("var i = 0; for (; i < 3; ) { i = i + 1; } i"), "3"); -} - -#[test] -fn eval_ternary_true() { - assert_eq!(eval("true ? 1 : 2"), "1"); -} - -#[test] -fn eval_ternary_false() { - assert_eq!(eval("false ? 1 : 2"), "2"); -} - -#[test] -fn eval_ternary_nested() { - assert_eq!(eval("true ? (false ? 1 : 2) : 3"), "2"); -} - -#[test] -fn eval_not_true() { - assert_eq!(eval("!true"), "false"); -} - -#[test] -fn eval_not_false() { - assert_eq!(eval("!false"), "true"); -} - -#[test] -fn eval_not_zero() { - assert_eq!(eval("!0"), "true"); -} - -#[test] -fn eval_not_string() { - assert_eq!(eval("!'hello'"), "false"); -} - -#[test] -fn eval_and_short_circuit() { - assert_eq!(eval("var x = 0; false && (x = 5); x"), "0"); -} - -#[test] -fn eval_and_truthy() { - assert_eq!(eval("1 && 2"), "2"); -} - -#[test] -fn eval_and_falsy_first() { - assert_eq!(eval("0 && 2"), "0"); -} - -#[test] -fn eval_or_short_circuit() { - assert_eq!(eval("var x = 0; true || (x = 5); x"), "0"); -} - -#[test] -fn eval_or_truthy_first() { - assert_eq!(eval("1 || 2"), "1"); -} - -#[test] -fn eval_or_falsy_both() { - assert_eq!(eval("0 || false"), "false"); -} - -#[test] -fn eval_break_in_while() { - assert_eq!( - eval("var i = 0; while (true) { i = i + 1; if (i >= 3) { break; } } i"), - "3" - ); -} - -#[test] -fn eval_continue_in_while() { - assert_eq!( - eval("var i = 0; var r = 0; while (i < 5) { i = i + 1; if (i == 3) { continue; } r = r + 1; } r"), - "4" - ); -} - -#[test] -fn eval_break_in_nested_loop() { - assert_eq!( - eval("var x = 0; while (true) { while (true) { x = x + 1; if (x >= 3) { break; } } break; } x"), - "3" - ); -} - -#[test] -fn eval_break_in_for() { - assert_eq!( - eval("var r = 0; for (i = 0; i < 10; i = i + 1) { if (i >= 3) { break; } r = r + i; } r"), - "3" - ); -} - -#[test] -fn eval_continue_in_for() { - assert_eq!( - eval("var r = 0; for (i = 0; i < 5; i = i + 1) { if (i == 3) { continue; } r = r + i; } r"), - "7" - ); -} - -#[test] -fn eval_control_flow_complex() { - assert_eq!( - eval("var r = 0; for (i = 0; i < 5; i = i + 1) { if (i == 2) { continue; } r = r + i; } r"), - "8" - ); -}