use super::{Object, PROTOTYPE};
use crate::{
builtins::function::{
create_unmapped_arguments_object, BuiltInFunction, Function, NativeFunction,
},
environment::{
function_environment_record::BindingStatus, lexical_environment::new_function_environment,
},
syntax::ast::node::RcStatementList,
Context, Executable, Result, Value,
};
use gc::{Finalize, Gc, GcCell, GcCellRef, GcCellRefMut, Trace};
use std::{
cell::RefCell,
collections::HashSet,
error::Error,
fmt::{self, Debug, Display},
result::Result as StdResult,
};
pub type Ref<'object> = GcCellRef<'object, Object>;
pub type RefMut<'object> = GcCellRefMut<'object, Object>;
#[derive(Trace, Finalize, Clone)]
pub struct GcObject(Gc<GcCell<Object>>);
enum FunctionBody {
BuiltIn(NativeFunction),
Ordinary(RcStatementList),
}
impl GcObject {
#[inline]
pub fn new(object: Object) -> Self {
Self(Gc::new(GcCell::new(object)))
}
#[inline]
#[track_caller]
pub fn borrow(&self) -> Ref<'_> {
self.try_borrow().expect("Object already mutably borrowed")
}
#[inline]
#[track_caller]
pub fn borrow_mut(&self) -> RefMut<'_> {
self.try_borrow_mut().expect("Object already borrowed")
}
#[inline]
pub fn try_borrow(&self) -> StdResult<Ref<'_>, BorrowError> {
self.0.try_borrow().map_err(|_| BorrowError)
}
#[inline]
pub fn try_borrow_mut(&self) -> StdResult<RefMut<'_>, BorrowMutError> {
self.0.try_borrow_mut().map_err(|_| BorrowMutError)
}
#[inline]
pub fn equals(lhs: &Self, rhs: &Self) -> bool {
std::ptr::eq(lhs.as_ref(), rhs.as_ref())
}
#[track_caller]
pub fn call(&self, this: &Value, args: &[Value], ctx: &mut Context) -> Result<Value> {
let this_function_object = self.clone();
let f_body = if let Some(function) = self.borrow().as_function() {
if function.is_callable() {
match function {
Function::BuiltIn(BuiltInFunction(function), _) => {
FunctionBody::BuiltIn(*function)
}
Function::Ordinary {
body,
params,
environment,
flags,
} => {
let local_env = new_function_environment(
this_function_object,
if flags.is_lexical_this_mode() {
None
} else {
Some(this.clone())
},
Some(environment.clone()),
if flags.is_lexical_this_mode() {
BindingStatus::Lexical
} else {
BindingStatus::Uninitialized
},
);
for (i, param) in params.iter().enumerate() {
if param.is_rest_param() {
function.add_rest_param(param, i, args, ctx, &local_env);
break;
}
let value = args.get(i).cloned().unwrap_or_else(Value::undefined);
function.add_arguments_to_environment(param, value, &local_env);
}
let arguments_obj = create_unmapped_arguments_object(args);
local_env
.borrow_mut()
.create_mutable_binding("arguments".to_string(), false);
local_env
.borrow_mut()
.initialize_binding("arguments", arguments_obj);
ctx.realm_mut().environment.push(local_env);
FunctionBody::Ordinary(body.clone())
}
}
} else {
return ctx.throw_type_error("function object is not callable");
}
} else {
return ctx.throw_type_error("not a function");
};
match f_body {
FunctionBody::BuiltIn(func) => func(this, args, ctx),
FunctionBody::Ordinary(body) => {
let result = body.run(ctx);
ctx.realm_mut().environment.pop();
result
}
}
}
#[track_caller]
pub fn construct(&self, args: &[Value], ctx: &mut Context) -> Result<Value> {
let this: Value = Object::create(self.borrow().get(&PROTOTYPE.into())).into();
let this_function_object = self.clone();
let body = if let Some(function) = self.borrow().as_function() {
if function.is_constructable() {
match function {
Function::BuiltIn(BuiltInFunction(function), _) => {
FunctionBody::BuiltIn(*function)
}
Function::Ordinary {
body,
params,
environment,
flags,
} => {
let local_env = new_function_environment(
this_function_object,
Some(this.clone()),
Some(environment.clone()),
if flags.is_lexical_this_mode() {
BindingStatus::Lexical
} else {
BindingStatus::Uninitialized
},
);
for (i, param) in params.iter().enumerate() {
if param.is_rest_param() {
function.add_rest_param(param, i, args, ctx, &local_env);
break;
}
let value = args.get(i).cloned().unwrap_or_else(Value::undefined);
function.add_arguments_to_environment(param, value, &local_env);
}
let arguments_obj = create_unmapped_arguments_object(args);
local_env
.borrow_mut()
.create_mutable_binding("arguments".to_string(), false);
local_env
.borrow_mut()
.initialize_binding("arguments", arguments_obj);
ctx.realm_mut().environment.push(local_env);
FunctionBody::Ordinary(body.clone())
}
}
} else {
let name = this.get_field("name").display().to_string();
return ctx.throw_type_error(format!("{} is not a constructor", name));
}
} else {
return ctx.throw_type_error("not a function");
};
match body {
FunctionBody::BuiltIn(function) => {
function(&this, args, ctx)?;
Ok(this)
}
FunctionBody::Ordinary(body) => {
let _ = body.run(ctx);
let binding = ctx.realm_mut().environment.get_this_binding();
Ok(binding)
}
}
}
}
impl AsRef<GcCell<Object>> for GcObject {
#[inline]
fn as_ref(&self) -> &GcCell<Object> {
&*self.0
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct BorrowError;
impl Display for BorrowError {
#[inline]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
Display::fmt("Object already mutably borrowed", f)
}
}
impl Error for BorrowError {}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct BorrowMutError;
impl Display for BorrowMutError {
#[inline]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
Display::fmt("Object already borrowed", f)
}
}
impl Error for BorrowMutError {}
#[derive(Debug)]
struct RecursionLimiter {
free: bool,
first: bool,
}
impl Clone for RecursionLimiter {
fn clone(&self) -> Self {
Self {
free: false,
first: false,
}
}
}
impl Drop for RecursionLimiter {
fn drop(&mut self) {
if self.free {
Self::VISITED.with(|hs| hs.borrow_mut().clear());
}
}
}
impl RecursionLimiter {
thread_local! {
static VISITED: RefCell<HashSet<usize>> = RefCell::new(HashSet::new());
}
fn new(o: &GcObject) -> Self {
let ptr = (o.as_ref() as *const _) as usize;
let (free, first) = Self::VISITED.with(|hs| {
let mut hs = hs.borrow_mut();
(hs.is_empty(), hs.insert(ptr))
});
Self { free, first }
}
}
impl Debug for GcObject {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> std::fmt::Result {
let limiter = RecursionLimiter::new(&self);
if limiter.first {
f.debug_tuple("GcObject").field(&self.0).finish()
} else {
f.write_str("{ ... }")
}
}
}