mod array;
mod block;
mod break_node;
mod conditional;
mod declaration;
mod exception;
mod expression;
mod field;
mod identifier;
mod iteration;
mod object;
mod operator;
mod return_smt;
mod spread;
mod statement_list;
mod switch;
#[cfg(test)]
mod tests;
mod throw;
mod try_node;
use crate::{
builtins::{
function::{Function as FunctionObject, FunctionBody, ThisMode},
object::{Object, ObjectData, INSTANCE_PROTOTYPE, PROTOTYPE},
property::Property,
value::{RcBigInt, RcString, ResultValue, Type, Value},
BigInt, Number,
},
realm::Realm,
syntax::ast::{
constant::Const,
node::{FormalParameter, Node, StatementList},
},
BoaProfiler,
};
use std::convert::TryFrom;
use std::ops::Deref;
pub trait Executable {
fn run(&self, interpreter: &mut Interpreter) -> ResultValue;
}
#[derive(Debug, Eq, PartialEq)]
pub(crate) enum InterpreterState {
Executing,
Return,
Break(Option<String>),
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum PreferredType {
String,
Number,
Default,
}
#[derive(Debug)]
pub struct Interpreter {
state: InterpreterState,
pub realm: Realm,
symbol_count: u32,
}
impl Interpreter {
pub fn new(realm: Realm) -> Self {
Self {
state: InterpreterState::Executing,
realm,
symbol_count: 0,
}
}
#[inline]
pub(crate) fn realm(&self) -> &Realm {
&self.realm
}
#[inline]
pub(crate) fn realm_mut(&mut self) -> &mut Realm {
&mut self.realm
}
#[inline]
pub(crate) fn generate_hash(&mut self) -> u32 {
let hash = self.symbol_count;
self.symbol_count += 1;
hash
}
pub(crate) fn create_function<P, B>(
&mut self,
params: P,
body: B,
this_mode: ThisMode,
constructable: bool,
callable: bool,
) -> Value
where
P: Into<Box<[FormalParameter]>>,
B: Into<StatementList>,
{
let function_prototype = &self
.realm
.environment
.get_global_object()
.expect("Could not get the global object")
.get_field("Function")
.get_field(PROTOTYPE);
let global_val = &self
.realm
.environment
.get_global_object()
.expect("Could not get the global object");
let proto = Value::new_object(Some(global_val));
let params = params.into();
let params_len = params.len();
let func = FunctionObject::new(
params,
Some(self.realm.environment.get_current_environment().clone()),
FunctionBody::Ordinary(body.into()),
this_mode,
constructable,
callable,
);
let new_func = Object::function(func);
let val = Value::from(new_func);
val.set_internal_slot(INSTANCE_PROTOTYPE, function_prototype.clone());
val.set_field(PROTOTYPE, proto);
val.set_field("length", Value::from(params_len));
val
}
pub(crate) fn call(
&mut self,
f: &Value,
this: &Value,
arguments_list: &[Value],
) -> ResultValue {
match *f {
Value::Object(ref obj) => {
let obj = obj.borrow();
if let ObjectData::Function(ref func) = obj.data {
return func.call(f.clone(), this, arguments_list, self);
}
self.throw_type_error("not a function")
}
_ => Err(Value::undefined()),
}
}
#[allow(clippy::wrong_self_convention)]
pub fn to_string(&mut self, value: &Value) -> Result<RcString, Value> {
match value {
Value::Null => Ok(RcString::from("null")),
Value::Undefined => Ok(RcString::from("undefined".to_owned())),
Value::Boolean(boolean) => Ok(RcString::from(boolean.to_string())),
Value::Rational(rational) => Ok(RcString::from(Number::to_native_string(*rational))),
Value::Integer(integer) => Ok(RcString::from(integer.to_string())),
Value::String(string) => Ok(string.clone()),
Value::Symbol(_) => Err(self.construct_type_error("can't convert symbol to string")),
Value::BigInt(ref bigint) => Ok(RcString::from(bigint.to_string())),
Value::Object(_) => {
let primitive = self.to_primitive(value, PreferredType::String)?;
self.to_string(&primitive)
}
}
}
#[allow(clippy::wrong_self_convention)]
pub fn to_bigint(&mut self, value: &Value) -> Result<RcBigInt, Value> {
match value {
Value::Null => Err(self.construct_type_error("cannot convert null to a BigInt")),
Value::Undefined => {
Err(self.construct_type_error("cannot convert undefined to a BigInt"))
}
Value::String(ref string) => Ok(RcBigInt::from(BigInt::from_string(string, self)?)),
Value::Boolean(true) => Ok(RcBigInt::from(BigInt::from(1))),
Value::Boolean(false) => Ok(RcBigInt::from(BigInt::from(0))),
Value::Integer(num) => Ok(RcBigInt::from(BigInt::from(*num))),
Value::Rational(num) => {
if let Ok(bigint) = BigInt::try_from(*num) {
return Ok(RcBigInt::from(bigint));
}
Err(self.construct_type_error(format!(
"The number {} cannot be converted to a BigInt because it is not an integer",
num
)))
}
Value::BigInt(b) => Ok(b.clone()),
Value::Object(_) => {
let primitive = self.to_primitive(value, PreferredType::Number)?;
self.to_bigint(&primitive)
}
Value::Symbol(_) => Err(self.construct_type_error("cannot convert Symbol to a BigInt")),
}
}
#[allow(clippy::wrong_self_convention)]
pub fn to_index(&mut self, value: &Value) -> Result<usize, Value> {
if value.is_undefined() {
return Ok(0);
}
let integer_index = self.to_integer(value)?;
if integer_index < 0 {
return Err(self.construct_range_error("Integer index must be >= 0"));
}
if integer_index > 2i64.pow(53) - 1 {
return Err(self.construct_range_error("Integer index must be less than 2**(53) - 1"));
}
Ok(integer_index as usize)
}
#[allow(clippy::wrong_self_convention)]
pub fn to_integer(&mut self, value: &Value) -> Result<i64, Value> {
let number = self.to_number(value)?;
if number.is_nan() {
return Ok(0);
}
Ok(number as i64)
}
#[allow(clippy::wrong_self_convention)]
pub fn to_number(&mut self, value: &Value) -> Result<f64, Value> {
match *value {
Value::Null => Ok(0.0),
Value::Undefined => Ok(f64::NAN),
Value::Boolean(b) => Ok(if b { 1.0 } else { 0.0 }),
Value::String(ref string) => Ok(string.parse().unwrap_or(f64::NAN)),
Value::Rational(number) => Ok(number),
Value::Integer(integer) => Ok(f64::from(integer)),
Value::Symbol(_) => Err(self.construct_type_error("argument must not be a symbol")),
Value::BigInt(_) => Err(self.construct_type_error("argument must not be a bigint")),
Value::Object(_) => {
let primitive = self.to_primitive(value, PreferredType::Number)?;
self.to_number(&primitive)
}
}
}
#[allow(clippy::wrong_self_convention)]
pub fn to_numeric(&mut self, value: &Value) -> ResultValue {
let primitive = self.to_primitive(value, PreferredType::Number)?;
if primitive.is_bigint() {
return Ok(primitive);
}
Ok(Value::from(self.to_number(&primitive)?))
}
#[allow(clippy::wrong_self_convention)]
pub(crate) fn to_numeric_number(&mut self, value: &Value) -> Result<f64, Value> {
let primitive = self.to_primitive(value, PreferredType::Number)?;
if let Some(ref bigint) = primitive.as_bigint() {
return Ok(bigint.to_f64());
}
Ok(self.to_number(&primitive)?)
}
pub(crate) fn extract_array_properties(&mut self, value: &Value) -> Result<Vec<Value>, ()> {
if let Value::Object(ref x) = value {
if let ObjectData::Array = x.deref().borrow().data {
let length = i32::from(&value.get_field("length"));
let values = (0..length)
.map(|idx| value.get_field(idx.to_string()))
.collect();
return Ok(values);
}
return Err(());
}
Err(())
}
pub(crate) fn ordinary_to_primitive(&mut self, o: &Value, hint: PreferredType) -> ResultValue {
debug_assert!(o.get_type() == Type::Object);
debug_assert!(hint == PreferredType::String || hint == PreferredType::Number);
let method_names = if hint == PreferredType::String {
["toString", "valueOf"]
} else {
["valueOf", "toString"]
};
for name in &method_names {
let method: Value = o.get_field(*name);
if method.is_function() {
let result = self.call(&method, &o, &[])?;
if !result.is_object() {
return Ok(result);
}
}
}
self.throw_type_error("cannot convert object to primitive value")
}
#[allow(clippy::wrong_self_convention)]
pub(crate) fn to_primitive(
&mut self,
input: &Value,
preferred_type: PreferredType,
) -> ResultValue {
if let Value::Object(_) = input {
let mut hint = preferred_type;
if hint == PreferredType::Default {
hint = PreferredType::Number;
};
self.ordinary_to_primitive(input, hint)
} else {
Ok(input.clone())
}
}
#[allow(clippy::wrong_self_convention)]
pub(crate) fn to_property_key(&mut self, value: &Value) -> ResultValue {
let key = self.to_primitive(value, PreferredType::String)?;
if key.is_symbol() {
Ok(key)
} else {
self.to_string(&key).map(Value::from)
}
}
pub(crate) fn has_property(&self, obj: &Value, key: &Value) -> bool {
if let Some(obj) = obj.as_object() {
if !Property::is_property_key(key) {
false
} else {
obj.has_property(key)
}
} else {
false
}
}
#[allow(clippy::wrong_self_convention)]
pub(crate) fn to_object(&mut self, value: &Value) -> ResultValue {
match value {
Value::Undefined | Value::Null => {
self.throw_type_error("cannot convert 'null' or 'undefined' to object")
}
Value::Boolean(boolean) => {
let proto = self
.realm
.environment
.get_binding_value("Boolean")
.expect("Boolean was not initialized")
.get_field(PROTOTYPE);
Ok(Value::new_object_from_prototype(
proto,
ObjectData::Boolean(*boolean),
))
}
Value::Integer(integer) => {
let proto = self
.realm
.environment
.get_binding_value("Number")
.expect("Number was not initialized")
.get_field(PROTOTYPE);
Ok(Value::new_object_from_prototype(
proto,
ObjectData::Number(f64::from(*integer)),
))
}
Value::Rational(rational) => {
let proto = self
.realm
.environment
.get_binding_value("Number")
.expect("Number was not initialized")
.get_field(PROTOTYPE);
Ok(Value::new_object_from_prototype(
proto,
ObjectData::Number(*rational),
))
}
Value::String(ref string) => {
let proto = self
.realm
.environment
.get_binding_value("String")
.expect("String was not initialized")
.get_field(PROTOTYPE);
Ok(Value::new_object_from_prototype(
proto,
ObjectData::String(string.clone()),
))
}
Value::Symbol(ref symbol) => {
let proto = self
.realm
.environment
.get_binding_value("Symbol")
.expect("Symbol was not initialized")
.get_field(PROTOTYPE);
Ok(Value::new_object_from_prototype(
proto,
ObjectData::Symbol(symbol.clone()),
))
}
Value::BigInt(ref bigint) => {
let proto = self
.realm
.environment
.get_binding_value("BigInt")
.expect("BigInt was not initialized")
.get_field(PROTOTYPE);
let bigint_obj =
Value::new_object_from_prototype(proto, ObjectData::BigInt(bigint.clone()));
Ok(bigint_obj)
}
Value::Object(_) => Ok(value.clone()),
}
}
fn set_value(&mut self, node: &Node, value: Value) -> ResultValue {
match node {
Node::Identifier(ref name) => {
self.realm
.environment
.set_mutable_binding(name.as_ref(), value.clone(), true);
Ok(value)
}
Node::GetConstField(ref get_const_field_node) => Ok(get_const_field_node
.obj()
.run(self)?
.set_field(get_const_field_node.field(), value)),
Node::GetField(ref get_field) => Ok(get_field
.obj()
.run(self)?
.set_field(get_field.field().run(self)?, value)),
_ => panic!("TypeError: invalid assignment to {}", node),
}
}
#[inline]
pub(crate) fn set_current_state(&mut self, new_state: InterpreterState) {
self.state = new_state
}
#[inline]
pub(crate) fn get_current_state(&self) -> &InterpreterState {
&self.state
}
#[inline]
pub fn require_object_coercible<'a>(&mut self, value: &'a Value) -> Result<&'a Value, Value> {
if value.is_null_or_undefined() {
Err(self.construct_type_error("cannot convert null or undefined to Object"))
} else {
Ok(value)
}
}
}
impl Executable for Node {
fn run(&self, interpreter: &mut Interpreter) -> ResultValue {
let _timer = BoaProfiler::global().start_event("Executable", "exec");
match *self {
Node::Const(Const::Null) => Ok(Value::null()),
Node::Const(Const::Num(num)) => Ok(Value::rational(num)),
Node::Const(Const::Int(num)) => Ok(Value::integer(num)),
Node::Const(Const::BigInt(ref num)) => Ok(Value::from(num.clone())),
Node::Const(Const::String(ref value)) => Ok(Value::string(value.to_string())),
Node::Const(Const::Bool(value)) => Ok(Value::boolean(value)),
Node::Block(ref block) => block.run(interpreter),
Node::Identifier(ref identifier) => identifier.run(interpreter),
Node::GetConstField(ref get_const_field_node) => get_const_field_node.run(interpreter),
Node::GetField(ref get_field) => get_field.run(interpreter),
Node::Call(ref expr) => expr.run(interpreter),
Node::WhileLoop(ref while_loop) => while_loop.run(interpreter),
Node::DoWhileLoop(ref do_while) => do_while.run(interpreter),
Node::ForLoop(ref for_loop) => for_loop.run(interpreter),
Node::If(ref if_smt) => if_smt.run(interpreter),
Node::Switch(ref switch) => switch.run(interpreter),
Node::Object(ref obj) => obj.run(interpreter),
Node::ArrayDecl(ref arr) => arr.run(interpreter),
Node::FunctionDecl(ref decl) => decl.run(interpreter),
Node::FunctionExpr(ref expr) => expr.run(interpreter),
Node::ArrowFunctionDecl(ref decl) => decl.run(interpreter),
Node::BinOp(ref op) => op.run(interpreter),
Node::UnaryOp(ref op) => op.run(interpreter),
Node::New(ref call) => call.run(interpreter),
Node::Return(ref ret) => ret.run(interpreter),
Node::Throw(ref throw) => throw.run(interpreter),
Node::Assign(ref op) => op.run(interpreter),
Node::VarDeclList(ref decl) => decl.run(interpreter),
Node::LetDeclList(ref decl) => decl.run(interpreter),
Node::ConstDeclList(ref decl) => decl.run(interpreter),
Node::Spread(ref spread) => spread.run(interpreter),
Node::This => {
Ok(interpreter.realm().environment.get_this_binding())
}
Node::Try(ref try_node) => try_node.run(interpreter),
Node::Break(ref break_node) => break_node.run(interpreter),
ref i => unimplemented!("{:?}", i),
}
}
}