Browse Source

Moved value operations from `Interpreter` to `Value` (#625)

pull/638/head
HalidOdat 4 years ago committed by GitHub
parent
commit
c5b708b2ef
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 86
      boa/src/builtins/array/mod.rs
  2. 8
      boa/src/builtins/bigint/mod.rs
  3. 39
      boa/src/builtins/console/mod.rs
  4. 40
      boa/src/builtins/date/mod.rs
  5. 22
      boa/src/builtins/date/tests.rs
  6. 8
      boa/src/builtins/error/mod.rs
  7. 6
      boa/src/builtins/error/range.rs
  8. 6
      boa/src/builtins/error/reference.rs
  9. 5
      boa/src/builtins/error/syntax.rs
  10. 6
      boa/src/builtins/error/type.rs
  11. 2
      boa/src/builtins/function/mod.rs
  12. 9
      boa/src/builtins/json/mod.rs
  13. 20
      boa/src/builtins/json/tests.rs
  14. 4
      boa/src/builtins/map/mod.rs
  15. 74
      boa/src/builtins/math/mod.rs
  16. 196
      boa/src/builtins/math/tests.rs
  17. 24
      boa/src/builtins/number/mod.rs
  18. 26
      boa/src/builtins/number/tests.rs
  19. 15
      boa/src/builtins/object/mod.rs
  20. 14
      boa/src/builtins/regexp/mod.rs
  21. 177
      boa/src/builtins/string/mod.rs
  22. 2
      boa/src/builtins/symbol/mod.rs
  23. 2
      boa/src/builtins/symbol/tests.rs
  24. 19
      boa/src/builtins/value/conversions.rs
  25. 74
      boa/src/builtins/value/display.rs
  26. 22
      boa/src/builtins/value/equality.rs
  27. 489
      boa/src/builtins/value/mod.rs
  28. 131
      boa/src/builtins/value/operations.rs
  29. 47
      boa/src/builtins/value/tests.rs
  30. 9
      boa/src/exec/call/mod.rs
  31. 10
      boa/src/exec/field/mod.rs
  32. 341
      boa/src/exec/mod.rs
  33. 26
      boa/src/exec/operator/mod.rs
  34. 81
      boa/src/exec/tests.rs
  35. 6
      boa/src/lib.rs
  36. 10
      boa_cli/src/main.rs
  37. 4
      boa_wasm/src/lib.rs

86
boa/src/builtins/array/mod.rs

@ -69,7 +69,7 @@ impl Array {
let array_obj_ptr = array_obj.clone(); let array_obj_ptr = array_obj.clone();
// Wipe existing contents of the array object // Wipe existing contents of the array object
let orig_length = i32::from(&array_obj.get_field("length")); let orig_length = array_obj.get_field("length").as_number().unwrap() as i32;
for n in 0..orig_length { for n in 0..orig_length {
array_obj_ptr.remove_property(&n.to_string()); array_obj_ptr.remove_property(&n.to_string());
} }
@ -90,7 +90,7 @@ impl Array {
/// Utility function which takes an existing array object and puts additional /// Utility function which takes an existing array object and puts additional
/// values on the end, correctly rewriting the length /// values on the end, correctly rewriting the length
pub(crate) fn add_to_array_object(array_ptr: &Value, add_values: &[Value]) -> ResultValue { pub(crate) fn add_to_array_object(array_ptr: &Value, add_values: &[Value]) -> ResultValue {
let orig_length = i32::from(&array_ptr.get_field("length")); let orig_length = array_ptr.get_field("length").as_number().unwrap() as i32;
for (n, value) in add_values.iter().enumerate() { for (n, value) in add_values.iter().enumerate() {
let new_index = orig_length.wrapping_add(n as i32); let new_index = orig_length.wrapping_add(n as i32);
@ -124,7 +124,7 @@ impl Array {
let mut length = args.len() as i32; let mut length = args.len() as i32;
match args.len() { match args.len() {
1 if args[0].is_integer() => { 1 if args[0].is_integer() => {
length = i32::from(&args[0]); length = args[0].as_number().unwrap() as i32;
// TODO: It should not create an array of undefineds, but an empty array ("holy" array in V8) with length `n`. // 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 { for n in 0..length {
this.set_field(n.to_string(), Value::undefined()); this.set_field(n.to_string(), Value::undefined());
@ -195,13 +195,13 @@ impl Array {
// one) // one)
let mut new_values: Vec<Value> = Vec::new(); let mut new_values: Vec<Value> = Vec::new();
let this_length = i32::from(&this.get_field("length")); let this_length = this.get_field("length").as_number().unwrap() as i32;
for n in 0..this_length { for n in 0..this_length {
new_values.push(this.get_field(n.to_string())); new_values.push(this.get_field(n.to_string()));
} }
for concat_array in args { for concat_array in args {
let concat_length = i32::from(&concat_array.get_field("length")); let concat_length = concat_array.get_field("length").as_number().unwrap() as i32;
for n in 0..concat_length { for n in 0..concat_length {
new_values.push(concat_array.get_field(n.to_string())); new_values.push(concat_array.get_field(n.to_string()));
} }
@ -238,7 +238,7 @@ impl Array {
/// [spec]: https://tc39.es/ecma262/#sec-array.prototype.pop /// [spec]: https://tc39.es/ecma262/#sec-array.prototype.pop
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/pop /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/pop
pub(crate) fn pop(this: &Value, _: &[Value], _: &mut Interpreter) -> ResultValue { pub(crate) fn pop(this: &Value, _: &[Value], _: &mut Interpreter) -> ResultValue {
let curr_length = i32::from(&this.get_field("length")); let curr_length = this.get_field("length").as_number().unwrap() as i32;
if curr_length < 1 { if curr_length < 1 {
return Ok(Value::undefined()); return Ok(Value::undefined());
} }
@ -271,7 +271,7 @@ impl Array {
let callback_arg = args.get(0).expect("Could not get `callbackFn` argument."); let callback_arg = args.get(0).expect("Could not get `callbackFn` argument.");
let this_arg = args.get(1).cloned().unwrap_or_else(Value::undefined); let this_arg = args.get(1).cloned().unwrap_or_else(Value::undefined);
let length = i32::from(&this.get_field("length")); let length = this.get_field("length").as_number().unwrap() as i32;
for i in 0..length { for i in 0..length {
let element = this.get_field(i.to_string()); let element = this.get_field(i.to_string());
@ -299,14 +299,16 @@ impl Array {
let separator = if args.is_empty() { let separator = if args.is_empty() {
String::from(",") String::from(",")
} else { } else {
ctx.to_string(args.get(0).expect("Could not get argument"))? args.get(0)
.expect("Could not get argument")
.to_string(ctx)?
.to_string() .to_string()
}; };
let mut elem_strs = Vec::new(); let mut elem_strs = Vec::new();
let length = i32::from(&this.get_field("length")); let length = this.get_field("length").as_number().unwrap() as i32;
for n in 0..length { for n in 0..length {
let elem_str = ctx.to_string(&this.get_field(n.to_string()))?.to_string(); let elem_str = this.get_field(n.to_string()).to_string(ctx)?.to_string();
elem_strs.push(elem_str); elem_strs.push(elem_str);
} }
@ -367,7 +369,7 @@ impl Array {
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/reverse /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/reverse
#[allow(clippy::else_if_without_else)] #[allow(clippy::else_if_without_else)]
pub(crate) fn reverse(this: &Value, _: &[Value], _: &mut Interpreter) -> ResultValue { pub(crate) fn reverse(this: &Value, _: &[Value], _: &mut Interpreter) -> ResultValue {
let len = i32::from(&this.get_field("length")); let len = this.get_field("length").as_number().unwrap() as i32;
let middle: i32 = len.wrapping_div(2); let middle: i32 = len.wrapping_div(2);
for lower in 0..middle { for lower in 0..middle {
@ -405,7 +407,7 @@ impl Array {
/// [spec]: https://tc39.es/ecma262/#sec-array.prototype.shift /// [spec]: https://tc39.es/ecma262/#sec-array.prototype.shift
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/shift /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/shift
pub(crate) fn shift(this: &Value, _: &[Value], _: &mut Interpreter) -> ResultValue { pub(crate) fn shift(this: &Value, _: &[Value], _: &mut Interpreter) -> ResultValue {
let len = i32::from(&this.get_field("length")); let len = this.get_field("length").as_number().unwrap() as i32;
if len == 0 { if len == 0 {
this.set_field("length", 0); this.set_field("length", 0);
@ -447,7 +449,7 @@ impl Array {
/// [spec]: https://tc39.es/ecma262/#sec-array.prototype.unshift /// [spec]: https://tc39.es/ecma262/#sec-array.prototype.unshift
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/unshift /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/unshift
pub(crate) fn unshift(this: &Value, args: &[Value], _: &mut Interpreter) -> ResultValue { pub(crate) fn unshift(this: &Value, args: &[Value], _: &mut Interpreter) -> ResultValue {
let len = i32::from(&this.get_field("length")); let len = this.get_field("length").as_number().unwrap() as i32;
let arg_c: i32 = args.len() as i32; let arg_c: i32 = args.len() as i32;
if arg_c > 0 { if arg_c > 0 {
@ -507,7 +509,7 @@ impl Array {
Value::undefined() Value::undefined()
}; };
let mut i = 0; let mut i = 0;
let max_len = i32::from(&this.get_field("length")); let max_len = this.get_field("length").as_number().unwrap() as i32;
let mut len = max_len; let mut len = max_len;
while i < len { while i < len {
let element = this.get_field(i.to_string()); let element = this.get_field(i.to_string());
@ -516,7 +518,10 @@ impl Array {
if !result.to_boolean() { if !result.to_boolean() {
return Ok(Value::from(false)); return Ok(Value::from(false));
} }
len = min(max_len, i32::from(&this.get_field("length"))); len = min(
max_len,
this.get_field("length").as_number().unwrap() as i32,
);
i += 1; i += 1;
} }
Ok(Value::from(true)) Ok(Value::from(true))
@ -543,7 +548,7 @@ impl Array {
let callback = args.get(0).cloned().unwrap_or_else(Value::undefined); let callback = args.get(0).cloned().unwrap_or_else(Value::undefined);
let this_val = args.get(1).cloned().unwrap_or_else(Value::undefined); let this_val = args.get(1).cloned().unwrap_or_else(Value::undefined);
let length = i32::from(&this.get_field("length")); let length = this.get_field("length").as_number().unwrap() as i32;
let new = Self::new_array(interpreter)?; let new = Self::new_array(interpreter)?;
@ -587,11 +592,11 @@ impl Array {
} }
let search_element = args[0].clone(); let search_element = args[0].clone();
let len = i32::from(&this.get_field("length")); let len = this.get_field("length").as_number().unwrap() as i32;
let mut idx = match args.get(1) { let mut idx = match args.get(1) {
Some(from_idx_ptr) => { Some(from_idx_ptr) => {
let from_idx = i32::from(from_idx_ptr); let from_idx = from_idx_ptr.as_number().unwrap() as i32;
if from_idx < 0 { if from_idx < 0 {
len + from_idx len + from_idx
@ -640,11 +645,11 @@ impl Array {
} }
let search_element = args[0].clone(); let search_element = args[0].clone();
let len = i32::from(&this.get_field("length")); let len = this.get_field("length").as_number().unwrap() as i32;
let mut idx = match args.get(1) { let mut idx = match args.get(1) {
Some(from_idx_ptr) => { Some(from_idx_ptr) => {
let from_idx = i32::from(from_idx_ptr); let from_idx = from_idx_ptr.as_number().unwrap() as i32;
if from_idx >= 0 { if from_idx >= 0 {
min(from_idx, len - 1) min(from_idx, len - 1)
@ -688,7 +693,7 @@ impl Array {
} }
let callback = &args[0]; let callback = &args[0];
let this_arg = args.get(1).cloned().unwrap_or_else(Value::undefined); let this_arg = args.get(1).cloned().unwrap_or_else(Value::undefined);
let len = i32::from(&this.get_field("length")); let len = this.get_field("length").as_number().unwrap() as i32;
for i in 0..len { for i in 0..len {
let element = this.get_field(i.to_string()); let element = this.get_field(i.to_string());
let arguments = [element.clone(), Value::from(i), this.clone()]; let arguments = [element.clone(), Value::from(i), this.clone()];
@ -727,7 +732,7 @@ impl Array {
let this_arg = args.get(1).cloned().unwrap_or_else(Value::undefined); let this_arg = args.get(1).cloned().unwrap_or_else(Value::undefined);
let length = i32::from(&this.get_field("length")); let length = this.get_field("length").as_number().unwrap() as i32;
for i in 0..length { for i in 0..length {
let element = this.get_field(i.to_string()); let element = this.get_field(i.to_string());
@ -754,16 +759,16 @@ impl Array {
/// ///
/// [spec]: https://tc39.es/ecma262/#sec-array.prototype.fill /// [spec]: https://tc39.es/ecma262/#sec-array.prototype.fill
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/fill /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/fill
pub(crate) fn fill(this: &Value, args: &[Value], _: &mut Interpreter) -> ResultValue { pub(crate) fn fill(this: &Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue {
let len: i32 = i32::from(&this.get_field("length")); let len: i32 = this.get_field("length").as_number().unwrap() as i32;
let default_value = Value::undefined(); let default_value = Value::undefined();
let value = args.get(0).unwrap_or(&default_value); 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_start = args.get(1).unwrap_or(&default_value).to_number(ctx)? as i32;
let relative_end_val = args.get(2).unwrap_or(&default_value); let relative_end_val = args.get(2).unwrap_or(&default_value);
let relative_end = if relative_end_val.is_undefined() { let relative_end = if relative_end_val.is_undefined() {
len len
} else { } else {
relative_end_val.to_number() as i32 relative_end_val.to_number(ctx)? as i32
}; };
let start = if relative_start < 0 { let start = if relative_start < 0 {
max(len + relative_start, 0) max(len + relative_start, 0)
@ -796,7 +801,7 @@ impl Array {
pub(crate) fn includes_value(this: &Value, args: &[Value], _: &mut Interpreter) -> ResultValue { pub(crate) fn includes_value(this: &Value, args: &[Value], _: &mut Interpreter) -> ResultValue {
let search_element = args.get(0).cloned().unwrap_or_else(Value::undefined); let search_element = args.get(0).cloned().unwrap_or_else(Value::undefined);
let length = i32::from(&this.get_field("length")); let length = this.get_field("length").as_number().unwrap() as i32;
for idx in 0..length { for idx in 0..length {
let check_element = this.get_field(idx.to_string()).clone(); let check_element = this.get_field(idx.to_string()).clone();
@ -829,14 +834,14 @@ impl Array {
interpreter: &mut Interpreter, interpreter: &mut Interpreter,
) -> ResultValue { ) -> ResultValue {
let new_array = Self::new_array(interpreter)?; let new_array = Self::new_array(interpreter)?;
let len = i32::from(&this.get_field("length")); let len = this.get_field("length").as_number().unwrap() as i32;
let start = match args.get(0) { let start = match args.get(0) {
Some(v) => i32::from(v), Some(v) => v.as_number().unwrap() as i32,
None => 0, None => 0,
}; };
let end = match args.get(1) { let end = match args.get(1) {
Some(v) => i32::from(v), Some(v) => v.as_number().unwrap() as i32,
None => len, None => len,
}; };
@ -886,7 +891,7 @@ impl Array {
let callback = args.get(0).cloned().unwrap_or_else(Value::undefined); let callback = args.get(0).cloned().unwrap_or_else(Value::undefined);
let this_val = args.get(1).cloned().unwrap_or_else(Value::undefined); let this_val = args.get(1).cloned().unwrap_or_else(Value::undefined);
let length = i32::from(&this.get_field("length")); let length = this.get_field("length").as_number().unwrap() as i32;
let new = Self::new_array(interpreter)?; let new = Self::new_array(interpreter)?;
@ -939,7 +944,7 @@ impl Array {
Value::undefined() Value::undefined()
}; };
let mut i = 0; let mut i = 0;
let max_len = i32::from(&this.get_field("length")); let max_len = this.get_field("length").as_number().unwrap() as i32;
let mut len = max_len; let mut len = max_len;
while i < len { while i < len {
let element = this.get_field(i.to_string()); let element = this.get_field(i.to_string());
@ -949,7 +954,10 @@ impl Array {
return Ok(Value::from(true)); return Ok(Value::from(true));
} }
// the length of the array must be updated because the callback can mutate it. // the length of the array must be updated because the callback can mutate it.
len = min(max_len, i32::from(&this.get_field("length"))); len = min(
max_len,
this.get_field("length").as_number().unwrap() as i32,
);
i += 1; i += 1;
} }
Ok(Value::from(false)) Ok(Value::from(false))
@ -971,13 +979,13 @@ impl Array {
args: &[Value], args: &[Value],
interpreter: &mut Interpreter, interpreter: &mut Interpreter,
) -> ResultValue { ) -> ResultValue {
let this = interpreter.to_object(this)?; let this = this.to_object(interpreter)?;
let callback = match args.get(0) { let callback = match args.get(0) {
Some(value) if value.is_function() => value, Some(value) if value.is_function() => value,
_ => return interpreter.throw_type_error("Reduce was called without a callback"), _ => return interpreter.throw_type_error("Reduce was called without a callback"),
}; };
let initial_value = args.get(1).cloned().unwrap_or_else(Value::undefined); let initial_value = args.get(1).cloned().unwrap_or_else(Value::undefined);
let mut length = interpreter.to_length(&this.get_field("length"))?; let mut length = this.get_field("length").to_length(interpreter)?;
if length == 0 && initial_value.is_undefined() { if length == 0 && initial_value.is_undefined() {
return interpreter return interpreter
.throw_type_error("Reduce was called on an empty array and with no initial value"); .throw_type_error("Reduce was called on an empty array and with no initial value");
@ -1015,7 +1023,7 @@ impl Array {
/* We keep track of possibly shortened length in order to prevent unnecessary iteration. /* We keep track of possibly shortened length in order to prevent unnecessary iteration.
It may also be necessary to do this since shortening the array length does not It may also be necessary to do this since shortening the array length does not
delete array elements. See: https://github.com/boa-dev/boa/issues/557 */ delete array elements. See: https://github.com/boa-dev/boa/issues/557 */
length = min(length, interpreter.to_length(&this.get_field("length"))?); length = min(length, this.get_field("length").to_length(interpreter)?);
} }
k += 1; k += 1;
} }
@ -1038,13 +1046,13 @@ impl Array {
args: &[Value], args: &[Value],
interpreter: &mut Interpreter, interpreter: &mut Interpreter,
) -> ResultValue { ) -> ResultValue {
let this = interpreter.to_object(this)?; let this = this.to_object(interpreter)?;
let callback = match args.get(0) { let callback = match args.get(0) {
Some(value) if value.is_function() => value, Some(value) if value.is_function() => value,
_ => return interpreter.throw_type_error("reduceRight was called without a callback"), _ => return interpreter.throw_type_error("reduceRight was called without a callback"),
}; };
let initial_value = args.get(1).cloned().unwrap_or_else(Value::undefined); let initial_value = args.get(1).cloned().unwrap_or_else(Value::undefined);
let mut length = interpreter.to_length(&this.get_field("length"))?; let mut length = this.get_field("length").to_length(interpreter)?;
if length == 0 { if length == 0 {
if initial_value.is_undefined() { if initial_value.is_undefined() {
return interpreter.throw_type_error( return interpreter.throw_type_error(
@ -1092,7 +1100,7 @@ impl Array {
/* We keep track of possibly shortened length in order to prevent unnecessary iteration. /* We keep track of possibly shortened length in order to prevent unnecessary iteration.
It may also be necessary to do this since shortening the array length does not It may also be necessary to do this since shortening the array length does not
delete array elements. See: https://github.com/boa-dev/boa/issues/557 */ delete array elements. See: https://github.com/boa-dev/boa/issues/557 */
length = min(length, interpreter.to_length(&this.get_field("length"))?); length = min(length, this.get_field("length").to_length(interpreter)?);
// move k to the last defined element if necessary or return if the length was set to 0 // move k to the last defined element if necessary or return if the length was set to 0
if k >= length { if k >= length {

8
boa/src/builtins/bigint/mod.rs

@ -93,7 +93,7 @@ impl BigInt {
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/BigInt/BigInt /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/BigInt/BigInt
pub(crate) fn make_bigint(_: &Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue { pub(crate) fn make_bigint(_: &Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue {
let data = match args.get(0) { let data = match args.get(0) {
Some(ref value) => ctx.to_bigint(value)?, Some(ref value) => value.to_bigint(ctx)?,
None => RcBigInt::from(Self::from(0)), None => RcBigInt::from(Self::from(0)),
}; };
Ok(Value::from(data)) Ok(Value::from(data))
@ -112,7 +112,7 @@ impl BigInt {
#[allow(clippy::wrong_self_convention)] #[allow(clippy::wrong_self_convention)]
pub(crate) fn to_string(this: &Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue { pub(crate) fn to_string(this: &Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue {
let radix = if !args.is_empty() { let radix = if !args.is_empty() {
args[0].to_integer() args[0].to_integer(ctx)? as i32
} else { } else {
10 10
}; };
@ -184,10 +184,10 @@ impl BigInt {
let bits_arg = args.get(0).unwrap_or(&undefined_value); let bits_arg = args.get(0).unwrap_or(&undefined_value);
let bigint_arg = args.get(1).unwrap_or(&undefined_value); let bigint_arg = args.get(1).unwrap_or(&undefined_value);
let bits = ctx.to_index(bits_arg)?; let bits = bits_arg.to_index(ctx)?;
let bits = u32::try_from(bits).unwrap_or(u32::MAX); let bits = u32::try_from(bits).unwrap_or(u32::MAX);
let bigint = ctx.to_bigint(bigint_arg)?; let bigint = bigint_arg.to_bigint(ctx)?;
Ok(( Ok((
bigint bigint

39
boa/src/builtins/console/mod.rs

@ -60,7 +60,7 @@ pub(crate) fn logger(msg: LogMessage, console_state: &Console) {
/// This represents the `console` formatter. /// This represents the `console` formatter.
pub fn formatter(data: &[Value], ctx: &mut Interpreter) -> Result<String, Value> { pub fn formatter(data: &[Value], ctx: &mut Interpreter) -> Result<String, Value> {
let target = ctx.to_string(&data.get(0).cloned().unwrap_or_default())?; let target = data.get(0).cloned().unwrap_or_default().to_string(ctx)?;
match data.len() { match data.len() {
0 => Ok(String::new()), 0 => Ok(String::new()),
1 => Ok(target.to_string()), 1 => Ok(target.to_string()),
@ -74,26 +74,37 @@ pub fn formatter(data: &[Value], ctx: &mut Interpreter) -> Result<String, Value>
match fmt { match fmt {
/* integer */ /* integer */
'd' | 'i' => { 'd' | 'i' => {
let arg = get_arg_at_index::<i32>(data, arg_index).unwrap_or_default(); let arg = data
.get(arg_index)
.cloned()
.unwrap_or_default()
.to_integer(ctx)?;
formatted.push_str(&format!("{}", arg)); formatted.push_str(&format!("{}", arg));
arg_index += 1; arg_index += 1;
} }
/* float */ /* float */
'f' => { 'f' => {
let arg = get_arg_at_index::<f64>(data, arg_index).unwrap_or_default(); let arg = data
.get(arg_index)
.cloned()
.unwrap_or_default()
.to_number(ctx)?;
formatted.push_str(&format!("{number:.prec$}", number = arg, prec = 6)); formatted.push_str(&format!("{number:.prec$}", number = arg, prec = 6));
arg_index += 1 arg_index += 1
} }
/* object, FIXME: how to render this properly? */ /* object, FIXME: how to render this properly? */
'o' | 'O' => { 'o' | 'O' => {
let arg = data.get(arg_index).cloned().unwrap_or_default(); let arg = data.get(arg_index).cloned().unwrap_or_default();
formatted.push_str(&format!("{}", arg)); formatted.push_str(&format!("{}", arg.display()));
arg_index += 1 arg_index += 1
} }
/* string */ /* string */
's' => { 's' => {
let arg = let arg = data
ctx.to_string(&data.get(arg_index).cloned().unwrap_or_default())?; .get(arg_index)
.cloned()
.unwrap_or_default()
.to_string(ctx)?;
formatted.push_str(&arg); formatted.push_str(&arg);
arg_index += 1 arg_index += 1
} }
@ -111,7 +122,7 @@ pub fn formatter(data: &[Value], ctx: &mut Interpreter) -> Result<String, Value>
/* unformatted data */ /* unformatted data */
for rest in data.iter().skip(arg_index) { for rest in data.iter().skip(arg_index) {
formatted.push_str(&format!(" {}", ctx.to_string(rest)?)) formatted.push_str(&format!(" {}", rest.to_string(ctx)?))
} }
Ok(formatted) Ok(formatted)
@ -153,7 +164,7 @@ impl Console {
} else if !args[0].is_string() { } else if !args[0].is_string() {
args.insert(0, Value::from(message)); args.insert(0, Value::from(message));
} else { } else {
let concat = format!("{}: {}", message, args[0]); let concat = format!("{}: {}", message, args[0].display());
args[0] = Value::from(concat); args[0] = Value::from(concat);
} }
@ -289,7 +300,7 @@ impl Console {
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/API/console/count /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/API/console/count
pub(crate) fn count(_: &Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue { pub(crate) fn count(_: &Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue {
let label = match args.get(0) { let label = match args.get(0) {
Some(value) => ctx.to_string(value)?, Some(value) => value.to_string(ctx)?,
None => "default".into(), None => "default".into(),
}; };
@ -313,7 +324,7 @@ impl Console {
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/API/countReset /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/API/countReset
pub(crate) fn count_reset(_: &Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue { pub(crate) fn count_reset(_: &Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue {
let label = match args.get(0) { let label = match args.get(0) {
Some(value) => ctx.to_string(value)?, Some(value) => value.to_string(ctx)?,
None => "default".into(), None => "default".into(),
}; };
@ -347,7 +358,7 @@ impl Console {
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/API/console/time /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/API/console/time
pub(crate) fn time(_: &Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue { pub(crate) fn time(_: &Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue {
let label = match args.get(0) { let label = match args.get(0) {
Some(value) => ctx.to_string(value)?, Some(value) => value.to_string(ctx)?,
None => "default".into(), None => "default".into(),
}; };
@ -376,7 +387,7 @@ impl Console {
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/API/console/timeLog /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/API/console/timeLog
pub(crate) fn time_log(_: &Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue { pub(crate) fn time_log(_: &Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue {
let label = match args.get(0) { let label = match args.get(0) {
Some(value) => ctx.to_string(value)?, Some(value) => value.to_string(ctx)?,
None => "default".into(), None => "default".into(),
}; };
@ -384,7 +395,7 @@ impl Console {
let time = Self::system_time_in_ms(); let time = Self::system_time_in_ms();
let mut concat = format!("{}: {} ms", label, time - t); let mut concat = format!("{}: {} ms", label, time - t);
for msg in args.iter().skip(1) { for msg in args.iter().skip(1) {
concat = concat + " " + &msg.to_string(); concat = concat + " " + &msg.display().to_string();
} }
logger(LogMessage::Log(concat), ctx.console()); logger(LogMessage::Log(concat), ctx.console());
} else { } else {
@ -409,7 +420,7 @@ impl Console {
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/API/console/timeEnd /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/API/console/timeEnd
pub(crate) fn time_end(_: &Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue { pub(crate) fn time_end(_: &Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue {
let label = match args.get(0) { let label = match args.get(0) {
Some(value) => ctx.to_string(value)?, Some(value) => value.to_string(ctx)?,
None => "default".into(), None => "default".into(),
}; };

40
boa/src/builtins/date/mod.rs

@ -5,9 +5,9 @@ use crate::{
builtins::{ builtins::{
function::{make_builtin_fn, make_constructor_fn}, function::{make_builtin_fn, make_constructor_fn},
object::ObjectData, object::ObjectData,
value::PreferredType,
ResultValue, Value, ResultValue, Value,
}, },
exec::PreferredType,
BoaProfiler, Interpreter, BoaProfiler, Interpreter,
}; };
use chrono::{prelude::*, Duration, LocalResult}; use chrono::{prelude::*, Duration, LocalResult};
@ -62,7 +62,7 @@ macro_rules! setter_method {
args args
.get($e) .get($e)
.and_then(|value| { .and_then(|value| {
ctx.to_numeric_number(value).map_or_else( value.to_numeric_number(ctx).map_or_else(
|_| None, |_| None,
|value| { |value| {
if value == 0f64 || value.is_normal() { if value == 0f64 || value.is_normal() {
@ -306,13 +306,13 @@ impl Date {
let value = &args[0]; let value = &args[0];
let tv = match this_time_value(value, ctx) { let tv = match this_time_value(value, ctx) {
Ok(dt) => dt.0, Ok(dt) => dt.0,
_ => match &ctx.to_primitive(value, PreferredType::Default)? { _ => match value.to_primitive(ctx, PreferredType::Default)? {
Value::String(str) => match chrono::DateTime::parse_from_rfc3339(&str) { Value::String(ref str) => match chrono::DateTime::parse_from_rfc3339(&str) {
Ok(dt) => Some(dt.naive_utc()), Ok(dt) => Some(dt.naive_utc()),
_ => None, _ => None,
}, },
tv => { tv => {
let tv = ctx.to_number(&tv)?; let tv = tv.to_number(ctx)?;
let secs = (tv / 1_000f64) as i64; let secs = (tv / 1_000f64) as i64;
let nsecs = ((tv % 1_000f64) * 1_000_000f64) as u32; let nsecs = ((tv % 1_000f64) * 1_000_000f64) as u32;
NaiveDateTime::from_timestamp_opt(secs, nsecs) NaiveDateTime::from_timestamp_opt(secs, nsecs)
@ -340,13 +340,13 @@ impl Date {
args: &[Value], args: &[Value],
ctx: &mut Interpreter, ctx: &mut Interpreter,
) -> ResultValue { ) -> ResultValue {
let year = ctx.to_number(&args[0])?; let year = args[0].to_number(ctx)?;
let month = ctx.to_number(&args[1])?; let month = args[1].to_number(ctx)?;
let day = args.get(2).map_or(Ok(1f64), |value| ctx.to_number(value))?; let day = args.get(2).map_or(Ok(1f64), |value| value.to_number(ctx))?;
let hour = args.get(3).map_or(Ok(0f64), |value| ctx.to_number(value))?; let hour = args.get(3).map_or(Ok(0f64), |value| value.to_number(ctx))?;
let min = args.get(4).map_or(Ok(0f64), |value| ctx.to_number(value))?; let min = args.get(4).map_or(Ok(0f64), |value| value.to_number(ctx))?;
let sec = args.get(5).map_or(Ok(0f64), |value| ctx.to_number(value))?; let sec = args.get(5).map_or(Ok(0f64), |value| value.to_number(ctx))?;
let milli = args.get(6).map_or(Ok(0f64), |value| ctx.to_number(value))?; let milli = args.get(6).map_or(Ok(0f64), |value| value.to_number(ctx))?;
// If any of the args are infinity or NaN, return an invalid date. // If any of the args are infinity or NaN, return an invalid date.
if !check_normal_opt!(year, month, day, hour, min, sec, milli) { if !check_normal_opt!(year, month, day, hour, min, sec, milli) {
@ -1188,7 +1188,7 @@ impl Date {
return Ok(Value::number(f64::NAN)); return Ok(Value::number(f64::NAN));
} }
match DateTime::parse_from_rfc3339(&ctx.to_string(&args[0])?) { match DateTime::parse_from_rfc3339(&args[0].to_string(ctx)?) {
Ok(v) => Ok(Value::number(v.naive_utc().timestamp_millis() as f64)), Ok(v) => Ok(Value::number(v.naive_utc().timestamp_millis() as f64)),
_ => Ok(Value::number(f64::NAN)), _ => Ok(Value::number(f64::NAN)),
} }
@ -1207,13 +1207,13 @@ impl Date {
pub(crate) fn utc(_: &Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue { pub(crate) fn utc(_: &Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue {
let year = args let year = args
.get(0) .get(0)
.map_or(Ok(f64::NAN), |value| ctx.to_number(value))?; .map_or(Ok(f64::NAN), |value| value.to_number(ctx))?;
let month = args.get(1).map_or(Ok(1f64), |value| ctx.to_number(value))?; let month = args.get(1).map_or(Ok(1f64), |value| value.to_number(ctx))?;
let day = args.get(2).map_or(Ok(1f64), |value| ctx.to_number(value))?; let day = args.get(2).map_or(Ok(1f64), |value| value.to_number(ctx))?;
let hour = args.get(3).map_or(Ok(0f64), |value| ctx.to_number(value))?; let hour = args.get(3).map_or(Ok(0f64), |value| value.to_number(ctx))?;
let min = args.get(4).map_or(Ok(0f64), |value| ctx.to_number(value))?; let min = args.get(4).map_or(Ok(0f64), |value| value.to_number(ctx))?;
let sec = args.get(5).map_or(Ok(0f64), |value| ctx.to_number(value))?; let sec = args.get(5).map_or(Ok(0f64), |value| value.to_number(ctx))?;
let milli = args.get(6).map_or(Ok(0f64), |value| ctx.to_number(value))?; let milli = args.get(6).map_or(Ok(0f64), |value| value.to_number(ctx))?;
if !check_normal_opt!(year, month, day, hour, min, sec, milli) { if !check_normal_opt!(year, month, day, hour, min, sec, milli) {
return Ok(Value::number(f64::NAN)); return Ok(Value::number(f64::NAN));

22
boa/src/builtins/date/tests.rs

@ -1,10 +1,12 @@
#![allow(clippy::zero_prefixed_literal)]
use crate::{ use crate::{
builtins::{object::ObjectData, Value}, builtins::{object::ObjectData, Value},
forward, forward_val, Interpreter, Realm, forward, forward_val, Interpreter, Realm,
}; };
use chrono::prelude::*; use chrono::prelude::*;
// NB: Javascript Uses 0-based months, where chrono uses 1-based months. Many of the assertions look wrong because of // NOTE: Javascript Uses 0-based months, where chrono uses 1-based months. Many of the assertions look wrong because of
// this. // this.
fn forward_dt_utc(engine: &mut Interpreter, src: &str) -> Option<NaiveDateTime> { fn forward_dt_utc(engine: &mut Interpreter, src: &str) -> Option<NaiveDateTime> {
@ -14,19 +16,15 @@ fn forward_dt_utc(engine: &mut Interpreter, src: &str) -> Option<NaiveDateTime>
panic!("expected success") panic!("expected success")
}; };
let date_time = if let Value::Object(date_time) = &date_time { if let Value::Object(ref date_time) = date_time {
date_time if let ObjectData::Date(ref date_time) = date_time.borrow().data {
date_time.0
} else {
panic!("expected date")
}
} else { } else {
panic!("expected object") panic!("expected object")
}; }
let date_time = if let ObjectData::Date(date_time) = &date_time.borrow().data {
date_time.0
} else {
panic!("expected date")
};
date_time.clone()
} }
fn forward_dt_local(engine: &mut Interpreter, src: &str) -> Option<NaiveDateTime> { fn forward_dt_local(engine: &mut Interpreter, src: &str) -> Option<NaiveDateTime> {

8
boa/src/builtins/error/mod.rs

@ -48,7 +48,7 @@ impl Error {
/// Create a new error object. /// Create a new error object.
pub(crate) fn make_error(this: &Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue { pub(crate) fn make_error(this: &Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue {
if let Some(message) = args.get(0) { if let Some(message) = args.get(0) {
this.set_field("message", ctx.to_string(message)?); this.set_field("message", message.to_string(ctx)?);
} }
// This value is used by console.log and other routines to match Object type // This value is used by console.log and other routines to match Object type
@ -71,7 +71,11 @@ impl Error {
pub(crate) fn to_string(this: &Value, _: &[Value], _: &mut Interpreter) -> ResultValue { pub(crate) fn to_string(this: &Value, _: &[Value], _: &mut Interpreter) -> ResultValue {
let name = this.get_field("name"); let name = this.get_field("name");
let message = this.get_field("message"); let message = this.get_field("message");
Ok(Value::from(format!("{}: {}", name, message))) Ok(Value::from(format!(
"{}: {}",
name.display(),
message.display()
)))
} }
/// Initialise the global object with the `Error` object. /// Initialise the global object with the `Error` object.

6
boa/src/builtins/error/range.rs

@ -34,7 +34,7 @@ impl RangeError {
/// Create a new error object. /// Create a new error object.
pub(crate) fn make_error(this: &Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue { pub(crate) fn make_error(this: &Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue {
if let Some(message) = args.get(0) { if let Some(message) = args.get(0) {
this.set_field("message", ctx.to_string(message)?); this.set_field("message", message.to_string(ctx)?);
} }
// This value is used by console.log and other routines to match Object type // This value is used by console.log and other routines to match Object type
@ -55,8 +55,8 @@ impl RangeError {
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error/toString /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error/toString
#[allow(clippy::wrong_self_convention)] #[allow(clippy::wrong_self_convention)]
pub(crate) fn to_string(this: &Value, _: &[Value], ctx: &mut Interpreter) -> ResultValue { pub(crate) fn to_string(this: &Value, _: &[Value], ctx: &mut Interpreter) -> ResultValue {
let name = ctx.to_string(&this.get_field("name"))?; let name = this.get_field("name").to_string(ctx)?;
let message = ctx.to_string(&this.get_field("message"))?; let message = this.get_field("message").to_string(ctx)?;
Ok(Value::from(format!("{}: {}", name, message))) Ok(Value::from(format!("{}: {}", name, message)))
} }

6
boa/src/builtins/error/reference.rs

@ -33,7 +33,7 @@ impl ReferenceError {
/// Create a new error object. /// Create a new error object.
pub(crate) fn make_error(this: &Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue { pub(crate) fn make_error(this: &Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue {
if let Some(message) = args.get(0) { if let Some(message) = args.get(0) {
this.set_field("message", ctx.to_string(message)?); this.set_field("message", message.to_string(ctx)?);
} }
// This value is used by console.log and other routines to match Object type // This value is used by console.log and other routines to match Object type
@ -54,8 +54,8 @@ impl ReferenceError {
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error/toString /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error/toString
#[allow(clippy::wrong_self_convention)] #[allow(clippy::wrong_self_convention)]
pub(crate) fn to_string(this: &Value, _: &[Value], ctx: &mut Interpreter) -> ResultValue { pub(crate) fn to_string(this: &Value, _: &[Value], ctx: &mut Interpreter) -> ResultValue {
let name = ctx.to_string(&this.get_field("name"))?; let name = this.get_field("name").to_string(ctx)?;
let message = ctx.to_string(&this.get_field("message"))?; let message = this.get_field("message").to_string(ctx)?;
Ok(Value::from(format!("{}: {}", name, message))) Ok(Value::from(format!("{}: {}", name, message)))
} }

5
boa/src/builtins/error/syntax.rs

@ -36,7 +36,7 @@ impl SyntaxError {
/// Create a new error object. /// Create a new error object.
pub(crate) fn make_error(this: &Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue { pub(crate) fn make_error(this: &Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue {
if let Some(message) = args.get(0) { if let Some(message) = args.get(0) {
this.set_field("message", ctx.to_string(message)?); this.set_field("message", message.to_string(ctx)?);
} }
// This value is used by console.log and other routines to match Object type // This value is used by console.log and other routines to match Object type
@ -59,7 +59,8 @@ impl SyntaxError {
pub(crate) fn to_string(this: &Value, _: &[Value], _: &mut Interpreter) -> ResultValue { pub(crate) fn to_string(this: &Value, _: &[Value], _: &mut Interpreter) -> ResultValue {
let name = this.get_field("name"); let name = this.get_field("name");
let message = this.get_field("message"); let message = this.get_field("message");
Ok(format!("{}: {}", name, message).into()) // FIXME: This should not use `.display()`
Ok(format!("{}: {}", name.display(), message.display()).into())
} }
/// Initialise the global object with the `SyntaxError` object. /// Initialise the global object with the `SyntaxError` object.

6
boa/src/builtins/error/type.rs

@ -40,7 +40,7 @@ impl TypeError {
/// Create a new error object. /// Create a new error object.
pub(crate) fn make_error(this: &Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue { pub(crate) fn make_error(this: &Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue {
if let Some(message) = args.get(0) { if let Some(message) = args.get(0) {
this.set_field("message", ctx.to_string(message)?); this.set_field("message", message.to_string(ctx)?);
} }
// This value is used by console.log and other routines to match Object type // This value is used by console.log and other routines to match Object type
@ -61,8 +61,8 @@ impl TypeError {
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error/toString /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error/toString
#[allow(clippy::wrong_self_convention)] #[allow(clippy::wrong_self_convention)]
pub(crate) fn to_string(this: &Value, _: &[Value], ctx: &mut Interpreter) -> ResultValue { pub(crate) fn to_string(this: &Value, _: &[Value], ctx: &mut Interpreter) -> ResultValue {
let name = ctx.to_string(&this.get_field("name"))?; let name = this.get_field("name").to_string(ctx)?;
let message = ctx.to_string(&this.get_field("message"))?; let message = this.get_field("message").to_string(ctx)?;
Ok(Value::from(format!("{}: {}", name, message))) Ok(Value::from(format!("{}: {}", name, message)))
} }

2
boa/src/builtins/function/mod.rs

@ -337,7 +337,7 @@ impl Function {
} }
} }
} else { } else {
let name = this.get_field("name").to_string(); let name = this.get_field("name").display().to_string();
panic!("TypeError: {} is not a constructor", name); panic!("TypeError: {} is not a constructor", name);
} }
} }

9
boa/src/builtins/json/mod.rs

@ -46,7 +46,10 @@ impl Json {
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/parse /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/parse
pub(crate) fn parse(_: &Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue { pub(crate) fn parse(_: &Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue {
match serde_json::from_str::<JSONValue>( match serde_json::from_str::<JSONValue>(
&ctx.to_string(args.get(0).expect("cannot get argument for JSON.parse"))?, &args
.get(0)
.expect("cannot get argument for JSON.parse")
.to_string(ctx)?,
) { ) {
Ok(json) => { Ok(json) => {
let j = Value::from_json(json, ctx); let j = Value::from_json(json, ctx);
@ -157,11 +160,11 @@ impl Json {
}); });
for field in fields { for field in fields {
if let Some(value) = object if let Some(value) = object
.get_property(&ctx.to_string(&field)?) .get_property(&field.to_string(ctx)?)
.and_then(|prop| prop.value.as_ref().map(|v| v.to_json(ctx))) .and_then(|prop| prop.value.as_ref().map(|v| v.to_json(ctx)))
.transpose()? .transpose()?
{ {
obj_to_return.insert(ctx.to_string(&field)?.to_string(), value); obj_to_return.insert(field.to_string(ctx)?.to_string(), value);
} }
} }
Ok(Value::from(JSONValue::Object(obj_to_return).to_string())) Ok(Value::from(JSONValue::Object(obj_to_return).to_string()))

20
boa/src/builtins/json/tests.rs

@ -234,10 +234,22 @@ fn json_parse_array_with_reviver() {
}})"#, }})"#,
) )
.unwrap(); .unwrap();
assert_eq!(result.get_field("0").to_number() as u8, 2u8); assert_eq!(
assert_eq!(result.get_field("1").to_number() as u8, 4u8); result.get_field("0").to_number(&mut engine).unwrap() as u8,
assert_eq!(result.get_field("2").to_number() as u8, 6u8); 2u8
assert_eq!(result.get_field("3").to_number() as u8, 8u8); );
assert_eq!(
result.get_field("1").to_number(&mut engine).unwrap() as u8,
4u8
);
assert_eq!(
result.get_field("2").to_number(&mut engine).unwrap() as u8,
6u8
);
assert_eq!(
result.get_field("3").to_number(&mut engine).unwrap() as u8,
8u8
);
} }
#[test] #[test]

4
boa/src/builtins/map/mod.rs

@ -215,7 +215,7 @@ impl Map {
fn get_key_value(value: &Value) -> Option<(Value, Value)> { fn get_key_value(value: &Value) -> Option<(Value, Value)> {
if let Value::Object(object) = value { if let Value::Object(object) = value {
if object.borrow().is_array() { if object.borrow().is_array() {
let (key, value) = match i32::from(&value.get_field("length")) { let (key, value) = match value.get_field("length").as_number().unwrap() as i32 {
0 => (Value::Undefined, Value::Undefined), 0 => (Value::Undefined, Value::Undefined),
1 => (value.get_field("0"), Value::Undefined), 1 => (value.get_field("0"), Value::Undefined),
_ => (value.get_field("0"), value.get_field("1")), _ => (value.get_field("0"), value.get_field("1")),
@ -250,7 +250,7 @@ impl Map {
map map
} else if object.is_array() { } else if object.is_array() {
let mut map = OrderedMap::new(); let mut map = OrderedMap::new();
let len = i32::from(&args[0].get_field("length")); let len = args[0].get_field("length").to_integer(ctx)? as i32;
for i in 0..len { for i in 0..len {
let val = &args[0].get_field(i.to_string()); let val = &args[0].get_field(i.to_string());
let (key, value) = Self::get_key_value(val).ok_or_else(|| { let (key, value) = Self::get_key_value(val).ok_or_else(|| {

74
boa/src/builtins/math/mod.rs

@ -43,7 +43,7 @@ impl Math {
pub(crate) fn abs(_: &Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue { pub(crate) fn abs(_: &Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue {
Ok(args Ok(args
.get(0) .get(0)
.map(|x| ctx.to_number(x)) .map(|x| x.to_number(ctx))
.transpose()? .transpose()?
.map_or(f64::NAN, f64::abs) .map_or(f64::NAN, f64::abs)
.into()) .into())
@ -60,7 +60,7 @@ impl Math {
pub(crate) fn acos(_: &Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue { pub(crate) fn acos(_: &Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue {
Ok(args Ok(args
.get(0) .get(0)
.map(|x| ctx.to_number(x)) .map(|x| x.to_number(ctx))
.transpose()? .transpose()?
.map_or(f64::NAN, f64::acos) .map_or(f64::NAN, f64::acos)
.into()) .into())
@ -77,7 +77,7 @@ impl Math {
pub(crate) fn acosh(_: &Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue { pub(crate) fn acosh(_: &Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue {
Ok(args Ok(args
.get(0) .get(0)
.map(|x| ctx.to_number(x)) .map(|x| x.to_number(ctx))
.transpose()? .transpose()?
.map_or(f64::NAN, f64::acosh) .map_or(f64::NAN, f64::acosh)
.into()) .into())
@ -94,7 +94,7 @@ impl Math {
pub(crate) fn asin(_: &Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue { pub(crate) fn asin(_: &Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue {
Ok(args Ok(args
.get(0) .get(0)
.map(|x| ctx.to_number(x)) .map(|x| x.to_number(ctx))
.transpose()? .transpose()?
.map_or(f64::NAN, f64::asin) .map_or(f64::NAN, f64::asin)
.into()) .into())
@ -111,7 +111,7 @@ impl Math {
pub(crate) fn asinh(_: &Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue { pub(crate) fn asinh(_: &Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue {
Ok(args Ok(args
.get(0) .get(0)
.map(|x| ctx.to_number(x)) .map(|x| x.to_number(ctx))
.transpose()? .transpose()?
.map_or(f64::NAN, f64::asinh) .map_or(f64::NAN, f64::asinh)
.into()) .into())
@ -128,7 +128,7 @@ impl Math {
pub(crate) fn atan(_: &Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue { pub(crate) fn atan(_: &Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue {
Ok(args Ok(args
.get(0) .get(0)
.map(|x| ctx.to_number(x)) .map(|x| x.to_number(ctx))
.transpose()? .transpose()?
.map_or(f64::NAN, f64::atan) .map_or(f64::NAN, f64::atan)
.into()) .into())
@ -145,7 +145,7 @@ impl Math {
pub(crate) fn atanh(_: &Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue { pub(crate) fn atanh(_: &Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue {
Ok(args Ok(args
.get(0) .get(0)
.map(|x| ctx.to_number(x)) .map(|x| x.to_number(ctx))
.transpose()? .transpose()?
.map_or(f64::NAN, f64::atanh) .map_or(f64::NAN, f64::atanh)
.into()) .into())
@ -161,8 +161,8 @@ impl Math {
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/atan2 /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/atan2
pub(crate) fn atan2(_: &Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue { pub(crate) fn atan2(_: &Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue {
Ok(match ( Ok(match (
args.get(0).map(|x| ctx.to_number(x)).transpose()?, args.get(0).map(|x| x.to_number(ctx)).transpose()?,
args.get(1).map(|x| ctx.to_number(x)).transpose()?, args.get(1).map(|x| x.to_number(ctx)).transpose()?,
) { ) {
(Some(x), Some(y)) => x.atan2(y), (Some(x), Some(y)) => x.atan2(y),
(_, _) => f64::NAN, (_, _) => f64::NAN,
@ -181,7 +181,7 @@ impl Math {
pub(crate) fn cbrt(_: &Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue { pub(crate) fn cbrt(_: &Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue {
Ok(args Ok(args
.get(0) .get(0)
.map(|x| ctx.to_number(x)) .map(|x| x.to_number(ctx))
.transpose()? .transpose()?
.map_or(f64::NAN, f64::cbrt) .map_or(f64::NAN, f64::cbrt)
.into()) .into())
@ -198,7 +198,7 @@ impl Math {
pub(crate) fn ceil(_: &Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue { pub(crate) fn ceil(_: &Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue {
Ok(args Ok(args
.get(0) .get(0)
.map(|x| ctx.to_number(x)) .map(|x| x.to_number(ctx))
.transpose()? .transpose()?
.map_or(f64::NAN, f64::ceil) .map_or(f64::NAN, f64::ceil)
.into()) .into())
@ -215,7 +215,7 @@ impl Math {
pub(crate) fn clz32(_: &Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue { pub(crate) fn clz32(_: &Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue {
Ok(args Ok(args
.get(0) .get(0)
.map(|x| ctx.to_uint32(x)) .map(|x| x.to_uint32(ctx))
.transpose()? .transpose()?
.map(u32::leading_zeros) .map(u32::leading_zeros)
.unwrap_or(32) .unwrap_or(32)
@ -233,7 +233,7 @@ impl Math {
pub(crate) fn cos(_: &Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue { pub(crate) fn cos(_: &Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue {
Ok(args Ok(args
.get(0) .get(0)
.map(|x| ctx.to_number(x)) .map(|x| x.to_number(ctx))
.transpose()? .transpose()?
.map_or(f64::NAN, f64::cos) .map_or(f64::NAN, f64::cos)
.into()) .into())
@ -250,7 +250,7 @@ impl Math {
pub(crate) fn cosh(_: &Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue { pub(crate) fn cosh(_: &Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue {
Ok(args Ok(args
.get(0) .get(0)
.map(|x| ctx.to_number(x)) .map(|x| x.to_number(ctx))
.transpose()? .transpose()?
.map_or(f64::NAN, f64::cosh) .map_or(f64::NAN, f64::cosh)
.into()) .into())
@ -267,7 +267,7 @@ impl Math {
pub(crate) fn exp(_: &Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue { pub(crate) fn exp(_: &Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue {
Ok(args Ok(args
.get(0) .get(0)
.map(|x| ctx.to_number(x)) .map(|x| x.to_number(ctx))
.transpose()? .transpose()?
.map_or(f64::NAN, f64::exp) .map_or(f64::NAN, f64::exp)
.into()) .into())
@ -286,7 +286,7 @@ impl Math {
pub(crate) fn expm1(_: &Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue { pub(crate) fn expm1(_: &Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue {
Ok(args Ok(args
.get(0) .get(0)
.map(|x| ctx.to_number(x)) .map(|x| x.to_number(ctx))
.transpose()? .transpose()?
.map_or(f64::NAN, f64::exp_m1) .map_or(f64::NAN, f64::exp_m1)
.into()) .into())
@ -303,7 +303,7 @@ impl Math {
pub(crate) fn floor(_: &Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue { pub(crate) fn floor(_: &Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue {
Ok(args Ok(args
.get(0) .get(0)
.map(|x| ctx.to_number(x)) .map(|x| x.to_number(ctx))
.transpose()? .transpose()?
.map_or(f64::NAN, f64::floor) .map_or(f64::NAN, f64::floor)
.into()) .into())
@ -320,7 +320,7 @@ impl Math {
pub(crate) fn fround(_: &Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue { pub(crate) fn fround(_: &Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue {
Ok(args Ok(args
.get(0) .get(0)
.map(|x| ctx.to_number(x)) .map(|x| x.to_number(ctx))
.transpose()? .transpose()?
.map_or(f64::NAN, |x| (x as f32) as f64) .map_or(f64::NAN, |x| (x as f32) as f64)
.into()) .into())
@ -337,7 +337,7 @@ impl Math {
pub(crate) fn hypot(_: &Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue { pub(crate) fn hypot(_: &Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue {
let mut result = 0f64; let mut result = 0f64;
for arg in args { for arg in args {
let x = ctx.to_number(arg)?; let x = arg.to_number(ctx)?;
result = result.hypot(x); result = result.hypot(x);
} }
Ok(result.into()) Ok(result.into())
@ -353,8 +353,8 @@ impl Math {
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/imul /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/imul
pub(crate) fn imul(_: &Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue { pub(crate) fn imul(_: &Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue {
Ok(match ( Ok(match (
args.get(0).map(|x| ctx.to_uint32(x)).transpose()?, args.get(0).map(|x| x.to_uint32(ctx)).transpose()?,
args.get(1).map(|x| ctx.to_uint32(x)).transpose()?, args.get(1).map(|x| x.to_uint32(ctx)).transpose()?,
) { ) {
(Some(x), Some(y)) => x.wrapping_mul(y) as i32, (Some(x), Some(y)) => x.wrapping_mul(y) as i32,
(_, _) => 0, (_, _) => 0,
@ -373,7 +373,7 @@ impl Math {
pub(crate) fn log(_: &Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue { pub(crate) fn log(_: &Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue {
Ok(args Ok(args
.get(0) .get(0)
.map(|x| ctx.to_number(x)) .map(|x| x.to_number(ctx))
.transpose()? .transpose()?
.map_or(f64::NAN, |x| if x <= 0.0 { f64::NAN } else { x.ln() }) .map_or(f64::NAN, |x| if x <= 0.0 { f64::NAN } else { x.ln() })
.into()) .into())
@ -390,7 +390,7 @@ impl Math {
pub(crate) fn log1p(_: &Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue { pub(crate) fn log1p(_: &Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue {
Ok(args Ok(args
.get(0) .get(0)
.map(|x| ctx.to_number(x)) .map(|x| x.to_number(ctx))
.transpose()? .transpose()?
.map_or(f64::NAN, f64::ln_1p) .map_or(f64::NAN, f64::ln_1p)
.into()) .into())
@ -407,7 +407,7 @@ impl Math {
pub(crate) fn log10(_: &Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue { pub(crate) fn log10(_: &Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue {
Ok(args Ok(args
.get(0) .get(0)
.map(|x| ctx.to_number(x)) .map(|x| x.to_number(ctx))
.transpose()? .transpose()?
.map_or(f64::NAN, |x| if x <= 0.0 { f64::NAN } else { x.log10() }) .map_or(f64::NAN, |x| if x <= 0.0 { f64::NAN } else { x.log10() })
.into()) .into())
@ -424,7 +424,7 @@ impl Math {
pub(crate) fn log2(_: &Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue { pub(crate) fn log2(_: &Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue {
Ok(args Ok(args
.get(0) .get(0)
.map(|x| ctx.to_number(x)) .map(|x| x.to_number(ctx))
.transpose()? .transpose()?
.map_or(f64::NAN, |x| if x <= 0.0 { f64::NAN } else { x.log2() }) .map_or(f64::NAN, |x| if x <= 0.0 { f64::NAN } else { x.log2() })
.into()) .into())
@ -441,7 +441,7 @@ impl Math {
pub(crate) fn max(_: &Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue { pub(crate) fn max(_: &Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue {
let mut max = f64::NEG_INFINITY; let mut max = f64::NEG_INFINITY;
for arg in args { for arg in args {
let num = ctx.to_number(arg)?; let num = arg.to_number(ctx)?;
max = max.max(num); max = max.max(num);
} }
Ok(max.into()) Ok(max.into())
@ -458,7 +458,7 @@ impl Math {
pub(crate) fn min(_: &Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue { pub(crate) fn min(_: &Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue {
let mut min = f64::INFINITY; let mut min = f64::INFINITY;
for arg in args { for arg in args {
let num = ctx.to_number(arg)?; let num = arg.to_number(ctx)?;
min = min.min(num); min = min.min(num);
} }
Ok(min.into()) Ok(min.into())
@ -474,8 +474,8 @@ impl Math {
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/pow /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/pow
pub(crate) fn pow(_: &Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue { pub(crate) fn pow(_: &Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue {
Ok(match ( Ok(match (
args.get(0).map(|x| ctx.to_number(x)).transpose()?, args.get(0).map(|x| x.to_number(ctx)).transpose()?,
args.get(1).map(|x| ctx.to_number(x)).transpose()?, args.get(1).map(|x| x.to_number(ctx)).transpose()?,
) { ) {
(Some(x), Some(y)) => x.powf(y), (Some(x), Some(y)) => x.powf(y),
(_, _) => f64::NAN, (_, _) => f64::NAN,
@ -506,7 +506,7 @@ impl Math {
pub(crate) fn round(_: &Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue { pub(crate) fn round(_: &Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue {
Ok(args Ok(args
.get(0) .get(0)
.map(|x| ctx.to_number(x)) .map(|x| x.to_number(ctx))
.transpose()? .transpose()?
.map_or(f64::NAN, f64::round) .map_or(f64::NAN, f64::round)
.into()) .into())
@ -523,7 +523,7 @@ impl Math {
pub(crate) fn sign(_: &Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue { pub(crate) fn sign(_: &Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue {
Ok(args Ok(args
.get(0) .get(0)
.map(|x| ctx.to_number(x)) .map(|x| x.to_number(ctx))
.transpose()? .transpose()?
.map_or( .map_or(
f64::NAN, f64::NAN,
@ -549,7 +549,7 @@ impl Math {
pub(crate) fn sin(_: &Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue { pub(crate) fn sin(_: &Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue {
Ok(args Ok(args
.get(0) .get(0)
.map(|x| ctx.to_number(x)) .map(|x| x.to_number(ctx))
.transpose()? .transpose()?
.map_or(f64::NAN, f64::sin) .map_or(f64::NAN, f64::sin)
.into()) .into())
@ -566,7 +566,7 @@ impl Math {
pub(crate) fn sinh(_: &Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue { pub(crate) fn sinh(_: &Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue {
Ok(args Ok(args
.get(0) .get(0)
.map(|x| ctx.to_number(x)) .map(|x| x.to_number(ctx))
.transpose()? .transpose()?
.map_or(f64::NAN, f64::sinh) .map_or(f64::NAN, f64::sinh)
.into()) .into())
@ -583,7 +583,7 @@ impl Math {
pub(crate) fn sqrt(_: &Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue { pub(crate) fn sqrt(_: &Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue {
Ok(args Ok(args
.get(0) .get(0)
.map(|x| ctx.to_number(x)) .map(|x| x.to_number(ctx))
.transpose()? .transpose()?
.map_or(f64::NAN, f64::sqrt) .map_or(f64::NAN, f64::sqrt)
.into()) .into())
@ -600,7 +600,7 @@ impl Math {
pub(crate) fn tan(_: &Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue { pub(crate) fn tan(_: &Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue {
Ok(args Ok(args
.get(0) .get(0)
.map(|x| ctx.to_number(x)) .map(|x| x.to_number(ctx))
.transpose()? .transpose()?
.map_or(f64::NAN, f64::tan) .map_or(f64::NAN, f64::tan)
.into()) .into())
@ -617,7 +617,7 @@ impl Math {
pub(crate) fn tanh(_: &Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue { pub(crate) fn tanh(_: &Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue {
Ok(args Ok(args
.get(0) .get(0)
.map(|x| ctx.to_number(x)) .map(|x| x.to_number(ctx))
.transpose()? .transpose()?
.map_or(f64::NAN, f64::tanh) .map_or(f64::NAN, f64::tanh)
.into()) .into())
@ -634,7 +634,7 @@ impl Math {
pub(crate) fn trunc(_: &Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue { pub(crate) fn trunc(_: &Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue {
Ok(args Ok(args
.get(0) .get(0)
.map(|x| ctx.to_number(x)) .map(|x| x.to_number(ctx))
.transpose()? .transpose()?
.map_or(f64::NAN, f64::trunc) .map_or(f64::NAN, f64::trunc)
.into()) .into())

196
boa/src/builtins/math/tests.rs

@ -17,8 +17,8 @@ fn abs() {
let a = forward_val(&mut engine, "a").unwrap(); let a = forward_val(&mut engine, "a").unwrap();
let b = forward_val(&mut engine, "b").unwrap(); let b = forward_val(&mut engine, "b").unwrap();
assert_eq!(a.to_number(), 2.0); assert_eq!(a.to_number(&mut engine).unwrap(), 2.0);
assert_eq!(b.to_number(), 6.655_559_999_999_999_5); assert_eq!(b.to_number(&mut engine).unwrap(), 6.655_559_999_999_999_5);
} }
#[test] #[test]
@ -39,10 +39,10 @@ fn acos() {
let c = forward_val(&mut engine, "c").unwrap(); let c = forward_val(&mut engine, "c").unwrap();
let d = forward(&mut engine, "d"); let d = forward(&mut engine, "d");
assert_eq!(a.to_number(), 0.643_501_108_793_284_3); assert_eq!(a.to_number(&mut engine).unwrap(), 0.643_501_108_793_284_3);
assert_eq!(b, String::from("NaN")); assert_eq!(b, "NaN");
assert_eq!(c.to_number(), 0_f64); assert_eq!(c.to_number(&mut engine).unwrap(), 0_f64);
assert_eq!(d, String::from("NaN")); assert_eq!(d, "NaN");
} }
#[test] #[test]
@ -61,9 +61,9 @@ fn acosh() {
let b = forward(&mut engine, "b"); let b = forward(&mut engine, "b");
let c = forward(&mut engine, "c"); let c = forward(&mut engine, "c");
assert_eq!(a.to_number(), 1.316_957_896_924_816_6); assert_eq!(a.to_number(&mut engine).unwrap(), 1.316_957_896_924_816_6);
assert_eq!(b, String::from("NaN")); assert_eq!(b, "NaN");
assert_eq!(c, String::from("NaN")); assert_eq!(c, "NaN");
} }
#[test] #[test]
@ -80,7 +80,7 @@ fn asin() {
let a = forward_val(&mut engine, "a").unwrap(); let a = forward_val(&mut engine, "a").unwrap();
let b = forward(&mut engine, "b"); let b = forward(&mut engine, "b");
assert_eq!(a.to_number(), 0.643_501_108_793_284_4); assert_eq!(a.to_number(&mut engine).unwrap(), 0.643_501_108_793_284_4);
assert_eq!(b, String::from("NaN")); assert_eq!(b, String::from("NaN"));
} }
@ -98,8 +98,8 @@ fn asinh() {
let a = forward_val(&mut engine, "a").unwrap(); let a = forward_val(&mut engine, "a").unwrap();
let b = forward_val(&mut engine, "b").unwrap(); let b = forward_val(&mut engine, "b").unwrap();
assert_eq!(a.to_number(), 0.881_373_587_019_542_9); assert_eq!(a.to_number(&mut engine).unwrap(), 0.881_373_587_019_542_9);
assert_eq!(b.to_number(), 0_f64); assert_eq!(b.to_number(&mut engine).unwrap(), 0_f64);
} }
#[test] #[test]
@ -118,9 +118,9 @@ fn atan() {
let b = forward_val(&mut engine, "b").unwrap(); let b = forward_val(&mut engine, "b").unwrap();
let c = forward_val(&mut engine, "c").unwrap(); let c = forward_val(&mut engine, "c").unwrap();
assert_eq!(a.to_number(), f64::consts::FRAC_PI_4); assert_eq!(a.to_number(&mut engine).unwrap(), f64::consts::FRAC_PI_4);
assert_eq!(b.to_number(), 0_f64); assert_eq!(b.to_number(&mut engine).unwrap(), 0_f64);
assert_eq!(c.to_number(), f64::from(-0)); assert_eq!(c.to_number(&mut engine).unwrap(), f64::from(-0));
} }
#[test] #[test]
@ -137,8 +137,8 @@ fn atan2() {
let a = forward_val(&mut engine, "a").unwrap(); let a = forward_val(&mut engine, "a").unwrap();
let b = forward_val(&mut engine, "b").unwrap(); let b = forward_val(&mut engine, "b").unwrap();
assert_eq!(a.to_number(), 1.405_647_649_380_269_9); assert_eq!(a.to_number(&mut engine).unwrap(), 1.405_647_649_380_269_9);
assert_eq!(b.to_number(), 0.165_148_677_414_626_83); assert_eq!(b.to_number(&mut engine).unwrap(), 0.165_148_677_414_626_83);
} }
#[test] #[test]
@ -157,9 +157,9 @@ fn cbrt() {
let b = forward_val(&mut engine, "b").unwrap(); let b = forward_val(&mut engine, "b").unwrap();
let c = forward_val(&mut engine, "c").unwrap(); let c = forward_val(&mut engine, "c").unwrap();
assert_eq!(a.to_number(), 4_f64); assert_eq!(a.to_number(&mut engine).unwrap(), 4_f64);
assert_eq!(b.to_number(), -1_f64); assert_eq!(b.to_number(&mut engine).unwrap(), -1_f64);
assert_eq!(c.to_number(), 1_f64); assert_eq!(c.to_number(&mut engine).unwrap(), 1_f64);
} }
#[test] #[test]
@ -178,9 +178,9 @@ fn ceil() {
let b = forward_val(&mut engine, "b").unwrap(); let b = forward_val(&mut engine, "b").unwrap();
let c = forward_val(&mut engine, "c").unwrap(); let c = forward_val(&mut engine, "c").unwrap();
assert_eq!(a.to_number(), 2_f64); assert_eq!(a.to_number(&mut engine).unwrap(), 2_f64);
assert_eq!(b.to_number(), 4_f64); assert_eq!(b.to_number(&mut engine).unwrap(), 4_f64);
assert_eq!(c.to_number(), -7_f64); assert_eq!(c.to_number(&mut engine).unwrap(), -7_f64);
} }
#[test] #[test]
@ -210,14 +210,14 @@ fn clz32() {
let g = forward_val(&mut engine, "g").unwrap(); let g = forward_val(&mut engine, "g").unwrap();
let h = forward_val(&mut engine, "h").unwrap(); let h = forward_val(&mut engine, "h").unwrap();
assert_eq!(a.to_number(), 32_f64); assert_eq!(a.to_number(&mut engine).unwrap(), 32_f64);
assert_eq!(b.to_number(), 32_f64); assert_eq!(b.to_number(&mut engine).unwrap(), 32_f64);
assert_eq!(c.to_number(), 0_f64); assert_eq!(c.to_number(&mut engine).unwrap(), 0_f64);
assert_eq!(d.to_number(), 31_f64); assert_eq!(d.to_number(&mut engine).unwrap(), 31_f64);
assert_eq!(e.to_number(), 1_f64); assert_eq!(e.to_number(&mut engine).unwrap(), 1_f64);
assert_eq!(f.to_number(), 32_f64); assert_eq!(f.to_number(&mut engine).unwrap(), 32_f64);
assert_eq!(g.to_number(), 31_f64); assert_eq!(g.to_number(&mut engine).unwrap(), 31_f64);
assert_eq!(h.to_number(), 32_f64); assert_eq!(h.to_number(&mut engine).unwrap(), 32_f64);
} }
#[test] #[test]
@ -234,8 +234,8 @@ fn cos() {
let a = forward_val(&mut engine, "a").unwrap(); let a = forward_val(&mut engine, "a").unwrap();
let b = forward_val(&mut engine, "b").unwrap(); let b = forward_val(&mut engine, "b").unwrap();
assert_eq!(a.to_number(), 1_f64); assert_eq!(a.to_number(&mut engine).unwrap(), 1_f64);
assert_eq!(b.to_number(), 0.540_302_305_868_139_8); assert_eq!(b.to_number(&mut engine).unwrap(), 0.540_302_305_868_139_8);
} }
#[test] #[test]
@ -254,9 +254,9 @@ fn cosh() {
let b = forward_val(&mut engine, "b").unwrap(); let b = forward_val(&mut engine, "b").unwrap();
let c = forward_val(&mut engine, "c").unwrap(); let c = forward_val(&mut engine, "c").unwrap();
assert_eq!(a.to_number(), 1_f64); assert_eq!(a.to_number(&mut engine).unwrap(), 1_f64);
assert_eq!(b.to_number(), 1.543_080_634_815_243_7); assert_eq!(b.to_number(&mut engine).unwrap(), 1.543_080_634_815_243_7);
assert_eq!(c.to_number(), 1.543_080_634_815_243_7); assert_eq!(c.to_number(&mut engine).unwrap(), 1.543_080_634_815_243_7);
} }
#[test] #[test]
@ -275,9 +275,9 @@ fn exp() {
let b = forward_val(&mut engine, "b").unwrap(); let b = forward_val(&mut engine, "b").unwrap();
let c = forward_val(&mut engine, "c").unwrap(); let c = forward_val(&mut engine, "c").unwrap();
assert_eq!(a.to_number(), 1_f64); assert_eq!(a.to_number(&mut engine).unwrap(), 1_f64);
assert_eq!(b.to_number(), 0.367_879_441_171_442_33); assert_eq!(b.to_number(&mut engine).unwrap(), 0.367_879_441_171_442_33);
assert_eq!(c.to_number(), 7.389_056_098_930_65); assert_eq!(c.to_number(&mut engine).unwrap(), 7.389_056_098_930_65);
} }
#[test] #[test]
@ -305,10 +305,10 @@ fn expm1() {
assert_eq!(a, String::from("NaN")); assert_eq!(a, String::from("NaN"));
assert_eq!(b, String::from("NaN")); assert_eq!(b, String::from("NaN"));
assert_eq!(c.to_number(), 1.718_281_828_459_045); assert_eq!(c.to_number(&mut engine).unwrap(), 1.718_281_828_459_045);
assert_eq!(d.to_number(), -0.632_120_558_828_557_7); assert_eq!(d.to_number(&mut engine).unwrap(), -0.632_120_558_828_557_7);
assert_eq!(e.to_number(), 0_f64); assert_eq!(e.to_number(&mut engine).unwrap(), 0_f64);
assert_eq!(f.to_number(), 6.389_056_098_930_65); assert_eq!(f.to_number(&mut engine).unwrap(), 6.389_056_098_930_65);
} }
#[test] #[test]
@ -327,9 +327,9 @@ fn floor() {
let b = forward_val(&mut engine, "b").unwrap(); let b = forward_val(&mut engine, "b").unwrap();
let c = forward_val(&mut engine, "c").unwrap(); let c = forward_val(&mut engine, "c").unwrap();
assert_eq!(a.to_number(), 1_f64); assert_eq!(a.to_number(&mut engine).unwrap(), 1_f64);
assert_eq!(b.to_number(), -4_f64); assert_eq!(b.to_number(&mut engine).unwrap(), -4_f64);
assert_eq!(c.to_number(), 3_f64); assert_eq!(c.to_number(&mut engine).unwrap(), 3_f64);
} }
#[test] #[test]
@ -359,10 +359,10 @@ fn fround() {
assert_eq!(a, String::from("NaN")); assert_eq!(a, String::from("NaN"));
assert_eq!(b, String::from("Infinity")); assert_eq!(b, String::from("Infinity"));
assert_eq!(c.to_number(), 5f64); assert_eq!(c.to_number(&mut engine).unwrap(), 5f64);
assert_eq!(d.to_number(), 5.5f64); assert_eq!(d.to_number(&mut engine).unwrap(), 5.5f64);
assert_eq!(e.to_number(), 5.050_000_190_734_863); assert_eq!(e.to_number(&mut engine).unwrap(), 5.050_000_190_734_863);
assert_eq!(f.to_number(), -5.050_000_190_734_863); assert_eq!(f.to_number(&mut engine).unwrap(), -5.050_000_190_734_863);
assert_eq!(g, String::from("NaN")); assert_eq!(g, String::from("NaN"));
} }
@ -391,13 +391,13 @@ fn hypot() {
let f = forward_val(&mut engine, "f").unwrap(); let f = forward_val(&mut engine, "f").unwrap();
let g = forward_val(&mut engine, "g").unwrap(); let g = forward_val(&mut engine, "g").unwrap();
assert_eq!(a.to_number(), 0f64); assert_eq!(a.to_number(&mut engine).unwrap(), 0f64);
assert_eq!(b.to_number(), 5f64); assert_eq!(b.to_number(&mut engine).unwrap(), 5f64);
assert_eq!(c.to_number(), 13f64); assert_eq!(c.to_number(&mut engine).unwrap(), 13f64);
assert_eq!(d.to_number(), 7.071_067_811_865_475_5); assert_eq!(d.to_number(&mut engine).unwrap(), 7.071_067_811_865_475_5);
assert_eq!(e.to_number(), 8.774964387392123); assert_eq!(e.to_number(&mut engine).unwrap(), 8.774964387392123);
assert!(f.to_number().is_infinite()); assert!(f.to_number(&mut engine).unwrap().is_infinite());
assert_eq!(g.to_number(), 12f64); assert_eq!(g.to_number(&mut engine).unwrap(), 12f64);
} }
#[test] #[test]
@ -423,12 +423,12 @@ fn imul() {
let e = forward_val(&mut engine, "e").unwrap(); let e = forward_val(&mut engine, "e").unwrap();
let f = forward_val(&mut engine, "f").unwrap(); let f = forward_val(&mut engine, "f").unwrap();
assert_eq!(a.to_number(), 12f64); assert_eq!(a.to_number(&mut engine).unwrap(), 12f64);
assert_eq!(b.to_number(), -60f64); assert_eq!(b.to_number(&mut engine).unwrap(), -60f64);
assert_eq!(c.to_number(), -5f64); assert_eq!(c.to_number(&mut engine).unwrap(), -5f64);
assert_eq!(d.to_number(), -10f64); assert_eq!(d.to_number(&mut engine).unwrap(), -10f64);
assert_eq!(e.to_number(), 0f64); assert_eq!(e.to_number(&mut engine).unwrap(), 0f64);
assert_eq!(f.to_number(), 0f64); assert_eq!(f.to_number(&mut engine).unwrap(), 0f64);
} }
#[test] #[test]
@ -447,8 +447,8 @@ fn log() {
let b = forward_val(&mut engine, "b").unwrap(); let b = forward_val(&mut engine, "b").unwrap();
let c = forward(&mut engine, "c"); let c = forward(&mut engine, "c");
assert_eq!(a.to_number(), 0_f64); assert_eq!(a.to_number(&mut engine).unwrap(), 0_f64);
assert_eq!(b.to_number(), f64::consts::LN_10); assert_eq!(b.to_number(&mut engine).unwrap(), f64::consts::LN_10);
assert_eq!(c, String::from("NaN")); assert_eq!(c, String::from("NaN"));
} }
@ -477,9 +477,9 @@ fn log1p() {
let f = forward(&mut engine, "f"); let f = forward(&mut engine, "f");
let g = forward(&mut engine, "g"); let g = forward(&mut engine, "g");
assert_eq!(a.to_number(), f64::consts::LN_2); assert_eq!(a.to_number(&mut engine).unwrap(), f64::consts::LN_2);
assert_eq!(b.to_number(), 0f64); assert_eq!(b.to_number(&mut engine).unwrap(), 0f64);
assert_eq!(c.to_number(), -36.736_800_569_677_1); assert_eq!(c.to_number(&mut engine).unwrap(), -36.736_800_569_677_1);
assert_eq!(d, "-Infinity"); assert_eq!(d, "-Infinity");
assert_eq!(e, String::from("NaN")); assert_eq!(e, String::from("NaN"));
assert_eq!(f, String::from("NaN")); assert_eq!(f, String::from("NaN"));
@ -502,8 +502,8 @@ fn log10() {
let b = forward_val(&mut engine, "b").unwrap(); let b = forward_val(&mut engine, "b").unwrap();
let c = forward(&mut engine, "c"); let c = forward(&mut engine, "c");
assert_eq!(a.to_number(), f64::consts::LOG10_2); assert_eq!(a.to_number(&mut engine).unwrap(), f64::consts::LOG10_2);
assert_eq!(b.to_number(), 0_f64); assert_eq!(b.to_number(&mut engine).unwrap(), 0_f64);
assert_eq!(c, String::from("NaN")); assert_eq!(c, String::from("NaN"));
} }
@ -523,8 +523,8 @@ fn log2() {
let b = forward_val(&mut engine, "b").unwrap(); let b = forward_val(&mut engine, "b").unwrap();
let c = forward(&mut engine, "c"); let c = forward(&mut engine, "c");
assert_eq!(a.to_number(), 1.584_962_500_721_156); assert_eq!(a.to_number(&mut engine).unwrap(), 1.584_962_500_721_156);
assert_eq!(b.to_number(), 0_f64); assert_eq!(b.to_number(&mut engine).unwrap(), 0_f64);
assert_eq!(c, String::from("NaN")); assert_eq!(c, String::from("NaN"));
} }
@ -544,9 +544,9 @@ fn max() {
let b = forward_val(&mut engine, "b").unwrap(); let b = forward_val(&mut engine, "b").unwrap();
let c = forward_val(&mut engine, "c").unwrap(); let c = forward_val(&mut engine, "c").unwrap();
assert_eq!(a.to_number(), 20_f64); assert_eq!(a.to_number(&mut engine).unwrap(), 20_f64);
assert_eq!(b.to_number(), -10_f64); assert_eq!(b.to_number(&mut engine).unwrap(), -10_f64);
assert_eq!(c.to_number(), 20_f64); assert_eq!(c.to_number(&mut engine).unwrap(), 20_f64);
} }
#[test] #[test]
@ -565,9 +565,9 @@ fn min() {
let b = forward_val(&mut engine, "b").unwrap(); let b = forward_val(&mut engine, "b").unwrap();
let c = forward_val(&mut engine, "c").unwrap(); let c = forward_val(&mut engine, "c").unwrap();
assert_eq!(a.to_number(), 10_f64); assert_eq!(a.to_number(&mut engine).unwrap(), 10_f64);
assert_eq!(b.to_number(), -20_f64); assert_eq!(b.to_number(&mut engine).unwrap(), -20_f64);
assert_eq!(c.to_number(), -10_f64); assert_eq!(c.to_number(&mut engine).unwrap(), -10_f64);
} }
#[test] #[test]
@ -588,10 +588,10 @@ fn pow() {
let c = forward_val(&mut engine, "c").unwrap(); let c = forward_val(&mut engine, "c").unwrap();
let d = forward_val(&mut engine, "d").unwrap(); let d = forward_val(&mut engine, "d").unwrap();
assert_eq!(a.to_number(), 1_024_f64); assert_eq!(a.to_number(&mut engine).unwrap(), 1_024_f64);
assert_eq!(b.to_number(), 49_f64); assert_eq!(b.to_number(&mut engine).unwrap(), 49_f64);
assert_eq!(c.to_number(), 2.0); assert_eq!(c.to_number(&mut engine).unwrap(), 2.0);
assert_eq!(d.to_number(), 0.020_408_163_265_306_12); assert_eq!(d.to_number(&mut engine).unwrap(), 0.020_408_163_265_306_12);
} }
#[test] #[test]
@ -608,8 +608,8 @@ fn round() {
let a = forward_val(&mut engine, "a").unwrap(); let a = forward_val(&mut engine, "a").unwrap();
let b = forward_val(&mut engine, "b").unwrap(); let b = forward_val(&mut engine, "b").unwrap();
assert_eq!(a.to_number(), 21.0); assert_eq!(a.to_number(&mut engine).unwrap(), 21.0);
assert_eq!(b.to_number(), -20.0); assert_eq!(b.to_number(&mut engine).unwrap(), -20.0);
} }
#[test] #[test]
@ -628,9 +628,9 @@ fn sign() {
let b = forward_val(&mut engine, "b").unwrap(); let b = forward_val(&mut engine, "b").unwrap();
let c = forward_val(&mut engine, "c").unwrap(); let c = forward_val(&mut engine, "c").unwrap();
assert_eq!(a.to_number(), 1_f64); assert_eq!(a.to_number(&mut engine).unwrap(), 1_f64);
assert_eq!(b.to_number(), -1_f64); assert_eq!(b.to_number(&mut engine).unwrap(), -1_f64);
assert_eq!(c.to_number(), 0_f64); assert_eq!(c.to_number(&mut engine).unwrap(), 0_f64);
} }
#[test] #[test]
@ -647,8 +647,8 @@ fn sin() {
let a = forward_val(&mut engine, "a").unwrap(); let a = forward_val(&mut engine, "a").unwrap();
let b = forward_val(&mut engine, "b").unwrap(); let b = forward_val(&mut engine, "b").unwrap();
assert_eq!(a.to_number(), 0_f64); assert_eq!(a.to_number(&mut engine).unwrap(), 0_f64);
assert_eq!(b.to_number(), 0.841_470_984_807_896_5); assert_eq!(b.to_number(&mut engine).unwrap(), 0.841_470_984_807_896_5);
} }
#[test] #[test]
@ -665,8 +665,8 @@ fn sinh() {
let a = forward_val(&mut engine, "a").unwrap(); let a = forward_val(&mut engine, "a").unwrap();
let b = forward_val(&mut engine, "b").unwrap(); let b = forward_val(&mut engine, "b").unwrap();
assert_eq!(a.to_number(), 0_f64); assert_eq!(a.to_number(&mut engine).unwrap(), 0_f64);
assert_eq!(b.to_number(), 1.175_201_193_643_801_4); assert_eq!(b.to_number(&mut engine).unwrap(), 1.175_201_193_643_801_4);
} }
#[test] #[test]
@ -685,9 +685,9 @@ fn sqrt() {
let b = forward_val(&mut engine, "b").unwrap(); let b = forward_val(&mut engine, "b").unwrap();
let c = forward_val(&mut engine, "c").unwrap(); let c = forward_val(&mut engine, "c").unwrap();
assert_eq!(a.to_number(), 0_f64); assert_eq!(a.to_number(&mut engine).unwrap(), 0_f64);
assert_eq!(b.to_number(), f64::consts::SQRT_2); assert_eq!(b.to_number(&mut engine).unwrap(), f64::consts::SQRT_2);
assert_eq!(c.to_number(), 3_f64); assert_eq!(c.to_number(&mut engine).unwrap(), 3_f64);
} }
// TODO: Precision is always off between ci and local. We proably need a better way to compare floats anyways // TODO: Precision is always off between ci and local. We proably need a better way to compare floats anyways
@ -721,8 +721,8 @@ fn tanh() {
let a = forward_val(&mut engine, "a").unwrap(); let a = forward_val(&mut engine, "a").unwrap();
let b = forward_val(&mut engine, "b").unwrap(); let b = forward_val(&mut engine, "b").unwrap();
assert_eq!(a.to_number(), 0.761_594_155_955_764_9); assert_eq!(a.to_number(&mut engine).unwrap(), 0.761_594_155_955_764_9);
assert_eq!(b.to_number(), 0_f64); assert_eq!(b.to_number(&mut engine).unwrap(), 0_f64);
} }
#[test] #[test]
@ -739,6 +739,6 @@ fn trunc() {
let a = forward_val(&mut engine, "a").unwrap(); let a = forward_val(&mut engine, "a").unwrap();
let b = forward_val(&mut engine, "b").unwrap(); let b = forward_val(&mut engine, "b").unwrap();
assert_eq!(a.to_number(), 13_f64); assert_eq!(a.to_number(&mut engine).unwrap(), 13_f64);
assert_eq!(b.to_number(), 0_f64); assert_eq!(b.to_number(&mut engine).unwrap(), 0_f64);
} }

24
boa/src/builtins/number/mod.rs

@ -135,7 +135,7 @@ impl Number {
/// `[[Call]]` - Creates a number primitive /// `[[Call]]` - Creates a number primitive
pub(crate) fn make_number(this: &Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue { pub(crate) fn make_number(this: &Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue {
let data = match args.get(0) { let data = match args.get(0) {
Some(ref value) => ctx.to_numeric_number(value)?, Some(ref value) => value.to_numeric_number(ctx)?,
None => 0.0, None => 0.0,
}; };
this.set_data(ObjectData::Number(data)); this.set_data(ObjectData::Number(data));
@ -178,8 +178,8 @@ impl Number {
pub(crate) fn to_fixed(this: &Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue { pub(crate) fn to_fixed(this: &Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue {
let this_num = Self::this_number_value(this, ctx)?; let this_num = Self::this_number_value(this, ctx)?;
let precision = match args.get(0) { let precision = match args.get(0) {
Some(n) => match n.to_integer() { Some(n) => match n.to_integer(ctx)? as i32 {
x if x > 0 => n.to_integer() as usize, x if x > 0 => n.to_integer(ctx)? as usize,
_ => 0, _ => 0,
}, },
None => 0, None => 0,
@ -227,8 +227,8 @@ impl Number {
let this_num = Self::this_number_value(this, ctx)?; let this_num = Self::this_number_value(this, ctx)?;
let _num_str_len = format!("{}", this_num).len(); let _num_str_len = format!("{}", this_num).len();
let _precision = match args.get(0) { let _precision = match args.get(0) {
Some(n) => match n.to_integer() { Some(n) => match n.to_integer(ctx)? as i32 {
x if x > 0 => n.to_integer() as usize, x if x > 0 => n.to_integer(ctx)? as usize,
_ => 0, _ => 0,
}, },
None => 0, None => 0,
@ -383,7 +383,11 @@ impl Number {
// 2. If radix is undefined, let radixNumber be 10. // 2. If radix is undefined, let radixNumber be 10.
// 3. Else, let radixNumber be ? ToInteger(radix). // 3. Else, let radixNumber be ? ToInteger(radix).
let radix = args.get(0).map_or(10, |arg| arg.to_integer()) as u8; let radix = args
.get(0)
.map(|arg| arg.to_integer(ctx))
.transpose()?
.map_or(10, |radix| radix as u8);
// 4. If radixNumber < 2 or radixNumber > 36, throw a RangeError exception. // 4. If radixNumber < 2 or radixNumber > 36, throw a RangeError exception.
if radix < 2 || radix > 36 { if radix < 2 || radix > 36 {
@ -563,8 +567,8 @@ impl Number {
args: &[Value], args: &[Value],
ctx: &mut Interpreter, ctx: &mut Interpreter,
) -> ResultValue { ) -> ResultValue {
if let Some(val) = args.get(0) { if let Some(value) = args.get(0) {
let number = ctx.to_number(val)?; let number = value.to_number(ctx)?;
Ok(number.is_finite().into()) Ok(number.is_finite().into())
} else { } else {
Ok(false.into()) Ok(false.into())
@ -590,8 +594,8 @@ impl Number {
args: &[Value], args: &[Value],
ctx: &mut Interpreter, ctx: &mut Interpreter,
) -> ResultValue { ) -> ResultValue {
if let Some(val) = args.get(0) { if let Some(value) = args.get(0) {
let number = ctx.to_number(val)?; let number = value.to_number(ctx)?;
Ok(number.is_nan().into()) Ok(number.is_nan().into())
} else { } else {
Ok(true.into()) Ok(true.into())

26
boa/src/builtins/number/tests.rs

@ -39,14 +39,14 @@ fn call_number() {
let invalid_nan = forward_val(&mut engine, "invalid_nan").unwrap(); let invalid_nan = forward_val(&mut engine, "invalid_nan").unwrap();
let from_exp = forward_val(&mut engine, "from_exp").unwrap(); let from_exp = forward_val(&mut engine, "from_exp").unwrap();
assert_eq!(default_zero.to_number(), 0_f64); assert_eq!(default_zero.to_number(&mut engine).unwrap(), 0_f64);
assert_eq!(int_one.to_number(), 1_f64); assert_eq!(int_one.to_number(&mut engine).unwrap(), 1_f64);
assert_eq!(float_two.to_number(), 2.1); assert_eq!(float_two.to_number(&mut engine).unwrap(), 2.1);
assert_eq!(str_three.to_number(), 3.2); assert_eq!(str_three.to_number(&mut engine).unwrap(), 3.2);
assert_eq!(bool_one.to_number(), 1_f64); assert_eq!(bool_one.to_number(&mut engine).unwrap(), 1_f64);
assert!(invalid_nan.to_number().is_nan()); assert!(invalid_nan.to_number(&mut engine).unwrap().is_nan());
assert_eq!(bool_zero.to_number(), 0_f64); assert_eq!(bool_zero.to_number(&mut engine).unwrap(), 0_f64);
assert_eq!(from_exp.to_number(), 234_f64); assert_eq!(from_exp.to_number(&mut engine).unwrap(), 234_f64);
} }
#[test] #[test]
@ -396,11 +396,11 @@ fn value_of() {
let exp_val = forward_val(&mut engine, "exp_val").unwrap(); let exp_val = forward_val(&mut engine, "exp_val").unwrap();
let neg_val = forward_val(&mut engine, "neg_val").unwrap(); let neg_val = forward_val(&mut engine, "neg_val").unwrap();
assert_eq!(default_val.to_number(), 0_f64); assert_eq!(default_val.to_number(&mut engine).unwrap(), 0_f64);
assert_eq!(int_val.to_number(), 123_f64); assert_eq!(int_val.to_number(&mut engine).unwrap(), 123_f64);
assert_eq!(float_val.to_number(), 1.234); assert_eq!(float_val.to_number(&mut engine).unwrap(), 1.234);
assert_eq!(exp_val.to_number(), 12_000_f64); assert_eq!(exp_val.to_number(&mut engine).unwrap(), 12_000_f64);
assert_eq!(neg_val.to_number(), -12_000_f64); assert_eq!(neg_val.to_number(&mut engine).unwrap(), -12_000_f64);
} }
#[test] #[test]

15
boa/src/builtins/object/mod.rs

@ -478,7 +478,7 @@ pub fn create(_: &Value, args: &[Value], interpreter: &mut Interpreter) -> Resul
)), )),
_ => interpreter.throw_type_error(format!( _ => interpreter.throw_type_error(format!(
"Object prototype may only be an Object or null: {}", "Object prototype may only be an Object or null: {}",
prototype prototype.display()
)), )),
} }
} }
@ -510,7 +510,7 @@ pub fn set_prototype_of(_: &Value, args: &[Value], _: &mut Interpreter) -> Resul
/// Define a property in an object /// Define a property in an object
pub fn define_property(_: &Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue { pub fn define_property(_: &Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue {
let obj = args.get(0).expect("Cannot get object"); let obj = args.get(0).expect("Cannot get object");
let prop = ctx.to_string(args.get(1).expect("Cannot get object"))?; let prop = args.get(1).expect("Cannot get object").to_string(ctx)?;
let desc = Property::from(args.get(2).expect("Cannot get object")); let desc = Property::from(args.get(2).expect("Cannot get object"));
obj.set_property(prop, desc); obj.set_property(prop, desc);
Ok(Value::undefined()) Ok(Value::undefined())
@ -527,7 +527,8 @@ pub fn define_property(_: &Value, args: &[Value], ctx: &mut Interpreter) -> Resu
/// [spec]: https://tc39.es/ecma262/#sec-object.prototype.tostring /// [spec]: https://tc39.es/ecma262/#sec-object.prototype.tostring
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/toString /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/toString
pub fn to_string(this: &Value, _: &[Value], _: &mut Interpreter) -> ResultValue { pub fn to_string(this: &Value, _: &[Value], _: &mut Interpreter) -> ResultValue {
Ok(Value::from(this.to_string())) // FIXME: it should not display the object.
Ok(Value::from(this.display().to_string()))
} }
/// `Object.prototype.hasOwnPrototype( property )` /// `Object.prototype.hasOwnPrototype( property )`
@ -545,7 +546,7 @@ pub fn has_own_property(this: &Value, args: &[Value], ctx: &mut Interpreter) ->
let prop = if args.is_empty() { let prop = if args.is_empty() {
None None
} else { } else {
Some(ctx.to_string(args.get(0).expect("Cannot get object"))?) Some(args.get(0).expect("Cannot get object").to_string(ctx)?)
}; };
let own_property = this let own_property = this
.as_object() .as_object()
@ -565,11 +566,11 @@ pub fn property_is_enumerable(this: &Value, args: &[Value], ctx: &mut Interprete
Some(key) => key, Some(key) => key,
}; };
let property_key = ctx.to_property_key(key)?; let key = key.to_property_key(ctx)?;
let own_property = ctx.to_object(this).map(|obj| { let own_property = this.to_object(ctx).map(|obj| {
obj.as_object() obj.as_object()
.expect("Unable to deref object") .expect("Unable to deref object")
.get_own_property(&property_key) .get_own_property(&key)
}); });
Ok(own_property.map_or(Value::from(false), |own_prop| { Ok(own_property.map_or(Value::from(false), |own_prop| {

14
boa/src/builtins/regexp/mod.rs

@ -289,8 +289,11 @@ impl RegExp {
/// [spec]: https://tc39.es/ecma262/#sec-regexp.prototype.test /// [spec]: https://tc39.es/ecma262/#sec-regexp.prototype.test
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp/test /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp/test
pub(crate) fn test(this: &Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue { pub(crate) fn test(this: &Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue {
let arg_str = ctx.to_string(args.get(0).expect("could not get argument"))?; let arg_str = args
let mut last_index = usize::from(&this.get_field("lastIndex")); .get(0)
.expect("could not get argument")
.to_string(ctx)?;
let mut last_index = this.get_field("lastIndex").to_index(ctx)?;
let result = if let Some(object) = this.as_object() { let result = if let Some(object) = this.as_object() {
let regex = object.as_regexp().unwrap(); let regex = object.as_regexp().unwrap();
let result = if let Some(m) = regex.matcher.find_at(arg_str.as_str(), last_index) { let result = if let Some(m) = regex.matcher.find_at(arg_str.as_str(), last_index) {
@ -325,8 +328,11 @@ impl RegExp {
/// [spec]: https://tc39.es/ecma262/#sec-regexp.prototype.exec /// [spec]: https://tc39.es/ecma262/#sec-regexp.prototype.exec
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp/exec /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp/exec
pub(crate) fn exec(this: &Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue { pub(crate) fn exec(this: &Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue {
let arg_str = ctx.to_string(args.get(0).expect("could not get argument"))?; let arg_str = args
let mut last_index = usize::from(&this.get_field("lastIndex")); .get(0)
.expect("could not get argument")
.to_string(ctx)?;
let mut last_index = this.get_field("lastIndex").to_index(ctx)?;
let result = if let Some(object) = this.as_object() { let result = if let Some(object) = this.as_object() {
let regex = object.as_regexp().unwrap(); let regex = object.as_regexp().unwrap();
let mut locations = regex.matcher.capture_locations(); let mut locations = regex.matcher.capture_locations();

177
boa/src/builtins/string/mod.rs

@ -71,7 +71,7 @@ impl String {
// This value is used by console.log and other routines to match Obexpecty"failed to parse argument for String method"pe // 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) // to its Javascript Identifier (global constructor method name)
let string = match args.get(0) { let string = match args.get(0) {
Some(ref value) => ctx.to_string(value)?, Some(ref value) => value.to_string(ctx)?,
None => RcString::default(), None => RcString::default(),
}; };
@ -111,11 +111,11 @@ impl String {
pub(crate) fn char_at(this: &Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue { pub(crate) fn char_at(this: &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. // 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 // Then we convert it into a Rust String by wrapping it in from_value
let primitive_val = ctx.to_string(this)?; let primitive_val = this.to_string(ctx)?;
let pos = i32::from( let pos = args
args.get(0) .get(0)
.expect("failed to get argument for String method"), .expect("failed to get argument for String method")
); .to_integer(ctx)? as i32;
// Calling .len() on a string would give the wrong result, as they are bytes not the number of // Calling .len() on a string would give the wrong result, as they are bytes not the number of
// unicode code points // unicode code points
@ -153,15 +153,15 @@ impl String {
pub(crate) fn char_code_at(this: &Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue { pub(crate) fn char_code_at(this: &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. // 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 // Then we convert it into a Rust String by wrapping it in from_value
let primitive_val = ctx.to_string(this)?; let primitive_val = this.to_string(ctx)?;
// Calling .len() on a string would give the wrong result, as they are bytes not the number of unicode code points // 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. // 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 length = primitive_val.chars().count();
let pos = i32::from( let pos = args
args.get(0) .get(0)
.expect("failed to get argument for String method"), .expect("failed to get argument for String method")
); .to_integer(ctx)? as i32;
if pos >= length as i32 || pos < 0 { if pos >= length as i32 || pos < 0 {
return Ok(Value::from(NAN)); return Ok(Value::from(NAN));
@ -192,10 +192,10 @@ impl String {
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/concat /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/concat
pub(crate) fn concat(this: &Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue { pub(crate) fn concat(this: &Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue {
let object = ctx.require_object_coercible(this)?; let object = ctx.require_object_coercible(this)?;
let mut string = ctx.to_string(object)?.to_string(); let mut string = object.to_string(ctx)?.to_string();
for arg in args { for arg in args {
string.push_str(&ctx.to_string(arg)?); string.push_str(&arg.to_string(ctx)?);
} }
Ok(Value::from(string)) Ok(Value::from(string))
@ -214,10 +214,10 @@ impl String {
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/repeat /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/repeat
pub(crate) fn repeat(this: &Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue { pub(crate) fn repeat(this: &Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue {
let object = ctx.require_object_coercible(this)?; let object = ctx.require_object_coercible(this)?;
let string = ctx.to_string(object)?; let string = object.to_string(ctx)?;
if let Some(arg) = args.get(0) { if let Some(arg) = args.get(0) {
let n = ctx.to_integer(arg)?; let n = arg.to_integer(ctx)?;
if n < 0.0 { if n < 0.0 {
return ctx.throw_range_error("repeat count cannot be a negative number"); return ctx.throw_range_error("repeat count cannot be a negative number");
} }
@ -249,14 +249,17 @@ impl String {
pub(crate) fn slice(this: &Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue { pub(crate) fn slice(this: &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. // 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 // Then we convert it into a Rust String by wrapping it in from_value
let primitive_val = ctx.to_string(this)?; let primitive_val = this.to_string(ctx)?;
let start = i32::from( let start = args
args.get(0) .get(0)
.expect("failed to get argument for String method"), .expect("failed to get argument for String method")
); .to_integer(ctx)? as i32;
let end = i32::from(args.get(1).expect("failed to get argument in slice")); let end = args
.get(1)
.expect("failed to get argument in slice")
.to_integer(ctx)? as i32;
// Calling .len() on a string would give the wrong result, as they are bytes not the number of unicode code points // 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. // Note that this is an O(N) operation (because UTF-8 is complex) while getting the number of bytes is an O(1) operation.
@ -296,13 +299,13 @@ impl String {
pub(crate) fn starts_with(this: &Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue { pub(crate) fn starts_with(this: &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. // 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 // Then we convert it into a Rust String by wrapping it in from_value
let primitive_val = ctx.to_string(this)?; let primitive_val = this.to_string(ctx)?;
// TODO: Should throw TypeError if pattern is regular expression // TODO: Should throw TypeError if pattern is regular expression
let search_string = ctx.to_string( let search_string = args
args.get(0) .get(0)
.expect("failed to get argument for String method"), .expect("failed to get argument for String method")
)?; .to_string(ctx)?;
let length = primitive_val.chars().count() as i32; let length = primitive_val.chars().count() as i32;
let search_length = search_string.chars().count() as i32; let search_length = search_string.chars().count() as i32;
@ -311,7 +314,7 @@ impl String {
let position = if args.len() < 2 { let position = if args.len() < 2 {
0 0
} else { } else {
i32::from(args.get(1).expect("failed to get arg")) args.get(1).expect("failed to get arg").to_integer(ctx)? as i32
}; };
let start = min(max(position, 0), length); let start = min(max(position, 0), length);
@ -339,13 +342,13 @@ impl String {
pub(crate) fn ends_with(this: &Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue { pub(crate) fn ends_with(this: &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. // 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 // Then we convert it into a Rust String by wrapping it in from_value
let primitive_val = ctx.to_string(this)?; let primitive_val = this.to_string(ctx)?;
// TODO: Should throw TypeError if search_string is regular expression // TODO: Should throw TypeError if search_string is regular expression
let search_string = ctx.to_string( let search_string = args
args.get(0) .get(0)
.expect("failed to get argument for String method"), .expect("failed to get argument for String method")
)?; .to_string(ctx)?;
let length = primitive_val.chars().count() as i32; let length = primitive_val.chars().count() as i32;
let search_length = search_string.chars().count() as i32; let search_length = search_string.chars().count() as i32;
@ -355,7 +358,9 @@ impl String {
let end_position = if args.len() < 2 { let end_position = if args.len() < 2 {
length length
} else { } else {
i32::from(args.get(1).expect("Could not get argumetn")) args.get(1)
.expect("Could not get argumetn")
.to_integer(ctx)? as i32
}; };
let end = min(max(end_position, 0), length); let end = min(max(end_position, 0), length);
@ -383,13 +388,13 @@ impl String {
pub(crate) fn includes(this: &Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue { pub(crate) fn includes(this: &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. // 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 // Then we convert it into a Rust String by wrapping it in from_value
let primitive_val = ctx.to_string(this)?; let primitive_val = this.to_string(ctx)?;
// TODO: Should throw TypeError if search_string is regular expression // TODO: Should throw TypeError if search_string is regular expression
let search_string = ctx.to_string( let search_string = args
args.get(0) .get(0)
.expect("failed to get argument for String method"), .expect("failed to get argument for String method")
)?; .to_string(ctx)?;
let length = primitive_val.chars().count() as i32; let length = primitive_val.chars().count() as i32;
@ -397,7 +402,9 @@ impl String {
let position = if args.len() < 2 { let position = if args.len() < 2 {
0 0
} else { } else {
i32::from(args.get(1).expect("Could not get argument")) args.get(1)
.expect("Could not get argument")
.to_integer(ctx)? as i32
}; };
let start = min(max(position, 0), length); let start = min(max(position, 0), length);
@ -442,7 +449,7 @@ impl String {
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/replace /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/replace
pub(crate) fn replace(this: &Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue { pub(crate) fn replace(this: &Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue {
// TODO: Support Symbol replacer // TODO: Support Symbol replacer
let primitive_val = ctx.to_string(this)?; let primitive_val = this.to_string(ctx)?;
if args.is_empty() { if args.is_empty() {
return Ok(Value::from(primitive_val)); return Ok(Value::from(primitive_val));
} }
@ -515,7 +522,7 @@ impl String {
let result = ctx.call(&replace_object, this, &results).unwrap(); let result = ctx.call(&replace_object, this, &results).unwrap();
ctx.to_string(&result)?.to_string() result.to_string(ctx)?.to_string()
} }
_ => "undefined".to_string(), _ => "undefined".to_string(),
} }
@ -545,15 +552,18 @@ impl String {
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/indexOf /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/indexOf
pub(crate) fn index_of(this: &Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue { pub(crate) fn index_of(this: &Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue {
let this = ctx.require_object_coercible(this)?; let this = ctx.require_object_coercible(this)?;
let string = ctx.to_string(this)?; let string = this.to_string(ctx)?;
let search_string = let search_string = args
ctx.to_string(&args.get(0).cloned().unwrap_or_else(Value::undefined))?; .get(0)
.cloned()
.unwrap_or_else(Value::undefined)
.to_string(ctx)?;
let length = string.chars().count(); let length = string.chars().count();
let start = args let start = args
.get(1) .get(1)
.map(|position| ctx.to_integer(position)) .map(|position| position.to_integer(ctx))
.transpose()? .transpose()?
.map_or(0, |position| position.max(0.0).min(length as f64) as usize); .map_or(0, |position| position.max(0.0).min(length as f64) as usize);
@ -589,15 +599,18 @@ impl String {
ctx: &mut Interpreter, ctx: &mut Interpreter,
) -> ResultValue { ) -> ResultValue {
let this = ctx.require_object_coercible(this)?; let this = ctx.require_object_coercible(this)?;
let string = ctx.to_string(this)?; let string = this.to_string(ctx)?;
let search_string = let search_string = args
ctx.to_string(&args.get(0).cloned().unwrap_or_else(Value::undefined))?; .get(0)
.cloned()
.unwrap_or_else(Value::undefined)
.to_string(ctx)?;
let length = string.chars().count(); let length = string.chars().count();
let start = args let start = args
.get(1) .get(1)
.map(|position| ctx.to_integer(position)) .map(|position| position.to_integer(ctx))
.transpose()? .transpose()?
.map_or(0, |position| position.max(0.0).min(length as f64) as usize); .map_or(0, |position| position.max(0.0).min(length as f64) as usize);
@ -627,7 +640,7 @@ impl String {
/// [regex]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions /// [regex]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions
pub(crate) fn r#match(this: &Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue { pub(crate) fn r#match(this: &Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue {
let re = RegExp::make_regexp(&Value::from(Object::default()), &[args[0].clone()], ctx)?; let re = RegExp::make_regexp(&Value::from(Object::default()), &[args[0].clone()], ctx)?;
RegExp::r#match(&re, ctx.to_string(this)?, ctx) RegExp::r#match(&re, this.to_string(ctx)?, ctx)
} }
/// Abstract method `StringPad`. /// Abstract method `StringPad`.
@ -677,16 +690,16 @@ impl String {
/// [spec]: https://tc39.es/ecma262/#sec-string.prototype.padend /// [spec]: https://tc39.es/ecma262/#sec-string.prototype.padend
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/padEnd /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/padEnd
pub(crate) fn pad_end(this: &Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue { pub(crate) fn pad_end(this: &Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue {
let primitive = ctx.to_string(this)?; let primitive = this.to_string(ctx)?;
if args.is_empty() { if args.is_empty() {
return Err(Value::from("padEnd requires maxLength argument")); return Err(Value::from("padEnd requires maxLength argument"));
} }
let max_length = i32::from( let max_length = args
args.get(0) .get(0)
.expect("failed to get argument for String method"), .expect("failed to get argument for String method")
); .to_integer(ctx)? as i32;
let fill_string = args.get(1).map(|arg| ctx.to_string(arg)).transpose()?; let fill_string = args.get(1).map(|arg| arg.to_string(ctx)).transpose()?;
Self::string_pad(primitive, max_length, fill_string, false) Self::string_pad(primitive, max_length, fill_string, false)
} }
@ -704,16 +717,16 @@ impl String {
/// [spec]: https://tc39.es/ecma262/#sec-string.prototype.padstart /// [spec]: https://tc39.es/ecma262/#sec-string.prototype.padstart
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/padStart /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/padStart
pub(crate) fn pad_start(this: &Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue { pub(crate) fn pad_start(this: &Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue {
let primitive = ctx.to_string(this)?; let primitive = this.to_string(ctx)?;
if args.is_empty() { if args.is_empty() {
return Err(Value::from("padStart requires maxLength argument")); return Err(Value::from("padStart requires maxLength argument"));
} }
let max_length = i32::from( let max_length = args
args.get(0) .get(0)
.expect("failed to get argument for String method"), .expect("failed to get argument for String method")
); .to_integer(ctx)? as i32;
let fill_string = args.get(1).map(|arg| ctx.to_string(arg)).transpose()?; let fill_string = args.get(1).map(|arg| arg.to_string(ctx)).transpose()?;
Self::string_pad(primitive, max_length, fill_string, true) Self::string_pad(primitive, max_length, fill_string, true)
} }
@ -752,7 +765,7 @@ impl String {
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/trim /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/trim
pub(crate) fn trim(this: &Value, _: &[Value], ctx: &mut Interpreter) -> ResultValue { pub(crate) fn trim(this: &Value, _: &[Value], ctx: &mut Interpreter) -> ResultValue {
let this = ctx.require_object_coercible(this)?; let this = ctx.require_object_coercible(this)?;
let string = ctx.to_string(this)?; let string = this.to_string(ctx)?;
Ok(Value::from( Ok(Value::from(
string.trim_matches(Self::is_trimmable_whitespace), string.trim_matches(Self::is_trimmable_whitespace),
)) ))
@ -772,7 +785,7 @@ impl String {
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/trimStart /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/trimStart
pub(crate) fn trim_start(this: &Value, _: &[Value], ctx: &mut Interpreter) -> ResultValue { pub(crate) fn trim_start(this: &Value, _: &[Value], ctx: &mut Interpreter) -> ResultValue {
let this = ctx.require_object_coercible(this)?; let this = ctx.require_object_coercible(this)?;
let string = ctx.to_string(this)?; let string = this.to_string(ctx)?;
Ok(Value::from( Ok(Value::from(
string.trim_start_matches(Self::is_trimmable_whitespace), string.trim_start_matches(Self::is_trimmable_whitespace),
)) ))
@ -792,7 +805,7 @@ impl String {
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/trimEnd /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/trimEnd
pub(crate) fn trim_end(this: &Value, _: &[Value], ctx: &mut Interpreter) -> ResultValue { pub(crate) fn trim_end(this: &Value, _: &[Value], ctx: &mut Interpreter) -> ResultValue {
let this = ctx.require_object_coercible(this)?; let this = ctx.require_object_coercible(this)?;
let string = ctx.to_string(this)?; let string = this.to_string(ctx)?;
Ok(Value::from( Ok(Value::from(
string.trim_end_matches(Self::is_trimmable_whitespace), string.trim_end_matches(Self::is_trimmable_whitespace),
)) ))
@ -812,7 +825,7 @@ impl String {
pub(crate) fn to_lowercase(this: &Value, _: &[Value], ctx: &mut Interpreter) -> ResultValue { pub(crate) fn to_lowercase(this: &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. // 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 // Then we convert it into a Rust String by wrapping it in from_value
let this_str = ctx.to_string(this)?; let this_str = this.to_string(ctx)?;
// The Rust String is mapped to uppercase using the builtin .to_lowercase(). // 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 // There might be corner cases where it does not behave exactly like Javascript expects
Ok(Value::from(this_str.to_lowercase())) Ok(Value::from(this_str.to_lowercase()))
@ -834,7 +847,7 @@ impl String {
pub(crate) fn to_uppercase(this: &Value, _: &[Value], ctx: &mut Interpreter) -> ResultValue { pub(crate) fn to_uppercase(this: &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. // 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 // Then we convert it into a Rust String by wrapping it in from_value
let this_str = ctx.to_string(this)?; let this_str = this.to_string(ctx)?;
// The Rust String is mapped to uppercase using the builtin .to_uppercase(). // 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 // There might be corner cases where it does not behave exactly like Javascript expects
Ok(Value::from(this_str.to_uppercase())) Ok(Value::from(this_str.to_uppercase()))
@ -853,22 +866,23 @@ impl String {
pub(crate) fn substring(this: &Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue { pub(crate) fn substring(this: &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. // 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 // Then we convert it into a Rust String by wrapping it in from_value
let primitive_val = ctx.to_string(this)?; let primitive_val = this.to_string(ctx)?;
// If no args are specified, start is 'undefined', defaults to 0 // If no args are specified, start is 'undefined', defaults to 0
let start = if args.is_empty() { let start = if args.is_empty() {
0 0
} else { } else {
i32::from( args.get(0)
args.get(0) .expect("failed to get argument for String method")
.expect("failed to get argument for String method"), .to_integer(ctx)? as i32
)
}; };
let length = primitive_val.chars().count() as i32; 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 // If less than 2 args specified, end is the length of the this object converted to a String
let end = if args.len() < 2 { let end = if args.len() < 2 {
length length
} else { } else {
i32::from(args.get(1).expect("Could not get argument")) args.get(1)
.expect("Could not get argument")
.to_integer(ctx)? as i32
}; };
// Both start and end args replaced by 0 if they were negative // Both start and end args replaced by 0 if they were negative
// or by the length of the String if they were greater // or by the length of the String if they were greater
@ -901,15 +915,14 @@ impl String {
pub(crate) fn substr(this: &Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue { pub(crate) fn substr(this: &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. // 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 // Then we convert it into a Rust String by wrapping it in from_value
let primitive_val = ctx.to_string(this)?; let primitive_val = this.to_string(ctx)?;
// If no args are specified, start is 'undefined', defaults to 0 // If no args are specified, start is 'undefined', defaults to 0
let mut start = if args.is_empty() { let mut start = if args.is_empty() {
0 0
} else { } else {
i32::from( args.get(0)
args.get(0) .expect("failed to get argument for String method")
.expect("failed to get argument for String method"), .to_integer(ctx)? as i32
)
}; };
let length = primitive_val.chars().count() as i32; let length = primitive_val.chars().count() as i32;
// If less than 2 args specified, end is +infinity, the maximum number value. // If less than 2 args specified, end is +infinity, the maximum number value.
@ -919,7 +932,9 @@ impl String {
let end = if args.len() < 2 { let end = if args.len() < 2 {
i32::max_value() i32::max_value()
} else { } else {
i32::from(args.get(1).expect("Could not get argument")) args.get(1)
.expect("Could not get argument")
.to_integer(ctx)? as i32
}; };
// If start is negative it become the number of code units from the end of the string // If start is negative it become the number of code units from the end of the string
if start < 0 { if start < 0 {
@ -977,7 +992,7 @@ impl String {
if arg.is_null() { if arg.is_null() {
RegExp::make_regexp( RegExp::make_regexp(
&Value::from(Object::default()), &Value::from(Object::default()),
&[Value::from(ctx.to_string(arg)?), Value::from("g")], &[Value::from(arg.to_string(ctx)?), Value::from("g")],
ctx, ctx,
) )
} else if arg.is_undefined() { } else if arg.is_undefined() {
@ -997,7 +1012,7 @@ impl String {
), ),
}?; }?;
RegExp::match_all(&re, ctx.to_string(this)?.to_string()) RegExp::match_all(&re, this.to_string(ctx)?.to_string())
} }
/// Initialise the `String` object on the global object. /// Initialise the `String` object on the global object.

2
boa/src/builtins/symbol/mod.rs

@ -74,7 +74,7 @@ impl Symbol {
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Symbol/Symbol /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Symbol/Symbol
pub(crate) fn call(_: &Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue { pub(crate) fn call(_: &Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue {
let description = match args.get(0) { let description = match args.get(0) {
Some(ref value) if !value.is_undefined() => Some(ctx.to_string(value)?), Some(ref value) if !value.is_undefined() => Some(value.to_string(ctx)?),
_ => None, _ => None,
}; };

2
boa/src/builtins/symbol/tests.rs

@ -21,5 +21,5 @@ fn print_symbol_expect_description() {
"#; "#;
eprintln!("{}", forward(&mut engine, init)); eprintln!("{}", forward(&mut engine, init));
let sym = forward_val(&mut engine, "sym.toString()").unwrap(); let sym = forward_val(&mut engine, "sym.toString()").unwrap();
assert_eq!(sym.to_string(), "\"Symbol(Hello)\""); assert_eq!(sym.display().to_string(), "\"Symbol(Hello)\"");
} }

19
boa/src/builtins/value/conversions.rs

@ -71,7 +71,7 @@ impl TryFrom<&Value> for char {
type Error = TryFromCharError; type Error = TryFromCharError;
fn try_from(value: &Value) -> Result<Self, Self::Error> { fn try_from(value: &Value) -> Result<Self, Self::Error> {
if let Some(c) = value.to_string().chars().next() { if let Some(c) = value.display().to_string().chars().next() {
Ok(c) Ok(c)
} else { } else {
Err(TryFromCharError) Err(TryFromCharError)
@ -85,12 +85,6 @@ impl From<f64> for Value {
} }
} }
impl From<&Value> for f64 {
fn from(value: &Value) -> Self {
value.to_number()
}
}
impl From<u32> for Value { impl From<u32> for Value {
#[inline] #[inline]
fn from(value: u32) -> Value { fn from(value: u32) -> Value {
@ -108,12 +102,6 @@ impl From<i32> for Value {
} }
} }
impl From<&Value> for i32 {
fn from(value: &Value) -> i32 {
value.to_integer()
}
}
impl From<BigInt> for Value { impl From<BigInt> for Value {
fn from(value: BigInt) -> Self { fn from(value: BigInt) -> Self {
Value::bigint(value) Value::bigint(value)
@ -131,11 +119,6 @@ impl From<usize> for Value {
Value::integer(value as i32) Value::integer(value as i32)
} }
} }
impl From<&Value> for usize {
fn from(value: &Value) -> usize {
value.to_integer() as Self
}
}
impl From<bool> for Value { impl From<bool> for Value {
fn from(value: bool) -> Self { fn from(value: bool) -> Self {

74
boa/src/builtins/value/display.rs

@ -1,5 +1,11 @@
use super::*; use super::*;
/// This object is used for displaying a `Value`.
#[derive(Debug, Clone, Copy)]
pub struct ValueDisplay<'value> {
pub(super) value: &'value Value,
}
/// A helper macro for printing objects /// A helper macro for printing objects
/// Can be used to print both properties and internal slots /// Can be used to print both properties and internal slots
/// All of the overloads take: /// All of the overloads take:
@ -34,7 +40,7 @@ macro_rules! print_obj_value {
vec![format!( vec![format!(
"{:>width$}: {}", "{:>width$}: {}",
"__proto__", "__proto__",
object.prototype(), object.prototype().display(),
width = $indent, width = $indent,
)] )]
} }
@ -86,15 +92,16 @@ pub(crate) fn log_string_from(x: &Value, print_internals: bool, print_children:
} }
} }
ObjectData::Array => { ObjectData::Array => {
let len = i32::from( let len = v
&v.borrow() .borrow()
.properties() .properties()
.get("length") .get("length")
.expect("Could not get Array's length property") .expect("Could not get Array's length property")
.value .value
.clone() .clone()
.expect("Could not borrow value"), .expect("Could not borrow value")
); .as_number()
.unwrap() as i32;
if print_children { if print_children {
if len == 0 { if len == 0 {
@ -126,15 +133,16 @@ pub(crate) fn log_string_from(x: &Value, print_internals: bool, print_children:
} }
} }
ObjectData::Map(ref map) => { ObjectData::Map(ref map) => {
let size = i32::from( let size = v
&v.borrow() .borrow()
.properties() .properties()
.get("size") .get("size")
.unwrap() .unwrap()
.value .value
.clone() .clone()
.expect("Could not borrow value"), .expect("Could not borrow value")
); .as_number()
.unwrap() as i32;
if size == 0 { if size == 0 {
return String::from("Map(0)"); return String::from("Map(0)");
} }
@ -158,7 +166,7 @@ pub(crate) fn log_string_from(x: &Value, print_internals: bool, print_children:
} }
} }
Value::Symbol(ref symbol) => symbol.to_string(), Value::Symbol(ref symbol) => symbol.to_string(),
_ => format!("{}", x), _ => format!("{}", x.display()),
} }
} }
@ -179,7 +187,7 @@ pub(crate) fn display_obj(v: &Value, print_internals: bool) -> String {
if object.borrow().is_error() { if object.borrow().is_error() {
let name = v.get_field("name"); let name = v.get_field("name");
let message = v.get_field("message"); let message = v.get_field("message");
return format!("{}: {}", name, message); return format!("{}: {}", name.display(), message.display());
} }
} }
@ -219,28 +227,28 @@ pub(crate) fn display_obj(v: &Value, print_internals: bool) -> String {
format!("{{\n{}\n{}}}", result, closing_indent) format!("{{\n{}\n{}}}", result, closing_indent)
} else { } else {
// Every other type of data is printed with the display method // Every other type of data is printed with the display method
format!("{}", data) format!("{}", data.display())
} }
} }
display_obj_internal(v, &mut encounters, 4, print_internals) display_obj_internal(v, &mut encounters, 4, print_internals)
} }
impl Display for Value { impl Display for ValueDisplay<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self { match self.value {
Self::Null => write!(f, "null"), Value::Null => write!(f, "null"),
Self::Undefined => write!(f, "undefined"), Value::Undefined => write!(f, "undefined"),
Self::Boolean(v) => write!(f, "{}", v), Value::Boolean(v) => write!(f, "{}", v),
Self::Symbol(ref symbol) => match symbol.description() { Value::Symbol(ref symbol) => match symbol.description() {
Some(description) => write!(f, "Symbol({})", description), Some(description) => write!(f, "Symbol({})", description),
None => write!(f, "Symbol()"), None => write!(f, "Symbol()"),
}, },
Self::String(ref v) => write!(f, "\"{}\"", v), Value::String(ref v) => write!(f, "\"{}\"", v),
Self::Rational(v) => format_rational(*v, f), Value::Rational(v) => format_rational(*v, f),
Self::Object(_) => write!(f, "{}", log_string_from(self, true, true)), Value::Object(_) => write!(f, "{}", log_string_from(self.value, true, true)),
Self::Integer(v) => write!(f, "{}", v), Value::Integer(v) => write!(f, "{}", v),
Self::BigInt(ref num) => write!(f, "{}n", num), Value::BigInt(ref num) => write!(f, "{}n", num),
} }
} }
} }

22
boa/src/builtins/value/equality.rs

@ -1,7 +1,5 @@
use super::*; use super::*;
use crate::{builtins::Number, exec::PreferredType, Interpreter}; use crate::{builtins::Number, Interpreter};
use std::borrow::Borrow;
impl Value { impl Value {
/// Strict equality comparison. /// Strict equality comparison.
@ -61,9 +59,9 @@ impl Value {
| (Self::String(_), Self::Rational(_)) | (Self::String(_), Self::Rational(_))
| (Self::Rational(_), Self::Boolean(_)) | (Self::Rational(_), Self::Boolean(_))
| (Self::Integer(_), Self::Boolean(_)) => { | (Self::Integer(_), Self::Boolean(_)) => {
let a: &Value = self.borrow(); let x = self.to_number(interpreter)?;
let b: &Value = other.borrow(); let y = other.to_number(interpreter)?;
Number::equal(f64::from(a), f64::from(b)) Number::equal(x, y)
} }
// 6. If Type(x) is BigInt and Type(y) is String, then // 6. If Type(x) is BigInt and Type(y) is String, then
@ -82,26 +80,22 @@ impl Value {
}, },
// 8. If Type(x) is Boolean, return the result of the comparison ! ToNumber(x) == y. // 8. If Type(x) is Boolean, return the result of the comparison ! ToNumber(x) == y.
(Self::Boolean(_), _) => { (Self::Boolean(x), _) => return other.equals(&Value::from(*x as i32), interpreter),
return other.equals(&Value::from(self.to_integer()), interpreter)
}
// 9. If Type(y) is Boolean, return the result of the comparison x == ! ToNumber(y). // 9. If Type(y) is Boolean, return the result of the comparison x == ! ToNumber(y).
(_, Self::Boolean(_)) => { (_, Self::Boolean(y)) => return self.equals(&Value::from(*y as i32), interpreter),
return self.equals(&Value::from(other.to_integer()), interpreter)
}
// 10. If Type(x) is either String, Number, BigInt, or Symbol and Type(y) is Object, return the result // 10. If Type(x) is either String, Number, BigInt, or Symbol and Type(y) is Object, return the result
// of the comparison x == ? ToPrimitive(y). // of the comparison x == ? ToPrimitive(y).
(Self::Object(_), _) => { (Self::Object(_), _) => {
let primitive = interpreter.to_primitive(self, PreferredType::Default)?; let primitive = self.to_primitive(interpreter, PreferredType::Default)?;
return primitive.equals(other, interpreter); return primitive.equals(other, interpreter);
} }
// 11. If Type(x) is Object and Type(y) is either String, Number, BigInt, or Symbol, return the result // 11. If Type(x) is Object and Type(y) is either String, Number, BigInt, or Symbol, return the result
// of the comparison ? ToPrimitive(x) == y. // of the comparison ? ToPrimitive(x) == y.
(_, Self::Object(_)) => { (_, Self::Object(_)) => {
let primitive = interpreter.to_primitive(other, PreferredType::Default)?; let primitive = other.to_primitive(interpreter, PreferredType::Default)?;
return primitive.equals(self, interpreter); return primitive.equals(self, interpreter);
} }

489
boa/src/builtins/value/mod.rs

@ -5,11 +5,12 @@
#[cfg(test)] #[cfg(test)]
mod tests; mod tests;
use super::number::{f64_to_int32, f64_to_uint32};
use crate::builtins::{ use crate::builtins::{
function::Function, function::Function,
object::{GcObject, InternalState, InternalStateCell, Object, ObjectData, PROTOTYPE}, object::{GcObject, InternalState, InternalStateCell, Object, ObjectData, PROTOTYPE},
property::{Attribute, Property, PropertyKey}, property::{Attribute, Property, PropertyKey},
BigInt, Symbol, BigInt, Number, Symbol,
}; };
use crate::exec::Interpreter; use crate::exec::Interpreter;
use crate::BoaProfiler; use crate::BoaProfiler;
@ -36,6 +37,7 @@ mod r#type;
pub use conversions::*; pub use conversions::*;
pub(crate) use display::display_obj; pub(crate) use display::display_obj;
pub use display::ValueDisplay;
pub use equality::*; pub use equality::*;
pub use hash::*; pub use hash::*;
pub use operations::*; pub use operations::*;
@ -383,6 +385,15 @@ impl Value {
matches!(self, Self::Rational(_) | Self::Integer(_)) matches!(self, Self::Rational(_) | Self::Integer(_))
} }
#[inline]
pub fn as_number(&self) -> Option<f64> {
match *self {
Self::Integer(integer) => Some(integer.into()),
Self::Rational(rational) => Some(rational),
_ => None,
}
}
/// Returns true if the value is a string. /// Returns true if the value is a string.
#[inline] #[inline]
pub fn is_string(&self) -> bool { pub fn is_string(&self) -> bool {
@ -419,51 +430,6 @@ impl Value {
} }
} }
/// Converts the value into a 64-bit floating point number
pub fn to_number(&self) -> f64 {
match *self {
Self::Object(_) | Self::Symbol(_) | Self::Undefined => NAN,
Self::String(ref str) => {
if str.is_empty() {
return 0.0;
}
match FromStr::from_str(str) {
Ok(num) => num,
Err(_) => NAN,
}
}
Self::Boolean(true) => 1.0,
Self::Boolean(false) | Self::Null => 0.0,
Self::Rational(num) => num,
Self::Integer(num) => f64::from(num),
Self::BigInt(_) => {
panic!("TypeError: Cannot mix BigInt and other types, use explicit conversions")
}
}
}
/// Converts the value into a 32-bit integer
pub fn to_integer(&self) -> i32 {
match *self {
Self::Object(_)
| Self::Undefined
| Self::Symbol(_)
| Self::Null
| Self::Boolean(false) => 0,
Self::String(ref str) => match FromStr::from_str(str) {
Ok(num) => num,
Err(_) => 0,
},
Self::Rational(num) => num as i32,
Self::Boolean(true) => 1,
Self::Integer(num) => num,
Self::BigInt(_) => {
panic!("TypeError: Cannot mix BigInt and other types, use explicit conversions")
}
}
}
/// Converts the value to a `bool` type. /// Converts the value to a `bool` type.
/// ///
/// More information: /// More information:
@ -650,7 +616,7 @@ impl Value {
if obj.borrow().is_array() { if obj.borrow().is_array() {
if let Ok(num) = string.parse::<usize>() { if let Ok(num) = string.parse::<usize>() {
if num > 0 { if num > 0 {
let len = i32::from(&self.get_field("length")); let len = self.get_field("length").as_number().unwrap() as i32;
if len < (num + 1) as i32 { if len < (num + 1) as i32 {
self.set_field("length", num + 1); self.set_field("length", num + 1);
} }
@ -704,6 +670,346 @@ impl Value {
new_func_val.set_field("length", Value::from(length)); new_func_val.set_field("length", Value::from(length));
new_func_val new_func_val
} }
/// The abstract operation ToPrimitive takes an input argument and an optional argument PreferredType.
///
/// <https://tc39.es/ecma262/#sec-toprimitive>
pub fn to_primitive(
&self,
ctx: &mut Interpreter,
preferred_type: PreferredType,
) -> ResultValue {
// 1. Assert: input is an ECMAScript language value. (always a value not need to check)
// 2. If Type(input) is Object, then
if let Value::Object(_) = self {
let mut hint = preferred_type;
// Skip d, e we don't support Symbols yet
// TODO: add when symbols are supported
// TODO: Add other steps.
if hint == PreferredType::Default {
hint = PreferredType::Number;
};
// g. Return ? OrdinaryToPrimitive(input, hint).
ctx.ordinary_to_primitive(self, hint)
} else {
// 3. Return input.
Ok(self.clone())
}
}
/// Converts the value to a `BigInt`.
///
/// This function is equivelent to `BigInt(value)` in JavaScript.
pub fn to_bigint(&self, ctx: &mut Interpreter) -> Result<RcBigInt, Value> {
match self {
Value::Null => Err(ctx.construct_type_error("cannot convert null to a BigInt")),
Value::Undefined => {
Err(ctx.construct_type_error("cannot convert undefined to a BigInt"))
}
Value::String(ref string) => Ok(RcBigInt::from(BigInt::from_string(string, ctx)?)),
Value::Boolean(true) => Ok(RcBigInt::from(BigInt::from(1))),
Value::Boolean(false) => Ok(RcBigInt::from(BigInt::from(0))),
Value::Integer(num) => Ok(RcBigInt::from(BigInt::from(*num))),
Value::Rational(num) => {
if let Ok(bigint) = BigInt::try_from(*num) {
return Ok(RcBigInt::from(bigint));
}
Err(ctx.construct_type_error(format!(
"The number {} cannot be converted to a BigInt because it is not an integer",
num
)))
}
Value::BigInt(b) => Ok(b.clone()),
Value::Object(_) => {
let primitive = self.to_primitive(ctx, PreferredType::Number)?;
primitive.to_bigint(ctx)
}
Value::Symbol(_) => Err(ctx.construct_type_error("cannot convert Symbol to a BigInt")),
}
}
/// Returns an object that implements `Display`.
///
/// # Examples
///
/// ```
/// use boa::builtins::value::Value;
///
/// let value = Value::number(3);
///
/// println!("{}", value.display());
/// ```
#[inline]
pub fn display(&self) -> ValueDisplay<'_> {
ValueDisplay { value: self }
}
/// Converts the value to a string.
///
/// This function is equivalent to `String(value)` in JavaScript.
pub fn to_string(&self, ctx: &mut Interpreter) -> Result<RcString, Value> {
match self {
Value::Null => Ok("null".into()),
Value::Undefined => Ok("undefined".into()),
Value::Boolean(boolean) => Ok(boolean.to_string().into()),
Value::Rational(rational) => Ok(Number::to_native_string(*rational).into()),
Value::Integer(integer) => Ok(integer.to_string().into()),
Value::String(string) => Ok(string.clone()),
Value::Symbol(_) => Err(ctx.construct_type_error("can't convert symbol to string")),
Value::BigInt(ref bigint) => Ok(bigint.to_string().into()),
Value::Object(_) => {
let primitive = self.to_primitive(ctx, PreferredType::String)?;
primitive.to_string(ctx)
}
}
}
/// Converts th value to a value of type Object.
///
/// This function is equivalent to `Object(value)` in JavaScript
///
/// See: <https://tc39.es/ecma262/#sec-toobject>
pub fn to_object(&self, ctx: &mut Interpreter) -> ResultValue {
match self {
Value::Undefined | Value::Null => {
ctx.throw_type_error("cannot convert 'null' or 'undefined' to object")
}
Value::Boolean(boolean) => {
let proto = ctx
.realm
.environment
.get_binding_value("Boolean")
.expect("Boolean was not initialized")
.get_field(PROTOTYPE);
Ok(Value::new_object_from_prototype(
proto,
ObjectData::Boolean(*boolean),
))
}
Value::Integer(integer) => {
let proto = ctx
.realm
.environment
.get_binding_value("Number")
.expect("Number was not initialized")
.get_field(PROTOTYPE);
Ok(Value::new_object_from_prototype(
proto,
ObjectData::Number(f64::from(*integer)),
))
}
Value::Rational(rational) => {
let proto = ctx
.realm
.environment
.get_binding_value("Number")
.expect("Number was not initialized")
.get_field(PROTOTYPE);
Ok(Value::new_object_from_prototype(
proto,
ObjectData::Number(*rational),
))
}
Value::String(ref string) => {
let proto = ctx
.realm
.environment
.get_binding_value("String")
.expect("String was not initialized")
.get_field(PROTOTYPE);
Ok(Value::new_object_from_prototype(
proto,
ObjectData::String(string.clone()),
))
}
Value::Symbol(ref symbol) => {
let proto = ctx
.realm
.environment
.get_binding_value("Symbol")
.expect("Symbol was not initialized")
.get_field(PROTOTYPE);
Ok(Value::new_object_from_prototype(
proto,
ObjectData::Symbol(symbol.clone()),
))
}
Value::BigInt(ref bigint) => {
let proto = ctx
.realm
.environment
.get_binding_value("BigInt")
.expect("BigInt was not initialized")
.get_field(PROTOTYPE);
let bigint_obj =
Value::new_object_from_prototype(proto, ObjectData::BigInt(bigint.clone()));
Ok(bigint_obj)
}
Value::Object(_) => Ok(self.clone()),
}
}
/// Converts the value to a `PropertyKey`, that can be used as a key for properties.
///
/// See <https://tc39.es/ecma262/#sec-topropertykey>
pub fn to_property_key(&self, ctx: &mut Interpreter) -> Result<PropertyKey, Value> {
Ok(match self {
// Fast path:
Value::String(string) => string.clone().into(),
Value::Symbol(symbol) => symbol.clone().into(),
// Slow path:
_ => match self.to_primitive(ctx, PreferredType::String)? {
Value::String(ref string) => string.clone().into(),
Value::Symbol(ref symbol) => symbol.clone().into(),
primitive => primitive.to_string(ctx)?.into(),
},
})
}
/// It returns value converted to a numeric value of type `Number` or `BigInt`.
///
/// See: <https://tc39.es/ecma262/#sec-tonumeric>
pub fn to_numeric(&self, ctx: &mut Interpreter) -> Result<Numeric, Value> {
let primitive = self.to_primitive(ctx, PreferredType::Number)?;
if let Some(bigint) = primitive.as_bigint() {
return Ok(bigint.clone().into());
}
Ok(self.to_number(ctx)?.into())
}
/// Converts a value to an integral 32 bit unsigned integer.
///
/// This function is equivalent to `value | 0` in JavaScript
///
/// See: <https://tc39.es/ecma262/#sec-toint32>
pub fn to_uint32(&self, ctx: &mut Interpreter) -> Result<u32, Value> {
// This is the fast path, if the value is Integer we can just return it.
if let Value::Integer(number) = *self {
return Ok(number as u32);
}
let number = self.to_number(ctx)?;
Ok(f64_to_uint32(number))
}
/// Converts a value to an integral 32 bit signed integer.
///
/// See: <https://tc39.es/ecma262/#sec-toint32>
pub fn to_int32(&self, ctx: &mut Interpreter) -> Result<i32, Value> {
// This is the fast path, if the value is Integer we can just return it.
if let Value::Integer(number) = *self {
return Ok(number);
}
let number = self.to_number(ctx)?;
Ok(f64_to_int32(number))
}
/// Converts a value to a non-negative integer if it is a valid integer index value.
///
/// See: <https://tc39.es/ecma262/#sec-toindex>
pub fn to_index(&self, ctx: &mut Interpreter) -> Result<usize, Value> {
if self.is_undefined() {
return Ok(0);
}
let integer_index = self.to_integer(ctx)?;
if integer_index < 0.0 {
return Err(ctx.construct_range_error("Integer index must be >= 0"));
}
if integer_index > Number::MAX_SAFE_INTEGER {
return Err(ctx.construct_range_error("Integer index must be less than 2**(53) - 1"));
}
Ok(integer_index as usize)
}
/// Converts argument to an integer suitable for use as the length of an array-like object.
///
/// See: <https://tc39.es/ecma262/#sec-tolength>
pub fn to_length(&self, ctx: &mut Interpreter) -> Result<usize, Value> {
// 1. Let len be ? ToInteger(argument).
let len = self.to_integer(ctx)?;
// 2. If len ≤ +0, return +0.
if len < 0.0 {
return Ok(0);
}
// 3. Return min(len, 2^53 - 1).
Ok(len.min(Number::MAX_SAFE_INTEGER) as usize)
}
/// Converts a value to an integral Number value.
///
/// See: <https://tc39.es/ecma262/#sec-tointeger>
pub fn to_integer(&self, ctx: &mut Interpreter) -> Result<f64, Value> {
// 1. Let number be ? ToNumber(argument).
let number = self.to_number(ctx)?;
// 2. If number is +∞ or -∞, return number.
if !number.is_finite() {
// 3. If number is NaN, +0, or -0, return +0.
if number.is_nan() {
return Ok(0.0);
}
return Ok(number);
}
// 4. Let integer be the Number value that is the same sign as number and whose magnitude is floor(abs(number)).
// 5. If integer is -0, return +0.
// 6. Return integer.
Ok(number.trunc() + 0.0) // We add 0.0 to convert -0.0 to +0.0
}
/// Converts a value to a double precision floating point.
///
/// This function is equivalent to the unary `+` operator (`+value`) in JavaScript
///
/// See: https://tc39.es/ecma262/#sec-tonumber
pub fn to_number(&self, ctx: &mut Interpreter) -> Result<f64, Value> {
match *self {
Value::Null => Ok(0.0),
Value::Undefined => Ok(f64::NAN),
Value::Boolean(b) => Ok(if b { 1.0 } else { 0.0 }),
// TODO: this is probably not 100% correct, see https://tc39.es/ecma262/#sec-tonumber-applied-to-the-string-type
Value::String(ref string) => {
if string.trim().is_empty() {
return Ok(0.0);
}
Ok(string.parse().unwrap_or(f64::NAN))
}
Value::Rational(number) => Ok(number),
Value::Integer(integer) => Ok(f64::from(integer)),
Value::Symbol(_) => Err(ctx.construct_type_error("argument must not be a symbol")),
Value::BigInt(_) => Err(ctx.construct_type_error("argument must not be a bigint")),
Value::Object(_) => {
let primitive = self.to_primitive(ctx, PreferredType::Number)?;
primitive.to_number(ctx)
}
}
}
/// This is a more specialized version of `to_numeric`, including `BigInt`.
///
/// This function is equivalent to `Number(value)` in JavaScript
///
/// See: <https://tc39.es/ecma262/#sec-tonumeric>
pub fn to_numeric_number(&self, ctx: &mut Interpreter) -> Result<f64, Value> {
let primitive = self.to_primitive(ctx, PreferredType::Number)?;
if let Some(ref bigint) = primitive.as_bigint() {
return Ok(bigint.to_f64());
}
primitive.to_number(ctx)
}
} }
impl Default for Value { impl Default for Value {
@ -711,3 +1017,92 @@ impl Default for Value {
Self::Undefined Self::Undefined
} }
} }
/// The preffered type to convert an object to a primitive `Value`.
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum PreferredType {
String,
Number,
Default,
}
/// Numeric value which can be of two types `Number`, `BigInt`.
#[derive(Debug, Clone, PartialEq, PartialOrd)]
pub enum Numeric {
/// Double precision floating point number.
Number(f64),
/// BigInt an integer of arbitrary size.
BigInt(RcBigInt),
}
impl From<f64> for Numeric {
#[inline]
fn from(value: f64) -> Self {
Self::Number(value)
}
}
impl From<i32> for Numeric {
#[inline]
fn from(value: i32) -> Self {
Self::Number(value.into())
}
}
impl From<i16> for Numeric {
#[inline]
fn from(value: i16) -> Self {
Self::Number(value.into())
}
}
impl From<i8> for Numeric {
#[inline]
fn from(value: i8) -> Self {
Self::Number(value.into())
}
}
impl From<u32> for Numeric {
#[inline]
fn from(value: u32) -> Self {
Self::Number(value.into())
}
}
impl From<u16> for Numeric {
#[inline]
fn from(value: u16) -> Self {
Self::Number(value.into())
}
}
impl From<u8> for Numeric {
#[inline]
fn from(value: u8) -> Self {
Self::Number(value.into())
}
}
impl From<BigInt> for Numeric {
#[inline]
fn from(value: BigInt) -> Self {
Self::BigInt(value.into())
}
}
impl From<RcBigInt> for Numeric {
#[inline]
fn from(value: RcBigInt) -> Self {
Self::BigInt(value)
}
}
impl From<Numeric> for Value {
fn from(value: Numeric) -> Self {
match value {
Numeric::Number(number) => Self::rational(number),
Numeric::BigInt(bigint) => Self::bigint(bigint),
}
}
}

131
boa/src/builtins/value/operations.rs

@ -1,6 +1,8 @@
use super::*; use super::*;
use crate::builtins::number::{f64_to_int32, f64_to_uint32, Number}; use crate::builtins::{
use crate::exec::PreferredType; number::{f64_to_int32, f64_to_uint32, Number},
value::PreferredType,
};
impl Value { impl Value {
#[inline] #[inline]
@ -12,22 +14,22 @@ impl Value {
(Self::Integer(x), Self::Rational(y)) => Self::rational(f64::from(*x) + y), (Self::Integer(x), Self::Rational(y)) => Self::rational(f64::from(*x) + y),
(Self::Rational(x), Self::Integer(y)) => Self::rational(x + f64::from(*y)), (Self::Rational(x), Self::Integer(y)) => Self::rational(x + f64::from(*y)),
(Self::String(ref x), ref y) => Self::string(format!("{}{}", x, ctx.to_string(y)?)), (Self::String(ref x), ref y) => Self::string(format!("{}{}", x, y.to_string(ctx)?)),
(ref x, Self::String(ref y)) => Self::string(format!("{}{}", ctx.to_string(x)?, y)), (ref x, Self::String(ref y)) => Self::string(format!("{}{}", x.to_string(ctx)?, y)),
(Self::BigInt(ref n1), Self::BigInt(ref n2)) => { (Self::BigInt(ref n1), Self::BigInt(ref n2)) => {
Self::bigint(n1.as_inner().clone() + n2.as_inner().clone()) Self::bigint(n1.as_inner().clone() + n2.as_inner().clone())
} }
// Slow path: // Slow path:
(_, _) => match ( (_, _) => match (
ctx.to_primitive(self, PreferredType::Default)?, self.to_primitive(ctx, PreferredType::Default)?,
ctx.to_primitive(other, PreferredType::Default)?, other.to_primitive(ctx, PreferredType::Default)?,
) { ) {
(Self::String(ref x), ref y) => Self::string(format!("{}{}", x, ctx.to_string(y)?)), (Self::String(ref x), ref y) => Self::string(format!("{}{}", x, y.to_string(ctx)?)),
(ref x, Self::String(ref y)) => Self::string(format!("{}{}", ctx.to_string(x)?, y)), (ref x, Self::String(ref y)) => Self::string(format!("{}{}", x.to_string(ctx)?, y)),
(x, y) => match (ctx.to_numeric(&x)?, ctx.to_numeric(&y)?) { (x, y) => match (x.to_numeric(ctx)?, y.to_numeric(ctx)?) {
(Self::Rational(x), Self::Rational(y)) => Self::rational(x + y), (Numeric::Number(x), Numeric::Number(y)) => Self::rational(x + y),
(Self::BigInt(ref n1), Self::BigInt(ref n2)) => { (Numeric::BigInt(ref n1), Numeric::BigInt(ref n2)) => {
Self::bigint(n1.as_inner().clone() + n2.as_inner().clone()) Self::bigint(n1.as_inner().clone() + n2.as_inner().clone())
} }
(_, _) => { (_, _) => {
@ -54,9 +56,9 @@ impl Value {
} }
// Slow path: // Slow path:
(_, _) => match (ctx.to_numeric(self)?, ctx.to_numeric(other)?) { (_, _) => match (self.to_numeric(ctx)?, other.to_numeric(ctx)?) {
(Self::Rational(a), Self::Rational(b)) => Self::rational(a - b), (Numeric::Number(a), Numeric::Number(b)) => Self::rational(a - b),
(Self::BigInt(ref a), Self::BigInt(ref b)) => { (Numeric::BigInt(ref a), Numeric::BigInt(ref b)) => {
Self::bigint(a.as_inner().clone() - b.as_inner().clone()) Self::bigint(a.as_inner().clone() - b.as_inner().clone())
} }
(_, _) => { (_, _) => {
@ -82,9 +84,9 @@ impl Value {
} }
// Slow path: // Slow path:
(_, _) => match (ctx.to_numeric(self)?, ctx.to_numeric(other)?) { (_, _) => match (self.to_numeric(ctx)?, other.to_numeric(ctx)?) {
(Self::Rational(a), Self::Rational(b)) => Self::rational(a * b), (Numeric::Number(a), Numeric::Number(b)) => Self::rational(a * b),
(Self::BigInt(ref a), Self::BigInt(ref b)) => { (Numeric::BigInt(ref a), Numeric::BigInt(ref b)) => {
Self::bigint(a.as_inner().clone() * b.as_inner().clone()) Self::bigint(a.as_inner().clone() * b.as_inner().clone())
} }
(_, _) => { (_, _) => {
@ -110,9 +112,9 @@ impl Value {
} }
// Slow path: // Slow path:
(_, _) => match (ctx.to_numeric(self)?, ctx.to_numeric(other)?) { (_, _) => match (self.to_numeric(ctx)?, other.to_numeric(ctx)?) {
(Self::Rational(a), Self::Rational(b)) => Self::rational(a / b), (Numeric::Number(a), Numeric::Number(b)) => Self::rational(a / b),
(Self::BigInt(ref a), Self::BigInt(ref b)) => { (Numeric::BigInt(ref a), Numeric::BigInt(ref b)) => {
Self::bigint(a.as_inner().clone() / b.as_inner().clone()) Self::bigint(a.as_inner().clone() / b.as_inner().clone())
} }
(_, _) => { (_, _) => {
@ -138,9 +140,9 @@ impl Value {
} }
// Slow path: // Slow path:
(_, _) => match (ctx.to_numeric(self)?, ctx.to_numeric(other)?) { (_, _) => match (self.to_numeric(ctx)?, other.to_numeric(ctx)?) {
(Self::Rational(a), Self::Rational(b)) => Self::rational(a % b), (Numeric::Number(a), Numeric::Number(b)) => Self::rational(a % b),
(Self::BigInt(ref a), Self::BigInt(ref b)) => { (Numeric::BigInt(ref a), Numeric::BigInt(ref b)) => {
Self::bigint(a.as_inner().clone() % b.as_inner().clone()) Self::bigint(a.as_inner().clone() % b.as_inner().clone())
} }
(_, _) => { (_, _) => {
@ -164,9 +166,9 @@ impl Value {
(Self::BigInt(ref a), Self::BigInt(ref b)) => Self::bigint(a.as_inner().clone().pow(b)), (Self::BigInt(ref a), Self::BigInt(ref b)) => Self::bigint(a.as_inner().clone().pow(b)),
// Slow path: // Slow path:
(_, _) => match (ctx.to_numeric(self)?, ctx.to_numeric(other)?) { (_, _) => match (self.to_numeric(ctx)?, other.to_numeric(ctx)?) {
(Self::Rational(a), Self::Rational(b)) => Self::rational(a.powf(b)), (Numeric::Number(a), Numeric::Number(b)) => Self::rational(a.powf(b)),
(Self::BigInt(ref a), Self::BigInt(ref b)) => { (Numeric::BigInt(ref a), Numeric::BigInt(ref b)) => {
Self::bigint(a.as_inner().clone().pow(b)) Self::bigint(a.as_inner().clone().pow(b))
} }
(_, _) => { (_, _) => {
@ -194,11 +196,11 @@ impl Value {
} }
// Slow path: // Slow path:
(_, _) => match (ctx.to_numeric(self)?, ctx.to_numeric(other)?) { (_, _) => match (self.to_numeric(ctx)?, other.to_numeric(ctx)?) {
(Self::Rational(a), Self::Rational(b)) => { (Numeric::Number(a), Numeric::Number(b)) => {
Self::integer(f64_to_int32(a) & f64_to_int32(b)) Self::integer(f64_to_int32(a) & f64_to_int32(b))
} }
(Self::BigInt(ref a), Self::BigInt(ref b)) => { (Numeric::BigInt(ref a), Numeric::BigInt(ref b)) => {
Self::bigint(a.as_inner().clone() & b.as_inner().clone()) Self::bigint(a.as_inner().clone() & b.as_inner().clone())
} }
(_, _) => { (_, _) => {
@ -226,11 +228,11 @@ impl Value {
} }
// Slow path: // Slow path:
(_, _) => match (ctx.to_numeric(self)?, ctx.to_numeric(other)?) { (_, _) => match (self.to_numeric(ctx)?, other.to_numeric(ctx)?) {
(Self::Rational(a), Self::Rational(b)) => { (Numeric::Number(a), Numeric::Number(b)) => {
Self::integer(f64_to_int32(a) | f64_to_int32(b)) Self::integer(f64_to_int32(a) | f64_to_int32(b))
} }
(Self::BigInt(ref a), Self::BigInt(ref b)) => { (Numeric::BigInt(ref a), Numeric::BigInt(ref b)) => {
Self::bigint(a.as_inner().clone() | b.as_inner().clone()) Self::bigint(a.as_inner().clone() | b.as_inner().clone())
} }
(_, _) => { (_, _) => {
@ -258,11 +260,11 @@ impl Value {
} }
// Slow path: // Slow path:
(_, _) => match (ctx.to_numeric(self)?, ctx.to_numeric(other)?) { (_, _) => match (self.to_numeric(ctx)?, other.to_numeric(ctx)?) {
(Self::Rational(a), Self::Rational(b)) => { (Numeric::Number(a), Numeric::Number(b)) => {
Self::integer(f64_to_int32(a) ^ f64_to_int32(b)) Self::integer(f64_to_int32(a) ^ f64_to_int32(b))
} }
(Self::BigInt(ref a), Self::BigInt(ref b)) => { (Numeric::BigInt(ref a), Numeric::BigInt(ref b)) => {
Self::bigint(a.as_inner().clone() ^ b.as_inner().clone()) Self::bigint(a.as_inner().clone() ^ b.as_inner().clone())
} }
(_, _) => { (_, _) => {
@ -294,11 +296,11 @@ impl Value {
} }
// Slow path: // Slow path:
(_, _) => match (ctx.to_numeric(self)?, ctx.to_numeric(other)?) { (_, _) => match (self.to_numeric(ctx)?, other.to_numeric(ctx)?) {
(Self::Rational(x), Self::Rational(y)) => { (Numeric::Number(x), Numeric::Number(y)) => {
Self::integer(f64_to_int32(x).wrapping_shl(f64_to_uint32(y))) Self::integer(f64_to_int32(x).wrapping_shl(f64_to_uint32(y)))
} }
(Self::BigInt(ref x), Self::BigInt(ref y)) => { (Numeric::BigInt(ref x), Numeric::BigInt(ref y)) => {
Self::bigint(x.as_inner().clone() << y.as_inner().clone()) Self::bigint(x.as_inner().clone() << y.as_inner().clone())
} }
(_, _) => { (_, _) => {
@ -330,11 +332,11 @@ impl Value {
} }
// Slow path: // Slow path:
(_, _) => match (ctx.to_numeric(self)?, ctx.to_numeric(other)?) { (_, _) => match (self.to_numeric(ctx)?, other.to_numeric(ctx)?) {
(Self::Rational(x), Self::Rational(y)) => { (Numeric::Number(x), Numeric::Number(y)) => {
Self::integer(f64_to_int32(x).wrapping_shr(f64_to_uint32(y))) Self::integer(f64_to_int32(x).wrapping_shr(f64_to_uint32(y)))
} }
(Self::BigInt(ref x), Self::BigInt(ref y)) => { (Numeric::BigInt(ref x), Numeric::BigInt(ref y)) => {
Self::bigint(x.as_inner().clone() >> y.as_inner().clone()) Self::bigint(x.as_inner().clone() >> y.as_inner().clone())
} }
(_, _) => { (_, _) => {
@ -364,11 +366,11 @@ impl Value {
} }
// Slow path: // Slow path:
(_, _) => match (ctx.to_numeric(self)?, ctx.to_numeric(other)?) { (_, _) => match (self.to_numeric(ctx)?, other.to_numeric(ctx)?) {
(Self::Rational(x), Self::Rational(y)) => { (Numeric::Number(x), Numeric::Number(y)) => {
Self::rational(f64_to_uint32(x).wrapping_shr(f64_to_uint32(y))) Self::rational(f64_to_uint32(x).wrapping_shr(f64_to_uint32(y)))
} }
(Self::BigInt(_), Self::BigInt(_)) => { (Numeric::BigInt(_), Numeric::BigInt(_)) => {
return ctx return ctx
.throw_type_error("BigInts have no unsigned right shift, use >> instead"); .throw_type_error("BigInts have no unsigned right shift, use >> instead");
} }
@ -385,7 +387,7 @@ impl Value {
pub fn neg(&self, interpreter: &mut Interpreter) -> ResultValue { pub fn neg(&self, interpreter: &mut Interpreter) -> ResultValue {
Ok(match *self { Ok(match *self {
Self::Symbol(_) | Self::Undefined => Self::rational(NAN), Self::Symbol(_) | Self::Undefined => Self::rational(NAN),
Self::Object(_) => Self::rational(match interpreter.to_numeric_number(self) { Self::Object(_) => Self::rational(match self.to_numeric_number(interpreter) {
Ok(num) => -num, Ok(num) => -num,
Err(_) => NAN, Err(_) => NAN,
}), }),
@ -402,8 +404,8 @@ impl Value {
} }
#[inline] #[inline]
pub fn not(&self, _: &mut Interpreter) -> ResultValue { pub fn not(&self, _: &mut Interpreter) -> Result<bool, Value> {
Ok(Self::boolean(!self.to_boolean())) Ok(!self.to_boolean())
} }
/// Abstract relational comparison /// Abstract relational comparison
@ -431,27 +433,27 @@ impl Value {
) -> Result<AbstractRelation, Value> { ) -> Result<AbstractRelation, Value> {
Ok(match (self, other) { Ok(match (self, other) {
// Fast path (for some common operations): // Fast path (for some common operations):
(Value::Integer(x), Value::Integer(y)) => (x < y).into(), (Self::Integer(x), Self::Integer(y)) => (x < y).into(),
(Value::Integer(x), Value::Rational(y)) => Number::less_than(f64::from(*x), *y), (Self::Integer(x), Self::Rational(y)) => Number::less_than(f64::from(*x), *y),
(Value::Rational(x), Value::Integer(y)) => Number::less_than(*x, f64::from(*y)), (Self::Rational(x), Self::Integer(y)) => Number::less_than(*x, f64::from(*y)),
(Value::Rational(x), Value::Rational(y)) => Number::less_than(*x, *y), (Self::Rational(x), Self::Rational(y)) => Number::less_than(*x, *y),
(Value::BigInt(ref x), Value::BigInt(ref y)) => (x < y).into(), (Self::BigInt(ref x), Self::BigInt(ref y)) => (x < y).into(),
// Slow path: // Slow path:
(_, _) => { (_, _) => {
let (px, py) = if left_first { let (px, py) = if left_first {
let px = ctx.to_primitive(self, PreferredType::Number)?; let px = self.to_primitive(ctx, PreferredType::Number)?;
let py = ctx.to_primitive(other, PreferredType::Number)?; let py = other.to_primitive(ctx, PreferredType::Number)?;
(px, py) (px, py)
} else { } else {
// NOTE: The order of evaluation needs to be reversed to preserve left to right evaluation. // NOTE: The order of evaluation needs to be reversed to preserve left to right evaluation.
let py = ctx.to_primitive(other, PreferredType::Number)?; let py = other.to_primitive(ctx, PreferredType::Number)?;
let px = ctx.to_primitive(self, PreferredType::Number)?; let px = self.to_primitive(ctx, PreferredType::Number)?;
(px, py) (px, py)
}; };
match (px, py) { match (px, py) {
(Value::String(ref x), Value::String(ref y)) => { (Self::String(ref x), Self::String(ref y)) => {
if x.starts_with(y.as_str()) { if x.starts_with(y.as_str()) {
return Ok(AbstractRelation::False); return Ok(AbstractRelation::False);
} }
@ -465,24 +467,24 @@ impl Value {
} }
unreachable!() unreachable!()
} }
(Value::BigInt(ref x), Value::String(ref y)) => { (Self::BigInt(ref x), Self::String(ref y)) => {
if let Some(y) = string_to_bigint(&y) { if let Some(y) = string_to_bigint(&y) {
(*x.as_inner() < y).into() (*x.as_inner() < y).into()
} else { } else {
AbstractRelation::Undefined AbstractRelation::Undefined
} }
} }
(Value::String(ref x), Value::BigInt(ref y)) => { (Self::String(ref x), Self::BigInt(ref y)) => {
if let Some(x) = string_to_bigint(&x) { if let Some(x) = string_to_bigint(&x) {
(x < *y.as_inner()).into() (x < *y.as_inner()).into()
} else { } else {
AbstractRelation::Undefined AbstractRelation::Undefined
} }
} }
(px, py) => match (ctx.to_numeric(&px)?, ctx.to_numeric(&py)?) { (px, py) => match (px.to_numeric(ctx)?, py.to_numeric(ctx)?) {
(Value::Rational(x), Value::Rational(y)) => Number::less_than(x, y), (Numeric::Number(x), Numeric::Number(y)) => Number::less_than(x, y),
(Value::BigInt(ref x), Value::BigInt(ref y)) => (x < y).into(), (Numeric::BigInt(ref x), Numeric::BigInt(ref y)) => (x < y).into(),
(Value::BigInt(ref x), Value::Rational(y)) => { (Numeric::BigInt(ref x), Numeric::Number(y)) => {
if y.is_nan() { if y.is_nan() {
return Ok(AbstractRelation::Undefined); return Ok(AbstractRelation::Undefined);
} }
@ -496,7 +498,7 @@ impl Value {
}; };
(*x.as_inner() < BigInt::try_from(n).unwrap()).into() (*x.as_inner() < BigInt::try_from(n).unwrap()).into()
} }
(Value::Rational(x), Value::BigInt(ref y)) => { (Numeric::Number(x), Numeric::BigInt(ref y)) => {
if x.is_nan() { if x.is_nan() {
return Ok(AbstractRelation::Undefined); return Ok(AbstractRelation::Undefined);
} }
@ -510,7 +512,6 @@ impl Value {
}; };
(BigInt::try_from(n).unwrap() < *y.as_inner()).into() (BigInt::try_from(n).unwrap() < *y.as_inner()).into()
} }
(_, _) => unreachable!("to_numeric should only retrun number and bigint"),
}, },
} }
} }

47
boa/src/builtins/value/tests.rs

@ -22,7 +22,7 @@ fn string_to_value() {
fn undefined() { fn undefined() {
let u = Value::Undefined; let u = Value::Undefined;
assert_eq!(u.get_type(), Type::Undefined); assert_eq!(u.get_type(), Type::Undefined);
assert_eq!(u.to_string(), "undefined"); assert_eq!(u.display().to_string(), "undefined");
} }
#[test] #[test]
@ -31,7 +31,7 @@ fn get_set_field() {
// Create string and convert it to a Value // Create string and convert it to a Value
let s = Value::from("bar"); let s = Value::from("bar");
obj.set_field("foo", s); obj.set_field("foo", s);
assert_eq!(obj.get_field("foo").to_string(), "\"bar\""); assert_eq!(obj.get_field("foo").display().to_string(), "\"bar\"");
} }
#[test] #[test]
@ -216,7 +216,7 @@ fn get_types() {
#[test] #[test]
fn to_string() { fn to_string() {
let f64_to_str = |f| Value::Rational(f).to_string(); let f64_to_str = |f| Value::Rational(f).display().to_string();
assert_eq!(f64_to_str(f64::NAN), "NaN"); assert_eq!(f64_to_str(f64::NAN), "NaN");
assert_eq!(f64_to_str(0.0), "0"); assert_eq!(f64_to_str(0.0), "0");
@ -254,7 +254,7 @@ fn add_number_and_number() {
let mut engine = Interpreter::new(realm); let mut engine = Interpreter::new(realm);
let value = forward_val(&mut engine, "1 + 2").unwrap(); let value = forward_val(&mut engine, "1 + 2").unwrap();
let value = engine.to_int32(&value).unwrap(); let value = value.to_int32(&mut engine).unwrap();
assert_eq!(value, 3); assert_eq!(value, 3);
} }
@ -264,7 +264,7 @@ fn add_number_and_string() {
let mut engine = Interpreter::new(realm); let mut engine = Interpreter::new(realm);
let value = forward_val(&mut engine, "1 + \" + 2 = 3\"").unwrap(); let value = forward_val(&mut engine, "1 + \" + 2 = 3\"").unwrap();
let value = engine.to_string(&value).unwrap(); let value = value.to_string(&mut engine).unwrap();
assert_eq!(value, "1 + 2 = 3"); assert_eq!(value, "1 + 2 = 3");
} }
@ -274,7 +274,7 @@ fn add_string_and_string() {
let mut engine = Interpreter::new(realm); let mut engine = Interpreter::new(realm);
let value = forward_val(&mut engine, "\"Hello\" + \", world\"").unwrap(); let value = forward_val(&mut engine, "\"Hello\" + \", world\"").unwrap();
let value = engine.to_string(&value).unwrap(); let value = value.to_string(&mut engine).unwrap();
assert_eq!(value, "Hello, world"); assert_eq!(value, "Hello, world");
} }
@ -284,7 +284,7 @@ fn add_number_object_and_number() {
let mut engine = Interpreter::new(realm); let mut engine = Interpreter::new(realm);
let value = forward_val(&mut engine, "new Number(10) + 6").unwrap(); let value = forward_val(&mut engine, "new Number(10) + 6").unwrap();
let value = engine.to_int32(&value).unwrap(); let value = value.to_int32(&mut engine).unwrap();
assert_eq!(value, 16); assert_eq!(value, 16);
} }
@ -294,7 +294,7 @@ fn add_number_object_and_string_object() {
let mut engine = Interpreter::new(realm); let mut engine = Interpreter::new(realm);
let value = forward_val(&mut engine, "new Number(10) + new String(\"0\")").unwrap(); let value = forward_val(&mut engine, "new Number(10) + new String(\"0\")").unwrap();
let value = engine.to_string(&value).unwrap(); let value = value.to_string(&mut engine).unwrap();
assert_eq!(value, "100"); assert_eq!(value, "100");
} }
@ -304,7 +304,7 @@ fn sub_number_and_number() {
let mut engine = Interpreter::new(realm); let mut engine = Interpreter::new(realm);
let value = forward_val(&mut engine, "1 - 999").unwrap(); let value = forward_val(&mut engine, "1 - 999").unwrap();
let value = engine.to_int32(&value).unwrap(); let value = value.to_int32(&mut engine).unwrap();
assert_eq!(value, -998); assert_eq!(value, -998);
} }
@ -314,7 +314,7 @@ fn sub_number_object_and_number_object() {
let mut engine = Interpreter::new(realm); let mut engine = Interpreter::new(realm);
let value = forward_val(&mut engine, "new Number(1) - new Number(999)").unwrap(); let value = forward_val(&mut engine, "new Number(1) - new Number(999)").unwrap();
let value = engine.to_int32(&value).unwrap(); let value = value.to_int32(&mut engine).unwrap();
assert_eq!(value, -998); assert_eq!(value, -998);
} }
@ -324,7 +324,7 @@ fn sub_string_and_number_object() {
let mut engine = Interpreter::new(realm); let mut engine = Interpreter::new(realm);
let value = forward_val(&mut engine, "'Hello' - new Number(999)").unwrap(); let value = forward_val(&mut engine, "'Hello' - new Number(999)").unwrap();
let value = engine.to_number(&value).unwrap(); let value = value.to_number(&mut engine).unwrap();
assert!(value.is_nan()); assert!(value.is_nan());
} }
@ -334,7 +334,7 @@ fn bitand_integer_and_integer() {
let mut engine = Interpreter::new(realm); let mut engine = Interpreter::new(realm);
let value = forward_val(&mut engine, "0xFFFF & 0xFF").unwrap(); let value = forward_val(&mut engine, "0xFFFF & 0xFF").unwrap();
let value = engine.to_int32(&value).unwrap(); let value = value.to_int32(&mut engine).unwrap();
assert_eq!(value, 255); assert_eq!(value, 255);
} }
@ -344,7 +344,7 @@ fn bitand_integer_and_rational() {
let mut engine = Interpreter::new(realm); let mut engine = Interpreter::new(realm);
let value = forward_val(&mut engine, "0xFFFF & 255.5").unwrap(); let value = forward_val(&mut engine, "0xFFFF & 255.5").unwrap();
let value = engine.to_int32(&value).unwrap(); let value = value.to_int32(&mut engine).unwrap();
assert_eq!(value, 255); assert_eq!(value, 255);
} }
@ -354,17 +354,18 @@ fn bitand_rational_and_rational() {
let mut engine = Interpreter::new(realm); let mut engine = Interpreter::new(realm);
let value = forward_val(&mut engine, "255.772 & 255.5").unwrap(); let value = forward_val(&mut engine, "255.772 & 255.5").unwrap();
let value = engine.to_int32(&value).unwrap(); let value = value.to_int32(&mut engine).unwrap();
assert_eq!(value, 255); assert_eq!(value, 255);
} }
#[test] #[test]
#[allow(clippy::float_cmp)]
fn pow_number_and_number() { fn pow_number_and_number() {
let realm = Realm::create(); let realm = Realm::create();
let mut engine = Interpreter::new(realm); let mut engine = Interpreter::new(realm);
let value = forward_val(&mut engine, "3 ** 3").unwrap(); let value = forward_val(&mut engine, "3 ** 3").unwrap();
let value = engine.to_number(&value).unwrap(); let value = value.to_number(&mut engine).unwrap();
assert_eq!(value, 27.0); assert_eq!(value, 27.0);
} }
@ -374,7 +375,7 @@ fn pow_number_and_string() {
let mut engine = Interpreter::new(realm); let mut engine = Interpreter::new(realm);
let value = forward_val(&mut engine, "3 ** 'Hello'").unwrap(); let value = forward_val(&mut engine, "3 ** 'Hello'").unwrap();
let value = engine.to_number(&value).unwrap(); let value = value.to_number(&mut engine).unwrap();
assert!(value.is_nan()); assert!(value.is_nan());
} }
@ -392,7 +393,7 @@ fn assign_pow_number_and_string() {
", ",
) )
.unwrap(); .unwrap();
let value = engine.to_number(&value).unwrap(); let value = value.to_number(&mut engine).unwrap();
assert!(value.is_nan()); assert!(value.is_nan());
} }
@ -400,7 +401,7 @@ fn assign_pow_number_and_string() {
fn display_string() { fn display_string() {
let s = String::from("Hello"); let s = String::from("Hello");
let v = Value::from(s); let v = Value::from(s);
assert_eq!(v.to_string(), "\"Hello\""); assert_eq!(v.display().to_string(), "\"Hello\"");
} }
#[test] #[test]
@ -409,7 +410,7 @@ fn display_array_string() {
let mut engine = Interpreter::new(realm); let mut engine = Interpreter::new(realm);
let value = forward_val(&mut engine, "[\"Hello\"]").unwrap(); let value = forward_val(&mut engine, "[\"Hello\"]").unwrap();
assert_eq!(value.to_string(), "[ \"Hello\" ]"); assert_eq!(value.display().to_string(), "[ \"Hello\" ]");
} }
#[test] #[test]
@ -421,7 +422,7 @@ fn display_boolean_object() {
bool bool
"#; "#;
let value = forward_val(&mut engine, d_obj).unwrap(); let value = forward_val(&mut engine, d_obj).unwrap();
assert_eq!(value.to_string(), "Boolean { false }") assert_eq!(value.display().to_string(), "Boolean { false }")
} }
#[test] #[test]
@ -433,7 +434,7 @@ fn display_number_object() {
num num
"#; "#;
let value = forward_val(&mut engine, d_obj).unwrap(); let value = forward_val(&mut engine, d_obj).unwrap();
assert_eq!(value.to_string(), "Number { 3.14 }") assert_eq!(value.display().to_string(), "Number { 3.14 }")
} }
#[test] #[test]
@ -445,7 +446,7 @@ fn display_negative_zero_object() {
num num
"#; "#;
let value = forward_val(&mut engine, d_obj).unwrap(); let value = forward_val(&mut engine, d_obj).unwrap();
assert_eq!(value.to_string(), "Number { -0 }") assert_eq!(value.display().to_string(), "Number { -0 }")
} }
#[test] #[test]
@ -459,7 +460,7 @@ fn display_object() {
"#; "#;
let value = forward_val(&mut engine, d_obj).unwrap(); let value = forward_val(&mut engine, d_obj).unwrap();
assert_eq!( assert_eq!(
value.to_string(), value.display().to_string(),
r#"{ r#"{
a: "a", a: "a",
__proto__: { __proto__: {

9
boa/src/exec/call/mod.rs

@ -11,17 +11,16 @@ impl Executable for Call {
let (this, func) = match self.expr() { let (this, func) = match self.expr() {
Node::GetConstField(ref get_const_field) => { Node::GetConstField(ref get_const_field) => {
let mut obj = get_const_field.obj().run(interpreter)?; let mut obj = get_const_field.obj().run(interpreter)?;
if obj.get_type() != Type::Object || obj.get_type() != Type::Symbol { if obj.get_type() != Type::Object {
obj = interpreter obj = obj.to_object(interpreter)?;
.to_object(&obj)
.expect("failed to convert to object");
} }
(obj.clone(), obj.get_field(get_const_field.field())) (obj.clone(), obj.get_field(get_const_field.field()))
} }
Node::GetField(ref get_field) => { Node::GetField(ref get_field) => {
let obj = get_field.obj().run(interpreter)?; let obj = get_field.obj().run(interpreter)?;
let field = get_field.field().run(interpreter)?; let field = get_field.field().run(interpreter)?;
(obj.clone(), obj.get_field(field.to_string())) // FIXME: This should not call `.display()`
(obj.clone(), obj.get_field(field.display().to_string()))
} }
_ => ( _ => (
interpreter.realm().global_obj.clone(), interpreter.realm().global_obj.clone(),

10
boa/src/exec/field/mod.rs

@ -7,8 +7,8 @@ use crate::{
impl Executable for GetConstField { impl Executable for GetConstField {
fn run(&self, interpreter: &mut Interpreter) -> ResultValue { fn run(&self, interpreter: &mut Interpreter) -> ResultValue {
let mut obj = self.obj().run(interpreter)?; let mut obj = self.obj().run(interpreter)?;
if obj.get_type() != Type::Object || obj.get_type() != Type::Symbol { if obj.get_type() != Type::Object {
obj = interpreter.to_object(&obj)?; obj = obj.to_object(interpreter)?;
} }
Ok(obj.get_field(self.field())) Ok(obj.get_field(self.field()))
@ -18,11 +18,11 @@ impl Executable for GetConstField {
impl Executable for GetField { impl Executable for GetField {
fn run(&self, interpreter: &mut Interpreter) -> ResultValue { fn run(&self, interpreter: &mut Interpreter) -> ResultValue {
let mut obj = self.obj().run(interpreter)?; let mut obj = self.obj().run(interpreter)?;
if obj.get_type() != Type::Object || obj.get_type() != Type::Symbol { if obj.get_type() != Type::Object {
obj = interpreter.to_object(&obj)?; obj = obj.to_object(interpreter)?;
} }
let field = self.field().run(interpreter)?; let field = self.field().run(interpreter)?;
Ok(obj.get_field(interpreter.to_string(&field)?)) Ok(obj.get_field(field.to_string(interpreter)?))
} }
} }

341
boa/src/exec/mod.rs

@ -26,11 +26,10 @@ use crate::{
builtins, builtins,
builtins::{ builtins::{
function::{Function as FunctionObject, FunctionBody, ThisMode}, function::{Function as FunctionObject, FunctionBody, ThisMode},
number::{f64_to_int32, f64_to_uint32},
object::{Object, ObjectData, PROTOTYPE}, object::{Object, ObjectData, PROTOTYPE},
property::PropertyKey, property::PropertyKey,
value::{RcBigInt, RcString, ResultValue, Type, Value}, value::{PreferredType, ResultValue, Type, Value},
BigInt, Console, Number, Console,
}, },
realm::Realm, realm::Realm,
syntax::ast::{ syntax::ast::{
@ -39,9 +38,6 @@ use crate::{
}, },
BoaProfiler, BoaProfiler,
}; };
use std::borrow::Borrow;
use std::convert::TryFrom;
use std::ops::Deref;
pub trait Executable { pub trait Executable {
/// Runs this executable in the given executor. /// Runs this executable in the given executor.
@ -54,12 +50,6 @@ pub(crate) enum InterpreterState {
Return, Return,
Break(Option<String>), Break(Option<String>),
} }
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum PreferredType {
String,
Number,
Default,
}
/// A Javascript intepreter /// A Javascript intepreter
#[derive(Debug)] #[derive(Debug)]
@ -197,210 +187,22 @@ impl Interpreter {
} }
} }
/// Converts a value into a rust heap allocated string.
#[allow(clippy::wrong_self_convention)]
pub fn to_string(&mut self, value: &Value) -> Result<RcString, Value> {
match value {
Value::Null => Ok(RcString::from("null")),
Value::Undefined => Ok(RcString::from("undefined".to_owned())),
Value::Boolean(boolean) => Ok(RcString::from(boolean.to_string())),
Value::Rational(rational) => Ok(RcString::from(Number::to_native_string(*rational))),
Value::Integer(integer) => Ok(RcString::from(integer.to_string())),
Value::String(string) => Ok(string.clone()),
Value::Symbol(_) => Err(self.construct_type_error("can't convert symbol to string")),
Value::BigInt(ref bigint) => Ok(RcString::from(bigint.to_string())),
Value::Object(_) => {
let primitive = self.to_primitive(value, PreferredType::String)?;
self.to_string(&primitive)
}
}
}
/// Helper function.
#[allow(clippy::wrong_self_convention)]
pub fn to_bigint(&mut self, value: &Value) -> Result<RcBigInt, Value> {
match value {
Value::Null => Err(self.construct_type_error("cannot convert null to a BigInt")),
Value::Undefined => {
Err(self.construct_type_error("cannot convert undefined to a BigInt"))
}
Value::String(ref string) => Ok(RcBigInt::from(BigInt::from_string(string, self)?)),
Value::Boolean(true) => Ok(RcBigInt::from(BigInt::from(1))),
Value::Boolean(false) => Ok(RcBigInt::from(BigInt::from(0))),
Value::Integer(num) => Ok(RcBigInt::from(BigInt::from(*num))),
Value::Rational(num) => {
if let Ok(bigint) = BigInt::try_from(*num) {
return Ok(RcBigInt::from(bigint));
}
Err(self.construct_type_error(format!(
"The number {} cannot be converted to a BigInt because it is not an integer",
num
)))
}
Value::BigInt(b) => Ok(b.clone()),
Value::Object(_) => {
let primitive = self.to_primitive(value, PreferredType::Number)?;
self.to_bigint(&primitive)
}
Value::Symbol(_) => Err(self.construct_type_error("cannot convert Symbol to a BigInt")),
}
}
/// Converts a value to a non-negative integer if it is a valid integer index value.
///
/// See: https://tc39.es/ecma262/#sec-toindex
#[allow(clippy::wrong_self_convention)]
pub fn to_index(&mut self, value: &Value) -> Result<usize, Value> {
if value.is_undefined() {
return Ok(0);
}
let integer_index = self.to_integer(value)?;
if integer_index < 0.0 {
return Err(self.construct_range_error("Integer index must be >= 0"));
}
if integer_index > Number::MAX_SAFE_INTEGER {
return Err(self.construct_range_error("Integer index must be less than 2**(53) - 1"));
}
Ok(integer_index as usize)
}
/// Converts a value to an integral Number value.
///
/// See: https://tc39.es/ecma262/#sec-tointeger
#[allow(clippy::wrong_self_convention)]
pub fn to_integer(&mut self, value: &Value) -> Result<f64, Value> {
// 1. Let number be ? ToNumber(argument).
let number = self.to_number(value)?;
// 2. If number is +∞ or -∞, return number.
if !number.is_finite() {
// 3. If number is NaN, +0, or -0, return +0.
if number.is_nan() {
return Ok(0.0);
}
return Ok(number);
}
// 4. Let integer be the Number value that is the same sign as number and whose magnitude is floor(abs(number)).
// 5. If integer is -0, return +0.
// 6. Return integer.
Ok(number.trunc() + 0.0) // We add 0.0 to convert -0.0 to +0.0
}
/// Converts a value to an integral 32 bit signed integer.
///
/// See: https://tc39.es/ecma262/#sec-toint32
#[allow(clippy::wrong_self_convention)]
pub fn to_int32(&mut self, value: &Value) -> Result<i32, Value> {
// This is the fast path, if the value is Integer we can just return it.
if let Value::Integer(number) = *value {
return Ok(number);
}
let number = self.to_number(value)?;
Ok(f64_to_int32(number))
}
/// Converts a value to an integral 32 bit unsigned integer.
///
/// See: https://tc39.es/ecma262/#sec-toint32
#[allow(clippy::wrong_self_convention)]
pub fn to_uint32(&mut self, value: &Value) -> Result<u32, Value> {
// This is the fast path, if the value is Integer we can just return it.
if let Value::Integer(number) = *value {
return Ok(number as u32);
}
let number = self.to_number(value)?;
Ok(f64_to_uint32(number))
}
/// Converts argument to an integer suitable for use as the length of an array-like object.
///
/// See: https://tc39.es/ecma262/#sec-tolength
#[allow(clippy::wrong_self_convention)]
pub fn to_length(&mut self, value: &Value) -> Result<usize, Value> {
// 1. Let len be ? ToInteger(argument).
let len = self.to_integer(value)?;
// 2. If len ≤ +0, return +0.
if len < 0.0 {
return Ok(0);
}
// 3. Return min(len, 2^53 - 1).
Ok(len.min(Number::MAX_SAFE_INTEGER) as usize)
}
/// Converts a value to a double precision floating point.
///
/// See: https://tc39.es/ecma262/#sec-tonumber
#[allow(clippy::wrong_self_convention)]
pub fn to_number(&mut self, value: &Value) -> Result<f64, Value> {
match *value {
Value::Null => Ok(0.0),
Value::Undefined => Ok(f64::NAN),
Value::Boolean(b) => Ok(if b { 1.0 } else { 0.0 }),
// TODO: this is probably not 100% correct, see https://tc39.es/ecma262/#sec-tonumber-applied-to-the-string-type
Value::String(ref string) => Ok(string.parse().unwrap_or(f64::NAN)),
Value::Rational(number) => Ok(number),
Value::Integer(integer) => Ok(f64::from(integer)),
Value::Symbol(_) => Err(self.construct_type_error("argument must not be a symbol")),
Value::BigInt(_) => Err(self.construct_type_error("argument must not be a bigint")),
Value::Object(_) => {
let primitive = self.to_primitive(value, PreferredType::Number)?;
self.to_number(&primitive)
}
}
}
/// It returns value converted to a numeric value of type Number or BigInt.
///
/// See: https://tc39.es/ecma262/#sec-tonumeric
#[allow(clippy::wrong_self_convention)]
pub fn to_numeric(&mut self, value: &Value) -> ResultValue {
let primitive = self.to_primitive(value, PreferredType::Number)?;
if primitive.is_bigint() {
return Ok(primitive);
}
Ok(Value::from(self.to_number(&primitive)?))
}
/// This is a more specialized version of `to_numeric`.
///
/// It returns value converted to a numeric value of type `Number`.
///
/// See: https://tc39.es/ecma262/#sec-tonumeric
#[allow(clippy::wrong_self_convention)]
pub(crate) fn to_numeric_number(&mut self, value: &Value) -> Result<f64, Value> {
let primitive = self.to_primitive(value, PreferredType::Number)?;
if let Some(ref bigint) = primitive.as_bigint() {
return Ok(bigint.to_f64());
}
Ok(self.to_number(&primitive)?)
}
/// Converts an array object into a rust vector of values. /// Converts an array object into a rust vector of values.
/// ///
/// This is useful for the spread operator, for any other object an `Err` is returned /// This is useful for the spread operator, for any other object an `Err` is returned
pub(crate) fn extract_array_properties(&mut self, value: &Value) -> Result<Vec<Value>, ()> { pub(crate) fn extract_array_properties(&mut self, value: &Value) -> Result<Vec<Value>, ()> {
if let Value::Object(ref x) = value { if let Value::Object(ref x) = value {
// Check if object is array // Check if object is array
if let ObjectData::Array = x.deref().borrow().data { if let ObjectData::Array = x.borrow().data {
let length = i32::from(&value.get_field("length")); let length = value.get_field("length").as_number().unwrap() as i32;
let values = (0..length) let values = (0..length)
.map(|idx| value.get_field(idx.to_string())) .map(|idx| value.get_field(idx.to_string()))
.collect(); .collect();
return Ok(values); return Ok(values);
} }
// Check if object is a Map // Check if object is a Map
else if let ObjectData::Map(ref map) = x.deref().borrow().data { else if let ObjectData::Map(ref map) = x.borrow().data {
let values = map let values = map
.borrow()
.iter() .iter()
.map(|(key, value)| { .map(|(key, value)| {
// Construct a new array containing the key-value pair // Construct a new array containing the key-value pair
@ -417,7 +219,6 @@ impl Interpreter {
.environment .environment
.get_binding_value("Array") .get_binding_value("Array")
.expect("Array was not initialized") .expect("Array was not initialized")
.borrow()
.get_field(PROTOTYPE), .get_field(PROTOTYPE),
); );
array.set_field("0", key); array.set_field("0", key);
@ -476,49 +277,6 @@ impl Interpreter {
self.throw_type_error("cannot convert object to primitive value") self.throw_type_error("cannot convert object to primitive value")
} }
/// The abstract operation ToPrimitive takes an input argument and an optional argument PreferredType.
///
/// <https://tc39.es/ecma262/#sec-toprimitive>
#[allow(clippy::wrong_self_convention)]
pub(crate) fn to_primitive(
&mut self,
input: &Value,
preferred_type: PreferredType,
) -> ResultValue {
// 1. Assert: input is an ECMAScript language value. (always a value not need to check)
// 2. If Type(input) is Object, then
if let Value::Object(_) = input {
let mut hint = preferred_type;
// Skip d, e we don't support Symbols yet
// TODO: add when symbols are supported
// TODO: Add other steps.
if hint == PreferredType::Default {
hint = PreferredType::Number;
};
// g. Return ? OrdinaryToPrimitive(input, hint).
self.ordinary_to_primitive(input, hint)
} else {
// 3. Return input.
Ok(input.clone())
}
}
/// The abstract operation ToPropertyKey takes argument argument. It converts argument to a value that can be used as a property key.
///
/// https://tc39.es/ecma262/#sec-topropertykey
#[allow(clippy::wrong_self_convention)]
pub(crate) fn to_property_key(&mut self, value: &Value) -> Result<PropertyKey, Value> {
let key = self.to_primitive(value, PreferredType::String)?;
if let Value::Symbol(ref symbol) = key {
Ok(PropertyKey::from(symbol.clone()))
} else {
let string = self.to_string(&key)?;
Ok(PropertyKey::from(string))
}
}
/// https://tc39.es/ecma262/#sec-hasproperty /// https://tc39.es/ecma262/#sec-hasproperty
pub(crate) fn has_property(&self, obj: &Value, key: &PropertyKey) -> bool { pub(crate) fn has_property(&self, obj: &Value, key: &PropertyKey) -> bool {
if let Some(obj) = obj.as_object() { if let Some(obj) = obj.as_object() {
@ -528,93 +286,6 @@ impl Interpreter {
} }
} }
/// The abstract operation ToObject converts argument to a value of type Object
/// https://tc39.es/ecma262/#sec-toobject
#[allow(clippy::wrong_self_convention)]
pub(crate) fn to_object(&mut self, value: &Value) -> ResultValue {
match value {
Value::Undefined | Value::Null => {
self.throw_type_error("cannot convert 'null' or 'undefined' to object")
}
Value::Boolean(boolean) => {
let proto = self
.realm
.environment
.get_binding_value("Boolean")
.expect("Boolean was not initialized")
.get_field(PROTOTYPE);
Ok(Value::new_object_from_prototype(
proto,
ObjectData::Boolean(*boolean),
))
}
Value::Integer(integer) => {
let proto = self
.realm
.environment
.get_binding_value("Number")
.expect("Number was not initialized")
.get_field(PROTOTYPE);
Ok(Value::new_object_from_prototype(
proto,
ObjectData::Number(f64::from(*integer)),
))
}
Value::Rational(rational) => {
let proto = self
.realm
.environment
.get_binding_value("Number")
.expect("Number was not initialized")
.get_field(PROTOTYPE);
Ok(Value::new_object_from_prototype(
proto,
ObjectData::Number(*rational),
))
}
Value::String(ref string) => {
let proto = self
.realm
.environment
.get_binding_value("String")
.expect("String was not initialized")
.get_field(PROTOTYPE);
Ok(Value::new_object_from_prototype(
proto,
ObjectData::String(string.clone()),
))
}
Value::Symbol(ref symbol) => {
let proto = self
.realm
.environment
.get_binding_value("Symbol")
.expect("Symbol was not initialized")
.get_field(PROTOTYPE);
Ok(Value::new_object_from_prototype(
proto,
ObjectData::Symbol(symbol.clone()),
))
}
Value::BigInt(ref bigint) => {
let proto = self
.realm
.environment
.get_binding_value("BigInt")
.expect("BigInt was not initialized")
.get_field(PROTOTYPE);
let bigint_obj =
Value::new_object_from_prototype(proto, ObjectData::BigInt(bigint.clone()));
Ok(bigint_obj)
}
Value::Object(_) => Ok(value.clone()),
}
}
fn set_value(&mut self, node: &Node, value: Value) -> ResultValue { fn set_value(&mut self, node: &Node, value: Value) -> ResultValue {
match node { match node {
Node::Identifier(ref name) => { Node::Identifier(ref name) => {
@ -629,7 +300,7 @@ impl Interpreter {
.set_field(get_const_field_node.field(), value)), .set_field(get_const_field_node.field(), value)),
Node::GetField(ref get_field) => { Node::GetField(ref get_field) => {
let field = get_field.field().run(self)?; let field = get_field.field().run(self)?;
let key = self.to_property_key(&field)?; let key = field.to_property_key(self)?;
Ok(get_field.obj().run(self)?.set_field(key, value)) Ok(get_field.obj().run(self)?.set_field(key, value))
} }
_ => panic!("TypeError: invalid assignment to {}", node), _ => panic!("TypeError: invalid assignment to {}", node),

26
boa/src/exec/operator/mod.rs

@ -40,7 +40,7 @@ impl Executable for Assign {
Node::GetField(ref get_field) => { Node::GetField(ref get_field) => {
let object = get_field.obj().run(interpreter)?; let object = get_field.obj().run(interpreter)?;
let field = get_field.field().run(interpreter)?; let field = get_field.field().run(interpreter)?;
let key = interpreter.to_property_key(&field)?; let key = field.to_property_key(interpreter)?;
object.set_field(key, val.clone()); object.set_field(key, val.clone());
} }
_ => (), _ => (),
@ -95,7 +95,7 @@ impl Executable for BinOp {
y.get_type().as_str() y.get_type().as_str()
)); ));
} }
let key = interpreter.to_property_key(&x)?; let key = x.to_property_key(interpreter)?;
interpreter.has_property(&y, &key) interpreter.has_property(&y, &key)
} }
})) }))
@ -173,30 +173,34 @@ impl Executable for UnaryOp {
Ok(match self.op() { Ok(match self.op() {
op::UnaryOp::Minus => x.neg(interpreter)?, op::UnaryOp::Minus => x.neg(interpreter)?,
op::UnaryOp::Plus => Value::from(x.to_number()), op::UnaryOp::Plus => Value::from(x.to_number(interpreter)?),
op::UnaryOp::IncrementPost => { op::UnaryOp::IncrementPost => {
let ret = x.clone(); let ret = x.clone();
interpreter.set_value(self.target(), Value::from(x.to_number() + 1.0))?; let result = x.to_number(interpreter)? + 1.0;
interpreter.set_value(self.target(), result.into())?;
ret ret
} }
op::UnaryOp::IncrementPre => { op::UnaryOp::IncrementPre => {
interpreter.set_value(self.target(), Value::from(x.to_number() + 1.0))? let result = x.to_number(interpreter)? + 1.0;
interpreter.set_value(self.target(), result.into())?
} }
op::UnaryOp::DecrementPost => { op::UnaryOp::DecrementPost => {
let ret = x.clone(); let ret = x.clone();
interpreter.set_value(self.target(), Value::from(x.to_number() - 1.0))?; let result = x.to_number(interpreter)? - 1.0;
interpreter.set_value(self.target(), result.into())?;
ret ret
} }
op::UnaryOp::DecrementPre => { op::UnaryOp::DecrementPre => {
interpreter.set_value(self.target(), Value::from(x.to_number() - 1.0))? let result = x.to_number(interpreter)? - 1.0;
interpreter.set_value(self.target(), result.into())?
} }
op::UnaryOp::Not => x.not(interpreter)?, op::UnaryOp::Not => x.not(interpreter)?.into(),
op::UnaryOp::Tilde => { op::UnaryOp::Tilde => {
let num_v_a = x.to_number(); let num_v_a = x.to_number(interpreter)?;
// NOTE: possible UB: https://github.com/rust-lang/rust/issues/10184
Value::from(if num_v_a.is_nan() { Value::from(if num_v_a.is_nan() {
-1 -1
} else { } else {
// TODO: this is not spec compliant.
!(num_v_a as i32) !(num_v_a as i32)
}) })
} }
@ -211,7 +215,7 @@ impl Executable for UnaryOp {
Node::GetField(ref get_field) => { Node::GetField(ref get_field) => {
let obj = get_field.obj().run(interpreter)?; let obj = get_field.obj().run(interpreter)?;
let field = &get_field.field().run(interpreter)?; let field = &get_field.field().run(interpreter)?;
let res = obj.remove_property(interpreter.to_string(field)?.as_str()); let res = obj.remove_property(field.to_string(interpreter)?.as_str());
return Ok(Value::boolean(res)); return Ok(Value::boolean(res));
} }
Node::Identifier(_) => Value::boolean(false), Node::Identifier(_) => Value::boolean(false),

81
boa/src/exec/tests.rs

@ -879,11 +879,11 @@ fn to_bigint() {
let realm = Realm::create(); let realm = Realm::create();
let mut engine = Interpreter::new(realm); let mut engine = Interpreter::new(realm);
assert!(engine.to_bigint(&Value::null()).is_err()); assert!(Value::null().to_bigint(&mut engine).is_err());
assert!(engine.to_bigint(&Value::undefined()).is_err()); assert!(Value::undefined().to_bigint(&mut engine).is_err());
assert!(engine.to_bigint(&Value::integer(55)).is_ok()); assert!(Value::integer(55).to_bigint(&mut engine).is_ok());
assert!(engine.to_bigint(&Value::rational(10.0)).is_ok()); assert!(Value::rational(10.0).to_bigint(&mut engine).is_ok());
assert!(engine.to_bigint(&Value::string("100")).is_ok()); assert!(Value::string("100").to_bigint(&mut engine).is_ok());
} }
#[test] #[test]
@ -891,8 +891,8 @@ fn to_index() {
let realm = Realm::create(); let realm = Realm::create();
let mut engine = Interpreter::new(realm); let mut engine = Interpreter::new(realm);
assert_eq!(engine.to_index(&Value::undefined()).unwrap(), 0); assert_eq!(Value::undefined().to_index(&mut engine).unwrap(), 0);
assert!(engine.to_index(&Value::integer(-1)).is_err()); assert!(Value::integer(-1).to_index(&mut engine).is_err());
} }
#[test] #[test]
@ -901,32 +901,34 @@ fn to_integer() {
let mut engine = Interpreter::new(realm); let mut engine = Interpreter::new(realm);
assert!(Number::equal( assert!(Number::equal(
engine.to_integer(&Value::number(f64::NAN)).unwrap(), Value::number(f64::NAN).to_integer(&mut engine).unwrap(),
0.0 0.0
)); ));
assert!(Number::equal( assert!(Number::equal(
engine Value::number(f64::NEG_INFINITY)
.to_integer(&Value::number(f64::NEG_INFINITY)) .to_integer(&mut engine)
.unwrap(), .unwrap(),
f64::NEG_INFINITY f64::NEG_INFINITY
)); ));
assert!(Number::equal( assert!(Number::equal(
engine.to_integer(&Value::number(f64::INFINITY)).unwrap(), Value::number(f64::INFINITY)
.to_integer(&mut engine)
.unwrap(),
f64::INFINITY f64::INFINITY
)); ));
assert!(Number::equal( assert!(Number::equal(
engine.to_integer(&Value::number(0.0)).unwrap(), Value::number(0.0).to_integer(&mut engine).unwrap(),
0.0 0.0
)); ));
let number = engine.to_integer(&Value::number(-0.0)).unwrap(); let number = Value::number(-0.0).to_integer(&mut engine).unwrap();
assert!(!number.is_sign_negative()); assert!(!number.is_sign_negative());
assert!(Number::equal(number, 0.0)); assert!(Number::equal(number, 0.0));
assert!(Number::equal( assert!(Number::equal(
engine.to_integer(&Value::number(20.9)).unwrap(), Value::number(20.9).to_integer(&mut engine).unwrap(),
20.0 20.0
)); ));
assert!(Number::equal( assert!(Number::equal(
engine.to_integer(&Value::number(-20.9)).unwrap(), Value::number(-20.9).to_integer(&mut engine).unwrap(),
-20.0 -20.0
)); ));
} }
@ -936,25 +938,29 @@ fn to_length() {
let realm = Realm::create(); let realm = Realm::create();
let mut engine = Interpreter::new(realm); let mut engine = Interpreter::new(realm);
assert_eq!(engine.to_length(&Value::number(f64::NAN)).unwrap(), 0); assert_eq!(Value::number(f64::NAN).to_length(&mut engine).unwrap(), 0);
assert_eq!( assert_eq!(
engine.to_length(&Value::number(f64::NEG_INFINITY)).unwrap(), Value::number(f64::NEG_INFINITY)
.to_length(&mut engine)
.unwrap(),
0 0
); );
assert_eq!( assert_eq!(
engine.to_length(&Value::number(f64::INFINITY)).unwrap(), Value::number(f64::INFINITY).to_length(&mut engine).unwrap(),
Number::MAX_SAFE_INTEGER as usize Number::MAX_SAFE_INTEGER as usize
); );
assert_eq!(engine.to_length(&Value::number(0.0)).unwrap(), 0); assert_eq!(Value::number(0.0).to_length(&mut engine).unwrap(), 0);
assert_eq!(engine.to_length(&Value::number(-0.0)).unwrap(), 0); assert_eq!(Value::number(-0.0).to_length(&mut engine).unwrap(), 0);
assert_eq!(engine.to_length(&Value::number(20.9)).unwrap(), 20); assert_eq!(Value::number(20.9).to_length(&mut engine).unwrap(), 20);
assert_eq!(engine.to_length(&Value::number(-20.9)).unwrap(), 0); assert_eq!(Value::number(-20.9).to_length(&mut engine).unwrap(), 0);
assert_eq!( assert_eq!(
engine.to_length(&Value::number(100000000000.0)).unwrap(), Value::number(100000000000.0)
.to_length(&mut engine)
.unwrap(),
100000000000 100000000000
); );
assert_eq!( assert_eq!(
engine.to_length(&Value::number(4010101101.0)).unwrap(), Value::number(4010101101.0).to_length(&mut engine).unwrap(),
4010101101 4010101101
); );
} }
@ -966,7 +972,7 @@ fn to_int32() {
macro_rules! check_to_int32 { macro_rules! check_to_int32 {
($from:expr => $to:expr) => { ($from:expr => $to:expr) => {
assert_eq!(engine.to_int32(&Value::number($from)).unwrap(), $to); assert_eq!(Value::from($from).to_int32(&mut engine).unwrap(), $to);
}; };
}; };
@ -1078,11 +1084,17 @@ fn to_string() {
let realm = Realm::create(); let realm = Realm::create();
let mut engine = Interpreter::new(realm); let mut engine = Interpreter::new(realm);
assert_eq!(engine.to_string(&Value::null()).unwrap(), "null"); assert_eq!(Value::null().to_string(&mut engine).unwrap(), "null");
assert_eq!(engine.to_string(&Value::undefined()).unwrap(), "undefined"); assert_eq!(
assert_eq!(engine.to_string(&Value::integer(55)).unwrap(), "55"); Value::undefined().to_string(&mut engine).unwrap(),
assert_eq!(engine.to_string(&Value::rational(55.0)).unwrap(), "55"); "undefined"
assert_eq!(engine.to_string(&Value::string("hello")).unwrap(), "hello"); );
assert_eq!(Value::integer(55).to_string(&mut engine).unwrap(), "55");
assert_eq!(Value::rational(55.0).to_string(&mut engine).unwrap(), "55");
assert_eq!(
Value::string("hello").to_string(&mut engine).unwrap(),
"hello"
);
} }
#[test] #[test]
@ -1105,11 +1117,14 @@ fn to_object() {
let realm = Realm::create(); let realm = Realm::create();
let mut engine = Interpreter::new(realm); let mut engine = Interpreter::new(realm);
assert!(engine assert!(Value::undefined()
.to_object(&Value::undefined()) .to_object(&mut engine)
.unwrap_err()
.is_object());
assert!(Value::null()
.to_object(&mut engine)
.unwrap_err() .unwrap_err()
.is_object()); .is_object());
assert!(engine.to_object(&Value::null()).unwrap_err().is_object());
} }
#[test] #[test]

6
boa/src/lib.rs

@ -66,8 +66,10 @@ pub fn forward(engine: &mut Interpreter, src: &str) -> String {
Ok(res) => res, Ok(res) => res,
Err(e) => return e, Err(e) => return e,
}; };
expr.run(engine) expr.run(engine).map_or_else(
.map_or_else(|e| format!("Error: {}", e), |v| v.to_string()) |e| format!("Error: {}", e.display()),
|v| v.display().to_string(),
)
} }
/// Execute the code using an existing Interpreter. /// Execute the code using an existing Interpreter.

10
boa_cli/src/main.rs

@ -194,8 +194,8 @@ pub fn main() -> Result<(), std::io::Error> {
} }
} else { } else {
match forward_val(&mut engine, &buffer) { match forward_val(&mut engine, &buffer) {
Ok(v) => print!("{}", v), Ok(v) => print!("{}", v.display()),
Err(v) => eprint!("{}", v), Err(v) => eprint!("{}", v.display()),
} }
} }
} }
@ -230,8 +230,10 @@ pub fn main() -> Result<(), std::io::Error> {
} }
} else { } else {
match forward_val(&mut engine, line.trim_end()) { match forward_val(&mut engine, line.trim_end()) {
Ok(v) => println!("{}", v), Ok(v) => println!("{}", v.display()),
Err(v) => eprintln!("{}: {}", "Uncaught".red(), v.to_string().red()), Err(v) => {
eprintln!("{}: {}", "Uncaught".red(), v.display().to_string().red())
}
} }
} }
} }

4
boa_wasm/src/lib.rs

@ -19,6 +19,6 @@ pub fn evaluate(src: &str) -> Result<String, JsValue> {
// Setup executor // Setup executor
expr.run(&mut engine) expr.run(&mut engine)
.map_err(|e| JsValue::from(format!("Error: {}", e))) .map_err(|e| JsValue::from(format!("Error: {}", e.display())))
.map(|v| v.to_string()) .map(|v| v.display().to_string())
} }

Loading…
Cancel
Save