feat: works
This commit is contained in:
@@ -0,0 +1,369 @@
|
||||
use crate::{
|
||||
environments::runtime::BindingLocator, property::PropertyDescriptor, Context, JsString, JsValue,
|
||||
};
|
||||
use boa_gc::{Cell, Finalize, Gc, Trace};
|
||||
use boa_interner::Sym;
|
||||
use rustc_hash::FxHashMap;
|
||||
|
||||
/// A compile time binding represents a binding at bytecode compile time in a [`CompileTimeEnvironment`].
|
||||
///
|
||||
/// It contains the binding index and a flag to indicate if this is a mutable binding or not.
|
||||
#[derive(Debug)]
|
||||
struct CompileTimeBinding {
|
||||
index: usize,
|
||||
mutable: bool,
|
||||
lex: bool,
|
||||
}
|
||||
|
||||
/// A compile time environment maps bound identifiers to their binding positions.
|
||||
///
|
||||
/// A compile time environment also indicates, if it is a function environment.
|
||||
#[derive(Debug, Finalize, Trace)]
|
||||
pub(crate) struct CompileTimeEnvironment {
|
||||
outer: Option<Gc<Cell<Self>>>,
|
||||
environment_index: usize,
|
||||
#[unsafe_ignore_trace]
|
||||
bindings: FxHashMap<Sym, CompileTimeBinding>,
|
||||
function_scope: bool,
|
||||
}
|
||||
|
||||
impl CompileTimeEnvironment {
|
||||
/// Crate a new global compile time environment.
|
||||
#[inline]
|
||||
pub(crate) fn new_global() -> Self {
|
||||
Self {
|
||||
outer: None,
|
||||
environment_index: 0,
|
||||
bindings: FxHashMap::default(),
|
||||
function_scope: true,
|
||||
}
|
||||
}
|
||||
|
||||
/// Check if environment has a lexical binding with the given name.
|
||||
#[inline]
|
||||
pub(crate) fn has_lex_binding(&self, name: Sym) -> bool {
|
||||
self.bindings
|
||||
.get(&name)
|
||||
.map_or(false, |binding| binding.lex)
|
||||
}
|
||||
|
||||
/// Returns the number of bindings in this environment.
|
||||
#[inline]
|
||||
pub(crate) fn num_bindings(&self) -> usize {
|
||||
self.bindings.len()
|
||||
}
|
||||
|
||||
/// Check if the environment is a function environment.
|
||||
#[inline]
|
||||
pub(crate) fn is_function(&self) -> bool {
|
||||
self.function_scope
|
||||
}
|
||||
|
||||
/// Get the locator for a binding name.
|
||||
#[inline]
|
||||
pub(crate) fn get_binding(&self, name: Sym) -> Option<BindingLocator> {
|
||||
self.bindings
|
||||
.get(&name)
|
||||
.map(|binding| BindingLocator::declarative(name, self.environment_index, binding.index))
|
||||
}
|
||||
|
||||
/// Get the locator for a binding name in this and all outer environments.
|
||||
#[inline]
|
||||
pub(crate) fn get_binding_recursive(&self, name: Sym) -> 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)
|
||||
}
|
||||
}
|
||||
|
||||
/// Check if a binding name exists in this and all outer environments.
|
||||
#[inline]
|
||||
pub(crate) fn has_binding_recursive(&self, name: Sym) -> bool {
|
||||
if self.bindings.contains_key(&name) {
|
||||
true
|
||||
} else if let Some(outer) = &self.outer {
|
||||
outer.borrow().has_binding_recursive(name)
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a mutable binding.
|
||||
///
|
||||
/// If the binding is a function scope binding and this is a declarative environment, try the outer environment.
|
||||
#[inline]
|
||||
pub(crate) fn create_mutable_binding(&mut self, name: Sym, 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();
|
||||
self.bindings.insert(
|
||||
name,
|
||||
CompileTimeBinding {
|
||||
index: binding_index,
|
||||
mutable: true,
|
||||
lex: !function_scope,
|
||||
},
|
||||
);
|
||||
}
|
||||
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();
|
||||
self.bindings.insert(
|
||||
name,
|
||||
CompileTimeBinding {
|
||||
index: binding_index,
|
||||
mutable: true,
|
||||
lex: !function_scope,
|
||||
},
|
||||
);
|
||||
}
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
/// Crate an immutable binding.
|
||||
#[inline]
|
||||
pub(crate) fn create_immutable_binding(&mut self, name: Sym) {
|
||||
let binding_index = self.bindings.len();
|
||||
self.bindings.insert(
|
||||
name,
|
||||
CompileTimeBinding {
|
||||
index: binding_index,
|
||||
mutable: false,
|
||||
lex: true,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/// Return the binding locator for a mutable binding with the given binding name and scope.
|
||||
#[inline]
|
||||
pub(crate) fn initialize_mutable_binding(
|
||||
&self,
|
||||
name: Sym,
|
||||
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);
|
||||
}
|
||||
if let Some(binding) = self.bindings.get(&name) {
|
||||
BindingLocator::declarative(name, self.environment_index, binding.index)
|
||||
} else {
|
||||
outer
|
||||
.borrow()
|
||||
.initialize_mutable_binding(name, function_scope)
|
||||
}
|
||||
} else if let Some(binding) = self.bindings.get(&name) {
|
||||
BindingLocator::declarative(name, self.environment_index, binding.index)
|
||||
} else {
|
||||
BindingLocator::global(name)
|
||||
}
|
||||
}
|
||||
|
||||
/// Return the binding locator for an immutable binding.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics if the binding is not in the current environment.
|
||||
#[inline]
|
||||
pub(crate) fn initialize_immutable_binding(&self, name: Sym) -> BindingLocator {
|
||||
let binding = self.bindings.get(&name).expect("binding must exist");
|
||||
BindingLocator::declarative(name, self.environment_index, binding.index)
|
||||
}
|
||||
|
||||
/// Return the binding locator for a mutable binding.
|
||||
#[inline]
|
||||
pub(crate) fn set_mutable_binding_recursive(&self, name: Sym) -> BindingLocator {
|
||||
match self.bindings.get(&name) {
|
||||
Some(binding) if binding.mutable => {
|
||||
BindingLocator::declarative(name, self.environment_index, binding.index)
|
||||
}
|
||||
Some(_) => BindingLocator::mutate_immutable(name),
|
||||
None => {
|
||||
if let Some(outer) = &self.outer {
|
||||
outer.borrow().set_mutable_binding_recursive(name)
|
||||
} else {
|
||||
BindingLocator::global(name)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Context {
|
||||
/// Push either a new declarative or function environment on the compile time environment stack.
|
||||
///
|
||||
/// Note: This function only works at bytecode compile time!
|
||||
#[inline]
|
||||
pub(crate) fn push_compile_time_environment(&mut self, function_scope: bool) {
|
||||
let environment_index = self.realm.compile_env.borrow().environment_index + 1;
|
||||
let outer = self.realm.compile_env.clone();
|
||||
|
||||
self.realm.compile_env = Gc::new(Cell::new(CompileTimeEnvironment {
|
||||
outer: Some(outer),
|
||||
environment_index,
|
||||
bindings: FxHashMap::default(),
|
||||
function_scope,
|
||||
}));
|
||||
}
|
||||
|
||||
/// Pop the last compile time environment from the stack.
|
||||
///
|
||||
/// Note: This function only works at bytecode compile time!
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics if there are no more environments that can be pop'ed.
|
||||
#[inline]
|
||||
pub(crate) fn pop_compile_time_environment(
|
||||
&mut self,
|
||||
) -> (usize, Gc<Cell<CompileTimeEnvironment>>) {
|
||||
let current_env_borrow = self.realm.compile_env.borrow();
|
||||
if let Some(outer) = ¤t_env_borrow.outer {
|
||||
let outer_clone = outer.clone();
|
||||
let num_bindings = current_env_borrow.num_bindings();
|
||||
drop(current_env_borrow);
|
||||
let current = self.realm.compile_env.clone();
|
||||
self.realm.compile_env = outer_clone;
|
||||
(num_bindings, current)
|
||||
} else {
|
||||
panic!("cannot pop global environment")
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the number of bindings for the current compile time environment.
|
||||
///
|
||||
/// Note: This function only works at bytecode compile time!
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics if there are no environments on the compile time environment stack.
|
||||
#[inline]
|
||||
pub(crate) fn get_binding_number(&self) -> usize {
|
||||
self.realm.compile_env.borrow().num_bindings()
|
||||
}
|
||||
|
||||
/// Get the binding locator of the binding at bytecode compile time.
|
||||
///
|
||||
/// Note: This function only works at bytecode compile time!
|
||||
#[inline]
|
||||
pub(crate) fn get_binding_value(&self, name: Sym) -> BindingLocator {
|
||||
self.realm.compile_env.borrow().get_binding_recursive(name)
|
||||
}
|
||||
|
||||
/// Return if a declarative binding exists at bytecode compile time.
|
||||
/// This does not include bindings on the global object.
|
||||
///
|
||||
/// Note: This function only works at bytecode compile time!
|
||||
#[inline]
|
||||
pub(crate) fn has_binding(&self, name: Sym) -> bool {
|
||||
self.realm.compile_env.borrow().has_binding_recursive(name)
|
||||
}
|
||||
|
||||
/// Create a mutable binding at bytecode compile time.
|
||||
/// This function returns a syntax error, if the binding is a redeclaration.
|
||||
///
|
||||
/// Note: This function only works at bytecode compile time!
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics if the global environment is not function scoped.
|
||||
#[inline]
|
||||
pub(crate) fn create_mutable_binding(&mut self, name: Sym, function_scope: bool) {
|
||||
if !self
|
||||
.realm
|
||||
.compile_env
|
||||
.borrow_mut()
|
||||
.create_mutable_binding(name, function_scope)
|
||||
{
|
||||
let name_str = JsString::from(self.interner().resolve_expect(name));
|
||||
let desc = self
|
||||
.realm
|
||||
.global_property_map
|
||||
.string_property_map()
|
||||
.get(&name_str);
|
||||
if desc.is_none() {
|
||||
self.global_bindings_mut().insert(
|
||||
name_str,
|
||||
PropertyDescriptor::builder()
|
||||
.value(JsValue::Undefined)
|
||||
.writable(true)
|
||||
.enumerable(true)
|
||||
.configurable(true)
|
||||
.build(),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Initialize a mutable binding at bytecode compile time and return it's binding locator.
|
||||
///
|
||||
/// Note: This function only works at bytecode compile time!
|
||||
#[inline]
|
||||
pub(crate) fn initialize_mutable_binding(
|
||||
&self,
|
||||
name: Sym,
|
||||
function_scope: bool,
|
||||
) -> BindingLocator {
|
||||
self.realm
|
||||
.compile_env
|
||||
.borrow()
|
||||
.initialize_mutable_binding(name, function_scope)
|
||||
}
|
||||
|
||||
/// Create an immutable binding at bytecode compile time.
|
||||
/// This function returns a syntax error, if the binding is a redeclaration.
|
||||
///
|
||||
/// Note: This function only works at bytecode compile time!
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics if the global environment does not exist.
|
||||
#[inline]
|
||||
pub(crate) fn create_immutable_binding(&mut self, name: Sym) {
|
||||
self.realm
|
||||
.compile_env
|
||||
.borrow_mut()
|
||||
.create_immutable_binding(name);
|
||||
}
|
||||
|
||||
/// Initialize an immutable binding at bytecode compile time and return it's binding locator.
|
||||
///
|
||||
/// Note: This function only works at bytecode compile time!
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics if the global environment does not exist or a the binding was not created on the current environment.
|
||||
#[inline]
|
||||
pub(crate) fn initialize_immutable_binding(&self, name: Sym) -> BindingLocator {
|
||||
self.realm
|
||||
.compile_env
|
||||
.borrow()
|
||||
.initialize_immutable_binding(name)
|
||||
}
|
||||
|
||||
/// Return the binding locator for a set operation on an existing binding.
|
||||
///
|
||||
/// Note: This function only works at bytecode compile time!
|
||||
#[inline]
|
||||
pub(crate) fn set_mutable_binding(&self, name: Sym) -> BindingLocator {
|
||||
self.realm
|
||||
.compile_env
|
||||
.borrow()
|
||||
.set_mutable_binding_recursive(name)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
//! This module implements ECMAScript `Environment Records`.
|
||||
//!
|
||||
//! Environments contain the bindings of identifiers to their values.
|
||||
//! The implementation differs from the methods defined by the specification,
|
||||
//! but the resulting behavior should be the same.
|
||||
//!
|
||||
//! To make the runtime more performant, environment specific behavior is split
|
||||
//! between bytecode compilation and the runtime.
|
||||
//! While the association of identifiers to values seems like a natural fit for a hashmap,
|
||||
//! lookups of the values at runtime are very expensive.
|
||||
//! Environments can also have outer environments.
|
||||
//! In the worst case, there are as many hashmap lookups, as there are environments.
|
||||
//!
|
||||
//! To avoid these costs, hashmaps are not used at runtime.
|
||||
//! At runtime, environments are represented as fixed size lists of binding values.
|
||||
//! The positions of the bindings in these lists is determined at bytecode compile time.
|
||||
//!
|
||||
//! A binding is uniquely identified by two indices:
|
||||
//! - An environment index, that identifies the environment in which the binding exists
|
||||
//! - A binding index, that identifies the binding in the environment
|
||||
//!
|
||||
//! More information:
|
||||
//! - [ECMAScript reference][spec]
|
||||
//!
|
||||
//! [spec]: https://tc39.es/ecma262/#sec-environment-records
|
||||
|
||||
mod compile;
|
||||
mod runtime;
|
||||
|
||||
pub(crate) use {
|
||||
compile::CompileTimeEnvironment,
|
||||
runtime::{
|
||||
BindingLocator, DeclarativeEnvironment, DeclarativeEnvironmentStack, EnvironmentSlots,
|
||||
},
|
||||
};
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
@@ -0,0 +1,806 @@
|
||||
use crate::{environments::CompileTimeEnvironment, object::JsObject, Context, JsResult, JsValue};
|
||||
use boa_gc::{Cell, Finalize, Gc, Trace};
|
||||
use boa_interner::Sym;
|
||||
use rustc_hash::FxHashSet;
|
||||
|
||||
/// A declarative environment holds binding values at runtime.
|
||||
///
|
||||
/// Bindings are stored in a fixed size list of optional values.
|
||||
/// If a binding is not initialized, the value is `None`.
|
||||
///
|
||||
/// Optionally, an environment can hold a `this` value.
|
||||
/// The `this` value is present only if the environment is a function environment.
|
||||
///
|
||||
/// Code evaluation at runtime (e.g. the `eval` built-in function) can add
|
||||
/// bindings to existing, compiled function environments.
|
||||
/// This makes it impossible to determine the location of all bindings at compile time.
|
||||
/// To dynamically check for added bindings at runtime, a reference to the
|
||||
/// corresponding compile time environment is needed.
|
||||
///
|
||||
/// Checking all environments for potential added bindings at runtime on every get/set
|
||||
/// would offset the performance improvement of determining binding locations at compile time.
|
||||
/// To minimize this, each environment holds a `poisoned` flag.
|
||||
/// If bindings where added at runtime, the current environment and all inner environments
|
||||
/// are marked as poisoned.
|
||||
/// All poisoned environments have to be checked for added bindings.
|
||||
#[derive(Debug, Trace, Finalize)]
|
||||
pub(crate) struct DeclarativeEnvironment {
|
||||
bindings: Cell<Vec<Option<JsValue>>>,
|
||||
compile: Gc<Cell<CompileTimeEnvironment>>,
|
||||
poisoned: Cell<bool>,
|
||||
slots: Option<EnvironmentSlots>,
|
||||
}
|
||||
|
||||
/// Describes the different types of internal slot data that an environment can hold.
|
||||
#[derive(Clone, Debug, Trace, Finalize)]
|
||||
pub(crate) enum EnvironmentSlots {
|
||||
Function(Cell<FunctionSlots>),
|
||||
Global,
|
||||
}
|
||||
|
||||
impl EnvironmentSlots {
|
||||
/// Return the slots if they are part of a function environment.
|
||||
pub(crate) fn as_function_slots(&self) -> Option<&Cell<FunctionSlots>> {
|
||||
if let Self::Function(env) = &self {
|
||||
Some(env)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Holds the internal slots of a function environment.
|
||||
#[derive(Clone, Debug, Trace, Finalize)]
|
||||
pub(crate) struct FunctionSlots {
|
||||
/// The `[[ThisValue]]` internal slot.
|
||||
this: JsValue,
|
||||
|
||||
/// The `[[ThisBindingStatus]]` internal slot.
|
||||
#[unsafe_ignore_trace]
|
||||
this_binding_status: ThisBindingStatus,
|
||||
|
||||
/// The `[[FunctionObject]]` internal slot.
|
||||
function_object: JsObject,
|
||||
|
||||
/// The `[[NewTarget]]` internal slot.
|
||||
new_target: Option<JsObject>,
|
||||
}
|
||||
|
||||
impl FunctionSlots {
|
||||
/// Returns the value of the `[[FunctionObject]]` internal slot.
|
||||
pub(crate) fn function_object(&self) -> &JsObject {
|
||||
&self.function_object
|
||||
}
|
||||
|
||||
/// Returns the value of the `[[NewTarget]]` internal slot.
|
||||
pub(crate) fn new_target(&self) -> Option<&JsObject> {
|
||||
self.new_target.as_ref()
|
||||
}
|
||||
|
||||
/// `BindThisValue`
|
||||
///
|
||||
/// Sets the given value as the `this` binding of the environment.
|
||||
/// Returns `false` if the `this` binding has already been initialized.
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript specification][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-bindthisvalue
|
||||
pub(crate) fn bind_this_value(&mut self, this: &JsObject) -> bool {
|
||||
// 1. Assert: envRec.[[ThisBindingStatus]] is not lexical.
|
||||
debug_assert!(self.this_binding_status != ThisBindingStatus::Lexical);
|
||||
|
||||
// 2. If envRec.[[ThisBindingStatus]] is initialized, throw a ReferenceError exception.
|
||||
if self.this_binding_status == ThisBindingStatus::Initialized {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 3. Set envRec.[[ThisValue]] to V.
|
||||
self.this = this.clone().into();
|
||||
|
||||
// 4. Set envRec.[[ThisBindingStatus]] to initialized.
|
||||
self.this_binding_status = ThisBindingStatus::Initialized;
|
||||
|
||||
// 5. Return V.
|
||||
true
|
||||
}
|
||||
|
||||
/// `HasThisBinding`
|
||||
///
|
||||
/// Returns if the environment has a `this` binding.
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript specification][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-function-environment-records-hasthisbinding
|
||||
pub(crate) fn has_this_binding(&self) -> bool {
|
||||
// 1. If envRec.[[ThisBindingStatus]] is lexical, return false; otherwise, return true.
|
||||
self.this_binding_status != ThisBindingStatus::Lexical
|
||||
}
|
||||
|
||||
/// `HasSuperBinding`
|
||||
///
|
||||
/// Returns if the environment has a `super` binding.
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript specification][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-function-environment-records-hassuperbinding
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics if the function object of the environment is not a function.
|
||||
pub(crate) fn has_super_binding(&self) -> bool {
|
||||
// 1.If envRec.[[ThisBindingStatus]] is lexical, return false.
|
||||
if self.this_binding_status == ThisBindingStatus::Lexical {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 2. If envRec.[[FunctionObject]].[[HomeObject]] is undefined, return false; otherwise, return true.
|
||||
self.function_object
|
||||
.borrow()
|
||||
.as_function()
|
||||
.expect("function object must be function")
|
||||
.get_home_object()
|
||||
.is_some()
|
||||
}
|
||||
|
||||
/// `GetThisBinding`
|
||||
///
|
||||
/// Returns the `this` binding on the function environment.
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript specification][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-function-environment-records-getthisbinding
|
||||
pub(crate) fn get_this_binding(&self) -> Option<&JsValue> {
|
||||
// 1. Assert: envRec.[[ThisBindingStatus]] is not lexical.
|
||||
debug_assert!(self.this_binding_status != ThisBindingStatus::Lexical);
|
||||
|
||||
// 2. If envRec.[[ThisBindingStatus]] is uninitialized, throw a ReferenceError exception.
|
||||
if self.this_binding_status == ThisBindingStatus::Uninitialized {
|
||||
return None;
|
||||
}
|
||||
|
||||
// 3. Return envRec.[[ThisValue]].
|
||||
Some(&self.this)
|
||||
}
|
||||
}
|
||||
|
||||
/// Describes the status of a `this` binding in function environments.
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
enum ThisBindingStatus {
|
||||
Lexical,
|
||||
Initialized,
|
||||
Uninitialized,
|
||||
}
|
||||
|
||||
impl DeclarativeEnvironment {
|
||||
/// Returns the internal slot data of the current environment.
|
||||
pub(crate) fn slots(&self) -> Option<&EnvironmentSlots> {
|
||||
self.slots.as_ref()
|
||||
}
|
||||
|
||||
/// Get the binding value from the environment by it's index.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics if the binding value is out of range or not initialized.
|
||||
#[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")
|
||||
}
|
||||
|
||||
/// Set the binding value at the specified index.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics if the binding value is out of range or not 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);
|
||||
}
|
||||
}
|
||||
|
||||
/// A declarative environment stack holds all declarative environments at runtime.
|
||||
///
|
||||
/// Environments themselves are garbage collected,
|
||||
/// because they must be preserved for function calls.
|
||||
#[derive(Clone, Debug, Trace, Finalize)]
|
||||
pub struct DeclarativeEnvironmentStack {
|
||||
stack: Vec<Gc<DeclarativeEnvironment>>,
|
||||
}
|
||||
|
||||
impl DeclarativeEnvironmentStack {
|
||||
/// Create a new environment stack with the most outer declarative environment.
|
||||
#[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),
|
||||
})],
|
||||
}
|
||||
}
|
||||
|
||||
/// Extends the length of the next outer function environment to the number of compiled bindings.
|
||||
///
|
||||
/// This is only useful when compiled bindings are added after the initial compilation (eval).
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Check if any of the provided binding names are defined as lexical bindings.
|
||||
///
|
||||
/// Start at the current environment.
|
||||
/// Stop at the next outer function environment.
|
||||
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
|
||||
}
|
||||
|
||||
/// Pop all current environments except the global environment.
|
||||
pub(crate) fn pop_to_global(&mut self) -> Vec<Gc<DeclarativeEnvironment>> {
|
||||
self.stack.split_off(1)
|
||||
}
|
||||
|
||||
/// Get the number of current environments.
|
||||
pub(crate) fn len(&self) -> usize {
|
||||
self.stack.len()
|
||||
}
|
||||
|
||||
/// Truncate current environments to the given number.
|
||||
pub(crate) fn truncate(&mut self, len: usize) {
|
||||
self.stack.truncate(len);
|
||||
}
|
||||
|
||||
/// Extend the current environment stack with the given environments.
|
||||
pub(crate) fn extend(&mut self, other: Vec<Gc<DeclarativeEnvironment>>) {
|
||||
self.stack.extend(other);
|
||||
}
|
||||
|
||||
/// Set the number of bindings on the global environment.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics if no environment exists on the stack.
|
||||
#[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);
|
||||
}
|
||||
}
|
||||
|
||||
/// `GetThisEnvironment`
|
||||
///
|
||||
/// Returns the environment that currently provides a `this` biding.
|
||||
///
|
||||
/// More information:
|
||||
/// - [ECMAScript specification][spec]
|
||||
///
|
||||
/// [spec]: https://tc39.es/ecma262/#sec-getthisenvironment
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics if no environment exists on the stack.
|
||||
#[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")
|
||||
}
|
||||
|
||||
/// Push a declarative environment on the environments stack.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics if no environment exists on the stack.
|
||||
#[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,
|
||||
}));
|
||||
}
|
||||
|
||||
/// Push a function environment on the environments stack.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics if no environment exists on the stack.
|
||||
#[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,
|
||||
}))),
|
||||
}));
|
||||
}
|
||||
|
||||
/// Push a function environment that inherits it's internal slots from the outer environment.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics if no environment exists on the stack.
|
||||
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,
|
||||
}));
|
||||
}
|
||||
|
||||
/// Pop environment from the environments stack.
|
||||
#[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")
|
||||
}
|
||||
|
||||
/// Get the most outer environment.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics if no environment exists on the stack.
|
||||
#[inline]
|
||||
pub(crate) fn current(&mut self) -> Gc<DeclarativeEnvironment> {
|
||||
self.stack
|
||||
.last()
|
||||
.expect("global environment must always exist")
|
||||
.clone()
|
||||
}
|
||||
|
||||
/// Get the compile environment for the current runtime environment.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics if no environment exists on the stack.
|
||||
pub(crate) fn current_compile_environment(&self) -> Gc<Cell<CompileTimeEnvironment>> {
|
||||
self.stack
|
||||
.last()
|
||||
.expect("global environment must always exist")
|
||||
.compile
|
||||
.clone()
|
||||
}
|
||||
|
||||
/// Mark that there may be added bindings in the current environment.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics if no environment exists on the stack.
|
||||
#[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;
|
||||
}
|
||||
|
||||
/// Mark that there may be added binding in all environments.
|
||||
#[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;
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the value of a binding.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics if the environment or binding index are out of range.
|
||||
#[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()
|
||||
}
|
||||
|
||||
/// Get the value of a binding by it's name.
|
||||
///
|
||||
/// This only considers function environments that are poisoned.
|
||||
/// All other bindings are accessed via indices.
|
||||
#[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
|
||||
}
|
||||
|
||||
/// Set the value of a binding.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics if the environment or binding index are out of range.
|
||||
#[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);
|
||||
}
|
||||
|
||||
/// Set the value of a binding if it is initialized.
|
||||
/// Return `true` if the value has been set.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics if the environment or binding index are out of range.
|
||||
#[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
|
||||
}
|
||||
}
|
||||
|
||||
/// Set the value of a binding if it is uninitialized.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics if the environment or binding index are out of range.
|
||||
#[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);
|
||||
}
|
||||
}
|
||||
|
||||
/// Set the value of a binding by it's name.
|
||||
///
|
||||
/// This only considers function environments that are poisoned.
|
||||
/// All other bindings are set via indices.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics if the environment or binding index are out of range.
|
||||
#[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
|
||||
}
|
||||
}
|
||||
|
||||
/// A binding locator contains all information about a binding that is needed to resolve it at runtime.
|
||||
///
|
||||
/// Binding locators get created at bytecode compile time and are accessible at runtime via the [`crate::vm::CodeBlock`].
|
||||
#[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 {
|
||||
/// Creates a new declarative binding locator that has knows indices.
|
||||
#[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,
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates a binding locator that indicates that the binding is on the global object.
|
||||
#[inline]
|
||||
pub(in crate::environments) fn global(name: Sym) -> Self {
|
||||
Self {
|
||||
name,
|
||||
environment_index: 0,
|
||||
binding_index: 0,
|
||||
global: true,
|
||||
mutate_immutable: false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates a binding locator that indicates that it was attempted to mutate an immutable binding.
|
||||
/// At runtime this should always produce a type error.
|
||||
#[inline]
|
||||
pub(in crate::environments) fn mutate_immutable(name: Sym) -> Self {
|
||||
Self {
|
||||
name,
|
||||
environment_index: 0,
|
||||
binding_index: 0,
|
||||
global: false,
|
||||
mutate_immutable: true,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the name of the binding.
|
||||
#[inline]
|
||||
pub(crate) fn name(&self) -> Sym {
|
||||
self.name
|
||||
}
|
||||
|
||||
/// Returns if the binding is located on the global object.
|
||||
#[inline]
|
||||
pub(crate) fn is_global(&self) -> bool {
|
||||
self.global
|
||||
}
|
||||
|
||||
/// Returns the environment index of the binding.
|
||||
#[inline]
|
||||
pub(crate) fn environment_index(&self) -> usize {
|
||||
self.environment_index
|
||||
}
|
||||
|
||||
/// Returns the binding index of the binding.
|
||||
#[inline]
|
||||
pub(crate) fn binding_index(&self) -> usize {
|
||||
self.binding_index
|
||||
}
|
||||
|
||||
/// Helper method to throws an error if the binding access is illegal.
|
||||
#[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(())
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
use crate::exec;
|
||||
|
||||
#[test]
|
||||
fn let_is_block_scoped() {
|
||||
let scenario = r#"
|
||||
{
|
||||
let bar = "bar";
|
||||
}
|
||||
|
||||
try{
|
||||
bar;
|
||||
} catch (err) {
|
||||
err.message
|
||||
}
|
||||
"#;
|
||||
|
||||
assert_eq!(&exec(scenario), "\"bar is not defined\"");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn const_is_block_scoped() {
|
||||
let scenario = r#"
|
||||
{
|
||||
const bar = "bar";
|
||||
}
|
||||
|
||||
try{
|
||||
bar;
|
||||
} catch (err) {
|
||||
err.message
|
||||
}
|
||||
"#;
|
||||
|
||||
assert_eq!(&exec(scenario), "\"bar is not defined\"");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn var_not_block_scoped() {
|
||||
let scenario = r#"
|
||||
{
|
||||
var bar = "bar";
|
||||
}
|
||||
bar == "bar";
|
||||
"#;
|
||||
|
||||
assert_eq!(&exec(scenario), "true");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn functions_use_declaration_scope() {
|
||||
let scenario = r#"
|
||||
function foo() {
|
||||
try {
|
||||
bar;
|
||||
} catch (err) {
|
||||
return err.message;
|
||||
}
|
||||
}
|
||||
{
|
||||
let bar = "bar";
|
||||
foo();
|
||||
}
|
||||
"#;
|
||||
|
||||
assert_eq!(&exec(scenario), "\"bar is not defined\"");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn set_outer_var_in_block_scope() {
|
||||
let scenario = r#"
|
||||
var bar;
|
||||
{
|
||||
bar = "foo";
|
||||
}
|
||||
bar == "foo";
|
||||
"#;
|
||||
|
||||
assert_eq!(&exec(scenario), "true");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn set_outer_let_in_block_scope() {
|
||||
let scenario = r#"
|
||||
let bar;
|
||||
{
|
||||
bar = "foo";
|
||||
}
|
||||
bar == "foo";
|
||||
"#;
|
||||
|
||||
assert_eq!(&exec(scenario), "true");
|
||||
}
|
||||
Reference in New Issue
Block a user