use std::{cell::RefCell, rc::Rc};
use crate::environments::runtime::BindingLocator;
use boa_ast::expression::Identifier;
use boa_gc::{empty_trace, Finalize, Trace};
use rustc_hash::FxHashMap;
use super::runtime::BindingLocatorError;
#[derive(Debug)]
struct CompileTimeBinding {
index: u32,
mutable: bool,
lex: bool,
strict: bool,
}
#[derive(Debug, Finalize)]
pub(crate) struct CompileTimeEnvironment {
outer: Option<Rc<RefCell<Self>>>,
environment_index: u32,
bindings: FxHashMap<Identifier, CompileTimeBinding>,
function_scope: bool,
}
unsafe impl Trace for CompileTimeEnvironment {
empty_trace!();
}
impl CompileTimeEnvironment {
pub(crate) fn new_global() -> Self {
Self {
outer: None,
environment_index: 0,
bindings: FxHashMap::default(),
function_scope: true,
}
}
pub(crate) fn new(parent: Rc<RefCell<Self>>, function_scope: bool) -> Self {
let index = parent.borrow().environment_index + 1;
Self {
outer: Some(parent),
environment_index: index,
bindings: FxHashMap::default(),
function_scope,
}
}
pub(crate) fn has_lex_binding(&self, name: Identifier) -> bool {
self.bindings
.get(&name)
.map_or(false, |binding| binding.lex)
}
#[cfg(feature = "annex-b")]
pub(crate) fn has_binding(&self, name: Identifier) -> bool {
self.bindings.contains_key(&name)
}
pub(crate) fn is_lex_binding(&self, name: Identifier) -> bool {
if let Some(binding) = self.bindings.get(&name) {
binding.lex
} else if let Some(outer) = &self.outer {
outer.borrow().is_lex_binding(name)
} else {
false
}
}
pub(crate) fn num_bindings(&self) -> u32 {
self.bindings.len() as u32
}
pub(crate) const fn is_function(&self) -> bool {
self.function_scope
}
pub(crate) fn get_binding(&self, name: Identifier) -> Option<BindingLocator> {
self.bindings
.get(&name)
.map(|binding| BindingLocator::declarative(name, self.environment_index, binding.index))
}
pub(crate) fn get_binding_recursive(&self, name: Identifier) -> BindingLocator {
if let Some(binding) = self.bindings.get(&name) {
BindingLocator::declarative(name, self.environment_index, binding.index)
} else if let Some(outer) = &self.outer {
outer.borrow().get_binding_recursive(name)
} else {
BindingLocator::global(name)
}
}
pub(crate) fn has_binding_recursive(&self, name: Identifier) -> bool {
if self.bindings.contains_key(&name) {
true
} else if let Some(outer) = &self.outer {
outer.borrow().has_binding_recursive(name)
} else {
false
}
}
pub(crate) fn has_binding_eval(&self, name: Identifier, strict: bool) -> bool {
let exists = self.bindings.contains_key(&name);
if exists || strict {
return exists;
}
if self.function_scope {
return false;
}
if let Some(outer) = &self.outer {
outer.borrow().has_binding_eval(name, false)
} else {
false
}
}
#[cfg(feature = "annex-b")]
pub(crate) fn has_binding_until_var(&self, name: Identifier) -> bool {
if self.function_scope {
return false;
}
if self.bindings.contains_key(&name) {
return true;
}
if let Some(outer) = &self.outer {
outer.borrow().has_binding_until_var(name)
} else {
false
}
}
pub(crate) fn create_mutable_binding(
&mut self,
name: Identifier,
function_scope: bool,
) -> bool {
if let Some(outer) = &self.outer {
if !function_scope || self.function_scope {
if !self.bindings.contains_key(&name) {
let binding_index = self.bindings.len() as u32;
self.bindings.insert(
name,
CompileTimeBinding {
index: binding_index,
mutable: true,
lex: !function_scope,
strict: false,
},
);
}
true
} else {
return outer
.borrow_mut()
.create_mutable_binding(name, function_scope);
}
} else if function_scope {
false
} else {
if !self.bindings.contains_key(&name) {
let binding_index = self.bindings.len() as u32;
self.bindings.insert(
name,
CompileTimeBinding {
index: binding_index,
mutable: true,
lex: !function_scope,
strict: false,
},
);
}
true
}
}
pub(crate) fn create_immutable_binding(&mut self, name: Identifier, strict: bool) {
let binding_index = self.bindings.len() as u32;
self.bindings.insert(
name,
CompileTimeBinding {
index: binding_index,
mutable: false,
lex: true,
strict,
},
);
}
pub(crate) fn initialize_mutable_binding(
&self,
name: Identifier,
function_scope: bool,
) -> BindingLocator {
if let Some(outer) = &self.outer {
if function_scope && !self.function_scope {
return outer
.borrow()
.initialize_mutable_binding(name, function_scope);
}
self.bindings.get(&name).map_or_else(
|| {
outer
.borrow()
.initialize_mutable_binding(name, function_scope)
},
|binding| BindingLocator::declarative(name, self.environment_index, binding.index),
)
} else if let Some(binding) = self.bindings.get(&name) {
BindingLocator::declarative(name, self.environment_index, binding.index)
} else {
BindingLocator::global(name)
}
}
pub(crate) fn initialize_immutable_binding(&self, name: Identifier) -> BindingLocator {
let binding = self.bindings.get(&name).expect("binding must exist");
BindingLocator::declarative(name, self.environment_index, binding.index)
}
pub(crate) fn set_mutable_binding_recursive(
&self,
name: Identifier,
) -> Result<BindingLocator, BindingLocatorError> {
Ok(match self.bindings.get(&name) {
Some(binding) if binding.mutable => {
BindingLocator::declarative(name, self.environment_index, binding.index)
}
Some(binding) if binding.strict => return Err(BindingLocatorError::MutateImmutable),
Some(_) => return Err(BindingLocatorError::Silent),
None => self.outer.as_ref().map_or_else(
|| Ok(BindingLocator::global(name)),
|outer| outer.borrow().set_mutable_binding_recursive(name),
)?,
})
}
#[cfg(feature = "annex-b")]
pub(crate) fn set_mutable_binding_var_recursive(
&self,
name: Identifier,
) -> Result<BindingLocator, BindingLocatorError> {
if !self.is_function() {
return self.outer.as_ref().map_or_else(
|| Ok(BindingLocator::global(name)),
|outer| outer.borrow().set_mutable_binding_var_recursive(name),
);
}
Ok(match self.bindings.get(&name) {
Some(binding) if binding.mutable => {
BindingLocator::declarative(name, self.environment_index, binding.index)
}
Some(binding) if binding.strict => return Err(BindingLocatorError::MutateImmutable),
Some(_) => return Err(BindingLocatorError::Silent),
None => self.outer.as_ref().map_or_else(
|| Ok(BindingLocator::global(name)),
|outer| outer.borrow().set_mutable_binding_var_recursive(name),
)?,
})
}
pub(crate) fn outer(&self) -> Option<Rc<RefCell<Self>>> {
self.outer.clone()
}
pub(crate) const fn environment_index(&self) -> u32 {
self.environment_index
}
}