use super::ErrorKind;
use crate::{
environment::{
declarative_environment_record::DeclarativeEnvironmentRecordBinding,
environment_record_trait::EnvironmentRecordTrait,
lexical_environment::{Environment, EnvironmentType},
},
gc::{empty_trace, Finalize, Trace},
object::GcObject,
Value,
};
use rustc_hash::FxHashMap;
#[derive(Copy, Finalize, Debug, Clone)]
pub enum BindingStatus {
Lexical,
Initialized,
Uninitialized,
}
unsafe impl Trace for BindingStatus {
empty_trace!();
}
#[derive(Debug, Trace, Finalize, Clone)]
pub struct FunctionEnvironmentRecord {
pub env_rec: FxHashMap<String, DeclarativeEnvironmentRecordBinding>,
pub this_value: Value,
pub this_binding_status: BindingStatus,
pub function: GcObject,
pub home_object: Value,
pub new_target: Value,
pub outer_env: Option<Environment>,
}
impl FunctionEnvironmentRecord {
pub fn bind_this_value(&mut self, value: Value) -> Result<Value, ErrorKind> {
match self.this_binding_status {
BindingStatus::Lexical => {
panic!("Cannot bind to an arrow function!");
}
BindingStatus::Initialized => Err(ErrorKind::new_reference_error(
"Cannot bind to an initialised function!",
)),
BindingStatus::Uninitialized => {
self.this_value = value.clone();
self.this_binding_status = BindingStatus::Initialized;
Ok(value)
}
}
}
pub fn get_super_base(&self) -> Value {
let home = &self.home_object;
if home.is_undefined() {
Value::Undefined
} else {
assert!(home.is_object());
home.as_object()
.expect("home_object must be an Object")
.prototype_instance()
}
}
}
impl EnvironmentRecordTrait for FunctionEnvironmentRecord {
fn has_binding(&self, name: &str) -> bool {
self.env_rec.contains_key(name)
}
fn create_mutable_binding(
&mut self,
name: String,
deletion: bool,
allow_name_reuse: bool,
) -> Result<(), ErrorKind> {
if !allow_name_reuse {
assert!(
!self.env_rec.contains_key(&name),
"Identifier {} has already been declared",
name
);
}
self.env_rec.insert(
name,
DeclarativeEnvironmentRecordBinding {
value: None,
can_delete: deletion,
mutable: true,
strict: false,
},
);
Ok(())
}
fn get_this_binding(&self) -> Result<Value, ErrorKind> {
match self.this_binding_status {
BindingStatus::Lexical => {
panic!("There is no this for a lexical function record");
}
BindingStatus::Uninitialized => Err(ErrorKind::new_reference_error(
"Uninitialised binding for this function",
)),
BindingStatus::Initialized => Ok(self.this_value.clone()),
}
}
fn create_immutable_binding(&mut self, name: String, strict: bool) -> Result<(), ErrorKind> {
assert!(
!self.env_rec.contains_key(&name),
"Identifier {} has already been declared",
name
);
self.env_rec.insert(
name,
DeclarativeEnvironmentRecordBinding {
value: None,
can_delete: true,
mutable: false,
strict,
},
);
Ok(())
}
fn initialize_binding(&mut self, name: &str, value: Value) -> Result<(), ErrorKind> {
if let Some(ref mut record) = self.env_rec.get_mut(name) {
if record.value.is_none() {
record.value = Some(value);
return Ok(());
}
}
panic!("record must have binding for {}", name)
}
#[allow(clippy::else_if_without_else)]
fn set_mutable_binding(
&mut self,
name: &str,
value: Value,
mut strict: bool,
) -> Result<(), ErrorKind> {
if self.env_rec.get(name).is_none() {
if strict {
return Err(ErrorKind::new_reference_error(format!(
"{} not found",
name
)));
}
self.create_mutable_binding(name.to_owned(), true, false)?;
self.initialize_binding(name, value)?;
return Ok(());
}
let record: &mut DeclarativeEnvironmentRecordBinding = self.env_rec.get_mut(name).unwrap();
if record.strict {
strict = true
}
if record.value.is_none() {
return Err(ErrorKind::new_reference_error(format!(
"{} has not been initialized",
name
)));
}
if record.mutable {
record.value = Some(value);
} else if strict {
return Err(ErrorKind::new_type_error(format!(
"Cannot mutate an immutable binding {}",
name
)));
}
Ok(())
}
fn get_binding_value(&self, name: &str, _strict: bool) -> Result<Value, ErrorKind> {
if let Some(binding) = self.env_rec.get(name) {
if let Some(ref val) = binding.value {
Ok(val.clone())
} else {
Err(ErrorKind::new_reference_error(format!(
"{} is an uninitialized binding",
name
)))
}
} else {
panic!("Cannot get binding value for {}", name);
}
}
fn delete_binding(&mut self, name: &str) -> bool {
match self.env_rec.get(name) {
Some(binding) => {
if binding.can_delete {
self.env_rec.remove(name);
true
} else {
false
}
}
None => panic!("env_rec has no binding for {}", name),
}
}
fn has_super_binding(&self) -> bool {
if let BindingStatus::Lexical = self.this_binding_status {
false
} else {
!self.home_object.is_undefined()
}
}
fn has_this_binding(&self) -> bool {
!matches!(self.this_binding_status, BindingStatus::Lexical)
}
fn with_base_object(&self) -> Value {
Value::undefined()
}
fn get_outer_environment(&self) -> Option<Environment> {
match &self.outer_env {
Some(outer) => Some(outer.clone()),
None => None,
}
}
fn set_outer_environment(&mut self, env: Environment) {
self.outer_env = Some(env);
}
fn get_environment_type(&self) -> EnvironmentType {
EnvironmentType::Function
}
fn get_global_object(&self) -> Option<Value> {
match &self.outer_env {
Some(ref outer) => outer.borrow().get_global_object(),
None => None,
}
}
}