use crate::{
builtins::value::Value,
environment::{
declarative_environment_record::DeclarativeEnvironmentRecord,
environment_record_trait::EnvironmentRecordTrait,
function_environment_record::{BindingStatus, FunctionEnvironmentRecord},
global_environment_record::GlobalEnvironmentRecord,
object_environment_record::ObjectEnvironmentRecord,
},
BoaProfiler,
};
use gc::{Gc, GcCell};
use rustc_hash::{FxHashMap, FxHashSet};
use std::{collections::VecDeque, error, fmt};
pub type Environment = Gc<GcCell<Box<dyn EnvironmentRecordTrait>>>;
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum EnvironmentType {
Declarative,
Function,
Global,
Object,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum VariableScope {
Block,
Function,
}
#[derive(Debug, Clone)]
pub struct LexicalEnvironment {
environment_stack: VecDeque<Environment>,
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct EnvironmentError {
details: String,
}
impl EnvironmentError {
pub fn new(msg: &str) -> Self {
Self {
details: msg.to_string(),
}
}
}
impl fmt::Display for EnvironmentError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.details)
}
}
impl error::Error for EnvironmentError {
fn description(&self) -> &str {
&self.details
}
fn cause(&self) -> Option<&dyn error::Error> {
None
}
}
impl LexicalEnvironment {
pub fn new(global: Value) -> Self {
let _timer = BoaProfiler::global().start_event("LexicalEnvironment::new", "env");
let global_env = new_global_environment(global.clone(), global);
let mut lexical_env = Self {
environment_stack: VecDeque::new(),
};
lexical_env.environment_stack.push_back(global_env);
lexical_env
}
pub fn push(&mut self, env: Environment) {
let current_env: Environment = self.get_current_environment().clone();
env.borrow_mut().set_outer_environment(current_env);
self.environment_stack.push_back(env);
}
pub fn pop(&mut self) -> Option<Environment> {
self.environment_stack.pop_back()
}
pub fn environments(&self) -> impl Iterator<Item = &Environment> {
self.environment_stack.iter().rev()
}
pub fn get_global_object(&self) -> Option<Value> {
self.environment_stack
.get(0)
.expect("")
.borrow()
.get_global_object()
}
pub fn get_this_binding(&self) -> Value {
self.environments()
.find(|env| env.borrow().has_this_binding())
.map(|env| env.borrow().get_this_binding())
.unwrap_or_else(Value::undefined)
}
pub fn create_mutable_binding(&mut self, name: String, deletion: bool, scope: VariableScope) {
match scope {
VariableScope::Block => self
.get_current_environment()
.borrow_mut()
.create_mutable_binding(name, deletion),
VariableScope::Function => {
let env = self
.environments()
.find(|env| match env.borrow().get_environment_type() {
EnvironmentType::Function | EnvironmentType::Global => true,
_ => false,
})
.expect("No function or global environment");
env.borrow_mut().create_mutable_binding(name, deletion);
}
}
}
pub fn create_immutable_binding(
&mut self,
name: String,
deletion: bool,
scope: VariableScope,
) -> bool {
match scope {
VariableScope::Block => self
.get_current_environment()
.borrow_mut()
.create_immutable_binding(name, deletion),
VariableScope::Function => {
let env = self
.environments()
.find(|env| match env.borrow().get_environment_type() {
EnvironmentType::Function | EnvironmentType::Global => true,
_ => false,
})
.expect("No function or global environment");
#[allow(clippy::let_and_return)]
let b = env.borrow_mut().create_immutable_binding(name, deletion);
b
}
}
}
pub fn set_mutable_binding(&mut self, name: &str, value: Value, strict: bool) {
let env = self
.environments()
.find(|env| env.borrow().has_binding(name))
.expect("Binding does not exists");
env.borrow_mut().set_mutable_binding(name, value, strict);
}
pub fn initialize_binding(&mut self, name: &str, value: Value) {
let env = self
.environments()
.find(|env| env.borrow().has_binding(name))
.expect("Binding does not exists");
env.borrow_mut().initialize_binding(name, value);
}
pub fn get_current_environment_ref(&self) -> &Environment {
self.environment_stack
.back()
.expect("Could not get current environment")
}
pub fn get_current_environment(&mut self) -> &mut Environment {
self.environment_stack
.back_mut()
.expect("Could not get mutable reference to back object")
}
pub fn has_binding(&self, name: &str) -> bool {
self.environments()
.any(|env| env.borrow().has_binding(name))
}
pub fn get_binding_value(&self, name: &str) -> Option<Value> {
self.environments()
.find(|env| env.borrow().has_binding(name))
.map(|env| env.borrow().get_binding_value(name, false))
}
}
pub fn new_declarative_environment(env: Option<Environment>) -> Environment {
let _timer = BoaProfiler::global().start_event("new_declarative_environment", "env");
let boxed_env = Box::new(DeclarativeEnvironmentRecord {
env_rec: FxHashMap::default(),
outer_env: env,
});
Gc::new(GcCell::new(boxed_env))
}
pub fn new_function_environment(
f: Value,
this: Option<Value>,
outer: Option<Environment>,
binding_status: BindingStatus,
) -> Environment {
let mut func_env = FunctionEnvironmentRecord {
env_rec: FxHashMap::default(),
function: f,
this_binding_status: binding_status,
home_object: Value::undefined(),
new_target: Value::undefined(),
outer_env: outer,
this_value: Value::undefined(),
};
if let Some(v) = this {
func_env.bind_this_value(v);
}
Gc::new(GcCell::new(Box::new(func_env)))
}
pub fn new_object_environment(object: Value, environment: Option<Environment>) -> Environment {
Gc::new(GcCell::new(Box::new(ObjectEnvironmentRecord {
bindings: object,
outer_env: environment,
with_environment: false,
})))
}
pub fn new_global_environment(global: Value, this_value: Value) -> Environment {
let obj_rec = ObjectEnvironmentRecord {
bindings: global,
outer_env: None,
with_environment: false,
};
let dcl_rec = DeclarativeEnvironmentRecord {
env_rec: FxHashMap::default(),
outer_env: None,
};
Gc::new(GcCell::new(Box::new(GlobalEnvironmentRecord {
object_record: obj_rec,
global_this_binding: this_value,
declarative_record: dcl_rec,
var_names: FxHashSet::default(),
})))
}
#[cfg(test)]
mod tests {
use crate::exec;
#[test]
fn let_is_blockscoped() {
let scenario = r#"
{
let bar = "bar";
}
try{
bar;
} catch (err) {
err.message
}
"#;
assert_eq!(&exec(scenario), "bar is not defined");
}
#[test]
fn const_is_blockscoped() {
let scenario = r#"
{
const bar = "bar";
}
try{
bar;
} catch (err) {
err.message
}
"#;
assert_eq!(&exec(scenario), "bar is not defined");
}
#[test]
fn var_not_blockscoped() {
let scenario = r#"
{
var bar = "bar";
}
bar == "bar";
"#;
assert_eq!(&exec(scenario), "true");
}
#[test]
fn set_outer_var_in_blockscope() {
let scenario = r#"
var bar;
{
bar = "foo";
}
bar == "foo";
"#;
assert_eq!(&exec(scenario), "true");
}
#[test]
fn set_outer_let_in_blockscope() {
let scenario = r#"
let bar;
{
bar = "foo";
}
bar == "foo";
"#;
assert_eq!(&exec(scenario), "true");
}
}