1use fusevm::{Chunk, NumOp, VMResult, Value, VM};
17use indexmap::IndexMap;
18use std::cell::RefCell;
19use std::rc::Rc;
20
21pub mod ops {
25 pub const GETLOCAL: u16 = 1; pub const SETLOCAL: u16 = 2; pub const DECLARE: u16 = 3; pub const DELNAME: u16 = 4; pub const GETATTR: u16 = 5; pub const SETATTR: u16 = 6; pub const GETITEM: u16 = 7; pub const SETITEM: u16 = 8; pub const DELITEM: u16 = 9; pub const MKSTR: u16 = 10; pub const MKARR: u16 = 11; pub const MKOBJ: u16 = 12; pub const CALL: u16 = 13; pub const CALL_METHOD: u16 = 14; pub const CALL_VALUE: u16 = 15; pub const NEW: u16 = 16; pub const TRUTHY: u16 = 17; pub const TOSTR: u16 = 18; pub const MKFUNC: u16 = 19; pub const GETITER: u16 = 20; pub const FORITER: u16 = 21; pub const FORIN_KEYS: u16 = 22; pub const CONTAINS: u16 = 23; pub const SIG_RETURN: u16 = 24; pub const BINOP: u16 = 25; pub const UNARY: u16 = 26; pub const STRICT_EQ: u16 = 27; pub const LOOSE_EQ: u16 = 28; pub const TYPEOF: u16 = 29; pub const LOAD_NULL: u16 = 30; pub const THROW: u16 = 31; pub const TRY: u16 = 32; pub const NULLISH: u16 = 33; pub const UNPACK: u16 = 34; pub const BUILD_ARGS: u16 = 35; pub const THIS: u16 = 36; pub const INSTANCEOF: u16 = 37; pub const DELPROP_NAME: u16 = 38; pub const APPLY: u16 = 39; pub const APPLY_METHOD: u16 = 40; pub const OBJ_REST: u16 = 41; pub const DIV: u16 = 42; }
68
69pub mod binop {
71 pub const BITAND: i64 = 0;
72 pub const BITOR: i64 = 1;
73 pub const BITXOR: i64 = 2;
74 pub const SHL: i64 = 3;
75 pub const SHR: i64 = 4;
76 pub const USHR: i64 = 5;
77}
78
79pub mod unop {
81 pub const POS: i64 = 0; pub const BITNOT: i64 = 1; }
84
85#[derive(Clone)]
90pub struct FuncDef {
91 pub name: String,
92 pub params: Vec<ParamSlot>,
95 pub chunk: Chunk,
96 pub is_arrow: bool,
97}
98
99#[derive(Clone)]
102pub struct ParamSlot {
103 pub name: String,
104 pub rest: bool,
106 pub has_default: bool,
108}
109
110#[derive(Clone)]
113pub struct TryDef {
114 pub block: Chunk,
115 pub handler: Option<(Option<String>, Chunk)>,
117 pub finalizer: Option<Chunk>,
118}
119
120#[derive(Clone)]
122pub struct FuncVal {
123 pub def_id: usize,
124 pub env: Option<Env>,
126 pub this: Option<Value>,
128 pub is_arrow: bool,
129}
130
131#[derive(Clone)]
133pub enum JsObj {
134 Str(String),
135 Array(Vec<Value>),
136 Object(IndexMap<String, Value>),
137 Func(FuncVal),
138 Builtin(String),
141 BoundMethod { recv: Value, name: String },
144 Null,
146 Iter { items: Vec<Value>, idx: usize },
148}
149
150pub struct EnvData {
155 pub vars: IndexMap<String, Value>,
156 pub parent: Option<Env>,
157}
158pub type Env = Rc<RefCell<EnvData>>;
159
160fn new_env(parent: Option<Env>) -> Env {
161 Rc::new(RefCell::new(EnvData {
162 vars: IndexMap::new(),
163 parent,
164 }))
165}
166
167pub struct Frame {
169 pub env: Env,
170 pub this_obj: Option<Value>,
171}
172
173#[derive(Clone)]
175pub enum Signal {
176 Return(Value),
177 Break,
178 Continue,
179}
180
181pub struct JsHost {
183 heap: Vec<JsObj>,
184 pub funcs: Vec<FuncDef>,
186 pub tries: Vec<TryDef>,
188 globals: IndexMap<String, Value>,
190 frames: Vec<Frame>,
192 pub error: Option<String>,
193 pub exc: Option<Value>,
195 pub signal: Option<Signal>,
196 null_val: Value,
198}
199
200thread_local! {
201 static HOST: RefCell<JsHost> = RefCell::new(JsHost::new());
202}
203
204pub fn with_host<R>(f: impl FnOnce(&mut JsHost) -> R) -> R {
206 HOST.with(|h| f(&mut h.borrow_mut()))
207}
208
209pub fn reset_host() {
211 with_host(|h| *h = JsHost::new());
212}
213
214impl Default for JsHost {
215 fn default() -> Self {
216 Self::new()
217 }
218}
219
220impl JsHost {
221 pub fn new() -> JsHost {
222 let module_env = new_env(None);
223 let mut h = JsHost {
224 heap: Vec::new(),
225 funcs: Vec::new(),
226 tries: Vec::new(),
227 globals: IndexMap::new(),
228 frames: vec![Frame {
229 env: module_env,
230 this_obj: None,
231 }],
232 error: None,
233 exc: None,
234 signal: None,
235 null_val: Value::Undef,
236 };
237 h.null_val = h.alloc(JsObj::Null);
238 h
239 }
240
241 pub fn null(&self) -> Value {
242 self.null_val.clone()
243 }
244 pub fn is_null(&self, v: &Value) -> bool {
245 matches!(self.get(v), Some(JsObj::Null))
246 }
247
248 pub fn program_offsets(&self) -> (usize, usize) {
250 (self.funcs.len(), self.tries.len())
251 }
252 pub fn load_program(&mut self, funcs: Vec<FuncDef>, tries: Vec<TryDef>) {
253 self.funcs.extend(funcs);
254 self.tries.extend(tries);
255 }
256 pub fn try_def(&self, id: usize) -> Option<TryDef> {
257 self.tries.get(id).cloned()
258 }
259
260 pub fn alloc(&mut self, obj: JsObj) -> Value {
262 self.heap.push(obj);
263 Value::Obj((self.heap.len() - 1) as u32)
264 }
265 pub fn get(&self, v: &Value) -> Option<&JsObj> {
266 if let Value::Obj(i) = v {
267 self.heap.get(*i as usize)
268 } else {
269 None
270 }
271 }
272 pub fn get_mut(&mut self, v: &Value) -> Option<&mut JsObj> {
273 if let Value::Obj(i) = v {
274 self.heap.get_mut(*i as usize)
275 } else {
276 None
277 }
278 }
279 pub fn new_str(&mut self, s: impl Into<String>) -> Value {
280 self.alloc(JsObj::Str(s.into()))
281 }
282 pub fn new_array(&mut self, items: Vec<Value>) -> Value {
283 self.alloc(JsObj::Array(items))
284 }
285 pub fn new_object(&mut self, props: IndexMap<String, Value>) -> Value {
286 self.alloc(JsObj::Object(props))
287 }
288 pub fn as_str(&self, v: &Value) -> Option<String> {
289 match v {
290 Value::Str(s) => Some((**s).clone()),
291 Value::Obj(_) => match self.get(v) {
292 Some(JsObj::Str(s)) => Some(s.clone()),
293 _ => None,
294 },
295 _ => None,
296 }
297 }
298
299 fn frame(&self) -> &Frame {
301 self.frames.last().unwrap()
302 }
303 fn cur_env(&self) -> Env {
304 self.frame().env.clone()
305 }
306
307 pub fn read_name(&self, name: &str) -> Option<Value> {
309 let mut env = Some(self.cur_env());
310 while let Some(e) = env {
311 if let Some(v) = e.borrow().vars.get(name) {
312 return Some(v.clone());
313 }
314 env = e.borrow().parent.clone();
315 }
316 self.globals.get(name).cloned()
317 }
318 pub fn read_global(&self, name: &str) -> Option<Value> {
319 self.globals.get(name).cloned()
320 }
321
322 pub fn set_name(&mut self, name: &str, val: Value) {
325 let mut env = Some(self.cur_env());
326 while let Some(e) = env {
327 if e.borrow().vars.contains_key(name) {
328 e.borrow_mut().vars.insert(name.to_string(), val);
329 return;
330 }
331 env = e.borrow().parent.clone();
332 }
333 self.globals.insert(name.to_string(), val);
334 }
335
336 pub fn declare_name(&mut self, name: &str, val: Value) {
338 if self.frames.len() == 1 {
339 self.globals.insert(name.to_string(), val);
340 } else {
341 self.cur_env().borrow_mut().vars.insert(name.to_string(), val);
342 }
343 }
344 pub fn set_global(&mut self, name: &str, val: Value) {
345 self.globals.insert(name.to_string(), val);
346 }
347 pub fn del_name(&mut self, name: &str) {
348 if self.cur_env().borrow_mut().vars.shift_remove(name).is_some() {
349 return;
350 }
351 self.globals.shift_remove(name);
352 }
353
354 pub fn current_this(&self) -> Option<Value> {
355 self.frame().this_obj.clone()
356 }
357 pub fn current_env_capture(&self) -> Env {
358 self.frame().env.clone()
359 }
360
361 pub fn take_error(&mut self) -> Option<String> {
363 self.error.take()
364 }
365 pub fn raise_str(&mut self, class: &str, msg: &str) -> String {
366 let s = if msg.is_empty() {
367 class.to_string()
368 } else {
369 format!("{class}: {msg}")
370 };
371 self.error = Some(s.clone());
372 s
373 }
374}
375
376pub fn type_error(msg: &str) -> String {
379 format!("TypeError: {msg}")
380}
381pub fn ref_error(name: &str) -> String {
382 format!("ReferenceError: {name} is not defined")
383}
384
385pub fn run_chunk_on(chunk: Chunk) -> Result<Value, String> {
389 let mut vm = VM::new(chunk);
390 crate::builtins::install(&mut vm);
391 vm.set_numeric_hook(std::sync::Arc::new(|op, a, b| {
392 crate::builtins::numeric_hook(op, a, b)
393 }));
394 vm.enable_tracing_jit();
395 let outcome = vm.run();
396 if let Some(e) = with_host(|h| h.take_error()) {
397 return Err(e);
398 }
399 match outcome {
400 VMResult::Ok(v) => Ok(v),
401 VMResult::Halted => Ok(vm.stack.last().cloned().unwrap_or(Value::Undef)),
402 VMResult::Error(e) => Err(e),
403 }
404}
405
406pub fn run_main(chunk: Chunk) -> Result<Value, String> {
408 let r = run_chunk_on(chunk);
409 with_host(|h| h.signal = None);
410 r
411}
412
413pub fn fmt_number(f: f64) -> String {
418 if f.is_nan() {
419 return "NaN".into();
420 }
421 if f.is_infinite() {
422 return if f > 0.0 { "Infinity" } else { "-Infinity" }.into();
423 }
424 if f == 0.0 {
425 return "0".into();
427 }
428 if f < 0.0 {
429 return format!("-{}", js_number_repr(-f));
430 }
431 js_number_repr(f)
432}
433
434fn js_number_repr(a: f64) -> String {
444 let sci = format!("{a:e}");
447 let (mant, exp_str) = sci.split_once('e').expect("LowerExp always has 'e'");
448 let e: i32 = exp_str.parse().expect("LowerExp exponent is an integer");
449 let s: String = mant.chars().filter(|c| *c != '.').collect();
450 let k = s.len() as i32; let n = e + 1; if k <= n && n <= 21 {
454 let mut out = s;
456 out.push_str(&"0".repeat((n - k) as usize));
457 out
458 } else if 0 < n && n <= 21 {
459 format!("{}.{}", &s[..n as usize], &s[n as usize..])
461 } else if -6 < n && n <= 0 {
462 format!("0.{}{}", "0".repeat((-n) as usize), s)
464 } else {
465 let exp = n - 1;
467 let sign = if exp >= 0 { '+' } else { '-' };
468 let mag = exp.abs();
469 if k == 1 {
470 format!("{s}e{sign}{mag}")
471 } else {
472 format!("{}.{}e{sign}{mag}", &s[..1], &s[1..])
473 }
474 }
475}
476
477impl JsHost {
478 pub fn type_of(&self, v: &Value) -> &'static str {
480 match v {
481 Value::Undef => "undefined",
482 Value::Bool(_) => "boolean",
483 Value::Int(_) | Value::Float(_) => "number",
484 Value::Str(_) => "string",
485 Value::Obj(_) => match self.get(v) {
486 Some(JsObj::Str(_)) => "string",
487 Some(JsObj::Func(_)) | Some(JsObj::Builtin(_)) | Some(JsObj::BoundMethod { .. }) => {
488 "function"
489 }
490 _ => "object", },
492 _ => "object",
493 }
494 }
495
496 pub fn truthy(&self, v: &Value) -> bool {
498 match v {
499 Value::Undef => false,
500 Value::Bool(b) => *b,
501 Value::Int(n) => *n != 0,
502 Value::Float(f) => *f != 0.0 && !f.is_nan(),
503 Value::Str(s) => !s.is_empty(),
504 Value::Obj(_) => match self.get(v) {
505 Some(JsObj::Str(s)) => !s.is_empty(),
506 Some(JsObj::Null) => false,
507 _ => true, },
509 _ => true,
510 }
511 }
512
513 pub fn to_number(&self, v: &Value) -> f64 {
515 match v {
516 Value::Undef => f64::NAN,
517 Value::Bool(b) => {
518 if *b {
519 1.0
520 } else {
521 0.0
522 }
523 }
524 Value::Int(n) => *n as f64,
525 Value::Float(f) => *f,
526 Value::Str(s) => str_to_number(s),
527 Value::Obj(_) => match self.get(v) {
528 Some(JsObj::Str(s)) => str_to_number(s),
529 Some(JsObj::Null) => 0.0,
530 Some(JsObj::Array(items)) => {
531 if items.is_empty() {
533 0.0
534 } else if items.len() == 1 {
535 self.to_number(&items[0])
536 } else {
537 f64::NAN
538 }
539 }
540 _ => f64::NAN,
541 },
542 _ => f64::NAN,
543 }
544 }
545
546 pub fn str_of(&self, v: &Value) -> String {
548 match v {
549 Value::Undef => "undefined".into(),
550 Value::Bool(b) => if *b { "true" } else { "false" }.into(),
551 Value::Int(n) => n.to_string(),
552 Value::Float(f) => fmt_number(*f),
553 Value::Str(s) => (**s).clone(),
554 Value::Obj(_) => match self.get(v) {
555 Some(JsObj::Str(s)) => s.clone(),
556 Some(JsObj::Null) => "null".into(),
557 Some(JsObj::Array(items)) => {
558 let parts: Vec<String> = items
560 .iter()
561 .map(|x| match x {
562 Value::Undef => String::new(),
563 _ if self.is_null(x) => String::new(),
564 _ => self.str_of(x),
565 })
566 .collect();
567 parts.join(",")
568 }
569 Some(JsObj::Object(_)) => "[object Object]".into(),
570 Some(JsObj::Func(f)) => {
571 let name = self.funcs.get(f.def_id).map(|d| d.name.clone()).unwrap_or_default();
572 format!("function {name}() {{ [code] }}")
573 }
574 Some(JsObj::Builtin(n)) => format!("function {n}() {{ [native code] }}"),
575 Some(JsObj::BoundMethod { .. }) => "function () { [native code] }".into(),
576 _ => "[object Object]".into(),
577 },
578 _ => "[object Object]".into(),
579 }
580 }
581
582 pub fn console_format(&self, v: &Value) -> String {
585 match v {
586 Value::Str(_) => self.str_of(v),
587 Value::Obj(_) if matches!(self.get(v), Some(JsObj::Str(_))) => self.str_of(v),
588 _ => self.inspect(v),
589 }
590 }
591
592 pub fn inspect(&self, v: &Value) -> String {
594 match v {
595 Value::Undef => "undefined".into(),
596 Value::Bool(b) => if *b { "true" } else { "false" }.into(),
597 Value::Int(n) => n.to_string(),
598 Value::Float(f) if *f == 0.0 && f.is_sign_negative() => "-0".into(),
600 Value::Float(f) => fmt_number(*f),
601 Value::Str(s) => quote_str(s),
602 Value::Obj(_) => match self.get(v) {
603 Some(JsObj::Str(s)) => quote_str(s),
604 Some(JsObj::Null) => "null".into(),
605 Some(JsObj::Array(items)) => {
606 if items.is_empty() {
607 return "[]".into();
608 }
609 let inner: Vec<String> = items.iter().map(|x| self.inspect(x)).collect();
610 format!("[ {} ]", inner.join(", "))
611 }
612 Some(JsObj::Object(props)) => {
613 if props.is_empty() {
614 return "{}".into();
615 }
616 let inner: Vec<String> = props
617 .iter()
618 .map(|(k, val)| format!("{}: {}", fmt_key(k), self.inspect(val)))
619 .collect();
620 format!("{{ {} }}", inner.join(", "))
621 }
622 Some(JsObj::Func(f)) => {
623 let name = self.funcs.get(f.def_id).map(|d| d.name.clone()).unwrap_or_default();
624 if name.is_empty() {
625 "[Function (anonymous)]".into()
626 } else {
627 format!("[Function: {name}]")
628 }
629 }
630 Some(JsObj::Builtin(n)) => {
631 let short = n.rsplit('.').next().unwrap_or(n);
632 format!("[Function: {short}]")
633 }
634 Some(JsObj::BoundMethod { .. }) => "[Function (anonymous)]".into(),
635 _ => "undefined".into(),
636 },
637 _ => "undefined".into(),
638 }
639 }
640
641 pub fn strict_eq(&self, a: &Value, b: &Value) -> bool {
645 match (a, b) {
646 (Value::Undef, Value::Undef) => true,
647 (Value::Bool(x), Value::Bool(y)) => x == y,
648 (Value::Str(x), Value::Str(y)) => x == y,
649 _ => {
650 let an = matches!(a, Value::Int(_) | Value::Float(_));
652 let bn = matches!(b, Value::Int(_) | Value::Float(_));
653 if an && bn {
654 let x = self.to_number(a);
655 let y = self.to_number(b);
656 return x == y;
657 }
658 if let (Some(sa), Some(sb)) = (self.as_str(a), self.as_str(b)) {
660 return sa == sb;
661 }
662 let na = self.is_null(a);
663 let nb = self.is_null(b);
664 if na || nb {
665 return na && nb;
666 }
667 matches!((a, b), (Value::Obj(x), Value::Obj(y)) if x == y)
669 }
670 }
671 }
672
673 pub fn is_nullish(&self, v: &Value) -> bool {
675 matches!(v, Value::Undef) || self.is_null(v)
676 }
677
678 fn js_type(&self, v: &Value) -> &'static str {
682 match v {
683 Value::Undef => "undefined",
684 Value::Bool(_) => "boolean",
685 Value::Int(_) | Value::Float(_) => "number",
686 Value::Str(_) => "string",
687 Value::Obj(_) => match self.get(v) {
688 Some(JsObj::Str(_)) => "string",
689 Some(JsObj::Null) => "null",
690 _ => "object",
691 },
692 _ => "object",
693 }
694 }
695
696 pub fn loose_eq(&self, a: &Value, b: &Value) -> bool {
701 if self.strict_eq(a, b) {
703 return true;
704 }
705 let ta = self.js_type(a);
706 let tb = self.js_type(b);
707 if self.is_nullish(a) || self.is_nullish(b) {
709 return self.is_nullish(a) && self.is_nullish(b);
710 }
711 if ta == tb {
712 return false;
714 }
715 if (ta == "number" && tb == "string") || (ta == "string" && tb == "number") {
717 return self.to_number(a) == self.to_number(b);
718 }
719 if ta == "boolean" {
721 return self.loose_eq(&Value::Float(self.to_number(a)), b);
722 }
723 if tb == "boolean" {
724 return self.loose_eq(a, &Value::Float(self.to_number(b)));
725 }
726 if ta == "object" && (tb == "number" || tb == "string") {
729 let pa = self.str_of(a);
730 return if tb == "string" {
731 pa == self.str_of(b)
732 } else {
733 str_to_number(&pa) == self.to_number(b)
734 };
735 }
736 if tb == "object" && (ta == "number" || ta == "string") {
737 let pb = self.str_of(b);
738 return if ta == "string" {
739 self.str_of(a) == pb
740 } else {
741 self.to_number(a) == str_to_number(&pb)
742 };
743 }
744 false
745 }
746
747 pub fn arith(&mut self, op: NumOp, a: &Value, b: &Value) -> Result<Value, String> {
750 use NumOp::*;
751 match op {
752 Add => {
753 let a_str = self.prefers_string(a);
756 let b_str = self.prefers_string(b);
757 if a_str || b_str {
758 let s = format!("{}{}", self.str_of(a), self.str_of(b));
759 Ok(self.new_str(s))
760 } else {
761 Ok(Value::Float(self.to_number(a) + self.to_number(b)))
762 }
763 }
764 Sub => Ok(Value::Float(self.to_number(a) - self.to_number(b))),
765 Mul => Ok(Value::Float(self.to_number(a) * self.to_number(b))),
766 Div => Ok(Value::Float(self.to_number(a) / self.to_number(b))),
767 Mod => Ok(Value::Float(js_mod(self.to_number(a), self.to_number(b)))),
768 Pow => Ok(Value::Float(self.to_number(a).powf(self.to_number(b)))),
769 Neg => Ok(Value::Float(-self.to_number(a))),
770 Lt | Le | Gt | Ge => Ok(Value::Bool(self.relational(op, a, b))),
771 Eq => Ok(Value::Bool(self.loose_eq(a, b))),
772 Ne => Ok(Value::Bool(!self.loose_eq(a, b))),
773 }
774 }
775
776 fn prefers_string(&self, v: &Value) -> bool {
782 match v {
783 Value::Str(_) => true,
784 Value::Obj(_) => !matches!(self.get(v), Some(JsObj::Null) | None),
785 _ => false,
786 }
787 }
788
789 fn relational(&self, op: NumOp, a: &Value, b: &Value) -> bool {
792 use std::cmp::Ordering;
793 let ord = if let (Some(x), Some(y)) = (self.as_str(a), self.as_str(b)) {
794 x.cmp(&y)
795 } else {
796 let x = self.to_number(a);
797 let y = self.to_number(b);
798 match x.partial_cmp(&y) {
799 Some(o) => o,
800 None => return false, }
802 };
803 match op {
804 NumOp::Lt => ord == Ordering::Less,
805 NumOp::Le => ord != Ordering::Greater,
806 NumOp::Gt => ord == Ordering::Greater,
807 NumOp::Ge => ord != Ordering::Less,
808 _ => false,
809 }
810 }
811
812 pub fn bitwise(&self, tag: i64, a: &Value, b: &Value) -> Value {
814 let x = to_int32(self.to_number(a));
815 let y = to_int32(self.to_number(b));
816 let r: i64 = match tag {
817 binop::BITAND => (x & y) as i64,
818 binop::BITOR => (x | y) as i64,
819 binop::BITXOR => (x ^ y) as i64,
820 binop::SHL => (x.wrapping_shl((y as u32) & 31)) as i64,
821 binop::SHR => (x >> ((y as u32) & 31)) as i64,
822 binop::USHR => (to_uint32(self.to_number(a)) >> ((y as u32) & 31)) as i64,
823 _ => 0,
824 };
825 Value::Float(r as f64)
826 }
827}
828
829fn js_mod(a: f64, b: f64) -> f64 {
831 a % b
832}
833
834fn to_int32(f: f64) -> i32 {
835 if !f.is_finite() {
836 return 0;
837 }
838 let n = f.trunc();
839 (n as i64 as u32) as i32
840}
841fn to_uint32(f: f64) -> u32 {
842 if !f.is_finite() {
843 return 0;
844 }
845 f.trunc() as i64 as u32
846}
847
848fn str_to_number(s: &str) -> f64 {
850 let t = s.trim();
851 if t.is_empty() {
852 return 0.0;
853 }
854 if let Some(hex) = t.strip_prefix("0x").or_else(|| t.strip_prefix("0X")) {
855 return i64::from_str_radix(hex, 16).map(|n| n as f64).unwrap_or(f64::NAN);
856 }
857 match t {
858 "Infinity" | "+Infinity" => f64::INFINITY,
859 "-Infinity" => f64::NEG_INFINITY,
860 _ => t.parse::<f64>().unwrap_or(f64::NAN),
861 }
862}
863
864fn quote_str(s: &str) -> String {
866 let mut out = String::from("'");
867 for c in s.chars() {
868 match c {
869 '\'' => out.push_str("\\'"),
870 '\\' => out.push_str("\\\\"),
871 '\n' => out.push_str("\\n"),
872 '\t' => out.push_str("\\t"),
873 '\r' => out.push_str("\\r"),
874 _ => out.push(c),
875 }
876 }
877 out.push('\'');
878 out
879}
880
881fn fmt_key(k: &str) -> String {
883 let ok = !k.is_empty()
884 && k.chars().next().map(|c| c.is_ascii_alphabetic() || c == '_' || c == '$').unwrap_or(false)
885 && k.chars().all(|c| c.is_ascii_alphanumeric() || c == '_' || c == '$');
886 if ok {
887 k.to_string()
888 } else {
889 quote_str(k)
890 }
891}
892
893impl JsHost {
896 pub fn iter_vec(&mut self, v: &Value) -> Result<Vec<Value>, String> {
898 match self.get(v) {
899 Some(JsObj::Array(items)) => Ok(items.clone()),
900 Some(JsObj::Str(s)) => {
901 let chars: Vec<String> = s.chars().map(|c| c.to_string()).collect();
902 Ok(chars.into_iter().map(|c| self.new_str(c)).collect())
903 }
904 Some(JsObj::Iter { items, idx }) => Ok(items[*idx..].to_vec()),
905 _ => Err(type_error(&format!(
906 "{} is not iterable",
907 self.type_of(v)
908 ))),
909 }
910 }
911
912 pub fn enum_keys(&mut self, v: &Value) -> Vec<Value> {
914 let keys: Vec<String> = match self.get(v) {
915 Some(JsObj::Object(props)) => props.keys().cloned().collect(),
916 Some(JsObj::Array(items)) => (0..items.len()).map(|i| i.to_string()).collect(),
917 _ => Vec::new(),
918 };
919 keys.into_iter().map(|k| self.new_str(k)).collect()
920 }
921}
922
923pub fn call_named(name: &str, args: Vec<Value>) -> Result<Value, String> {
927 if let Some(v) = with_host(|h| h.read_name(name)) {
928 return invoke(&v, args, None);
929 }
930 if crate::builtins::is_known_builtin(name) {
931 return crate::builtins::call_builtin_function(name, args);
932 }
933 Err(ref_error(name))
934}
935
936pub fn call_method(recv: &Value, name: &str, args: Vec<Value>) -> Result<Value, String> {
938 if let Some(JsObj::Builtin(ns)) = with_host(|h| h.get(recv).cloned()) {
941 let qualified = format!("{ns}.{name}");
942 if crate::builtins::is_known_builtin(&qualified) {
943 return crate::builtins::call_builtin_function(&qualified, args);
944 }
945 }
946 if let Some(JsObj::Object(props)) = with_host(|h| h.get(recv).cloned()) {
948 if let Some(f) = props.get(name).cloned() {
949 return invoke(&f, args, Some(recv.clone()));
950 }
951 }
952 crate::builtins::call_type_method(recv, name, args)
954}
955
956pub fn invoke(callable: &Value, args: Vec<Value>, this: Option<Value>) -> Result<Value, String> {
958 let obj = with_host(|h| h.get(callable).cloned());
959 match obj {
960 Some(JsObj::Builtin(name)) => crate::builtins::call_builtin_function(&name, args),
961 Some(JsObj::Func(fv)) => run_user_func(&fv, args, this),
962 Some(JsObj::BoundMethod { recv, name }) => call_method(&recv, &name, args),
963 _ => Err(type_error(&format!(
964 "{} is not a function",
965 with_host(|h| h.str_of(callable))
966 ))),
967 }
968}
969
970pub fn run_user_func(fv: &FuncVal, args: Vec<Value>, this: Option<Value>) -> Result<Value, String> {
972 let def = with_host(|h| h.funcs[fv.def_id].clone());
973 let env = new_env(fv.env.clone());
974 bind_params(&env, &def, args);
977 let this_val = if fv.is_arrow {
979 fv.this.clone()
980 } else {
981 this
982 };
983 with_host(|h| {
984 h.frames.push(Frame {
985 env,
986 this_obj: this_val,
987 })
988 });
989 let r = run_chunk_on(def.chunk.clone());
990 let sig = with_host(|h| {
991 h.frames.pop();
992 h.signal.take()
993 });
994 match r {
995 Err(e) => Err(e),
996 Ok(_) => Ok(match sig {
997 Some(Signal::Return(v)) => v,
998 _ => Value::Undef,
999 }),
1000 }
1001}
1002
1003fn bind_params(env: &Env, def: &FuncDef, args: Vec<Value>) {
1006 let mut vars: IndexMap<String, Value> = IndexMap::new();
1007 let mut i = 0;
1008 for slot in &def.params {
1009 if slot.rest {
1010 let rest: Vec<Value> = args.get(i..).map(|s| s.to_vec()).unwrap_or_default();
1011 let arr = with_host(|h| h.new_array(rest));
1012 vars.insert(slot.name.clone(), arr);
1013 } else {
1014 let v = args.get(i).cloned().unwrap_or(Value::Undef);
1015 vars.insert(slot.name.clone(), v);
1016 i += 1;
1017 }
1018 }
1019 let args_arr = with_host(|h| h.new_array(args));
1021 vars.entry("arguments".to_string()).or_insert(args_arr);
1022 env.borrow_mut().vars = vars;
1023}
1024
1025pub fn construct(ctor: &Value, args: Vec<Value>) -> Result<Value, String> {
1029 let inst = with_host(|h| h.new_object(IndexMap::new()));
1030 let obj = with_host(|h| h.get(ctor).cloned());
1031 match obj {
1032 Some(JsObj::Func(fv)) => {
1033 let r = run_user_func(&fv, args, Some(inst.clone()))?;
1034 if matches!(
1036 with_host(|h| h.get(&r).cloned()),
1037 Some(JsObj::Object(_)) | Some(JsObj::Array(_))
1038 ) {
1039 Ok(r)
1040 } else {
1041 Ok(inst)
1042 }
1043 }
1044 Some(JsObj::Builtin(name)) => crate::builtins::construct_builtin(&name, args),
1045 _ => Err(type_error("not a constructor")),
1046 }
1047}