Files
OxideJS-New/crates/oxide_vm/tests/control_flow_tests.rs
T

341 lines
8.7 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
use std::sync::Arc;
use oxide_compiler::compiler::Compiler;
use oxide_compiler::DefaultCompilerService;
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();
vm.set_compiler_service(Arc::new(DefaultCompilerService));
match vm.run(&Arc::new(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_if_no_else_false_branch_undefined() {
// 顶层脚本完成值直读 if 的 result_reg:假支前有用后死的 vreg(x 持 1,
// 被 keep 调用读取后死亡),其物理槽可能与 result_reg 别名。假支必须显式
// 写 undefined,不得泄漏该槽前值。
assert_eq!(eval("let x = 1; function keep(v){ return v; } keep(x); if (false) { }"), "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"
);
}
#[test]
fn eval_do_while_body_executes_once() {
assert_eq!(eval("var x = 0; do { x = 1; } while (false); x"), "1");
}
#[test]
fn eval_do_while_multi_iteration() {
assert_eq!(eval("var i = 0; do { i = i + 1; } while (i < 3); i"), "3");
}
#[test]
fn eval_do_while_break() {
assert_eq!(eval("var i = 0; do { i = i + 1; if (i >= 3) break; } while (true); i"), "3");
}
#[test]
fn eval_do_while_continue() {
assert_eq!(
eval("var i = 0; var r = 0; do { i = i + 1; if (i == 3) continue; r = r + 1; } while (i < 5); r"),
"4"
);
}
#[test]
fn eval_do_while_continue_jumps_to_condition() {
// continue 直达条件位置:i 到 3 后 continue,条件 3<3 为假即退,结果 3;
// 若 continue 重入体首(跳过条件求值)会多跑一轮得 4。
assert_eq!(eval("var i = 0; do { i = i + 1; if (i == 3) continue; } while (i < 3); i"), "3");
}
#[test]
fn eval_for_in_enumerates_keys() {
assert_eq!(eval("var n=0; for (k in {a:1,b:2}) { n=n+1; } n"), "2");
}
#[test]
fn eval_for_in_empty_object() {
assert_eq!(eval("var r=0; for (k in {}) { r=1; } r"), "0");
}
#[test]
fn eval_for_in_primitive_number() {
// 右值经 ToObject 装箱后枚举:数字盒无自身可枚举属性,空枚举。
assert_eq!(eval("var n=0; for (k in 42) { n=n+1; } n"), "0");
}
#[test]
fn eval_for_in_nested() {
assert_eq!(eval("var r=0; for (k in {a:{x:1}}) { for (j in {y:2}) { r=r+1; } } r"), "1");
}
#[test]
fn eval_for_in_break() {
assert_eq!(eval("var r=0; for (k in {a:1,b:2,c:3}) { if (k=='b') break; r=r+1; } r"), "1");
}
#[test]
fn eval_for_in_continue() {
assert_eq!(eval("var r=0; for (k in {a:1,b:2}) { if (k=='a') continue; r=r+1; } r"), "1");
}
#[test]
fn eval_for_in_let_scoping() {
let result = eval("var r=0; for (let k in {a:1}) { r=1; } r");
assert_eq!(result, "1");
}
#[test]
fn eval_adjacent_for_of_lexical_bindings_do_not_conflict() {
assert_eq!(
eval("let out=0; for (const [arg, descr] of [[1, 'a']]) out += arg; for (const [arg, descr] of [[2, 'b']]) out += arg; out"),
"3"
);
}
#[test]
fn eval_switch_basic_match() {
assert_eq!(eval("var x=0;switch(2){case 1:x=10;case 2:x=20;break;default:x=30;}x"), "20");
}
#[test]
fn eval_switch_fallthrough() {
assert_eq!(eval("var x=0;switch(1){case 1:case 2:x=42;}x"), "42");
}
#[test]
fn eval_switch_default() {
assert_eq!(eval("var x=0;switch(99){case 1:x=10;default:x=30;}x"), "30");
}
#[test]
fn eval_switch_no_match_no_default() {
assert_eq!(eval("var x=0;switch(99){case 1:x=10;}x"), "0");
}
#[test]
fn eval_switch_break() {
assert_eq!(eval("var x=0;switch(1){case 1:x=10;break;case 2:x=20;}x"), "10");
}
#[test]
fn eval_break_in_switch_in_loop() {
assert_eq!(eval("var r=0;while(true){switch(1){case 1:r=1;break;}r=2;break;}r"), "2");
}
#[test]
fn eval_large_adjacent_switches_do_not_collide_labels() {
let mut source = String::from("var r=0;switch(256){");
for i in 0..=256 {
source.push_str(&format!("case {i}:"));
if i == 256 {
source.push_str("r=1;break;");
}
}
source.push_str("}switch(0){case 0:r=r+1;break;}r");
assert_eq!(eval(&source), "2");
}
#[test]
fn eval_sequence_expression_returns_last() {
assert_eq!(eval("var x = 0; (x = 1, x = x + 2, x)"), "3");
assert_eq!(eval("(1, 2, 3)"), "3");
}
#[test]
fn eval_sequence_expression_in_for_clauses() {
assert_eq!(eval("for (var i = (0, 1); i < 3; i = (i + 1, i + 1)) {} i"), "3");
}
#[test]
fn eval_empty_statement_is_noop() {
assert_eq!(eval("while (false); 1"), "1");
assert_eq!(eval("; ; var x = 1; x"), "1");
}
#[test]
fn eval_labeled_break_exits_loop() {
assert_eq!(eval("outer: while (true) { break outer; } 7"), "7");
}
#[test]
fn eval_labeled_break_exits_block() {
assert_eq!(eval("label: { break label; } 5"), "5");
}
#[test]
fn eval_labeled_continue_loop() {
assert_eq!(eval("var i = 0; outer: while (i < 3) { i = i + 1; continue outer; } i"), "3");
}
#[test]
fn eval_labeled_continue_outer_from_inner_loop() {
assert_eq!(
eval("var c = 0; outer: for (var i = 0; i < 3; i++) { for (var j = 0; j < 3; j++) { if (j == 1) continue outer; c = c + 1; } } c"),
"3"
);
}
#[test]
fn eval_labeled_break_outer_from_inner_loop() {
assert_eq!(
eval("var c = 0; outer: for (var i = 0; i < 3; i++) { for (var j = 0; j < 3; j++) { if (i == 1 && j == 1) break outer; c = c + 1; } } c"),
"4"
);
}
#[test]
fn eval_duplicate_label_is_compile_error() {
let r = eval("a: a: while (false) {}");
assert!(
(r.contains("parse error") || r.contains("compile error")) && r.contains("already been declared"),
"got: {r}"
);
}
#[test]
fn eval_continue_to_non_loop_label_is_compile_error() {
let r = eval("label: { continue label; }");
assert!(
(r.contains("parse error") || r.contains("compile error")) && r.contains("continue"),
"got: {r}"
);
}
#[test]
fn eval_break_to_undefined_label_is_compile_error() {
let r = eval("foo: while (false) {} break bar;");
assert!(
(r.contains("parse error") || r.contains("compile error")) && r.contains("undefined label"),
"got: {r}"
);
}