use crate::{environments::CompileTimeEnvironment, object::JsObject, Context, JsResult, JsValue};
use boa_gc::{Cell, Finalize, Gc, Trace};
use boa_interner::Sym;
use rustc_hash::FxHashSet;
#[derive(Debug, Trace, Finalize)]
pub(crate) struct DeclarativeEnvironment {
bindings: Cell<Vec<Option<JsValue>>>,
compile: Gc<Cell<CompileTimeEnvironment>>,
poisoned: Cell<bool>,
slots: Option<EnvironmentSlots>,
}
#[derive(Clone, Debug, Trace, Finalize)]
pub(crate) enum EnvironmentSlots {
Function(Cell<FunctionSlots>),
Global,
}
impl EnvironmentSlots {
pub(crate) fn as_function_slots(&self) -> Option<&Cell<FunctionSlots>> {
if let Self::Function(env) = &self {
Some(env)
} else {
None
}
}
}
#[derive(Clone, Debug, Trace, Finalize)]
pub(crate) struct FunctionSlots {
this: JsValue,
#[unsafe_ignore_trace]
this_binding_status: ThisBindingStatus,
function_object: JsObject,
new_target: Option<JsObject>,
}
impl FunctionSlots {
pub(crate) fn function_object(&self) -> &JsObject {
&self.function_object
}
pub(crate) fn new_target(&self) -> Option<&JsObject> {
self.new_target.as_ref()
}
pub(crate) fn bind_this_value(&mut self, this: &JsObject) -> bool {
debug_assert!(self.this_binding_status != ThisBindingStatus::Lexical);
if self.this_binding_status == ThisBindingStatus::Initialized {
return false;
}
self.this = this.clone().into();
self.this_binding_status = ThisBindingStatus::Initialized;
true
}
pub(crate) fn has_this_binding(&self) -> bool {
self.this_binding_status != ThisBindingStatus::Lexical
}
pub(crate) fn has_super_binding(&self) -> bool {
if self.this_binding_status == ThisBindingStatus::Lexical {
return false;
}
self.function_object
.borrow()
.as_function()
.expect("function object must be function")
.get_home_object()
.is_some()
}
pub(crate) fn get_this_binding(&self) -> Option<&JsValue> {
debug_assert!(self.this_binding_status != ThisBindingStatus::Lexical);
if self.this_binding_status == ThisBindingStatus::Uninitialized {
return None;
}
Some(&self.this)
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
enum ThisBindingStatus {
Lexical,
Initialized,
Uninitialized,
}
impl DeclarativeEnvironment {
pub(crate) fn slots(&self) -> Option<&EnvironmentSlots> {
self.slots.as_ref()
}
#[inline]
pub(crate) fn get(&self, index: usize) -> JsValue {
self.bindings
.borrow()
.get(index)
.expect("binding index must be in range")
.clone()
.expect("binding must be initialized")
}
#[inline]
pub(crate) fn set(&self, index: usize, value: JsValue) {
let mut bindings = self.bindings.borrow_mut();
let binding = bindings
.get_mut(index)
.expect("binding index must be in range");
assert!(!binding.is_none(), "binding must be initialized");
*binding = Some(value);
}
}
#[derive(Clone, Debug, Trace, Finalize)]
pub struct DeclarativeEnvironmentStack {
stack: Vec<Gc<DeclarativeEnvironment>>,
}
impl DeclarativeEnvironmentStack {
#[inline]
pub(crate) fn new(global_compile_environment: Gc<Cell<CompileTimeEnvironment>>) -> Self {
Self {
stack: vec![Gc::new(DeclarativeEnvironment {
bindings: Cell::new(Vec::new()),
compile: global_compile_environment,
poisoned: Cell::new(false),
slots: Some(EnvironmentSlots::Global),
})],
}
}
pub(crate) fn extend_outer_function_environment(&mut self) {
for env in self.stack.iter().rev() {
if let Some(EnvironmentSlots::Function(_)) = env.slots {
let compile_bindings_number = env.compile.borrow().num_bindings();
let mut bindings_mut = env.bindings.borrow_mut();
if compile_bindings_number > bindings_mut.len() {
let diff = compile_bindings_number - bindings_mut.len();
bindings_mut.extend(vec![None; diff]);
}
break;
}
}
}
pub(crate) fn has_lex_binding_until_function_environment(
&self,
names: &FxHashSet<Sym>,
) -> Option<Sym> {
for env in self.stack.iter().rev() {
let compile = env.compile.borrow();
for name in names {
if compile.has_lex_binding(*name) {
return Some(*name);
}
}
if compile.is_function() {
break;
}
}
None
}
pub(crate) fn pop_to_global(&mut self) -> Vec<Gc<DeclarativeEnvironment>> {
self.stack.split_off(1)
}
pub(crate) fn len(&self) -> usize {
self.stack.len()
}
pub(crate) fn truncate(&mut self, len: usize) {
self.stack.truncate(len);
}
pub(crate) fn extend(&mut self, other: Vec<Gc<DeclarativeEnvironment>>) {
self.stack.extend(other);
}
#[inline]
pub(crate) fn set_global_binding_number(&mut self, binding_number: usize) {
let environment = self
.stack
.get(0)
.expect("global environment must always exist");
let mut bindings = environment.bindings.borrow_mut();
if bindings.len() < binding_number {
bindings.resize(binding_number, None);
}
}
#[inline]
pub(crate) fn get_this_environment(&self) -> &EnvironmentSlots {
for env in self.stack.iter().rev() {
if let Some(slots) = &env.slots {
match slots {
EnvironmentSlots::Function(function_env) => {
if function_env.borrow().has_this_binding() {
return slots;
}
}
EnvironmentSlots::Global => return slots,
}
}
}
panic!("global environment must exist")
}
#[inline]
pub(crate) fn push_declarative(
&mut self,
num_bindings: usize,
compile_environment: Gc<Cell<CompileTimeEnvironment>>,
) {
let poisoned = self
.stack
.last()
.expect("global environment must always exist")
.poisoned
.borrow()
.to_owned();
self.stack.push(Gc::new(DeclarativeEnvironment {
bindings: Cell::new(vec![None; num_bindings]),
compile: compile_environment,
poisoned: Cell::new(poisoned),
slots: None,
}));
}
#[inline]
pub(crate) fn push_function(
&mut self,
num_bindings: usize,
compile_environment: Gc<Cell<CompileTimeEnvironment>>,
this: Option<JsValue>,
function_object: JsObject,
new_target: Option<JsObject>,
lexical: bool,
) {
let outer = self
.stack
.last()
.expect("global environment must always exist");
let poisoned = outer.poisoned.borrow().to_owned();
let this_binding_status = if lexical {
ThisBindingStatus::Lexical
} else if this.is_some() {
ThisBindingStatus::Initialized
} else {
ThisBindingStatus::Uninitialized
};
let this = if let Some(this) = this {
this
} else {
JsValue::Null
};
self.stack.push(Gc::new(DeclarativeEnvironment {
bindings: Cell::new(vec![None; num_bindings]),
compile: compile_environment,
poisoned: Cell::new(poisoned),
slots: Some(EnvironmentSlots::Function(Cell::new(FunctionSlots {
this,
this_binding_status,
function_object,
new_target,
}))),
}));
}
pub(crate) fn push_function_inherit(
&mut self,
num_bindings: usize,
compile_environment: Gc<Cell<CompileTimeEnvironment>>,
) {
let outer = self
.stack
.last()
.expect("global environment must always exist");
let poisoned = outer.poisoned.borrow().to_owned();
let slots = outer.slots.clone();
self.stack.push(Gc::new(DeclarativeEnvironment {
bindings: Cell::new(vec![None; num_bindings]),
compile: compile_environment,
poisoned: Cell::new(poisoned),
slots,
}));
}
#[inline]
pub(crate) fn pop(&mut self) -> Gc<DeclarativeEnvironment> {
debug_assert!(self.stack.len() > 1);
self.stack
.pop()
.expect("environment stack is cannot be empty")
}
#[inline]
pub(crate) fn current(&mut self) -> Gc<DeclarativeEnvironment> {
self.stack
.last()
.expect("global environment must always exist")
.clone()
}
pub(crate) fn current_compile_environment(&self) -> Gc<Cell<CompileTimeEnvironment>> {
self.stack
.last()
.expect("global environment must always exist")
.compile
.clone()
}
#[inline]
pub(crate) fn poison_current(&mut self) {
let mut poisoned = self
.stack
.last()
.expect("global environment must always exist")
.poisoned
.borrow_mut();
*poisoned = true;
}
#[inline]
pub(crate) fn poison_all(&mut self) {
for env in &mut self.stack {
let mut poisoned = env.poisoned.borrow_mut();
if *poisoned {
return;
}
*poisoned = true;
}
}
#[inline]
pub(crate) fn get_value_optional(
&self,
mut environment_index: usize,
mut binding_index: usize,
name: Sym,
) -> Option<JsValue> {
if environment_index != self.stack.len() - 1 {
for env_index in (environment_index + 1..self.stack.len()).rev() {
let env = self
.stack
.get(env_index)
.expect("environment index must be in range");
if !*env.poisoned.borrow() {
break;
}
let compile = env.compile.borrow();
if compile.is_function() {
if let Some(b) = compile.get_binding(name) {
environment_index = b.environment_index;
binding_index = b.binding_index;
break;
}
}
}
}
self.stack
.get(environment_index)
.expect("environment index must be in range")
.bindings
.borrow()
.get(binding_index)
.expect("binding index must be in range")
.clone()
}
#[inline]
pub(crate) fn get_value_global_poisoned(&self, name: Sym) -> Option<JsValue> {
for env in self.stack.iter().rev() {
if !*env.poisoned.borrow() {
return None;
}
let compile = env.compile.borrow();
if compile.is_function() {
if let Some(b) = compile.get_binding(name) {
return self
.stack
.get(b.environment_index)
.expect("environment index must be in range")
.bindings
.borrow()
.get(b.binding_index)
.expect("binding index must be in range")
.clone();
}
}
}
None
}
#[inline]
pub(crate) fn put_value(
&mut self,
environment_index: usize,
binding_index: usize,
value: JsValue,
) {
let mut bindings = self
.stack
.get(environment_index)
.expect("environment index must be in range")
.bindings
.borrow_mut();
let binding = bindings
.get_mut(binding_index)
.expect("binding index must be in range");
*binding = Some(value);
}
#[inline]
pub(crate) fn put_value_if_initialized(
&mut self,
mut environment_index: usize,
mut binding_index: usize,
name: Sym,
value: JsValue,
) -> bool {
if environment_index != self.stack.len() - 1 {
for env_index in (environment_index + 1..self.stack.len()).rev() {
let env = self
.stack
.get(env_index)
.expect("environment index must be in range");
if !*env.poisoned.borrow() {
break;
}
let compile = env.compile.borrow();
if compile.is_function() {
if let Some(b) = compile.get_binding(name) {
environment_index = b.environment_index;
binding_index = b.binding_index;
break;
}
}
}
}
let mut bindings = self
.stack
.get(environment_index)
.expect("environment index must be in range")
.bindings
.borrow_mut();
let binding = bindings
.get_mut(binding_index)
.expect("binding index must be in range");
if binding.is_none() {
false
} else {
*binding = Some(value);
true
}
}
#[inline]
pub(crate) fn put_value_if_uninitialized(
&mut self,
environment_index: usize,
binding_index: usize,
value: JsValue,
) {
let mut bindings = self
.stack
.get(environment_index)
.expect("environment index must be in range")
.bindings
.borrow_mut();
let binding = bindings
.get_mut(binding_index)
.expect("binding index must be in range");
if binding.is_none() {
*binding = Some(value);
}
}
#[inline]
pub(crate) fn put_value_global_poisoned(&mut self, name: Sym, value: &JsValue) -> bool {
for env in self.stack.iter().rev() {
if !*env.poisoned.borrow() {
return false;
}
let compile = env.compile.borrow();
if compile.is_function() {
if let Some(b) = compile.get_binding(name) {
let mut bindings = self
.stack
.get(b.environment_index)
.expect("environment index must be in range")
.bindings
.borrow_mut();
let binding = bindings
.get_mut(b.binding_index)
.expect("binding index must be in range");
*binding = Some(value.clone());
return true;
}
}
}
false
}
}
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub(crate) struct BindingLocator {
name: Sym,
environment_index: usize,
binding_index: usize,
global: bool,
mutate_immutable: bool,
}
impl BindingLocator {
#[inline]
pub(in crate::environments) fn declarative(
name: Sym,
environment_index: usize,
binding_index: usize,
) -> Self {
Self {
name,
environment_index,
binding_index,
global: false,
mutate_immutable: false,
}
}
#[inline]
pub(in crate::environments) fn global(name: Sym) -> Self {
Self {
name,
environment_index: 0,
binding_index: 0,
global: true,
mutate_immutable: false,
}
}
#[inline]
pub(in crate::environments) fn mutate_immutable(name: Sym) -> Self {
Self {
name,
environment_index: 0,
binding_index: 0,
global: false,
mutate_immutable: true,
}
}
#[inline]
pub(crate) fn name(&self) -> Sym {
self.name
}
#[inline]
pub(crate) fn is_global(&self) -> bool {
self.global
}
#[inline]
pub(crate) fn environment_index(&self) -> usize {
self.environment_index
}
#[inline]
pub(crate) fn binding_index(&self) -> usize {
self.binding_index
}
#[inline]
pub(crate) fn throw_mutate_immutable(&self, context: &mut Context) -> JsResult<()> {
if self.mutate_immutable {
context.throw_type_error(format!(
"cannot mutate an immutable binding '{}'",
context.interner().resolve_expect(self.name)
))
} else {
Ok(())
}
}
}