diff --git a/crates/oxide_compiler/tests/compile_expr_tests.rs b/crates/oxide_compiler/tests/compile_expr_tests.rs index 31030b0..16f2919 100644 --- a/crates/oxide_compiler/tests/compile_expr_tests.rs +++ b/crates/oxide_compiler/tests/compile_expr_tests.rs @@ -501,3 +501,35 @@ fn compile_builtin_globals_are_registered_lazily() { let module = compile_source("Object; Array; Math; JSON"); assert_eq!(module.builtin_reg_map.len(), 4, "only referenced builtins should allocate registers"); } + +#[test] +fn compile_comparison_complement_ops() { + let module = compile_source("let a = 1, b = 2; a > b; a <= b; a >= b; a != b;"); + assert!(module.bytecode.iter().any(|&i| opcode::opcode(i) == OpCode::GT), "a > b should emit GT"); + assert!(module.bytecode.iter().any(|&i| opcode::opcode(i) == OpCode::LTE), "a <= b should emit LTE"); + assert!(module.bytecode.iter().any(|&i| opcode::opcode(i) == OpCode::GTE), "a >= b should emit GTE"); + assert!(module.bytecode.iter().any(|&i| opcode::opcode(i) == OpCode::NEQ), "a != b should emit NEQ"); +} + +#[test] +fn compile_template_literal_emits_template_str() { + let module = compile_source("let name = 'x'; `hello ${name}`;"); + assert!(module.bytecode.iter().any(|&i| opcode::opcode(i) == OpCode::TEMPLATE_STR), "template literal should emit TEMPLATE_STR"); +} + +#[test] +fn compile_dynamic_member_ops() { + let module = compile_source("let obj = {}, k = 'x', arr = [1]; obj[k]; obj[k] = 1; arr[0];"); + assert!(module.bytecode.iter().any(|&i| opcode::opcode(i) == OpCode::NEW_ARRAY), "array literal should emit NEW_ARRAY"); + assert!(module.bytecode.iter().any(|&i| opcode::opcode(i) == OpCode::GET_PROP_DYNAMIC), "obj[k] should emit GET_PROP_DYNAMIC"); + assert!(module.bytecode.iter().any(|&i| opcode::opcode(i) == OpCode::SET_PROP_DYNAMIC), "obj[k] = should emit SET_PROP_DYNAMIC"); +} + +#[test] +fn compile_delete_new_instanceof_in() { + let module = compile_source("let obj = { x: 1 }, B = function(){}; delete obj.x; new B(); obj instanceof B; 'x' in obj;"); + assert!(module.bytecode.iter().any(|&i| opcode::opcode(i) == OpCode::DELETE_PROP_STATIC), "delete obj.x should emit DELETE_PROP_STATIC"); + assert!(module.bytecode.iter().any(|&i| opcode::opcode(i) == OpCode::NEW_EXPRESSION), "new B() should emit NEW_EXPRESSION"); + assert!(module.bytecode.iter().any(|&i| opcode::opcode(i) == OpCode::INSTANCEOF), "instanceof should emit INSTANCEOF"); + assert!(module.bytecode.iter().any(|&i| opcode::opcode(i) == OpCode::IN), "in should emit IN"); +} diff --git a/crates/oxide_compiler/tests/compile_stmt_tests.rs b/crates/oxide_compiler/tests/compile_stmt_tests.rs index f3d56b6..ed8f98c 100644 --- a/crates/oxide_compiler/tests/compile_stmt_tests.rs +++ b/crates/oxide_compiler/tests/compile_stmt_tests.rs @@ -332,3 +332,45 @@ fn compile_empty_statements_are_skipped() { "var after empty statements should still compile" ); } + +#[test] +fn compile_for_of_emits_iterator_protocol() { + let module = compile_source("let arr = [1]; for (const v of arr) { v; }"); + assert!(module.bytecode.iter().any(|&i| opcode::opcode(i) == OpCode::FOR_OF_INIT), "for-of should emit FOR_OF_INIT"); + assert!(module.bytecode.iter().any(|&i| opcode::opcode(i) == OpCode::FOR_OF_NEXT), "for-of should emit FOR_OF_NEXT"); + assert!(module.bytecode.iter().any(|&i| opcode::opcode(i) == OpCode::FOR_OF_DONE), "for-of should emit FOR_OF_DONE"); + assert!(module.bytecode.iter().any(|&i| opcode::opcode(i) == OpCode::FOR_OF_CLOSE), "for-of should emit FOR_OF_CLOSE"); +} + +#[test] +fn compile_try_catch_emits_try_begin_and_throw() { + let module = compile_source("try { throw new Error('x'); } catch (e) { e; }"); + assert!(module.bytecode.iter().any(|&i| opcode::opcode(i) == OpCode::THROW), "throw should emit THROW"); + assert!(module.bytecode.iter().any(|&i| opcode::opcode(i) == OpCode::TRY_BEGIN), "try-catch should emit TRY_BEGIN"); + assert!(module.bytecode.iter().any(|&i| opcode::opcode(i) == OpCode::TRY_END), "try-catch should emit TRY_END"); +} + +fn has_opcode(module: &CompiledModule, target: OpCode) -> bool { + if module.bytecode.iter().any(|&i| opcode::opcode(i) == target) { + return true; + } + module.sub_modules.iter().any(|m| has_opcode(m, target)) +} + +#[test] +fn compile_closure_capture_emits_upvalue_load() { + let module = compile_source("function outer() { var x = 1; return function() { return x; }; }"); + assert!( + has_opcode(&module, OpCode::LOAD_UPVALUE), + "inner function should emit LOAD_UPVALUE somewhere in nested sub_modules" + ); +} + +#[test] +fn compile_compound_assignment_complement_ops() { + let module = compile_source("let x = 10; x -= 1; x *= 2; x /= 3; x %= 4;"); + assert!(module.bytecode.iter().any(|&i| opcode::opcode(i) == OpCode::COMPOUND_SUB), "x -= should emit COMPOUND_SUB"); + assert!(module.bytecode.iter().any(|&i| opcode::opcode(i) == OpCode::COMPOUND_MUL), "x *= should emit COMPOUND_MUL"); + assert!(module.bytecode.iter().any(|&i| opcode::opcode(i) == OpCode::COMPOUND_DIV), "x /= should emit COMPOUND_DIV"); + assert!(module.bytecode.iter().any(|&i| opcode::opcode(i) == OpCode::COMPOUND_MOD), "x %= should emit COMPOUND_MOD"); +}