1#![allow(dead_code)]
8
9use alloc::{
10 collections::BTreeMap,
11 format,
12 string::{String, ToString},
13 vec::Vec,
14};
15
16use super::{
17 js_compiler::{Chunk, Constant, FunctionTemplate, Opcode},
18 js_lexer::{JsNumber, JS_FRAC_BITS, JS_NAN, JS_ONE, JS_ZERO},
19};
20
21#[derive(Debug, Clone)]
27pub enum JsVmError {
28 ExecutionLimitExceeded,
30 UnknownOpcode { byte: u8 },
32 UncaughtException { message: String },
34 StackOverflow,
36 StackUnderflow,
38 NoCallFrame,
40 IpOutOfBounds,
42 NotCallable,
44 InvalidFunctionId,
46}
47
48impl core::fmt::Display for JsVmError {
49 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
50 match self {
51 Self::ExecutionLimitExceeded => write!(f, "Execution limit exceeded"),
52 Self::UnknownOpcode { byte } => write!(f, "Unknown opcode: {}", byte),
53 Self::UncaughtException { message } => write!(f, "Uncaught: {}", message),
54 Self::StackOverflow => write!(f, "Stack overflow"),
55 Self::StackUnderflow => write!(f, "Stack underflow"),
56 Self::NoCallFrame => write!(f, "No call frame"),
57 Self::IpOutOfBounds => write!(f, "IP out of bounds"),
58 Self::NotCallable => write!(f, "Not callable"),
59 Self::InvalidFunctionId => write!(f, "Invalid function ID"),
60 }
61 }
62}
63
64pub type ObjectId = usize;
70
71pub type FunctionId = usize;
73
74#[derive(Debug, Clone, Default)]
76pub enum JsValue {
77 #[default]
78 Undefined,
79 Null,
80 Boolean(bool),
81 Number(JsNumber),
82 String(String),
83 Object(ObjectId),
84 Function(FunctionId),
85}
86
87impl JsValue {
88 pub fn to_boolean(&self) -> bool {
90 match self {
91 Self::Undefined | Self::Null => false,
92 Self::Boolean(b) => *b,
93 Self::Number(n) => *n != JS_ZERO && *n != JS_NAN,
94 Self::String(s) => !s.is_empty(),
95 Self::Object(_) | Self::Function(_) => true,
96 }
97 }
98
99 pub fn to_number(&self) -> JsNumber {
101 match self {
102 Self::Undefined => JS_NAN,
103 Self::Null => JS_ZERO,
104 Self::Boolean(true) => JS_ONE,
105 Self::Boolean(false) => JS_ZERO,
106 Self::Number(n) => *n,
107 Self::String(s) => parse_js_number(s),
108 Self::Object(_) | Self::Function(_) => JS_NAN,
109 }
110 }
111
112 pub fn to_js_string(&self) -> String {
114 match self {
115 Self::Undefined => "undefined".to_string(),
116 Self::Null => "null".to_string(),
117 Self::Boolean(b) => if *b { "true" } else { "false" }.to_string(),
118 Self::Number(n) => format_js_number(*n),
119 Self::String(s) => s.clone(),
120 Self::Object(_) => "[object Object]".to_string(),
121 Self::Function(_) => "function".to_string(),
122 }
123 }
124
125 pub fn js_typeof(&self) -> &'static str {
127 match self {
128 Self::Undefined => "undefined",
129 Self::Null => "object", Self::Boolean(_) => "boolean",
131 Self::Number(_) => "number",
132 Self::String(_) => "string",
133 Self::Object(_) => "object",
134 Self::Function(_) => "function",
135 }
136 }
137
138 pub fn strict_eq(&self, other: &Self) -> bool {
140 match (self, other) {
141 (Self::Undefined, Self::Undefined) => true,
142 (Self::Null, Self::Null) => true,
143 (Self::Boolean(a), Self::Boolean(b)) => a == b,
144 (Self::Number(a), Self::Number(b)) => a == b,
145 (Self::String(a), Self::String(b)) => a == b,
146 (Self::Object(a), Self::Object(b)) => a == b,
147 (Self::Function(a), Self::Function(b)) => a == b,
148 _ => false,
149 }
150 }
151
152 pub fn abstract_eq(&self, other: &Self) -> bool {
154 if self.strict_eq(other) {
155 return true;
156 }
157 match (self, other) {
158 (Self::Null, Self::Undefined) | (Self::Undefined, Self::Null) => true,
159 (Self::Number(a), Self::String(b)) => *a == parse_js_number(b),
160 (Self::String(a), Self::Number(b)) => parse_js_number(a) == *b,
161 _ => false,
162 }
163 }
164}
165
166#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
172pub enum ObjectType {
173 #[default]
174 Ordinary,
175 Array,
176 Function,
177 Error,
178}
179
180#[derive(Debug, Clone, Default)]
182pub struct JsObject {
183 pub properties: BTreeMap<String, JsValue>,
185 pub prototype: Option<ObjectId>,
187 pub internal_type: ObjectType,
189}
190
191impl JsObject {
192 pub fn new() -> Self {
193 Self::default()
194 }
195
196 pub fn array() -> Self {
197 Self {
198 internal_type: ObjectType::Array,
199 ..Default::default()
200 }
201 }
202
203 pub fn get(&self, key: &str) -> JsValue {
204 self.properties
205 .get(key)
206 .cloned()
207 .unwrap_or(JsValue::Undefined)
208 }
209
210 pub fn set(&mut self, key: &str, value: JsValue) {
211 self.properties.insert(key.to_string(), value);
212 }
213}
214
215#[derive(Debug, Clone)]
221pub struct CallFrame {
222 pub function_id: FunctionId,
224 pub ip: usize,
226 pub base_slot: usize,
228 pub locals: Vec<JsValue>,
230 pub bytecode: Vec<u8>,
232 pub constants: Vec<Constant>,
234}
235
236#[derive(Debug, Clone)]
241struct TryState {
242 catch_ip: usize,
243 stack_depth: usize,
244 call_depth: usize,
245}
246
247pub struct JsVm {
253 pub stack: Vec<JsValue>,
255 pub call_stack: Vec<CallFrame>,
257 pub globals: BTreeMap<String, JsValue>,
259 pub object_arena: Vec<JsObject>,
261 pub function_arena: Vec<FunctionTemplate>,
263 try_stack: Vec<TryState>,
265 pub output: Vec<String>,
267 max_stack: usize,
269 max_steps: usize,
271}
272
273impl Default for JsVm {
274 fn default() -> Self {
275 Self::new()
276 }
277}
278
279impl JsVm {
280 pub fn new() -> Self {
281 let mut vm = Self {
282 stack: Vec::with_capacity(256),
283 call_stack: Vec::new(),
284 globals: BTreeMap::new(),
285 object_arena: Vec::new(),
286 function_arena: Vec::new(),
287 try_stack: Vec::new(),
288 output: Vec::new(),
289 max_stack: 1024,
290 max_steps: 100_000,
291 };
292 vm.init_builtins();
293 vm
294 }
295
296 const NATIVE_CONSOLE_ID: ObjectId = usize::MAX;
299
300 fn init_builtins(&mut self) {
302 self.globals.insert(
305 "console".to_string(),
306 JsValue::Object(Self::NATIVE_CONSOLE_ID),
307 );
308 }
309
310 pub fn alloc_object(&mut self, obj: JsObject) -> ObjectId {
312 let id = self.object_arena.len();
313 self.object_arena.push(obj);
314 id
315 }
316
317 pub fn register_function(&mut self, template: FunctionTemplate) -> FunctionId {
319 let id = self.function_arena.len();
320 self.function_arena.push(template);
321 id
322 }
323
324 pub fn run_chunk(&mut self, chunk: &Chunk) -> Result<JsValue, JsVmError> {
326 let template = FunctionTemplate {
328 name: "<main>".to_string(),
329 param_count: 0,
330 bytecode: chunk.bytecode.clone(),
331 constants: chunk.constants.clone(),
332 local_count: 0,
333 upvalue_count: 0,
334 line_numbers: chunk.line_numbers.clone(),
335 };
336
337 let frame = CallFrame {
338 function_id: usize::MAX,
339 ip: 0,
340 base_slot: 0,
341 locals: Vec::new(),
342 bytecode: template.bytecode,
343 constants: template.constants,
344 };
345
346 self.call_stack.push(frame);
347 self.run_loop()
348 }
349
350 fn run_loop(&mut self) -> Result<JsValue, JsVmError> {
352 let mut steps = 0usize;
353
354 loop {
355 steps += 1;
356 if steps > self.max_steps {
357 return Err(JsVmError::ExecutionLimitExceeded);
358 }
359
360 let frame = match self.call_stack.last_mut() {
361 Some(f) => f,
362 None => {
363 return Ok(self.stack.pop().unwrap_or(JsValue::Undefined));
364 }
365 };
366
367 if frame.ip >= frame.bytecode.len() {
368 self.call_stack.pop();
369 continue;
370 }
371
372 let op_byte = frame.bytecode[frame.ip];
373 frame.ip += 1;
374
375 let op = match Opcode::from_byte(op_byte) {
376 Some(o) => o,
377 None => {
378 return Err(JsVmError::UnknownOpcode { byte: op_byte });
379 }
380 };
381
382 match op {
383 Opcode::Halt => {
384 return Ok(self.stack.pop().unwrap_or(JsValue::Undefined));
385 }
386
387 Opcode::LoadConst => {
388 let idx = self.read_u16()? as usize;
389 let frame = self.call_stack.last().unwrap();
390 let val = match frame.constants.get(idx) {
391 Some(Constant::Number(n)) => JsValue::Number(*n),
392 Some(Constant::Str(s)) => JsValue::String(s.clone()),
393 Some(Constant::Bool(b)) => JsValue::Boolean(*b),
394 Some(Constant::Null) => JsValue::Null,
395 Some(Constant::Undefined) => JsValue::Undefined,
396 Some(Constant::Function(f)) => {
397 let fid = self.register_function(f.clone());
398 JsValue::Function(fid)
399 }
400 None => JsValue::Undefined,
401 };
402 self.push(val)?;
403 }
404
405 Opcode::LoadLocal => {
406 let idx = self.read_u16()? as usize;
407 let val = self
408 .call_stack
409 .last()
410 .and_then(|f| f.locals.get(idx))
411 .cloned()
412 .unwrap_or(JsValue::Undefined);
413 self.push(val)?;
414 }
415
416 Opcode::StoreLocal => {
417 let idx = self.read_u16()? as usize;
418 let val = self.pop()?;
419 if let Some(frame) = self.call_stack.last_mut() {
420 while frame.locals.len() <= idx {
421 frame.locals.push(JsValue::Undefined);
422 }
423 frame.locals[idx] = val;
424 }
425 }
426
427 Opcode::LoadGlobal => {
428 let idx = self.read_u16()? as usize;
429 let name = self.get_string_constant(idx)?;
430 let val = self
431 .globals
432 .get(&name)
433 .cloned()
434 .unwrap_or(JsValue::Undefined);
435 self.push(val)?;
436 }
437
438 Opcode::StoreGlobal => {
439 let idx = self.read_u16()? as usize;
440 let name = self.get_string_constant(idx)?;
441 let val = self.pop()?;
442 self.globals.insert(name, val);
443 }
444
445 Opcode::LoadProperty => {
446 let idx = self.read_u16()? as usize;
447 let prop = self.get_string_constant(idx)?;
448 let obj_val = self.pop()?;
449 let val = self.get_property(&obj_val, &prop);
450 self.push(val)?;
451 }
452
453 Opcode::StoreProperty => {
454 let idx = self.read_u16()? as usize;
455 let prop = self.get_string_constant(idx)?;
456 let val = self.pop()?;
457 let obj_val = self.pop()?;
458 self.set_property(&obj_val, &prop, val);
459 }
460
461 Opcode::LoadIndex => {
462 let index = self.pop()?;
463 let obj = self.pop()?;
464 let key = index.to_js_string();
465 let val = self.get_property(&obj, &key);
466 self.push(val)?;
467 }
468
469 Opcode::StoreIndex => {
470 let val = self.pop()?;
471 let index = self.pop()?;
472 let obj = self.pop()?;
473 let key = index.to_js_string();
474 self.set_property(&obj, &key, val);
475 }
476
477 Opcode::Add => {
479 let b = self.pop()?;
480 let a = self.pop()?;
481 let result = self.js_add(&a, &b);
482 self.push(result)?;
483 }
484 Opcode::Sub => {
485 let b = self.pop()?;
486 let a = self.pop()?;
487 let result = JsValue::Number(a.to_number().wrapping_sub(b.to_number()));
488 self.push(result)?;
489 }
490 Opcode::Mul => {
491 let b = self.pop()?;
492 let a = self.pop()?;
493 let result = js_mul(a.to_number(), b.to_number());
494 self.push(JsValue::Number(result))?;
495 }
496 Opcode::Div => {
497 let b = self.pop()?;
498 let a = self.pop()?;
499 let result = js_div(a.to_number(), b.to_number());
500 self.push(JsValue::Number(result))?;
501 }
502 Opcode::Mod => {
503 let b = self.pop()?;
504 let a = self.pop()?;
505 let bn = b.to_number();
506 let result = if bn == 0 {
507 JS_NAN
508 } else {
509 a.to_number().wrapping_rem(bn)
510 };
511 self.push(JsValue::Number(result))?;
512 }
513 Opcode::Neg => {
514 let a = self.pop()?;
515 self.push(JsValue::Number(-a.to_number()))?;
516 }
517 Opcode::Not => {
518 let a = self.pop()?;
519 self.push(JsValue::Boolean(!a.to_boolean()))?;
520 }
521
522 Opcode::BitAnd => {
524 let b = self.pop()?.to_number() >> JS_FRAC_BITS;
525 let a = self.pop()?.to_number() >> JS_FRAC_BITS;
526 self.push(JsValue::Number((a & b) << JS_FRAC_BITS))?;
527 }
528 Opcode::BitOr => {
529 let b = self.pop()?.to_number() >> JS_FRAC_BITS;
530 let a = self.pop()?.to_number() >> JS_FRAC_BITS;
531 self.push(JsValue::Number((a | b) << JS_FRAC_BITS))?;
532 }
533 Opcode::BitXor => {
534 let b = self.pop()?.to_number() >> JS_FRAC_BITS;
535 let a = self.pop()?.to_number() >> JS_FRAC_BITS;
536 self.push(JsValue::Number((a ^ b) << JS_FRAC_BITS))?;
537 }
538 Opcode::ShiftLeft => {
539 let b = (self.pop()?.to_number() >> JS_FRAC_BITS) as u32;
540 let a = self.pop()?.to_number() >> JS_FRAC_BITS;
541 let shift = b & 31;
542 self.push(JsValue::Number((a << shift) << JS_FRAC_BITS))?;
543 }
544 Opcode::ShiftRight => {
545 let b = (self.pop()?.to_number() >> JS_FRAC_BITS) as u32;
546 let a = self.pop()?.to_number() >> JS_FRAC_BITS;
547 let shift = b & 31;
548 self.push(JsValue::Number((a >> shift) << JS_FRAC_BITS))?;
549 }
550
551 Opcode::Eq => {
553 let b = self.pop()?;
554 let a = self.pop()?;
555 self.push(JsValue::Boolean(a.abstract_eq(&b)))?;
556 }
557 Opcode::StrictEq => {
558 let b = self.pop()?;
559 let a = self.pop()?;
560 self.push(JsValue::Boolean(a.strict_eq(&b)))?;
561 }
562 Opcode::NotEq => {
563 let b = self.pop()?;
564 let a = self.pop()?;
565 self.push(JsValue::Boolean(!a.abstract_eq(&b)))?;
566 }
567 Opcode::StrictNotEq => {
568 let b = self.pop()?;
569 let a = self.pop()?;
570 self.push(JsValue::Boolean(!a.strict_eq(&b)))?;
571 }
572 Opcode::Lt => {
573 let b = self.pop()?.to_number();
574 let a = self.pop()?.to_number();
575 self.push(JsValue::Boolean(a < b))?;
576 }
577 Opcode::Gt => {
578 let b = self.pop()?.to_number();
579 let a = self.pop()?.to_number();
580 self.push(JsValue::Boolean(a > b))?;
581 }
582 Opcode::LtEq => {
583 let b = self.pop()?.to_number();
584 let a = self.pop()?.to_number();
585 self.push(JsValue::Boolean(a <= b))?;
586 }
587 Opcode::GtEq => {
588 let b = self.pop()?.to_number();
589 let a = self.pop()?.to_number();
590 self.push(JsValue::Boolean(a >= b))?;
591 }
592
593 Opcode::LogicalAnd => {
595 let b = self.pop()?;
596 let a = self.pop()?;
597 if a.to_boolean() {
598 self.push(b)?;
599 } else {
600 self.push(a)?;
601 }
602 }
603 Opcode::LogicalOr => {
604 let b = self.pop()?;
605 let a = self.pop()?;
606 if a.to_boolean() {
607 self.push(a)?;
608 } else {
609 self.push(b)?;
610 }
611 }
612
613 Opcode::Jump => {
615 let target = self.read_u16()? as usize;
616 if let Some(frame) = self.call_stack.last_mut() {
617 frame.ip = target;
618 }
619 }
620 Opcode::JumpIfFalse => {
621 let target = self.read_u16()? as usize;
622 let val = self.pop()?;
623 if !val.to_boolean() {
624 if let Some(frame) = self.call_stack.last_mut() {
625 frame.ip = target;
626 }
627 }
628 }
629 Opcode::JumpIfTrue => {
630 let target = self.read_u16()? as usize;
631 let val = self.pop()?;
632 if val.to_boolean() {
633 if let Some(frame) = self.call_stack.last_mut() {
634 frame.ip = target;
635 }
636 }
637 }
638
639 Opcode::Call => {
641 let argc = self.read_byte()? as usize;
642 self.call_function(argc)?;
643 }
644 Opcode::Return => {
645 let val = self.pop()?;
646 self.call_stack.pop();
647 self.push(val)?;
648 }
649 Opcode::CreateClosure => {
650 let idx = self.read_u16()? as usize;
651 let frame = self.call_stack.last().unwrap();
652 if let Some(Constant::Function(f)) = frame.constants.get(idx) {
653 let fid = self.register_function(f.clone());
654 self.push(JsValue::Function(fid))?;
655 } else {
656 self.push(JsValue::Undefined)?;
657 }
658 }
659
660 Opcode::CreateObject => {
662 let oid = self.alloc_object(JsObject::new());
663 self.push(JsValue::Object(oid))?;
664 }
665 Opcode::CreateArray => {
666 let count = self.read_byte()? as usize;
667 let mut arr = JsObject::array();
668 let start = self.stack.len().saturating_sub(count);
669 for i in 0..count {
670 let idx = start + i;
671 let val = if idx < self.stack.len() {
672 self.stack[idx].clone()
673 } else {
674 JsValue::Undefined
675 };
676 arr.set(&format!("{}", i), val);
677 }
678 arr.set("length", JsValue::Number((count as i64) << JS_FRAC_BITS));
679 self.stack.truncate(start);
680 let oid = self.alloc_object(arr);
681 self.push(JsValue::Object(oid))?;
682 }
683
684 Opcode::GetThis => {
686 self.push(JsValue::Undefined)?; }
688 Opcode::Typeof => {
689 let val = self.pop()?;
690 self.push(JsValue::String(val.js_typeof().to_string()))?;
691 }
692 Opcode::Instanceof | Opcode::In => {
693 let _b = self.pop()?;
694 let _a = self.pop()?;
695 self.push(JsValue::Boolean(false))?; }
697 Opcode::Throw => {
698 let val = self.pop()?;
699 if let Some(try_state) = self.try_stack.pop() {
700 while self.call_stack.len() > try_state.call_depth {
701 self.call_stack.pop();
702 }
703 self.stack.truncate(try_state.stack_depth);
704 self.push(val)?;
705 if let Some(frame) = self.call_stack.last_mut() {
706 frame.ip = try_state.catch_ip;
707 }
708 } else {
709 return Err(JsVmError::UncaughtException {
710 message: val.to_js_string(),
711 });
712 }
713 }
714 Opcode::EnterTry => {
715 let catch_ip = self.read_u16()? as usize;
716 self.try_stack.push(TryState {
717 catch_ip,
718 stack_depth: self.stack.len(),
719 call_depth: self.call_stack.len(),
720 });
721 }
722 Opcode::LeaveTry => {
723 self.try_stack.pop();
724 }
725
726 Opcode::Pop => {
728 self.pop()?;
729 }
730 Opcode::Dup => {
731 let val = self.stack.last().cloned().unwrap_or(JsValue::Undefined);
732 self.push(val)?;
733 }
734 }
735 }
736 }
737
738 fn push(&mut self, val: JsValue) -> Result<(), JsVmError> {
741 if self.stack.len() >= self.max_stack {
742 return Err(JsVmError::StackOverflow);
743 }
744 self.stack.push(val);
745 Ok(())
746 }
747
748 fn pop(&mut self) -> Result<JsValue, JsVmError> {
749 self.stack.pop().ok_or(JsVmError::StackUnderflow)
750 }
751
752 fn read_byte(&mut self) -> Result<u8, JsVmError> {
753 let frame = self.call_stack.last_mut().ok_or(JsVmError::NoCallFrame)?;
754 if frame.ip >= frame.bytecode.len() {
755 return Err(JsVmError::IpOutOfBounds);
756 }
757 let b = frame.bytecode[frame.ip];
758 frame.ip += 1;
759 Ok(b)
760 }
761
762 fn read_u16(&mut self) -> Result<u16, JsVmError> {
763 let hi = self.read_byte()? as u16;
764 let lo = self.read_byte()? as u16;
765 Ok((hi << 8) | lo)
766 }
767
768 fn get_string_constant(&self, idx: usize) -> Result<String, JsVmError> {
769 let frame = self.call_stack.last().ok_or(JsVmError::NoCallFrame)?;
770 match frame.constants.get(idx) {
771 Some(Constant::Str(s)) => Ok(s.clone()),
772 _ => Ok(format!("{}", idx)),
773 }
774 }
775
776 fn get_property(&self, obj: &JsValue, key: &str) -> JsValue {
777 match obj {
778 JsValue::Object(oid) => {
779 if let Some(obj) = self.object_arena.get(*oid) {
780 if key == "length" && obj.internal_type == ObjectType::Array {
781 return obj.get("length");
782 }
783 obj.get(key)
784 } else {
785 JsValue::Undefined
786 }
787 }
788 JsValue::String(s) => {
789 if key == "length" {
790 JsValue::Number((s.len() as i64) << JS_FRAC_BITS)
791 } else {
792 JsValue::Undefined
793 }
794 }
795 _ => JsValue::Undefined,
796 }
797 }
798
799 fn set_property(&mut self, obj: &JsValue, key: &str, val: JsValue) {
800 if let JsValue::Object(oid) = obj {
801 if let Some(o) = self.object_arena.get_mut(*oid) {
802 if o.internal_type == ObjectType::Array {
803 if let Ok(idx) = key.parse::<usize>() {
804 let current_len = match o.get("length") {
805 JsValue::Number(n) => (n >> JS_FRAC_BITS) as usize,
806 _ => 0,
807 };
808 if idx >= current_len {
809 o.set(
810 "length",
811 JsValue::Number(((idx + 1) as i64) << JS_FRAC_BITS),
812 );
813 }
814 }
815 }
816 o.set(key, val);
817 }
818 }
819 }
820
821 fn js_add(&self, a: &JsValue, b: &JsValue) -> JsValue {
823 if matches!(a, JsValue::String(_)) || matches!(b, JsValue::String(_)) {
824 let mut s = a.to_js_string();
825 s.push_str(&b.to_js_string());
826 JsValue::String(s)
827 } else {
828 JsValue::Number(a.to_number().wrapping_add(b.to_number()))
829 }
830 }
831
832 fn call_function(&mut self, argc: usize) -> Result<(), JsVmError> {
834 let mut args = Vec::with_capacity(argc);
835 for _ in 0..argc {
836 args.push(self.pop()?);
837 }
838 args.reverse();
839
840 let callee = self.pop()?;
841
842 match callee {
843 JsValue::Function(fid) => {
844 let template = self
845 .function_arena
846 .get(fid)
847 .cloned()
848 .ok_or(JsVmError::InvalidFunctionId)?;
849
850 let mut locals = Vec::with_capacity(template.local_count);
851 for i in 0..template.param_count {
852 locals.push(args.get(i).cloned().unwrap_or(JsValue::Undefined));
853 }
854 while locals.len() < template.local_count {
855 locals.push(JsValue::Undefined);
856 }
857
858 let frame = CallFrame {
859 function_id: fid,
860 ip: 0,
861 base_slot: self.stack.len(),
862 locals,
863 bytecode: template.bytecode.clone(),
864 constants: template.constants.clone(),
865 };
866 self.call_stack.push(frame);
867 }
868 JsValue::Object(_oid) => {
869 let msg: Vec<String> = args.iter().map(|a| a.to_js_string()).collect();
871 self.output.push(msg.join(" "));
872 self.push(JsValue::Undefined)?;
873 }
874 _ => {
875 return Err(JsVmError::NotCallable);
876 }
877 }
878
879 Ok(())
880 }
881}
882
883fn js_mul(a: JsNumber, b: JsNumber) -> JsNumber {
889 if a == JS_NAN || b == JS_NAN {
890 return JS_NAN;
891 }
892 ((a as i128 * b as i128) >> JS_FRAC_BITS) as i64
893}
894
895fn js_div(a: JsNumber, b: JsNumber) -> JsNumber {
897 if b == JS_ZERO || a == JS_NAN || b == JS_NAN {
898 return JS_NAN;
899 }
900 (((a as i128) << JS_FRAC_BITS) / b as i128) as i64
901}
902
903fn parse_js_number(s: &str) -> JsNumber {
905 let s = s.trim();
906 if s.is_empty() {
907 return JS_ZERO;
908 }
909 let (neg, s) = if let Some(rest) = s.strip_prefix('-') {
910 (true, rest)
911 } else {
912 (false, s)
913 };
914 let mut result: i64 = 0;
915 for &b in s.as_bytes() {
916 if b.is_ascii_digit() {
917 result = result.wrapping_mul(10).wrapping_add((b - b'0') as i64);
918 } else {
919 return JS_NAN;
920 }
921 }
922 let fixed = result << JS_FRAC_BITS;
923 if neg {
924 -fixed
925 } else {
926 fixed
927 }
928}
929
930fn format_js_number(n: JsNumber) -> String {
932 if n == JS_NAN {
933 return "NaN".to_string();
934 }
935 let int_part = n >> JS_FRAC_BITS;
936 let frac_mask = (1i64 << JS_FRAC_BITS) - 1;
937 let frac_part = n & frac_mask;
938
939 if frac_part == 0 {
940 format!("{}", int_part)
941 } else {
942 let frac_decimal = (frac_part.unsigned_abs() * 1_000_000) >> JS_FRAC_BITS;
943 let frac_str = format!("{:06}", frac_decimal);
944 let trimmed = frac_str.trim_end_matches('0');
945 if int_part < 0 || (int_part == 0 && n < 0) {
946 format!("-{}.{}", int_part.unsigned_abs(), trimmed)
947 } else {
948 format!("{}.{}", int_part, trimmed)
949 }
950 }
951}
952
953#[cfg(test)]
958mod tests {
959 use super::*;
960 use crate::browser::{js_compiler::Compiler, js_lexer::js_int, js_parser::JsParser};
961
962 fn run_js(src: &str) -> Result<JsValue, JsVmError> {
963 let mut parser = JsParser::from_source(src);
964 let root = parser.parse();
965 let mut compiler = Compiler::new();
966 let chunk = compiler.compile(&parser.arena, root);
967 let mut vm = JsVm::new();
968 vm.run_chunk(&chunk)
969 }
970
971 fn run_js_global(src: &str, name: &str) -> JsValue {
972 let mut parser = JsParser::from_source(src);
973 let root = parser.parse();
974 let mut compiler = Compiler::new();
975 let chunk = compiler.compile(&parser.arena, root);
976 let mut vm = JsVm::new();
977 let _ = vm.run_chunk(&chunk);
978 vm.globals.get(name).cloned().unwrap_or(JsValue::Undefined)
979 }
980
981 #[test]
982 fn test_number_literal() {
983 let _result = run_js("42;").unwrap();
984 }
985
986 #[test]
987 fn test_var_and_load() {
988 let val = run_js_global("let x = 10;", "x");
989 assert!(matches!(val, JsValue::Number(n) if n == js_int(10)));
990 }
991
992 #[test]
993 fn test_addition() {
994 let val = run_js_global("let x = 1 + 2;", "x");
995 assert!(matches!(val, JsValue::Number(n) if n == js_int(3)));
996 }
997
998 #[test]
999 fn test_subtraction() {
1000 let val = run_js_global("let x = 10 - 3;", "x");
1001 assert!(matches!(val, JsValue::Number(n) if n == js_int(7)));
1002 }
1003
1004 #[test]
1005 fn test_multiplication() {
1006 let val = run_js_global("let x = 3 * 4;", "x");
1007 assert!(matches!(val, JsValue::Number(n) if n == js_int(12)));
1008 }
1009
1010 #[test]
1011 fn test_division() {
1012 let val = run_js_global("let x = 10 / 2;", "x");
1013 assert!(matches!(val, JsValue::Number(n) if n == js_int(5)));
1014 }
1015
1016 #[test]
1017 fn test_string_concat() {
1018 let val = run_js_global("let x = 'hello' + ' ' + 'world';", "x");
1019 assert!(matches!(val, JsValue::String(ref s) if s == "hello world"));
1020 }
1021
1022 #[test]
1023 fn test_comparison_lt() {
1024 let val = run_js_global("let x = 1 < 2;", "x");
1025 assert!(matches!(val, JsValue::Boolean(true)));
1026 }
1027
1028 #[test]
1029 fn test_comparison_gt() {
1030 let val = run_js_global("let x = 5 > 3;", "x");
1031 assert!(matches!(val, JsValue::Boolean(true)));
1032 }
1033
1034 #[test]
1035 fn test_strict_eq() {
1036 let val = run_js_global("let x = 1 === 1;", "x");
1037 assert!(matches!(val, JsValue::Boolean(true)));
1038 }
1039
1040 #[test]
1041 fn test_strict_neq() {
1042 let val = run_js_global("let x = 1 !== 2;", "x");
1043 assert!(matches!(val, JsValue::Boolean(true)));
1044 }
1045
1046 #[test]
1047 fn test_if_true() {
1048 let val = run_js_global("let x = 0; if (true) { x = 1; }", "x");
1049 assert!(matches!(val, JsValue::Number(n) if n == js_int(1)));
1050 }
1051
1052 #[test]
1053 fn test_if_false() {
1054 let val = run_js_global("let x = 0; if (false) { x = 1; } else { x = 2; }", "x");
1055 assert!(matches!(val, JsValue::Number(n) if n == js_int(2)));
1056 }
1057
1058 #[test]
1059 fn test_while_loop() {
1060 let val = run_js_global("let x = 0; while (x < 5) { x = x + 1; }", "x");
1061 assert!(matches!(val, JsValue::Number(n) if n == js_int(5)));
1062 }
1063
1064 #[test]
1065 fn test_for_loop() {
1066 let val = run_js_global(
1067 "let sum = 0; for (let i = 1; i < 4; i = i + 1) { sum = sum + i; }",
1068 "sum",
1069 );
1070 assert!(matches!(val, JsValue::Number(n) if n == js_int(6)));
1071 }
1072
1073 #[test]
1074 fn test_function_call() {
1075 let val = run_js_global(
1076 "function add(a, b) { return a + b; } let x = add(3, 4);",
1077 "x",
1078 );
1079 assert!(matches!(val, JsValue::Number(n) if n == js_int(7)));
1080 }
1081
1082 #[test]
1083 fn test_nested_function() {
1084 let val = run_js_global(
1085 "function f(x) { return x * 2; } function g(x) { return f(x) + 1; } let r = g(5);",
1086 "r",
1087 );
1088 assert!(matches!(val, JsValue::Number(n) if n == js_int(11)));
1089 }
1090
1091 #[test]
1092 fn test_object_property() {
1093 let val = run_js_global("let o = {x: 42}; let v = o.x;", "v");
1094 assert!(matches!(val, JsValue::Number(n) if n == js_int(42)));
1095 }
1096
1097 #[test]
1098 fn test_array_access() {
1099 let val = run_js_global("let a = [10, 20, 30]; let v = a[1];", "v");
1100 assert!(matches!(val, JsValue::Number(n) if n == js_int(20)));
1101 }
1102
1103 #[test]
1104 fn test_string_length() {
1105 let val = run_js_global("let s = 'hello'; let v = s.length;", "v");
1106 assert!(matches!(val, JsValue::Number(n) if n == js_int(5)));
1107 }
1108
1109 #[test]
1110 fn test_boolean_not() {
1111 let val = run_js_global("let x = !true;", "x");
1112 assert!(matches!(val, JsValue::Boolean(false)));
1113 }
1114
1115 #[test]
1116 fn test_negation() {
1117 let val = run_js_global("let x = -5;", "x");
1118 assert!(matches!(val, JsValue::Number(n) if n == js_int(-5)));
1119 }
1120
1121 #[test]
1122 fn test_typeof() {
1123 let val = run_js_global("let x = typeof 42;", "x");
1124 assert!(matches!(val, JsValue::String(ref s) if s == "number"));
1125 }
1126
1127 #[test]
1128 fn test_null_undefined() {
1129 let val = run_js_global("let x = null;", "x");
1130 assert!(matches!(val, JsValue::Null));
1131 let val2 = run_js_global("let x = undefined;", "x");
1132 assert!(matches!(val2, JsValue::Undefined));
1133 }
1134
1135 #[test]
1136 fn test_logical_and() {
1137 let val = run_js_global("let x = true && 42;", "x");
1138 assert!(matches!(val, JsValue::Number(n) if n == js_int(42)));
1139 }
1140
1141 #[test]
1142 fn test_logical_or() {
1143 let val = run_js_global("let x = false || 'fallback';", "x");
1144 assert!(matches!(val, JsValue::String(ref s) if s == "fallback"));
1145 }
1146
1147 #[test]
1148 fn test_compound_assign() {
1149 let val = run_js_global("let x = 5; x += 3;", "x");
1150 assert!(matches!(val, JsValue::Number(n) if n == js_int(8)));
1151 }
1152
1153 #[test]
1154 fn test_js_value_truthiness() {
1155 assert!(!JsValue::Undefined.to_boolean());
1156 assert!(!JsValue::Null.to_boolean());
1157 assert!(!JsValue::Boolean(false).to_boolean());
1158 assert!(!JsValue::Number(JS_ZERO).to_boolean());
1159 assert!(!JsValue::String(String::new()).to_boolean());
1160 assert!(JsValue::Boolean(true).to_boolean());
1161 assert!(JsValue::Number(JS_ONE).to_boolean());
1162 assert!(JsValue::String("x".to_string()).to_boolean());
1163 }
1164
1165 #[test]
1166 fn test_js_value_to_string() {
1167 assert_eq!(JsValue::Undefined.to_js_string(), "undefined");
1168 assert_eq!(JsValue::Null.to_js_string(), "null");
1169 assert_eq!(JsValue::Boolean(true).to_js_string(), "true");
1170 assert_eq!(JsValue::Number(js_int(42)).to_js_string(), "42");
1171 }
1172
1173 #[test]
1174 fn test_abstract_eq_null_undefined() {
1175 assert!(JsValue::Null.abstract_eq(&JsValue::Undefined));
1176 assert!(JsValue::Undefined.abstract_eq(&JsValue::Null));
1177 }
1178
1179 #[test]
1180 fn test_js_mul_overflow_safe() {
1181 let r = js_mul(js_int(1000), js_int(1000));
1182 assert_eq!(r >> JS_FRAC_BITS, 1_000_000);
1183 }
1184
1185 #[test]
1186 fn test_js_div_by_zero() {
1187 assert_eq!(js_div(js_int(1), JS_ZERO), JS_NAN);
1188 }
1189
1190 #[test]
1191 fn test_format_js_number_integer() {
1192 assert_eq!(format_js_number(js_int(42)), "42");
1193 assert_eq!(format_js_number(js_int(-5)), "-5");
1194 assert_eq!(format_js_number(js_int(0)), "0");
1195 }
1196
1197 #[test]
1198 fn test_format_js_number_nan() {
1199 assert_eq!(format_js_number(JS_NAN), "NaN");
1200 }
1201
1202 #[test]
1203 fn test_parse_js_number() {
1204 assert_eq!(parse_js_number("42"), js_int(42));
1205 assert_eq!(parse_js_number("-10"), js_int(-10));
1206 assert_eq!(parse_js_number(""), JS_ZERO);
1207 assert_eq!(parse_js_number("abc"), JS_NAN);
1208 }
1209
1210 #[test]
1211 fn test_execution_limit() {
1212 let result = run_js("while (true) { }");
1213 assert!(result.is_err());
1214 }
1215
1216 #[test]
1217 fn test_object_type_default() {
1218 assert_eq!(ObjectType::default(), ObjectType::Ordinary);
1219 }
1220
1221 #[test]
1222 fn test_js_object_array() {
1223 let arr = JsObject::array();
1224 assert_eq!(arr.internal_type, ObjectType::Array);
1225 }
1226
1227 #[test]
1228 fn test_recursive_function() {
1229 let val = run_js_global(
1230 "function fib(n) { if (n < 2) { return n; } return fib(n - 1) + fib(n - 2); } let x = \
1231 fib(6);",
1232 "x",
1233 );
1234 assert!(matches!(val, JsValue::Number(n) if n == js_int(8)));
1235 }
1236
1237 #[test]
1238 fn test_multiple_vars() {
1239 run_js_global("let a = 1; let b = 2; let c = 3;", "c");
1240 }
1241
1242 #[test]
1243 fn test_empty_function() {
1244 run_js_global("function f() {} let x = f();", "x");
1245 }
1246
1247 #[test]
1248 fn test_return_no_value() {
1249 let val = run_js_global("function f() { return; } let x = f();", "x");
1250 assert!(matches!(val, JsValue::Undefined));
1251 }
1252
1253 #[test]
1254 fn test_bitwise_and() {
1255 let val = run_js_global("let x = 6 & 3;", "x");
1256 assert!(matches!(val, JsValue::Number(n) if n == js_int(2)));
1257 }
1258
1259 #[test]
1260 fn test_bitwise_or() {
1261 let val = run_js_global("let x = 4 | 2;", "x");
1262 assert!(matches!(val, JsValue::Number(n) if n == js_int(6)));
1263 }
1264
1265 #[test]
1266 fn test_modulo() {
1267 let val = run_js_global("let x = 10 % 3;", "x");
1268 assert!(matches!(val, JsValue::Number(n) if n == js_int(1)));
1269 }
1270}