1#![allow(dead_code)]
8
9use alloc::{
10 string::{String, ToString},
11 vec::Vec,
12};
13
14use super::{
15 js_lexer::JsNumber,
16 js_parser::{AstArena, AstNode, AstNodeId, BinOp, UnaryOp},
17};
18
19#[derive(Debug, Clone, Copy, PartialEq, Eq)]
25#[repr(u8)]
26pub enum Opcode {
27 LoadConst = 0,
29 LoadLocal = 1,
31 StoreLocal = 2,
33 LoadGlobal = 3,
35 StoreGlobal = 4,
37 LoadProperty = 5,
39 StoreProperty = 6,
41 LoadIndex = 7,
43 StoreIndex = 8,
45
46 Add = 10,
48 Sub = 11,
49 Mul = 12,
50 Div = 13,
51 Mod = 14,
52 Neg = 15,
53
54 Not = 20,
56 BitAnd = 21,
57 BitOr = 22,
58 BitXor = 23,
59 ShiftLeft = 24,
60 ShiftRight = 25,
61
62 Eq = 30,
64 StrictEq = 31,
65 NotEq = 32,
66 StrictNotEq = 33,
67 Lt = 34,
68 Gt = 35,
69 LtEq = 36,
70 GtEq = 37,
71
72 LogicalAnd = 38,
74 LogicalOr = 39,
75
76 Jump = 40,
78 JumpIfFalse = 41,
79 JumpIfTrue = 42,
80
81 Call = 50,
83 Return = 51,
84 CreateClosure = 52,
85
86 CreateObject = 60,
88 CreateArray = 61,
89
90 GetThis = 70,
92 Typeof = 71,
93 Instanceof = 72,
94 In = 73,
95 Throw = 74,
96 EnterTry = 75,
97 LeaveTry = 76,
98
99 Pop = 80,
101 Dup = 81,
102
103 Halt = 255,
105}
106
107impl Opcode {
108 pub fn from_byte(b: u8) -> Option<Self> {
110 match b {
111 0 => Some(Self::LoadConst),
112 1 => Some(Self::LoadLocal),
113 2 => Some(Self::StoreLocal),
114 3 => Some(Self::LoadGlobal),
115 4 => Some(Self::StoreGlobal),
116 5 => Some(Self::LoadProperty),
117 6 => Some(Self::StoreProperty),
118 7 => Some(Self::LoadIndex),
119 8 => Some(Self::StoreIndex),
120 10 => Some(Self::Add),
121 11 => Some(Self::Sub),
122 12 => Some(Self::Mul),
123 13 => Some(Self::Div),
124 14 => Some(Self::Mod),
125 15 => Some(Self::Neg),
126 20 => Some(Self::Not),
127 21 => Some(Self::BitAnd),
128 22 => Some(Self::BitOr),
129 23 => Some(Self::BitXor),
130 24 => Some(Self::ShiftLeft),
131 25 => Some(Self::ShiftRight),
132 30 => Some(Self::Eq),
133 31 => Some(Self::StrictEq),
134 32 => Some(Self::NotEq),
135 33 => Some(Self::StrictNotEq),
136 34 => Some(Self::Lt),
137 35 => Some(Self::Gt),
138 36 => Some(Self::LtEq),
139 37 => Some(Self::GtEq),
140 38 => Some(Self::LogicalAnd),
141 39 => Some(Self::LogicalOr),
142 40 => Some(Self::Jump),
143 41 => Some(Self::JumpIfFalse),
144 42 => Some(Self::JumpIfTrue),
145 50 => Some(Self::Call),
146 51 => Some(Self::Return),
147 52 => Some(Self::CreateClosure),
148 60 => Some(Self::CreateObject),
149 61 => Some(Self::CreateArray),
150 70 => Some(Self::GetThis),
151 71 => Some(Self::Typeof),
152 72 => Some(Self::Instanceof),
153 73 => Some(Self::In),
154 74 => Some(Self::Throw),
155 75 => Some(Self::EnterTry),
156 76 => Some(Self::LeaveTry),
157 80 => Some(Self::Pop),
158 81 => Some(Self::Dup),
159 255 => Some(Self::Halt),
160 _ => None,
161 }
162 }
163}
164
165#[derive(Debug, Clone)]
171pub enum Constant {
172 Number(JsNumber),
173 Str(String),
174 Bool(bool),
175 Null,
176 Undefined,
177 Function(FunctionTemplate),
178}
179
180#[derive(Debug, Clone, Default)]
182pub struct FunctionTemplate {
183 pub name: String,
185 pub param_count: usize,
187 pub bytecode: Vec<u8>,
189 pub constants: Vec<Constant>,
191 pub local_count: usize,
193 pub upvalue_count: usize,
195 pub line_numbers: Vec<u32>,
197}
198
199#[derive(Debug, Clone, Default)]
201pub struct Chunk {
202 pub bytecode: Vec<u8>,
203 pub constants: Vec<Constant>,
204 pub line_numbers: Vec<u32>,
205}
206
207#[derive(Debug, Clone)]
212struct Local {
213 name: String,
214 depth: u32,
215 index: u16,
216}
217
218pub struct Compiler {
224 chunk: Chunk,
226 scope_depth: u32,
228 locals: Vec<Local>,
230 next_local: u16,
232 current_line: u32,
234 functions: Vec<FunctionTemplate>,
236 break_patches: Vec<Vec<usize>>,
238 continue_targets: Vec<usize>,
240}
241
242impl Default for Compiler {
243 fn default() -> Self {
244 Self::new()
245 }
246}
247
248impl Compiler {
249 pub fn new() -> Self {
250 Self {
251 chunk: Chunk::default(),
252 scope_depth: 0,
253 locals: Vec::new(),
254 next_local: 0,
255 current_line: 1,
256 functions: Vec::new(),
257 break_patches: Vec::new(),
258 continue_targets: Vec::new(),
259 }
260 }
261
262 pub fn compile(&mut self, arena: &AstArena, root: AstNodeId) -> Chunk {
264 self.compile_node(arena, root);
265 self.emit_op(Opcode::Halt);
266 core::mem::take(&mut self.chunk)
267 }
268
269 pub fn compile_function(
271 &mut self,
272 arena: &AstArena,
273 name: &str,
274 params: &[String],
275 body: AstNodeId,
276 ) -> FunctionTemplate {
277 let prev_chunk = core::mem::take(&mut self.chunk);
278 let prev_locals = core::mem::take(&mut self.locals);
279 let prev_next_local = self.next_local;
280 let prev_depth = self.scope_depth;
281
282 self.scope_depth = 1;
283 self.next_local = 0;
284 self.locals.clear();
285
286 for param in params {
288 self.define_local(param);
289 }
290
291 self.compile_node(arena, body);
292 let undef_idx = self.add_constant(Constant::Undefined);
294 self.emit_op(Opcode::LoadConst);
295 self.emit_u16(undef_idx);
296 self.emit_op(Opcode::Return);
297
298 let func = FunctionTemplate {
299 name: name.to_string(),
300 param_count: params.len(),
301 bytecode: core::mem::take(&mut self.chunk.bytecode),
302 constants: core::mem::take(&mut self.chunk.constants),
303 local_count: self.next_local as usize,
304 upvalue_count: 0,
305 line_numbers: core::mem::take(&mut self.chunk.line_numbers),
306 };
307
308 self.chunk = prev_chunk;
309 self.locals = prev_locals;
310 self.next_local = prev_next_local;
311 self.scope_depth = prev_depth;
312
313 func
314 }
315
316 fn compile_node(&mut self, arena: &AstArena, id: AstNodeId) {
319 let node = match arena.get(id) {
320 Some(n) => n.clone(),
321 None => return,
322 };
323
324 match node {
325 AstNode::Program(stmts) => {
326 for stmt in &stmts {
327 self.compile_node(arena, *stmt);
328 }
329 }
330
331 AstNode::VarDecl {
332 name,
333 init,
334 kind: _,
335 } => {
336 if self.scope_depth > 0 {
337 let idx = self.define_local(&name);
338 if let Some(init_id) = init {
339 self.compile_node(arena, init_id);
340 self.emit_op(Opcode::StoreLocal);
341 self.emit_u16(idx);
342 }
343 } else {
344 if let Some(init_id) = init {
345 self.compile_node(arena, init_id);
346 } else {
347 let c = self.add_constant(Constant::Undefined);
348 self.emit_op(Opcode::LoadConst);
349 self.emit_u16(c);
350 }
351 let name_idx = self.add_constant(Constant::Str(name));
352 self.emit_op(Opcode::StoreGlobal);
353 self.emit_u16(name_idx);
354 }
355 }
356
357 AstNode::FuncDecl { name, params, body } => {
358 let func = self.compile_function(arena, &name, ¶ms, body);
359 let func_idx = self.add_constant(Constant::Function(func));
360 self.emit_op(Opcode::LoadConst);
361 self.emit_u16(func_idx);
362 if self.scope_depth > 0 {
363 let local = self.define_local(&name);
364 self.emit_op(Opcode::StoreLocal);
365 self.emit_u16(local);
366 } else {
367 let name_idx = self.add_constant(Constant::Str(name));
368 self.emit_op(Opcode::StoreGlobal);
369 self.emit_u16(name_idx);
370 }
371 }
372
373 AstNode::Return(value) => {
374 if let Some(val_id) = value {
375 self.compile_node(arena, val_id);
376 } else {
377 let c = self.add_constant(Constant::Undefined);
378 self.emit_op(Opcode::LoadConst);
379 self.emit_u16(c);
380 }
381 self.emit_op(Opcode::Return);
382 }
383
384 AstNode::If {
385 condition,
386 then_branch,
387 else_branch,
388 } => {
389 self.compile_node(arena, condition);
390 let else_jump = self.emit_jump(Opcode::JumpIfFalse);
391 self.compile_node(arena, then_branch);
392 if let Some(else_id) = else_branch {
393 let end_jump = self.emit_jump(Opcode::Jump);
394 self.patch_jump(else_jump);
395 self.compile_node(arena, else_id);
396 self.patch_jump(end_jump);
397 } else {
398 self.patch_jump(else_jump);
399 }
400 }
401
402 AstNode::While { condition, body } => {
403 let loop_start = self.chunk.bytecode.len();
404 self.continue_targets.push(loop_start);
405 self.break_patches.push(Vec::new());
406
407 self.compile_node(arena, condition);
408 let exit_jump = self.emit_jump(Opcode::JumpIfFalse);
409 self.compile_node(arena, body);
410 self.emit_loop(loop_start);
411 self.patch_jump(exit_jump);
412
413 let breaks = self.break_patches.pop().unwrap_or_default();
414 for bp in breaks {
415 self.patch_jump(bp);
416 }
417 self.continue_targets.pop();
418 }
419
420 AstNode::For {
421 init,
422 condition,
423 update,
424 body,
425 } => {
426 if let Some(init_id) = init {
427 self.compile_node(arena, init_id);
428 }
429 let loop_start = self.chunk.bytecode.len();
430 self.continue_targets.push(loop_start);
431 self.break_patches.push(Vec::new());
432
433 let exit_jump = if let Some(cond_id) = condition {
434 self.compile_node(arena, cond_id);
435 Some(self.emit_jump(Opcode::JumpIfFalse))
436 } else {
437 None
438 };
439
440 self.compile_node(arena, body);
441
442 if let Some(upd_id) = update {
443 self.compile_node(arena, upd_id);
444 self.emit_op(Opcode::Pop);
445 }
446
447 self.emit_loop(loop_start);
448
449 if let Some(ej) = exit_jump {
450 self.patch_jump(ej);
451 }
452
453 let breaks = self.break_patches.pop().unwrap_or_default();
454 for bp in breaks {
455 self.patch_jump(bp);
456 }
457 self.continue_targets.pop();
458 }
459
460 AstNode::Block(stmts) => {
461 self.scope_depth += 1;
462 for stmt in &stmts {
463 self.compile_node(arena, *stmt);
464 }
465 self.scope_depth -= 1;
466 while let Some(local) = self.locals.last() {
468 if local.depth > self.scope_depth {
469 self.locals.pop();
470 } else {
471 break;
472 }
473 }
474 }
475
476 AstNode::ExprStatement(expr) => {
477 self.compile_node(arena, expr);
478 self.emit_op(Opcode::Pop);
479 }
480
481 AstNode::BinaryExpr { op, left, right } => {
482 self.compile_node(arena, left);
483 self.compile_node(arena, right);
484 let opcode = match op {
485 BinOp::Add => Opcode::Add,
486 BinOp::Sub => Opcode::Sub,
487 BinOp::Mul => Opcode::Mul,
488 BinOp::Div => Opcode::Div,
489 BinOp::Mod => Opcode::Mod,
490 BinOp::Eq => Opcode::Eq,
491 BinOp::StrictEq => Opcode::StrictEq,
492 BinOp::NotEq => Opcode::NotEq,
493 BinOp::StrictNotEq => Opcode::StrictNotEq,
494 BinOp::Lt => Opcode::Lt,
495 BinOp::Gt => Opcode::Gt,
496 BinOp::LtEq => Opcode::LtEq,
497 BinOp::GtEq => Opcode::GtEq,
498 BinOp::And => Opcode::LogicalAnd,
499 BinOp::Or => Opcode::LogicalOr,
500 BinOp::BitAnd => Opcode::BitAnd,
501 BinOp::BitOr => Opcode::BitOr,
502 BinOp::BitXor => Opcode::BitXor,
503 BinOp::ShiftLeft => Opcode::ShiftLeft,
504 BinOp::ShiftRight => Opcode::ShiftRight,
505 BinOp::Instanceof => Opcode::Instanceof,
506 BinOp::In => Opcode::In,
507 };
508 self.emit_op(opcode);
509 }
510
511 AstNode::UnaryExpr { op, operand } => {
512 self.compile_node(arena, operand);
513 match op {
514 UnaryOp::Neg => self.emit_op(Opcode::Neg),
515 UnaryOp::Not => self.emit_op(Opcode::Not),
516 UnaryOp::Typeof => self.emit_op(Opcode::Typeof),
517 UnaryOp::Void => {
518 self.emit_op(Opcode::Pop);
519 let c = self.add_constant(Constant::Undefined);
520 self.emit_op(Opcode::LoadConst);
521 self.emit_u16(c);
522 }
523 UnaryOp::Delete => self.emit_op(Opcode::Pop), UnaryOp::BitNot => self.emit_op(Opcode::BitXor), }
526 }
527
528 AstNode::AssignExpr { target, value } => {
529 self.compile_node(arena, value);
530 self.emit_op(Opcode::Dup);
531 self.compile_store(arena, target);
532 }
533
534 AstNode::CompoundAssign { op, target, value } => {
535 self.compile_load(arena, target);
536 self.compile_node(arena, value);
537 let opcode = match op {
538 BinOp::Add => Opcode::Add,
539 BinOp::Sub => Opcode::Sub,
540 BinOp::Mul => Opcode::Mul,
541 BinOp::Div => Opcode::Div,
542 _ => Opcode::Add,
543 };
544 self.emit_op(opcode);
545 self.emit_op(Opcode::Dup);
546 self.compile_store(arena, target);
547 }
548
549 AstNode::CallExpr { callee, args } => {
550 self.compile_node(arena, callee);
551 let argc = args.len();
552 for arg in &args {
553 self.compile_node(arena, *arg);
554 }
555 self.emit_op(Opcode::Call);
556 self.emit_byte(argc as u8);
557 }
558
559 AstNode::MemberExpr { object, property } => {
560 self.compile_node(arena, object);
561 let prop_idx = self.add_constant(Constant::Str(property));
562 self.emit_op(Opcode::LoadProperty);
563 self.emit_u16(prop_idx);
564 }
565
566 AstNode::IndexExpr { object, index } => {
567 self.compile_node(arena, object);
568 self.compile_node(arena, index);
569 self.emit_op(Opcode::LoadIndex);
570 }
571
572 AstNode::ObjectLiteral(props) => {
573 self.emit_op(Opcode::CreateObject);
574 for (key, val_id) in &props {
575 self.emit_op(Opcode::Dup);
576 self.compile_node(arena, *val_id);
577 let key_idx = self.add_constant(Constant::Str(key.clone()));
578 self.emit_op(Opcode::StoreProperty);
579 self.emit_u16(key_idx);
580 }
581 }
582
583 AstNode::ArrayLiteral(elems) => {
584 let count = elems.len();
585 for elem in &elems {
586 self.compile_node(arena, *elem);
587 }
588 self.emit_op(Opcode::CreateArray);
589 self.emit_byte(count as u8);
590 }
591
592 AstNode::NumberLit(n) => {
593 let idx = self.add_constant(Constant::Number(n));
594 self.emit_op(Opcode::LoadConst);
595 self.emit_u16(idx);
596 }
597 AstNode::StringLit(s) => {
598 let idx = self.add_constant(Constant::Str(s));
599 self.emit_op(Opcode::LoadConst);
600 self.emit_u16(idx);
601 }
602 AstNode::BoolLit(b) => {
603 let idx = self.add_constant(Constant::Bool(b));
604 self.emit_op(Opcode::LoadConst);
605 self.emit_u16(idx);
606 }
607 AstNode::NullLit => {
608 let idx = self.add_constant(Constant::Null);
609 self.emit_op(Opcode::LoadConst);
610 self.emit_u16(idx);
611 }
612 AstNode::UndefinedLit => {
613 let idx = self.add_constant(Constant::Undefined);
614 self.emit_op(Opcode::LoadConst);
615 self.emit_u16(idx);
616 }
617
618 AstNode::Identifier(name) => {
619 if let Some(idx) = self.resolve_local(&name) {
620 self.emit_op(Opcode::LoadLocal);
621 self.emit_u16(idx);
622 } else {
623 let name_idx = self.add_constant(Constant::Str(name));
624 self.emit_op(Opcode::LoadGlobal);
625 self.emit_u16(name_idx);
626 }
627 }
628
629 AstNode::This => {
630 self.emit_op(Opcode::GetThis);
631 }
632
633 AstNode::FuncExpr { params, body } | AstNode::ArrowFunc { params, body } => {
634 let func = self.compile_function(arena, "", ¶ms, body);
635 let idx = self.add_constant(Constant::Function(func));
636 self.emit_op(Opcode::CreateClosure);
637 self.emit_u16(idx);
638 }
639
640 AstNode::NewExpr { callee, args } => {
641 self.compile_node(arena, callee);
642 let argc = args.len();
643 for arg in &args {
644 self.compile_node(arena, *arg);
645 }
646 self.emit_op(Opcode::Call);
647 self.emit_byte(argc as u8);
648 }
649
650 AstNode::TypeofExpr(operand) => {
651 self.compile_node(arena, operand);
652 self.emit_op(Opcode::Typeof);
653 }
654
655 AstNode::Throw(expr) => {
656 self.compile_node(arena, expr);
657 self.emit_op(Opcode::Throw);
658 }
659
660 AstNode::TryCatch {
661 try_body,
662 catch_body,
663 finally_body,
664 catch_param: _,
665 } => {
666 let try_jump = self.emit_jump(Opcode::EnterTry);
667 self.compile_node(arena, try_body);
668 self.emit_op(Opcode::LeaveTry);
669 let end_jump = self.emit_jump(Opcode::Jump);
670 self.patch_jump(try_jump);
671 if let Some(catch_id) = catch_body {
672 self.compile_node(arena, catch_id);
673 }
674 self.patch_jump(end_jump);
675 if let Some(finally_id) = finally_body {
676 self.compile_node(arena, finally_id);
677 }
678 }
679
680 AstNode::Break => {
681 let jump = self.emit_jump(Opcode::Jump);
682 if let Some(patches) = self.break_patches.last_mut() {
683 patches.push(jump);
684 }
685 }
686
687 AstNode::Continue => {
688 if let Some(&target) = self.continue_targets.last() {
689 self.emit_loop(target);
690 }
691 }
692
693 AstNode::Empty => {}
694 }
695 }
696
697 fn compile_load(&mut self, arena: &AstArena, id: AstNodeId) {
699 self.compile_node(arena, id);
700 }
701
702 fn compile_store(&mut self, arena: &AstArena, target: AstNodeId) {
704 match arena.get(target) {
705 Some(AstNode::Identifier(name)) => {
706 if let Some(idx) = self.resolve_local(name) {
707 self.emit_op(Opcode::StoreLocal);
708 self.emit_u16(idx);
709 } else {
710 let name_idx = self.add_constant(Constant::Str(name.clone()));
711 self.emit_op(Opcode::StoreGlobal);
712 self.emit_u16(name_idx);
713 }
714 }
715 Some(AstNode::MemberExpr { object, property }) => {
716 let object = *object;
717 let property = property.clone();
718 self.compile_node(arena, object);
719 let prop_idx = self.add_constant(Constant::Str(property));
720 self.emit_op(Opcode::StoreProperty);
721 self.emit_u16(prop_idx);
722 }
723 _ => {
724 self.emit_op(Opcode::Pop);
726 }
727 }
728 }
729
730 fn define_local(&mut self, name: &str) -> u16 {
733 let idx = self.next_local;
734 self.locals.push(Local {
735 name: name.to_string(),
736 depth: self.scope_depth,
737 index: idx,
738 });
739 self.next_local += 1;
740 idx
741 }
742
743 fn resolve_local(&self, name: &str) -> Option<u16> {
744 for local in self.locals.iter().rev() {
745 if local.name == name {
746 return Some(local.index);
747 }
748 }
749 None
750 }
751
752 fn emit_op(&mut self, op: Opcode) {
755 self.chunk.bytecode.push(op as u8);
756 self.chunk.line_numbers.push(self.current_line);
757 }
758
759 fn emit_byte(&mut self, byte: u8) {
760 self.chunk.bytecode.push(byte);
761 self.chunk.line_numbers.push(self.current_line);
762 }
763
764 fn emit_u16(&mut self, val: u16) {
765 self.chunk.bytecode.push((val >> 8) as u8);
766 self.chunk.bytecode.push(val as u8);
767 self.chunk.line_numbers.push(self.current_line);
768 self.chunk.line_numbers.push(self.current_line);
769 }
770
771 fn emit_jump(&mut self, op: Opcode) -> usize {
772 self.emit_op(op);
773 let offset = self.chunk.bytecode.len();
774 self.emit_u16(0xFFFF); offset
776 }
777
778 fn patch_jump(&mut self, offset: usize) {
779 let target = self.chunk.bytecode.len() as u16;
780 self.chunk.bytecode[offset] = (target >> 8) as u8;
781 self.chunk.bytecode[offset + 1] = target as u8;
782 }
783
784 fn emit_loop(&mut self, target: usize) {
785 self.emit_op(Opcode::Jump);
786 self.emit_u16(target as u16);
787 }
788
789 fn add_constant(&mut self, constant: Constant) -> u16 {
790 let idx = self.chunk.constants.len();
791 self.chunk.constants.push(constant);
792 idx as u16
793 }
794}
795
796#[cfg(test)]
801mod tests {
802 use super::*;
803 use crate::browser::{js_lexer::js_int, js_parser::JsParser};
804
805 fn compile_src(src: &str) -> Chunk {
806 let mut parser = JsParser::from_source(src);
807 let root = parser.parse();
808 let mut compiler = Compiler::new();
809 compiler.compile(&parser.arena, root)
810 }
811
812 #[test]
813 fn test_empty_program() {
814 let chunk = compile_src("");
815 assert_eq!(*chunk.bytecode.last().unwrap(), Opcode::Halt as u8);
817 }
818
819 #[test]
820 fn test_number_literal() {
821 let chunk = compile_src("42;");
822 assert!(chunk.bytecode.contains(&(Opcode::LoadConst as u8)));
823 assert!(chunk
824 .constants
825 .iter()
826 .any(|c| matches!(c, Constant::Number(n) if *n == js_int(42))));
827 }
828
829 #[test]
830 fn test_string_literal() {
831 let chunk = compile_src("\"hello\";");
832 assert!(chunk
833 .constants
834 .iter()
835 .any(|c| matches!(c, Constant::Str(s) if s == "hello")));
836 }
837
838 #[test]
839 fn test_var_decl_global() {
840 let chunk = compile_src("let x = 10;");
841 assert!(chunk.bytecode.contains(&(Opcode::StoreGlobal as u8)));
842 }
843
844 #[test]
845 fn test_binary_add() {
846 let chunk = compile_src("1 + 2;");
847 assert!(chunk.bytecode.contains(&(Opcode::Add as u8)));
848 }
849
850 #[test]
851 fn test_binary_mul() {
852 let chunk = compile_src("3 * 4;");
853 assert!(chunk.bytecode.contains(&(Opcode::Mul as u8)));
854 }
855
856 #[test]
857 fn test_unary_neg() {
858 let chunk = compile_src("-5;");
859 assert!(chunk.bytecode.contains(&(Opcode::Neg as u8)));
860 }
861
862 #[test]
863 fn test_if_statement() {
864 let chunk = compile_src("if (true) { 1; }");
865 assert!(chunk.bytecode.contains(&(Opcode::JumpIfFalse as u8)));
866 }
867
868 #[test]
869 fn test_if_else() {
870 let chunk = compile_src("if (false) { 1; } else { 2; }");
871 let jumps = chunk
872 .bytecode
873 .iter()
874 .filter(|&&b| b == Opcode::JumpIfFalse as u8 || b == Opcode::Jump as u8)
875 .count();
876 assert!(jumps >= 2);
877 }
878
879 #[test]
880 fn test_while_loop() {
881 let chunk = compile_src("while (true) { 1; }");
882 assert!(chunk.bytecode.contains(&(Opcode::JumpIfFalse as u8)));
883 assert!(chunk.bytecode.contains(&(Opcode::Jump as u8)));
884 }
885
886 #[test]
887 fn test_function_decl() {
888 let chunk = compile_src("function add(a, b) { return a + b; }");
889 let has_func = chunk
890 .constants
891 .iter()
892 .any(|c| matches!(c, Constant::Function(f) if f.name == "add" && f.param_count == 2));
893 assert!(has_func);
894 }
895
896 #[test]
897 fn test_function_call() {
898 let chunk = compile_src("foo(1);");
899 assert!(chunk.bytecode.contains(&(Opcode::Call as u8)));
900 }
901
902 #[test]
903 fn test_member_access() {
904 let chunk = compile_src("obj.x;");
905 assert!(chunk.bytecode.contains(&(Opcode::LoadProperty as u8)));
906 }
907
908 #[test]
909 fn test_index_access() {
910 let chunk = compile_src("arr[0];");
911 assert!(chunk.bytecode.contains(&(Opcode::LoadIndex as u8)));
912 }
913
914 #[test]
915 fn test_object_literal() {
916 let chunk = compile_src("let o = {a: 1};");
917 assert!(chunk.bytecode.contains(&(Opcode::CreateObject as u8)));
918 }
919
920 #[test]
921 fn test_array_literal() {
922 let chunk = compile_src("let a = [1, 2];");
923 assert!(chunk.bytecode.contains(&(Opcode::CreateArray as u8)));
924 }
925
926 #[test]
927 fn test_assignment() {
928 let chunk = compile_src("x = 5;");
929 assert!(chunk.bytecode.contains(&(Opcode::StoreGlobal as u8)));
930 }
931
932 #[test]
933 fn test_return_value() {
934 let chunk = compile_src("function f() { return 42; }");
935 let has_func = chunk.constants.iter().any(
936 |c| matches!(c, Constant::Function(f) if f.bytecode.contains(&(Opcode::Return as u8))),
937 );
938 assert!(has_func);
939 }
940
941 #[test]
942 fn test_comparison() {
943 let chunk = compile_src("1 < 2;");
944 assert!(chunk.bytecode.contains(&(Opcode::Lt as u8)));
945 }
946
947 #[test]
948 fn test_strict_eq() {
949 let chunk = compile_src("a === b;");
950 assert!(chunk.bytecode.contains(&(Opcode::StrictEq as u8)));
951 }
952
953 #[test]
954 fn test_logical_and() {
955 let chunk = compile_src("a && b;");
956 assert!(chunk.bytecode.contains(&(Opcode::LogicalAnd as u8)));
957 }
958
959 #[test]
960 fn test_typeof() {
961 let chunk = compile_src("typeof x;");
962 assert!(chunk.bytecode.contains(&(Opcode::Typeof as u8)));
963 }
964
965 #[test]
966 fn test_throw() {
967 let chunk = compile_src("throw 'error';");
968 assert!(chunk.bytecode.contains(&(Opcode::Throw as u8)));
969 }
970
971 #[test]
972 fn test_try_catch() {
973 let chunk = compile_src("try { x(); } catch (e) { y(); }");
974 assert!(chunk.bytecode.contains(&(Opcode::EnterTry as u8)));
975 assert!(chunk.bytecode.contains(&(Opcode::LeaveTry as u8)));
976 }
977
978 #[test]
979 fn test_this() {
980 let chunk = compile_src("this;");
981 assert!(chunk.bytecode.contains(&(Opcode::GetThis as u8)));
982 }
983
984 #[test]
985 fn test_not_operator() {
986 let chunk = compile_src("!x;");
987 assert!(chunk.bytecode.contains(&(Opcode::Not as u8)));
988 }
989
990 #[test]
991 fn test_opcode_from_byte() {
992 assert_eq!(Opcode::from_byte(0), Some(Opcode::LoadConst));
993 assert_eq!(Opcode::from_byte(255), Some(Opcode::Halt));
994 assert_eq!(Opcode::from_byte(99), None);
995 }
996
997 #[test]
998 fn test_for_loop_compile() {
999 let chunk = compile_src("for (let i = 0; i < 10; i = i + 1) { x; }");
1000 assert!(chunk.bytecode.contains(&(Opcode::Jump as u8)));
1001 }
1002
1003 #[test]
1004 fn test_compound_assign() {
1005 let chunk = compile_src("x += 1;");
1006 assert!(chunk.bytecode.contains(&(Opcode::Add as u8)));
1007 }
1008
1009 #[test]
1010 fn test_bool_null_undefined_constants() {
1011 let chunk = compile_src("true; false; null; undefined;");
1012 let has_bool_true = chunk
1013 .constants
1014 .iter()
1015 .any(|c| matches!(c, Constant::Bool(true)));
1016 let has_null = chunk.constants.iter().any(|c| matches!(c, Constant::Null));
1017 let has_undef = chunk
1018 .constants
1019 .iter()
1020 .any(|c| matches!(c, Constant::Undefined));
1021 assert!(has_bool_true);
1022 assert!(has_null);
1023 assert!(has_undef);
1024 }
1025
1026 #[test]
1027 fn test_closure_compile() {
1028 let chunk = compile_src("let f = function(x) { return x; };");
1029 assert!(chunk.bytecode.contains(&(Opcode::CreateClosure as u8)));
1030 }
1031}