From 2d948598eb10c0961e3a1abd2590f6071bb139bb Mon Sep 17 00:00:00 2001 From: Iban Eguia Moraza Date: Sat, 23 May 2020 17:57:11 +0200 Subject: [PATCH] Added initial RangeError exceptions --- boa/src/builtins/array/mod.rs | 1732 +++++++-------- boa/src/builtins/bigint/mod.rs | 165 +- boa/src/builtins/boolean/mod.rs | 166 +- boa/src/builtins/boolean/tests.rs | 2 +- boa/src/builtins/console/mod.rs | 41 +- boa/src/builtins/error/mod.rs | 97 +- boa/src/builtins/error/range.rs | 100 +- boa/src/builtins/function/mod.rs | 34 +- boa/src/builtins/json/mod.rs | 11 +- boa/src/builtins/math/mod.rs | 81 +- boa/src/builtins/mod.rs | 49 +- boa/src/builtins/number/mod.rs | 742 ++++--- boa/src/builtins/number/tests.rs | 45 +- boa/src/builtins/object/mod.rs | 18 +- boa/src/builtins/property/mod.rs | 24 +- boa/src/builtins/regexp/mod.rs | 794 +++---- boa/src/builtins/regexp/tests.rs | 2 +- boa/src/builtins/string/mod.rs | 1887 +++++++++-------- boa/src/builtins/string/tests.rs | 156 +- boa/src/builtins/symbol/mod.rs | 10 +- boa/src/builtins/value/conversions.rs | 6 + boa/src/builtins/value/mod.rs | 42 +- boa/src/builtins/value/operations.rs | 11 +- boa/src/builtins/value/tests.rs | 4 +- .../environment/object_environment_record.rs | 2 +- boa/src/exec/array.rs | 9 +- boa/src/exec/declaration.rs | 4 +- boa/src/exec/mod.rs | 43 +- boa/src/exec/operator.rs | 6 +- 29 files changed, 3241 insertions(+), 3042 deletions(-) diff --git a/boa/src/builtins/array/mod.rs b/boa/src/builtins/array/mod.rs index a7f7802d372..49efe22c78c 100644 --- a/boa/src/builtins/array/mod.rs +++ b/boa/src/builtins/array/mod.rs @@ -12,9 +12,10 @@ #[cfg(test)] mod tests; -use super::function::make_constructor_fn; +use super::function::{make_builtin_fn, make_constructor_fn}; use crate::{ builtins::{ + error::RangeError, object::{ObjectKind, INSTANCE_PROTOTYPE, PROTOTYPE}, property::Property, value::{same_value_zero, ResultValue, Value, ValueData}, @@ -27,969 +28,1020 @@ use std::{ ops::Deref, }; -/// Creates a new `Array` instance. -pub(crate) fn new_array(interpreter: &Interpreter) -> ResultValue { - let array = Value::new_object(Some( - &interpreter - .realm() - .environment - .get_global_object() - .expect("Could not get global object"), - )); - array.set_kind(ObjectKind::Array); - array.borrow().set_internal_slot( - INSTANCE_PROTOTYPE, - interpreter - .realm() - .environment - .get_binding_value("Array") - .borrow() - .get_field_slice(PROTOTYPE), - ); - array.borrow().set_field_slice("length", Value::from(0)); - Ok(array) -} - -/// Utility function for creating array objects. -/// -/// `array_obj` can be any array with prototype already set (it will be wiped and -/// recreated from `array_contents`) -pub fn construct_array(array_obj: &Value, array_contents: &[Value]) -> ResultValue { - let array_obj_ptr = array_obj.clone(); - - // Wipe existing contents of the array object - let orig_length = i32::from(&array_obj.get_field_slice("length")); - for n in 0..orig_length { - array_obj_ptr.remove_property(&n.to_string()); +/// JavaScript `Array` built-in implementation. +#[derive(Debug, Clone, Copy)] +pub(crate) struct Array; + +impl Array { + /// Creates a new `Array` instance. + pub(crate) fn new_array(interpreter: &Interpreter) -> ResultValue { + let array = Value::new_object(Some( + &interpreter + .realm() + .environment + .get_global_object() + .expect("Could not get global object"), + )); + array.set_kind(ObjectKind::Array); + array.borrow().set_internal_slot( + INSTANCE_PROTOTYPE, + interpreter + .realm() + .environment + .get_binding_value("Array") + .borrow() + .get_field(PROTOTYPE), + ); + array.borrow().set_field("length", Value::from(0)); + Ok(array) } - // Create length - let length = Property::new() - .value(Value::from(array_contents.len() as i32)) - .writable(true) - .configurable(false) - .enumerable(false); + /// Utility function for creating array objects. + /// + /// `array_obj` can be any array with prototype already set (it will be wiped and + /// recreated from `array_contents`) + pub(crate) fn construct_array(array_obj: &Value, array_contents: &[Value]) -> ResultValue { + let array_obj_ptr = array_obj.clone(); + + // Wipe existing contents of the array object + let orig_length = i32::from(&array_obj.get_field("length")); + for n in 0..orig_length { + array_obj_ptr.remove_property(&n.to_string()); + } + + // Create length + let length = Property::new() + .value(Value::from(array_contents.len() as i32)) + .writable(true) + .configurable(false) + .enumerable(false); - array_obj_ptr.set_property("length".to_string(), length); + array_obj_ptr.set_property("length".to_string(), length); - for (n, value) in array_contents.iter().enumerate() { - array_obj_ptr.set_field_slice(&n.to_string(), value.clone()); + for (n, value) in array_contents.iter().enumerate() { + array_obj_ptr.set_field(n.to_string(), value); + } + Ok(array_obj_ptr) } - Ok(array_obj_ptr) -} -/// Utility function which takes an existing array object and puts additional -/// values on the end, correctly rewriting the length -pub(crate) fn add_to_array_object(array_ptr: &Value, add_values: &[Value]) -> ResultValue { - let orig_length = i32::from(&array_ptr.get_field_slice("length")); + /// Utility function which takes an existing array object and puts additional + /// values on the end, correctly rewriting the length + pub(crate) fn add_to_array_object(array_ptr: &Value, add_values: &[Value]) -> ResultValue { + let orig_length = i32::from(&array_ptr.get_field("length")); - for (n, value) in add_values.iter().enumerate() { - let new_index = orig_length.wrapping_add(n as i32); - array_ptr.set_field_slice(&new_index.to_string(), value.clone()); - } + for (n, value) in add_values.iter().enumerate() { + let new_index = orig_length.wrapping_add(n as i32); + array_ptr.set_field(new_index.to_string(), value); + } - array_ptr.set_field_slice( - "length", - Value::from(orig_length.wrapping_add(add_values.len() as i32)), - ); + array_ptr.set_field( + "length", + Value::from(orig_length.wrapping_add(add_values.len() as i32)), + ); - Ok(array_ptr.clone()) -} + Ok(array_ptr.clone()) + } -/// Create a new array -pub fn make_array(this: &mut Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue { - // Make a new Object which will internally represent the Array (mapping - // between indices and values): this creates an Object with no prototype - - // Set Prototype - let prototype = ctx - .realm - .global_obj - .get_field_slice("Array") - .get_field_slice(PROTOTYPE); - - this.set_internal_slot(INSTANCE_PROTOTYPE, prototype); - // This value is used by console.log and other routines to match Object type - // to its Javascript Identifier (global constructor method name) - this.set_kind(ObjectKind::Array); - - // add our arguments in - let mut length = args.len() as i32; - match args.len() { - 1 if args[0].is_integer() => { - length = i32::from(&args[0]); - // TODO: It should not create an array of undefineds, but an empty array ("holy" array in V8) with length `n`. - for n in 0..length { - this.set_field_slice(&n.to_string(), Value::undefined()); + /// Create a new array + pub(crate) fn make_array( + this: &mut Value, + args: &[Value], + ctx: &mut Interpreter, + ) -> ResultValue { + // Make a new Object which will internally represent the Array (mapping + // between indices and values): this creates an Object with no prototype + + // Set Prototype + let prototype = ctx.realm.global_obj.get_field("Array").get_field(PROTOTYPE); + + this.set_internal_slot(INSTANCE_PROTOTYPE, prototype); + // This value is used by console.log and other routines to match Object type + // to its Javascript Identifier (global constructor method name) + this.set_kind(ObjectKind::Array); + + // add our arguments in + let mut length = args.len() as i32; + match args.len() { + 1 if args[0].is_integer() => { + length = i32::from(&args[0]); + // TODO: It should not create an array of undefineds, but an empty array ("holy" array in V8) with length `n`. + for n in 0..length { + this.set_field(n.to_string(), Value::undefined()); + } } - } - 1 if args[0].is_double() => { - // TODO: throw `RangeError`. - panic!("RangeError: invalid array length"); - } - _ => { - for (n, value) in args.iter().enumerate() { - this.set_field_slice(&n.to_string(), value.clone()); + 1 if args[0].is_double() => { + return Err(RangeError::run_new("invalid array length", ctx)); + } + _ => { + for (n, value) in args.iter().enumerate() { + this.set_field(n.to_string(), value.clone()); + } } } - } - // finally create length property - let length = Property::new() - .value(Value::from(length)) - .writable(true) - .configurable(false) - .enumerable(false); + // finally create length property + let length = Property::new() + .value(Value::from(length)) + .writable(true) + .configurable(false) + .enumerable(false); - this.set_property("length".to_string(), length); + this.set_property("length".to_string(), length); - Ok(this.clone()) -} + Ok(this.clone()) + } -/// `Array.isArray( arg )` -/// -/// The isArray function takes one argument arg, and returns the Boolean value true -/// if the argument is an object whose class internal property is "Array"; otherwise it returns false. -/// -/// More information: -/// - [ECMAScript reference][spec] -/// - [MDN documentation][mdn] -/// -/// [spec]: https://tc39.es/ecma262/#sec-array.isarray -/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/isArray -pub fn is_array(_this: &mut Value, args: &[Value], _interpreter: &mut Interpreter) -> ResultValue { - let value_true = Value::boolean(true); - let value_false = Value::boolean(false); - - match args.get(0) { - Some(arg) => { - match arg.data() { - // 1. - ValueData::Object(ref obj) => { - // 2. - if (*obj).deref().borrow().kind == ObjectKind::Array { - return Ok(value_true); + /// `Array.isArray( arg )` + /// + /// The isArray function takes one argument arg, and returns the Boolean value true + /// if the argument is an object whose class internal property is "Array"; otherwise it returns false. + /// + /// More information: + /// - [ECMAScript reference][spec] + /// - [MDN documentation][mdn] + /// + /// [spec]: https://tc39.es/ecma262/#sec-array.isarray + /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/isArray + pub(crate) fn is_array( + _this: &mut Value, + args: &[Value], + _interpreter: &mut Interpreter, + ) -> ResultValue { + let value_true = Value::boolean(true); + let value_false = Value::boolean(false); + + match args.get(0) { + Some(arg) => { + match arg.data() { + // 1. + ValueData::Object(ref obj) => { + // 2. + if (*obj).deref().borrow().kind == ObjectKind::Array { + return Ok(value_true); + } + Ok(value_false) } - Ok(value_false) + // 3. + _ => Ok(value_false), } - // 3. - _ => Ok(value_false), } + None => Ok(value_false), } - None => Ok(value_false), } -} -/// `Array.prototype.concat(...arguments)` -/// -/// When the concat method is called with zero or more arguments, it returns an -/// array containing the array elements of the object followed by the array -/// elements of each argument in order. -/// -/// More information: -/// - [ECMAScript reference][spec] -/// - [MDN documentation][mdn] -/// -/// [spec]: https://tc39.es/ecma262/#sec-array.prototype.concat -/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/concat -pub fn concat(this: &mut Value, args: &[Value], _: &mut Interpreter) -> ResultValue { - if args.is_empty() { - // If concat is called with no arguments, it returns the original array - return Ok(this.clone()); - } + /// `Array.prototype.concat(...arguments)` + /// + /// When the concat method is called with zero or more arguments, it returns an + /// array containing the array elements of the object followed by the array + /// elements of each argument in order. + /// + /// More information: + /// - [ECMAScript reference][spec] + /// - [MDN documentation][mdn] + /// + /// [spec]: https://tc39.es/ecma262/#sec-array.prototype.concat + /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/concat + pub(crate) fn concat(this: &mut Value, args: &[Value], _: &mut Interpreter) -> ResultValue { + if args.is_empty() { + // If concat is called with no arguments, it returns the original array + return Ok(this.clone()); + } - // Make a new array (using this object as the prototype basis for the new - // one) - let mut new_values: Vec = Vec::new(); + // Make a new array (using this object as the prototype basis for the new + // one) + let mut new_values: Vec = Vec::new(); - let this_length = i32::from(&this.get_field_slice("length")); - for n in 0..this_length { - new_values.push(this.get_field_slice(&n.to_string())); - } - - for concat_array in args { - let concat_length = i32::from(&concat_array.get_field_slice("length")); - for n in 0..concat_length { - new_values.push(concat_array.get_field_slice(&n.to_string())); + let this_length = i32::from(&this.get_field("length")); + for n in 0..this_length { + new_values.push(this.get_field(n.to_string())); } - } - construct_array(this, &new_values) -} + for concat_array in args { + let concat_length = i32::from(&concat_array.get_field("length")); + for n in 0..concat_length { + new_values.push(concat_array.get_field(n.to_string())); + } + } -/// `Array.prototype.push( ...items )` -/// -/// The arguments are appended to the end of the array, in the order in which -/// they appear. The new length of the array is returned as the result of the -/// call. -/// -/// More information: -/// - [ECMAScript reference][spec] -/// - [MDN documentation][mdn] -/// -/// [spec]: https://tc39.es/ecma262/#sec-array.prototype.push -/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/push -pub fn push(this: &mut Value, args: &[Value], _: &mut Interpreter) -> ResultValue { - let new_array = add_to_array_object(this, args)?; - Ok(new_array.get_field_slice("length")) -} + Self::construct_array(this, &new_values) + } -/// `Array.prototype.pop()` -/// -/// The last element of the array is removed from the array and returned. -/// -/// More information: -/// - [ECMAScript reference][spec] -/// - [MDN documentation][mdn] -/// -/// [spec]: https://tc39.es/ecma262/#sec-array.prototype.pop -/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/pop -pub fn pop(this: &mut Value, _: &[Value], _: &mut Interpreter) -> ResultValue { - let curr_length = i32::from(&this.get_field_slice("length")); - if curr_length < 1 { - return Ok(Value::undefined()); + /// `Array.prototype.push( ...items )` + /// + /// The arguments are appended to the end of the array, in the order in which + /// they appear. The new length of the array is returned as the result of the + /// call. + /// + /// More information: + /// - [ECMAScript reference][spec] + /// - [MDN documentation][mdn] + /// + /// [spec]: https://tc39.es/ecma262/#sec-array.prototype.push + /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/push + pub(crate) fn push(this: &mut Value, args: &[Value], _: &mut Interpreter) -> ResultValue { + let new_array = Self::add_to_array_object(this, args)?; + Ok(new_array.get_field("length")) } - let pop_index = curr_length.wrapping_sub(1); - let pop_value: Value = this.get_field_slice(&pop_index.to_string()); - this.remove_property(&pop_index.to_string()); - this.set_field_slice("length", Value::from(pop_index)); - Ok(pop_value) -} -/// `Array.prototype.forEach( callbackFn [ , thisArg ] )` -/// -/// This method executes the provided callback function for each element in the array. -/// -/// More information: -/// - [ECMAScript reference][spec] -/// - [MDN documentation][mdn] -/// -/// [spec]: https://tc39.es/ecma262/#sec-array.prototype.foreach -/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/forEach -pub fn for_each(this: &mut Value, args: &[Value], interpreter: &mut Interpreter) -> ResultValue { - if args.is_empty() { - return Err(Value::from("Missing argument for Array.prototype.forEach")); + /// `Array.prototype.pop()` + /// + /// The last element of the array is removed from the array and returned. + /// + /// More information: + /// - [ECMAScript reference][spec] + /// - [MDN documentation][mdn] + /// + /// [spec]: https://tc39.es/ecma262/#sec-array.prototype.pop + /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/pop + pub(crate) fn pop(this: &mut Value, _: &[Value], _: &mut Interpreter) -> ResultValue { + let curr_length = i32::from(&this.get_field("length")); + if curr_length < 1 { + return Ok(Value::undefined()); + } + let pop_index = curr_length.wrapping_sub(1); + let pop_value: Value = this.get_field(pop_index.to_string()); + this.remove_property(&pop_index.to_string()); + this.set_field("length", Value::from(pop_index)); + Ok(pop_value) } - let callback_arg = args.get(0).expect("Could not get `callbackFn` argument."); - let mut this_arg = args.get(1).cloned().unwrap_or_else(Value::undefined); + /// `Array.prototype.forEach( callbackFn [ , thisArg ] )` + /// + /// This method executes the provided callback function for each element in the array. + /// + /// More information: + /// - [ECMAScript reference][spec] + /// - [MDN documentation][mdn] + /// + /// [spec]: https://tc39.es/ecma262/#sec-array.prototype.foreach + /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/forEach + pub(crate) fn for_each( + this: &mut Value, + args: &[Value], + interpreter: &mut Interpreter, + ) -> ResultValue { + if args.is_empty() { + return Err(Value::from("Missing argument for Array.prototype.forEach")); + } - let length = i32::from(&this.get_field_slice("length")); + let callback_arg = args.get(0).expect("Could not get `callbackFn` argument."); + let mut this_arg = args.get(1).cloned().unwrap_or_else(Value::undefined); - for i in 0..length { - let element = this.get_field_slice(&i.to_string()); - let arguments = [element, Value::from(i), this.clone()]; + let length = i32::from(&this.get_field("length")); - interpreter.call(callback_arg, &mut this_arg, &arguments)?; - } + for i in 0..length { + let element = this.get_field(i.to_string()); + let arguments = [element, Value::from(i), this.clone()]; - Ok(Value::undefined()) -} + interpreter.call(callback_arg, &mut this_arg, &arguments)?; + } -/// `Array.prototype.join( separator )` -/// -/// The elements of the array are converted to Strings, and these Strings are -/// then concatenated, separated by occurrences of the separator. If no -/// separator is provided, a single comma is used as the separator. -/// -/// More information: -/// - [ECMAScript reference][spec] -/// - [MDN documentation][mdn] -/// -/// [spec]: https://tc39.es/ecma262/#sec-array.prototype.join -/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/join -pub fn join(this: &mut Value, args: &[Value], _: &mut Interpreter) -> ResultValue { - let separator = if args.is_empty() { - String::from(",") - } else { - args.get(0).expect("Could not get argument").to_string() - }; - - let mut elem_strs: Vec = Vec::new(); - let length = i32::from(&this.get_field_slice("length")); - for n in 0..length { - let elem_str: String = this.get_field_slice(&n.to_string()).to_string(); - elem_strs.push(elem_str); + Ok(Value::undefined()) } - Ok(Value::from(elem_strs.join(&separator))) -} + /// `Array.prototype.join( separator )` + /// + /// The elements of the array are converted to Strings, and these Strings are + /// then concatenated, separated by occurrences of the separator. If no + /// separator is provided, a single comma is used as the separator. + /// + /// More information: + /// - [ECMAScript reference][spec] + /// - [MDN documentation][mdn] + /// + /// [spec]: https://tc39.es/ecma262/#sec-array.prototype.join + /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/join + pub(crate) fn join(this: &mut Value, args: &[Value], _: &mut Interpreter) -> ResultValue { + let separator = if args.is_empty() { + String::from(",") + } else { + args.get(0).expect("Could not get argument").to_string() + }; + + let mut elem_strs: Vec = Vec::new(); + let length = i32::from(&this.get_field("length")); + for n in 0..length { + let elem_str: String = this.get_field(n.to_string()).to_string(); + elem_strs.push(elem_str); + } -/// `Array.prototype.toString( separator )` -/// -/// The toString function is intentionally generic; it does not require that -/// its this value be an Array object. Therefore it can be transferred to -/// other kinds of objects for use as a method. -/// -/// More information: -/// - [ECMAScript reference][spec] -/// - [MDN documentation][mdn] -/// -/// [spec]: https://tc39.es/ecma262/#sec-array.prototype.tostring -/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/toString -pub fn to_string(this: &mut Value, _args: &[Value], _ctx: &mut Interpreter) -> ResultValue { - let method_name = "join"; - let mut arguments = vec![Value::from(",")]; - // 2. - let mut method = this.get_field_slice(method_name); - // 3. - if !method.is_function() { - method = _ctx - .realm - .global_obj - .get_field_slice("Object") - .get_field_slice(PROTOTYPE) - .get_field_slice("toString"); - - arguments = Vec::new(); + Ok(Value::from(elem_strs.join(&separator))) } - // 4. - let join_result = _ctx.call(&method, this, &arguments); - let match_string = match join_result { - Ok(v) => match *v { - ValueData::String(ref s) => (*s).clone(), - _ => "".to_string(), - }, - Err(v) => format!("error: {}", v), - }; - Ok(Value::from(match_string)) -} -/// `Array.prototype.reverse()` -/// -/// The elements of the array are rearranged so as to reverse their order. -/// The object is returned as the result of the call. -/// -/// More information: -/// - [ECMAScript reference][spec] -/// - [MDN documentation][mdn] -/// -/// [spec]: https://tc39.es/ecma262/#sec-array.prototype.reverse -/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/reverse -#[allow(clippy::else_if_without_else)] -pub fn reverse(this: &mut Value, _: &[Value], _: &mut Interpreter) -> ResultValue { - let len = i32::from(&this.get_field_slice("length")); - let middle: i32 = len.wrapping_div(2); - - for lower in 0..middle { - let upper = len.wrapping_sub(lower).wrapping_sub(1); - - let upper_exists = this.has_field(&upper.to_string()); - let lower_exists = this.has_field(&lower.to_string()); - - let upper_value = this.get_field_slice(&upper.to_string()); - let lower_value = this.get_field_slice(&lower.to_string()); - - if upper_exists && lower_exists { - this.set_field_slice(&upper.to_string(), lower_value); - this.set_field_slice(&lower.to_string(), upper_value); - } else if upper_exists { - this.set_field_slice(&lower.to_string(), upper_value); - this.remove_property(&upper.to_string()); - } else if lower_exists { - this.set_field_slice(&upper.to_string(), lower_value); - this.remove_property(&lower.to_string()); + /// `Array.prototype.toString( separator )` + /// + /// The toString function is intentionally generic; it does not require that + /// its this value be an Array object. Therefore it can be transferred to + /// other kinds of objects for use as a method. + /// + /// More information: + /// - [ECMAScript reference][spec] + /// - [MDN documentation][mdn] + /// + /// [spec]: https://tc39.es/ecma262/#sec-array.prototype.tostring + /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/toString + #[allow(clippy::wrong_self_convention)] + pub(crate) fn to_string( + this: &mut Value, + _args: &[Value], + _ctx: &mut Interpreter, + ) -> ResultValue { + let method_name = "join"; + let mut arguments = vec![Value::from(",")]; + // 2. + let mut method = this.get_field(method_name); + // 3. + if !method.is_function() { + method = _ctx + .realm + .global_obj + .get_field("Object") + .get_field(PROTOTYPE) + .get_field("toString"); + + arguments = Vec::new(); } + // 4. + let join_result = _ctx.call(&method, this, &arguments); + let match_string = match join_result { + Ok(v) => match *v { + ValueData::String(ref s) => (*s).clone(), + _ => "".to_string(), + }, + Err(v) => format!("error: {}", v), + }; + Ok(Value::from(match_string)) } - Ok(this.clone()) -} + /// `Array.prototype.reverse()` + /// + /// The elements of the array are rearranged so as to reverse their order. + /// The object is returned as the result of the call. + /// + /// More information: + /// - [ECMAScript reference][spec] + /// - [MDN documentation][mdn] + /// + /// [spec]: https://tc39.es/ecma262/#sec-array.prototype.reverse + /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/reverse + #[allow(clippy::else_if_without_else)] + pub(crate) fn reverse(this: &mut Value, _: &[Value], _: &mut Interpreter) -> ResultValue { + let len = i32::from(&this.get_field("length")); + let middle: i32 = len.wrapping_div(2); + + for lower in 0..middle { + let upper = len.wrapping_sub(lower).wrapping_sub(1); + + let upper_exists = this.has_field(&upper.to_string()); + let lower_exists = this.has_field(&lower.to_string()); + + let upper_value = this.get_field(upper.to_string()); + let lower_value = this.get_field(lower.to_string()); + + if upper_exists && lower_exists { + this.set_field(upper.to_string(), lower_value); + this.set_field(lower.to_string(), upper_value); + } else if upper_exists { + this.set_field(lower.to_string(), upper_value); + this.remove_property(&upper.to_string()); + } else if lower_exists { + this.set_field(upper.to_string(), lower_value); + this.remove_property(&lower.to_string()); + } + } -/// `Array.prototype.shift()` -/// -/// The first element of the array is removed from the array and returned. -/// -/// More information: -/// - [ECMAScript reference][spec] -/// - [MDN documentation][mdn] -/// -/// [spec]: https://tc39.es/ecma262/#sec-array.prototype.shift -/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/shift -pub fn shift(this: &mut Value, _: &[Value], _: &mut Interpreter) -> ResultValue { - let len = i32::from(&this.get_field_slice("length")); - - if len == 0 { - this.set_field_slice("length", Value::from(0)); - // Since length is 0, this will be an Undefined value - return Ok(this.get_field_slice(&0.to_string())); + Ok(this.clone()) } - let first: Value = this.get_field_slice(&0.to_string()); - - for k in 1..len { - let from = k.to_string(); - let to = (k.wrapping_sub(1)).to_string(); - - let from_value = this.get_field_slice(&from); - if from_value.is_undefined() { - this.remove_property(&to); - } else { - this.set_field_slice(&to, from_value); + /// `Array.prototype.shift()` + /// + /// The first element of the array is removed from the array and returned. + /// + /// More information: + /// - [ECMAScript reference][spec] + /// - [MDN documentation][mdn] + /// + /// [spec]: https://tc39.es/ecma262/#sec-array.prototype.shift + /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/shift + pub(crate) fn shift(this: &mut Value, _: &[Value], _: &mut Interpreter) -> ResultValue { + let len = i32::from(&this.get_field("length")); + + if len == 0 { + this.set_field("length", Value::from(0)); + // Since length is 0, this will be an Undefined value + return Ok(this.get_field(0.to_string())); } - } - let final_index = len.wrapping_sub(1); - this.remove_property(&(final_index).to_string()); - this.set_field_slice("length", Value::from(final_index)); + let first: Value = this.get_field(0.to_string()); - Ok(first) -} + for k in 1..len { + let from = k.to_string(); + let to = (k.wrapping_sub(1)).to_string(); -/// `Array.prototype.unshift( ...items )` -/// -/// The arguments are prepended to the start of the array, such that their order -/// within the array is the same as the order in which they appear in the -/// argument list. -/// -/// More information: -/// - [ECMAScript reference][spec] -/// - [MDN documentation][mdn] -/// -/// [spec]: https://tc39.es/ecma262/#sec-array.prototype.unshift -/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/unshift -pub fn unshift(this: &mut Value, args: &[Value], _: &mut Interpreter) -> ResultValue { - let len = i32::from(&this.get_field_slice("length")); - let arg_c: i32 = args.len() as i32; - - if arg_c > 0 { - for k in (1..=len).rev() { - let from = (k.wrapping_sub(1)).to_string(); - let to = (k.wrapping_add(arg_c).wrapping_sub(1)).to_string(); - - let from_value = this.get_field_slice(&from); + let from_value = this.get_field(from); if from_value.is_undefined() { this.remove_property(&to); } else { - this.set_field_slice(&to, from_value); + this.set_field(to, from_value); } } - for j in 0..arg_c { - this.set_field_slice( - &j.to_string(), - args.get(j as usize) - .expect("Could not get argument") - .clone(), - ); - } - } - let temp = len.wrapping_add(arg_c); - this.set_field_slice("length", Value::from(temp)); - Ok(Value::from(temp)) -} + let final_index = len.wrapping_sub(1); + this.remove_property(&(final_index).to_string()); + this.set_field("length", Value::from(final_index)); -/// `Array.prototype.every( callback, [ thisArg ] )` -/// -/// The every method executes the provided callback function once for each -/// element present in the array until it finds the one where callback returns -/// a falsy value. It returns `false` if it finds such element, otherwise it -/// returns `true`. -/// -/// More information: -/// - [ECMAScript reference][spec] -/// - [MDN documentation][mdn] -/// -/// [spec]: https://tc39.es/ecma262/#sec-array.prototype.every -/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/every -pub fn every(this: &mut Value, args: &[Value], interpreter: &mut Interpreter) -> ResultValue { - if args.is_empty() { - return Err(Value::from( - "missing callback when calling function Array.prototype.every", - )); + Ok(first) } - let callback = &args[0]; - let mut this_arg = if args.len() > 1 { - args[1].clone() - } else { - Value::undefined() - }; - let mut i = 0; - let max_len = i32::from(&this.get_field_slice("length")); - let mut len = max_len; - while i < len { - let element = this.get_field_slice(&i.to_string()); - let arguments = [element, Value::from(i), this.clone()]; - let result = interpreter - .call(callback, &mut this_arg, &arguments)? - .is_true(); - if !result { - return Ok(Value::from(false)); - } - len = min(max_len, i32::from(&this.get_field_slice("length"))); - i += 1; + + /// `Array.prototype.unshift( ...items )` + /// + /// The arguments are prepended to the start of the array, such that their order + /// within the array is the same as the order in which they appear in the + /// argument list. + /// + /// More information: + /// - [ECMAScript reference][spec] + /// - [MDN documentation][mdn] + /// + /// [spec]: https://tc39.es/ecma262/#sec-array.prototype.unshift + /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/unshift + pub(crate) fn unshift(this: &mut Value, args: &[Value], _: &mut Interpreter) -> ResultValue { + let len = i32::from(&this.get_field("length")); + let arg_c: i32 = args.len() as i32; + + if arg_c > 0 { + for k in (1..=len).rev() { + let from = (k.wrapping_sub(1)).to_string(); + let to = (k.wrapping_add(arg_c).wrapping_sub(1)).to_string(); + + let from_value = this.get_field(from); + if from_value.is_undefined() { + this.remove_property(&to); + } else { + this.set_field(to, from_value); + } + } + for j in 0..arg_c { + this.set_field( + j.to_string(), + args.get(j as usize) + .expect("Could not get argument") + .clone(), + ); + } + } + + let temp = len.wrapping_add(arg_c); + this.set_field("length", Value::from(temp)); + Ok(Value::from(temp)) } - Ok(Value::from(true)) -} -/// `Array.prototype.map( callback, [ thisArg ] )` -/// -/// For each element in the array the callback function is called, and a new -/// array is constructed from the return values of these calls. -/// -/// More information: -/// - [ECMAScript reference][spec] -/// - [MDN documentation][mdn] -/// -/// [spec]: https://tc39.es/ecma262/#sec-array.prototype.map -/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map -pub fn map(this: &mut Value, args: &[Value], interpreter: &mut Interpreter) -> ResultValue { - if args.is_empty() { - return Err(Value::from( - "missing argument 0 when calling function Array.prototype.map", - )); + /// `Array.prototype.every( callback, [ thisArg ] )` + /// + /// The every method executes the provided callback function once for each + /// element present in the array until it finds the one where callback returns + /// a falsy value. It returns `false` if it finds such element, otherwise it + /// returns `true`. + /// + /// More information: + /// - [ECMAScript reference][spec] + /// - [MDN documentation][mdn] + /// + /// [spec]: https://tc39.es/ecma262/#sec-array.prototype.every + /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/every + pub(crate) fn every( + this: &mut Value, + args: &[Value], + interpreter: &mut Interpreter, + ) -> ResultValue { + if args.is_empty() { + return Err(Value::from( + "missing callback when calling function Array.prototype.every", + )); + } + let callback = &args[0]; + let mut this_arg = if args.len() > 1 { + args[1].clone() + } else { + Value::undefined() + }; + let mut i = 0; + let max_len = i32::from(&this.get_field("length")); + let mut len = max_len; + while i < len { + let element = this.get_field(i.to_string()); + let arguments = [element, Value::from(i), this.clone()]; + let result = interpreter + .call(callback, &mut this_arg, &arguments)? + .is_true(); + if !result { + return Ok(Value::from(false)); + } + len = min(max_len, i32::from(&this.get_field("length"))); + i += 1; + } + Ok(Value::from(true)) } - let callback = args.get(0).cloned().unwrap_or_else(Value::undefined); - let mut this_val = args.get(1).cloned().unwrap_or_else(Value::undefined); + /// `Array.prototype.map( callback, [ thisArg ] )` + /// + /// For each element in the array the callback function is called, and a new + /// array is constructed from the return values of these calls. + /// + /// More information: + /// - [ECMAScript reference][spec] + /// - [MDN documentation][mdn] + /// + /// [spec]: https://tc39.es/ecma262/#sec-array.prototype.map + /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map + pub(crate) fn map( + this: &mut Value, + args: &[Value], + interpreter: &mut Interpreter, + ) -> ResultValue { + if args.is_empty() { + return Err(Value::from( + "missing argument 0 when calling function Array.prototype.map", + )); + } - let length = i32::from(&this.get_field_slice("length")); + let callback = args.get(0).cloned().unwrap_or_else(Value::undefined); + let mut this_val = args.get(1).cloned().unwrap_or_else(Value::undefined); - let new = new_array(interpreter)?; + let length = i32::from(&this.get_field("length")); - let values: Vec = (0..length) - .map(|idx| { - let element = this.get_field_slice(&idx.to_string()); - let args = [element, Value::from(idx), new.clone()]; + let new = Self::new_array(interpreter)?; - interpreter - .call(&callback, &mut this_val, &args) - .unwrap_or_else(|_| Value::undefined()) - }) - .collect(); + let values: Vec = (0..length) + .map(|idx| { + let element = this.get_field(idx.to_string()); + let args = [element, Value::from(idx), new.clone()]; - construct_array(&new, &values) -} + interpreter + .call(&callback, &mut this_val, &args) + .unwrap_or_else(|_| Value::undefined()) + }) + .collect(); -/// `Array.prototype.indexOf( searchElement[, fromIndex ] )` -/// -/// -/// indexOf compares searchElement to the elements of the array, in ascending order, -/// using the Strict Equality Comparison algorithm, and if found at one or more indices, -/// returns the smallest such index; otherwise, -1 is returned. -/// -/// The optional second argument fromIndex defaults to 0 (i.e. the whole array is searched). -/// If it is greater than or equal to the length of the array, -1 is returned, -/// i.e. the array will not be searched. If it is negative, it is used as the offset -/// from the end of the array to compute fromIndex. If the computed index is less than 0, -/// the whole array will be searched. -/// -/// More information: -/// - [ECMAScript reference][spec] -/// - [MDN documentation][mdn] -/// -/// [spec]: https://tc39.es/ecma262/#sec-array.prototype.indexof -/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/indexOf -pub fn index_of(this: &mut Value, args: &[Value], _: &mut Interpreter) -> ResultValue { - // If no arguments, return -1. Not described in spec, but is what chrome does. - if args.is_empty() { - return Ok(Value::from(-1)); + Self::construct_array(&new, &values) } - let search_element = args[0].clone(); - let len = i32::from(&this.get_field_slice("length")); + /// `Array.prototype.indexOf( searchElement[, fromIndex ] )` + /// + /// + /// indexOf compares searchElement to the elements of the array, in ascending order, + /// using the Strict Equality Comparison algorithm, and if found at one or more indices, + /// returns the smallest such index; otherwise, -1 is returned. + /// + /// The optional second argument fromIndex defaults to 0 (i.e. the whole array is searched). + /// If it is greater than or equal to the length of the array, -1 is returned, + /// i.e. the array will not be searched. If it is negative, it is used as the offset + /// from the end of the array to compute fromIndex. If the computed index is less than 0, + /// the whole array will be searched. + /// + /// More information: + /// - [ECMAScript reference][spec] + /// - [MDN documentation][mdn] + /// + /// [spec]: https://tc39.es/ecma262/#sec-array.prototype.indexof + /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/indexOf + pub(crate) fn index_of(this: &mut Value, args: &[Value], _: &mut Interpreter) -> ResultValue { + // If no arguments, return -1. Not described in spec, but is what chrome does. + if args.is_empty() { + return Ok(Value::from(-1)); + } - let mut idx = match args.get(1) { - Some(from_idx_ptr) => { - let from_idx = i32::from(from_idx_ptr); + let search_element = args[0].clone(); + let len = i32::from(&this.get_field("length")); - if from_idx < 0 { - len + from_idx - } else { - from_idx + let mut idx = match args.get(1) { + Some(from_idx_ptr) => { + let from_idx = i32::from(from_idx_ptr); + + if from_idx < 0 { + len + from_idx + } else { + from_idx + } } - } - None => 0, - }; + None => 0, + }; - while idx < len { - let check_element = this.get_field_slice(&idx.to_string()).clone(); + while idx < len { + let check_element = this.get_field(idx.to_string()).clone(); - if check_element.strict_equals(&search_element) { - return Ok(Value::from(idx)); + if check_element.strict_equals(&search_element) { + return Ok(Value::from(idx)); + } + + idx += 1; } - idx += 1; + Ok(Value::from(-1)) } - Ok(Value::from(-1)) -} - -/// `Array.prototype.lastIndexOf( searchElement[, fromIndex ] )` -/// -/// -/// lastIndexOf compares searchElement to the elements of the array in descending order -/// using the Strict Equality Comparison algorithm, and if found at one or more indices, -/// returns the largest such index; otherwise, -1 is returned. -/// -/// The optional second argument fromIndex defaults to the array's length minus one -/// (i.e. the whole array is searched). If it is greater than or equal to the length of the array, -/// the whole array will be searched. If it is negative, it is used as the offset from the end -/// of the array to compute fromIndex. If the computed index is less than 0, -1 is returned. -/// -/// More information: -/// - [ECMAScript reference][spec] -/// - [MDN documentation][mdn] -/// -/// [spec]: https://tc39.es/ecma262/#sec-array.prototype.lastindexof -/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/lastIndexOf -pub fn last_index_of(this: &mut Value, args: &[Value], _: &mut Interpreter) -> ResultValue { - // If no arguments, return -1. Not described in spec, but is what chrome does. - if args.is_empty() { - return Ok(Value::from(-1)); - } + /// `Array.prototype.lastIndexOf( searchElement[, fromIndex ] )` + /// + /// + /// lastIndexOf compares searchElement to the elements of the array in descending order + /// using the Strict Equality Comparison algorithm, and if found at one or more indices, + /// returns the largest such index; otherwise, -1 is returned. + /// + /// The optional second argument fromIndex defaults to the array's length minus one + /// (i.e. the whole array is searched). If it is greater than or equal to the length of the array, + /// the whole array will be searched. If it is negative, it is used as the offset from the end + /// of the array to compute fromIndex. If the computed index is less than 0, -1 is returned. + /// + /// More information: + /// - [ECMAScript reference][spec] + /// - [MDN documentation][mdn] + /// + /// [spec]: https://tc39.es/ecma262/#sec-array.prototype.lastindexof + /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/lastIndexOf + pub(crate) fn last_index_of( + this: &mut Value, + args: &[Value], + _: &mut Interpreter, + ) -> ResultValue { + // If no arguments, return -1. Not described in spec, but is what chrome does. + if args.is_empty() { + return Ok(Value::from(-1)); + } - let search_element = args[0].clone(); - let len = i32::from(&this.get_field_slice("length")); + let search_element = args[0].clone(); + let len = i32::from(&this.get_field("length")); - let mut idx = match args.get(1) { - Some(from_idx_ptr) => { - let from_idx = i32::from(from_idx_ptr); + let mut idx = match args.get(1) { + Some(from_idx_ptr) => { + let from_idx = i32::from(from_idx_ptr); - if from_idx >= 0 { - min(from_idx, len - 1) - } else { - len + from_idx + if from_idx >= 0 { + min(from_idx, len - 1) + } else { + len + from_idx + } } - } - None => len - 1, - }; + None => len - 1, + }; - while idx >= 0 { - let check_element = this.get_field_slice(&idx.to_string()).clone(); + while idx >= 0 { + let check_element = this.get_field(idx.to_string()).clone(); - if check_element.strict_equals(&search_element) { - return Ok(Value::from(idx)); + if check_element.strict_equals(&search_element) { + return Ok(Value::from(idx)); + } + + idx -= 1; } - idx -= 1; + Ok(Value::from(-1)) } - Ok(Value::from(-1)) -} - -/// `Array.prototype.find( callback, [thisArg] )` -/// -/// The find method executes the callback function once for each index of the array -/// until the callback returns a truthy value. If so, find immediately returns the value -/// of that element. Otherwise, find returns undefined. -/// -/// More information: -/// - [ECMAScript reference][spec] -/// - [MDN documentation][mdn] -/// -/// [spec]: https://tc39.es/ecma262/#sec-array.prototype.find -/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/find -pub fn find(this: &mut Value, args: &[Value], interpreter: &mut Interpreter) -> ResultValue { - if args.is_empty() { - return Err(Value::from( - "missing callback when calling function Array.prototype.find", - )); - } - let callback = &args[0]; - let mut this_arg = if args.len() > 1 { - args[1].clone() - } else { - Value::undefined() - }; - let len = i32::from(&this.get_field_slice("length")); - for i in 0..len { - let element = this.get_field_slice(&i.to_string()); - let arguments = [element.clone(), Value::from(i), this.clone()]; - let result = interpreter.call(callback, &mut this_arg, &arguments)?; - if result.is_true() { - return Ok(element); + /// `Array.prototype.find( callback, [thisArg] )` + /// + /// The find method executes the callback function once for each index of the array + /// until the callback returns a truthy value. If so, find immediately returns the value + /// of that element. Otherwise, find returns undefined. + /// + /// More information: + /// - [ECMAScript reference][spec] + /// - [MDN documentation][mdn] + /// + /// [spec]: https://tc39.es/ecma262/#sec-array.prototype.find + /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/find + pub(crate) fn find( + this: &mut Value, + args: &[Value], + interpreter: &mut Interpreter, + ) -> ResultValue { + if args.is_empty() { + return Err(Value::from( + "missing callback when calling function Array.prototype.find", + )); + } + let callback = &args[0]; + let mut this_arg = if args.len() > 1 { + args[1].clone() + } else { + Value::undefined() + }; + let len = i32::from(&this.get_field("length")); + for i in 0..len { + let element = this.get_field(i.to_string()); + let arguments = [element.clone(), Value::from(i), this.clone()]; + let result = interpreter.call(callback, &mut this_arg, &arguments)?; + if result.is_true() { + return Ok(element); + } } + Ok(Value::undefined()) } - Ok(Value::undefined()) -} -/// `Array.prototype.findIndex( predicate [ , thisArg ] )` -/// -/// This method executes the provided predicate function for each element of the array. -/// If the predicate function returns `true` for an element, this method returns the index of the element. -/// If all elements return `false`, the value `-1` is returned. -/// -/// More information: -/// - [ECMAScript reference][spec] -/// - [MDN documentation][mdn] -/// -/// [spec]: https://tc39.es/ecma262/#sec-array.prototype.findindex -/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/findIndex -pub fn find_index(this: &mut Value, args: &[Value], interpreter: &mut Interpreter) -> ResultValue { - if args.is_empty() { - return Err(Value::from( - "Missing argument for Array.prototype.findIndex", - )); - } + /// `Array.prototype.findIndex( predicate [ , thisArg ] )` + /// + /// This method executes the provided predicate function for each element of the array. + /// If the predicate function returns `true` for an element, this method returns the index of the element. + /// If all elements return `false`, the value `-1` is returned. + /// + /// More information: + /// - [ECMAScript reference][spec] + /// - [MDN documentation][mdn] + /// + /// [spec]: https://tc39.es/ecma262/#sec-array.prototype.findindex + /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/findIndex + pub(crate) fn find_index( + this: &mut Value, + args: &[Value], + interpreter: &mut Interpreter, + ) -> ResultValue { + if args.is_empty() { + return Err(Value::from( + "Missing argument for Array.prototype.findIndex", + )); + } - let predicate_arg = args.get(0).expect("Could not get `predicate` argument."); + let predicate_arg = args.get(0).expect("Could not get `predicate` argument."); - let mut this_arg = args.get(1).cloned().unwrap_or_else(Value::undefined); + let mut this_arg = args.get(1).cloned().unwrap_or_else(Value::undefined); - let length = i32::from(&this.get_field_slice("length")); + let length = i32::from(&this.get_field("length")); - for i in 0..length { - let element = this.get_field_slice(&i.to_string()); - let arguments = [element, Value::from(i), this.clone()]; + for i in 0..length { + let element = this.get_field(i.to_string()); + let arguments = [element, Value::from(i), this.clone()]; - let result = interpreter.call(predicate_arg, &mut this_arg, &arguments)?; + let result = interpreter.call(predicate_arg, &mut this_arg, &arguments)?; - if result.is_true() { - return Ok(Value::rational(f64::from(i))); + if result.is_true() { + return Ok(Value::rational(f64::from(i))); + } } - } - - Ok(Value::rational(-1_f64)) -} -/// `Array.prototype.fill( value[, start[, end]] )` -/// -/// The method fills (modifies) all the elements of an array from start index (default 0) -/// to an end index (default array length) with a static value. It returns the modified array. -/// -/// More information: -/// - [ECMAScript reference][spec] -/// - [MDN documentation][mdn] -/// -/// [spec]: https://tc39.es/ecma262/#sec-array.prototype.fill -/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/fill -pub fn fill(this: &mut Value, args: &[Value], _: &mut Interpreter) -> ResultValue { - let len: i32 = i32::from(&this.get_field_slice("length")); - let default_value = Value::undefined(); - let value = args.get(0).unwrap_or(&default_value); - let relative_start = args.get(1).unwrap_or(&default_value).to_number() as i32; - let relative_end_val = args.get(2).unwrap_or(&default_value); - let relative_end = if relative_end_val.is_undefined() { - len - } else { - relative_end_val.to_number() as i32 - }; - let start = if relative_start < 0 { - max(len + relative_start, 0) - } else { - min(relative_start, len) - }; - let fin = if relative_end < 0 { - max(len + relative_end, 0) - } else { - min(relative_end, len) - }; - - for i in start..fin { - this.set_field_slice(&i.to_string(), value.clone()); + Ok(Value::rational(-1_f64)) } - Ok(this.clone()) -} - -/// `Array.prototype.includes( valueToFind [, fromIndex] )` -/// -/// Determines whether an array includes a certain value among its entries, returning `true` or `false` as appropriate. -/// -/// More information: -/// - [ECMAScript reference][spec] -/// - [MDN documentation][mdn] -/// -/// [spec]: https://tc39.es/ecma262/#sec-array.prototype.includes -/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/includes -pub fn includes_value(this: &mut Value, args: &[Value], _: &mut Interpreter) -> ResultValue { - let search_element = args.get(0).cloned().unwrap_or_else(Value::undefined); - - let length = i32::from(&this.get_field_slice("length")); - - for idx in 0..length { - let check_element = this.get_field_slice(&idx.to_string()).clone(); + /// `Array.prototype.fill( value[, start[, end]] )` + /// + /// The method fills (modifies) all the elements of an array from start index (default 0) + /// to an end index (default array length) with a static value. It returns the modified array. + /// + /// More information: + /// - [ECMAScript reference][spec] + /// - [MDN documentation][mdn] + /// + /// [spec]: https://tc39.es/ecma262/#sec-array.prototype.fill + /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/fill + pub(crate) fn fill(this: &mut Value, args: &[Value], _: &mut Interpreter) -> ResultValue { + let len: i32 = i32::from(&this.get_field("length")); + let default_value = Value::undefined(); + let value = args.get(0).unwrap_or(&default_value); + let relative_start = args.get(1).unwrap_or(&default_value).to_number() as i32; + let relative_end_val = args.get(2).unwrap_or(&default_value); + let relative_end = if relative_end_val.is_undefined() { + len + } else { + relative_end_val.to_number() as i32 + }; + let start = if relative_start < 0 { + max(len + relative_start, 0) + } else { + min(relative_start, len) + }; + let fin = if relative_end < 0 { + max(len + relative_end, 0) + } else { + min(relative_end, len) + }; - if same_value_zero(&check_element, &search_element) { - return Ok(Value::from(true)); + for i in start..fin { + this.set_field(i.to_string(), value.clone()); } + + Ok(this.clone()) } - Ok(Value::from(false)) -} + /// `Array.prototype.includes( valueToFind [, fromIndex] )` + /// + /// Determines whether an array includes a certain value among its entries, returning `true` or `false` as appropriate. + /// + /// More information: + /// - [ECMAScript reference][spec] + /// - [MDN documentation][mdn] + /// + /// [spec]: https://tc39.es/ecma262/#sec-array.prototype.includes + /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/includes + pub(crate) fn includes_value( + this: &mut Value, + args: &[Value], + _: &mut Interpreter, + ) -> ResultValue { + let search_element = args.get(0).cloned().unwrap_or_else(Value::undefined); + + let length = i32::from(&this.get_field("length")); + + for idx in 0..length { + let check_element = this.get_field(idx.to_string()).clone(); + + if same_value_zero(&check_element, &search_element) { + return Ok(Value::from(true)); + } + } -/// `Array.prototype.slice( [begin[, end]] )` -/// -/// The slice method takes two arguments, start and end, and returns an array containing the -/// elements of the array from element start up to, but not including, element end (or through the -/// end of the array if end is undefined). If start is negative, it is treated as length + start -/// where length is the length of the array. If end is negative, it is treated as length + end where -/// length is the length of the array. -/// -/// More information: -/// - [ECMAScript reference][spec] -/// - [MDN documentation][mdn] -/// -/// [spec]: https://tc39.es/ecma262/#sec-array.prototype.slice -/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/slice -pub fn slice(this: &mut Value, args: &[Value], interpreter: &mut Interpreter) -> ResultValue { - let new_array = new_array(interpreter)?; - let len = i32::from(&this.get_field_slice("length")); - - let start = match args.get(0) { - Some(v) => i32::from(v), - None => 0, - }; - let end = match args.get(1) { - Some(v) => i32::from(v), - None => len, - }; - - let from = if start < 0 { - max(len.wrapping_add(start), 0) - } else { - min(start, len) - }; - let to = if end < 0 { - max(len.wrapping_add(end), 0) - } else { - min(end, len) - }; - - let span = max(to.wrapping_sub(from), 0); - let mut new_array_len: i32 = 0; - for i in from..from.wrapping_add(span) { - new_array.set_field_slice( - &new_array_len.to_string(), - this.get_field_slice(&i.to_string()), - ); - new_array_len = new_array_len.wrapping_add(1); + Ok(Value::from(false)) } - new_array.set_field_slice("length", Value::from(new_array_len)); - Ok(new_array) -} -/// `Array.prototype.filter( callback, [ thisArg ] )` -/// -/// For each element in the array the callback function is called, and a new -/// array is constructed for every value whose callback returned a truthy value. -/// -/// More information: -/// - [ECMAScript reference][spec] -/// - [MDN documentation][mdn] -/// -/// [spec]: https://tc39.es/ecma262/#sec-array.prototype.filter -/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/filter -pub fn filter(this: &mut Value, args: &[Value], interpreter: &mut Interpreter) -> ResultValue { - if args.is_empty() { - return Err(Value::from( - "missing argument 0 when calling function Array.prototype.filter", - )); + /// `Array.prototype.slice( [begin[, end]] )` + /// + /// The slice method takes two arguments, start and end, and returns an array containing the + /// elements of the array from element start up to, but not including, element end (or through the + /// end of the array if end is undefined). If start is negative, it is treated as length + start + /// where length is the length of the array. If end is negative, it is treated as length + end where + /// length is the length of the array. + /// + /// More information: + /// - [ECMAScript reference][spec] + /// - [MDN documentation][mdn] + /// + /// [spec]: https://tc39.es/ecma262/#sec-array.prototype.slice + /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/slice + pub(crate) fn slice( + this: &mut Value, + args: &[Value], + interpreter: &mut Interpreter, + ) -> ResultValue { + let new_array = Self::new_array(interpreter)?; + let len = i32::from(&this.get_field("length")); + + let start = match args.get(0) { + Some(v) => i32::from(v), + None => 0, + }; + let end = match args.get(1) { + Some(v) => i32::from(v), + None => len, + }; + + let from = if start < 0 { + max(len.wrapping_add(start), 0) + } else { + min(start, len) + }; + let to = if end < 0 { + max(len.wrapping_add(end), 0) + } else { + min(end, len) + }; + + let span = max(to.wrapping_sub(from), 0); + let mut new_array_len: i32 = 0; + for i in from..from.wrapping_add(span) { + new_array.set_field(new_array_len.to_string(), this.get_field(i.to_string())); + new_array_len = new_array_len.wrapping_add(1); + } + new_array.set_field("length", Value::from(new_array_len)); + Ok(new_array) } - let callback = args.get(0).cloned().unwrap_or_else(Value::undefined); - let mut this_val = args.get(1).cloned().unwrap_or_else(Value::undefined); + /// `Array.prototype.filter( callback, [ thisArg ] )` + /// + /// For each element in the array the callback function is called, and a new + /// array is constructed for every value whose callback returned a truthy value. + /// + /// More information: + /// - [ECMAScript reference][spec] + /// - [MDN documentation][mdn] + /// + /// [spec]: https://tc39.es/ecma262/#sec-array.prototype.filter + /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/filter + pub(crate) fn filter( + this: &mut Value, + args: &[Value], + interpreter: &mut Interpreter, + ) -> ResultValue { + if args.is_empty() { + return Err(Value::from( + "missing argument 0 when calling function Array.prototype.filter", + )); + } - let length = i32::from(&this.get_field_slice("length")); + let callback = args.get(0).cloned().unwrap_or_else(Value::undefined); + let mut this_val = args.get(1).cloned().unwrap_or_else(Value::undefined); - let new = new_array(interpreter)?; + let length = i32::from(&this.get_field("length")); - let values = (0..length) - .filter_map(|idx| { - let element = this.get_field_slice(&idx.to_string()); + let new = Self::new_array(interpreter)?; - let args = [element.clone(), Value::from(idx), new.clone()]; + let values = (0..length) + .filter_map(|idx| { + let element = this.get_field(idx.to_string()); - let callback_result = interpreter - .call(&callback, &mut this_val, &args) - .unwrap_or_else(|_| Value::undefined()); + let args = [element.clone(), Value::from(idx), new.clone()]; - if callback_result.is_true() { - Some(element) - } else { - None - } - }) - .collect::>(); + let callback_result = interpreter + .call(&callback, &mut this_val, &args) + .unwrap_or_else(|_| Value::undefined()); - construct_array(&new, &values) -} + if callback_result.is_true() { + Some(element) + } else { + None + } + }) + .collect::>(); -/// Array.prototype.some ( callbackfn [ , thisArg ] ) -/// -/// The some method tests whether at least one element in the array passes -/// the test implemented by the provided callback function. It returns a Boolean value, -/// true if the callback function returns a truthy value for at least one element -/// in the array. Otherwise, false. -/// -/// Caution: Calling this method on an empty array returns false for any condition! -/// -/// More information: -/// - [ECMAScript reference][spec] -/// - [MDN documentation][mdn] -/// -/// [spec]: https://tc39.es/ecma262/#sec-array.prototype.some -/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/some -pub fn some(this: &mut Value, args: &[Value], interpreter: &mut Interpreter) -> ResultValue { - if args.is_empty() { - return Err(Value::from( - "missing callback when calling function Array.prototype.some", - )); + Self::construct_array(&new, &values) } - let callback = &args[0]; - let mut this_arg = if args.len() > 1 { - args[1].clone() - } else { - Value::undefined() - }; - let mut i = 0; - let max_len = i32::from(&this.get_field_slice("length")); - let mut len = max_len; - while i < len { - let element = this.get_field_slice(&i.to_string()); - let arguments = [element, Value::from(i), this.clone()]; - let result = interpreter - .call(callback, &mut this_arg, &arguments)? - .is_true(); - if result { - return Ok(Value::from(true)); - } - // the length of the array must be updated because the callback can mutate it. - len = min(max_len, i32::from(&this.get_field_slice("length"))); - i += 1; + + /// Array.prototype.some ( callbackfn [ , thisArg ] ) + /// + /// The some method tests whether at least one element in the array passes + /// the test implemented by the provided callback function. It returns a Boolean value, + /// true if the callback function returns a truthy value for at least one element + /// in the array. Otherwise, false. + /// + /// Caution: Calling this method on an empty array returns false for any condition! + /// + /// More information: + /// - [ECMAScript reference][spec] + /// - [MDN documentation][mdn] + /// + /// [spec]: https://tc39.es/ecma262/#sec-array.prototype.some + /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/some + pub(crate) fn some( + this: &mut Value, + args: &[Value], + interpreter: &mut Interpreter, + ) -> ResultValue { + if args.is_empty() { + return Err(Value::from( + "missing callback when calling function Array.prototype.some", + )); + } + let callback = &args[0]; + let mut this_arg = if args.len() > 1 { + args[1].clone() + } else { + Value::undefined() + }; + let mut i = 0; + let max_len = i32::from(&this.get_field("length")); + let mut len = max_len; + while i < len { + let element = this.get_field(i.to_string()); + let arguments = [element, Value::from(i), this.clone()]; + let result = interpreter + .call(callback, &mut this_arg, &arguments)? + .is_true(); + if result { + return Ok(Value::from(true)); + } + // the length of the array must be updated because the callback can mutate it. + len = min(max_len, i32::from(&this.get_field("length"))); + i += 1; + } + Ok(Value::from(false)) } - Ok(Value::from(false)) -} -/// Create a new `Array` object. -pub fn create(global: &Value) -> Value { - // Create prototype - let prototype = Value::new_object(None); - let length = Property::default().value(Value::from(0)); - - prototype.set_property_slice("length", length); - - make_builtin_fn!(concat, named "concat", with length 1, of prototype); - make_builtin_fn!(push, named "push", with length 1, of prototype); - make_builtin_fn!(index_of, named "indexOf", with length 1, of prototype); - make_builtin_fn!(last_index_of, named "lastIndexOf", with length 1, of prototype); - make_builtin_fn!(includes_value, named "includes", with length 1, of prototype); - make_builtin_fn!(map, named "map", with length 1, of prototype); - make_builtin_fn!(fill, named "fill", with length 1, of prototype); - make_builtin_fn!(for_each, named "forEach", with length 1, of prototype); - make_builtin_fn!(filter, named "filter", with length 1, of prototype); - make_builtin_fn!(pop, named "pop", of prototype); - make_builtin_fn!(join, named "join", with length 1, of prototype); - make_builtin_fn!(to_string, named "toString", of prototype); - make_builtin_fn!(reverse, named "reverse", of prototype); - make_builtin_fn!(shift, named "shift", of prototype); - make_builtin_fn!(unshift, named "unshift", with length 1, of prototype); - make_builtin_fn!(every, named "every", with length 1, of prototype); - make_builtin_fn!(find, named "find", with length 1, of prototype); - make_builtin_fn!(find_index, named "findIndex", with length 1, of prototype); - make_builtin_fn!(slice, named "slice", with length 2, of prototype); - make_builtin_fn!(some, named "some", with length 2, of prototype); - - let array = make_constructor_fn(make_array, global, prototype); - - // Static Methods - make_builtin_fn!(is_array, named "isArray", with length 1, of array); - - array -} + /// Create a new `Array` object. + pub(crate) fn create(global: &Value) -> Value { + // Create prototype + let prototype = Value::new_object(None); + let length = Property::default().value(Value::from(0)); + + prototype.set_property_slice("length", length); + + make_builtin_fn(Self::concat, "concat", &prototype, 1); + make_builtin_fn(Self::push, "push", &prototype, 1); + make_builtin_fn(Self::index_of, "indexOf", &prototype, 1); + make_builtin_fn(Self::last_index_of, "lastIndexOf", &prototype, 1); + make_builtin_fn(Self::includes_value, "includes", &prototype, 1); + make_builtin_fn(Self::map, "map", &prototype, 1); + make_builtin_fn(Self::fill, "fill", &prototype, 1); + make_builtin_fn(Self::for_each, "forEach", &prototype, 1); + make_builtin_fn(Self::filter, "filter", &prototype, 1); + make_builtin_fn(Self::pop, "pop", &prototype, 0); + make_builtin_fn(Self::join, "join", &prototype, 1); + make_builtin_fn(Self::to_string, "toString", &prototype, 0); + make_builtin_fn(Self::reverse, "reverse", &prototype, 0); + make_builtin_fn(Self::shift, "shift", &prototype, 0); + make_builtin_fn(Self::unshift, "unshift", &prototype, 1); + make_builtin_fn(Self::every, "every", &prototype, 1); + make_builtin_fn(Self::find, "find", &prototype, 1); + make_builtin_fn(Self::find_index, "findIndex", &prototype, 1); + make_builtin_fn(Self::slice, "slice", &prototype, 2); + make_builtin_fn(Self::some, "some", &prototype, 2); + + let array = make_constructor_fn(Self::make_array, global, prototype); + + // Static Methods + make_builtin_fn(Self::is_array, "isArray", &array, 1); + + array + } -/// Initialise the `Array` object on the global object. -#[inline] -pub fn init(global: &Value) { - global.set_field_slice("Array", create(global)); + /// Initialise the `Array` object on the global object. + #[inline] + pub(crate) fn init(global: &Value) { + global.set_field("Array", Self::create(global)); + } } diff --git a/boa/src/builtins/bigint/mod.rs b/boa/src/builtins/bigint/mod.rs index 403aae28d9b..55877c4cad1 100644 --- a/boa/src/builtins/bigint/mod.rs +++ b/boa/src/builtins/bigint/mod.rs @@ -14,93 +14,112 @@ use crate::{ builtins::{ - function::make_constructor_fn, + function::{make_builtin_fn, make_constructor_fn}, value::{ResultValue, Value}, }, exec::Interpreter, - syntax::ast::bigint::BigInt, + syntax::ast::bigint::BigInt as AstBigInt, }; #[cfg(test)] mod tests; -/// `BigInt()` -/// -/// The `BigInt()` constructor is used to create BigInt objects. -/// -/// More information: -/// - [ECMAScript reference][spec] -/// - [MDN documentation][mdn] -/// -/// [spec]: https://tc39.es/ecma262/#sec-bigint-objects -/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/BigInt/BigInt -pub fn make_bigint(_this: &mut Value, args: &[Value], _ctx: &mut Interpreter) -> ResultValue { - let data = match args.get(0) { - Some(ref value) => { - if let Some(bigint) = value.to_bigint() { - Value::from(bigint) - } else { - panic!("RangeError: The value cannot be converted to a BigInt because it is not an integer"); +/// `BigInt` implementation. +#[derive(Debug, Clone, Copy)] +pub(crate) struct BigInt; + +impl BigInt { + /// `BigInt()` + /// + /// The `BigInt()` constructor is used to create BigInt objects. + /// + /// More information: + /// - [ECMAScript reference][spec] + /// - [MDN documentation][mdn] + /// + /// [spec]: https://tc39.es/ecma262/#sec-bigint-objects + /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/BigInt/BigInt + pub(crate) fn make_bigint( + _this: &mut Value, + args: &[Value], + _ctx: &mut Interpreter, + ) -> ResultValue { + let data = match args.get(0) { + Some(ref value) => { + if let Some(bigint) = value.to_bigint() { + Value::from(bigint) + } else { + panic!("RangeError: The value cannot be converted to a BigInt because it is not an integer"); + } } - } - None => Value::from(BigInt::from(0)), - }; - Ok(data) -} + None => Value::from(AstBigInt::from(0)), + }; + Ok(data) + } -/// `BigInt.prototype.toString( [radix] )` -/// -/// The `toString()` method returns a string representing the specified BigInt object. -/// -/// More information: -/// - [ECMAScript reference][spec] -/// - [MDN documentation][mdn] -/// -/// [spec]: https://tc39.es/ecma262/#sec-bigint.prototype.tostring -/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/BigInt/toString -pub fn to_string(this: &mut Value, args: &[Value], _ctx: &mut Interpreter) -> ResultValue { - let radix = if !args.is_empty() { - args[0].to_integer() - } else { - 10 - }; - if radix < 2 && radix > 36 { - panic!("RangeError: toString() radix argument must be between 2 and 36"); + /// `BigInt.prototype.toString( [radix] )` + /// + /// The `toString()` method returns a string representing the specified BigInt object. + /// + /// More information: + /// - [ECMAScript reference][spec] + /// - [MDN documentation][mdn] + /// + /// [spec]: https://tc39.es/ecma262/#sec-bigint.prototype.tostring + /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/BigInt/toString + #[allow(clippy::wrong_self_convention)] + pub(crate) fn to_string( + this: &mut Value, + args: &[Value], + _ctx: &mut Interpreter, + ) -> ResultValue { + let radix = if !args.is_empty() { + args[0].to_integer() + } else { + 10 + }; + if radix < 2 && radix > 36 { + panic!("RangeError: toString() radix argument must be between 2 and 36"); + } + Ok(Value::from( + this.to_bigint().unwrap().to_str_radix(radix as u32), + )) } - Ok(Value::from( - this.to_bigint().unwrap().to_str_radix(radix as u32), - )) -} -// /// `BigInt.prototype.valueOf()` -// /// -// /// The `valueOf()` method returns the wrapped primitive value of a Number object. -// /// -// /// More information: -// /// - [ECMAScript reference][spec] -// /// - [MDN documentation][mdn] -// /// -/// [spec]: https://tc39.es/ecma262/#sec-bigint.prototype.valueof -/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/BigInt/valueOf -pub fn value_of(this: &mut Value, _args: &[Value], _ctx: &mut Interpreter) -> ResultValue { - Ok(Value::from( - this.to_bigint().expect("BigInt.prototype.valueOf"), - )) -} + // /// `BigInt.prototype.valueOf()` + // /// + // /// The `valueOf()` method returns the wrapped primitive value of a Number object. + // /// + // /// More information: + // /// - [ECMAScript reference][spec] + // /// - [MDN documentation][mdn] + // /// + /// [spec]: https://tc39.es/ecma262/#sec-bigint.prototype.valueof + /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/BigInt/valueOf + pub(crate) fn value_of( + this: &mut Value, + _args: &[Value], + _ctx: &mut Interpreter, + ) -> ResultValue { + Ok(Value::from( + this.to_bigint().expect("BigInt.prototype.valueOf"), + )) + } -/// Create a new `Number` object -pub fn create(global: &Value) -> Value { - let prototype = Value::new_object(Some(global)); - prototype.set_internal_slot("BigIntData", Value::from(BigInt::from(0))); + /// Create a new `Number` object + pub(crate) fn create(global: &Value) -> Value { + let prototype = Value::new_object(Some(global)); + prototype.set_internal_slot("BigIntData", Value::from(AstBigInt::from(0))); - make_builtin_fn!(to_string, named "toString", with length 1, of prototype); - make_builtin_fn!(value_of, named "valueOf", of prototype); + make_builtin_fn(Self::to_string, "toString", &prototype, 1); + make_builtin_fn(Self::value_of, "valueOf", &prototype, 0); - make_constructor_fn(make_bigint, global, prototype) -} + make_constructor_fn(Self::make_bigint, global, prototype) + } -/// Initialise the `BigInt` object on the global object. -#[inline] -pub fn init(global: &Value) { - global.set_field_slice("BigInt", create(global)); + /// Initialise the `BigInt` object on the global object. + #[inline] + pub(crate) fn init(global: &Value) { + global.set_field("BigInt", Self::create(global)); + } } diff --git a/boa/src/builtins/boolean/mod.rs b/boa/src/builtins/boolean/mod.rs index 58956cacda2..f70ae4b7422 100644 --- a/boa/src/builtins/boolean/mod.rs +++ b/boa/src/builtins/boolean/mod.rs @@ -12,7 +12,7 @@ #[cfg(test)] mod tests; -use super::function::make_constructor_fn; +use super::function::{make_builtin_fn, make_constructor_fn}; use crate::{ builtins::{ object::{internal_methods_trait::ObjectInternalMethods, ObjectKind}, @@ -22,93 +22,105 @@ use crate::{ }; use std::{borrow::Borrow, ops::Deref}; -/// `[[Construct]]` Create a new boolean object -/// -/// `[[Call]]` Creates a new boolean primitive -pub fn construct_boolean(this: &mut Value, args: &[Value], _: &mut Interpreter) -> ResultValue { - this.set_kind(ObjectKind::Boolean); +/// Boolean implementation. +#[derive(Debug, Clone, Copy)] +pub(crate) struct Boolean; - // Get the argument, if any - if let Some(ref value) = args.get(0) { - this.set_internal_slot("BooleanData", to_boolean(value)); - } else { - this.set_internal_slot("BooleanData", to_boolean(&Value::from(false))); - } +impl Boolean { + /// `[[Construct]]` Create a new boolean object + /// + /// `[[Call]]` Creates a new boolean primitive + pub(crate) fn construct_boolean( + this: &mut Value, + args: &[Value], + _: &mut Interpreter, + ) -> ResultValue { + this.set_kind(ObjectKind::Boolean); + + // Get the argument, if any + if let Some(ref value) = args.get(0) { + this.set_internal_slot("BooleanData", Self::to_boolean(value)); + } else { + this.set_internal_slot("BooleanData", Self::to_boolean(&Value::from(false))); + } - match args.get(0) { - Some(ref value) => Ok(to_boolean(value)), - None => Ok(to_boolean(&Value::from(false))), + match args.get(0) { + Some(ref value) => Ok(Self::to_boolean(value)), + None => Ok(Self::to_boolean(&Value::from(false))), + } } -} -/// The `toString()` method returns a string representing the specified `Boolean` object. -/// -/// More information: -/// - [ECMAScript reference][spec] -/// - [MDN documentation][mdn] -/// -/// [spec]: https://tc39.es/ecma262/#sec-boolean-object -/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean/toString -pub fn to_string(this: &mut Value, _: &[Value], _: &mut Interpreter) -> ResultValue { - let b = this_boolean_value(this); - Ok(Value::from(b.to_string())) -} + /// The `toString()` method returns a string representing the specified `Boolean` object. + /// + /// More information: + /// - [ECMAScript reference][spec] + /// - [MDN documentation][mdn] + /// + /// [spec]: https://tc39.es/ecma262/#sec-boolean-object + /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean/toString + #[allow(clippy::wrong_self_convention)] + pub(crate) fn to_string(this: &mut Value, _: &[Value], _: &mut Interpreter) -> ResultValue { + let b = Self::this_boolean_value(this); + Ok(Value::from(b.to_string())) + } -/// The valueOf() method returns the primitive value of a `Boolean` object. -/// -/// More information: -/// - [ECMAScript reference][spec] -/// - [MDN documentation][mdn] -/// -/// [spec]: https://tc39.es/ecma262/#sec-boolean.prototype.valueof -/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean/valueOf -pub fn value_of(this: &mut Value, _: &[Value], _: &mut Interpreter) -> ResultValue { - Ok(this_boolean_value(this)) -} + /// The valueOf() method returns the primitive value of a `Boolean` object. + /// + /// More information: + /// - [ECMAScript reference][spec] + /// - [MDN documentation][mdn] + /// + /// [spec]: https://tc39.es/ecma262/#sec-boolean.prototype.valueof + /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean/valueOf + pub(crate) fn value_of(this: &mut Value, _: &[Value], _: &mut Interpreter) -> ResultValue { + Ok(Self::this_boolean_value(this)) + } -// === Utility Functions === -/// [toBoolean](https://tc39.es/ecma262/#sec-toboolean) -/// Creates a new boolean value from the input -pub fn to_boolean(value: &Value) -> Value { - match *value.deref().borrow() { - ValueData::Object(_) => Value::from(true), - ValueData::String(ref s) if !s.is_empty() => Value::from(true), - ValueData::Rational(n) if n != 0.0 && !n.is_nan() => Value::from(true), - ValueData::Integer(n) if n != 0 => Value::from(true), - ValueData::Boolean(v) => Value::from(v), - _ => Value::from(false), + // === Utility Functions === + /// [toBoolean](https://tc39.es/ecma262/#sec-toboolean) + /// Creates a new boolean value from the input + #[allow(clippy::wrong_self_convention)] + pub(crate) fn to_boolean(value: &Value) -> Value { + match *value.deref().borrow() { + ValueData::Object(_) => Value::from(true), + ValueData::String(ref s) if !s.is_empty() => Value::from(true), + ValueData::Rational(n) if n != 0.0 && !n.is_nan() => Value::from(true), + ValueData::Integer(n) if n != 0 => Value::from(true), + ValueData::Boolean(v) => Value::from(v), + _ => Value::from(false), + } } -} -/// An Utility function used to get the internal BooleanData. -/// -/// More information: -/// - [ECMAScript reference][spec] -/// -/// [spec]: https://tc39.es/ecma262/#sec-thisbooleanvalue -pub fn this_boolean_value(value: &Value) -> Value { - match *value.deref().borrow() { - ValueData::Boolean(v) => Value::from(v), - ValueData::Object(ref v) => (v).deref().borrow().get_internal_slot("BooleanData"), - _ => Value::from(false), + /// An Utility function used to get the internal BooleanData. + /// + /// More information: + /// - [ECMAScript reference][spec] + /// + /// [spec]: https://tc39.es/ecma262/#sec-thisbooleanvalue + pub(crate) fn this_boolean_value(value: &Value) -> Value { + match *value.deref().borrow() { + ValueData::Boolean(v) => Value::from(v), + ValueData::Object(ref v) => (v).deref().borrow().get_internal_slot("BooleanData"), + _ => Value::from(false), + } } -} -/// Create a new `Boolean` object. -pub fn create(global: &Value) -> Value { - // Create Prototype - // https://tc39.es/ecma262/#sec-properties-of-the-boolean-prototype-object - let prototype = Value::new_object(Some(global)); - prototype.set_internal_slot("BooleanData", to_boolean(&Value::from(false))); + /// Create a new `Boolean` object. + pub(crate) fn create(global: &Value) -> Value { + // Create Prototype + // https://tc39.es/ecma262/#sec-properties-of-the-boolean-prototype-object + let prototype = Value::new_object(Some(global)); + prototype.set_internal_slot("BooleanData", Self::to_boolean(&Value::from(false))); - make_builtin_fn!(to_string, named "toString", of prototype); - make_builtin_fn!(value_of, named "valueOf", of prototype); + make_builtin_fn(Self::to_string, "toString", &prototype, 0); + make_builtin_fn(Self::value_of, "valueOf", &prototype, 0); - make_constructor_fn(construct_boolean, global, prototype) -} + make_constructor_fn(Self::construct_boolean, global, prototype) + } -/// Initialise the `Boolean` object on the global object. -#[inline] -pub fn init(global: &Value) { - global.set_field_slice("Boolean", create(global)); + /// Initialise the `Boolean` object on the global object. + #[inline] + pub(crate) fn init(global: &Value) { + global.set_field("Boolean", Self::create(global)); + } } diff --git a/boa/src/builtins/boolean/tests.rs b/boa/src/builtins/boolean/tests.rs index 9041ddf40c9..200e3eaa370 100644 --- a/boa/src/builtins/boolean/tests.rs +++ b/boa/src/builtins/boolean/tests.rs @@ -4,7 +4,7 @@ use crate::{builtins::value::same_value, exec::Interpreter, forward, forward_val #[test] fn check_boolean_constructor_is_function() { let global = Value::new_object(None); - let boolean_constructor = create(&global); + let boolean_constructor = Boolean::create(&global); assert_eq!(boolean_constructor.is_function(), true); } diff --git a/boa/src/builtins/console/mod.rs b/boa/src/builtins/console/mod.rs index bd5cf26cea0..b181e217458 100644 --- a/boa/src/builtins/console/mod.rs +++ b/boa/src/builtins/console/mod.rs @@ -18,6 +18,7 @@ mod tests; use crate::{ builtins::{ + function::make_builtin_fn, object::InternalState, value::{display_obj, ResultValue, Value}, }, @@ -491,25 +492,25 @@ pub fn dir(this: &mut Value, args: &[Value], _: &mut Interpreter) -> ResultValue pub fn create(global: &Value) -> Value { let console = Value::new_object(Some(global)); - make_builtin_fn!(assert, named "assert", of console); - make_builtin_fn!(clear, named "clear", of console); - make_builtin_fn!(debug, named "debug", of console); - make_builtin_fn!(error, named "error", of console); - make_builtin_fn!(info, named "info", of console); - make_builtin_fn!(log, named "log", of console); - make_builtin_fn!(trace, named "trace", of console); - make_builtin_fn!(warn, named "warn", of console); - make_builtin_fn!(error, named "exception", of console); - make_builtin_fn!(count, named "count", of console); - make_builtin_fn!(count_reset, named "countReset", of console); - make_builtin_fn!(group, named "group", of console); - make_builtin_fn!(group, named "groupCollapsed", of console); - make_builtin_fn!(group_end , named "groupEnd", of console); - make_builtin_fn!(time, named "time", of console); - make_builtin_fn!(time_log, named "timeLog", of console); - make_builtin_fn!(time_end, named "timeEnd", of console); - make_builtin_fn!(dir, named "dir", of console); - make_builtin_fn!(dir, named "dirxml", of console); + make_builtin_fn(assert, "assert", &console, 0); + make_builtin_fn(clear, "clear", &console, 0); + make_builtin_fn(debug, "debug", &console, 0); + make_builtin_fn(error, "error", &console, 0); + make_builtin_fn(info, "info", &console, 0); + make_builtin_fn(log, "log", &console, 0); + make_builtin_fn(trace, "trace", &console, 0); + make_builtin_fn(warn, "warn", &console, 0); + make_builtin_fn(error, "exception", &console, 0); + make_builtin_fn(count, "count", &console, 0); + make_builtin_fn(count_reset, "countReset", &console, 0); + make_builtin_fn(group, "group", &console, 0); + make_builtin_fn(group, "groupCollapsed", &console, 0); + make_builtin_fn(group_end, "groupEnd", &console, 0); + make_builtin_fn(time, "time", &console, 0); + make_builtin_fn(time_log, "timeLog", &console, 0); + make_builtin_fn(time_end, "timeEnd", &console, 0); + make_builtin_fn(dir, "dir", &console, 0); + make_builtin_fn(dir, "dirxml", &console, 0); console.set_internal_state(ConsoleState::default()); @@ -519,5 +520,5 @@ pub fn create(global: &Value) -> Value { /// Initialise the `console` object on the global object. #[inline] pub fn init(global: &Value) { - global.set_field_slice("console", create(global)); + global.set_field("console", create(global)); } diff --git a/boa/src/builtins/error/mod.rs b/boa/src/builtins/error/mod.rs index 2293ea5ba2a..d430330fc3f 100644 --- a/boa/src/builtins/error/mod.rs +++ b/boa/src/builtins/error/mod.rs @@ -12,7 +12,7 @@ use crate::{ builtins::{ - function::make_constructor_fn, + function::{make_builtin_fn, make_constructor_fn}, object::ObjectKind, value::{ResultValue, Value}, }, @@ -20,56 +20,65 @@ use crate::{ }; // mod eval; -pub mod range; +pub(crate) mod range; // mod reference; // mod syntax; // mod type_err; // mod uri; -/// Create a new error object. -pub fn make_error(this: &mut Value, args: &[Value], _: &mut Interpreter) -> ResultValue { - if !args.is_empty() { - this.set_field_slice( - "message", - Value::from( - args.get(0) - .expect("failed getting error message") - .to_string(), - ), - ); +pub(crate) use self::range::RangeError; + +/// Built-in `Error` object. +#[derive(Debug, Clone, Copy)] +pub(crate) struct Error; + +impl Error { + /// Create a new error object. + pub(crate) fn make_error(this: &mut Value, args: &[Value], _: &mut Interpreter) -> ResultValue { + if !args.is_empty() { + this.set_field( + "message", + Value::from( + args.get(0) + .expect("failed getting error message") + .to_string(), + ), + ); + } + // This value is used by console.log and other routines to match Object type + // to its Javascript Identifier (global constructor method name) + this.set_kind(ObjectKind::Error); + Ok(Value::undefined()) } - // This value is used by console.log and other routines to match Object type - // to its Javascript Identifier (global constructor method name) - this.set_kind(ObjectKind::Error); - Ok(Value::undefined()) -} -/// `Error.prototype.toString()` -/// -/// The toString() method returns a string representing the specified Error object. -/// -/// More information: -/// - [MDN documentation][mdn] -/// - [ECMAScript reference][spec] -/// -/// [spec]: https://tc39.es/ecma262/#sec-error.prototype.tostring -/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error/toString -pub fn to_string(this: &mut Value, _: &[Value], _: &mut Interpreter) -> ResultValue { - let name = this.get_field_slice("name"); - let message = this.get_field_slice("message"); - Ok(Value::from(format!("{}: {}", name, message))) -} + /// `Error.prototype.toString()` + /// + /// The toString() method returns a string representing the specified Error object. + /// + /// More information: + /// - [MDN documentation][mdn] + /// - [ECMAScript reference][spec] + /// + /// [spec]: https://tc39.es/ecma262/#sec-error.prototype.tostring + /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error/toString + #[allow(clippy::wrong_self_convention)] + pub(crate) fn to_string(this: &mut Value, _: &[Value], _: &mut Interpreter) -> ResultValue { + let name = this.get_field("name"); + let message = this.get_field("message"); + Ok(Value::from(format!("{}: {}", name, message))) + } -/// Create a new `Error` object. -pub fn create(global: &Value) -> Value { - let prototype = Value::new_object(Some(global)); - prototype.set_field_slice("message", Value::from("")); - prototype.set_field_slice("name", Value::from("Error")); - make_builtin_fn!(to_string, named "toString", of prototype); - make_constructor_fn(make_error, global, prototype) -} + /// Create a new `Error` object. + pub(crate) fn create(global: &Value) -> Value { + let prototype = Value::new_object(Some(global)); + prototype.set_field("message", Value::from("")); + prototype.set_field("name", Value::from("Error")); + make_builtin_fn(Self::to_string, "toString", &prototype, 0); + make_constructor_fn(Self::make_error, global, prototype) + } -/// Initialise the global object with the `Error` object. -pub fn init(global: &Value) { - global.set_field_slice("Error", create(global)); + /// Initialise the global object with the `Error` object. + pub(crate) fn init(global: &Value) { + global.set_field("Error", Self::create(global)); + } } diff --git a/boa/src/builtins/error/range.rs b/boa/src/builtins/error/range.rs index 72492728322..7c2cec63a92 100644 --- a/boa/src/builtins/error/range.rs +++ b/boa/src/builtins/error/range.rs @@ -11,6 +11,7 @@ use crate::{ builtins::{ + function::make_builtin_fn, function::make_constructor_fn, object::ObjectKind, value::{ResultValue, Value}, @@ -18,50 +19,65 @@ use crate::{ exec::Interpreter, }; -/// Create a new error object. -pub fn make_error(this: &mut Value, args: &[Value], _: &mut Interpreter) -> ResultValue { - if !args.is_empty() { - this.set_field_slice( - "message", - Value::from( - args.get(0) - .expect("failed getting error message") - .to_string(), - ), - ); +/// JavaScript `RangeError` impleentation. +#[derive(Debug, Clone, Copy)] +pub(crate) struct RangeError; + +impl RangeError { + /// Create a new error object. + pub(crate) fn make_error(this: &mut Value, args: &[Value], _: &mut Interpreter) -> ResultValue { + if !args.is_empty() { + this.set_field( + "message", + Value::from( + args.get(0) + .expect("failed getting error message") + .to_string(), + ), + ); + } + // This value is used by console.log and other routines to match Object type + // to its Javascript Identifier (global constructor method name) + this.set_kind(ObjectKind::Error); + Ok(Value::undefined()) } - // This value is used by console.log and other routines to match Object type - // to its Javascript Identifier (global constructor method name) - this.set_kind(ObjectKind::Error); - Ok(Value::undefined()) -} -/// `Error.prototype.toString()` -/// -/// The toString() method returns a string representing the specified Error object. -/// -/// More information: -/// - [MDN documentation][mdn] -/// - [ECMAScript reference][spec] -/// -/// [spec]: https://tc39.es/ecma262/#sec-error.prototype.tostring -/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error/toString -pub fn to_string(this: &mut Value, _: &[Value], _: &mut Interpreter) -> ResultValue { - let name = this.get_field_slice("name"); - let message = this.get_field_slice("message"); - Ok(Value::from(format!("{}: {}", name, message))) -} + /// `Error.prototype.toString()` + /// + /// The toString() method returns a string representing the specified Error object. + /// + /// More information: + /// - [MDN documentation][mdn] + /// - [ECMAScript reference][spec] + /// + /// [spec]: https://tc39.es/ecma262/#sec-error.prototype.tostring + /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error/toString + #[allow(clippy::wrong_self_convention)] + pub(crate) fn to_string(this: &mut Value, _: &[Value], _: &mut Interpreter) -> ResultValue { + let name = this.get_field("name"); + let message = this.get_field("message"); + Ok(Value::from(format!("{}: {}", name, message))) + } -/// Create a new `RangeError` object. -pub fn create(global: &Value) -> Value { - let prototype = Value::new_object(Some(global)); - prototype.set_field_slice("message", Value::from("")); - prototype.set_field_slice("name", Value::from("RangeError")); - make_builtin_fn!(to_string, named "toString", of prototype); - make_constructor_fn(make_error, global, prototype) -} + /// Create a new `RangeError` object. + pub(crate) fn create(global: &Value) -> Value { + let prototype = Value::new_object(Some(global)); + prototype.set_field("message", Value::from("")); + prototype.set_field("name", Value::from("RangeError")); + make_builtin_fn(Self::to_string, "toString", &prototype, 0); + make_constructor_fn(Self::make_error, global, prototype) + } + + /// Runs a `new RangeError(message)`. + pub(crate) fn run_new(message: M, interpreter: &mut Interpreter) -> Value + where + M: Into, + { + unimplemented!() + } -/// Initialise the global object with the `RangeError` object. -pub fn init(global: &Value) { - global.set_field_slice("RangeError", create(global)); + /// Initialise the global object with the `RangeError` object. + pub(crate) fn init(global: &Value) { + global.set_field("RangeError", Self::create(global)); + } } diff --git a/boa/src/builtins/function/mod.rs b/boa/src/builtins/function/mod.rs index ba81a561cca..92bd850e340 100644 --- a/boa/src/builtins/function/mod.rs +++ b/boa/src/builtins/function/mod.rs @@ -13,7 +13,7 @@ use crate::{ builtins::{ - array, + array::Array, object::{Object, ObjectInternalMethods, ObjectKind, PROTOTYPE}, property::Property, value::{ResultValue, Value}, @@ -278,8 +278,8 @@ impl Function { local_env: &Environment, ) { // Create array of values - let array = array::new_array(interpreter).unwrap(); - array::add_to_array_object(&array, &args_list[index..]).unwrap(); + let array = Array::new_array(interpreter).unwrap(); + Array::add_to_array_object(&array, &args_list[index..]).unwrap(); // Create binding local_env @@ -384,9 +384,7 @@ pub fn make_constructor_fn(body: NativeFunctionData, global: &Value, proto: Valu ); // Get reference to Function.prototype - let func_prototype = global - .get_field_slice("Function") - .get_field_slice(PROTOTYPE); + let func_prototype = global.get_field("Function").get_field(PROTOTYPE); // Create the function object and point its instance prototype to Function.prototype let mut constructor_obj = Object::function(); @@ -396,14 +394,32 @@ pub fn make_constructor_fn(body: NativeFunctionData, global: &Value, proto: Valu let constructor_val = Value::from(constructor_obj); // Set proto.constructor -> constructor_obj - proto.set_field_slice("constructor", constructor_val.clone()); - constructor_val.set_field_slice(PROTOTYPE, proto); + proto.set_field("constructor", constructor_val.clone()); + constructor_val.set_field(PROTOTYPE, proto); constructor_val } +/// Macro to create a new member function of a prototype. +/// +/// If no length is provided, the length will be set to 0. +pub fn make_builtin_fn(function: NativeFunctionData, name: N, parent: &Value, length: i32) +where + N: Into, +{ + let func = Function::create_builtin(vec![], FunctionBody::BuiltIn(function)); + + let mut new_func = Object::function(); + new_func.set_func(func); + + let new_func_obj = Value::from(new_func); + new_func_obj.set_field("length", length); + + parent.set_field(name.into(), new_func_obj); +} + /// Initialise the `Function` object on the global object. #[inline] pub fn init(global: &Value) { - global.set_field_slice("Function", create(global)); + global.set_field("Function", create(global)); } diff --git a/boa/src/builtins/json/mod.rs b/boa/src/builtins/json/mod.rs index 592a58fad56..7a5e80458c6 100644 --- a/boa/src/builtins/json/mod.rs +++ b/boa/src/builtins/json/mod.rs @@ -13,7 +13,10 @@ //! [json]: https://www.json.org/json-en.html //! [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON -use crate::builtins::value::{ResultValue, Value}; +use crate::builtins::{ + function::make_builtin_fn, + value::{ResultValue, Value}, +}; use crate::exec::Interpreter; use serde_json::{self, Value as JSONValue}; @@ -72,8 +75,8 @@ pub fn stringify(_: &mut Value, args: &[Value], _: &mut Interpreter) -> ResultVa pub fn create(global: &Value) -> Value { let json = Value::new_object(Some(global)); - make_builtin_fn!(parse, named "parse", with length 2, of json); - make_builtin_fn!(stringify, named "stringify", with length 3, of json); + make_builtin_fn(parse, "parse", &json, 2); + make_builtin_fn(stringify, "stringify", &json, 3); json } @@ -81,5 +84,5 @@ pub fn create(global: &Value) -> Value { /// Initialise the `JSON` object on the global object. #[inline] pub fn init(global: &Value) { - global.set_field_slice("JSON", create(global)); + global.set_field("JSON", create(global)); } diff --git a/boa/src/builtins/math/mod.rs b/boa/src/builtins/math/mod.rs index 2419bcfcf9c..04591cad309 100644 --- a/boa/src/builtins/math/mod.rs +++ b/boa/src/builtins/math/mod.rs @@ -12,7 +12,10 @@ //! [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math use crate::{ - builtins::value::{ResultValue, Value}, + builtins::{ + function::make_builtin_fn, + value::{ResultValue, Value}, + }, exec::Interpreter, }; use rand::random; @@ -506,43 +509,43 @@ pub fn trunc(_: &mut Value, args: &[Value], _: &mut Interpreter) -> ResultValue pub fn create(global: &Value) -> Value { let math = Value::new_object(Some(global)); - math.set_field_slice("E", Value::from(f64::consts::E)); - math.set_field_slice("LN2", Value::from(f64::consts::LN_2)); - math.set_field_slice("LN10", Value::from(f64::consts::LN_10)); - math.set_field_slice("LOG2E", Value::from(f64::consts::LOG2_E)); - math.set_field_slice("LOG10E", Value::from(f64::consts::LOG10_E)); - math.set_field_slice("SQRT1_2", Value::from(0.5_f64.sqrt())); - math.set_field_slice("SQRT2", Value::from(f64::consts::SQRT_2)); - math.set_field_slice("PI", Value::from(f64::consts::PI)); - make_builtin_fn!(abs, named "abs", with length 1, of math); - make_builtin_fn!(acos, named "acos", with length 1, of math); - make_builtin_fn!(acosh, named "acosh", with length 1, of math); - make_builtin_fn!(asin, named "asin", with length 1, of math); - make_builtin_fn!(asinh, named "asinh", with length 1, of math); - make_builtin_fn!(atan, named "atan", with length 1, of math); - make_builtin_fn!(atanh, named "atanh", with length 1, of math); - make_builtin_fn!(atan2, named "atan2", with length 2, of math); - make_builtin_fn!(cbrt, named "cbrt", with length 1, of math); - make_builtin_fn!(ceil, named "ceil", with length 1, of math); - make_builtin_fn!(cos, named "cos", with length 1, of math); - make_builtin_fn!(cosh, named "cosh", with length 1, of math); - make_builtin_fn!(exp, named "exp", with length 1, of math); - make_builtin_fn!(floor, named "floor", with length 1, of math); - make_builtin_fn!(log, named "log", with length 1, of math); - make_builtin_fn!(log10, named "log10", with length 1, of math); - make_builtin_fn!(log2, named "log2", with length 1, of math); - make_builtin_fn!(max, named "max", with length 2, of math); - make_builtin_fn!(min, named "min", with length 2, of math); - make_builtin_fn!(pow, named "pow", with length 2, of math); - make_builtin_fn!(_random, named "random", of math); - make_builtin_fn!(round, named "round", with length 1, of math); - make_builtin_fn!(sign, named "sign", with length 1, of math); - make_builtin_fn!(sin, named "sin", with length 1, of math); - make_builtin_fn!(sinh, named "sinh", with length 1, of math); - make_builtin_fn!(sqrt, named "sqrt", with length 1, of math); - make_builtin_fn!(tan, named "tan", with length 1, of math); - make_builtin_fn!(tanh, named "tanh", with length 1, of math); - make_builtin_fn!(trunc, named "trunc", with length 1, of math); + math.set_field("E", Value::from(f64::consts::E)); + math.set_field("LN2", Value::from(f64::consts::LN_2)); + math.set_field("LN10", Value::from(f64::consts::LN_10)); + math.set_field("LOG2E", Value::from(f64::consts::LOG2_E)); + math.set_field("LOG10E", Value::from(f64::consts::LOG10_E)); + math.set_field("SQRT1_2", Value::from(0.5_f64.sqrt())); + math.set_field("SQRT2", Value::from(f64::consts::SQRT_2)); + math.set_field("PI", Value::from(f64::consts::PI)); + make_builtin_fn(abs, "abs", &math, 1); + make_builtin_fn(acos, "acos", &math, 1); + make_builtin_fn(acosh, "acosh", &math, 1); + make_builtin_fn(asin, "asin", &math, 1); + make_builtin_fn(asinh, "asinh", &math, 1); + make_builtin_fn(atan, "atan", &math, 1); + make_builtin_fn(atanh, "atanh", &math, 1); + make_builtin_fn(atan2, "atan2", &math, 2); + make_builtin_fn(cbrt, "cbrt", &math, 1); + make_builtin_fn(ceil, "ceil", &math, 1); + make_builtin_fn(cos, "cos", &math, 1); + make_builtin_fn(cosh, "cosh", &math, 1); + make_builtin_fn(exp, "exp", &math, 1); + make_builtin_fn(floor, "floor", &math, 1); + make_builtin_fn(log, "log", &math, 1); + make_builtin_fn(log10, "log10", &math, 1); + make_builtin_fn(log2, "log2", &math, 1); + make_builtin_fn(max, "max", &math, 2); + make_builtin_fn(min, "min", &math, 2); + make_builtin_fn(pow, "pow", &math, 2); + make_builtin_fn(_random, "random", &math, 0); + make_builtin_fn(round, "round", &math, 1); + make_builtin_fn(sign, "sign", &math, 1); + make_builtin_fn(sin, "sin", &math, 1); + make_builtin_fn(sinh, "sinh", &math, 1); + make_builtin_fn(sqrt, "sqrt", &math, 1); + make_builtin_fn(tan, "tan", &math, 1); + make_builtin_fn(tanh, "tanh", &math, 1); + make_builtin_fn(trunc, "trunc", &math, 1); math } @@ -550,5 +553,5 @@ pub fn create(global: &Value) -> Value { /// Initialise the `Math` object on the global object. #[inline] pub fn init(global: &Value) { - global.set_field_slice("Math", create(global)); + global.set_field("Math", create(global)); } diff --git a/boa/src/builtins/mod.rs b/boa/src/builtins/mod.rs index 912c5000e7c..2cfd2af68f8 100644 --- a/boa/src/builtins/mod.rs +++ b/boa/src/builtins/mod.rs @@ -1,25 +1,9 @@ //! Builtins live here, such as Object, String, Math etc -/// Macro to create a new member function of a prototype. -/// -/// If no length is provided, the length will be set to 0. -macro_rules! make_builtin_fn { - ($fn:ident, named $name:expr, with length $l:tt, of $p:ident) => { - let func = crate::builtins::function::Function::create_builtin( - vec![], - crate::builtins::function::FunctionBody::BuiltIn($fn), - ); - - let mut new_func = crate::builtins::object::Object::function(); - new_func.set_func(func); - let new_func_obj = Value::from(new_func); - new_func_obj.set_field_slice("length", Value::from($l)); - $p.set_field_slice($name, new_func_obj); - }; - ($fn:ident, named $name:expr, of $p:ident) => { - make_builtin_fn!($fn, named $name, with length 0, of $p); - }; -} +use crate::builtins::{ + function::{Function, FunctionBody, NativeFunctionData}, + object::Object, +}; pub mod array; pub mod bigint; @@ -37,21 +21,32 @@ pub mod string; pub mod symbol; pub mod value; -use value::Value; +pub(crate) use self::{ + array::Array, + bigint::BigInt, + boolean::Boolean, + error::{Error, RangeError}, + number::Number, + regexp::RegExp, + string::String, + value::{ResultValue, Value}, +}; /// Initializes builtin objects and functions #[inline] pub fn init(global: &Value) { - array::init(global); - bigint::init(global); - boolean::init(global); + Array::init(global); + BigInt::init(global); + Boolean::init(global); json::init(global); math::init(global); - number::init(global); + Number::init(global); object::init(global); function::init(global); - regexp::init(global); - string::init(global); + RegExp::init(global); + String::init(global); symbol::init(global); console::init(global); + Error::init(global); + RangeError::init(global); } diff --git a/boa/src/builtins/number/mod.rs b/boa/src/builtins/number/mod.rs index d1692f001f2..ed7869fa47e 100644 --- a/boa/src/builtins/number/mod.rs +++ b/boa/src/builtins/number/mod.rs @@ -16,7 +16,10 @@ #[cfg(test)] mod tests; -use super::{function::make_constructor_fn, object::ObjectKind}; +use super::{ + function::{make_builtin_fn, make_constructor_fn}, + object::ObjectKind, +}; use crate::{ builtins::{ object::internal_methods_trait::ObjectInternalMethods, @@ -27,394 +30,439 @@ use crate::{ use num_traits::float::FloatCore; use std::{borrow::Borrow, f64, ops::Deref}; -/// Helper function that converts a Value to a Number. -fn to_number(value: &Value) -> Value { - match *value.deref().borrow() { - ValueData::Boolean(b) => { - if b { - Value::from(1) - } else { - Value::from(0) +const BUF_SIZE: usize = 2200; + +/// `Number` implementation. +#[derive(Debug, Clone, Copy)] +pub(crate) struct Number; + +impl Number { + /// Helper function that converts a Value to a Number. + #[allow(clippy::wrong_self_convention)] + fn to_number(value: &Value) -> Value { + match *value.deref().borrow() { + ValueData::Boolean(b) => { + if b { + Value::from(1) + } else { + Value::from(0) + } } + ValueData::Symbol(_) | ValueData::Undefined => Value::from(f64::NAN), + ValueData::Integer(i) => Value::from(f64::from(i)), + ValueData::Object(ref o) => (o).deref().borrow().get_internal_slot("NumberData"), + ValueData::Null => Value::from(0), + ValueData::Rational(n) => Value::from(n), + ValueData::BigInt(ref bigint) => Value::from(bigint.to_f64()), + ValueData::String(ref s) => match s.parse::() { + Ok(n) => Value::from(n), + Err(_) => Value::from(f64::NAN), + }, } - ValueData::Symbol(_) | ValueData::Undefined => Value::from(f64::NAN), - ValueData::Integer(i) => Value::from(f64::from(i)), - ValueData::Object(ref o) => (o).deref().borrow().get_internal_slot("NumberData"), - ValueData::Null => Value::from(0), - ValueData::Rational(n) => Value::from(n), - ValueData::BigInt(ref bigint) => Value::from(bigint.to_f64()), - ValueData::String(ref s) => match s.parse::() { - Ok(n) => Value::from(n), - Err(_) => Value::from(f64::NAN), - }, } -} -/// Helper function that formats a float as a ES6-style exponential number string. -fn num_to_exponential(n: f64) -> String { - match n.abs() { - x if x > 1.0 => format!("{:e}", n).replace("e", "e+"), - x if x == 0.0 => format!("{:e}", n).replace("e", "e+"), - _ => format!("{:e}", n), + /// Helper function that formats a float as a ES6-style exponential number string. + fn num_to_exponential(n: f64) -> String { + match n.abs() { + x if x > 1.0 => format!("{:e}", n).replace("e", "e+"), + x if x == 0.0 => format!("{:e}", n).replace("e", "e+"), + _ => format!("{:e}", n), + } } -} - -/// `[[Construct]]` - Creates a Number instance -/// -/// `[[Call]]` - Creates a number primitive -pub fn make_number(this: &mut Value, args: &[Value], _ctx: &mut Interpreter) -> ResultValue { - let data = match args.get(0) { - Some(ref value) => to_number(value), - None => to_number(&Value::from(0)), - }; - this.set_kind(ObjectKind::Number); - this.set_internal_slot("NumberData", data.clone()); - - Ok(data) -} -/// `Number()` function. -/// -/// More Information https://tc39.es/ecma262/#sec-number-constructor-number-value -pub fn call_number(_this: &mut Value, args: &[Value], _ctx: &mut Interpreter) -> ResultValue { - let data = match args.get(0) { - Some(ref value) => to_number(value), - None => to_number(&Value::from(0)), - }; - Ok(data) -} + /// `[[Construct]]` - Creates a Number instance + /// + /// `[[Call]]` - Creates a number primitive + pub(crate) fn make_number( + this: &mut Value, + args: &[Value], + _ctx: &mut Interpreter, + ) -> ResultValue { + let data = match args.get(0) { + Some(ref value) => Self::to_number(value), + None => Self::to_number(&Value::from(0)), + }; + this.set_kind(ObjectKind::Number); + this.set_internal_slot("NumberData", data.clone()); -/// `Number.prototype.toExponential( [fractionDigits] )` -/// -/// The `toExponential()` method returns a string representing the Number object in exponential notation. -/// -/// More information: -/// - [ECMAScript reference][spec] -/// - [MDN documentation][mdn] -/// -/// [spec]: https://tc39.es/ecma262/#sec-number.prototype.toexponential -/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/toExponential -pub fn to_exponential(this: &mut Value, _args: &[Value], _ctx: &mut Interpreter) -> ResultValue { - let this_num = to_number(this).to_number(); - let this_str_num = num_to_exponential(this_num); - Ok(Value::from(this_str_num)) -} + Ok(data) + } -/// `Number.prototype.toFixed( [digits] )` -/// -/// The `toFixed()` method formats a number using fixed-point notation -/// -/// More information: -/// - [ECMAScript reference][spec] -/// - [MDN documentation][mdn] -/// -/// [spec]: https://tc39.es/ecma262/#sec-number.prototype.tofixed -/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/toFixed -pub fn to_fixed(this: &mut Value, args: &[Value], _ctx: &mut Interpreter) -> ResultValue { - let this_num = to_number(this).to_number(); - let precision = match args.get(0) { - Some(n) => match n.to_integer() { - x if x > 0 => n.to_integer() as usize, - _ => 0, - }, - None => 0, - }; - let this_fixed_num = format!("{:.*}", precision, this_num); - Ok(Value::from(this_fixed_num)) -} + /// `Number()` function. + /// + /// More Information https://tc39.es/ecma262/#sec-number-constructor-number-value + pub(crate) fn call_number( + _this: &mut Value, + args: &[Value], + _ctx: &mut Interpreter, + ) -> ResultValue { + let data = match args.get(0) { + Some(ref value) => Self::to_number(value), + None => Self::to_number(&Value::from(0)), + }; + Ok(data) + } -/// `Number.prototype.toLocaleString( [locales [, options]] )` -/// -/// The `toLocaleString()` method returns a string with a language-sensitive representation of this number. -/// -/// Note that while this technically conforms to the Ecma standard, it does no actual -/// internationalization logic. -/// -/// More information: -/// - [ECMAScript reference][spec] -/// - [MDN documentation][mdn] -/// -/// [spec]: https://tc39.es/ecma262/#sec-number.prototype.tolocalestring -/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/toLocaleString -pub fn to_locale_string(this: &mut Value, _args: &[Value], _ctx: &mut Interpreter) -> ResultValue { - let this_num = to_number(this).to_number(); - let this_str_num = format!("{}", this_num); - Ok(Value::from(this_str_num)) -} + /// `Number.prototype.toExponential( [fractionDigits] )` + /// + /// The `toExponential()` method returns a string representing the Number object in exponential notation. + /// + /// More information: + /// - [ECMAScript reference][spec] + /// - [MDN documentation][mdn] + /// + /// [spec]: https://tc39.es/ecma262/#sec-number.prototype.toexponential + /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/toExponential + #[allow(clippy::wrong_self_convention)] + pub(crate) fn to_exponential( + this: &mut Value, + _args: &[Value], + _ctx: &mut Interpreter, + ) -> ResultValue { + let this_num = Self::to_number(this).to_number(); + let this_str_num = Self::num_to_exponential(this_num); + Ok(Value::from(this_str_num)) + } -/// `Number.prototype.toPrecision( [precision] )` -/// -/// The `toPrecision()` method returns a string representing the Number object to the specified precision. -/// -/// More information: -/// - [ECMAScript reference][spec] -/// - [MDN documentation][mdn] -/// -/// [spec]: https://tc39.es/ecma262/#sec-number.prototype.toexponential -/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/toPrecision -pub fn to_precision(this: &mut Value, args: &[Value], _ctx: &mut Interpreter) -> ResultValue { - let this_num = to_number(this); - let _num_str_len = format!("{}", this_num.to_number()).len(); - let _precision = match args.get(0) { - Some(n) => match n.to_integer() { - x if x > 0 => n.to_integer() as usize, - _ => 0, - }, - None => 0, - }; - // TODO: Implement toPrecision - unimplemented!("TODO: Implement toPrecision"); -} + /// `Number.prototype.toFixed( [digits] )` + /// + /// The `toFixed()` method formats a number using fixed-point notation + /// + /// More information: + /// - [ECMAScript reference][spec] + /// - [MDN documentation][mdn] + /// + /// [spec]: https://tc39.es/ecma262/#sec-number.prototype.tofixed + /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/toFixed + #[allow(clippy::wrong_self_convention)] + pub(crate) fn to_fixed( + this: &mut Value, + args: &[Value], + _ctx: &mut Interpreter, + ) -> ResultValue { + let this_num = Self::to_number(this).to_number(); + let precision = match args.get(0) { + Some(n) => match n.to_integer() { + x if x > 0 => n.to_integer() as usize, + _ => 0, + }, + None => 0, + }; + let this_fixed_num = format!("{:.*}", precision, this_num); + Ok(Value::from(this_fixed_num)) + } -const BUF_SIZE: usize = 2200; + /// `Number.prototype.toLocaleString( [locales [, options]] )` + /// + /// The `toLocaleString()` method returns a string with a language-sensitive representation of this number. + /// + /// Note that while this technically conforms to the Ecma standard, it does no actual + /// internationalization logic. + /// + /// More information: + /// - [ECMAScript reference][spec] + /// - [MDN documentation][mdn] + /// + /// [spec]: https://tc39.es/ecma262/#sec-number.prototype.tolocalestring + /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/toLocaleString + #[allow(clippy::wrong_self_convention)] + pub(crate) fn to_locale_string( + this: &mut Value, + _args: &[Value], + _ctx: &mut Interpreter, + ) -> ResultValue { + let this_num = Self::to_number(this).to_number(); + let this_str_num = format!("{}", this_num); + Ok(Value::from(this_str_num)) + } -// https://golang.org/src/math/nextafter.go -#[inline] -fn next_after(x: f64, y: f64) -> f64 { - if x.is_nan() || y.is_nan() { - f64::NAN - } else if (x - y) == 0. { - x - } else if x == 0.0 { - f64::from_bits(1).copysign(y) - } else if y > x || x > 0.0 { - f64::from_bits(x.to_bits() + 1) - } else { - f64::from_bits(x.to_bits() - 1) + /// `Number.prototype.toPrecision( [precision] )` + /// + /// The `toPrecision()` method returns a string representing the Number object to the specified precision. + /// + /// More information: + /// - [ECMAScript reference][spec] + /// - [MDN documentation][mdn] + /// + /// [spec]: https://tc39.es/ecma262/#sec-number.prototype.toexponential + /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/toPrecision + #[allow(clippy::wrong_self_convention)] + pub(crate) fn to_precision( + this: &mut Value, + args: &[Value], + _ctx: &mut Interpreter, + ) -> ResultValue { + let this_num = Self::to_number(this); + let _num_str_len = format!("{}", this_num.to_number()).len(); + let _precision = match args.get(0) { + Some(n) => match n.to_integer() { + x if x > 0 => n.to_integer() as usize, + _ => 0, + }, + None => 0, + }; + // TODO: Implement toPrecision + unimplemented!("TODO: Implement toPrecision"); } -} -// https://chromium.googlesource.com/v8/v8/+/refs/heads/master/src/numbers/conversions.cc#1230 -pub fn num_to_string(mut value: f64, radix: u8) -> String { - assert!(radix >= 2); - assert!(radix <= 36); - assert!(value.is_finite()); - // assert_ne!(0.0, value); - - // Character array used for conversion. - // Temporary buffer for the result. We start with the decimal point in the - // middle and write to the left for the integer part and to the right for the - // fractional part. 1024 characters for the exponent and 52 for the mantissa - // either way, with additional space for sign, decimal point and string - // termination should be sufficient. - let mut buffer: [u8; BUF_SIZE] = [0; BUF_SIZE]; - let (int_buf, frac_buf) = buffer.split_at_mut(BUF_SIZE / 2); - let mut fraction_cursor = 0; - let negative = value.is_sign_negative(); - if negative { - value = -value + // https://golang.org/src/math/nextafter.go + #[inline] + fn next_after(x: f64, y: f64) -> f64 { + if x.is_nan() || y.is_nan() { + f64::NAN + } else if (x - y) == 0. { + x + } else if x == 0.0 { + f64::from_bits(1).copysign(y) + } else if y > x || x > 0.0 { + f64::from_bits(x.to_bits() + 1) + } else { + f64::from_bits(x.to_bits() - 1) + } } - // Split the value into an integer part and a fractional part. - // let mut integer = value.trunc(); - // let mut fraction = value.fract(); - let mut integer = value.floor(); - let mut fraction = value - integer; - - // We only compute fractional digits up to the input double's precision. - let mut delta = 0.5 * (next_after(value, f64::MAX) - value); - delta = next_after(0.0, f64::MAX).max(delta); - assert!(delta > 0.0); - if fraction >= delta { - // Insert decimal point. - frac_buf[fraction_cursor] = b'.'; - fraction_cursor += 1; - loop { - // Shift up by one digit. - fraction *= radix as f64; - delta *= radix as f64; - // Write digit. - let digit = fraction as u32; - frac_buf[fraction_cursor] = std::char::from_digit(digit, radix as u32).unwrap() as u8; + + // https://chromium.googlesource.com/v8/v8/+/refs/heads/master/src/numbers/conversions.cc#1230 + pub(crate) fn num_to_string(mut value: f64, radix: u8) -> String { + assert!(radix >= 2); + assert!(radix <= 36); + assert!(value.is_finite()); + // assert_ne!(0.0, value); + + // Character array used for conversion. + // Temporary buffer for the result. We start with the decimal point in the + // middle and write to the left for the integer part and to the right for the + // fractional part. 1024 characters for the exponent and 52 for the mantissa + // either way, with additional space for sign, decimal point and string + // termination should be sufficient. + let mut buffer: [u8; BUF_SIZE] = [0; BUF_SIZE]; + let (int_buf, frac_buf) = buffer.split_at_mut(BUF_SIZE / 2); + let mut fraction_cursor = 0; + let negative = value.is_sign_negative(); + if negative { + value = -value + } + // Split the value into an integer part and a fractional part. + // let mut integer = value.trunc(); + // let mut fraction = value.fract(); + let mut integer = value.floor(); + let mut fraction = value - integer; + + // We only compute fractional digits up to the input double's precision. + let mut delta = 0.5 * (Self::next_after(value, f64::MAX) - value); + delta = Self::next_after(0.0, f64::MAX).max(delta); + assert!(delta > 0.0); + if fraction >= delta { + // Insert decimal point. + frac_buf[fraction_cursor] = b'.'; fraction_cursor += 1; - // Calculate remainder. - fraction -= digit as f64; - // Round to even. - if fraction + delta > 1.0 - && (fraction > 0.5 || (fraction - 0.5) < f64::EPSILON && digit & 1 != 0) - { - loop { - // We need to back trace already written digits in case of carry-over. - fraction_cursor -= 1; - if fraction_cursor == 0 { - // CHECK_EQ('.', buffer[fraction_cursor]); - // Carry over to the integer part. - integer += 1.; - break; - } else { - let c: u8 = frac_buf[fraction_cursor]; - // Reconstruct digit. - let digit_0 = (c as char).to_digit(10).unwrap(); - if digit_0 + 1 >= radix as u32 { - continue; + loop { + // Shift up by one digit. + fraction *= radix as f64; + delta *= radix as f64; + // Write digit. + let digit = fraction as u32; + frac_buf[fraction_cursor] = + std::char::from_digit(digit, radix as u32).unwrap() as u8; + fraction_cursor += 1; + // Calculate remainder. + fraction -= digit as f64; + // Round to even. + if fraction + delta > 1.0 + && (fraction > 0.5 || (fraction - 0.5) < f64::EPSILON && digit & 1 != 0) + { + loop { + // We need to back trace already written digits in case of carry-over. + fraction_cursor -= 1; + if fraction_cursor == 0 { + // CHECK_EQ('.', buffer[fraction_cursor]); + // Carry over to the integer part. + integer += 1.; + break; + } else { + let c: u8 = frac_buf[fraction_cursor]; + // Reconstruct digit. + let digit_0 = (c as char).to_digit(10).unwrap(); + if digit_0 + 1 >= radix as u32 { + continue; + } + frac_buf[fraction_cursor] = + std::char::from_digit(digit_0 + 1, radix as u32).unwrap() as u8; + fraction_cursor += 1; + break; } - frac_buf[fraction_cursor] = - std::char::from_digit(digit_0 + 1, radix as u32).unwrap() as u8; - fraction_cursor += 1; - break; } + break; + } + if fraction < delta { + break; } - break; } - if fraction < delta { + } + + // Compute integer digits. Fill unrepresented digits with zero. + let mut int_iter = int_buf.iter_mut().enumerate().rev(); //.rev(); + while FloatCore::integer_decode(integer / f64::from(radix)).1 > 0 { + integer /= radix as f64; + *int_iter.next().unwrap().1 = b'0'; + } + + loop { + let remainder = integer % (radix as f64); + *int_iter.next().unwrap().1 = + std::char::from_digit(remainder as u32, radix as u32).unwrap() as u8; + integer = (integer - remainder) / radix as f64; + if integer <= 0f64 { break; } } - } + // Add sign and terminate string. + if negative { + *int_iter.next().unwrap().1 = b'-'; + } + assert!(fraction_cursor < BUF_SIZE); - // Compute integer digits. Fill unrepresented digits with zero. - let mut int_iter = int_buf.iter_mut().enumerate().rev(); //.rev(); - while FloatCore::integer_decode(integer / f64::from(radix)).1 > 0 { - integer /= radix as f64; - *int_iter.next().unwrap().1 = b'0'; + let integer_cursor = int_iter.next().unwrap().0 + 1; + let fraction_cursor = fraction_cursor + BUF_SIZE / 2; + // dbg!("Number: {}, Radix: {}, Cursors: {}, {}", value, radix, integer_cursor, fraction_cursor); + String::from_utf8_lossy(&buffer[integer_cursor..fraction_cursor]).into() } - loop { - let remainder = integer % (radix as f64); - *int_iter.next().unwrap().1 = - std::char::from_digit(remainder as u32, radix as u32).unwrap() as u8; - integer = (integer - remainder) / radix as f64; - if integer <= 0f64 { - break; + /// `Number.prototype.toString( [radix] )` + /// + /// The `toString()` method returns a string representing the specified Number object. + /// + /// More information: + /// - [ECMAScript reference][spec] + /// - [MDN documentation][mdn] + /// + /// [spec]: https://tc39.es/ecma262/#sec-number.prototype.tostring + /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/toString + #[allow(clippy::wrong_self_convention)] + pub(crate) fn to_string( + this: &mut Value, + args: &[Value], + _ctx: &mut Interpreter, + ) -> ResultValue { + // 1. Let x be ? thisNumberValue(this value). + let x = Self::to_number(this).to_number(); + // 2. If radix is undefined, let radixNumber be 10. + // 3. Else, let radixNumber be ? ToInteger(radix). + let radix_number = args.get(0).map_or(10, |arg| arg.to_integer()) as u8; + + if x == -0. { + return Ok(Value::from("0")); + } else if x.is_nan() { + return Ok(Value::from("NaN")); + } else if x.is_infinite() && x.is_sign_positive() { + return Ok(Value::from("Infinity")); + } else if x.is_infinite() && x.is_sign_negative() { + return Ok(Value::from("-Infinity")); } - } - // Add sign and terminate string. - if negative { - *int_iter.next().unwrap().1 = b'-'; - } - assert!(fraction_cursor < BUF_SIZE); - let integer_cursor = int_iter.next().unwrap().0 + 1; - let fraction_cursor = fraction_cursor + BUF_SIZE / 2; - // dbg!("Number: {}, Radix: {}, Cursors: {}, {}", value, radix, integer_cursor, fraction_cursor); - String::from_utf8_lossy(&buffer[integer_cursor..fraction_cursor]).into() -} + // 4. If radixNumber < 2 or radixNumber > 36, throw a RangeError exception. + if radix_number < 2 || radix_number > 36 { + panic!("RangeError: radix must be an integer at least 2 and no greater than 36"); + } -/// `Number.prototype.toString( [radix] )` -/// -/// The `toString()` method returns a string representing the specified Number object. -/// -/// More information: -/// - [ECMAScript reference][spec] -/// - [MDN documentation][mdn] -/// -/// [spec]: https://tc39.es/ecma262/#sec-number.prototype.tostring -/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/toString -pub fn to_string(this: &mut Value, args: &[Value], _ctx: &mut Interpreter) -> ResultValue { - // 1. Let x be ? thisNumberValue(this value). - let x = to_number(this).to_number(); - // 2. If radix is undefined, let radixNumber be 10. - // 3. Else, let radixNumber be ? ToInteger(radix). - let radix_number = args.get(0).map_or(10, |arg| arg.to_integer()) as u8; - - if x == -0. { - return Ok(Value::from("0")); - } else if x.is_nan() { - return Ok(Value::from("NaN")); - } else if x.is_infinite() && x.is_sign_positive() { - return Ok(Value::from("Infinity")); - } else if x.is_infinite() && x.is_sign_negative() { - return Ok(Value::from("-Infinity")); - } + // 5. If radixNumber = 10, return ! ToString(x). + // This part should use exponential notations for long integer numbers commented tests + if radix_number == 10 { + // return Ok(to_value(format!("{}", Self::to_number(this).to_num()))); + return Ok(Value::from(format!("{}", x))); + } - // 4. If radixNumber < 2 or radixNumber > 36, throw a RangeError exception. - if radix_number < 2 || radix_number > 36 { - panic!("RangeError: radix must be an integer at least 2 and no greater than 36"); - } + // This is a Optimization from the v8 source code to print values that can fit in a single character + // Since the actual num_to_string allocates a 2200 bytes buffer for actual conversion + // I am not sure if this part is effective as the v8 equivalent https://chromium.googlesource.com/v8/v8/+/refs/heads/master/src/builtins/number.tq#53 + // // Fast case where the result is a one character string. + // if x.is_sign_positive() && x.fract() == 0.0 && x < radix_number as f64 { + // return Ok(to_value(format!("{}", std::char::from_digit(x as u32, radix_number as u32).unwrap()))) + // } - // 5. If radixNumber = 10, return ! ToString(x). - // This part should use exponential notations for long integer numbers commented tests - if radix_number == 10 { - // return Ok(to_value(format!("{}", to_number(this).to_num()))); - return Ok(Value::from(format!("{}", x))); + // 6. Return the String representation of this Number value using the radix specified by radixNumber. + Ok(Value::from(Self::num_to_string(x, radix_number))) } - // This is a Optimization from the v8 source code to print values that can fit in a single character - // Since the actual num_to_string allocates a 2200 bytes buffer for actual conversion - // I am not sure if this part is effective as the v8 equivalent https://chromium.googlesource.com/v8/v8/+/refs/heads/master/src/builtins/number.tq#53 - // // Fast case where the result is a one character string. - // if x.is_sign_positive() && x.fract() == 0.0 && x < radix_number as f64 { - // return Ok(to_value(format!("{}", std::char::from_digit(x as u32, radix_number as u32).unwrap()))) - // } - - // 6. Return the String representation of this Number value using the radix specified by radixNumber. - Ok(Value::from(num_to_string(x, radix_number))) -} - -/// `Number.prototype.toString()` -/// -/// The `valueOf()` method returns the wrapped primitive value of a Number object. -/// -/// More information: -/// - [ECMAScript reference][spec] -/// - [MDN documentation][mdn] -/// -/// [spec]: https://tc39.es/ecma262/#sec-number.prototype.valueof -/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/valueOf -pub fn value_of(this: &mut Value, _args: &[Value], _ctx: &mut Interpreter) -> ResultValue { - Ok(to_number(this)) -} - -/// Create a new `Number` object -pub fn create(global: &Value) -> Value { - let prototype = Value::new_object(Some(global)); - prototype.set_internal_slot("NumberData", Value::from(0)); - - make_builtin_fn!(to_exponential, named "toExponential", with length 1, of prototype); - make_builtin_fn!(to_fixed, named "toFixed", with length 1, of prototype); - make_builtin_fn!(to_locale_string, named "toLocaleString", of prototype); - make_builtin_fn!(to_precision, named "toPrecision", with length 1, of prototype); - make_builtin_fn!(to_string, named "toString", with length 1, of prototype); - make_builtin_fn!(value_of, named "valueOf", of prototype); + /// `Number.prototype.toString()` + /// + /// The `valueOf()` method returns the wrapped primitive value of a Number object. + /// + /// More information: + /// - [ECMAScript reference][spec] + /// - [MDN documentation][mdn] + /// + /// [spec]: https://tc39.es/ecma262/#sec-number.prototype.valueof + /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/valueOf + pub(crate) fn value_of( + this: &mut Value, + _args: &[Value], + _ctx: &mut Interpreter, + ) -> ResultValue { + Ok(Self::to_number(this)) + } - make_constructor_fn(make_number, global, prototype) -} + /// Create a new `Number` object + pub(crate) fn create(global: &Value) -> Value { + let prototype = Value::new_object(Some(global)); + prototype.set_internal_slot("NumberData", Value::from(0)); -/// Initialise the `Number` object on the global object. -#[inline] -pub fn init(global: &Value) { - global.set_field_slice("Number", create(global)); -} + make_builtin_fn(Self::to_exponential, "toExponential", &prototype, 1); + make_builtin_fn(Self::to_fixed, "toFixed", &prototype, 1); + make_builtin_fn(Self::to_locale_string, "toLocaleString", &prototype, 0); + make_builtin_fn(Self::to_precision, "toPrecision", &prototype, 1); + make_builtin_fn(Self::to_string, "toString", &prototype, 1); + make_builtin_fn(Self::value_of, "valueOf", &prototype, 0); -/// The abstract operation Number::equal takes arguments -/// x (a Number) and y (a Number). It performs the following steps when called: -/// -/// https://tc39.es/ecma262/#sec-numeric-types-number-equal -#[allow(clippy::float_cmp)] -pub fn equals(a: f64, b: f64) -> bool { - a == b -} + make_constructor_fn(Self::make_number, global, prototype) + } -/// The abstract operation Number::sameValue takes arguments -/// x (a Number) and y (a Number). It performs the following steps when called: -/// -/// https://tc39.es/ecma262/#sec-numeric-types-number-sameValue -#[allow(clippy::float_cmp)] -pub fn same_value(a: f64, b: f64) -> bool { - if a.is_nan() && b.is_nan() { - return true; + /// Initialise the `Number` object on the global object. + #[inline] + pub(crate) fn init(global: &Value) { + global.set_field("Number", Self::create(global)); } - if a == 0.0 && b == 0.0 { - if (a.is_sign_negative() && b.is_sign_positive()) - || (a.is_sign_positive() && b.is_sign_negative()) - { - return false; - }; - true - } else { + /// The abstract operation Number::equal takes arguments + /// x (a Number) and y (a Number). It performs the following steps when called: + /// + /// https://tc39.es/ecma262/#sec-numeric-types-number-equal + #[allow(clippy::float_cmp)] + pub(crate) fn equals(a: f64, b: f64) -> bool { a == b } -} -/// The abstract operation Number::sameValueZero takes arguments -/// x (a Number) and y (a Number). It performs the following steps when called: -/// -/// https://tc39.es/ecma262/#sec-numeric-types-number-sameValueZero -#[allow(clippy::float_cmp)] -pub fn same_value_zero(a: f64, b: f64) -> bool { - if a.is_nan() && b.is_nan() { - return true; + /// The abstract operation Number::sameValue takes arguments + /// x (a Number) and y (a Number). It performs the following steps when called: + /// + /// https://tc39.es/ecma262/#sec-numeric-types-number-sameValue + #[allow(clippy::float_cmp)] + pub(crate) fn same_value(a: f64, b: f64) -> bool { + if a.is_nan() && b.is_nan() { + return true; + } + + if a == 0.0 && b == 0.0 { + if (a.is_sign_negative() && b.is_sign_positive()) + || (a.is_sign_positive() && b.is_sign_negative()) + { + return false; + }; + true + } else { + a == b + } } - a == b + /// The abstract operation Number::sameValueZero takes arguments + /// x (a Number) and y (a Number). It performs the following steps when called: + /// + /// https://tc39.es/ecma262/#sec-numeric-types-number-sameValueZero + #[allow(clippy::float_cmp)] + pub(crate) fn same_value_zero(a: f64, b: f64) -> bool { + if a.is_nan() && b.is_nan() { + return true; + } + + a == b + } } diff --git a/boa/src/builtins/number/tests.rs b/boa/src/builtins/number/tests.rs index 0b5dcfd859d..4e01da96c87 100644 --- a/boa/src/builtins/number/tests.rs +++ b/boa/src/builtins/number/tests.rs @@ -1,11 +1,16 @@ #![allow(clippy::float_cmp)] -use crate::{builtins::value::Value, exec::Interpreter, forward, forward_val, realm::Realm}; +use crate::{ + builtins::{Number, Value}, + exec::Interpreter, + forward, forward_val, + realm::Realm, +}; #[test] fn check_number_constructor_is_function() { let global = Value::new_object(None); - let number_constructor = super::create(&global); + let number_constructor = Number::create(&global); assert_eq!(number_constructor.is_function(), true); } @@ -398,33 +403,33 @@ fn value_of() { #[test] fn equal() { - assert_eq!(super::equals(0.0, 0.0), true); - assert_eq!(super::equals(-0.0, 0.0), true); - assert_eq!(super::equals(0.0, -0.0), true); - assert_eq!(super::equals(f64::NAN, -0.0), false); - assert_eq!(super::equals(0.0, f64::NAN), false); + assert_eq!(Number::equals(0.0, 0.0), true); + assert_eq!(Number::equals(-0.0, 0.0), true); + assert_eq!(Number::equals(0.0, -0.0), true); + assert_eq!(Number::equals(f64::NAN, -0.0), false); + assert_eq!(Number::equals(0.0, f64::NAN), false); - assert_eq!(super::equals(1.0, 1.0), true); + assert_eq!(Number::equals(1.0, 1.0), true); } #[test] fn same_value() { - assert_eq!(super::same_value(0.0, 0.0), true); - assert_eq!(super::same_value(-0.0, 0.0), false); - assert_eq!(super::same_value(0.0, -0.0), false); - assert_eq!(super::same_value(f64::NAN, -0.0), false); - assert_eq!(super::same_value(0.0, f64::NAN), false); - assert_eq!(super::equals(1.0, 1.0), true); + assert_eq!(Number::same_value(0.0, 0.0), true); + assert_eq!(Number::same_value(-0.0, 0.0), false); + assert_eq!(Number::same_value(0.0, -0.0), false); + assert_eq!(Number::same_value(f64::NAN, -0.0), false); + assert_eq!(Number::same_value(0.0, f64::NAN), false); + assert_eq!(Number::equals(1.0, 1.0), true); } #[test] fn same_value_zero() { - assert_eq!(super::same_value_zero(0.0, 0.0), true); - assert_eq!(super::same_value_zero(-0.0, 0.0), true); - assert_eq!(super::same_value_zero(0.0, -0.0), true); - assert_eq!(super::same_value_zero(f64::NAN, -0.0), false); - assert_eq!(super::same_value_zero(0.0, f64::NAN), false); - assert_eq!(super::equals(1.0, 1.0), true); + assert_eq!(Number::same_value_zero(0.0, 0.0), true); + assert_eq!(Number::same_value_zero(-0.0, 0.0), true); + assert_eq!(Number::same_value_zero(0.0, -0.0), true); + assert_eq!(Number::same_value_zero(f64::NAN, -0.0), false); + assert_eq!(Number::same_value_zero(0.0, f64::NAN), false); + assert_eq!(Number::equals(1.0, 1.0), true); } #[test] diff --git a/boa/src/builtins/object/mod.rs b/boa/src/builtins/object/mod.rs index 88afd80c24b..c2ec7df9ff3 100644 --- a/boa/src/builtins/object/mod.rs +++ b/boa/src/builtins/object/mod.rs @@ -29,7 +29,7 @@ use std::{ ops::Deref, }; -use super::function::make_constructor_fn; +use super::function::{make_builtin_fn, make_constructor_fn}; pub use internal_methods_trait::ObjectInternalMethods; pub use internal_state::{InternalState, InternalStateCell}; @@ -548,7 +548,7 @@ pub fn make_object(_: &mut Value, args: &[Value], ctx: &mut Interpreter) -> Resu /// Get the `prototype` of an object. pub fn get_prototype_of(_: &mut Value, args: &[Value], _: &mut Interpreter) -> ResultValue { let obj = args.get(0).expect("Cannot get object"); - Ok(obj.get_field_slice(INSTANCE_PROTOTYPE)) + Ok(obj.get_field(INSTANCE_PROTOTYPE)) } /// Set the `prototype` of an object. @@ -611,15 +611,15 @@ pub fn has_own_property(this: &mut Value, args: &[Value], _: &mut Interpreter) - pub fn create(global: &Value) -> Value { let prototype = Value::new_object(None); - make_builtin_fn!(has_own_property, named "hasOwnProperty", of prototype); - make_builtin_fn!(to_string, named "toString", of prototype); + make_builtin_fn(has_own_property, "hasOwnProperty", &prototype, 0); + make_builtin_fn(to_string, "toString", &prototype, 0); let object = make_constructor_fn(make_object, global, prototype); - object.set_field_slice("length", Value::from(1)); - make_builtin_fn!(set_prototype_of, named "setPrototypeOf", with length 2, of object); - make_builtin_fn!(get_prototype_of, named "getPrototypeOf", with length 1, of object); - make_builtin_fn!(define_property, named "defineProperty", with length 3, of object); + object.set_field("length", Value::from(1)); + make_builtin_fn(set_prototype_of, "setPrototypeOf", &object, 2); + make_builtin_fn(get_prototype_of, "getPrototypeOf", &object, 1); + make_builtin_fn(define_property, "defineProperty", &object, 3); object } @@ -627,5 +627,5 @@ pub fn create(global: &Value) -> Value { /// Initialise the `Object` object on the global object. #[inline] pub fn init(global: &Value) { - global.set_field_slice("Object", create(global)); + global.set_field("Object", create(global)); } diff --git a/boa/src/builtins/property/mod.rs b/boa/src/builtins/property/mod.rs index 2153642b2f4..5496e6256c2 100644 --- a/boa/src/builtins/property/mod.rs +++ b/boa/src/builtins/property/mod.rs @@ -173,12 +173,12 @@ impl Default for Property { impl From<&Property> for Value { fn from(value: &Property) -> Value { let property = Value::new_object(None); - property.set_field_slice("configurable", Value::from(value.configurable)); - property.set_field_slice("enumerable", Value::from(value.enumerable)); - property.set_field_slice("writable", Value::from(value.writable)); - property.set_field_slice("value", value.value.clone().unwrap_or_else(Value::null)); - property.set_field_slice("get", value.get.clone().unwrap_or_else(Value::null)); - property.set_field_slice("set", value.set.clone().unwrap_or_else(Value::null)); + property.set_field("configurable", Value::from(value.configurable)); + property.set_field("enumerable", Value::from(value.enumerable)); + property.set_field("writable", Value::from(value.writable)); + property.set_field("value", value.value.clone().unwrap_or_else(Value::null)); + property.set_field("get", value.get.clone().unwrap_or_else(Value::null)); + property.set_field("set", value.set.clone().unwrap_or_else(Value::null)); property } } @@ -188,12 +188,12 @@ impl<'a> From<&'a Value> for Property { /// if they're not there default to false fn from(value: &Value) -> Self { Self { - configurable: { Some(bool::from(&value.get_field_slice("configurable"))) }, - enumerable: { Some(bool::from(&value.get_field_slice("enumerable"))) }, - writable: { Some(bool::from(&value.get_field_slice("writable"))) }, - value: Some(value.get_field_slice("value")), - get: Some(value.get_field_slice("get")), - set: Some(value.get_field_slice("set")), + configurable: { Some(bool::from(&value.get_field("configurable"))) }, + enumerable: { Some(bool::from(&value.get_field("enumerable"))) }, + writable: { Some(bool::from(&value.get_field("writable"))) }, + value: Some(value.get_field("value")), + get: Some(value.get_field("get")), + set: Some(value.get_field("set")), } } } diff --git a/boa/src/builtins/regexp/mod.rs b/boa/src/builtins/regexp/mod.rs index 521ebf8e51b..53de4cf4ddf 100644 --- a/boa/src/builtins/regexp/mod.rs +++ b/boa/src/builtins/regexp/mod.rs @@ -13,7 +13,7 @@ use std::ops::Deref; use regex::Regex; -use super::function::make_constructor_fn; +use super::function::{make_builtin_fn, make_constructor_fn}; use crate::{ builtins::{ object::{InternalState, ObjectKind}, @@ -28,7 +28,7 @@ mod tests; /// The internal representation on a `RegExp` object. #[derive(Debug)] -struct RegExp { +pub(crate) struct RegExp { /// Regex matcher. matcher: Regex, @@ -59,428 +59,438 @@ struct RegExp { impl InternalState for RegExp {} -/// Create a new `RegExp` -pub fn make_regexp(this: &mut Value, args: &[Value], _: &mut Interpreter) -> ResultValue { - if args.is_empty() { - return Err(Value::undefined()); - } - let mut regex_body = String::new(); - let mut regex_flags = String::new(); - #[allow(clippy::indexing_slicing)] // length has been checked - match args[0].deref() { - ValueData::String(ref body) => { - // first argument is a string -> use it as regex pattern - regex_body = body.into(); +impl RegExp { + /// Create a new `RegExp` + pub(crate) fn make_regexp( + this: &mut Value, + args: &[Value], + _: &mut Interpreter, + ) -> ResultValue { + if args.is_empty() { + return Err(Value::undefined()); } - ValueData::Object(ref obj) => { - let slots = &obj.borrow().internal_slots; - if slots.get("RegExpMatcher").is_some() { - // first argument is another `RegExp` object, so copy its pattern and flags - if let Some(body) = slots.get("OriginalSource") { - regex_body = String::from(body); - } - if let Some(flags) = slots.get("OriginalFlags") { - regex_flags = String::from(flags); + let mut regex_body = String::new(); + let mut regex_flags = String::new(); + #[allow(clippy::indexing_slicing)] // length has been checked + match args[0].deref() { + ValueData::String(ref body) => { + // first argument is a string -> use it as regex pattern + regex_body = body.into(); + } + ValueData::Object(ref obj) => { + let slots = &obj.borrow().internal_slots; + if slots.get("RegExpMatcher").is_some() { + // first argument is another `RegExp` object, so copy its pattern and flags + if let Some(body) = slots.get("OriginalSource") { + regex_body = String::from(body); + } + if let Some(flags) = slots.get("OriginalFlags") { + regex_flags = String::from(flags); + } } } + _ => return Err(Value::undefined()), } - _ => return Err(Value::undefined()), - } - // if a second argument is given and it's a string, use it as flags - match args.get(1) { - None => {} - Some(flags) => { - if let ValueData::String(flags) = flags.deref() { - regex_flags = flags.into(); + // if a second argument is given and it's a string, use it as flags + match args.get(1) { + None => {} + Some(flags) => { + if let ValueData::String(flags) = flags.deref() { + regex_flags = flags.into(); + } } } - } - // parse flags - let mut sorted_flags = String::new(); - let mut pattern = String::new(); - let mut dot_all = false; - let mut global = false; - let mut ignore_case = false; - let mut multiline = false; - let mut sticky = false; - let mut unicode = false; - if regex_flags.contains('g') { - global = true; - sorted_flags.push('g'); - } - if regex_flags.contains('i') { - ignore_case = true; - sorted_flags.push('i'); - pattern.push('i'); - } - if regex_flags.contains('m') { - multiline = true; - sorted_flags.push('m'); - pattern.push('m'); - } - if regex_flags.contains('s') { - dot_all = true; - sorted_flags.push('s'); - pattern.push('s'); - } - if regex_flags.contains('u') { - unicode = true; - sorted_flags.push('u'); - //pattern.push('s'); // rust uses utf-8 anyway + // parse flags + let mut sorted_flags = String::new(); + let mut pattern = String::new(); + let mut dot_all = false; + let mut global = false; + let mut ignore_case = false; + let mut multiline = false; + let mut sticky = false; + let mut unicode = false; + if regex_flags.contains('g') { + global = true; + sorted_flags.push('g'); + } + if regex_flags.contains('i') { + ignore_case = true; + sorted_flags.push('i'); + pattern.push('i'); + } + if regex_flags.contains('m') { + multiline = true; + sorted_flags.push('m'); + pattern.push('m'); + } + if regex_flags.contains('s') { + dot_all = true; + sorted_flags.push('s'); + pattern.push('s'); + } + if regex_flags.contains('u') { + unicode = true; + sorted_flags.push('u'); + //pattern.push('s'); // rust uses utf-8 anyway + } + if regex_flags.contains('y') { + sticky = true; + sorted_flags.push('y'); + } + // the `regex` crate uses '(?{flags})` inside the pattern to enable flags + if !pattern.is_empty() { + pattern = format!("(?{})", pattern); + } + pattern.push_str(regex_body.as_str()); + + let matcher = Regex::new(pattern.as_str()).expect("failed to create matcher"); + let regexp = RegExp { + matcher, + use_last_index: global || sticky, + flags: sorted_flags, + dot_all, + global, + ignore_case, + multiline, + sticky, + unicode, + }; + + // This value is used by console.log and other routines to match Object type + // to its Javascript Identifier (global constructor method name) + this.set_kind(ObjectKind::Ordinary); + this.set_internal_slot("RegExpMatcher", Value::undefined()); + this.set_internal_slot("OriginalSource", Value::from(regex_body)); + this.set_internal_slot("OriginalFlags", Value::from(regex_flags)); + + this.set_internal_state(regexp); + Ok(this.clone()) } - if regex_flags.contains('y') { - sticky = true; - sorted_flags.push('y'); + + /// `RegExp.prototype.dotAll` + /// + /// The `dotAll` property indicates whether or not the "`s`" flag is used with the regular expression. + /// + /// More information: + /// - [ECMAScript reference][spec] + /// - [MDN documentation][mdn] + /// + /// [spec]: https://tc39.es/ecma262/#sec-get-regexp.prototype.dotAll + /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp/dotAll + fn get_dot_all(this: &mut Value, _: &[Value], _: &mut Interpreter) -> ResultValue { + this.with_internal_state_ref(|regex: &RegExp| Ok(Value::from(regex.dot_all))) } - // the `regex` crate uses '(?{flags})` inside the pattern to enable flags - if !pattern.is_empty() { - pattern = format!("(?{})", pattern); + + /// `RegExp.prototype.flags` + /// + /// The `flags` property returns a string consisting of the [`flags`][flags] of the current regular expression object. + /// + /// More information: + /// - [ECMAScript reference][spec] + /// - [MDN documentation][mdn] + /// + /// [spec]: https://tc39.es/ecma262/#sec-get-regexp.prototype.flags + /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp/flags + /// [flags]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions#Advanced_searching_with_flags_2 + fn get_flags(this: &mut Value, _: &[Value], _: &mut Interpreter) -> ResultValue { + this.with_internal_state_ref(|regex: &RegExp| Ok(Value::from(regex.flags.clone()))) } - pattern.push_str(regex_body.as_str()); - - let matcher = Regex::new(pattern.as_str()).expect("failed to create matcher"); - let regexp = RegExp { - matcher, - use_last_index: global || sticky, - flags: sorted_flags, - dot_all, - global, - ignore_case, - multiline, - sticky, - unicode, - }; - - // This value is used by console.log and other routines to match Object type - // to its Javascript Identifier (global constructor method name) - this.set_kind(ObjectKind::Ordinary); - this.set_internal_slot("RegExpMatcher", Value::undefined()); - this.set_internal_slot("OriginalSource", Value::from(regex_body)); - this.set_internal_slot("OriginalFlags", Value::from(regex_flags)); - - this.set_internal_state(regexp); - Ok(this.clone()) -} -/// `RegExp.prototype.dotAll` -/// -/// The `dotAll` property indicates whether or not the "`s`" flag is used with the regular expression. -/// -/// More information: -/// - [ECMAScript reference][spec] -/// - [MDN documentation][mdn] -/// -/// [spec]: https://tc39.es/ecma262/#sec-get-regexp.prototype.dotAll -/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp/dotAll -fn get_dot_all(this: &mut Value, _: &[Value], _: &mut Interpreter) -> ResultValue { - this.with_internal_state_ref(|regex: &RegExp| Ok(Value::from(regex.dot_all))) -} + /// `RegExp.prototype.global` + /// + /// The `global` property indicates whether or not the "`g`" flag is used with the regular expression. + /// + /// More information: + /// - [ECMAScript reference][spec] + /// - [MDN documentation][mdn] + /// + /// [spec]: https://tc39.es/ecma262/#sec-get-regexp.prototype.global + /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp/global + fn get_global(this: &mut Value, _: &[Value], _: &mut Interpreter) -> ResultValue { + this.with_internal_state_ref(|regex: &RegExp| Ok(Value::from(regex.global))) + } -/// `RegExp.prototype.flags` -/// -/// The `flags` property returns a string consisting of the [`flags`][flags] of the current regular expression object. -/// -/// More information: -/// - [ECMAScript reference][spec] -/// - [MDN documentation][mdn] -/// -/// [spec]: https://tc39.es/ecma262/#sec-get-regexp.prototype.flags -/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp/flags -/// [flags]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions#Advanced_searching_with_flags_2 -fn get_flags(this: &mut Value, _: &[Value], _: &mut Interpreter) -> ResultValue { - this.with_internal_state_ref(|regex: &RegExp| Ok(Value::from(regex.flags.clone()))) -} + /// `RegExp.prototype.ignoreCase` + /// + /// The `ignoreCase` property indicates whether or not the "`i`" flag is used with the regular expression. + /// + /// More information: + /// - [ECMAScript reference][spec] + /// - [MDN documentation][mdn] + /// + /// [spec]: https://tc39.es/ecma262/#sec-get-regexp.prototype.ignorecase + /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp/ignoreCase + fn get_ignore_case(this: &mut Value, _: &[Value], _: &mut Interpreter) -> ResultValue { + this.with_internal_state_ref(|regex: &RegExp| Ok(Value::from(regex.ignore_case))) + } -/// `RegExp.prototype.global` -/// -/// The `global` property indicates whether or not the "`g`" flag is used with the regular expression. -/// -/// More information: -/// - [ECMAScript reference][spec] -/// - [MDN documentation][mdn] -/// -/// [spec]: https://tc39.es/ecma262/#sec-get-regexp.prototype.global -/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp/global -fn get_global(this: &mut Value, _: &[Value], _: &mut Interpreter) -> ResultValue { - this.with_internal_state_ref(|regex: &RegExp| Ok(Value::from(regex.global))) -} + /// `RegExp.prototype.multiline` + /// + /// The multiline property indicates whether or not the "m" flag is used with the regular expression. + /// + /// More information: + /// - [ECMAScript reference][spec] + /// - [MDN documentation][mdn] + /// + /// [spec]: https://tc39.es/ecma262/#sec-get-regexp.prototype.multiline + /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp/multiline + fn get_multiline(this: &mut Value, _: &[Value], _: &mut Interpreter) -> ResultValue { + this.with_internal_state_ref(|regex: &RegExp| Ok(Value::from(regex.multiline))) + } -/// `RegExp.prototype.ignoreCase` -/// -/// The `ignoreCase` property indicates whether or not the "`i`" flag is used with the regular expression. -/// -/// More information: -/// - [ECMAScript reference][spec] -/// - [MDN documentation][mdn] -/// -/// [spec]: https://tc39.es/ecma262/#sec-get-regexp.prototype.ignorecase -/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp/ignoreCase -fn get_ignore_case(this: &mut Value, _: &[Value], _: &mut Interpreter) -> ResultValue { - this.with_internal_state_ref(|regex: &RegExp| Ok(Value::from(regex.ignore_case))) -} + /// `RegExp.prototype.source` + /// + /// The `source` property returns a `String` containing the source text of the regexp object, + /// and it doesn't contain the two forward slashes on both sides and any flags. + /// + /// More information: + /// - [ECMAScript reference][spec] + /// - [MDN documentation][mdn] + /// + /// [spec]: https://tc39.es/ecma262/#sec-get-regexp.prototype.source + /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp/source + fn get_source(this: &mut Value, _: &[Value], _: &mut Interpreter) -> ResultValue { + Ok(this.get_internal_slot("OriginalSource")) + } -/// `RegExp.prototype.multiline` -/// -/// The multiline property indicates whether or not the "m" flag is used with the regular expression. -/// -/// More information: -/// - [ECMAScript reference][spec] -/// - [MDN documentation][mdn] -/// -/// [spec]: https://tc39.es/ecma262/#sec-get-regexp.prototype.multiline -/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp/multiline -fn get_multiline(this: &mut Value, _: &[Value], _: &mut Interpreter) -> ResultValue { - this.with_internal_state_ref(|regex: &RegExp| Ok(Value::from(regex.multiline))) -} + /// `RegExp.prototype.sticky` + /// + /// The `flags` property returns a string consisting of the [`flags`][flags] of the current regular expression object. + /// + /// More information: + /// - [ECMAScript reference][spec] + /// - [MDN documentation][mdn] + /// + /// [spec]: https://tc39.es/ecma262/#sec-get-regexp.prototype.sticky + /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp/sticky + fn get_sticky(this: &mut Value, _: &[Value], _: &mut Interpreter) -> ResultValue { + this.with_internal_state_ref(|regex: &RegExp| Ok(Value::from(regex.sticky))) + } -/// `RegExp.prototype.source` -/// -/// The `source` property returns a `String` containing the source text of the regexp object, -/// and it doesn't contain the two forward slashes on both sides and any flags. -/// -/// More information: -/// - [ECMAScript reference][spec] -/// - [MDN documentation][mdn] -/// -/// [spec]: https://tc39.es/ecma262/#sec-get-regexp.prototype.source -/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp/source -fn get_source(this: &mut Value, _: &[Value], _: &mut Interpreter) -> ResultValue { - Ok(this.get_internal_slot("OriginalSource")) -} + /// `RegExp.prototype.unicode` + /// + /// The unicode property indicates whether or not the "`u`" flag is used with a regular expression. + /// unicode is a read-only property of an individual regular expression instance. + /// + /// More information: + /// - [ECMAScript reference][spec] + /// - [MDN documentation][mdn] + /// + /// [spec]: https://tc39.es/ecma262/#sec-get-regexp.prototype.unicode + /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp/unicode + fn get_unicode(this: &mut Value, _: &[Value], _: &mut Interpreter) -> ResultValue { + this.with_internal_state_ref(|regex: &RegExp| Ok(Value::from(regex.unicode))) + } -/// `RegExp.prototype.sticky` -/// -/// The `flags` property returns a string consisting of the [`flags`][flags] of the current regular expression object. -/// -/// More information: -/// - [ECMAScript reference][spec] -/// - [MDN documentation][mdn] -/// -/// [spec]: https://tc39.es/ecma262/#sec-get-regexp.prototype.sticky -/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp/sticky -fn get_sticky(this: &mut Value, _: &[Value], _: &mut Interpreter) -> ResultValue { - this.with_internal_state_ref(|regex: &RegExp| Ok(Value::from(regex.sticky))) -} + /// `RegExp.prototype.test( string )` + /// + /// The `test()` method executes a search for a match between a regular expression and a specified string. + /// + /// Returns `true` or `false`. + /// + /// More information: + /// - [ECMAScript reference][spec] + /// - [MDN documentation][mdn] + /// + /// [spec]: https://tc39.es/ecma262/#sec-regexp.prototype.test + /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp/test + pub(crate) fn test(this: &mut Value, args: &[Value], _: &mut Interpreter) -> ResultValue { + let arg_str = String::from(args.get(0).expect("could not get argument")); + let mut last_index = usize::from(&this.get_field("lastIndex")); + let result = this.with_internal_state_ref(|regex: &RegExp| { + let result = if let Some(m) = regex.matcher.find_at(arg_str.as_str(), last_index) { + if regex.use_last_index { + last_index = m.end(); + } + true + } else { + if regex.use_last_index { + last_index = 0; + } + false + }; + Ok(Value::boolean(result)) + }); + this.set_field("lastIndex", Value::from(last_index)); + result + } -/// `RegExp.prototype.unicode` -/// -/// The unicode property indicates whether or not the "`u`" flag is used with a regular expression. -/// unicode is a read-only property of an individual regular expression instance. -/// -/// More information: -/// - [ECMAScript reference][spec] -/// - [MDN documentation][mdn] -/// -/// [spec]: https://tc39.es/ecma262/#sec-get-regexp.prototype.unicode -/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp/unicode -fn get_unicode(this: &mut Value, _: &[Value], _: &mut Interpreter) -> ResultValue { - this.with_internal_state_ref(|regex: &RegExp| Ok(Value::from(regex.unicode))) -} + /// `RegExp.prototype.exec( string )` + /// + /// The exec() method executes a search for a match in a specified string. + /// + /// Returns a result array, or `null`. + /// + /// More information: + /// - [ECMAScript reference][spec] + /// - [MDN documentation][mdn] + /// + /// [spec]: https://tc39.es/ecma262/#sec-regexp.prototype.exec + /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp/exec + pub(crate) fn exec(this: &mut Value, args: &[Value], _: &mut Interpreter) -> ResultValue { + let arg_str = String::from(args.get(0).expect("could not get argument")); + let mut last_index = usize::from(&this.get_field("lastIndex")); + let result = this.with_internal_state_ref(|regex: &RegExp| { + let mut locations = regex.matcher.capture_locations(); + let result = if let Some(m) = + regex + .matcher + .captures_read_at(&mut locations, arg_str.as_str(), last_index) + { + if regex.use_last_index { + last_index = m.end(); + } + let mut result = Vec::with_capacity(locations.len()); + for i in 0..locations.len() { + if let Some((start, end)) = locations.get(i) { + result.push(Value::from( + arg_str.get(start..end).expect("Could not get slice"), + )); + } else { + result.push(Value::undefined()); + } + } -/// `RegExp.prototype.test( string )` -/// -/// The `test()` method executes a search for a match between a regular expression and a specified string. -/// -/// Returns `true` or `false`. -/// -/// More information: -/// - [ECMAScript reference][spec] -/// - [MDN documentation][mdn] -/// -/// [spec]: https://tc39.es/ecma262/#sec-regexp.prototype.test -/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp/test -pub fn test(this: &mut Value, args: &[Value], _: &mut Interpreter) -> ResultValue { - let arg_str = String::from(args.get(0).expect("could not get argument")); - let mut last_index = usize::from(&this.get_field_slice("lastIndex")); - let result = this.with_internal_state_ref(|regex: &RegExp| { - let result = if let Some(m) = regex.matcher.find_at(arg_str.as_str(), last_index) { - if regex.use_last_index { - last_index = m.end(); - } - true - } else { - if regex.use_last_index { - last_index = 0; - } - false - }; - Ok(Value::boolean(result)) - }); - this.set_field_slice("lastIndex", Value::from(last_index)); - result -} + let result = Value::from(result); + result + .set_property_slice("index", Property::default().value(Value::from(m.start()))); + result.set_property_slice("input", Property::default().value(Value::from(arg_str))); + result + } else { + if regex.use_last_index { + last_index = 0; + } + Value::null() + }; + Ok(result) + }); + this.set_field("lastIndex", Value::from(last_index)); + result + } -/// `RegExp.prototype.exec( string )` -/// -/// The exec() method executes a search for a match in a specified string. -/// -/// Returns a result array, or `null`. -/// -/// More information: -/// - [ECMAScript reference][spec] -/// - [MDN documentation][mdn] -/// -/// [spec]: https://tc39.es/ecma262/#sec-regexp.prototype.exec -/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp/exec -pub fn exec(this: &mut Value, args: &[Value], _: &mut Interpreter) -> ResultValue { - let arg_str = String::from(args.get(0).expect("could not get argument")); - let mut last_index = usize::from(&this.get_field_slice("lastIndex")); - let result = this.with_internal_state_ref(|regex: &RegExp| { - let mut locations = regex.matcher.capture_locations(); - let result = if let Some(m) = - regex - .matcher - .captures_read_at(&mut locations, arg_str.as_str(), last_index) - { - if regex.use_last_index { - last_index = m.end(); + /// `RegExp.prototype[ @@match ]( string )` + /// + /// This method retrieves the matches when matching a string against a regular expression. + /// + /// More information: + /// - [ECMAScript reference][spec] + /// - [MDN documentation][mdn] + /// + /// [spec]: https://tc39.es/ecma262/#sec-regexp.prototype-@@match + /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp/@@match + pub(crate) fn r#match(this: &mut Value, arg: String, ctx: &mut Interpreter) -> ResultValue { + let (matcher, flags) = this + .with_internal_state_ref(|regex: &RegExp| (regex.matcher.clone(), regex.flags.clone())); + if flags.contains('g') { + let mut matches = Vec::new(); + for mat in matcher.find_iter(&arg) { + matches.push(Value::from(mat.as_str())); } - let mut result = Vec::with_capacity(locations.len()); - for i in 0..locations.len() { - if let Some((start, end)) = locations.get(i) { - result.push(Value::from( - arg_str.get(start..end).expect("Could not get slice"), - )); - } else { - result.push(Value::undefined()); - } + if matches.is_empty() { + return Ok(Value::null()); } - - let result = Value::from(result); - result.set_property_slice("index", Property::default().value(Value::from(m.start()))); - result.set_property_slice("input", Property::default().value(Value::from(arg_str))); - result + Ok(Value::from(matches)) } else { - if regex.use_last_index { - last_index = 0; - } - Value::null() - }; - Ok(result) - }); - this.set_field_slice("lastIndex", Value::from(last_index)); - result -} - -/// `RegExp.prototype[ @@match ]( string )` -/// -/// This method retrieves the matches when matching a string against a regular expression. -/// -/// More information: -/// - [ECMAScript reference][spec] -/// - [MDN documentation][mdn] -/// -/// [spec]: https://tc39.es/ecma262/#sec-regexp.prototype-@@match -/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp/@@match -pub fn r#match(this: &mut Value, arg: String, ctx: &mut Interpreter) -> ResultValue { - let (matcher, flags) = - this.with_internal_state_ref(|regex: &RegExp| (regex.matcher.clone(), regex.flags.clone())); - if flags.contains('g') { - let mut matches = Vec::new(); - for mat in matcher.find_iter(&arg) { - matches.push(Value::from(mat.as_str())); + Self::exec(this, &[Value::from(arg)], ctx) } - if matches.is_empty() { - return Ok(Value::null()); - } - Ok(Value::from(matches)) - } else { - exec(this, &[Value::from(arg)], ctx) } -} -/// `RegExp.prototype.toString()` -/// -/// Return a string representing the regular expression. -/// -/// More information: -/// - [ECMAScript reference][spec] -/// - [MDN documentation][mdn] -/// -/// [spec]: https://tc39.es/ecma262/#sec-regexp.prototype.tostring -/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp/toString -pub fn to_string(this: &mut Value, _: &[Value], _: &mut Interpreter) -> ResultValue { - let body = String::from(&this.get_internal_slot("OriginalSource")); - let flags = this.with_internal_state_ref(|regex: &RegExp| regex.flags.clone()); - Ok(Value::from(format!("/{}/{}", body, flags))) -} + /// `RegExp.prototype.toString()` + /// + /// Return a string representing the regular expression. + /// + /// More information: + /// - [ECMAScript reference][spec] + /// - [MDN documentation][mdn] + /// + /// [spec]: https://tc39.es/ecma262/#sec-regexp.prototype.tostring + /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp/toString + #[allow(clippy::wrong_self_convention)] + pub(crate) fn to_string(this: &mut Value, _: &[Value], _: &mut Interpreter) -> ResultValue { + let body = String::from(&this.get_internal_slot("OriginalSource")); + let flags = this.with_internal_state_ref(|regex: &RegExp| regex.flags.clone()); + Ok(Value::from(format!("/{}/{}", body, flags))) + } -/// `RegExp.prototype[ @@matchAll ]( string )` -/// -/// The `[@@matchAll]` method returns all matches of the regular expression against a string. -/// -/// More information: -/// - [ECMAScript reference][spec] -/// - [MDN documentation][mdn] -/// -/// [spec]: https://tc39.es/ecma262/#sec-regexp-prototype-matchall -/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp/@@matchAll -// TODO: it's returning an array, it should return an iterator -pub fn match_all(this: &mut Value, arg_str: String) -> ResultValue { - let matches: Vec = this.with_internal_state_ref(|regex: &RegExp| { - let mut matches = Vec::new(); - - for m in regex.matcher.find_iter(&arg_str) { - if let Some(caps) = regex.matcher.captures(&m.as_str()) { - let match_vec = caps - .iter() - .map(|group| match group { - Some(g) => Value::from(g.as_str()), - None => Value::undefined(), - }) - .collect::>(); - - let match_val = Value::from(match_vec); - - match_val - .set_property_slice("index", Property::default().value(Value::from(m.start()))); - match_val.set_property_slice( - "input", - Property::default().value(Value::from(arg_str.clone())), - ); - matches.push(match_val); - - if !regex.flags.contains('g') { - break; + /// `RegExp.prototype[ @@matchAll ]( string )` + /// + /// The `[@@matchAll]` method returns all matches of the regular expression against a string. + /// + /// More information: + /// - [ECMAScript reference][spec] + /// - [MDN documentation][mdn] + /// + /// [spec]: https://tc39.es/ecma262/#sec-regexp-prototype-matchall + /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp/@@matchAll + // TODO: it's returning an array, it should return an iterator + pub(crate) fn match_all(this: &mut Value, arg_str: String) -> ResultValue { + let matches: Vec = this.with_internal_state_ref(|regex: &RegExp| { + let mut matches = Vec::new(); + + for m in regex.matcher.find_iter(&arg_str) { + if let Some(caps) = regex.matcher.captures(&m.as_str()) { + let match_vec = caps + .iter() + .map(|group| match group { + Some(g) => Value::from(g.as_str()), + None => Value::undefined(), + }) + .collect::>(); + + let match_val = Value::from(match_vec); + + match_val.set_property_slice( + "index", + Property::default().value(Value::from(m.start())), + ); + match_val.set_property_slice( + "input", + Property::default().value(Value::from(arg_str.clone())), + ); + matches.push(match_val); + + if !regex.flags.contains('g') { + break; + } } } - } - matches - }); + matches + }); - let length = matches.len(); - let result = Value::from(matches); - result.set_field_slice("length", Value::from(length)); - result.set_kind(ObjectKind::Array); + let length = matches.len(); + let result = Value::from(matches); + result.set_field("length", Value::from(length)); + result.set_kind(ObjectKind::Array); - Ok(result) -} + Ok(result) + } -/// Create a new `RegExp` object. -pub fn create(global: &Value) -> Value { - // Create prototype - let prototype = Value::new_object(Some(global)); - prototype.set_field_slice("lastIndex", Value::from(0)); - - make_builtin_fn!(test, named "test", with length 1, of prototype); - make_builtin_fn!(exec, named "exec", with length 1, of prototype); - make_builtin_fn!(to_string, named "toString", of prototype); - make_builtin_fn!(get_dot_all, named "dotAll", of prototype); - make_builtin_fn!(get_flags, named "flags", of prototype); - make_builtin_fn!(get_global, named "global", of prototype); - make_builtin_fn!(get_ignore_case, named "ignoreCase", of prototype); - make_builtin_fn!(get_multiline, named "multiline", of prototype); - make_builtin_fn!(get_source, named "source", of prototype); - make_builtin_fn!(get_sticky, named "sticky", of prototype); - make_builtin_fn!(get_unicode, named "unicode", of prototype); - - make_constructor_fn(make_regexp, global, prototype) -} + /// Create a new `RegExp` object. + pub(crate) fn create(global: &Value) -> Value { + // Create prototype + let prototype = Value::new_object(Some(global)); + prototype.set_field("lastIndex", Value::from(0)); + + make_builtin_fn(Self::test, "test", &prototype, 1); + make_builtin_fn(Self::exec, "exec", &prototype, 1); + make_builtin_fn(Self::to_string, "toString", &prototype, 0); + make_builtin_fn(Self::get_dot_all, "dotAll", &prototype, 0); + make_builtin_fn(Self::get_flags, "flags", &prototype, 0); + make_builtin_fn(Self::get_global, "global", &prototype, 0); + make_builtin_fn(Self::get_ignore_case, "ignoreCase", &prototype, 0); + make_builtin_fn(Self::get_multiline, "multiline", &prototype, 0); + make_builtin_fn(Self::get_source, "source", &prototype, 0); + make_builtin_fn(Self::get_sticky, "sticky", &prototype, 0); + make_builtin_fn(Self::get_unicode, "unicode", &prototype, 0); + + make_constructor_fn(Self::make_regexp, global, prototype) + } -/// Initialise the `RegExp` object on the global object. -#[inline] -pub fn init(global: &Value) { - global.set_field_slice("RegExp", create(global)); + /// Initialise the `RegExp` object on the global object. + #[inline] + pub(crate) fn init(global: &Value) { + global.set_field("RegExp", Self::create(global)); + } } diff --git a/boa/src/builtins/regexp/tests.rs b/boa/src/builtins/regexp/tests.rs index fbf388fc04b..984a392a231 100644 --- a/boa/src/builtins/regexp/tests.rs +++ b/boa/src/builtins/regexp/tests.rs @@ -20,7 +20,7 @@ fn constructors() { #[test] fn check_regexp_constructor_is_function() { let global = Value::new_object(None); - let regexp_constructor = create(&global); + let regexp_constructor = RegExp::create(&global); assert_eq!(regexp_constructor.is_function(), true); } diff --git a/boa/src/builtins/string/mod.rs b/boa/src/builtins/string/mod.rs index e3c1a665ca5..dc4da878320 100644 --- a/boa/src/builtins/string/mod.rs +++ b/boa/src/builtins/string/mod.rs @@ -12,716 +12,748 @@ #[cfg(test)] mod tests; -use super::function::make_constructor_fn; +use super::function::{make_builtin_fn, make_constructor_fn}; use crate::{ builtins::{ object::{Object, ObjectKind}, property::Property, - regexp::{make_regexp, match_all as regexp_match_all, r#match as regexp_match}, value::{ResultValue, Value, ValueData}, + RegExp, }, exec::Interpreter, }; use regex::Regex; +use std::string::String as StdString; use std::{ cmp::{max, min}, f64::NAN, ops::Deref, }; -/// [[Construct]] - Creates a new instance `this` -/// -/// [[Call]] - Returns a new native `string` -/// -pub fn make_string(this: &mut Value, args: &[Value], _: &mut Interpreter) -> ResultValue { - // This value is used by console.log and other routines to match Obexpecty"failed to parse argument for String method"pe - // to its Javascript Identifier (global constructor method name) - let s = args.get(0).unwrap_or(&Value::string("")).clone(); - let length_str = s.to_string().chars().count(); +/// JavaScript `String` implementation. +#[derive(Debug, Clone, Copy)] +pub(crate) struct String; + +impl String { + /// [[Construct]] - Creates a new instance `this` + /// + /// [[Call]] - Returns a new native `string` + /// + pub(crate) fn make_string( + this: &mut Value, + args: &[Value], + _: &mut Interpreter, + ) -> ResultValue { + // This value is used by console.log and other routines to match Obexpecty"failed to parse argument for String method"pe + // to its Javascript Identifier (global constructor method name) + let s = args.get(0).unwrap_or(&Value::string("")).clone(); + let length_str = s.to_string().chars().count(); + + this.set_field("length", Value::from(length_str as i32)); + + this.set_kind(ObjectKind::String); + this.set_internal_slot("StringData", s); + + let arg = match args.get(0) { + Some(v) => v.clone(), + None => Value::undefined(), + }; + + if arg.is_undefined() { + return Ok("".into()); + } + + Ok(Value::from(arg.to_string())) + } + + /// Get the string value to a primitive string + #[allow(clippy::wrong_self_convention)] + pub(crate) fn to_string(this: &mut Value, _: &[Value], _: &mut Interpreter) -> ResultValue { + // Get String from String Object and send it back as a new value + let primitive_val = this.get_internal_slot("StringData"); + Ok(Value::from(format!("{}", primitive_val))) + } - this.set_field_slice("length", Value::from(length_str as i32)); + /// `String.prototype.charAt( index )` + /// + /// The `String` object's `charAt()` method returns a new string consisting of the single UTF-16 code unit located at the specified offset into the string. + /// + /// Characters in a string are indexed from left to right. The index of the first character is `0`, + /// and the index of the last character—in a string called `stringName`—is `stringName.length - 1`. + /// If the `index` you supply is out of this range, JavaScript returns an empty string. + /// + /// If no index is provided to `charAt()`, the default is `0`. + /// + /// More information: + /// - [ECMAScript reference][spec] + /// - [MDN documentation][mdn] + /// + /// [spec]: https://tc39.es/ecma262/#sec-string.prototype.charat + /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/charAt + pub(crate) fn char_at(this: &mut Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue { + // First we get it the actual string a private field stored on the object only the engine has access to. + // Then we convert it into a Rust String by wrapping it in from_value + let primitive_val = ctx.value_to_rust_string(this); + let pos = i32::from( + args.get(0) + .expect("failed to get argument for String method"), + ); - this.set_kind(ObjectKind::String); - this.set_internal_slot("StringData", s); + // Calling .len() on a string would give the wrong result, as they are bytes not the number of + // unicode code points + // Note that this is an O(N) operation (because UTF-8 is complex) while getting the number of + // bytes is an O(1) operation. + let length = primitive_val.chars().count(); - let arg = match args.get(0) { - Some(v) => v.clone(), - None => Value::undefined(), - }; + // We should return an empty string is pos is out of range + if pos >= length as i32 || pos < 0 { + return Ok("".into()); + } - if arg.is_undefined() { - return Ok(Value::from(String::new())); + Ok(Value::from( + primitive_val + .chars() + .nth(pos as usize) + .expect("failed to get value"), + )) } - Ok(Value::from(arg.to_string())) -} + /// `String.prototype.charCodeAt( index )` + /// + /// The `charCodeAt()` method returns an integer between `0` and `65535` representing the UTF-16 code unit at the given index. + /// + /// Unicode code points range from `0` to `1114111` (`0x10FFFF`). The first 128 Unicode code points are a direct match of the ASCII character encoding. + /// + /// `charCodeAt()` returns `NaN` if the given index is less than `0`, or if it is equal to or greater than the `length` of the string. + /// + /// More information: + /// - [ECMAScript reference][spec] + /// - [MDN documentation][mdn] + /// + /// [spec]: https://tc39.es/ecma262/#sec-string.prototype.charcodeat + /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/charCodeAt + pub(crate) fn char_code_at( + this: &mut Value, + args: &[Value], + ctx: &mut Interpreter, + ) -> ResultValue { + // First we get it the actual string a private field stored on the object only the engine has access to. + // Then we convert it into a Rust String by wrapping it in from_value + let primitive_val = ctx.value_to_rust_string(this); + + // Calling .len() on a string would give the wrong result, as they are bytes not the number of unicode code points + // Note that this is an O(N) operation (because UTF-8 is complex) while getting the number of bytes is an O(1) operation. + let length = primitive_val.chars().count(); + let pos = i32::from( + args.get(0) + .expect("failed to get argument for String method"), + ); -/// Get the string value to a primitive string -pub fn to_string(this: &mut Value, _: &[Value], _: &mut Interpreter) -> ResultValue { - // Get String from String Object and send it back as a new value - let primitive_val = this.get_internal_slot("StringData"); - Ok(Value::from(format!("{}", primitive_val))) -} + if pos >= length as i32 || pos < 0 { + return Ok(Value::from(NAN)); + } -/// `String.prototype.charAt( index )` -/// -/// The `String` object's `charAt()` method returns a new string consisting of the single UTF-16 code unit located at the specified offset into the string. -/// -/// Characters in a string are indexed from left to right. The index of the first character is `0`, -/// and the index of the last character—in a string called `stringName`—is `stringName.length - 1`. -/// If the `index` you supply is out of this range, JavaScript returns an empty string. -/// -/// If no index is provided to `charAt()`, the default is `0`. -/// -/// More information: -/// - [ECMAScript reference][spec] -/// - [MDN documentation][mdn] -/// -/// [spec]: https://tc39.es/ecma262/#sec-string.prototype.charat -/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/charAt -pub fn char_at(this: &mut Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue { - // First we get it the actual string a private field stored on the object only the engine has access to. - // Then we convert it into a Rust String by wrapping it in from_value - let primitive_val = ctx.value_to_rust_string(this); - let pos = i32::from( - args.get(0) - .expect("failed to get argument for String method"), - ); - - // Calling .len() on a string would give the wrong result, as they are bytes not the number of - // unicode code points - // Note that this is an O(N) operation (because UTF-8 is complex) while getting the number of - // bytes is an O(1) operation. - let length = primitive_val.chars().count(); - - // We should return an empty string is pos is out of range - if pos >= length as i32 || pos < 0 { - return Ok(Value::from(String::new())); + let utf16_val = primitive_val + .encode_utf16() + .nth(pos as usize) + .expect("failed to get utf16 value"); + // If there is no element at that index, the result is NaN + // TODO: We currently don't have NaN + Ok(Value::from(f64::from(utf16_val))) } - Ok(Value::from( - primitive_val - .chars() - .nth(pos as usize) - .expect("failed to get value"), - )) -} + /// `String.prototype.concat( str1[, ...strN] )` + /// + /// The `concat()` method concatenates the string arguments to the calling string and returns a new string. + /// + /// Changes to the original string or the returned string don't affect the other. + /// + /// If the arguments are not of the type string, they are converted to string values before concatenating. + /// + /// More information: + /// - [ECMAScript reference][spec] + /// - [MDN documentation][mdn] + /// + /// [spec]: https://tc39.es/ecma262/#sec-string.prototype.concat + /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/concat + pub(crate) fn concat(this: &mut Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue { + // First we get it the actual string a private field stored on the object only the engine has access to. + // Then we convert it into a Rust String by wrapping it in from_value + let mut new_str = ctx.value_to_rust_string(this); + + for arg in args { + let concat_str = arg.to_string(); + new_str.push_str(&concat_str); + } -/// `String.prototype.charCodeAt( index )` -/// -/// The `charCodeAt()` method returns an integer between `0` and `65535` representing the UTF-16 code unit at the given index. -/// -/// Unicode code points range from `0` to `1114111` (`0x10FFFF`). The first 128 Unicode code points are a direct match of the ASCII character encoding. -/// -/// `charCodeAt()` returns `NaN` if the given index is less than `0`, or if it is equal to or greater than the `length` of the string. -/// -/// More information: -/// - [ECMAScript reference][spec] -/// - [MDN documentation][mdn] -/// -/// [spec]: https://tc39.es/ecma262/#sec-string.prototype.charcodeat -/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/charCodeAt -pub fn char_code_at(this: &mut Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue { - // First we get it the actual string a private field stored on the object only the engine has access to. - // Then we convert it into a Rust String by wrapping it in from_value - let primitive_val: String = ctx.value_to_rust_string(this); - - // Calling .len() on a string would give the wrong result, as they are bytes not the number of unicode code points - // Note that this is an O(N) operation (because UTF-8 is complex) while getting the number of bytes is an O(1) operation. - let length = primitive_val.chars().count(); - let pos = i32::from( - args.get(0) - .expect("failed to get argument for String method"), - ); - - if pos >= length as i32 || pos < 0 { - return Ok(Value::from(NAN)); + Ok(Value::from(new_str)) } - let utf16_val = primitive_val - .encode_utf16() - .nth(pos as usize) - .expect("failed to get utf16 value"); - // If there is no element at that index, the result is NaN - // TODO: We currently don't have NaN - Ok(Value::from(f64::from(utf16_val))) -} + /// `String.prototype.repeat( count )` + /// + /// The `repeat()` method constructs and returns a new string which contains the specified number of + /// copies of the string on which it was called, concatenated together. + /// + /// More information: + /// - [ECMAScript reference][spec] + /// - [MDN documentation][mdn] + /// + /// [spec]: https://tc39.es/ecma262/#sec-string.prototype.repeat + /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/repeat + pub(crate) fn repeat(this: &mut Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue { + // First we get it the actual string a private field stored on the object only the engine has access to. + // Then we convert it into a Rust String by wrapping it in from_value + let primitive_val = ctx.value_to_rust_string(this); + + let repeat_times = usize::from( + args.get(0) + .expect("failed to get argument for String method"), + ); -/// `String.prototype.concat( str1[, ...strN] )` -/// -/// The `concat()` method concatenates the string arguments to the calling string and returns a new string. -/// -/// Changes to the original string or the returned string don't affect the other. -/// -/// If the arguments are not of the type string, they are converted to string values before concatenating. -/// -/// More information: -/// - [ECMAScript reference][spec] -/// - [MDN documentation][mdn] -/// -/// [spec]: https://tc39.es/ecma262/#sec-string.prototype.concat -/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/concat -pub fn concat(this: &mut Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue { - // First we get it the actual string a private field stored on the object only the engine has access to. - // Then we convert it into a Rust String by wrapping it in from_value - let mut new_str = ctx.value_to_rust_string(this); - - for arg in args { - let concat_str = String::from(arg); - new_str.push_str(&concat_str); + Ok(Value::from(primitive_val.repeat(repeat_times))) } - Ok(Value::from(new_str)) -} + /// `String.prototype.slice( beginIndex [, endIndex] )` + /// + /// The `slice()` method extracts a section of a string and returns it as a new string, without modifying the original string. + /// + /// More information: + /// - [ECMAScript reference][spec] + /// - [MDN documentation][mdn] + /// + /// [spec]: https://tc39.es/ecma262/#sec-string.prototype.slice + /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/slice + pub(crate) fn slice(this: &mut Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue { + // First we get it the actual string a private field stored on the object only the engine has access to. + // Then we convert it into a Rust String by wrapping it in from_value + let primitive_val = ctx.value_to_rust_string(this); + + let start = i32::from( + args.get(0) + .expect("failed to get argument for String method"), + ); -/// `String.prototype.repeat( count )` -/// -/// The `repeat()` method constructs and returns a new string which contains the specified number of -/// copies of the string on which it was called, concatenated together. -/// -/// More information: -/// - [ECMAScript reference][spec] -/// - [MDN documentation][mdn] -/// -/// [spec]: https://tc39.es/ecma262/#sec-string.prototype.repeat -/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/repeat -pub fn repeat(this: &mut Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue { - // First we get it the actual string a private field stored on the object only the engine has access to. - // Then we convert it into a Rust String by wrapping it in from_value - let primitive_val: String = ctx.value_to_rust_string(this); - - let repeat_times = usize::from( - args.get(0) - .expect("failed to get argument for String method"), - ); - - Ok(Value::from(primitive_val.repeat(repeat_times))) -} + let end = i32::from(args.get(1).expect("failed to get argument in slice")); -/// `String.prototype.slice( beginIndex [, endIndex] )` -/// -/// The `slice()` method extracts a section of a string and returns it as a new string, without modifying the original string. -/// -/// More information: -/// - [ECMAScript reference][spec] -/// - [MDN documentation][mdn] -/// -/// [spec]: https://tc39.es/ecma262/#sec-string.prototype.slice -/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/slice -pub fn slice(this: &mut Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue { - // First we get it the actual string a private field stored on the object only the engine has access to. - // Then we convert it into a Rust String by wrapping it in from_value - let primitive_val: String = ctx.value_to_rust_string(this); - - let start = i32::from( - args.get(0) - .expect("failed to get argument for String method"), - ); - - let end = i32::from(args.get(1).expect("failed to get argument in slice")); - - // Calling .len() on a string would give the wrong result, as they are bytes not the number of unicode code points - // Note that this is an O(N) operation (because UTF-8 is complex) while getting the number of bytes is an O(1) operation. - let length: i32 = primitive_val.chars().count() as i32; - - let from: i32 = if start < 0 { - max(length.wrapping_add(start), 0) - } else { - min(start, length) - }; - let to: i32 = if end < 0 { - max(length.wrapping_add(end), 0) - } else { - min(end, length) - }; - - let span = max(to.wrapping_sub(from), 0); - - let mut new_str = String::new(); - for i in from..from.wrapping_add(span) { - new_str.push( - primitive_val - .chars() - .nth(i as usize) - .expect("Could not get nth char"), - ); - } - Ok(Value::from(new_str)) -} + // Calling .len() on a string would give the wrong result, as they are bytes not the number of unicode code points + // Note that this is an O(N) operation (because UTF-8 is complex) while getting the number of bytes is an O(1) operation. + let length = primitive_val.chars().count() as i32; -/// `String.prototype.startWith( searchString[, position] )` -/// -/// The `startsWith()` method determines whether a string begins with the characters of a specified string, returning `true` or `false` as appropriate. -/// -/// More information: -/// - [ECMAScript reference][spec] -/// - [MDN documentation][mdn] -/// -/// [spec]: https://tc39.es/ecma262/#sec-string.prototype.startswith -/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/startsWith -pub fn starts_with(this: &mut Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue { - // First we get it the actual string a private field stored on the object only the engine has access to. - // Then we convert it into a Rust String by wrapping it in from_value - let primitive_val: String = ctx.value_to_rust_string(this); - - // TODO: Should throw TypeError if pattern is regular expression - let search_string = String::from( - args.get(0) - .expect("failed to get argument for String method"), - ); - - let length: i32 = primitive_val.chars().count() as i32; - let search_length: i32 = search_string.chars().count() as i32; - - // If less than 2 args specified, position is 'undefined', defaults to 0 - let position: i32 = if args.len() < 2 { - 0 - } else { - i32::from(args.get(1).expect("failed to get arg")) - }; - - let start = min(max(position, 0), length); - let end = start.wrapping_add(search_length); - - if end > length { - Ok(Value::from(false)) - } else { - // Only use the part of the string from "start" - let this_string: String = primitive_val.chars().skip(start as usize).collect(); - Ok(Value::from(this_string.starts_with(&search_string))) - } -} + let from = if start < 0 { + max(length.wrapping_add(start), 0) + } else { + min(start, length) + }; + let to = if end < 0 { + max(length.wrapping_add(end), 0) + } else { + min(end, length) + }; + + let span = max(to.wrapping_sub(from), 0); -/// `String.prototype.endsWith( searchString[, length] )` -/// -/// The `endsWith()` method determines whether a string ends with the characters of a specified string, returning `true` or `false` as appropriate. -/// -/// More information: -/// - [ECMAScript reference][spec] -/// - [MDN documentation][mdn] -/// -/// [spec]: https://tc39.es/ecma262/#sec-string.prototype.endswith -/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/endsWith -pub fn ends_with(this: &mut Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue { - // First we get it the actual string a private field stored on the object only the engine has access to. - // Then we convert it into a Rust String by wrapping it in from_value - let primitive_val: String = ctx.value_to_rust_string(this); - - // TODO: Should throw TypeError if search_string is regular expression - let search_string = String::from( - args.get(0) - .expect("failed to get argument for String method"), - ); - - let length: i32 = primitive_val.chars().count() as i32; - let search_length: i32 = search_string.chars().count() as i32; - - // If less than 2 args specified, end_position is 'undefined', defaults to - // length of this - let end_position: i32 = if args.len() < 2 { - length - } else { - i32::from(args.get(1).expect("Could not get argumetn")) - }; - - let end = min(max(end_position, 0), length); - let start = end.wrapping_sub(search_length); - - if start < 0 { - Ok(Value::from(false)) - } else { - // Only use the part of the string up to "end" - let this_string: String = primitive_val.chars().take(end as usize).collect(); - Ok(Value::from(this_string.ends_with(&search_string))) + let new_str: StdString = primitive_val + .chars() + .skip(from as usize) + .take(span as usize) + .collect(); + Ok(Value::from(new_str)) } -} -/// `String.prototype.includes( searchString[, position] )` -/// -/// The `includes()` method determines whether one string may be found within another string, returning `true` or `false` as appropriate. -/// -/// More information: -/// - [ECMAScript reference][spec] -/// - [MDN documentation][mdn] -/// -/// [spec]: https://tc39.es/ecma262/#sec-string.prototype.includes -/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/includes -pub fn includes(this: &mut Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue { - // First we get it the actual string a private field stored on the object only the engine has access to. - // Then we convert it into a Rust String by wrapping it in from_value - let primitive_val: String = ctx.value_to_rust_string(this); - - // TODO: Should throw TypeError if search_string is regular expression - let search_string = String::from( - args.get(0) - .expect("failed to get argument for String method"), - ); - - let length: i32 = primitive_val.chars().count() as i32; - - // If less than 2 args specified, position is 'undefined', defaults to 0 - let position: i32 = if args.len() < 2 { - 0 - } else { - i32::from(args.get(1).expect("Could not get argument")) - }; - - let start = min(max(position, 0), length); - - // Take the string from "this" and use only the part of it after "start" - let this_string: String = primitive_val.chars().skip(start as usize).collect(); - - Ok(Value::from(this_string.contains(&search_string))) -} + /// `String.prototype.startWith( searchString[, position] )` + /// + /// The `startsWith()` method determines whether a string begins with the characters of a specified string, returning `true` or `false` as appropriate. + /// + /// More information: + /// - [ECMAScript reference][spec] + /// - [MDN documentation][mdn] + /// + /// [spec]: https://tc39.es/ecma262/#sec-string.prototype.startswith + /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/startsWith + pub(crate) fn starts_with( + this: &mut Value, + args: &[Value], + ctx: &mut Interpreter, + ) -> ResultValue { + // First we get it the actual string a private field stored on the object only the engine has access to. + // Then we convert it into a Rust String by wrapping it in from_value + let primitive_val = ctx.value_to_rust_string(this); + + // TODO: Should throw TypeError if pattern is regular expression + let search_string = StdString::from( + args.get(0) + .expect("failed to get argument for String method"), + ); -/// Return either the string itself or the string of the regex equivalent -fn get_regex_string(value: &Value) -> String { - match value.deref() { - ValueData::String(ref body) => body.into(), - ValueData::Object(ref obj) => { - let slots = &obj.borrow().internal_slots; - if slots.get("RegExpMatcher").is_some() { - // first argument is another `RegExp` object, so copy its pattern and flags - if let Some(body) = slots.get("OriginalSource") { - return String::from(r#body); - } - } - "undefined".to_string() + let length = primitive_val.chars().count() as i32; + let search_length = search_string.chars().count() as i32; + + // If less than 2 args specified, position is 'undefined', defaults to 0 + let position = if args.len() < 2 { + 0 + } else { + i32::from(args.get(1).expect("failed to get arg")) + }; + + let start = min(max(position, 0), length); + let end = start.wrapping_add(search_length); + + if end > length { + Ok(Value::from(false)) + } else { + // Only use the part of the string from "start" + let this_string: StdString = primitive_val.chars().skip(start as usize).collect(); + Ok(Value::from(this_string.starts_with(&search_string))) } - _ => "undefined".to_string(), } -} -/// `String.prototype.replace( regexp|substr, newSubstr|function )` -/// -/// The `replace()` method returns a new string with some or all matches of a `pattern` replaced by a `replacement`. -/// -/// The `pattern` can be a string or a `RegExp`, and the `replacement` can be a string or a function to be called for each match. -/// If `pattern` is a string, only the first occurrence will be replaced. -/// -/// The original string is left unchanged. -/// -/// More information: -/// - [ECMAScript reference][spec] -/// - [MDN documentation][mdn] -/// -/// [spec]: https://tc39.es/ecma262/#sec-string.prototype.replace -/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/replace -pub fn replace(this: &mut Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue { - // TODO: Support Symbol replacer - let primitive_val: String = ctx.value_to_rust_string(this); - if args.is_empty() { - return Ok(Value::from(primitive_val)); + /// `String.prototype.endsWith( searchString[, length] )` + /// + /// The `endsWith()` method determines whether a string ends with the characters of a specified string, returning `true` or `false` as appropriate. + /// + /// More information: + /// - [ECMAScript reference][spec] + /// - [MDN documentation][mdn] + /// + /// [spec]: https://tc39.es/ecma262/#sec-string.prototype.endswith + /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/endsWith + pub(crate) fn ends_with( + this: &mut Value, + args: &[Value], + ctx: &mut Interpreter, + ) -> ResultValue { + // First we get it the actual string a private field stored on the object only the engine has access to. + // Then we convert it into a Rust String by wrapping it in from_value + let primitive_val = ctx.value_to_rust_string(this); + + // TODO: Should throw TypeError if search_string is regular expression + let search_string = StdString::from( + args.get(0) + .expect("failed to get argument for String method"), + ); + + let length = primitive_val.chars().count() as i32; + let search_length = search_string.chars().count() as i32; + + // If less than 2 args specified, end_position is 'undefined', defaults to + // length of this + let end_position = if args.len() < 2 { + length + } else { + i32::from(args.get(1).expect("Could not get argumetn")) + }; + + let end = min(max(end_position, 0), length); + let start = end.wrapping_sub(search_length); + + if start < 0 { + Ok(Value::from(false)) + } else { + // Only use the part of the string up to "end" + let this_string: StdString = primitive_val.chars().take(end as usize).collect(); + Ok(Value::from(this_string.ends_with(&search_string))) + } } - let regex_body = get_regex_string(args.get(0).expect("Value needed")); - let re = Regex::new(®ex_body).expect("unable to convert regex to regex object"); - let mat = re.find(&primitive_val).expect("unable to find value"); - let caps = re - .captures(&primitive_val) - .expect("unable to get capture groups from text"); - - let replace_value = if args.len() > 1 { - // replace_object could be a string or function or not exist at all - let replace_object: &Value = args.get(1).expect("second argument expected"); - match replace_object.deref() { - ValueData::String(val) => { - // https://tc39.es/ecma262/#table-45 - let mut result: String = val.to_string(); - let re = Regex::new(r"\$(\d)").unwrap(); - - if val.find("$$").is_some() { - result = val.replace("$$", "$") - } + /// `String.prototype.includes( searchString[, position] )` + /// + /// The `includes()` method determines whether one string may be found within another string, returning `true` or `false` as appropriate. + /// + /// More information: + /// - [ECMAScript reference][spec] + /// - [MDN documentation][mdn] + /// + /// [spec]: https://tc39.es/ecma262/#sec-string.prototype.includes + /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/includes + pub(crate) fn includes(this: &mut Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue { + // First we get it the actual string a private field stored on the object only the engine has access to. + // Then we convert it into a Rust String by wrapping it in from_value + let primitive_val = ctx.value_to_rust_string(this); + + // TODO: Should throw TypeError if search_string is regular expression + let search_string = StdString::from( + args.get(0) + .expect("failed to get argument for String method"), + ); - if val.find("$`").is_some() { - let start_of_match = mat.start(); - let slice = &primitive_val[..start_of_match]; - result = val.replace("$`", slice); - } + let length = primitive_val.chars().count() as i32; - if val.find("$'").is_some() { - let end_of_match = mat.end(); - let slice = &primitive_val[end_of_match..]; - result = val.replace("$'", slice); - } + // If less than 2 args specified, position is 'undefined', defaults to 0 + let position = if args.len() < 2 { + 0 + } else { + i32::from(args.get(1).expect("Could not get argument")) + }; - if val.find("$&").is_some() { - // get matched value - let matched = caps.get(0).expect("cannot get matched value"); - result = val.replace("$&", matched.as_str()); - } + let start = min(max(position, 0), length); - // Capture $1, $2, $3 etc - if re.is_match(&result) { - let mat_caps = re.captures(&result).unwrap(); - let group_str = mat_caps.get(1).unwrap().as_str(); - let group_int = group_str.parse::().unwrap(); - result = re - .replace(result.as_str(), caps.get(group_int).unwrap().as_str()) - .to_string() - } + // Take the string from "this" and use only the part of it after "start" + let this_string: StdString = primitive_val.chars().skip(start as usize).collect(); - result - } - ValueData::Object(_) => { - // This will return the matched substring first, then captured parenthesized groups later - let mut results: Vec = caps - .iter() - .map(|capture| Value::from(capture.unwrap().as_str())) - .collect(); - - // Returns the starting byte offset of the match - let start = caps - .get(0) - .expect("Unable to get Byte offset from string for match") - .start(); - results.push(Value::from(start)); - // Push the whole string being examined - results.push(Value::from(primitive_val.to_string())); - - let result = ctx.call(&replace_object, this, &results).unwrap(); - - ctx.value_to_rust_string(&result) + Ok(Value::from(this_string.contains(&search_string))) + } + + /// Return either the string itself or the string of the regex equivalent + fn get_regex_string(value: &Value) -> StdString { + match value.deref() { + ValueData::String(ref body) => body.into(), + ValueData::Object(ref obj) => { + let slots = &obj.borrow().internal_slots; + if slots.get("RegExpMatcher").is_some() { + // first argument is another `RegExp` object, so copy its pattern and flags + if let Some(body) = slots.get("OriginalSource") { + return body.to_string(); + } + } + "undefined".to_string() } _ => "undefined".to_string(), } - } else { - "undefined".to_string() - }; - - Ok(Value::from(primitive_val.replacen( - &mat.as_str(), - &replace_value, - 1, - ))) -} + } -/// `String.prototype.indexOf( searchValue[, fromIndex] )` -/// -/// The `indexOf()` method returns the index within the calling `String` object of the first occurrence of the specified value, starting the search at `fromIndex`. -/// -/// Returns -1 if the value is not found. -/// -/// More information: -/// - [ECMAScript reference][spec] -/// - [MDN documentation][mdn] -/// -/// [spec]: https://tc39.es/ecma262/#sec-string.prototype.indexof -/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/indexOf -pub fn index_of(this: &mut Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue { - // First we get it the actual string a private field stored on the object only the engine has access to. - // Then we convert it into a Rust String by wrapping it in from_value - let primitive_val: String = ctx.value_to_rust_string(this); - - // TODO: Should throw TypeError if search_string is regular expression - let search_string = String::from( - args.get(0) - .expect("failed to get argument for String method"), - ); - - let length: i32 = primitive_val.chars().count() as i32; - - // If less than 2 args specified, position is 'undefined', defaults to 0 - let position: i32 = if args.len() < 2 { - 0 - } else { - i32::from(args.get(1).expect("Could not get argument")) - }; - - let start = min(max(position, 0), length); - - // Here cannot use the &str method "find", because this returns the byte - // index: we need to return the char index in the JS String - // Instead, iterate over the part we're checking until the slice we're - // checking "starts with" the search string - for index in start..length { - let this_string: String = primitive_val.chars().skip(index as usize).collect(); - if this_string.starts_with(&search_string) { - // Explicitly return early with the index value - return Ok(Value::from(index)); + /// `String.prototype.replace( regexp|substr, newSubstr|function )` + /// + /// The `replace()` method returns a new string with some or all matches of a `pattern` replaced by a `replacement`. + /// + /// The `pattern` can be a string or a `RegExp`, and the `replacement` can be a string or a function to be called for each match. + /// If `pattern` is a string, only the first occurrence will be replaced. + /// + /// The original string is left unchanged. + /// + /// More information: + /// - [ECMAScript reference][spec] + /// - [MDN documentation][mdn] + /// + /// [spec]: https://tc39.es/ecma262/#sec-string.prototype.replace + /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/replace + pub(crate) fn replace(this: &mut Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue { + // TODO: Support Symbol replacer + let primitive_val = ctx.value_to_rust_string(this); + if args.is_empty() { + return Ok(Value::from(primitive_val)); } + + let regex_body = Self::get_regex_string(args.get(0).expect("Value needed")); + let re = Regex::new(®ex_body).expect("unable to convert regex to regex object"); + let mat = re.find(&primitive_val).expect("unable to find value"); + let caps = re + .captures(&primitive_val) + .expect("unable to get capture groups from text"); + + let replace_value = if args.len() > 1 { + // replace_object could be a string or function or not exist at all + let replace_object: &Value = args.get(1).expect("second argument expected"); + match replace_object.deref() { + ValueData::String(val) => { + // https://tc39.es/ecma262/#table-45 + let mut result = val.to_string(); + let re = Regex::new(r"\$(\d)").unwrap(); + + if val.find("$$").is_some() { + result = val.replace("$$", "$") + } + + if val.find("$`").is_some() { + let start_of_match = mat.start(); + let slice = &primitive_val[..start_of_match]; + result = val.replace("$`", slice); + } + + if val.find("$'").is_some() { + let end_of_match = mat.end(); + let slice = &primitive_val[end_of_match..]; + result = val.replace("$'", slice); + } + + if val.find("$&").is_some() { + // get matched value + let matched = caps.get(0).expect("cannot get matched value"); + result = val.replace("$&", matched.as_str()); + } + + // Capture $1, $2, $3 etc + if re.is_match(&result) { + let mat_caps = re.captures(&result).unwrap(); + let group_str = mat_caps.get(1).unwrap().as_str(); + let group_int = group_str.parse::().unwrap(); + result = re + .replace(result.as_str(), caps.get(group_int).unwrap().as_str()) + .to_string() + } + + result + } + ValueData::Object(_) => { + // This will return the matched substring first, then captured parenthesized groups later + let mut results: Vec = caps + .iter() + .map(|capture| Value::from(capture.unwrap().as_str())) + .collect(); + + // Returns the starting byte offset of the match + let start = caps + .get(0) + .expect("Unable to get Byte offset from string for match") + .start(); + results.push(Value::from(start)); + // Push the whole string being examined + results.push(Value::from(primitive_val.to_string())); + + let result = ctx.call(&replace_object, this, &results).unwrap(); + + ctx.value_to_rust_string(&result) + } + _ => "undefined".to_string(), + } + } else { + "undefined".to_string() + }; + + Ok(Value::from(primitive_val.replacen( + &mat.as_str(), + &replace_value, + 1, + ))) } - // Didn't find a match, so return -1 - Ok(Value::from(-1)) -} -/// `String.prototype.lastIndexOf( searchValue[, fromIndex] )` -/// -/// The `lastIndexOf()` method returns the index within the calling `String` object of the last occurrence of the specified value, searching backwards from `fromIndex`. -/// -/// Returns -1 if the value is not found. -/// -/// More information: -/// - [ECMAScript reference][spec] -/// - [MDN documentation][mdn] -/// -/// [spec]: https://tc39.es/ecma262/#sec-string.prototype.lastindexof -/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/lastIndexOf -pub fn last_index_of(this: &mut Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue { - // First we get it the actual string a private field stored on the object only the engine has access to. - // Then we convert it into a Rust String by wrapping it in from_value - let primitive_val: String = ctx.value_to_rust_string(this); - - // TODO: Should throw TypeError if search_string is regular expression - let search_string = String::from( - args.get(0) - .expect("failed to get argument for String method"), - ); - - let length: i32 = primitive_val.chars().count() as i32; - - // If less than 2 args specified, position is 'undefined', defaults to 0 - let position: i32 = if args.len() < 2 { - 0 - } else { - i32::from(args.get(1).expect("Could not get argument")) - }; - - let start = min(max(position, 0), length); - - // Here cannot use the &str method "rfind", because this returns the last - // byte index: we need to return the last char index in the JS String - // Instead, iterate over the part we're checking keeping track of the higher - // index we found that "starts with" the search string - let mut highest_index: i32 = -1; - for index in start..length { - let this_string: String = primitive_val.chars().skip(index as usize).collect(); - if this_string.starts_with(&search_string) { - highest_index = index; + /// `String.prototype.indexOf( searchValue[, fromIndex] )` + /// + /// The `indexOf()` method returns the index within the calling `String` object of the first occurrence of the specified value, starting the search at `fromIndex`. + /// + /// Returns -1 if the value is not found. + /// + /// More information: + /// - [ECMAScript reference][spec] + /// - [MDN documentation][mdn] + /// + /// [spec]: https://tc39.es/ecma262/#sec-string.prototype.indexof + /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/indexOf + pub(crate) fn index_of(this: &mut Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue { + // First we get it the actual string a private field stored on the object only the engine has access to. + // Then we convert it into a Rust String by wrapping it in from_value + let primitive_val = ctx.value_to_rust_string(this); + + // TODO: Should throw TypeError if search_string is regular expression + let search_string = StdString::from( + args.get(0) + .expect("failed to get argument for String method"), + ); + + let length = primitive_val.chars().count() as i32; + + // If less than 2 args specified, position is 'undefined', defaults to 0 + let position = if args.len() < 2 { + 0 + } else { + i32::from(args.get(1).expect("Could not get argument")) + }; + + let start = min(max(position, 0), length); + + // Here cannot use the &str method "find", because this returns the byte + // index: we need to return the char index in the JS String + // Instead, iterate over the part we're checking until the slice we're + // checking "starts with" the search string + for index in start..length { + let this_string: StdString = primitive_val.chars().skip(index as usize).collect(); + if this_string.starts_with(&search_string) { + // Explicitly return early with the index value + return Ok(Value::from(index)); + } } + // Didn't find a match, so return -1 + Ok(Value::from(-1)) } - // This will still be -1 if no matches were found, else with be >= 0 - Ok(Value::from(highest_index)) -} + /// `String.prototype.lastIndexOf( searchValue[, fromIndex] )` + /// + /// The `lastIndexOf()` method returns the index within the calling `String` object of the last occurrence of the specified value, searching backwards from `fromIndex`. + /// + /// Returns -1 if the value is not found. + /// + /// More information: + /// - [ECMAScript reference][spec] + /// - [MDN documentation][mdn] + /// + /// [spec]: https://tc39.es/ecma262/#sec-string.prototype.lastindexof + /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/lastIndexOf + pub(crate) fn last_index_of( + this: &mut Value, + args: &[Value], + ctx: &mut Interpreter, + ) -> ResultValue { + // First we get it the actual string a private field stored on the object only the engine has access to. + // Then we convert it into a Rust String by wrapping it in from_value + let primitive_val = ctx.value_to_rust_string(this); + + // TODO: Should throw TypeError if search_string is regular expression + let search_string = StdString::from( + args.get(0) + .expect("failed to get argument for String method"), + ); -/// `String.prototype.match( regexp )` -/// -/// The `match()` method retrieves the result of matching a **string** against a [`regular expression`][regex]. -/// -/// More information: -/// - [ECMAScript reference][spec] -/// - [MDN documentation][mdn] -/// -/// [spec]: https://tc39.es/ecma262/#sec-string.prototype.match -/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/match -/// [regex]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions -pub fn r#match(this: &mut Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue { - let mut re = make_regexp(&mut Value::from(Object::default()), &[args[0].clone()], ctx)?; - regexp_match(&mut re, ctx.value_to_rust_string(this), ctx) -} + let length = primitive_val.chars().count() as i32; + + // If less than 2 args specified, position is 'undefined', defaults to 0 + let position = if args.len() < 2 { + 0 + } else { + i32::from(args.get(1).expect("Could not get argument")) + }; + + let start = min(max(position, 0), length); + + // Here cannot use the &str method "rfind", because this returns the last + // byte index: we need to return the last char index in the JS String + // Instead, iterate over the part we're checking keeping track of the higher + // index we found that "starts with" the search string + let mut highest_index = -1; + for index in start..length { + let this_string: StdString = primitive_val.chars().skip(index as usize).collect(); + if this_string.starts_with(&search_string) { + highest_index = index; + } + } -/// Abstract method `StringPad`. -/// -/// Performs the actual string padding for padStart/End. -/// -fn string_pad( - primitive: String, - max_length: i32, - fill_string: Option, - at_start: bool, -) -> ResultValue { - let primitive_length = primitive.len() as i32; - - if max_length <= primitive_length { - return Ok(Value::from(primitive)); + // This will still be -1 if no matches were found, else with be >= 0 + Ok(Value::from(highest_index)) } - let filler = match fill_string { - Some(filler) => filler, - None => String::from(" "), - }; - - if filler == "" { - return Ok(Value::from(primitive)); + /// `String.prototype.match( regexp )` + /// + /// The `match()` method retrieves the result of matching a **string** against a [`regular expression`][regex]. + /// + /// More information: + /// - [ECMAScript reference][spec] + /// - [MDN documentation][mdn] + /// + /// [spec]: https://tc39.es/ecma262/#sec-string.prototype.match + /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/match + /// [regex]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions + pub(crate) fn r#match(this: &mut Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue { + let mut re = + RegExp::make_regexp(&mut Value::from(Object::default()), &[args[0].clone()], ctx)?; + RegExp::r#match(&mut re, ctx.value_to_rust_string(this), ctx) } - let fill_len = max_length.wrapping_sub(primitive_length); - let mut fill_str = String::new(); + /// Abstract method `StringPad`. + /// + /// Performs the actual string padding for padStart/End. + /// + fn string_pad( + primitive: StdString, + max_length: i32, + fill_string: Option, + at_start: bool, + ) -> ResultValue { + let primitive_length = primitive.len() as i32; + + if max_length <= primitive_length { + return Ok(Value::from(primitive)); + } - while fill_str.len() < fill_len as usize { - fill_str.push_str(&filler); - } - // Cut to size max_length - let concat_fill_str: String = fill_str.chars().take(fill_len as usize).collect(); + let filler = match fill_string { + Some(filler) => filler, + None => " ".to_owned(), + }; - if at_start { - Ok(Value::from(format!("{}{}", concat_fill_str, &primitive))) - } else { - Ok(Value::from(format!("{}{}", primitive, &concat_fill_str))) - } -} + if filler == "" { + return Ok(Value::from(primitive)); + } + + let fill_len = max_length.wrapping_sub(primitive_length); + let mut fill_str = StdString::new(); + + while fill_str.len() < fill_len as usize { + fill_str.push_str(&filler); + } + // Cut to size max_length + let concat_fill_str: StdString = fill_str.chars().take(fill_len as usize).collect(); -/// `String.prototype.padEnd( targetLength[, padString] )` -/// -/// The `padEnd()` method pads the current string with a given string (repeated, if needed) so that the resulting string reaches a given length. -/// -/// The padding is applied from the end of the current string. -/// -/// More information: -/// - [ECMAScript reference][spec] -/// - [MDN documentation][mdn] -/// -/// [spec]: https://tc39.es/ecma262/#sec-string.prototype.padend -/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/padEnd -pub fn pad_end(this: &mut Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue { - let primitive_val: String = ctx.value_to_rust_string(this); - if args.is_empty() { - return Err(Value::from("padEnd requires maxLength argument")); + if at_start { + Ok(Value::from(format!("{}{}", concat_fill_str, &primitive))) + } else { + Ok(Value::from(format!("{}{}", primitive, &concat_fill_str))) + } } - let max_length = i32::from( - args.get(0) - .expect("failed to get argument for String method"), - ); - let fill_string: Option = match args.len() { - 1 => None, - _ => Some(String::from(args.get(1).expect("Could not get argument"))), - }; + /// `String.prototype.padEnd( targetLength[, padString] )` + /// + /// The `padEnd()` method pads the current string with a given string (repeated, if needed) so that the resulting string reaches a given length. + /// + /// The padding is applied from the end of the current string. + /// + /// More information: + /// - [ECMAScript reference][spec] + /// - [MDN documentation][mdn] + /// + /// [spec]: https://tc39.es/ecma262/#sec-string.prototype.padend + /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/padEnd + pub(crate) fn pad_end(this: &mut Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue { + let primitive_val = ctx.value_to_rust_string(this); + if args.is_empty() { + return Err(Value::from("padEnd requires maxLength argument")); + } + let max_length = i32::from( + args.get(0) + .expect("failed to get argument for String method"), + ); - string_pad(primitive_val, max_length, fill_string, false) -} + let fill_string = match args.len() { + 1 => None, + _ => Some(StdString::from( + args.get(1).expect("Could not get argument"), + )), + }; -/// `String.prototype.padStart( targetLength [, padString] )` -/// -/// The `padStart()` method pads the current string with another string (multiple times, if needed) until the resulting string reaches the given length. -/// -/// The padding is applied from the start of the current string. -/// -/// More information: -/// - [ECMAScript reference][spec] -/// - [MDN documentation][mdn] -/// -/// [spec]: https://tc39.es/ecma262/#sec-string.prototype.padstart -/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/padStart -pub fn pad_start(this: &mut Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue { - let primitive_val: String = ctx.value_to_rust_string(this); - if args.is_empty() { - return Err(Value::from("padStart requires maxLength argument")); + Self::string_pad(primitive_val, max_length, fill_string, false) } - let max_length = i32::from( - args.get(0) - .expect("failed to get argument for String method"), - ); - let fill_string: Option = match args.len() { - 1 => None, - _ => Some(String::from(args.get(1).expect("Could not get argument"))), - }; + /// `String.prototype.padStart( targetLength [, padString] )` + /// + /// The `padStart()` method pads the current string with another string (multiple times, if needed) until the resulting string reaches the given length. + /// + /// The padding is applied from the start of the current string. + /// + /// More information: + /// - [ECMAScript reference][spec] + /// - [MDN documentation][mdn] + /// + /// [spec]: https://tc39.es/ecma262/#sec-string.prototype.padstart + /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/padStart + pub(crate) fn pad_start( + this: &mut Value, + args: &[Value], + ctx: &mut Interpreter, + ) -> ResultValue { + let primitive_val = ctx.value_to_rust_string(this); + if args.is_empty() { + return Err(Value::from("padStart requires maxLength argument")); + } + let max_length = i32::from( + args.get(0) + .expect("failed to get argument for String method"), + ); - string_pad(primitive_val, max_length, fill_string, true) -} + let fill_string = match args.len() { + 1 => None, + _ => Some(StdString::from( + args.get(1).expect("Could not get argument"), + )), + }; + + Self::string_pad(primitive_val, max_length, fill_string, true) + } -/// Helper function to check if a `char` is trimmable. -fn is_trimmable_whitespace(c: char) -> bool { - // The rust implementation of `trim` does not regard the same characters whitespace as ecma standard does - // - // Rust uses \p{White_Space} by default, which also includes: - // `\u{0085}' (next line) - // And does not include: - // '\u{FEFF}' (zero width non-breaking space) - match c { + /// Helper function to check if a `char` is trimmable. + fn is_trimmable_whitespace(c: char) -> bool { + // The rust implementation of `trim` does not regard the same characters whitespace as ecma standard does + // + // Rust uses \p{White_Space} by default, which also includes: + // `\u{0085}' (next line) + // And does not include: + // '\u{FEFF}' (zero width non-breaking space) + match c { // Explicit whitespace: https://tc39.es/ecma262/#sec-white-space '\u{0009}' | '\u{000B}' | '\u{000C}' | '\u{0020}' | '\u{00A0}' | '\u{FEFF}' | // Unicode Space_Seperator category @@ -730,302 +762,321 @@ fn is_trimmable_whitespace(c: char) -> bool { '\u{000A}' | '\u{000D}' | '\u{2028}' | '\u{2029}' => true, _ => false, } -} - -/// String.prototype.trim() -/// -/// The `trim()` method removes whitespace from both ends of a string. -/// -/// Whitespace in this context is all the whitespace characters (space, tab, no-break space, etc.) and all the line terminator characters (LF, CR, etc.). -/// -/// More information: -/// - [ECMAScript reference][spec] -/// - [MDN documentation][mdn] -/// -/// [spec]: https://tc39.es/ecma262/#sec-string.prototype.trim -/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/trim -pub fn trim(this: &mut Value, _: &[Value], ctx: &mut Interpreter) -> ResultValue { - let this_str: String = ctx.value_to_rust_string(this); - Ok(Value::from(this_str.trim_matches(is_trimmable_whitespace))) -} - -/// `String.prototype.trimStart()` -/// -/// The `trimStart()` method removes whitespace from the beginning of a string. -/// -/// Whitespace in this context is all the whitespace characters (space, tab, no-break space, etc.) and all the line terminator characters (LF, CR, etc.). -/// -/// More information: -/// - [ECMAScript reference][spec] -/// - [MDN documentation][mdn] -/// -/// [spec]: https://tc39.es/ecma262/#sec-string.prototype.trimstart -/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/trimStart -pub fn trim_start(this: &mut Value, _: &[Value], ctx: &mut Interpreter) -> ResultValue { - let this_str: String = ctx.value_to_rust_string(this); - Ok(Value::from( - this_str.trim_start_matches(is_trimmable_whitespace), - )) -} + } -/// String.prototype.trimEnd() -/// -/// The `trimEnd()` method removes whitespace from the end of a string. -/// -/// Whitespace in this context is all the whitespace characters (space, tab, no-break space, etc.) and all the line terminator characters (LF, CR, etc.). -/// -/// More information: -/// - [ECMAScript reference][spec] -/// - [MDN documentation][mdn] -/// -/// [spec]: https://tc39.es/ecma262/#sec-string.prototype.trimend -/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/trimEnd -pub fn trim_end(this: &mut Value, _: &[Value], ctx: &mut Interpreter) -> ResultValue { - let this_str: String = ctx.value_to_rust_string(this); - Ok(Value::from( - this_str.trim_end_matches(is_trimmable_whitespace), - )) -} + /// String.prototype.trim() + /// + /// The `trim()` method removes whitespace from both ends of a string. + /// + /// Whitespace in this context is all the whitespace characters (space, tab, no-break space, etc.) and all the line terminator characters (LF, CR, etc.). + /// + /// More information: + /// - [ECMAScript reference][spec] + /// - [MDN documentation][mdn] + /// + /// [spec]: https://tc39.es/ecma262/#sec-string.prototype.trim + /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/trim + pub(crate) fn trim(this: &mut Value, _: &[Value], ctx: &mut Interpreter) -> ResultValue { + let this_str = ctx.value_to_rust_string(this); + Ok(Value::from( + this_str.trim_matches(Self::is_trimmable_whitespace), + )) + } -/// `String.prototype.toLowerCase()` -/// -/// The `toLowerCase()` method returns the calling string value converted to lower case. -/// -/// More information: -/// - [ECMAScript reference][spec] -/// - [MDN documentation][mdn] -/// -/// [spec]: https://tc39.es/ecma262/#sec-string.prototype.tolowercase -/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/toLowerCase -pub fn to_lowercase(this: &mut Value, _: &[Value], ctx: &mut Interpreter) -> ResultValue { - // First we get it the actual string a private field stored on the object only the engine has access to. - // Then we convert it into a Rust String by wrapping it in from_value - let this_str: String = ctx.value_to_rust_string(this); - // The Rust String is mapped to uppercase using the builtin .to_lowercase(). - // There might be corner cases where it does not behave exactly like Javascript expects - Ok(Value::from(this_str.to_lowercase())) -} + /// `String.prototype.trimStart()` + /// + /// The `trimStart()` method removes whitespace from the beginning of a string. + /// + /// Whitespace in this context is all the whitespace characters (space, tab, no-break space, etc.) and all the line terminator characters (LF, CR, etc.). + /// + /// More information: + /// - [ECMAScript reference][spec] + /// - [MDN documentation][mdn] + /// + /// [spec]: https://tc39.es/ecma262/#sec-string.prototype.trimstart + /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/trimStart + pub(crate) fn trim_start(this: &mut Value, _: &[Value], ctx: &mut Interpreter) -> ResultValue { + let this_str = ctx.value_to_rust_string(this); + Ok(Value::from( + this_str.trim_start_matches(Self::is_trimmable_whitespace), + )) + } -/// `String.prototype.toUpperCase()` -/// -/// The `toUpperCase()` method returns the calling string value converted to uppercase. -/// -/// The value will be **converted** to a string if it isn't one -/// -/// More information: -/// - [ECMAScript reference][spec] -/// - [MDN documentation][mdn] -/// -/// [spec]: https://tc39.es/ecma262/#sec-string.prototype.toUppercase -/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/toUpperCase -pub fn to_uppercase(this: &mut Value, _: &[Value], ctx: &mut Interpreter) -> ResultValue { - // First we get it the actual string a private field stored on the object only the engine has access to. - // Then we convert it into a Rust String by wrapping it in from_value - let this_str: String = ctx.value_to_rust_string(this); - // The Rust String is mapped to uppercase using the builtin .to_uppercase(). - // There might be corner cases where it does not behave exactly like Javascript expects - Ok(Value::from(this_str.to_uppercase())) -} + /// String.prototype.trimEnd() + /// + /// The `trimEnd()` method removes whitespace from the end of a string. + /// + /// Whitespace in this context is all the whitespace characters (space, tab, no-break space, etc.) and all the line terminator characters (LF, CR, etc.). + /// + /// More information: + /// - [ECMAScript reference][spec] + /// - [MDN documentation][mdn] + /// + /// [spec]: https://tc39.es/ecma262/#sec-string.prototype.trimend + /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/trimEnd + pub(crate) fn trim_end(this: &mut Value, _: &[Value], ctx: &mut Interpreter) -> ResultValue { + let this_str = ctx.value_to_rust_string(this); + Ok(Value::from( + this_str.trim_end_matches(Self::is_trimmable_whitespace), + )) + } -/// `String.prototype.substring( indexStart[, indexEnd] )` -/// -/// The `substring()` method returns the part of the `string` between the start and end indexes, or to the end of the string. -/// -/// More information: -/// - [ECMAScript reference][spec] -/// - [MDN documentation][mdn] -/// -/// [spec]: https://tc39.es/ecma262/#sec-string.prototype.substring -/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/substring -pub fn substring(this: &mut Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue { - // First we get it the actual string a private field stored on the object only the engine has access to. - // Then we convert it into a Rust String by wrapping it in from_value - let primitive_val: String = ctx.value_to_rust_string(this); - // If no args are specified, start is 'undefined', defaults to 0 - let start = if args.is_empty() { - 0 - } else { - i32::from( - args.get(0) - .expect("failed to get argument for String method"), - ) - }; - let length: i32 = primitive_val.chars().count() as i32; - // If less than 2 args specified, end is the length of the this object converted to a String - let end = if args.len() < 2 { - length - } else { - i32::from(args.get(1).expect("Could not get argument")) - }; - // Both start and end args replaced by 0 if they were negative - // or by the length of the String if they were greater - let final_start = min(max(start, 0), length); - let final_end = min(max(end, 0), length); - // Start and end are swapped if start is greater than end - let from = min(final_start, final_end) as usize; - let to = max(final_start, final_end) as usize; - // Extract the part of the string contained between the start index and the end index - // where start is guaranteed to be smaller or equals to end - let extracted_string: String = primitive_val - .chars() - .skip(from) - .take(to.wrapping_sub(from)) - .collect(); - Ok(Value::from(extracted_string)) -} + /// `String.prototype.toLowerCase()` + /// + /// The `toLowerCase()` method returns the calling string value converted to lower case. + /// + /// More information: + /// - [ECMAScript reference][spec] + /// - [MDN documentation][mdn] + /// + /// [spec]: https://tc39.es/ecma262/#sec-string.prototype.tolowercase + /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/toLowerCase + #[allow(clippy::wrong_self_convention)] + pub(crate) fn to_lowercase( + this: &mut Value, + _: &[Value], + ctx: &mut Interpreter, + ) -> ResultValue { + // First we get it the actual string a private field stored on the object only the engine has access to. + // Then we convert it into a Rust String by wrapping it in from_value + let this_str = ctx.value_to_rust_string(this); + // The Rust String is mapped to uppercase using the builtin .to_lowercase(). + // There might be corner cases where it does not behave exactly like Javascript expects + Ok(Value::from(this_str.to_lowercase())) + } -/// `String.prototype.substr( start[, length] )` -/// -/// The `substr()` method returns a portion of the string, starting at the specified index and extending for a given number of characters afterward. -/// -/// More information: -/// - [ECMAScript reference][spec] -/// - [MDN documentation][mdn] -/// -/// [spec]: https://tc39.es/ecma262/#sec-string.prototype.substr -/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/substr -/// -pub fn substr(this: &mut Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue { - // First we get it the actual string a private field stored on the object only the engine has access to. - // Then we convert it into a Rust String by wrapping it in from_value - let primitive_val: String = ctx.value_to_rust_string(this); - // If no args are specified, start is 'undefined', defaults to 0 - let mut start = if args.is_empty() { - 0 - } else { - i32::from( - args.get(0) - .expect("failed to get argument for String method"), - ) - }; - let length: i32 = primitive_val.chars().count() as i32; - // If less than 2 args specified, end is +infinity, the maximum number value. - // Using i32::max_value() should be safe because the final length used is at most - // the number of code units from start to the end of the string, - // which should always be smaller or equals to both +infinity and i32::max_value - let end = if args.len() < 2 { - i32::max_value() - } else { - i32::from(args.get(1).expect("Could not get argument")) - }; - // If start is negative it become the number of code units from the end of the string - if start < 0 { - start = max(length.wrapping_add(start), 0); + /// `String.prototype.toUpperCase()` + /// + /// The `toUpperCase()` method returns the calling string value converted to uppercase. + /// + /// The value will be **converted** to a string if it isn't one + /// + /// More information: + /// - [ECMAScript reference][spec] + /// - [MDN documentation][mdn] + /// + /// [spec]: https://tc39.es/ecma262/#sec-string.prototype.toUppercase + /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/toUpperCase + #[allow(clippy::wrong_self_convention)] + pub(crate) fn to_uppercase( + this: &mut Value, + _: &[Value], + ctx: &mut Interpreter, + ) -> ResultValue { + // First we get it the actual string a private field stored on the object only the engine has access to. + // Then we convert it into a Rust String by wrapping it in from_value + let this_str = ctx.value_to_rust_string(this); + // The Rust String is mapped to uppercase using the builtin .to_uppercase(). + // There might be corner cases where it does not behave exactly like Javascript expects + Ok(Value::from(this_str.to_uppercase())) } - // length replaced by 0 if it was negative - // or by the number of code units from start to the end of the string if it was greater - let result_length = min(max(end, 0), length.wrapping_sub(start)); - // If length is negative we return an empty string - // otherwise we extract the part of the string from start and is length code units long - if result_length <= 0 { - Ok(Value::from("".to_string())) - } else { - let extracted_string: String = primitive_val + + /// `String.prototype.substring( indexStart[, indexEnd] )` + /// + /// The `substring()` method returns the part of the `string` between the start and end indexes, or to the end of the string. + /// + /// More information: + /// - [ECMAScript reference][spec] + /// - [MDN documentation][mdn] + /// + /// [spec]: https://tc39.es/ecma262/#sec-string.prototype.substring + /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/substring + pub(crate) fn substring( + this: &mut Value, + args: &[Value], + ctx: &mut Interpreter, + ) -> ResultValue { + // First we get it the actual string a private field stored on the object only the engine has access to. + // Then we convert it into a Rust String by wrapping it in from_value + let primitive_val = ctx.value_to_rust_string(this); + // If no args are specified, start is 'undefined', defaults to 0 + let start = if args.is_empty() { + 0 + } else { + i32::from( + args.get(0) + .expect("failed to get argument for String method"), + ) + }; + let length = primitive_val.chars().count() as i32; + // If less than 2 args specified, end is the length of the this object converted to a String + let end = if args.len() < 2 { + length + } else { + i32::from(args.get(1).expect("Could not get argument")) + }; + // Both start and end args replaced by 0 if they were negative + // or by the length of the String if they were greater + let final_start = min(max(start, 0), length); + let final_end = min(max(end, 0), length); + // Start and end are swapped if start is greater than end + let from = min(final_start, final_end) as usize; + let to = max(final_start, final_end) as usize; + // Extract the part of the string contained between the start index and the end index + // where start is guaranteed to be smaller or equals to end + let extracted_string: StdString = primitive_val .chars() - .skip(start as usize) - .take(result_length as usize) + .skip(from) + .take(to.wrapping_sub(from)) .collect(); - Ok(Value::from(extracted_string)) } -} -/// String.prototype.valueOf() -/// -/// The `valueOf()` method returns the primitive value of a `String` object. -/// -/// More information: -/// - [ECMAScript reference][spec] -/// - [MDN documentation][mdn] -/// -/// [spec]: https://tc39.es/ecma262/#sec-string.prototype.value_of -/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/valueOf -pub fn value_of(this: &mut Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue { - // Use the to_string method because it is specified to do the same thing in this case - to_string(this, args, ctx) -} + /// `String.prototype.substr( start[, length] )` + /// + /// The `substr()` method returns a portion of the string, starting at the specified index and extending for a given number of characters afterward. + /// + /// More information: + /// - [ECMAScript reference][spec] + /// - [MDN documentation][mdn] + /// + /// [spec]: https://tc39.es/ecma262/#sec-string.prototype.substr + /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/substr + /// + pub(crate) fn substr(this: &mut Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue { + // First we get it the actual string a private field stored on the object only the engine has access to. + // Then we convert it into a Rust String by wrapping it in from_value + let primitive_val = ctx.value_to_rust_string(this); + // If no args are specified, start is 'undefined', defaults to 0 + let mut start = if args.is_empty() { + 0 + } else { + i32::from( + args.get(0) + .expect("failed to get argument for String method"), + ) + }; + let length = primitive_val.chars().count() as i32; + // If less than 2 args specified, end is +infinity, the maximum number value. + // Using i32::max_value() should be safe because the final length used is at most + // the number of code units from start to the end of the string, + // which should always be smaller or equals to both +infinity and i32::max_value + let end = if args.len() < 2 { + i32::max_value() + } else { + i32::from(args.get(1).expect("Could not get argument")) + }; + // If start is negative it become the number of code units from the end of the string + if start < 0 { + start = max(length.wrapping_add(start), 0); + } + // length replaced by 0 if it was negative + // or by the number of code units from start to the end of the string if it was greater + let result_length = min(max(end, 0), length.wrapping_sub(start)); + // If length is negative we return an empty string + // otherwise we extract the part of the string from start and is length code units long + if result_length <= 0 { + Ok(Value::from("")) + } else { + let extracted_string: StdString = primitive_val + .chars() + .skip(start as usize) + .take(result_length as usize) + .collect(); -/// `String.prototype.matchAll( regexp )` -/// -/// The `matchAll()` method returns an iterator of all results matching a string against a [`regular expression`][regex], including [capturing groups][cg]. -/// -/// More information: -/// - [ECMAScript reference][spec] -/// - [MDN documentation][mdn] -/// -/// [spec]: https://tc39.es/ecma262/#sec-string.prototype.matchall -/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/matchAll -/// [regex]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions -/// [cg]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions/Groups_and_Ranges -// TODO: update this method to return iterator -pub fn match_all(this: &mut Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue { - let mut re: Value = match args.get(0) { - Some(arg) => { - if arg.is_null() { - make_regexp( - &mut Value::from(Object::default()), - &[ - Value::from(ctx.value_to_rust_string(arg)), - Value::from(String::from("g")), - ], - ctx, - ) - } else if arg.is_undefined() { - make_regexp( - &mut Value::from(Object::default()), - &[Value::undefined(), Value::from(String::from("g"))], - ctx, - ) - } else { - Ok(arg.clone()) - } + Ok(Value::from(extracted_string)) } - None => make_regexp( - &mut Value::from(Object::default()), - &[Value::from(String::new()), Value::from("g")], - ctx, - ), - }?; - - regexp_match_all(&mut re, ctx.value_to_rust_string(this)) -} + } -/// Create a new `String` object. -pub fn create(global: &Value) -> Value { - // Create prototype - let prototype = Value::new_object(Some(global)); - let length = Property::default().value(Value::from(0)); - - prototype.set_property_slice("length", length); - make_builtin_fn!(char_at, named "charAt", with length 1, of prototype); - make_builtin_fn!(char_code_at, named "charCodeAt", with length 1, of prototype); - make_builtin_fn!(to_string, named "toString", of prototype); - make_builtin_fn!(concat, named "concat", with length 1, of prototype); - make_builtin_fn!(repeat, named "repeat", with length 1, of prototype); - make_builtin_fn!(slice, named "slice", with length 2, of prototype); - make_builtin_fn!(starts_with, named "startsWith", with length 1, of prototype); - make_builtin_fn!(ends_with, named "endsWith", with length 1, of prototype); - make_builtin_fn!(includes, named "includes", with length 1, of prototype); - make_builtin_fn!(index_of, named "indexOf", with length 1, of prototype); - make_builtin_fn!(last_index_of, named "lastIndexOf", with length 1, of prototype); - make_builtin_fn!(r#match, named "match", with length 1, of prototype); - make_builtin_fn!(pad_end, named "padEnd", with length 1, of prototype); - make_builtin_fn!(pad_start, named "padStart", with length 1, of prototype); - make_builtin_fn!(trim, named "trim", of prototype); - make_builtin_fn!(trim_start, named "trimStart", of prototype); - make_builtin_fn!(to_lowercase, named "toLowerCase", of prototype); - make_builtin_fn!(to_uppercase, named "toUpperCase", of prototype); - make_builtin_fn!(substring, named "substring", with length 2, of prototype); - make_builtin_fn!(substr, named "substr", with length 2, of prototype); - make_builtin_fn!(value_of, named "valueOf", of prototype); - make_builtin_fn!(match_all, named "matchAll", with length 1, of prototype); - make_builtin_fn!(replace, named "replace", with length 2, of prototype); - - make_constructor_fn(make_string, global, prototype) -} + /// String.prototype.valueOf() + /// + /// The `valueOf()` method returns the primitive value of a `String` object. + /// + /// More information: + /// - [ECMAScript reference][spec] + /// - [MDN documentation][mdn] + /// + /// [spec]: https://tc39.es/ecma262/#sec-string.prototype.value_of + /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/valueOf + pub(crate) fn value_of(this: &mut Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue { + // Use the to_string method because it is specified to do the same thing in this case + Self::to_string(this, args, ctx) + } -/// Initialise the `String` object on the global object. -#[inline] -pub fn init(global: &Value) { - global.set_field_slice("String", create(global)); + /// `String.prototype.matchAll( regexp )` + /// + /// The `matchAll()` method returns an iterator of all results matching a string against a [`regular expression`][regex], including [capturing groups][cg]. + /// + /// More information: + /// - [ECMAScript reference][spec] + /// - [MDN documentation][mdn] + /// + /// [spec]: https://tc39.es/ecma262/#sec-string.prototype.matchall + /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/matchAll + /// [regex]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions + /// [cg]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions/Groups_and_Ranges + // TODO: update this method to return iterator + pub(crate) fn match_all( + this: &mut Value, + args: &[Value], + ctx: &mut Interpreter, + ) -> ResultValue { + let mut re: Value = match args.get(0) { + Some(arg) => { + if arg.is_null() { + RegExp::make_regexp( + &mut Value::from(Object::default()), + &[Value::from(ctx.value_to_rust_string(arg)), Value::from("g")], + ctx, + ) + } else if arg.is_undefined() { + RegExp::make_regexp( + &mut Value::from(Object::default()), + &[Value::undefined(), Value::from("g")], + ctx, + ) + } else { + Ok(arg.clone()) + } + } + None => RegExp::make_regexp( + &mut Value::from(Object::default()), + &[Value::from(""), Value::from("g")], + ctx, + ), + }?; + + RegExp::match_all(&mut re, ctx.value_to_rust_string(this)) + } + + /// Create a new `String` object. + pub(crate) fn create(global: &Value) -> Value { + // Create prototype + let prototype = Value::new_object(Some(global)); + let length = Property::default().value(Value::from(0)); + + prototype.set_property_slice("length", length); + make_builtin_fn(Self::char_at, "charAt", &prototype, 1); + make_builtin_fn(Self::char_code_at, "charCodeAt", &prototype, 1); + make_builtin_fn(Self::to_string, "toString", &prototype, 0); + make_builtin_fn(Self::concat, "concat", &prototype, 1); + make_builtin_fn(Self::repeat, "repeat", &prototype, 1); + make_builtin_fn(Self::slice, "slice", &prototype, 2); + make_builtin_fn(Self::starts_with, "startsWith", &prototype, 1); + make_builtin_fn(Self::ends_with, "endsWith", &prototype, 1); + make_builtin_fn(Self::includes, "includes", &prototype, 1); + make_builtin_fn(Self::index_of, "indexOf", &prototype, 1); + make_builtin_fn(Self::last_index_of, "lastIndexOf", &prototype, 1); + make_builtin_fn(Self::r#match, "match", &prototype, 1); + make_builtin_fn(Self::pad_end, "padEnd", &prototype, 1); + make_builtin_fn(Self::pad_start, "padStart", &prototype, 1); + make_builtin_fn(Self::trim, "trim", &prototype, 0); + make_builtin_fn(Self::trim_start, "trimStart", &prototype, 0); + make_builtin_fn(Self::trim_end, "trimEnd", &prototype, 0); + make_builtin_fn(Self::to_lowercase, "toLowerCase", &prototype, 0); + make_builtin_fn(Self::to_uppercase, "toUpperCase", &prototype, 0); + make_builtin_fn(Self::substring, "substring", &prototype, 2); + make_builtin_fn(Self::substr, "substr", &prototype, 2); + make_builtin_fn(Self::value_of, "valueOf", &prototype, 0); + make_builtin_fn(Self::match_all, "matchAll", &prototype, 1); + make_builtin_fn(Self::replace, "replace", &prototype, 2); + + make_constructor_fn(Self::make_string, global, prototype) + } + + /// Initialise the `String` object on the global object. + #[inline] + pub(crate) fn init(global: &Value) { + global.set_field("String", Self::create(global)); + } } diff --git a/boa/src/builtins/string/tests.rs b/boa/src/builtins/string/tests.rs index e70c6e24d11..31229bdb84f 100644 --- a/boa/src/builtins/string/tests.rs +++ b/boa/src/builtins/string/tests.rs @@ -4,7 +4,7 @@ use crate::{exec::Interpreter, forward, forward_val, realm::Realm}; #[test] fn check_string_constructor_is_function() { let global = Value::new_object(None); - let string_constructor = create(&global); + let string_constructor = String::create(&global); assert_eq!(string_constructor.is_function(), true); } @@ -88,7 +88,9 @@ fn construct_and_call() { var hello = new String('Hello'); var world = String('world'); "#; - eprintln!("{}", forward(&mut engine, init)); + + forward(&mut engine, init); + let hello = forward_val(&mut engine, "hello").unwrap(); let world = forward_val(&mut engine, "world").unwrap(); @@ -105,23 +107,17 @@ fn repeat() { var en = new String('english'); var zh = new String('中文'); "#; - eprintln!("{}", forward(&mut engine, init)); - let empty = String::from(""); - assert_eq!(forward(&mut engine, "empty.repeat(0)"), empty); - assert_eq!(forward(&mut engine, "empty.repeat(1)"), empty); + forward(&mut engine, init); - assert_eq!(forward(&mut engine, "en.repeat(0)"), empty); - assert_eq!(forward(&mut engine, "zh.repeat(0)"), empty); + assert_eq!(forward(&mut engine, "empty.repeat(0)"), ""); + assert_eq!(forward(&mut engine, "empty.repeat(1)"), ""); - assert_eq!( - forward(&mut engine, "en.repeat(1)"), - String::from("english") - ); - assert_eq!( - forward(&mut engine, "zh.repeat(2)"), - String::from("中文中文") - ); + assert_eq!(forward(&mut engine, "en.repeat(0)"), ""); + assert_eq!(forward(&mut engine, "zh.repeat(0)"), ""); + + assert_eq!(forward(&mut engine, "en.repeat(1)"), "english"); + assert_eq!(forward(&mut engine, "zh.repeat(2)"), "中文中文"); } #[test] @@ -133,10 +129,10 @@ fn replace() { a = a.replace("a", "2"); a "#; - eprintln!("{}", forward(&mut engine, init)); - let empty = String::from("2bc"); - assert_eq!(forward(&mut engine, "a"), empty); + forward(&mut engine, init); + + assert_eq!(forward(&mut engine, "a"), "2bc"); } #[test] @@ -156,15 +152,14 @@ fn replace_with_function() { a = a.replace(/c(o)(o)(l)/, replacer); a; "#; - eprintln!("{}", forward(&mut engine, init)); - assert_eq!( - forward(&mut engine, "a"), - String::from("ecmascript is awesome!") - ); - assert_eq!(forward(&mut engine, "p1"), String::from("o")); - assert_eq!(forward(&mut engine, "p2"), String::from("o")); - assert_eq!(forward(&mut engine, "p3"), String::from("l")); + forward(&mut engine, init); + + assert_eq!(forward(&mut engine, "a"), "ecmascript is awesome!"); + + assert_eq!(forward(&mut engine, "p1"), "o"); + assert_eq!(forward(&mut engine, "p2"), "o"); + assert_eq!(forward(&mut engine, "p3"), "l"); } #[test] @@ -180,15 +175,16 @@ fn starts_with() { var enLiteral = 'english'; var zhLiteral = '中文'; "#; - eprintln!("{}", forward(&mut engine, init)); - let pass = String::from("true"); - assert_eq!(forward(&mut engine, "empty.startsWith('')"), pass); - assert_eq!(forward(&mut engine, "en.startsWith('e')"), pass); - assert_eq!(forward(&mut engine, "zh.startsWith('中')"), pass); - - assert_eq!(forward(&mut engine, "emptyLiteral.startsWith('')"), pass); - assert_eq!(forward(&mut engine, "enLiteral.startsWith('e')"), pass); - assert_eq!(forward(&mut engine, "zhLiteral.startsWith('中')"), pass); + + forward(&mut engine, init); + + assert_eq!(forward(&mut engine, "empty.startsWith('')"), "true"); + assert_eq!(forward(&mut engine, "en.startsWith('e')"), "true"); + assert_eq!(forward(&mut engine, "zh.startsWith('中')"), "true"); + + assert_eq!(forward(&mut engine, "emptyLiteral.startsWith('')"), "true"); + assert_eq!(forward(&mut engine, "enLiteral.startsWith('e')"), "true"); + assert_eq!(forward(&mut engine, "zhLiteral.startsWith('中')"), "true"); } #[test] @@ -204,15 +200,16 @@ fn ends_with() { var enLiteral = 'english'; var zhLiteral = '中文'; "#; - eprintln!("{}", forward(&mut engine, init)); - let pass = String::from("true"); - assert_eq!(forward(&mut engine, "empty.endsWith('')"), pass); - assert_eq!(forward(&mut engine, "en.endsWith('h')"), pass); - assert_eq!(forward(&mut engine, "zh.endsWith('文')"), pass); - - assert_eq!(forward(&mut engine, "emptyLiteral.endsWith('')"), pass); - assert_eq!(forward(&mut engine, "enLiteral.endsWith('h')"), pass); - assert_eq!(forward(&mut engine, "zhLiteral.endsWith('文')"), pass); + + forward(&mut engine, init); + + assert_eq!(forward(&mut engine, "empty.endsWith('')"), "true"); + assert_eq!(forward(&mut engine, "en.endsWith('h')"), "true"); + assert_eq!(forward(&mut engine, "zh.endsWith('文')"), "true"); + + assert_eq!(forward(&mut engine, "emptyLiteral.endsWith('')"), "true"); + assert_eq!(forward(&mut engine, "enLiteral.endsWith('h')"), "true"); + assert_eq!(forward(&mut engine, "zhLiteral.endsWith('文')"), "true"); } #[test] @@ -220,54 +217,28 @@ fn match_all() { let realm = Realm::create(); let mut engine = Interpreter::new(realm); - assert_eq!( - forward(&mut engine, "'aa'.matchAll(null).length"), - String::from("0") - ); - assert_eq!( - forward(&mut engine, "'aa'.matchAll(/b/).length"), - String::from("0") - ); - assert_eq!( - forward(&mut engine, "'aa'.matchAll(/a/).length"), - String::from("1") - ); - assert_eq!( - forward(&mut engine, "'aa'.matchAll(/a/g).length"), - String::from("2") - ); + assert_eq!(forward(&mut engine, "'aa'.matchAll(null).length"), "0"); + assert_eq!(forward(&mut engine, "'aa'.matchAll(/b/).length"), "0"); + assert_eq!(forward(&mut engine, "'aa'.matchAll(/a/).length"), "1"); + assert_eq!(forward(&mut engine, "'aa'.matchAll(/a/g).length"), "2"); forward( &mut engine, "var groupMatches = 'test1test2'.matchAll(/t(e)(st(\\d?))/g)", ); - assert_eq!( - forward(&mut engine, "groupMatches.length"), - String::from("2") - ); - assert_eq!( - forward(&mut engine, "groupMatches[0][1]"), - String::from("e") - ); - assert_eq!( - forward(&mut engine, "groupMatches[0][2]"), - String::from("st1") - ); - assert_eq!( - forward(&mut engine, "groupMatches[0][3]"), - String::from("1") - ); - assert_eq!( - forward(&mut engine, "groupMatches[1][3]"), - String::from("2") - ); + + assert_eq!(forward(&mut engine, "groupMatches.length"), "2"); + assert_eq!(forward(&mut engine, "groupMatches[0][1]"), "e"); + assert_eq!(forward(&mut engine, "groupMatches[0][2]"), "st1"); + assert_eq!(forward(&mut engine, "groupMatches[0][3]"), "1"); + assert_eq!(forward(&mut engine, "groupMatches[1][3]"), "2"); assert_eq!( forward( &mut engine, "'test1test2'.matchAll(/t(e)(st(\\d?))/).length" ), - String::from("1") + "1" ); let init = r#" @@ -275,17 +246,13 @@ fn match_all() { var str = 'table football, foosball'; var matches = str.matchAll(regexp); "#; - eprintln!("{}", forward(&mut engine, init)); - assert_eq!( - forward(&mut engine, "matches[0][0]"), - String::from("football") - ); - assert_eq!(forward(&mut engine, "matches[0].index"), String::from("6")); - assert_eq!( - forward(&mut engine, "matches[1][0]"), - String::from("foosball") - ); - assert_eq!(forward(&mut engine, "matches[1].index"), String::from("16")); + + forward(&mut engine, init); + + assert_eq!(forward(&mut engine, "matches[0][0]"), "football"); + assert_eq!(forward(&mut engine, "matches[0].index"), "6"); + assert_eq!(forward(&mut engine, "matches[1][0]"), "foosball"); + assert_eq!(forward(&mut engine, "matches[1].index"), "16"); } #[test] @@ -300,7 +267,8 @@ fn test_match() { var result4 = str.match(RegExp("B", 'g')); "#; - eprintln!("{}", forward(&mut engine, init)); + forward(&mut engine, init); + assert_eq!(forward(&mut engine, "result1[0]"), "Quick Brown Fox Jumps"); assert_eq!(forward(&mut engine, "result1[1]"), "Brown"); assert_eq!(forward(&mut engine, "result1[2]"), "Jumps"); diff --git a/boa/src/builtins/symbol/mod.rs b/boa/src/builtins/symbol/mod.rs index 16cd4c0e17c..7e9f5f1ed17 100644 --- a/boa/src/builtins/symbol/mod.rs +++ b/boa/src/builtins/symbol/mod.rs @@ -18,7 +18,7 @@ #[cfg(test)] mod tests; -use super::function::make_constructor_fn; +use super::function::{make_builtin_fn, make_constructor_fn}; use crate::{ builtins::{ object::{ @@ -63,8 +63,8 @@ pub fn call_symbol(_: &mut Value, args: &[Value], ctx: &mut Interpreter) -> Resu let proto = ctx .realm .global_obj - .get_field_slice("Symbol") - .get_field_slice(PROTOTYPE); + .get_field("Symbol") + .get_field(PROTOTYPE); sym_instance.set_internal_slot(INSTANCE_PROTOTYPE, proto); Ok(Value(Gc::new(ValueData::Symbol(Box::new(GcCell::new( @@ -92,12 +92,12 @@ pub fn to_string(this: &mut Value, _: &[Value], _: &mut Interpreter) -> ResultVa pub fn create(global: &Value) -> Value { // Create prototype object let prototype = Value::new_object(Some(global)); - make_builtin_fn!(to_string, named "toString", of prototype); + make_builtin_fn(to_string, "toString", &prototype, 0); make_constructor_fn(call_symbol, global, prototype) } /// Initialise the `Symbol` object on the global object. #[inline] pub fn init(global: &Value) { - global.set_field_slice("Symbol", create(global)); + global.set_field("Symbol", create(global)); } diff --git a/boa/src/builtins/value/conversions.rs b/boa/src/builtins/value/conversions.rs index 299ccffebaf..b2459cf40f0 100644 --- a/boa/src/builtins/value/conversions.rs +++ b/boa/src/builtins/value/conversions.rs @@ -31,6 +31,12 @@ impl From<&str> for Value { } } +impl From<&Box> for Value { + fn from(value: &Box) -> Self { + Self::string(value.as_ref()) + } +} + impl From for Value { fn from(value: char) -> Self { Value::string(value.to_string()) diff --git a/boa/src/builtins/value/mod.rs b/boa/src/builtins/value/mod.rs index 77de6df642c..2bdb1860375 100644 --- a/boa/src/builtins/value/mod.rs +++ b/boa/src/builtins/value/mod.rs @@ -123,7 +123,7 @@ impl Value { /// Returns a new empty object pub fn new_object(global: Option<&Value>) -> Self { if let Some(global) = global { - let object_prototype = global.get_field_slice("Object").get_field_slice(PROTOTYPE); + let object_prototype = global.get_field("Object").get_field(PROTOTYPE); let object = Object::create(object_prototype); Self::object(object) @@ -484,8 +484,11 @@ impl ValueData { /// Resolve the property in the object and get its value, or undefined if this is not an object or the field doesn't exist /// get_field recieves a Property from get_prop(). It should then return the [[Get]] result value if that's set, otherwise fall back to [[Value]] /// TODO: this function should use the get Value if its set - pub fn get_field(&self, field: Value) -> Value { - match *field { + pub fn get_field(&self, field: F) -> Value + where + F: Into, + { + match *field.into() { // Our field will either be a String or a Symbol Self::String(ref s) => { match self.get_property(s) { @@ -586,24 +589,23 @@ impl ValueData { self.get_property(field).is_some() } - /// Resolve the property in the object and get its value, or undefined if this is not an object or the field doesn't exist - pub fn get_field_slice(&self, field: &str) -> Value { - // get_field used to accept strings, but now Symbols accept it needs to accept a value - // So this function will now need to Box strings back into values (at least for now) - let f = Value::string(field.to_string()); - self.get_field(f) - } - /// Set the field in the value /// Field could be a Symbol, so we need to accept a Value (not a string) - pub fn set_field(&self, field: Value, val: Value) -> Value { + pub fn set_field(&self, field: F, val: V) -> Value + where + F: Into, + V: Into, + { + let field = field.into(); + let val = val.into(); + if let Self::Object(ref obj) = *self { if obj.borrow().kind == ObjectKind::Array { if let Ok(num) = field.to_string().parse::() { if num > 0 { - let len = i32::from(&self.get_field_slice("length")); + let len = i32::from(&self.get_field("length")); if len < (num + 1) as i32 { - self.set_field_slice("length", Value::from(num + 1)); + self.set_field("length", Value::from(num + 1)); } } } @@ -621,14 +623,6 @@ impl ValueData { val } - /// Set the field in the value - pub fn set_field_slice(&self, field: &str, val: Value) -> Value { - // set_field used to accept strings, but now Symbols accept it needs to accept a value - // So this function will now need to Box strings back into values (at least for now) - let f = Value::string(field.to_string()); - self.set_field(f, val) - } - /// Set the private field in the value pub fn set_internal_slot(&self, field: &str, val: Value) -> Value { if let Self::Object(ref obj) = *self { @@ -679,7 +673,7 @@ impl ValueData { // Wrap Object in GC'd Value let new_func_val = Value::from(new_func); // Set length to parameters - new_func_val.set_field_slice("length", Value::from(length)); + new_func_val.set_field("length", Value::from(length)); new_func_val } @@ -730,7 +724,7 @@ impl ValueData { .borrow() .properties .iter() - .map(|(k, _)| (k.clone(), self.get_field_slice(k).to_json())) + .map(|(k, _)| (k.clone(), self.get_field(k.as_str()).to_json())) .collect::>(); JSONValue::Object(new_obj) } diff --git a/boa/src/builtins/value/operations.rs b/boa/src/builtins/value/operations.rs index 1a733542b00..e54459ea84f 100644 --- a/boa/src/builtins/value/operations.rs +++ b/boa/src/builtins/value/operations.rs @@ -1,6 +1,5 @@ use super::*; -use crate::builtins::number; -use crate::Interpreter; +use crate::{builtins::Number, Interpreter}; use std::borrow::Borrow; use std::convert::TryFrom; @@ -16,7 +15,7 @@ impl Value { } if self.is_number() { - return number::equals(f64::from(self), f64::from(other)); + return Number::equals(f64::from(self), f64::from(other)); } //Null has to be handled specially because "typeof null" returns object and if we managed @@ -59,7 +58,7 @@ impl Value { | (ValueData::Integer(_), ValueData::Boolean(_)) => { let a: &Value = self.borrow(); let b: &Value = other.borrow(); - number::equals(f64::from(a), f64::from(b)) + Number::equals(f64::from(a), f64::from(b)) } // 6. If Type(x) is BigInt and Type(y) is String, then @@ -150,7 +149,7 @@ pub fn same_value(x: &Value, y: &Value, strict: bool) -> bool { // TODO: check BigInt // https://github.com/jasonwilliams/boa/pull/358 if x.is_number() { - return number::same_value(f64::from(x), f64::from(y)); + return Number::same_value(f64::from(x), f64::from(y)); } same_value_non_number(x, y) @@ -330,7 +329,7 @@ pub fn same_value_zero(x: &Value, y: &Value) -> bool { } if x.is_number() { - return number::same_value_zero(f64::from(x), f64::from(y)); + return Number::same_value_zero(f64::from(x), f64::from(y)); } same_value_non_number(x, y) diff --git a/boa/src/builtins/value/tests.rs b/boa/src/builtins/value/tests.rs index 8edd59a7dc8..193efee084c 100644 --- a/boa/src/builtins/value/tests.rs +++ b/boa/src/builtins/value/tests.rs @@ -27,8 +27,8 @@ fn check_get_set_field() { let obj = Value::new_object(None); // Create string and convert it to a Value let s = Value::from("bar"); - obj.set_field_slice("foo", s); - assert_eq!(obj.get_field_slice("foo").to_string(), "bar"); + obj.set_field("foo", s); + assert_eq!(obj.get_field("foo").to_string(), "bar"); } #[test] diff --git a/boa/src/environment/object_environment_record.rs b/boa/src/environment/object_environment_record.rs index add288fb75f..aada300d7a7 100644 --- a/boa/src/environment/object_environment_record.rs +++ b/boa/src/environment/object_environment_record.rs @@ -68,7 +68,7 @@ impl EnvironmentRecordTrait for ObjectEnvironmentRecord { fn get_binding_value(&self, name: &str, strict: bool) -> Value { if self.bindings.has_field(name) { - self.bindings.get_field_slice(name) + self.bindings.get_field(name) } else { if strict { // TODO: throw error here diff --git a/boa/src/exec/array.rs b/boa/src/exec/array.rs index 2aa92a955dd..548acd461f3 100644 --- a/boa/src/exec/array.rs +++ b/boa/src/exec/array.rs @@ -2,16 +2,13 @@ use super::{Executable, Interpreter}; use crate::{ - builtins::{ - array::{add_to_array_object, new_array}, - value::ResultValue, - }, + builtins::{Array, ResultValue}, syntax::ast::node::{ArrayDecl, Node}, }; impl Executable for ArrayDecl { fn run(&self, interpreter: &mut Interpreter) -> ResultValue { - let array = new_array(interpreter)?; + let array = Array::new_array(interpreter)?; let mut elements = Vec::new(); for elem in self.as_ref() { if let Node::Spread(ref x) = elem { @@ -22,7 +19,7 @@ impl Executable for ArrayDecl { } elements.push(elem.run(interpreter)?); } - add_to_array_object(&array, &elements)?; + Array::add_to_array_object(&array, &elements)?; Ok(array) } diff --git a/boa/src/exec/declaration.rs b/boa/src/exec/declaration.rs index c705b3c8ff9..3a2af605df3 100644 --- a/boa/src/exec/declaration.rs +++ b/boa/src/exec/declaration.rs @@ -21,7 +21,7 @@ impl Executable for FunctionDecl { ); // Set the name and assign it in the current environment - val.set_field_slice("name", self.name().into()); + val.set_field("name", self.name()); interpreter.realm_mut().environment.create_mutable_binding( self.name().to_owned(), false, @@ -46,7 +46,7 @@ impl Executable for FunctionExpr { ); if let Some(name) = self.name() { - val.set_field_slice("name", Value::from(name)); + val.set_field("name", Value::from(name)); } Ok(val) diff --git a/boa/src/exec/mod.rs b/boa/src/exec/mod.rs index 5ea5b5d4044..1185f538d7a 100644 --- a/boa/src/exec/mod.rs +++ b/boa/src/exec/mod.rs @@ -67,8 +67,8 @@ impl Interpreter { .environment .get_global_object() .expect("Could not get the global object") - .get_field_slice("Function") - .get_field_slice("Prototype"); + .get_field("Function") + .get_field("Prototype"); // Every new function has a prototype property pre-made let global_val = &self @@ -91,8 +91,8 @@ impl Interpreter { new_func.set_func(func); let val = Value::from(new_func); val.set_internal_slot(INSTANCE_PROTOTYPE, function_prototype.clone()); - val.set_field_slice(PROTOTYPE, proto); - val.set_field_slice("length", Value::from(params_len)); + val.set_field(PROTOTYPE, proto); + val.set_field("length", Value::from(params_len)); val } @@ -142,10 +142,9 @@ impl Interpreter { if let ValueData::Object(ref x) = *value.deref().borrow() { // Check if object is array if x.deref().borrow().kind == ObjectKind::Array { - let length: i32 = - self.value_to_rust_number(&value.get_field_slice("length")) as i32; + let length: i32 = self.value_to_rust_number(&value.get_field("length")) as i32; let values: Vec = (0..length) - .map(|idx| value.get_field_slice(&idx.to_string())) + .map(|idx| value.get_field(idx.to_string())) .collect(); return Ok(values); } @@ -166,7 +165,7 @@ impl Interpreter { vec!["valueOf", "toString"] }; for name in method_names.iter() { - let method: Value = o.get_field_slice(name); + let method: Value = o.get_field(*name); if method.is_function() { let result = self.call(&method, o, &[]); match result { @@ -277,7 +276,7 @@ impl Interpreter { .realm .environment .get_binding_value("Boolean") - .get_field_slice(PROTOTYPE); + .get_field(PROTOTYPE); let bool_obj = Value::new_object_from_prototype(proto, ObjectKind::Boolean); bool_obj.set_internal_slot("BooleanData", value.clone()); @@ -288,7 +287,7 @@ impl Interpreter { .realm .environment .get_binding_value("Number") - .get_field_slice(PROTOTYPE); + .get_field(PROTOTYPE); let number_obj = Value::new_object_from_prototype(proto, ObjectKind::Number); number_obj.set_internal_slot("NumberData", value.clone()); Ok(number_obj) @@ -298,7 +297,7 @@ impl Interpreter { .realm .environment .get_binding_value("String") - .get_field_slice(PROTOTYPE); + .get_field(PROTOTYPE); let string_obj = Value::new_object_from_prototype(proto, ObjectKind::String); string_obj.set_internal_slot("StringData", value.clone()); Ok(string_obj) @@ -309,7 +308,7 @@ impl Interpreter { .realm .environment .get_binding_value("BigInt") - .get_field_slice(PROTOTYPE); + .get_field(PROTOTYPE); let bigint_obj = Value::new_object_from_prototype(proto, ObjectKind::BigInt); bigint_obj.set_internal_slot("BigIntData", value.clone()); Ok(bigint_obj) @@ -353,9 +352,7 @@ impl Interpreter { .set_mutable_binding(name.as_ref(), value.clone(), true); Ok(value) } - Node::GetConstField(ref obj, ref field) => { - Ok(obj.run(self)?.set_field_slice(field, value)) - } + Node::GetConstField(ref obj, ref field) => Ok(obj.run(self)?.set_field(field, value)), Node::GetField(ref obj, ref field) => { Ok(obj.run(self)?.set_field(field.run(self)?, value)) } @@ -387,14 +384,12 @@ impl Executable for Node { } Node::GetConstField(ref obj, ref field) => { let val_obj = obj.run(interpreter)?; - Ok(val_obj.borrow().get_field_slice(field)) + Ok(val_obj.borrow().get_field(field)) } Node::GetField(ref obj, ref field) => { let val_obj = obj.run(interpreter)?; let val_field = field.run(interpreter)?; - Ok(val_obj - .borrow() - .get_field_slice(&val_field.borrow().to_string())) + Ok(val_obj.borrow().get_field(val_field.borrow().to_string())) } Node::Call(ref callee, ref args) => { let (mut this, func) = match callee.deref() { @@ -405,14 +400,14 @@ impl Executable for Node { .to_object(&obj) .expect("failed to convert to object"); } - (obj.clone(), obj.borrow().get_field_slice(field)) + (obj.clone(), obj.borrow().get_field(field)) } Node::GetField(ref obj, ref field) => { let obj = obj.run(interpreter)?; let field = field.run(interpreter)?; ( obj.clone(), - obj.borrow().get_field_slice(&field.borrow().to_string()), + obj.borrow().get_field(field.borrow().to_string()), ) } _ => ( @@ -506,12 +501,12 @@ impl Executable for Node { match property { PropertyDefinition::Property(key, value) => { obj.borrow() - .set_field_slice(&key.clone(), value.run(interpreter)?); + .set_field(&key.clone(), value.run(interpreter)?); } PropertyDefinition::MethodDefinition(kind, name, func) => { if let MethodDefinitionKind::Ordinary = kind { obj.borrow() - .set_field_slice(&name.clone(), func.run(interpreter)?); + .set_field(&name.clone(), func.run(interpreter)?); } else { // TODO: Implement other types of MethodDefinitionKinds. unimplemented!("other types of property method definitions."); @@ -546,7 +541,7 @@ impl Executable for Node { // Create a blank object, then set its __proto__ property to the [Constructor].prototype this.borrow().set_internal_slot( INSTANCE_PROTOTYPE, - func_object.borrow().get_field_slice(PROTOTYPE), + func_object.borrow().get_field(PROTOTYPE), ); match *(func_object.borrow()).deref() { diff --git a/boa/src/exec/operator.rs b/boa/src/exec/operator.rs index 4b7576d54d7..d4cb97495cb 100644 --- a/boa/src/exec/operator.rs +++ b/boa/src/exec/operator.rs @@ -32,7 +32,7 @@ impl Executable for Assign { } Node::GetConstField(ref obj, ref field) => { let val_obj = obj.run(interpreter)?; - val_obj.set_field_slice(field, val.clone()); + val_obj.set_field(field, val.clone()); } Node::GetField(ref obj, ref field) => { let val_obj = obj.run(interpreter)?; @@ -125,10 +125,10 @@ impl Executable for BinOp { } Node::GetConstField(ref obj, ref field) => { let v_r_a = obj.run(interpreter)?; - let v_a = v_r_a.get_field_slice(field); + let v_a = v_r_a.get_field(field); let v_b = self.rhs().run(interpreter)?; let value = Self::run_assign(op, v_a, v_b); - v_r_a.set_field_slice(&field.clone(), value.clone()); + v_r_a.set_field(&field.clone(), value.clone()); Ok(value) } _ => Ok(Value::undefined()),