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();
// 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 {
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
/// values on the end, correctly rewriting the length
pub(crate) fn add_to_array_object(array_ptr: &Value, add_values: &[Value]) -> ResultValue {
let orig_length = i32::from(&array_ptr.get_field("length"));
let orig_length = array_ptr.get_field("length").as_number().unwrap() as i32;
for (n, value) in add_values.iter().enumerate() {
let new_index = orig_length.wrapping_add(n as i32);
@ -124,7 +124,7 @@ impl Array {
let mut length = args.len() as i32;
match args.len() {
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`.
for n in 0..length {
this.set_field(n.to_string(), Value::undefined());
@ -195,13 +195,13 @@ impl Array {
// one)
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 {
new_values.push(this.get_field(n.to_string()));
}
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 {
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
/// [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 {
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 {
return Ok(Value::undefined());
}
@ -271,7 +271,7 @@ impl Array {
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 length = i32::from(&this.get_field("length"));
let length = this.get_field("length").as_number().unwrap() as i32;
for i in 0..length {
let element = this.get_field(i.to_string());
@ -299,14 +299,16 @@ impl Array {
let separator = if args.is_empty() {
String::from(",")
} 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()
};
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 {
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);
}
@ -367,7 +369,7 @@ impl Array {
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/reverse
#[allow(clippy::else_if_without_else)]
pub(crate) fn reverse(this: &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);
for lower in 0..middle {
@ -405,7 +407,7 @@ impl Array {
/// [spec]: https://tc39.es/ecma262/#sec-array.prototype.shift
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/shift
pub(crate) fn shift(this: &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 {
this.set_field("length", 0);
@ -447,7 +449,7 @@ impl Array {
/// [spec]: https://tc39.es/ecma262/#sec-array.prototype.unshift
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/unshift
pub(crate) fn unshift(this: &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;
if arg_c > 0 {
@ -507,7 +509,7 @@ impl Array {
Value::undefined()
};
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;
while i < len {
let element = this.get_field(i.to_string());
@ -516,7 +518,10 @@ impl Array {
if !result.to_boolean() {
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;
}
Ok(Value::from(true))
@ -543,7 +548,7 @@ impl Array {
let callback = args.get(0).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)?;
@ -587,11 +592,11 @@ impl Array {
}
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) {
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 {
len + from_idx
@ -640,11 +645,11 @@ impl Array {
}
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) {
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 {
min(from_idx, len - 1)
@ -688,7 +693,7 @@ impl Array {
}
let callback = &args[0];
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 {
let element = this.get_field(i.to_string());
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 length = i32::from(&this.get_field("length"));
let length = this.get_field("length").as_number().unwrap() as i32;
for i in 0..length {
let element = this.get_field(i.to_string());
@ -754,16 +759,16 @@ impl Array {
///
/// [spec]: https://tc39.es/ecma262/#sec-array.prototype.fill
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/fill
pub(crate) fn fill(this: &Value, args: &[Value], _: &mut Interpreter) -> ResultValue {
let len: i32 = i32::from(&this.get_field("length"));
pub(crate) fn fill(this: &Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue {
let len: i32 = this.get_field("length").as_number().unwrap() as i32;
let default_value = Value::undefined();
let value = args.get(0).unwrap_or(&default_value);
let relative_start = args.get(1).unwrap_or(&default_value).to_number() as i32;
let relative_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 = if relative_end_val.is_undefined() {
len
} else {
relative_end_val.to_number() as i32
relative_end_val.to_number(ctx)? as i32
};
let start = if 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 {
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 {
let check_element = this.get_field(idx.to_string()).clone();
@ -829,14 +834,14 @@ impl Array {
interpreter: &mut Interpreter,
) -> ResultValue {
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) {
Some(v) => i32::from(v),
Some(v) => v.as_number().unwrap() as i32,
None => 0,
};
let end = match args.get(1) {
Some(v) => i32::from(v),
Some(v) => v.as_number().unwrap() as i32,
None => len,
};
@ -886,7 +891,7 @@ impl Array {
let callback = args.get(0).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)?;
@ -939,7 +944,7 @@ impl Array {
Value::undefined()
};
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;
while i < len {
let element = this.get_field(i.to_string());
@ -949,7 +954,10 @@ impl Array {
return Ok(Value::from(true));
}
// the length of the array must be updated because the callback can mutate it.
len = min(max_len, i32::from(&this.get_field("length")));
len = min(
max_len,
this.get_field("length").as_number().unwrap() as i32,
);
i += 1;
}
Ok(Value::from(false))
@ -971,13 +979,13 @@ impl Array {
args: &[Value],
interpreter: &mut Interpreter,
) -> ResultValue {
let this = interpreter.to_object(this)?;
let this = this.to_object(interpreter)?;
let callback = match args.get(0) {
Some(value) if value.is_function() => value,
_ => return interpreter.throw_type_error("Reduce was called without a callback"),
};
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() {
return interpreter
.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.
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 */
length = min(length, interpreter.to_length(&this.get_field("length"))?);
length = min(length, this.get_field("length").to_length(interpreter)?);
}
k += 1;
}
@ -1038,13 +1046,13 @@ impl Array {
args: &[Value],
interpreter: &mut Interpreter,
) -> ResultValue {
let this = interpreter.to_object(this)?;
let this = this.to_object(interpreter)?;
let callback = match args.get(0) {
Some(value) if value.is_function() => value,
_ => return interpreter.throw_type_error("reduceRight was called without a callback"),
};
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 initial_value.is_undefined() {
return interpreter.throw_type_error(
@ -1092,7 +1100,7 @@ impl Array {
/* 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
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
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
pub(crate) fn make_bigint(_: &Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue {
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)),
};
Ok(Value::from(data))
@ -112,7 +112,7 @@ impl BigInt {
#[allow(clippy::wrong_self_convention)]
pub(crate) fn to_string(this: &Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue {
let radix = if !args.is_empty() {
args[0].to_integer()
args[0].to_integer(ctx)? as i32
} else {
10
};
@ -184,10 +184,10 @@ impl BigInt {
let bits_arg = args.get(0).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 bigint = ctx.to_bigint(bigint_arg)?;
let bigint = bigint_arg.to_bigint(ctx)?;
Ok((
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.
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() {
0 => Ok(String::new()),
1 => Ok(target.to_string()),
@ -74,26 +74,37 @@ pub fn formatter(data: &[Value], ctx: &mut Interpreter) -> Result<String, Value>
match fmt {
/* integer */
'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));
arg_index += 1;
}
/* float */
'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));
arg_index += 1
}
/* object, FIXME: how to render this properly? */
'o' | 'O' => {
let arg = data.get(arg_index).cloned().unwrap_or_default();
formatted.push_str(&format!("{}", arg));
formatted.push_str(&format!("{}", arg.display()));
arg_index += 1
}
/* string */
's' => {
let arg =
ctx.to_string(&data.get(arg_index).cloned().unwrap_or_default())?;
let arg = data
.get(arg_index)
.cloned()
.unwrap_or_default()
.to_string(ctx)?;
formatted.push_str(&arg);
arg_index += 1
}
@ -111,7 +122,7 @@ pub fn formatter(data: &[Value], ctx: &mut Interpreter) -> Result<String, Value>
/* unformatted data */
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)
@ -153,7 +164,7 @@ impl Console {
} else if !args[0].is_string() {
args.insert(0, Value::from(message));
} else {
let concat = format!("{}: {}", message, args[0]);
let concat = format!("{}: {}", message, args[0].display());
args[0] = Value::from(concat);
}
@ -289,7 +300,7 @@ impl Console {
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/API/console/count
pub(crate) fn count(_: &Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue {
let label = match args.get(0) {
Some(value) => ctx.to_string(value)?,
Some(value) => value.to_string(ctx)?,
None => "default".into(),
};
@ -313,7 +324,7 @@ impl Console {
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/API/countReset
pub(crate) fn count_reset(_: &Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue {
let label = match args.get(0) {
Some(value) => ctx.to_string(value)?,
Some(value) => value.to_string(ctx)?,
None => "default".into(),
};
@ -347,7 +358,7 @@ impl Console {
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/API/console/time
pub(crate) fn time(_: &Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue {
let label = match args.get(0) {
Some(value) => ctx.to_string(value)?,
Some(value) => value.to_string(ctx)?,
None => "default".into(),
};
@ -376,7 +387,7 @@ impl Console {
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/API/console/timeLog
pub(crate) fn time_log(_: &Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue {
let label = match args.get(0) {
Some(value) => ctx.to_string(value)?,
Some(value) => value.to_string(ctx)?,
None => "default".into(),
};
@ -384,7 +395,7 @@ impl Console {
let time = Self::system_time_in_ms();
let mut concat = format!("{}: {} ms", label, time - t);
for msg in args.iter().skip(1) {
concat = concat + " " + &msg.to_string();
concat = concat + " " + &msg.display().to_string();
}
logger(LogMessage::Log(concat), ctx.console());
} else {
@ -409,7 +420,7 @@ impl Console {
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/API/console/timeEnd
pub(crate) fn time_end(_: &Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue {
let label = match args.get(0) {
Some(value) => ctx.to_string(value)?,
Some(value) => value.to_string(ctx)?,
None => "default".into(),
};

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

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

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

@ -1,10 +1,12 @@
#![allow(clippy::zero_prefixed_literal)]
use crate::{
builtins::{object::ObjectData, Value},
forward, forward_val, Interpreter, Realm,
};
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.
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")
};
let date_time = if let Value::Object(date_time) = &date_time {
date_time
if let Value::Object(ref date_time) = date_time {
if let ObjectData::Date(ref date_time) = date_time.borrow().data {
date_time.0
} else {
panic!("expected date")
}
} else {
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> {

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

@ -48,7 +48,7 @@ impl Error {
/// Create a new error object.
pub(crate) fn make_error(this: &Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue {
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
@ -71,7 +71,11 @@ impl Error {
pub(crate) fn to_string(this: &Value, _: &[Value], _: &mut Interpreter) -> ResultValue {
let name = this.get_field("name");
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.

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

@ -34,7 +34,7 @@ impl RangeError {
/// Create a new error object.
pub(crate) fn make_error(this: &Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue {
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
@ -55,8 +55,8 @@ impl RangeError {
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error/toString
#[allow(clippy::wrong_self_convention)]
pub(crate) fn to_string(this: &Value, _: &[Value], ctx: &mut Interpreter) -> ResultValue {
let name = ctx.to_string(&this.get_field("name"))?;
let message = ctx.to_string(&this.get_field("message"))?;
let name = this.get_field("name").to_string(ctx)?;
let message = this.get_field("message").to_string(ctx)?;
Ok(Value::from(format!("{}: {}", name, message)))
}

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

@ -33,7 +33,7 @@ impl ReferenceError {
/// Create a new error object.
pub(crate) fn make_error(this: &Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue {
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
@ -54,8 +54,8 @@ impl ReferenceError {
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error/toString
#[allow(clippy::wrong_self_convention)]
pub(crate) fn to_string(this: &Value, _: &[Value], ctx: &mut Interpreter) -> ResultValue {
let name = ctx.to_string(&this.get_field("name"))?;
let message = ctx.to_string(&this.get_field("message"))?;
let name = this.get_field("name").to_string(ctx)?;
let message = this.get_field("message").to_string(ctx)?;
Ok(Value::from(format!("{}: {}", name, message)))
}

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

@ -36,7 +36,7 @@ impl SyntaxError {
/// Create a new error object.
pub(crate) fn make_error(this: &Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue {
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
@ -59,7 +59,8 @@ impl SyntaxError {
pub(crate) fn to_string(this: &Value, _: &[Value], _: &mut Interpreter) -> ResultValue {
let name = this.get_field("name");
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.

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

@ -40,7 +40,7 @@ impl TypeError {
/// Create a new error object.
pub(crate) fn make_error(this: &Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue {
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
@ -61,8 +61,8 @@ impl TypeError {
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error/toString
#[allow(clippy::wrong_self_convention)]
pub(crate) fn to_string(this: &Value, _: &[Value], ctx: &mut Interpreter) -> ResultValue {
let name = ctx.to_string(&this.get_field("name"))?;
let message = ctx.to_string(&this.get_field("message"))?;
let name = this.get_field("name").to_string(ctx)?;
let message = this.get_field("message").to_string(ctx)?;
Ok(Value::from(format!("{}: {}", name, message)))
}

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

@ -337,7 +337,7 @@ impl Function {
}
}
} 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);
}
}

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
pub(crate) fn parse(_: &Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue {
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) => {
let j = Value::from_json(json, ctx);
@ -157,11 +160,11 @@ impl Json {
});
for field in fields {
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)))
.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()))

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

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

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

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

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

@ -17,8 +17,8 @@ fn abs() {
let a = forward_val(&mut engine, "a").unwrap();
let b = forward_val(&mut engine, "b").unwrap();
assert_eq!(a.to_number(), 2.0);
assert_eq!(b.to_number(), 6.655_559_999_999_999_5);
assert_eq!(a.to_number(&mut engine).unwrap(), 2.0);
assert_eq!(b.to_number(&mut engine).unwrap(), 6.655_559_999_999_999_5);
}
#[test]
@ -39,10 +39,10 @@ fn acos() {
let c = forward_val(&mut engine, "c").unwrap();
let d = forward(&mut engine, "d");
assert_eq!(a.to_number(), 0.643_501_108_793_284_3);
assert_eq!(b, String::from("NaN"));
assert_eq!(c.to_number(), 0_f64);
assert_eq!(d, String::from("NaN"));
assert_eq!(a.to_number(&mut engine).unwrap(), 0.643_501_108_793_284_3);
assert_eq!(b, "NaN");
assert_eq!(c.to_number(&mut engine).unwrap(), 0_f64);
assert_eq!(d, "NaN");
}
#[test]
@ -61,9 +61,9 @@ fn acosh() {
let b = forward(&mut engine, "b");
let c = forward(&mut engine, "c");
assert_eq!(a.to_number(), 1.316_957_896_924_816_6);
assert_eq!(b, String::from("NaN"));
assert_eq!(c, String::from("NaN"));
assert_eq!(a.to_number(&mut engine).unwrap(), 1.316_957_896_924_816_6);
assert_eq!(b, "NaN");
assert_eq!(c, "NaN");
}
#[test]
@ -80,7 +80,7 @@ fn asin() {
let a = forward_val(&mut engine, "a").unwrap();
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"));
}
@ -98,8 +98,8 @@ fn asinh() {
let a = forward_val(&mut engine, "a").unwrap();
let b = forward_val(&mut engine, "b").unwrap();
assert_eq!(a.to_number(), 0.881_373_587_019_542_9);
assert_eq!(b.to_number(), 0_f64);
assert_eq!(a.to_number(&mut engine).unwrap(), 0.881_373_587_019_542_9);
assert_eq!(b.to_number(&mut engine).unwrap(), 0_f64);
}
#[test]
@ -118,9 +118,9 @@ fn atan() {
let b = forward_val(&mut engine, "b").unwrap();
let c = forward_val(&mut engine, "c").unwrap();
assert_eq!(a.to_number(), f64::consts::FRAC_PI_4);
assert_eq!(b.to_number(), 0_f64);
assert_eq!(c.to_number(), f64::from(-0));
assert_eq!(a.to_number(&mut engine).unwrap(), f64::consts::FRAC_PI_4);
assert_eq!(b.to_number(&mut engine).unwrap(), 0_f64);
assert_eq!(c.to_number(&mut engine).unwrap(), f64::from(-0));
}
#[test]
@ -137,8 +137,8 @@ fn atan2() {
let a = forward_val(&mut engine, "a").unwrap();
let b = forward_val(&mut engine, "b").unwrap();
assert_eq!(a.to_number(), 1.405_647_649_380_269_9);
assert_eq!(b.to_number(), 0.165_148_677_414_626_83);
assert_eq!(a.to_number(&mut engine).unwrap(), 1.405_647_649_380_269_9);
assert_eq!(b.to_number(&mut engine).unwrap(), 0.165_148_677_414_626_83);
}
#[test]
@ -157,9 +157,9 @@ fn cbrt() {
let b = forward_val(&mut engine, "b").unwrap();
let c = forward_val(&mut engine, "c").unwrap();
assert_eq!(a.to_number(), 4_f64);
assert_eq!(b.to_number(), -1_f64);
assert_eq!(c.to_number(), 1_f64);
assert_eq!(a.to_number(&mut engine).unwrap(), 4_f64);
assert_eq!(b.to_number(&mut engine).unwrap(), -1_f64);
assert_eq!(c.to_number(&mut engine).unwrap(), 1_f64);
}
#[test]
@ -178,9 +178,9 @@ fn ceil() {
let b = forward_val(&mut engine, "b").unwrap();
let c = forward_val(&mut engine, "c").unwrap();
assert_eq!(a.to_number(), 2_f64);
assert_eq!(b.to_number(), 4_f64);
assert_eq!(c.to_number(), -7_f64);
assert_eq!(a.to_number(&mut engine).unwrap(), 2_f64);
assert_eq!(b.to_number(&mut engine).unwrap(), 4_f64);
assert_eq!(c.to_number(&mut engine).unwrap(), -7_f64);
}
#[test]
@ -210,14 +210,14 @@ fn clz32() {
let g = forward_val(&mut engine, "g").unwrap();
let h = forward_val(&mut engine, "h").unwrap();
assert_eq!(a.to_number(), 32_f64);
assert_eq!(b.to_number(), 32_f64);
assert_eq!(c.to_number(), 0_f64);
assert_eq!(d.to_number(), 31_f64);
assert_eq!(e.to_number(), 1_f64);
assert_eq!(f.to_number(), 32_f64);
assert_eq!(g.to_number(), 31_f64);
assert_eq!(h.to_number(), 32_f64);
assert_eq!(a.to_number(&mut engine).unwrap(), 32_f64);
assert_eq!(b.to_number(&mut engine).unwrap(), 32_f64);
assert_eq!(c.to_number(&mut engine).unwrap(), 0_f64);
assert_eq!(d.to_number(&mut engine).unwrap(), 31_f64);
assert_eq!(e.to_number(&mut engine).unwrap(), 1_f64);
assert_eq!(f.to_number(&mut engine).unwrap(), 32_f64);
assert_eq!(g.to_number(&mut engine).unwrap(), 31_f64);
assert_eq!(h.to_number(&mut engine).unwrap(), 32_f64);
}
#[test]
@ -234,8 +234,8 @@ fn cos() {
let a = forward_val(&mut engine, "a").unwrap();
let b = forward_val(&mut engine, "b").unwrap();
assert_eq!(a.to_number(), 1_f64);
assert_eq!(b.to_number(), 0.540_302_305_868_139_8);
assert_eq!(a.to_number(&mut engine).unwrap(), 1_f64);
assert_eq!(b.to_number(&mut engine).unwrap(), 0.540_302_305_868_139_8);
}
#[test]
@ -254,9 +254,9 @@ fn cosh() {
let b = forward_val(&mut engine, "b").unwrap();
let c = forward_val(&mut engine, "c").unwrap();
assert_eq!(a.to_number(), 1_f64);
assert_eq!(b.to_number(), 1.543_080_634_815_243_7);
assert_eq!(c.to_number(), 1.543_080_634_815_243_7);
assert_eq!(a.to_number(&mut engine).unwrap(), 1_f64);
assert_eq!(b.to_number(&mut engine).unwrap(), 1.543_080_634_815_243_7);
assert_eq!(c.to_number(&mut engine).unwrap(), 1.543_080_634_815_243_7);
}
#[test]
@ -275,9 +275,9 @@ fn exp() {
let b = forward_val(&mut engine, "b").unwrap();
let c = forward_val(&mut engine, "c").unwrap();
assert_eq!(a.to_number(), 1_f64);
assert_eq!(b.to_number(), 0.367_879_441_171_442_33);
assert_eq!(c.to_number(), 7.389_056_098_930_65);
assert_eq!(a.to_number(&mut engine).unwrap(), 1_f64);
assert_eq!(b.to_number(&mut engine).unwrap(), 0.367_879_441_171_442_33);
assert_eq!(c.to_number(&mut engine).unwrap(), 7.389_056_098_930_65);
}
#[test]
@ -305,10 +305,10 @@ fn expm1() {
assert_eq!(a, String::from("NaN"));
assert_eq!(b, String::from("NaN"));
assert_eq!(c.to_number(), 1.718_281_828_459_045);
assert_eq!(d.to_number(), -0.632_120_558_828_557_7);
assert_eq!(e.to_number(), 0_f64);
assert_eq!(f.to_number(), 6.389_056_098_930_65);
assert_eq!(c.to_number(&mut engine).unwrap(), 1.718_281_828_459_045);
assert_eq!(d.to_number(&mut engine).unwrap(), -0.632_120_558_828_557_7);
assert_eq!(e.to_number(&mut engine).unwrap(), 0_f64);
assert_eq!(f.to_number(&mut engine).unwrap(), 6.389_056_098_930_65);
}
#[test]
@ -327,9 +327,9 @@ fn floor() {
let b = forward_val(&mut engine, "b").unwrap();
let c = forward_val(&mut engine, "c").unwrap();
assert_eq!(a.to_number(), 1_f64);
assert_eq!(b.to_number(), -4_f64);
assert_eq!(c.to_number(), 3_f64);
assert_eq!(a.to_number(&mut engine).unwrap(), 1_f64);
assert_eq!(b.to_number(&mut engine).unwrap(), -4_f64);
assert_eq!(c.to_number(&mut engine).unwrap(), 3_f64);
}
#[test]
@ -359,10 +359,10 @@ fn fround() {
assert_eq!(a, String::from("NaN"));
assert_eq!(b, String::from("Infinity"));
assert_eq!(c.to_number(), 5f64);
assert_eq!(d.to_number(), 5.5f64);
assert_eq!(e.to_number(), 5.050_000_190_734_863);
assert_eq!(f.to_number(), -5.050_000_190_734_863);
assert_eq!(c.to_number(&mut engine).unwrap(), 5f64);
assert_eq!(d.to_number(&mut engine).unwrap(), 5.5f64);
assert_eq!(e.to_number(&mut engine).unwrap(), 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"));
}
@ -391,13 +391,13 @@ fn hypot() {
let f = forward_val(&mut engine, "f").unwrap();
let g = forward_val(&mut engine, "g").unwrap();
assert_eq!(a.to_number(), 0f64);
assert_eq!(b.to_number(), 5f64);
assert_eq!(c.to_number(), 13f64);
assert_eq!(d.to_number(), 7.071_067_811_865_475_5);
assert_eq!(e.to_number(), 8.774964387392123);
assert!(f.to_number().is_infinite());
assert_eq!(g.to_number(), 12f64);
assert_eq!(a.to_number(&mut engine).unwrap(), 0f64);
assert_eq!(b.to_number(&mut engine).unwrap(), 5f64);
assert_eq!(c.to_number(&mut engine).unwrap(), 13f64);
assert_eq!(d.to_number(&mut engine).unwrap(), 7.071_067_811_865_475_5);
assert_eq!(e.to_number(&mut engine).unwrap(), 8.774964387392123);
assert!(f.to_number(&mut engine).unwrap().is_infinite());
assert_eq!(g.to_number(&mut engine).unwrap(), 12f64);
}
#[test]
@ -423,12 +423,12 @@ fn imul() {
let e = forward_val(&mut engine, "e").unwrap();
let f = forward_val(&mut engine, "f").unwrap();
assert_eq!(a.to_number(), 12f64);
assert_eq!(b.to_number(), -60f64);
assert_eq!(c.to_number(), -5f64);
assert_eq!(d.to_number(), -10f64);
assert_eq!(e.to_number(), 0f64);
assert_eq!(f.to_number(), 0f64);
assert_eq!(a.to_number(&mut engine).unwrap(), 12f64);
assert_eq!(b.to_number(&mut engine).unwrap(), -60f64);
assert_eq!(c.to_number(&mut engine).unwrap(), -5f64);
assert_eq!(d.to_number(&mut engine).unwrap(), -10f64);
assert_eq!(e.to_number(&mut engine).unwrap(), 0f64);
assert_eq!(f.to_number(&mut engine).unwrap(), 0f64);
}
#[test]
@ -447,8 +447,8 @@ fn log() {
let b = forward_val(&mut engine, "b").unwrap();
let c = forward(&mut engine, "c");
assert_eq!(a.to_number(), 0_f64);
assert_eq!(b.to_number(), f64::consts::LN_10);
assert_eq!(a.to_number(&mut engine).unwrap(), 0_f64);
assert_eq!(b.to_number(&mut engine).unwrap(), f64::consts::LN_10);
assert_eq!(c, String::from("NaN"));
}
@ -477,9 +477,9 @@ fn log1p() {
let f = forward(&mut engine, "f");
let g = forward(&mut engine, "g");
assert_eq!(a.to_number(), f64::consts::LN_2);
assert_eq!(b.to_number(), 0f64);
assert_eq!(c.to_number(), -36.736_800_569_677_1);
assert_eq!(a.to_number(&mut engine).unwrap(), f64::consts::LN_2);
assert_eq!(b.to_number(&mut engine).unwrap(), 0f64);
assert_eq!(c.to_number(&mut engine).unwrap(), -36.736_800_569_677_1);
assert_eq!(d, "-Infinity");
assert_eq!(e, String::from("NaN"));
assert_eq!(f, String::from("NaN"));
@ -502,8 +502,8 @@ fn log10() {
let b = forward_val(&mut engine, "b").unwrap();
let c = forward(&mut engine, "c");
assert_eq!(a.to_number(), f64::consts::LOG10_2);
assert_eq!(b.to_number(), 0_f64);
assert_eq!(a.to_number(&mut engine).unwrap(), f64::consts::LOG10_2);
assert_eq!(b.to_number(&mut engine).unwrap(), 0_f64);
assert_eq!(c, String::from("NaN"));
}
@ -523,8 +523,8 @@ fn log2() {
let b = forward_val(&mut engine, "b").unwrap();
let c = forward(&mut engine, "c");
assert_eq!(a.to_number(), 1.584_962_500_721_156);
assert_eq!(b.to_number(), 0_f64);
assert_eq!(a.to_number(&mut engine).unwrap(), 1.584_962_500_721_156);
assert_eq!(b.to_number(&mut engine).unwrap(), 0_f64);
assert_eq!(c, String::from("NaN"));
}
@ -544,9 +544,9 @@ fn max() {
let b = forward_val(&mut engine, "b").unwrap();
let c = forward_val(&mut engine, "c").unwrap();
assert_eq!(a.to_number(), 20_f64);
assert_eq!(b.to_number(), -10_f64);
assert_eq!(c.to_number(), 20_f64);
assert_eq!(a.to_number(&mut engine).unwrap(), 20_f64);
assert_eq!(b.to_number(&mut engine).unwrap(), -10_f64);
assert_eq!(c.to_number(&mut engine).unwrap(), 20_f64);
}
#[test]
@ -565,9 +565,9 @@ fn min() {
let b = forward_val(&mut engine, "b").unwrap();
let c = forward_val(&mut engine, "c").unwrap();
assert_eq!(a.to_number(), 10_f64);
assert_eq!(b.to_number(), -20_f64);
assert_eq!(c.to_number(), -10_f64);
assert_eq!(a.to_number(&mut engine).unwrap(), 10_f64);
assert_eq!(b.to_number(&mut engine).unwrap(), -20_f64);
assert_eq!(c.to_number(&mut engine).unwrap(), -10_f64);
}
#[test]
@ -588,10 +588,10 @@ fn pow() {
let c = forward_val(&mut engine, "c").unwrap();
let d = forward_val(&mut engine, "d").unwrap();
assert_eq!(a.to_number(), 1_024_f64);
assert_eq!(b.to_number(), 49_f64);
assert_eq!(c.to_number(), 2.0);
assert_eq!(d.to_number(), 0.020_408_163_265_306_12);
assert_eq!(a.to_number(&mut engine).unwrap(), 1_024_f64);
assert_eq!(b.to_number(&mut engine).unwrap(), 49_f64);
assert_eq!(c.to_number(&mut engine).unwrap(), 2.0);
assert_eq!(d.to_number(&mut engine).unwrap(), 0.020_408_163_265_306_12);
}
#[test]
@ -608,8 +608,8 @@ fn round() {
let a = forward_val(&mut engine, "a").unwrap();
let b = forward_val(&mut engine, "b").unwrap();
assert_eq!(a.to_number(), 21.0);
assert_eq!(b.to_number(), -20.0);
assert_eq!(a.to_number(&mut engine).unwrap(), 21.0);
assert_eq!(b.to_number(&mut engine).unwrap(), -20.0);
}
#[test]
@ -628,9 +628,9 @@ fn sign() {
let b = forward_val(&mut engine, "b").unwrap();
let c = forward_val(&mut engine, "c").unwrap();
assert_eq!(a.to_number(), 1_f64);
assert_eq!(b.to_number(), -1_f64);
assert_eq!(c.to_number(), 0_f64);
assert_eq!(a.to_number(&mut engine).unwrap(), 1_f64);
assert_eq!(b.to_number(&mut engine).unwrap(), -1_f64);
assert_eq!(c.to_number(&mut engine).unwrap(), 0_f64);
}
#[test]
@ -647,8 +647,8 @@ fn sin() {
let a = forward_val(&mut engine, "a").unwrap();
let b = forward_val(&mut engine, "b").unwrap();
assert_eq!(a.to_number(), 0_f64);
assert_eq!(b.to_number(), 0.841_470_984_807_896_5);
assert_eq!(a.to_number(&mut engine).unwrap(), 0_f64);
assert_eq!(b.to_number(&mut engine).unwrap(), 0.841_470_984_807_896_5);
}
#[test]
@ -665,8 +665,8 @@ fn sinh() {
let a = forward_val(&mut engine, "a").unwrap();
let b = forward_val(&mut engine, "b").unwrap();
assert_eq!(a.to_number(), 0_f64);
assert_eq!(b.to_number(), 1.175_201_193_643_801_4);
assert_eq!(a.to_number(&mut engine).unwrap(), 0_f64);
assert_eq!(b.to_number(&mut engine).unwrap(), 1.175_201_193_643_801_4);
}
#[test]
@ -685,9 +685,9 @@ fn sqrt() {
let b = forward_val(&mut engine, "b").unwrap();
let c = forward_val(&mut engine, "c").unwrap();
assert_eq!(a.to_number(), 0_f64);
assert_eq!(b.to_number(), f64::consts::SQRT_2);
assert_eq!(c.to_number(), 3_f64);
assert_eq!(a.to_number(&mut engine).unwrap(), 0_f64);
assert_eq!(b.to_number(&mut engine).unwrap(), f64::consts::SQRT_2);
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
@ -721,8 +721,8 @@ fn tanh() {
let a = forward_val(&mut engine, "a").unwrap();
let b = forward_val(&mut engine, "b").unwrap();
assert_eq!(a.to_number(), 0.761_594_155_955_764_9);
assert_eq!(b.to_number(), 0_f64);
assert_eq!(a.to_number(&mut engine).unwrap(), 0.761_594_155_955_764_9);
assert_eq!(b.to_number(&mut engine).unwrap(), 0_f64);
}
#[test]
@ -739,6 +739,6 @@ fn trunc() {
let a = forward_val(&mut engine, "a").unwrap();
let b = forward_val(&mut engine, "b").unwrap();
assert_eq!(a.to_number(), 13_f64);
assert_eq!(b.to_number(), 0_f64);
assert_eq!(a.to_number(&mut engine).unwrap(), 13_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
pub(crate) fn make_number(this: &Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue {
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,
};
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 {
let this_num = Self::this_number_value(this, ctx)?;
let precision = match args.get(0) {
Some(n) => match n.to_integer() {
x if x > 0 => n.to_integer() as usize,
Some(n) => match n.to_integer(ctx)? as i32 {
x if x > 0 => n.to_integer(ctx)? as usize,
_ => 0,
},
None => 0,
@ -227,8 +227,8 @@ impl Number {
let this_num = Self::this_number_value(this, ctx)?;
let _num_str_len = format!("{}", this_num).len();
let _precision = match args.get(0) {
Some(n) => match n.to_integer() {
x if x > 0 => n.to_integer() as usize,
Some(n) => match n.to_integer(ctx)? as i32 {
x if x > 0 => n.to_integer(ctx)? as usize,
_ => 0,
},
None => 0,
@ -383,7 +383,11 @@ impl Number {
// 2. If radix is undefined, let radixNumber be 10.
// 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.
if radix < 2 || radix > 36 {
@ -563,8 +567,8 @@ impl Number {
args: &[Value],
ctx: &mut Interpreter,
) -> ResultValue {
if let Some(val) = args.get(0) {
let number = ctx.to_number(val)?;
if let Some(value) = args.get(0) {
let number = value.to_number(ctx)?;
Ok(number.is_finite().into())
} else {
Ok(false.into())
@ -590,8 +594,8 @@ impl Number {
args: &[Value],
ctx: &mut Interpreter,
) -> ResultValue {
if let Some(val) = args.get(0) {
let number = ctx.to_number(val)?;
if let Some(value) = args.get(0) {
let number = value.to_number(ctx)?;
Ok(number.is_nan().into())
} else {
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 from_exp = forward_val(&mut engine, "from_exp").unwrap();
assert_eq!(default_zero.to_number(), 0_f64);
assert_eq!(int_one.to_number(), 1_f64);
assert_eq!(float_two.to_number(), 2.1);
assert_eq!(str_three.to_number(), 3.2);
assert_eq!(bool_one.to_number(), 1_f64);
assert!(invalid_nan.to_number().is_nan());
assert_eq!(bool_zero.to_number(), 0_f64);
assert_eq!(from_exp.to_number(), 234_f64);
assert_eq!(default_zero.to_number(&mut engine).unwrap(), 0_f64);
assert_eq!(int_one.to_number(&mut engine).unwrap(), 1_f64);
assert_eq!(float_two.to_number(&mut engine).unwrap(), 2.1);
assert_eq!(str_three.to_number(&mut engine).unwrap(), 3.2);
assert_eq!(bool_one.to_number(&mut engine).unwrap(), 1_f64);
assert!(invalid_nan.to_number(&mut engine).unwrap().is_nan());
assert_eq!(bool_zero.to_number(&mut engine).unwrap(), 0_f64);
assert_eq!(from_exp.to_number(&mut engine).unwrap(), 234_f64);
}
#[test]
@ -396,11 +396,11 @@ fn value_of() {
let exp_val = forward_val(&mut engine, "exp_val").unwrap();
let neg_val = forward_val(&mut engine, "neg_val").unwrap();
assert_eq!(default_val.to_number(), 0_f64);
assert_eq!(int_val.to_number(), 123_f64);
assert_eq!(float_val.to_number(), 1.234);
assert_eq!(exp_val.to_number(), 12_000_f64);
assert_eq!(neg_val.to_number(), -12_000_f64);
assert_eq!(default_val.to_number(&mut engine).unwrap(), 0_f64);
assert_eq!(int_val.to_number(&mut engine).unwrap(), 123_f64);
assert_eq!(float_val.to_number(&mut engine).unwrap(), 1.234);
assert_eq!(exp_val.to_number(&mut engine).unwrap(), 12_000_f64);
assert_eq!(neg_val.to_number(&mut engine).unwrap(), -12_000_f64);
}
#[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!(
"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
pub fn define_property(_: &Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue {
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"));
obj.set_property(prop, desc);
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
/// [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 {
Ok(Value::from(this.to_string()))
// FIXME: it should not display the object.
Ok(Value::from(this.display().to_string()))
}
/// `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() {
None
} 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
.as_object()
@ -565,11 +566,11 @@ pub fn property_is_enumerable(this: &Value, args: &[Value], ctx: &mut Interprete
Some(key) => key,
};
let property_key = ctx.to_property_key(key)?;
let own_property = ctx.to_object(this).map(|obj| {
let key = key.to_property_key(ctx)?;
let own_property = this.to_object(ctx).map(|obj| {
obj.as_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| {

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

@ -289,8 +289,11 @@ impl RegExp {
/// [spec]: https://tc39.es/ecma262/#sec-regexp.prototype.test
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp/test
pub(crate) fn test(this: &Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue {
let arg_str = ctx.to_string(args.get(0).expect("could not get argument"))?;
let mut last_index = usize::from(&this.get_field("lastIndex"));
let arg_str = args
.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 regex = object.as_regexp().unwrap();
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
/// [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 {
let arg_str = ctx.to_string(args.get(0).expect("could not get argument"))?;
let mut last_index = usize::from(&this.get_field("lastIndex"));
let arg_str = args
.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 regex = object.as_regexp().unwrap();
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
// to its Javascript Identifier (global constructor method name)
let string = match args.get(0) {
Some(ref value) => ctx.to_string(value)?,
Some(ref value) => value.to_string(ctx)?,
None => RcString::default(),
};
@ -111,11 +111,11 @@ impl String {
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.
// Then we convert it into a Rust String by wrapping it in from_value
let primitive_val = ctx.to_string(this)?;
let pos = i32::from(
args.get(0)
.expect("failed to get argument for String method"),
);
let primitive_val = this.to_string(ctx)?;
let pos = args
.get(0)
.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
// unicode code points
@ -153,15 +153,15 @@ impl String {
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.
// 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
// Note that this is an O(N) operation (because UTF-8 is complex) while getting the number of bytes is an O(1) operation.
let length = primitive_val.chars().count();
let pos = i32::from(
args.get(0)
.expect("failed to get argument for String method"),
);
let pos = args
.get(0)
.expect("failed to get argument for String method")
.to_integer(ctx)? as i32;
if pos >= length as i32 || pos < 0 {
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
pub(crate) fn concat(this: &Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue {
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 {
string.push_str(&ctx.to_string(arg)?);
string.push_str(&arg.to_string(ctx)?);
}
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
pub(crate) fn repeat(this: &Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue {
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) {
let n = ctx.to_integer(arg)?;
let n = arg.to_integer(ctx)?;
if n < 0.0 {
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 {
// First we get it the actual string a private field stored on the object only the engine has access to.
// Then we convert it into a Rust String by wrapping it in from_value
let primitive_val = ctx.to_string(this)?;
let primitive_val = this.to_string(ctx)?;
let start = i32::from(
args.get(0)
.expect("failed to get argument for String method"),
);
let start = args
.get(0)
.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
// 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 {
// First we get it the actual string a private field stored on the object only the engine has access to.
// Then we convert it into a Rust String by wrapping it in from_value
let primitive_val = ctx.to_string(this)?;
let primitive_val = this.to_string(ctx)?;
// TODO: Should throw TypeError if pattern is regular expression
let search_string = ctx.to_string(
args.get(0)
.expect("failed to get argument for String method"),
)?;
let search_string = args
.get(0)
.expect("failed to get argument for String method")
.to_string(ctx)?;
let length = primitive_val.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 {
0
} 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);
@ -339,13 +342,13 @@ impl String {
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.
// 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
let search_string = ctx.to_string(
args.get(0)
.expect("failed to get argument for String method"),
)?;
let search_string = args
.get(0)
.expect("failed to get argument for String method")
.to_string(ctx)?;
let length = primitive_val.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 {
length
} 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);
@ -383,13 +388,13 @@ impl String {
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.
// 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
let search_string = ctx.to_string(
args.get(0)
.expect("failed to get argument for String method"),
)?;
let search_string = args
.get(0)
.expect("failed to get argument for String method")
.to_string(ctx)?;
let length = primitive_val.chars().count() as i32;
@ -397,7 +402,9 @@ impl String {
let position = if args.len() < 2 {
0
} 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);
@ -442,7 +449,7 @@ impl String {
/// [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 {
// TODO: Support Symbol replacer
let primitive_val = ctx.to_string(this)?;
let primitive_val = this.to_string(ctx)?;
if args.is_empty() {
return Ok(Value::from(primitive_val));
}
@ -515,7 +522,7 @@ impl String {
let result = ctx.call(&replace_object, this, &results).unwrap();
ctx.to_string(&result)?.to_string()
result.to_string(ctx)?.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
pub(crate) fn index_of(this: &Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue {
let this = ctx.require_object_coercible(this)?;
let string = ctx.to_string(this)?;
let string = this.to_string(ctx)?;
let search_string =
ctx.to_string(&args.get(0).cloned().unwrap_or_else(Value::undefined))?;
let search_string = args
.get(0)
.cloned()
.unwrap_or_else(Value::undefined)
.to_string(ctx)?;
let length = string.chars().count();
let start = args
.get(1)
.map(|position| ctx.to_integer(position))
.map(|position| position.to_integer(ctx))
.transpose()?
.map_or(0, |position| position.max(0.0).min(length as f64) as usize);
@ -589,15 +599,18 @@ impl String {
ctx: &mut Interpreter,
) -> ResultValue {
let this = ctx.require_object_coercible(this)?;
let string = ctx.to_string(this)?;
let string = this.to_string(ctx)?;
let search_string =
ctx.to_string(&args.get(0).cloned().unwrap_or_else(Value::undefined))?;
let search_string = args
.get(0)
.cloned()
.unwrap_or_else(Value::undefined)
.to_string(ctx)?;
let length = string.chars().count();
let start = args
.get(1)
.map(|position| ctx.to_integer(position))
.map(|position| position.to_integer(ctx))
.transpose()?
.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
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)?;
RegExp::r#match(&re, ctx.to_string(this)?, ctx)
RegExp::r#match(&re, this.to_string(ctx)?, ctx)
}
/// Abstract method `StringPad`.
@ -677,16 +690,16 @@ impl String {
/// [spec]: https://tc39.es/ecma262/#sec-string.prototype.padend
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/padEnd
pub(crate) fn pad_end(this: &Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue {
let primitive = ctx.to_string(this)?;
let primitive = this.to_string(ctx)?;
if args.is_empty() {
return Err(Value::from("padEnd requires maxLength argument"));
}
let max_length = i32::from(
args.get(0)
.expect("failed to get argument for String method"),
);
let max_length = args
.get(0)
.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)
}
@ -704,16 +717,16 @@ impl String {
/// [spec]: https://tc39.es/ecma262/#sec-string.prototype.padstart
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/padStart
pub(crate) fn pad_start(this: &Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue {
let primitive = ctx.to_string(this)?;
let primitive = this.to_string(ctx)?;
if args.is_empty() {
return Err(Value::from("padStart requires maxLength argument"));
}
let max_length = i32::from(
args.get(0)
.expect("failed to get argument for String method"),
);
let max_length = args
.get(0)
.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)
}
@ -752,7 +765,7 @@ impl String {
/// [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 {
let this = ctx.require_object_coercible(this)?;
let string = ctx.to_string(this)?;
let string = this.to_string(ctx)?;
Ok(Value::from(
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
pub(crate) fn trim_start(this: &Value, _: &[Value], ctx: &mut Interpreter) -> ResultValue {
let this = ctx.require_object_coercible(this)?;
let string = ctx.to_string(this)?;
let string = this.to_string(ctx)?;
Ok(Value::from(
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
pub(crate) fn trim_end(this: &Value, _: &[Value], ctx: &mut Interpreter) -> ResultValue {
let this = ctx.require_object_coercible(this)?;
let string = ctx.to_string(this)?;
let string = this.to_string(ctx)?;
Ok(Value::from(
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 {
// First we get it the actual string a private field stored on the object only the engine has access to.
// Then we convert it into a Rust String by wrapping it in from_value
let this_str = ctx.to_string(this)?;
let this_str = this.to_string(ctx)?;
// The Rust String is mapped to uppercase using the builtin .to_lowercase().
// There might be corner cases where it does not behave exactly like Javascript expects
Ok(Value::from(this_str.to_lowercase()))
@ -834,7 +847,7 @@ impl String {
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.
// 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().
// There might be corner cases where it does not behave exactly like Javascript expects
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 {
// First we get it the actual string a private field stored on the object only the engine has access to.
// Then we convert it into a Rust String by wrapping it in from_value
let primitive_val = ctx.to_string(this)?;
let primitive_val = this.to_string(ctx)?;
// If no args are specified, start is 'undefined', defaults to 0
let start = if args.is_empty() {
0
} else {
i32::from(
args.get(0)
.expect("failed to get argument for String method"),
)
args.get(0)
.expect("failed to get argument for String method")
.to_integer(ctx)? 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
let end = if args.len() < 2 {
length
} 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
// 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 {
// First we get it the actual string a private field stored on the object only the engine has access to.
// Then we convert it into a Rust String by wrapping it in from_value
let primitive_val = ctx.to_string(this)?;
let primitive_val = this.to_string(ctx)?;
// If no args are specified, start is 'undefined', defaults to 0
let mut start = if args.is_empty() {
0
} else {
i32::from(
args.get(0)
.expect("failed to get argument for String method"),
)
args.get(0)
.expect("failed to get argument for String method")
.to_integer(ctx)? as i32
};
let length = primitive_val.chars().count() as i32;
// 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 {
i32::max_value()
} 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 < 0 {
@ -977,7 +992,7 @@ impl String {
if arg.is_null() {
RegExp::make_regexp(
&Value::from(Object::default()),
&[Value::from(ctx.to_string(arg)?), Value::from("g")],
&[Value::from(arg.to_string(ctx)?), Value::from("g")],
ctx,
)
} 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.

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
pub(crate) fn call(_: &Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue {
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,
};

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

@ -21,5 +21,5 @@ fn print_symbol_expect_description() {
"#;
eprintln!("{}", forward(&mut engine, init));
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;
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)
} else {
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 {
#[inline]
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 {
fn from(value: BigInt) -> Self {
Value::bigint(value)
@ -131,11 +119,6 @@ impl From<usize> for Value {
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 {
fn from(value: bool) -> Self {

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

@ -1,5 +1,11 @@
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
/// Can be used to print both properties and internal slots
/// All of the overloads take:
@ -34,7 +40,7 @@ macro_rules! print_obj_value {
vec![format!(
"{:>width$}: {}",
"__proto__",
object.prototype(),
object.prototype().display(),
width = $indent,
)]
}
@ -86,15 +92,16 @@ pub(crate) fn log_string_from(x: &Value, print_internals: bool, print_children:
}
}
ObjectData::Array => {
let len = i32::from(
&v.borrow()
.properties()
.get("length")
.expect("Could not get Array's length property")
.value
.clone()
.expect("Could not borrow value"),
);
let len = v
.borrow()
.properties()
.get("length")
.expect("Could not get Array's length property")
.value
.clone()
.expect("Could not borrow value")
.as_number()
.unwrap() as i32;
if print_children {
if len == 0 {
@ -126,15 +133,16 @@ pub(crate) fn log_string_from(x: &Value, print_internals: bool, print_children:
}
}
ObjectData::Map(ref map) => {
let size = i32::from(
&v.borrow()
.properties()
.get("size")
.unwrap()
.value
.clone()
.expect("Could not borrow value"),
);
let size = v
.borrow()
.properties()
.get("size")
.unwrap()
.value
.clone()
.expect("Could not borrow value")
.as_number()
.unwrap() as i32;
if size == 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(),
_ => 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() {
let name = v.get_field("name");
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)
} else {
// 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)
}
impl Display for Value {
impl Display for ValueDisplay<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Null => write!(f, "null"),
Self::Undefined => write!(f, "undefined"),
Self::Boolean(v) => write!(f, "{}", v),
Self::Symbol(ref symbol) => match symbol.description() {
match self.value {
Value::Null => write!(f, "null"),
Value::Undefined => write!(f, "undefined"),
Value::Boolean(v) => write!(f, "{}", v),
Value::Symbol(ref symbol) => match symbol.description() {
Some(description) => write!(f, "Symbol({})", description),
None => write!(f, "Symbol()"),
},
Self::String(ref v) => write!(f, "\"{}\"", v),
Self::Rational(v) => format_rational(*v, f),
Self::Object(_) => write!(f, "{}", log_string_from(self, true, true)),
Self::Integer(v) => write!(f, "{}", v),
Self::BigInt(ref num) => write!(f, "{}n", num),
Value::String(ref v) => write!(f, "\"{}\"", v),
Value::Rational(v) => format_rational(*v, f),
Value::Object(_) => write!(f, "{}", log_string_from(self.value, true, true)),
Value::Integer(v) => write!(f, "{}", v),
Value::BigInt(ref num) => write!(f, "{}n", num),
}
}
}

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

@ -1,7 +1,5 @@
use super::*;
use crate::{builtins::Number, exec::PreferredType, Interpreter};
use std::borrow::Borrow;
use crate::{builtins::Number, Interpreter};
impl Value {
/// Strict equality comparison.
@ -61,9 +59,9 @@ impl Value {
| (Self::String(_), Self::Rational(_))
| (Self::Rational(_), Self::Boolean(_))
| (Self::Integer(_), Self::Boolean(_)) => {
let a: &Value = self.borrow();
let b: &Value = other.borrow();
Number::equal(f64::from(a), f64::from(b))
let x = self.to_number(interpreter)?;
let y = other.to_number(interpreter)?;
Number::equal(x, y)
}
// 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.
(Self::Boolean(_), _) => {
return other.equals(&Value::from(self.to_integer()), interpreter)
}
(Self::Boolean(x), _) => return other.equals(&Value::from(*x as i32), interpreter),
// 9. If Type(y) is Boolean, return the result of the comparison x == ! ToNumber(y).
(_, Self::Boolean(_)) => {
return self.equals(&Value::from(other.to_integer()), interpreter)
}
(_, Self::Boolean(y)) => return self.equals(&Value::from(*y as i32), interpreter),
// 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).
(Self::Object(_), _) => {
let primitive = interpreter.to_primitive(self, PreferredType::Default)?;
let primitive = self.to_primitive(interpreter, PreferredType::Default)?;
return primitive.equals(other, interpreter);
}
// 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.
(_, Self::Object(_)) => {
let primitive = interpreter.to_primitive(other, PreferredType::Default)?;
let primitive = other.to_primitive(interpreter, PreferredType::Default)?;
return primitive.equals(self, interpreter);
}

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

@ -5,11 +5,12 @@
#[cfg(test)]
mod tests;
use super::number::{f64_to_int32, f64_to_uint32};
use crate::builtins::{
function::Function,
object::{GcObject, InternalState, InternalStateCell, Object, ObjectData, PROTOTYPE},
property::{Attribute, Property, PropertyKey},
BigInt, Symbol,
BigInt, Number, Symbol,
};
use crate::exec::Interpreter;
use crate::BoaProfiler;
@ -36,6 +37,7 @@ mod r#type;
pub use conversions::*;
pub(crate) use display::display_obj;
pub use display::ValueDisplay;
pub use equality::*;
pub use hash::*;
pub use operations::*;
@ -383,6 +385,15 @@ impl Value {
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.
#[inline]
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.
///
/// More information:
@ -650,7 +616,7 @@ impl Value {
if obj.borrow().is_array() {
if let Ok(num) = string.parse::<usize>() {
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 {
self.set_field("length", num + 1);
}
@ -704,6 +670,346 @@ impl Value {
new_func_val.set_field("length", Value::from(length));
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 {
@ -711,3 +1017,92 @@ impl Default for Value {
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 crate::builtins::number::{f64_to_int32, f64_to_uint32, Number};
use crate::exec::PreferredType;
use crate::builtins::{
number::{f64_to_int32, f64_to_uint32, Number},
value::PreferredType,
};
impl Value {
#[inline]
@ -12,22 +14,22 @@ impl Value {
(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::String(ref x), ref y) => Self::string(format!("{}{}", x, ctx.to_string(y)?)),
(ref x, Self::String(ref y)) => Self::string(format!("{}{}", ctx.to_string(x)?, y)),
(Self::String(ref x), ref y) => Self::string(format!("{}{}", x, y.to_string(ctx)?)),
(ref x, Self::String(ref y)) => Self::string(format!("{}{}", x.to_string(ctx)?, y)),
(Self::BigInt(ref n1), Self::BigInt(ref n2)) => {
Self::bigint(n1.as_inner().clone() + n2.as_inner().clone())
}
// Slow path:
(_, _) => match (
ctx.to_primitive(self, PreferredType::Default)?,
ctx.to_primitive(other, PreferredType::Default)?,
self.to_primitive(ctx, PreferredType::Default)?,
other.to_primitive(ctx, PreferredType::Default)?,
) {
(Self::String(ref x), ref y) => Self::string(format!("{}{}", x, ctx.to_string(y)?)),
(ref x, Self::String(ref y)) => Self::string(format!("{}{}", ctx.to_string(x)?, y)),
(x, y) => match (ctx.to_numeric(&x)?, ctx.to_numeric(&y)?) {
(Self::Rational(x), Self::Rational(y)) => Self::rational(x + y),
(Self::BigInt(ref n1), Self::BigInt(ref n2)) => {
(Self::String(ref x), ref y) => Self::string(format!("{}{}", x, y.to_string(ctx)?)),
(ref x, Self::String(ref y)) => Self::string(format!("{}{}", x.to_string(ctx)?, y)),
(x, y) => match (x.to_numeric(ctx)?, y.to_numeric(ctx)?) {
(Numeric::Number(x), Numeric::Number(y)) => Self::rational(x + y),
(Numeric::BigInt(ref n1), Numeric::BigInt(ref n2)) => {
Self::bigint(n1.as_inner().clone() + n2.as_inner().clone())
}
(_, _) => {
@ -54,9 +56,9 @@ impl Value {
}
// Slow path:
(_, _) => match (ctx.to_numeric(self)?, ctx.to_numeric(other)?) {
(Self::Rational(a), Self::Rational(b)) => Self::rational(a - b),
(Self::BigInt(ref a), Self::BigInt(ref b)) => {
(_, _) => match (self.to_numeric(ctx)?, other.to_numeric(ctx)?) {
(Numeric::Number(a), Numeric::Number(b)) => Self::rational(a - b),
(Numeric::BigInt(ref a), Numeric::BigInt(ref b)) => {
Self::bigint(a.as_inner().clone() - b.as_inner().clone())
}
(_, _) => {
@ -82,9 +84,9 @@ impl Value {
}
// Slow path:
(_, _) => match (ctx.to_numeric(self)?, ctx.to_numeric(other)?) {
(Self::Rational(a), Self::Rational(b)) => Self::rational(a * b),
(Self::BigInt(ref a), Self::BigInt(ref b)) => {
(_, _) => match (self.to_numeric(ctx)?, other.to_numeric(ctx)?) {
(Numeric::Number(a), Numeric::Number(b)) => Self::rational(a * b),
(Numeric::BigInt(ref a), Numeric::BigInt(ref b)) => {
Self::bigint(a.as_inner().clone() * b.as_inner().clone())
}
(_, _) => {
@ -110,9 +112,9 @@ impl Value {
}
// Slow path:
(_, _) => match (ctx.to_numeric(self)?, ctx.to_numeric(other)?) {
(Self::Rational(a), Self::Rational(b)) => Self::rational(a / b),
(Self::BigInt(ref a), Self::BigInt(ref b)) => {
(_, _) => match (self.to_numeric(ctx)?, other.to_numeric(ctx)?) {
(Numeric::Number(a), Numeric::Number(b)) => Self::rational(a / b),
(Numeric::BigInt(ref a), Numeric::BigInt(ref b)) => {
Self::bigint(a.as_inner().clone() / b.as_inner().clone())
}
(_, _) => {
@ -138,9 +140,9 @@ impl Value {
}
// Slow path:
(_, _) => match (ctx.to_numeric(self)?, ctx.to_numeric(other)?) {
(Self::Rational(a), Self::Rational(b)) => Self::rational(a % b),
(Self::BigInt(ref a), Self::BigInt(ref b)) => {
(_, _) => match (self.to_numeric(ctx)?, other.to_numeric(ctx)?) {
(Numeric::Number(a), Numeric::Number(b)) => Self::rational(a % b),
(Numeric::BigInt(ref a), Numeric::BigInt(ref b)) => {
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)),
// Slow path:
(_, _) => match (ctx.to_numeric(self)?, ctx.to_numeric(other)?) {
(Self::Rational(a), Self::Rational(b)) => Self::rational(a.powf(b)),
(Self::BigInt(ref a), Self::BigInt(ref b)) => {
(_, _) => match (self.to_numeric(ctx)?, other.to_numeric(ctx)?) {
(Numeric::Number(a), Numeric::Number(b)) => Self::rational(a.powf(b)),
(Numeric::BigInt(ref a), Numeric::BigInt(ref b)) => {
Self::bigint(a.as_inner().clone().pow(b))
}
(_, _) => {
@ -194,11 +196,11 @@ impl Value {
}
// Slow path:
(_, _) => match (ctx.to_numeric(self)?, ctx.to_numeric(other)?) {
(Self::Rational(a), Self::Rational(b)) => {
(_, _) => match (self.to_numeric(ctx)?, other.to_numeric(ctx)?) {
(Numeric::Number(a), Numeric::Number(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())
}
(_, _) => {
@ -226,11 +228,11 @@ impl Value {
}
// Slow path:
(_, _) => match (ctx.to_numeric(self)?, ctx.to_numeric(other)?) {
(Self::Rational(a), Self::Rational(b)) => {
(_, _) => match (self.to_numeric(ctx)?, other.to_numeric(ctx)?) {
(Numeric::Number(a), Numeric::Number(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())
}
(_, _) => {
@ -258,11 +260,11 @@ impl Value {
}
// Slow path:
(_, _) => match (ctx.to_numeric(self)?, ctx.to_numeric(other)?) {
(Self::Rational(a), Self::Rational(b)) => {
(_, _) => match (self.to_numeric(ctx)?, other.to_numeric(ctx)?) {
(Numeric::Number(a), Numeric::Number(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())
}
(_, _) => {
@ -294,11 +296,11 @@ impl Value {
}
// Slow path:
(_, _) => match (ctx.to_numeric(self)?, ctx.to_numeric(other)?) {
(Self::Rational(x), Self::Rational(y)) => {
(_, _) => match (self.to_numeric(ctx)?, other.to_numeric(ctx)?) {
(Numeric::Number(x), Numeric::Number(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())
}
(_, _) => {
@ -330,11 +332,11 @@ impl Value {
}
// Slow path:
(_, _) => match (ctx.to_numeric(self)?, ctx.to_numeric(other)?) {
(Self::Rational(x), Self::Rational(y)) => {
(_, _) => match (self.to_numeric(ctx)?, other.to_numeric(ctx)?) {
(Numeric::Number(x), Numeric::Number(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())
}
(_, _) => {
@ -364,11 +366,11 @@ impl Value {
}
// Slow path:
(_, _) => match (ctx.to_numeric(self)?, ctx.to_numeric(other)?) {
(Self::Rational(x), Self::Rational(y)) => {
(_, _) => match (self.to_numeric(ctx)?, other.to_numeric(ctx)?) {
(Numeric::Number(x), Numeric::Number(y)) => {
Self::rational(f64_to_uint32(x).wrapping_shr(f64_to_uint32(y)))
}
(Self::BigInt(_), Self::BigInt(_)) => {
(Numeric::BigInt(_), Numeric::BigInt(_)) => {
return ctx
.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 {
Ok(match *self {
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,
Err(_) => NAN,
}),
@ -402,8 +404,8 @@ impl Value {
}
#[inline]
pub fn not(&self, _: &mut Interpreter) -> ResultValue {
Ok(Self::boolean(!self.to_boolean()))
pub fn not(&self, _: &mut Interpreter) -> Result<bool, Value> {
Ok(!self.to_boolean())
}
/// Abstract relational comparison
@ -431,27 +433,27 @@ impl Value {
) -> Result<AbstractRelation, Value> {
Ok(match (self, other) {
// Fast path (for some common operations):
(Value::Integer(x), Value::Integer(y)) => (x < y).into(),
(Value::Integer(x), Value::Rational(y)) => Number::less_than(f64::from(*x), *y),
(Value::Rational(x), Value::Integer(y)) => Number::less_than(*x, f64::from(*y)),
(Value::Rational(x), Value::Rational(y)) => Number::less_than(*x, *y),
(Value::BigInt(ref x), Value::BigInt(ref y)) => (x < y).into(),
(Self::Integer(x), Self::Integer(y)) => (x < y).into(),
(Self::Integer(x), Self::Rational(y)) => Number::less_than(f64::from(*x), *y),
(Self::Rational(x), Self::Integer(y)) => Number::less_than(*x, f64::from(*y)),
(Self::Rational(x), Self::Rational(y)) => Number::less_than(*x, *y),
(Self::BigInt(ref x), Self::BigInt(ref y)) => (x < y).into(),
// Slow path:
(_, _) => {
let (px, py) = if left_first {
let px = ctx.to_primitive(self, PreferredType::Number)?;
let py = ctx.to_primitive(other, PreferredType::Number)?;
let px = self.to_primitive(ctx, PreferredType::Number)?;
let py = other.to_primitive(ctx, PreferredType::Number)?;
(px, py)
} else {
// NOTE: The order of evaluation needs to be reversed to preserve left to right evaluation.
let py = ctx.to_primitive(other, PreferredType::Number)?;
let px = ctx.to_primitive(self, PreferredType::Number)?;
let py = other.to_primitive(ctx, PreferredType::Number)?;
let px = self.to_primitive(ctx, PreferredType::Number)?;
(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()) {
return Ok(AbstractRelation::False);
}
@ -465,24 +467,24 @@ impl Value {
}
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) {
(*x.as_inner() < y).into()
} else {
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) {
(x < *y.as_inner()).into()
} else {
AbstractRelation::Undefined
}
}
(px, py) => match (ctx.to_numeric(&px)?, ctx.to_numeric(&py)?) {
(Value::Rational(x), Value::Rational(y)) => Number::less_than(x, y),
(Value::BigInt(ref x), Value::BigInt(ref y)) => (x < y).into(),
(Value::BigInt(ref x), Value::Rational(y)) => {
(px, py) => match (px.to_numeric(ctx)?, py.to_numeric(ctx)?) {
(Numeric::Number(x), Numeric::Number(y)) => Number::less_than(x, y),
(Numeric::BigInt(ref x), Numeric::BigInt(ref y)) => (x < y).into(),
(Numeric::BigInt(ref x), Numeric::Number(y)) => {
if y.is_nan() {
return Ok(AbstractRelation::Undefined);
}
@ -496,7 +498,7 @@ impl Value {
};
(*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() {
return Ok(AbstractRelation::Undefined);
}
@ -510,7 +512,6 @@ impl Value {
};
(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() {
let u = Value::Undefined;
assert_eq!(u.get_type(), Type::Undefined);
assert_eq!(u.to_string(), "undefined");
assert_eq!(u.display().to_string(), "undefined");
}
#[test]
@ -31,7 +31,7 @@ fn get_set_field() {
// Create string and convert it to a Value
let s = Value::from("bar");
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]
@ -216,7 +216,7 @@ fn get_types() {
#[test]
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(0.0), "0");
@ -254,7 +254,7 @@ fn add_number_and_number() {
let mut engine = Interpreter::new(realm);
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);
}
@ -264,7 +264,7 @@ fn add_number_and_string() {
let mut engine = Interpreter::new(realm);
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");
}
@ -274,7 +274,7 @@ fn add_string_and_string() {
let mut engine = Interpreter::new(realm);
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");
}
@ -284,7 +284,7 @@ fn add_number_object_and_number() {
let mut engine = Interpreter::new(realm);
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);
}
@ -294,7 +294,7 @@ fn add_number_object_and_string_object() {
let mut engine = Interpreter::new(realm);
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");
}
@ -304,7 +304,7 @@ fn sub_number_and_number() {
let mut engine = Interpreter::new(realm);
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);
}
@ -314,7 +314,7 @@ fn sub_number_object_and_number_object() {
let mut engine = Interpreter::new(realm);
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);
}
@ -324,7 +324,7 @@ fn sub_string_and_number_object() {
let mut engine = Interpreter::new(realm);
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());
}
@ -334,7 +334,7 @@ fn bitand_integer_and_integer() {
let mut engine = Interpreter::new(realm);
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);
}
@ -344,7 +344,7 @@ fn bitand_integer_and_rational() {
let mut engine = Interpreter::new(realm);
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);
}
@ -354,17 +354,18 @@ fn bitand_rational_and_rational() {
let mut engine = Interpreter::new(realm);
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);
}
#[test]
#[allow(clippy::float_cmp)]
fn pow_number_and_number() {
let realm = Realm::create();
let mut engine = Interpreter::new(realm);
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);
}
@ -374,7 +375,7 @@ fn pow_number_and_string() {
let mut engine = Interpreter::new(realm);
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());
}
@ -392,7 +393,7 @@ fn assign_pow_number_and_string() {
",
)
.unwrap();
let value = engine.to_number(&value).unwrap();
let value = value.to_number(&mut engine).unwrap();
assert!(value.is_nan());
}
@ -400,7 +401,7 @@ fn assign_pow_number_and_string() {
fn display_string() {
let s = String::from("Hello");
let v = Value::from(s);
assert_eq!(v.to_string(), "\"Hello\"");
assert_eq!(v.display().to_string(), "\"Hello\"");
}
#[test]
@ -409,7 +410,7 @@ fn display_array_string() {
let mut engine = Interpreter::new(realm);
let value = forward_val(&mut engine, "[\"Hello\"]").unwrap();
assert_eq!(value.to_string(), "[ \"Hello\" ]");
assert_eq!(value.display().to_string(), "[ \"Hello\" ]");
}
#[test]
@ -421,7 +422,7 @@ fn display_boolean_object() {
bool
"#;
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]
@ -433,7 +434,7 @@ fn display_number_object() {
num
"#;
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]
@ -445,7 +446,7 @@ fn display_negative_zero_object() {
num
"#;
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]
@ -459,7 +460,7 @@ fn display_object() {
"#;
let value = forward_val(&mut engine, d_obj).unwrap();
assert_eq!(
value.to_string(),
value.display().to_string(),
r#"{
a: "a",
__proto__: {

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

@ -11,17 +11,16 @@ impl Executable for Call {
let (this, func) = match self.expr() {
Node::GetConstField(ref get_const_field) => {
let mut obj = get_const_field.obj().run(interpreter)?;
if obj.get_type() != Type::Object || obj.get_type() != Type::Symbol {
obj = interpreter
.to_object(&obj)
.expect("failed to convert to object");
if obj.get_type() != Type::Object {
obj = obj.to_object(interpreter)?;
}
(obj.clone(), obj.get_field(get_const_field.field()))
}
Node::GetField(ref get_field) => {
let obj = get_field.obj().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(),

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

@ -7,8 +7,8 @@ use crate::{
impl Executable for GetConstField {
fn run(&self, interpreter: &mut Interpreter) -> ResultValue {
let mut obj = self.obj().run(interpreter)?;
if obj.get_type() != Type::Object || obj.get_type() != Type::Symbol {
obj = interpreter.to_object(&obj)?;
if obj.get_type() != Type::Object {
obj = obj.to_object(interpreter)?;
}
Ok(obj.get_field(self.field()))
@ -18,11 +18,11 @@ impl Executable for GetConstField {
impl Executable for GetField {
fn run(&self, interpreter: &mut Interpreter) -> ResultValue {
let mut obj = self.obj().run(interpreter)?;
if obj.get_type() != Type::Object || obj.get_type() != Type::Symbol {
obj = interpreter.to_object(&obj)?;
if obj.get_type() != Type::Object {
obj = obj.to_object(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::{
function::{Function as FunctionObject, FunctionBody, ThisMode},
number::{f64_to_int32, f64_to_uint32},
object::{Object, ObjectData, PROTOTYPE},
property::PropertyKey,
value::{RcBigInt, RcString, ResultValue, Type, Value},
BigInt, Console, Number,
value::{PreferredType, ResultValue, Type, Value},
Console,
},
realm::Realm,
syntax::ast::{
@ -39,9 +38,6 @@ use crate::{
},
BoaProfiler,
};
use std::borrow::Borrow;
use std::convert::TryFrom;
use std::ops::Deref;
pub trait Executable {
/// Runs this executable in the given executor.
@ -54,12 +50,6 @@ pub(crate) enum InterpreterState {
Return,
Break(Option<String>),
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum PreferredType {
String,
Number,
Default,
}
/// A Javascript intepreter
#[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.
///
/// 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>, ()> {
if let Value::Object(ref x) = value {
// Check if object is array
if let ObjectData::Array = x.deref().borrow().data {
let length = i32::from(&value.get_field("length"));
if let ObjectData::Array = x.borrow().data {
let length = value.get_field("length").as_number().unwrap() as i32;
let values = (0..length)
.map(|idx| value.get_field(idx.to_string()))
.collect();
return Ok(values);
}
// 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
.borrow()
.iter()
.map(|(key, value)| {
// Construct a new array containing the key-value pair
@ -417,7 +219,6 @@ impl Interpreter {
.environment
.get_binding_value("Array")
.expect("Array was not initialized")
.borrow()
.get_field(PROTOTYPE),
);
array.set_field("0", key);
@ -476,49 +277,6 @@ impl Interpreter {
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
pub(crate) fn has_property(&self, obj: &Value, key: &PropertyKey) -> bool {
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 {
match node {
Node::Identifier(ref name) => {
@ -629,7 +300,7 @@ impl Interpreter {
.set_field(get_const_field_node.field(), value)),
Node::GetField(ref get_field) => {
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))
}
_ => 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) => {
let object = get_field.obj().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());
}
_ => (),
@ -95,7 +95,7 @@ impl Executable for BinOp {
y.get_type().as_str()
));
}
let key = interpreter.to_property_key(&x)?;
let key = x.to_property_key(interpreter)?;
interpreter.has_property(&y, &key)
}
}))
@ -173,30 +173,34 @@ impl Executable for UnaryOp {
Ok(match self.op() {
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 => {
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
}
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 => {
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
}
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 => {
let num_v_a = x.to_number();
// NOTE: possible UB: https://github.com/rust-lang/rust/issues/10184
let num_v_a = x.to_number(interpreter)?;
Value::from(if num_v_a.is_nan() {
-1
} else {
// TODO: this is not spec compliant.
!(num_v_a as i32)
})
}
@ -211,7 +215,7 @@ impl Executable for UnaryOp {
Node::GetField(ref get_field) => {
let obj = get_field.obj().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));
}
Node::Identifier(_) => Value::boolean(false),

81
boa/src/exec/tests.rs

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

6
boa/src/lib.rs

@ -66,8 +66,10 @@ pub fn forward(engine: &mut Interpreter, src: &str) -> String {
Ok(res) => res,
Err(e) => return e,
};
expr.run(engine)
.map_or_else(|e| format!("Error: {}", e), |v| v.to_string())
expr.run(engine).map_or_else(
|e| format!("Error: {}", e.display()),
|v| v.display().to_string(),
)
}
/// 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 {
match forward_val(&mut engine, &buffer) {
Ok(v) => print!("{}", v),
Err(v) => eprint!("{}", v),
Ok(v) => print!("{}", v.display()),
Err(v) => eprint!("{}", v.display()),
}
}
}
@ -230,8 +230,10 @@ pub fn main() -> Result<(), std::io::Error> {
}
} else {
match forward_val(&mut engine, line.trim_end()) {
Ok(v) => println!("{}", v),
Err(v) => eprintln!("{}: {}", "Uncaught".red(), v.to_string().red()),
Ok(v) => println!("{}", v.display()),
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
expr.run(&mut engine)
.map_err(|e| JsValue::from(format!("Error: {}", e)))
.map(|v| v.to_string())
.map_err(|e| JsValue::from(format!("Error: {}", e.display())))
.map(|v| v.display().to_string())
}

Loading…
Cancel
Save