use crate::{
builtins::{
self,
function::{Function, FunctionFlags, NativeFunction},
iterable::IteratorPrototypes,
},
class::{Class, ClassBuilder},
exec::Interpreter,
object::{FunctionBuilder, JsObject, Object, PROTOTYPE},
property::{Attribute, PropertyDescriptor, PropertyKey},
realm::Realm,
syntax::{
ast::{
node::{
statement_list::RcStatementList, Call, FormalParameter, Identifier, New,
StatementList,
},
Const, Node,
},
Parser,
},
BoaProfiler, Executable, JsResult, JsString, JsValue,
};
#[cfg(feature = "console")]
use crate::builtins::console::Console;
#[cfg(feature = "vm")]
use crate::vm::Vm;
#[derive(Debug, Clone)]
pub struct StandardConstructor {
pub(crate) constructor: JsObject,
pub(crate) prototype: JsObject,
}
impl Default for StandardConstructor {
fn default() -> Self {
Self {
constructor: JsObject::new(Object::default()),
prototype: JsObject::new(Object::default()),
}
}
}
impl StandardConstructor {
fn with_prototype(prototype: Object) -> Self {
Self {
constructor: JsObject::new(Object::default()),
prototype: JsObject::new(prototype),
}
}
#[inline]
pub fn constructor(&self) -> JsObject {
self.constructor.clone()
}
#[inline]
pub fn prototype(&self) -> JsObject {
self.prototype.clone()
}
}
#[derive(Debug, Clone)]
pub struct StandardObjects {
object: StandardConstructor,
function: StandardConstructor,
array: StandardConstructor,
bigint: StandardConstructor,
number: StandardConstructor,
boolean: StandardConstructor,
string: StandardConstructor,
regexp: StandardConstructor,
symbol: StandardConstructor,
error: StandardConstructor,
type_error: StandardConstructor,
referece_error: StandardConstructor,
range_error: StandardConstructor,
syntax_error: StandardConstructor,
eval_error: StandardConstructor,
uri_error: StandardConstructor,
map: StandardConstructor,
set: StandardConstructor,
}
impl Default for StandardObjects {
fn default() -> Self {
Self {
object: StandardConstructor::default(),
function: StandardConstructor::default(),
array: StandardConstructor::default(),
bigint: StandardConstructor::default(),
number: StandardConstructor::with_prototype(Object::number(0.0)),
boolean: StandardConstructor::with_prototype(Object::boolean(false)),
string: StandardConstructor::with_prototype(Object::string("")),
regexp: StandardConstructor::default(),
symbol: StandardConstructor::default(),
error: StandardConstructor::default(),
type_error: StandardConstructor::default(),
referece_error: StandardConstructor::default(),
range_error: StandardConstructor::default(),
syntax_error: StandardConstructor::default(),
eval_error: StandardConstructor::default(),
uri_error: StandardConstructor::default(),
map: StandardConstructor::default(),
set: StandardConstructor::default(),
}
}
}
impl StandardObjects {
#[inline]
pub fn object_object(&self) -> &StandardConstructor {
&self.object
}
#[inline]
pub fn function_object(&self) -> &StandardConstructor {
&self.function
}
#[inline]
pub fn array_object(&self) -> &StandardConstructor {
&self.array
}
#[inline]
pub fn bigint_object(&self) -> &StandardConstructor {
&self.bigint
}
#[inline]
pub fn number_object(&self) -> &StandardConstructor {
&self.number
}
#[inline]
pub fn boolean_object(&self) -> &StandardConstructor {
&self.boolean
}
#[inline]
pub fn string_object(&self) -> &StandardConstructor {
&self.string
}
#[inline]
pub fn regexp_object(&self) -> &StandardConstructor {
&self.regexp
}
#[inline]
pub fn symbol_object(&self) -> &StandardConstructor {
&self.symbol
}
#[inline]
pub fn error_object(&self) -> &StandardConstructor {
&self.error
}
#[inline]
pub fn reference_error_object(&self) -> &StandardConstructor {
&self.referece_error
}
#[inline]
pub fn type_error_object(&self) -> &StandardConstructor {
&self.type_error
}
#[inline]
pub fn range_error_object(&self) -> &StandardConstructor {
&self.range_error
}
#[inline]
pub fn syntax_error_object(&self) -> &StandardConstructor {
&self.syntax_error
}
#[inline]
pub fn eval_error_object(&self) -> &StandardConstructor {
&self.eval_error
}
#[inline]
pub fn uri_error_object(&self) -> &StandardConstructor {
&self.uri_error
}
#[inline]
pub fn map_object(&self) -> &StandardConstructor {
&self.map
}
#[inline]
pub fn set_object(&self) -> &StandardConstructor {
&self.set
}
}
#[derive(Debug, Copy, Clone)]
pub(crate) enum StrictType {
Off,
Global,
Function,
}
#[derive(Debug)]
pub struct Context {
pub(crate) realm: Realm,
executor: Interpreter,
#[cfg(feature = "console")]
console: Console,
iterator_prototypes: IteratorPrototypes,
standard_objects: StandardObjects,
pub trace: bool,
strict: StrictType,
}
impl Default for Context {
fn default() -> Self {
let realm = Realm::create();
let executor = Interpreter::new();
let mut context = Self {
realm,
executor,
#[cfg(feature = "console")]
console: Console::default(),
iterator_prototypes: IteratorPrototypes::default(),
standard_objects: Default::default(),
trace: false,
strict: StrictType::Off,
};
context.create_intrinsics();
context.iterator_prototypes = IteratorPrototypes::init(&mut context);
context
}
}
impl Context {
#[inline]
pub fn new() -> Self {
Default::default()
}
#[inline]
pub fn executor(&mut self) -> &mut Interpreter {
&mut self.executor
}
#[cfg(feature = "console")]
pub(crate) fn console(&self) -> &Console {
&self.console
}
#[cfg(feature = "console")]
#[inline]
pub(crate) fn console_mut(&mut self) -> &mut Console {
&mut self.console
}
#[inline]
pub fn strict(&self) -> bool {
matches!(self.strict, StrictType::Global | StrictType::Function)
}
#[inline]
pub(crate) fn strict_type(&self) -> StrictType {
self.strict
}
#[inline]
pub(crate) fn set_strict(&mut self, strict: StrictType) {
self.strict = strict;
}
#[inline]
pub fn set_strict_mode_off(&mut self) {
self.strict = StrictType::Off;
}
#[inline]
pub fn set_strict_mode_global(&mut self) {
self.strict = StrictType::Global;
}
#[inline]
fn create_intrinsics(&mut self) {
let _timer = BoaProfiler::global().start_event("create_intrinsics", "interpreter");
builtins::init(self);
}
#[inline]
pub fn construct_object(&self) -> JsObject {
let object_prototype: JsValue = self.standard_objects().object_object().prototype().into();
JsObject::new(Object::create(object_prototype))
}
#[inline]
pub(crate) fn call(
&mut self,
f: &JsValue,
this: &JsValue,
args: &[JsValue],
) -> JsResult<JsValue> {
match *f {
JsValue::Object(ref object) => object.call(this, args, self),
_ => self.throw_type_error("not a function"),
}
}
#[inline]
pub fn global_object(&self) -> JsObject {
self.realm.global_object.clone()
}
#[inline]
pub fn construct_error<M>(&mut self, message: M) -> JsValue
where
M: Into<Box<str>>,
{
New::from(Call::new(
Identifier::from("Error"),
vec![Const::from(message.into()).into()],
))
.run(self)
.expect("Into<String> used as message")
}
#[inline]
pub fn throw_error<M>(&mut self, message: M) -> JsResult<JsValue>
where
M: Into<Box<str>>,
{
Err(self.construct_error(message))
}
#[inline]
pub fn construct_range_error<M>(&mut self, message: M) -> JsValue
where
M: Into<Box<str>>,
{
New::from(Call::new(
Identifier::from("RangeError"),
vec![Const::from(message.into()).into()],
))
.run(self)
.expect("Into<String> used as message")
}
#[inline]
pub fn throw_range_error<M>(&mut self, message: M) -> JsResult<JsValue>
where
M: Into<Box<str>>,
{
Err(self.construct_range_error(message))
}
#[inline]
pub fn construct_type_error<M>(&mut self, message: M) -> JsValue
where
M: Into<Box<str>>,
{
New::from(Call::new(
Identifier::from("TypeError"),
vec![Const::from(message.into()).into()],
))
.run(self)
.expect("Into<String> used as message")
}
#[inline]
pub fn throw_type_error<M>(&mut self, message: M) -> JsResult<JsValue>
where
M: Into<Box<str>>,
{
Err(self.construct_type_error(message))
}
#[inline]
pub fn construct_reference_error<M>(&mut self, message: M) -> JsValue
where
M: Into<Box<str>>,
{
New::from(Call::new(
Identifier::from("ReferenceError"),
vec![Const::from(message.into()).into()],
))
.run(self)
.expect("Into<String> used as message")
}
#[inline]
pub fn throw_reference_error<M>(&mut self, message: M) -> JsResult<JsValue>
where
M: Into<Box<str>>,
{
Err(self.construct_reference_error(message))
}
#[inline]
pub fn construct_syntax_error<M>(&mut self, message: M) -> JsValue
where
M: Into<Box<str>>,
{
New::from(Call::new(
Identifier::from("SyntaxError"),
vec![Const::from(message.into()).into()],
))
.run(self)
.expect("Into<String> used as message")
}
#[inline]
pub fn throw_syntax_error<M>(&mut self, message: M) -> JsResult<JsValue>
where
M: Into<Box<str>>,
{
Err(self.construct_syntax_error(message))
}
pub fn construct_eval_error<M>(&mut self, message: M) -> JsValue
where
M: Into<Box<str>>,
{
New::from(Call::new(
Identifier::from("EvalError"),
vec![Const::from(message.into()).into()],
))
.run(self)
.expect("Into<String> used as message")
}
pub fn construct_uri_error<M>(&mut self, message: M) -> JsValue
where
M: Into<Box<str>>,
{
New::from(Call::new(
Identifier::from("URIError"),
vec![Const::from(message.into()).into()],
))
.run(self)
.expect("Into<String> used as message")
}
pub fn throw_eval_error<M>(&mut self, message: M) -> JsResult<JsValue>
where
M: Into<Box<str>>,
{
Err(self.construct_eval_error(message))
}
pub fn throw_uri_error<M>(&mut self, message: M) -> JsResult<JsValue>
where
M: Into<Box<str>>,
{
Err(self.construct_uri_error(message))
}
pub(crate) fn create_function<N, P>(
&mut self,
name: N,
params: P,
mut body: StatementList,
flags: FunctionFlags,
) -> JsResult<JsValue>
where
N: Into<JsString>,
P: Into<Box<[FormalParameter]>>,
{
let name = name.into();
let function_prototype: JsValue =
self.standard_objects().function_object().prototype().into();
let prototype = self.construct_object();
if self.strict() {
body.set_strict(true);
}
let params = params.into();
let params_len = params.len();
let func = Function::Ordinary {
flags,
body: RcStatementList::from(body),
params,
environment: self.get_current_environment().clone(),
};
let function = JsObject::new(Object::function(func, function_prototype));
let constructor = PropertyDescriptor::builder()
.value(function.clone())
.writable(true)
.enumerable(false)
.configurable(true);
prototype.define_property_or_throw("constructor", constructor, self)?;
let prototype = PropertyDescriptor::builder()
.value(prototype)
.writable(true)
.enumerable(false)
.configurable(false);
function.define_property_or_throw(PROTOTYPE, prototype, self)?;
let length = PropertyDescriptor::builder()
.value(params_len)
.writable(false)
.enumerable(false)
.configurable(true);
function.define_property_or_throw("length", length, self)?;
let name = PropertyDescriptor::builder()
.value(name)
.writable(false)
.enumerable(false)
.configurable(true);
function.define_property_or_throw("name", name, self)?;
Ok(function.into())
}
#[inline]
pub fn register_global_function(
&mut self,
name: &str,
length: usize,
body: NativeFunction,
) -> JsResult<()> {
let function = FunctionBuilder::native(self, body)
.name(name)
.length(length)
.constructable(true)
.build();
self.global_object().insert_property(
name,
PropertyDescriptor::builder()
.value(function)
.writable(true)
.enumerable(false)
.configurable(true),
);
Ok(())
}
#[inline]
pub fn register_global_closure<F>(&mut self, name: &str, length: usize, body: F) -> JsResult<()>
where
F: Fn(&JsValue, &[JsValue], &mut Context) -> JsResult<JsValue> + Copy + 'static,
{
let function = FunctionBuilder::closure(self, body)
.name(name)
.length(length)
.constructable(true)
.build();
self.global_object().insert_property(
name,
PropertyDescriptor::builder()
.value(function)
.writable(true)
.enumerable(false)
.configurable(true),
);
Ok(())
}
#[inline]
pub(crate) fn has_property(&mut self, obj: &JsValue, key: &PropertyKey) -> JsResult<bool> {
if let Some(obj) = obj.as_object() {
obj.__has_property__(key, self)
} else {
Ok(false)
}
}
#[inline]
pub(crate) fn set_value(&mut self, node: &Node, value: JsValue) -> JsResult<JsValue> {
match node {
Node::Identifier(ref name) => {
self.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, false, self)?),
Node::GetField(ref get_field) => {
let field = get_field.field().run(self)?;
let key = field.to_property_key(self)?;
Ok(get_field
.obj()
.run(self)?
.set_field(key, value, false, self)?)
}
_ => self.throw_type_error(format!("invalid assignment to {}", node)),
}
}
#[inline]
pub fn register_global_class<T>(&mut self) -> JsResult<()>
where
T: Class,
{
let mut class_builder = ClassBuilder::new::<T>(self);
T::init(&mut class_builder)?;
let class = class_builder.build();
let property = PropertyDescriptor::builder()
.value(class)
.writable(T::ATTRIBUTES.writable())
.enumerable(T::ATTRIBUTES.enumerable())
.configurable(T::ATTRIBUTES.configurable());
self.global_object().insert(T::NAME, property);
Ok(())
}
#[inline]
pub fn register_global_property<K, V>(&mut self, key: K, value: V, attribute: Attribute)
where
K: Into<PropertyKey>,
V: Into<JsValue>,
{
self.global_object().insert(
key,
PropertyDescriptor::builder()
.value(value)
.writable(attribute.writable())
.enumerable(attribute.enumerable())
.configurable(attribute.configurable()),
);
}
#[cfg(not(feature = "vm"))]
#[allow(clippy::unit_arg, clippy::drop_copy)]
#[inline]
pub fn eval<T: AsRef<[u8]>>(&mut self, src: T) -> JsResult<JsValue> {
let main_timer = BoaProfiler::global().start_event("Main", "Main");
let src_bytes: &[u8] = src.as_ref();
let parsing_result = Parser::new(src_bytes, false)
.parse_all()
.map_err(|e| e.to_string());
let execution_result = match parsing_result {
Ok(statement_list) => {
if statement_list.strict() {
self.set_strict_mode_global();
}
statement_list.run(self)
}
Err(e) => self.throw_syntax_error(e),
};
drop(main_timer);
BoaProfiler::global().drop();
execution_result
}
#[cfg(feature = "vm")]
#[allow(clippy::unit_arg, clippy::drop_copy)]
pub fn eval<T: AsRef<[u8]>>(&mut self, src: T) -> JsResult<JsValue> {
let main_timer = BoaProfiler::global().start_event("Main", "Main");
let src_bytes: &[u8] = src.as_ref();
let parsing_result = Parser::new(src_bytes, false)
.parse_all()
.map_err(|e| e.to_string());
let statement_list = match parsing_result {
Ok(statement_list) => statement_list,
Err(e) => return self.throw_syntax_error(e),
};
let mut compiler = crate::bytecompiler::ByteCompiler::default();
compiler.compile_statement_list(&statement_list, true);
let code_block = compiler.finish();
let mut vm = Vm::new(code_block, self);
let result = vm.run();
drop(main_timer);
BoaProfiler::global().drop();
result
}
#[inline]
pub fn iterator_prototypes(&self) -> &IteratorPrototypes {
&self.iterator_prototypes
}
#[inline]
pub fn standard_objects(&self) -> &StandardObjects {
&self.standard_objects
}
pub fn set_trace(&mut self, trace: bool) {
self.trace = trace;
}
}