use boa_ast::scope::{BindingLocator, BindingLocatorScope};
use crate::{
Context, JsExpect, JsResult, JsValue,
environments::Environment,
error::JsNativeError,
object::{internal_methods::InternalMethodPropertyContext, shape::slot::SlotAttributes},
property::PropertyKey,
vm::opcode::{IndexOperand, Operation, RegisterOperand},
};
#[derive(Debug, Clone, Copy)]
pub(crate) struct GetName;
impl GetName {
#[inline(always)]
pub(crate) fn operation(
(value, index): (RegisterOperand, IndexOperand),
context: &mut Context,
) -> JsResult<()> {
let fast = {
let locator = &context.vm.frame().code_block.bindings[usize::from(index)];
if context.global_binding_is_static() && !context.is_deleted_binding(locator) {
match locator.scope() {
BindingLocatorScope::GlobalDeclarative => context
.vm
.frame()
.realm
.environment()
.get(locator.binding_index()),
BindingLocatorScope::Stack(env_index) => {
match context.environment_expect(env_index) {
Environment::Declarative(env) => env.get(locator.binding_index()),
Environment::Object(_) => None,
}
}
BindingLocatorScope::GlobalObject => None,
}
} else {
None
}
};
if let Some(result) = fast {
context.vm.set_register(value.into(), result);
return Ok(());
}
let mut binding_locator =
context.vm.frame().code_block.bindings[usize::from(index)].clone();
context.find_runtime_binding(&mut binding_locator)?;
let result = context.get_binding(&binding_locator)?.ok_or_else(|| {
let name = binding_locator.name().to_std_string_escaped();
JsNativeError::reference().with_message(format!("{name} is not defined"))
})?;
context.vm.set_register(value.into(), result);
Ok(())
}
}
impl Operation for GetName {
const NAME: &'static str = "GetName";
const INSTRUCTION: &'static str = "INST - GetName";
const COST: u8 = 4;
}
#[derive(Debug, Clone, Copy)]
pub(crate) struct GetNameGlobal;
impl GetNameGlobal {
#[inline(always)]
pub(crate) fn operation(
(dst, index, ic_index): (RegisterOperand, IndexOperand, IndexOperand),
context: &mut Context,
) -> JsResult<()> {
let is_global = context.vm.frame().code_block.bindings[usize::from(index)].is_global();
let binding_locator = if is_global && context.global_binding_is_static() {
None
} else {
let mut binding_locator =
context.vm.frame().code_block.bindings[usize::from(index)].clone();
context.find_runtime_binding(&mut binding_locator)?;
Some(binding_locator)
};
if binding_locator
.as_ref()
.is_none_or(BindingLocator::is_global)
{
let object = context.global_object();
let ic = &context.vm.frame().code_block().ic[usize::from(ic_index)];
let object_borrowed = object.borrow();
if let Some(slot) = ic.get(object_borrowed.shape()) {
let mut result = if slot.attributes.contains(SlotAttributes::PROTOTYPE) {
let prototype = object_borrowed
.shape()
.prototype()
.expect("prototype should have value");
let prototype = prototype.borrow();
prototype.properties().storage[slot.index as usize].clone()
} else {
object_borrowed.properties().storage[slot.index as usize].clone()
};
drop(object_borrowed);
if slot.attributes.has_get() && result.is_object() {
result = result.as_object().expect("should contain getter").call(
&object.clone().into(),
&[],
context,
)?;
}
context.vm.set_register(dst.into(), result);
return Ok(());
}
drop(object_borrowed);
let key: PropertyKey = ic.name.clone().into();
let lookup_shape = object.borrow().shape().clone();
let context = &mut InternalMethodPropertyContext::new(context);
let Some(result) = object.__try_get__(&key, object.clone().into(), context)? else {
return Err(JsNativeError::reference()
.with_message(format!("{key} is not defined"))
.into());
};
let slot = *context.slot();
if slot.is_cacheable() {
let ic = &context.vm.frame().code_block.ic[usize::from(ic_index)];
ic.set(&lookup_shape, slot);
}
context.vm.set_register(dst.into(), result);
return Ok(());
}
let binding_locator = binding_locator.js_expect("a non-global binding has a locator")?;
let result = context.get_binding(&binding_locator)?.ok_or_else(|| {
let name = binding_locator.name().to_std_string_escaped();
JsNativeError::reference().with_message(format!("{name} is not defined"))
})?;
context.vm.set_register(dst.into(), result);
Ok(())
}
}
impl Operation for GetNameGlobal {
const NAME: &'static str = "GetNameGlobal";
const INSTRUCTION: &'static str = "INST - GetNameGlobal";
const COST: u8 = 4;
}
#[derive(Debug, Clone, Copy)]
pub(crate) struct GetLocator;
impl GetLocator {
#[inline(always)]
pub(crate) fn operation(index: IndexOperand, context: &mut Context) -> JsResult<()> {
let mut binding_locator =
context.vm.frame().code_block.bindings[usize::from(index)].clone();
context.find_runtime_binding(&mut binding_locator)?;
context.vm.frame_mut().binding_stack.push(binding_locator);
Ok(())
}
}
impl Operation for GetLocator {
const NAME: &'static str = "GetLocator";
const INSTRUCTION: &'static str = "INST - GetLocator";
const COST: u8 = 4;
}
#[derive(Debug, Clone, Copy)]
pub(crate) struct GetNameAndLocator;
impl GetNameAndLocator {
#[inline(always)]
pub(crate) fn operation(
(value, index): (RegisterOperand, IndexOperand),
context: &mut Context,
) -> JsResult<()> {
let mut binding_locator =
context.vm.frame().code_block.bindings[usize::from(index)].clone();
context.find_runtime_binding(&mut binding_locator)?;
let result = context.get_binding(&binding_locator)?.ok_or_else(|| {
let name = binding_locator.name().to_std_string_escaped();
JsNativeError::reference().with_message(format!("{name} is not defined"))
})?;
context.vm.frame_mut().binding_stack.push(binding_locator);
context.vm.set_register(value.into(), result);
Ok(())
}
}
impl Operation for GetNameAndLocator {
const NAME: &'static str = "GetNameAndLocator";
const INSTRUCTION: &'static str = "INST - GetNameAndLocator";
const COST: u8 = 4;
}
#[derive(Debug, Clone, Copy)]
pub(crate) struct GetNameOrUndefined;
impl GetNameOrUndefined {
#[inline(always)]
pub(crate) fn operation(
(value, index): (RegisterOperand, IndexOperand),
context: &mut Context,
) -> JsResult<()> {
let mut binding_locator =
context.vm.frame().code_block.bindings[usize::from(index)].clone();
let is_global = binding_locator.is_global();
context.find_runtime_binding(&mut binding_locator)?;
let result = if let Some(value) = context.get_binding(&binding_locator)? {
value
} else if is_global {
JsValue::undefined()
} else {
let name = binding_locator.name().to_std_string_escaped();
return Err(JsNativeError::reference()
.with_message(format!("{name} is not defined"))
.into());
};
context.vm.set_register(value.into(), result);
Ok(())
}
}
impl Operation for GetNameOrUndefined {
const NAME: &'static str = "GetNameOrUndefined";
const INSTRUCTION: &'static str = "INST - GetNameOrUndefined";
const COST: u8 = 4;
}