use fusevm::{Chunk, NumOp, VMResult, Value, VM};
use indexmap::IndexMap;
use std::cell::RefCell;
use std::rc::Rc;
pub mod ops {
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; }
pub mod binop {
pub const BITAND: i64 = 0;
pub const BITOR: i64 = 1;
pub const BITXOR: i64 = 2;
pub const SHL: i64 = 3;
pub const SHR: i64 = 4;
pub const USHR: i64 = 5;
}
pub mod unop {
pub const POS: i64 = 0; pub const BITNOT: i64 = 1; }
#[derive(Clone)]
pub struct FuncDef {
pub name: String,
pub params: Vec<ParamSlot>,
pub chunk: Chunk,
pub is_arrow: bool,
}
#[derive(Clone)]
pub struct ParamSlot {
pub name: String,
pub rest: bool,
pub has_default: bool,
}
#[derive(Clone)]
pub struct TryDef {
pub block: Chunk,
pub handler: Option<(Option<String>, Chunk)>,
pub finalizer: Option<Chunk>,
}
#[derive(Clone)]
pub struct FuncVal {
pub def_id: usize,
pub env: Option<Env>,
pub this: Option<Value>,
pub is_arrow: bool,
}
#[derive(Clone)]
pub enum JsObj {
Str(String),
Array(Vec<Value>),
Object(IndexMap<String, Value>),
Func(FuncVal),
Builtin(String),
BoundMethod { recv: Value, name: String },
Null,
Iter { items: Vec<Value>, idx: usize },
}
pub struct EnvData {
pub vars: IndexMap<String, Value>,
pub parent: Option<Env>,
}
pub type Env = Rc<RefCell<EnvData>>;
fn new_env(parent: Option<Env>) -> Env {
Rc::new(RefCell::new(EnvData {
vars: IndexMap::new(),
parent,
}))
}
pub struct Frame {
pub env: Env,
pub this_obj: Option<Value>,
}
#[derive(Clone)]
pub enum Signal {
Return(Value),
Break,
Continue,
}
pub struct JsHost {
heap: Vec<JsObj>,
pub funcs: Vec<FuncDef>,
pub tries: Vec<TryDef>,
globals: IndexMap<String, Value>,
frames: Vec<Frame>,
pub error: Option<String>,
pub exc: Option<Value>,
pub signal: Option<Signal>,
null_val: Value,
}
thread_local! {
static HOST: RefCell<JsHost> = RefCell::new(JsHost::new());
}
pub fn with_host<R>(f: impl FnOnce(&mut JsHost) -> R) -> R {
HOST.with(|h| f(&mut h.borrow_mut()))
}
pub fn reset_host() {
with_host(|h| *h = JsHost::new());
}
impl Default for JsHost {
fn default() -> Self {
Self::new()
}
}
impl JsHost {
pub fn new() -> JsHost {
let module_env = new_env(None);
let mut h = JsHost {
heap: Vec::new(),
funcs: Vec::new(),
tries: Vec::new(),
globals: IndexMap::new(),
frames: vec![Frame {
env: module_env,
this_obj: None,
}],
error: None,
exc: None,
signal: None,
null_val: Value::Undef,
};
h.null_val = h.alloc(JsObj::Null);
h
}
pub fn null(&self) -> Value {
self.null_val.clone()
}
pub fn is_null(&self, v: &Value) -> bool {
matches!(self.get(v), Some(JsObj::Null))
}
pub fn program_offsets(&self) -> (usize, usize) {
(self.funcs.len(), self.tries.len())
}
pub fn load_program(&mut self, funcs: Vec<FuncDef>, tries: Vec<TryDef>) {
self.funcs.extend(funcs);
self.tries.extend(tries);
}
pub fn try_def(&self, id: usize) -> Option<TryDef> {
self.tries.get(id).cloned()
}
pub fn alloc(&mut self, obj: JsObj) -> Value {
self.heap.push(obj);
Value::Obj((self.heap.len() - 1) as u32)
}
pub fn get(&self, v: &Value) -> Option<&JsObj> {
if let Value::Obj(i) = v {
self.heap.get(*i as usize)
} else {
None
}
}
pub fn get_mut(&mut self, v: &Value) -> Option<&mut JsObj> {
if let Value::Obj(i) = v {
self.heap.get_mut(*i as usize)
} else {
None
}
}
pub fn new_str(&mut self, s: impl Into<String>) -> Value {
self.alloc(JsObj::Str(s.into()))
}
pub fn new_array(&mut self, items: Vec<Value>) -> Value {
self.alloc(JsObj::Array(items))
}
pub fn new_object(&mut self, props: IndexMap<String, Value>) -> Value {
self.alloc(JsObj::Object(props))
}
pub fn as_str(&self, v: &Value) -> Option<String> {
match v {
Value::Str(s) => Some((**s).clone()),
Value::Obj(_) => match self.get(v) {
Some(JsObj::Str(s)) => Some(s.clone()),
_ => None,
},
_ => None,
}
}
fn frame(&self) -> &Frame {
self.frames.last().unwrap()
}
fn cur_env(&self) -> Env {
self.frame().env.clone()
}
pub fn read_name(&self, name: &str) -> Option<Value> {
let mut env = Some(self.cur_env());
while let Some(e) = env {
if let Some(v) = e.borrow().vars.get(name) {
return Some(v.clone());
}
env = e.borrow().parent.clone();
}
self.globals.get(name).cloned()
}
pub fn read_global(&self, name: &str) -> Option<Value> {
self.globals.get(name).cloned()
}
pub fn set_name(&mut self, name: &str, val: Value) {
let mut env = Some(self.cur_env());
while let Some(e) = env {
if e.borrow().vars.contains_key(name) {
e.borrow_mut().vars.insert(name.to_string(), val);
return;
}
env = e.borrow().parent.clone();
}
self.globals.insert(name.to_string(), val);
}
pub fn declare_name(&mut self, name: &str, val: Value) {
if self.frames.len() == 1 {
self.globals.insert(name.to_string(), val);
} else {
self.cur_env().borrow_mut().vars.insert(name.to_string(), val);
}
}
pub fn set_global(&mut self, name: &str, val: Value) {
self.globals.insert(name.to_string(), val);
}
pub fn del_name(&mut self, name: &str) {
if self.cur_env().borrow_mut().vars.shift_remove(name).is_some() {
return;
}
self.globals.shift_remove(name);
}
pub fn current_this(&self) -> Option<Value> {
self.frame().this_obj.clone()
}
pub fn current_env_capture(&self) -> Env {
self.frame().env.clone()
}
pub fn take_error(&mut self) -> Option<String> {
self.error.take()
}
pub fn raise_str(&mut self, class: &str, msg: &str) -> String {
let s = if msg.is_empty() {
class.to_string()
} else {
format!("{class}: {msg}")
};
self.error = Some(s.clone());
s
}
}
pub fn type_error(msg: &str) -> String {
format!("TypeError: {msg}")
}
pub fn ref_error(name: &str) -> String {
format!("ReferenceError: {name} is not defined")
}
pub fn run_chunk_on(chunk: Chunk) -> Result<Value, String> {
let mut vm = VM::new(chunk);
crate::builtins::install(&mut vm);
vm.set_numeric_hook(std::sync::Arc::new(|op, a, b| {
crate::builtins::numeric_hook(op, a, b)
}));
vm.enable_tracing_jit();
let outcome = vm.run();
if let Some(e) = with_host(|h| h.take_error()) {
return Err(e);
}
match outcome {
VMResult::Ok(v) => Ok(v),
VMResult::Halted => Ok(vm.stack.last().cloned().unwrap_or(Value::Undef)),
VMResult::Error(e) => Err(e),
}
}
pub fn run_main(chunk: Chunk) -> Result<Value, String> {
let r = run_chunk_on(chunk);
with_host(|h| h.signal = None);
r
}
pub fn fmt_number(f: f64) -> String {
if f.is_nan() {
return "NaN".into();
}
if f.is_infinite() {
return if f > 0.0 { "Infinity" } else { "-Infinity" }.into();
}
if f == 0.0 {
return "0".into();
}
if f < 0.0 {
return format!("-{}", js_number_repr(-f));
}
js_number_repr(f)
}
fn js_number_repr(a: f64) -> String {
let sci = format!("{a:e}");
let (mant, exp_str) = sci.split_once('e').expect("LowerExp always has 'e'");
let e: i32 = exp_str.parse().expect("LowerExp exponent is an integer");
let s: String = mant.chars().filter(|c| *c != '.').collect();
let k = s.len() as i32; let n = e + 1;
if k <= n && n <= 21 {
let mut out = s;
out.push_str(&"0".repeat((n - k) as usize));
out
} else if 0 < n && n <= 21 {
format!("{}.{}", &s[..n as usize], &s[n as usize..])
} else if -6 < n && n <= 0 {
format!("0.{}{}", "0".repeat((-n) as usize), s)
} else {
let exp = n - 1;
let sign = if exp >= 0 { '+' } else { '-' };
let mag = exp.abs();
if k == 1 {
format!("{s}e{sign}{mag}")
} else {
format!("{}.{}e{sign}{mag}", &s[..1], &s[1..])
}
}
}
impl JsHost {
pub fn type_of(&self, v: &Value) -> &'static str {
match v {
Value::Undef => "undefined",
Value::Bool(_) => "boolean",
Value::Int(_) | Value::Float(_) => "number",
Value::Str(_) => "string",
Value::Obj(_) => match self.get(v) {
Some(JsObj::Str(_)) => "string",
Some(JsObj::Func(_)) | Some(JsObj::Builtin(_)) | Some(JsObj::BoundMethod { .. }) => {
"function"
}
_ => "object", },
_ => "object",
}
}
pub fn truthy(&self, v: &Value) -> bool {
match v {
Value::Undef => false,
Value::Bool(b) => *b,
Value::Int(n) => *n != 0,
Value::Float(f) => *f != 0.0 && !f.is_nan(),
Value::Str(s) => !s.is_empty(),
Value::Obj(_) => match self.get(v) {
Some(JsObj::Str(s)) => !s.is_empty(),
Some(JsObj::Null) => false,
_ => true, },
_ => true,
}
}
pub fn to_number(&self, v: &Value) -> f64 {
match v {
Value::Undef => f64::NAN,
Value::Bool(b) => {
if *b {
1.0
} else {
0.0
}
}
Value::Int(n) => *n as f64,
Value::Float(f) => *f,
Value::Str(s) => str_to_number(s),
Value::Obj(_) => match self.get(v) {
Some(JsObj::Str(s)) => str_to_number(s),
Some(JsObj::Null) => 0.0,
Some(JsObj::Array(items)) => {
if items.is_empty() {
0.0
} else if items.len() == 1 {
self.to_number(&items[0])
} else {
f64::NAN
}
}
_ => f64::NAN,
},
_ => f64::NAN,
}
}
pub fn str_of(&self, v: &Value) -> String {
match v {
Value::Undef => "undefined".into(),
Value::Bool(b) => if *b { "true" } else { "false" }.into(),
Value::Int(n) => n.to_string(),
Value::Float(f) => fmt_number(*f),
Value::Str(s) => (**s).clone(),
Value::Obj(_) => match self.get(v) {
Some(JsObj::Str(s)) => s.clone(),
Some(JsObj::Null) => "null".into(),
Some(JsObj::Array(items)) => {
let parts: Vec<String> = items
.iter()
.map(|x| match x {
Value::Undef => String::new(),
_ if self.is_null(x) => String::new(),
_ => self.str_of(x),
})
.collect();
parts.join(",")
}
Some(JsObj::Object(_)) => "[object Object]".into(),
Some(JsObj::Func(f)) => {
let name = self.funcs.get(f.def_id).map(|d| d.name.clone()).unwrap_or_default();
format!("function {name}() {{ [code] }}")
}
Some(JsObj::Builtin(n)) => format!("function {n}() {{ [native code] }}"),
Some(JsObj::BoundMethod { .. }) => "function () { [native code] }".into(),
_ => "[object Object]".into(),
},
_ => "[object Object]".into(),
}
}
pub fn console_format(&self, v: &Value) -> String {
match v {
Value::Str(_) => self.str_of(v),
Value::Obj(_) if matches!(self.get(v), Some(JsObj::Str(_))) => self.str_of(v),
_ => self.inspect(v),
}
}
pub fn inspect(&self, v: &Value) -> String {
match v {
Value::Undef => "undefined".into(),
Value::Bool(b) => if *b { "true" } else { "false" }.into(),
Value::Int(n) => n.to_string(),
Value::Float(f) if *f == 0.0 && f.is_sign_negative() => "-0".into(),
Value::Float(f) => fmt_number(*f),
Value::Str(s) => quote_str(s),
Value::Obj(_) => match self.get(v) {
Some(JsObj::Str(s)) => quote_str(s),
Some(JsObj::Null) => "null".into(),
Some(JsObj::Array(items)) => {
if items.is_empty() {
return "[]".into();
}
let inner: Vec<String> = items.iter().map(|x| self.inspect(x)).collect();
format!("[ {} ]", inner.join(", "))
}
Some(JsObj::Object(props)) => {
if props.is_empty() {
return "{}".into();
}
let inner: Vec<String> = props
.iter()
.map(|(k, val)| format!("{}: {}", fmt_key(k), self.inspect(val)))
.collect();
format!("{{ {} }}", inner.join(", "))
}
Some(JsObj::Func(f)) => {
let name = self.funcs.get(f.def_id).map(|d| d.name.clone()).unwrap_or_default();
if name.is_empty() {
"[Function (anonymous)]".into()
} else {
format!("[Function: {name}]")
}
}
Some(JsObj::Builtin(n)) => {
let short = n.rsplit('.').next().unwrap_or(n);
format!("[Function: {short}]")
}
Some(JsObj::BoundMethod { .. }) => "[Function (anonymous)]".into(),
_ => "undefined".into(),
},
_ => "undefined".into(),
}
}
pub fn strict_eq(&self, a: &Value, b: &Value) -> bool {
match (a, b) {
(Value::Undef, Value::Undef) => true,
(Value::Bool(x), Value::Bool(y)) => x == y,
(Value::Str(x), Value::Str(y)) => x == y,
_ => {
let an = matches!(a, Value::Int(_) | Value::Float(_));
let bn = matches!(b, Value::Int(_) | Value::Float(_));
if an && bn {
let x = self.to_number(a);
let y = self.to_number(b);
return x == y;
}
if let (Some(sa), Some(sb)) = (self.as_str(a), self.as_str(b)) {
return sa == sb;
}
let na = self.is_null(a);
let nb = self.is_null(b);
if na || nb {
return na && nb;
}
matches!((a, b), (Value::Obj(x), Value::Obj(y)) if x == y)
}
}
}
pub fn is_nullish(&self, v: &Value) -> bool {
matches!(v, Value::Undef) || self.is_null(v)
}
fn js_type(&self, v: &Value) -> &'static str {
match v {
Value::Undef => "undefined",
Value::Bool(_) => "boolean",
Value::Int(_) | Value::Float(_) => "number",
Value::Str(_) => "string",
Value::Obj(_) => match self.get(v) {
Some(JsObj::Str(_)) => "string",
Some(JsObj::Null) => "null",
_ => "object",
},
_ => "object",
}
}
pub fn loose_eq(&self, a: &Value, b: &Value) -> bool {
if self.strict_eq(a, b) {
return true;
}
let ta = self.js_type(a);
let tb = self.js_type(b);
if self.is_nullish(a) || self.is_nullish(b) {
return self.is_nullish(a) && self.is_nullish(b);
}
if ta == tb {
return false;
}
if (ta == "number" && tb == "string") || (ta == "string" && tb == "number") {
return self.to_number(a) == self.to_number(b);
}
if ta == "boolean" {
return self.loose_eq(&Value::Float(self.to_number(a)), b);
}
if tb == "boolean" {
return self.loose_eq(a, &Value::Float(self.to_number(b)));
}
if ta == "object" && (tb == "number" || tb == "string") {
let pa = self.str_of(a);
return if tb == "string" {
pa == self.str_of(b)
} else {
str_to_number(&pa) == self.to_number(b)
};
}
if tb == "object" && (ta == "number" || ta == "string") {
let pb = self.str_of(b);
return if ta == "string" {
self.str_of(a) == pb
} else {
self.to_number(a) == str_to_number(&pb)
};
}
false
}
pub fn arith(&mut self, op: NumOp, a: &Value, b: &Value) -> Result<Value, String> {
use NumOp::*;
match op {
Add => {
let a_str = self.prefers_string(a);
let b_str = self.prefers_string(b);
if a_str || b_str {
let s = format!("{}{}", self.str_of(a), self.str_of(b));
Ok(self.new_str(s))
} else {
Ok(Value::Float(self.to_number(a) + self.to_number(b)))
}
}
Sub => Ok(Value::Float(self.to_number(a) - self.to_number(b))),
Mul => Ok(Value::Float(self.to_number(a) * self.to_number(b))),
Div => Ok(Value::Float(self.to_number(a) / self.to_number(b))),
Mod => Ok(Value::Float(js_mod(self.to_number(a), self.to_number(b)))),
Pow => Ok(Value::Float(self.to_number(a).powf(self.to_number(b)))),
Neg => Ok(Value::Float(-self.to_number(a))),
Lt | Le | Gt | Ge => Ok(Value::Bool(self.relational(op, a, b))),
Eq => Ok(Value::Bool(self.loose_eq(a, b))),
Ne => Ok(Value::Bool(!self.loose_eq(a, b))),
}
}
fn prefers_string(&self, v: &Value) -> bool {
match v {
Value::Str(_) => true,
Value::Obj(_) => !matches!(self.get(v), Some(JsObj::Null) | None),
_ => false,
}
}
fn relational(&self, op: NumOp, a: &Value, b: &Value) -> bool {
use std::cmp::Ordering;
let ord = if let (Some(x), Some(y)) = (self.as_str(a), self.as_str(b)) {
x.cmp(&y)
} else {
let x = self.to_number(a);
let y = self.to_number(b);
match x.partial_cmp(&y) {
Some(o) => o,
None => return false, }
};
match op {
NumOp::Lt => ord == Ordering::Less,
NumOp::Le => ord != Ordering::Greater,
NumOp::Gt => ord == Ordering::Greater,
NumOp::Ge => ord != Ordering::Less,
_ => false,
}
}
pub fn bitwise(&self, tag: i64, a: &Value, b: &Value) -> Value {
let x = to_int32(self.to_number(a));
let y = to_int32(self.to_number(b));
let r: i64 = match tag {
binop::BITAND => (x & y) as i64,
binop::BITOR => (x | y) as i64,
binop::BITXOR => (x ^ y) as i64,
binop::SHL => (x.wrapping_shl((y as u32) & 31)) as i64,
binop::SHR => (x >> ((y as u32) & 31)) as i64,
binop::USHR => (to_uint32(self.to_number(a)) >> ((y as u32) & 31)) as i64,
_ => 0,
};
Value::Float(r as f64)
}
}
fn js_mod(a: f64, b: f64) -> f64 {
a % b
}
fn to_int32(f: f64) -> i32 {
if !f.is_finite() {
return 0;
}
let n = f.trunc();
(n as i64 as u32) as i32
}
fn to_uint32(f: f64) -> u32 {
if !f.is_finite() {
return 0;
}
f.trunc() as i64 as u32
}
fn str_to_number(s: &str) -> f64 {
let t = s.trim();
if t.is_empty() {
return 0.0;
}
if let Some(hex) = t.strip_prefix("0x").or_else(|| t.strip_prefix("0X")) {
return i64::from_str_radix(hex, 16).map(|n| n as f64).unwrap_or(f64::NAN);
}
match t {
"Infinity" | "+Infinity" => f64::INFINITY,
"-Infinity" => f64::NEG_INFINITY,
_ => t.parse::<f64>().unwrap_or(f64::NAN),
}
}
fn quote_str(s: &str) -> String {
let mut out = String::from("'");
for c in s.chars() {
match c {
'\'' => out.push_str("\\'"),
'\\' => out.push_str("\\\\"),
'\n' => out.push_str("\\n"),
'\t' => out.push_str("\\t"),
'\r' => out.push_str("\\r"),
_ => out.push(c),
}
}
out.push('\'');
out
}
fn fmt_key(k: &str) -> String {
let ok = !k.is_empty()
&& k.chars().next().map(|c| c.is_ascii_alphabetic() || c == '_' || c == '$').unwrap_or(false)
&& k.chars().all(|c| c.is_ascii_alphanumeric() || c == '_' || c == '$');
if ok {
k.to_string()
} else {
quote_str(k)
}
}
impl JsHost {
pub fn iter_vec(&mut self, v: &Value) -> Result<Vec<Value>, String> {
match self.get(v) {
Some(JsObj::Array(items)) => Ok(items.clone()),
Some(JsObj::Str(s)) => {
let chars: Vec<String> = s.chars().map(|c| c.to_string()).collect();
Ok(chars.into_iter().map(|c| self.new_str(c)).collect())
}
Some(JsObj::Iter { items, idx }) => Ok(items[*idx..].to_vec()),
_ => Err(type_error(&format!(
"{} is not iterable",
self.type_of(v)
))),
}
}
pub fn enum_keys(&mut self, v: &Value) -> Vec<Value> {
let keys: Vec<String> = match self.get(v) {
Some(JsObj::Object(props)) => props.keys().cloned().collect(),
Some(JsObj::Array(items)) => (0..items.len()).map(|i| i.to_string()).collect(),
_ => Vec::new(),
};
keys.into_iter().map(|k| self.new_str(k)).collect()
}
}
pub fn call_named(name: &str, args: Vec<Value>) -> Result<Value, String> {
if let Some(v) = with_host(|h| h.read_name(name)) {
return invoke(&v, args, None);
}
if crate::builtins::is_known_builtin(name) {
return crate::builtins::call_builtin_function(name, args);
}
Err(ref_error(name))
}
pub fn call_method(recv: &Value, name: &str, args: Vec<Value>) -> Result<Value, String> {
if let Some(JsObj::Builtin(ns)) = with_host(|h| h.get(recv).cloned()) {
let qualified = format!("{ns}.{name}");
if crate::builtins::is_known_builtin(&qualified) {
return crate::builtins::call_builtin_function(&qualified, args);
}
}
if let Some(JsObj::Object(props)) = with_host(|h| h.get(recv).cloned()) {
if let Some(f) = props.get(name).cloned() {
return invoke(&f, args, Some(recv.clone()));
}
}
crate::builtins::call_type_method(recv, name, args)
}
pub fn invoke(callable: &Value, args: Vec<Value>, this: Option<Value>) -> Result<Value, String> {
let obj = with_host(|h| h.get(callable).cloned());
match obj {
Some(JsObj::Builtin(name)) => crate::builtins::call_builtin_function(&name, args),
Some(JsObj::Func(fv)) => run_user_func(&fv, args, this),
Some(JsObj::BoundMethod { recv, name }) => call_method(&recv, &name, args),
_ => Err(type_error(&format!(
"{} is not a function",
with_host(|h| h.str_of(callable))
))),
}
}
pub fn run_user_func(fv: &FuncVal, args: Vec<Value>, this: Option<Value>) -> Result<Value, String> {
let def = with_host(|h| h.funcs[fv.def_id].clone());
let env = new_env(fv.env.clone());
bind_params(&env, &def, args);
let this_val = if fv.is_arrow {
fv.this.clone()
} else {
this
};
with_host(|h| {
h.frames.push(Frame {
env,
this_obj: this_val,
})
});
let r = run_chunk_on(def.chunk.clone());
let sig = with_host(|h| {
h.frames.pop();
h.signal.take()
});
match r {
Err(e) => Err(e),
Ok(_) => Ok(match sig {
Some(Signal::Return(v)) => v,
_ => Value::Undef,
}),
}
}
fn bind_params(env: &Env, def: &FuncDef, args: Vec<Value>) {
let mut vars: IndexMap<String, Value> = IndexMap::new();
let mut i = 0;
for slot in &def.params {
if slot.rest {
let rest: Vec<Value> = args.get(i..).map(|s| s.to_vec()).unwrap_or_default();
let arr = with_host(|h| h.new_array(rest));
vars.insert(slot.name.clone(), arr);
} else {
let v = args.get(i).cloned().unwrap_or(Value::Undef);
vars.insert(slot.name.clone(), v);
i += 1;
}
}
let args_arr = with_host(|h| h.new_array(args));
vars.entry("arguments".to_string()).or_insert(args_arr);
env.borrow_mut().vars = vars;
}
pub fn construct(ctor: &Value, args: Vec<Value>) -> Result<Value, String> {
let inst = with_host(|h| h.new_object(IndexMap::new()));
let obj = with_host(|h| h.get(ctor).cloned());
match obj {
Some(JsObj::Func(fv)) => {
let r = run_user_func(&fv, args, Some(inst.clone()))?;
if matches!(
with_host(|h| h.get(&r).cloned()),
Some(JsObj::Object(_)) | Some(JsObj::Array(_))
) {
Ok(r)
} else {
Ok(inst)
}
}
Some(JsObj::Builtin(name)) => crate::builtins::construct_builtin(&name, args),
_ => Err(type_error("not a constructor")),
}
}