Browse Source

Specification compliant `ToString` (`to_string`) (#425)

pull/438/head
HalidOdat 5 years ago committed by GitHub
parent
commit
5e71718928
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 26
      boa/src/builtins/array/mod.rs
  2. 21
      boa/src/builtins/bigint/mod.rs
  3. 102
      boa/src/builtins/console/mod.rs
  4. 51
      boa/src/builtins/console/tests.rs
  5. 29
      boa/src/builtins/json/mod.rs
  6. 43
      boa/src/builtins/number/mod.rs
  7. 8
      boa/src/builtins/object/mod.rs
  8. 18
      boa/src/builtins/regexp/mod.rs
  9. 88
      boa/src/builtins/string/mod.rs
  10. 6
      boa/src/builtins/value/conversions.rs
  11. 222
      boa/src/builtins/value/display.rs
  12. 222
      boa/src/builtins/value/mod.rs
  13. 2
      boa/src/exec/expression/mod.rs
  14. 66
      boa/src/exec/mod.rs
  15. 2
      boa/src/exec/operator/mod.rs

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

@ -311,17 +311,17 @@ impl Array {
///
/// [spec]: https://tc39.es/ecma262/#sec-array.prototype.join
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/join
pub(crate) fn join(this: &mut Value, args: &[Value], _: &mut Interpreter) -> ResultValue {
pub(crate) fn join(this: &mut Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue {
let separator = if args.is_empty() {
String::from(",")
} else {
args.get(0).expect("Could not get argument").to_string()
ctx.to_string(args.get(0).expect("Could not get argument"))?
};
let mut elem_strs: Vec<String> = Vec::new();
let length = i32::from(&this.get_field("length"));
for n in 0..length {
let elem_str: String = this.get_field(n.to_string()).to_string();
let elem_str: String = ctx.to_string(&this.get_field(n.to_string()))?;
elem_strs.push(elem_str);
}
@ -344,7 +344,7 @@ impl Array {
pub(crate) fn to_string(
this: &mut Value,
_args: &[Value],
_ctx: &mut Interpreter,
ctx: &mut Interpreter,
) -> ResultValue {
let method_name = "join";
let mut arguments = vec![Value::from(",")];
@ -352,7 +352,7 @@ impl Array {
let mut method = this.get_field(method_name);
// 3.
if !method.is_function() {
method = _ctx
method = ctx
.realm
.global_obj
.get_field("Object")
@ -362,15 +362,15 @@ impl Array {
arguments = Vec::new();
}
// 4.
let join_result = _ctx.call(&method, this, &arguments);
let match_string = match join_result {
Ok(v) => match *v {
ValueData::String(ref s) => (*s).clone(),
_ => "".to_string(),
},
Err(v) => format!("error: {}", v),
let join = ctx.call(&method, this, &arguments)?;
let string = if let ValueData::String(ref s) = join.data() {
Value::from(s.as_str())
} else {
Value::from("")
};
Ok(Value::from(match_string))
Ok(string)
}
/// `Array.prototype.reverse()`

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

@ -53,7 +53,7 @@ impl BigInt {
return Err(RangeError::run_new(
format!(
"{} can't be converted to BigInt because it isn't an integer",
value
ctx.to_string(value)?
),
ctx,
)?);
@ -64,6 +64,18 @@ impl BigInt {
Ok(data)
}
#[inline]
#[allow(clippy::wrong_self_convention)]
pub(crate) fn to_native_string_radix(bigint: &AstBigInt, radix: u32) -> String {
bigint.to_str_radix(radix)
}
#[inline]
#[allow(clippy::wrong_self_convention)]
pub(crate) fn to_native_string(bigint: &AstBigInt) -> String {
bigint.to_string()
}
/// `BigInt.prototype.toString( [radix] )`
///
/// The `toString()` method returns a string representing the specified BigInt object.
@ -91,9 +103,10 @@ impl BigInt {
ctx,
)?);
}
Ok(Value::from(
this.to_bigint().unwrap().to_str_radix(radix as u32),
))
Ok(Value::from(Self::to_native_string_radix(
&this.to_bigint().unwrap(),
radix as u32,
)))
}
/// `BigInt.prototype.valueOf()`

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

@ -69,11 +69,11 @@ pub fn logger(msg: LogMessage, console_state: &ConsoleState) {
}
/// This represents the `console` formatter.
pub fn formatter(data: &[Value]) -> String {
let target = get_arg_at_index::<String>(data, 0).unwrap_or_default();
pub fn formatter(data: &[Value], ctx: &mut Interpreter) -> Result<String, Value> {
let target = ctx.to_string(&data.get(0).cloned().unwrap_or_default())?;
match data.len() {
0 => String::new(),
1 => target,
0 => Ok(String::new()),
1 => Ok(target),
_ => {
let mut formatted = String::new();
let mut arg_index = 1;
@ -103,7 +103,7 @@ pub fn formatter(data: &[Value]) -> String {
/* string */
's' => {
let arg =
get_arg_at_index::<String>(data, arg_index).unwrap_or_default();
ctx.to_string(&data.get(arg_index).cloned().unwrap_or_default())?;
formatted.push_str(&arg);
arg_index += 1
}
@ -124,7 +124,7 @@ pub fn formatter(data: &[Value]) -> String {
formatted.push_str(&format!(" {}", rest))
}
formatted
Ok(formatted)
}
}
}
@ -140,7 +140,7 @@ pub fn formatter(data: &[Value]) -> String {
///
/// [spec]: https://console.spec.whatwg.org/#assert
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/API/console/assert
pub fn assert(this: &mut Value, args: &[Value], _: &mut Interpreter) -> ResultValue {
pub fn assert(this: &mut Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue {
let assertion = get_arg_at_index::<bool>(args, 0).unwrap_or_default();
if !assertion {
@ -155,9 +155,10 @@ pub fn assert(this: &mut Value, args: &[Value], _: &mut Interpreter) -> ResultVa
args[0] = Value::from(concat);
}
this.with_internal_state_ref(|state| {
logger(LogMessage::Error(formatter(&args[..])), state)
});
this.with_internal_state_ref::<_, Result<(), Value>, _>(|state| {
logger(LogMessage::Error(formatter(&args, ctx)?), state);
Ok(())
})?;
}
Ok(Value::undefined())
@ -191,8 +192,11 @@ pub fn clear(this: &mut Value, _: &[Value], _: &mut Interpreter) -> ResultValue
///
/// [spec]: https://console.spec.whatwg.org/#debug
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/API/console/debug
pub fn debug(this: &mut Value, args: &[Value], _: &mut Interpreter) -> ResultValue {
this.with_internal_state_ref(|state| logger(LogMessage::Log(formatter(&args[..])), state));
pub fn debug(this: &mut Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue {
this.with_internal_state_ref::<_, Result<(), Value>, _>(|state| {
logger(LogMessage::Log(formatter(args, ctx)?), state);
Ok(())
})?;
Ok(Value::undefined())
}
@ -206,8 +210,11 @@ pub fn debug(this: &mut Value, args: &[Value], _: &mut Interpreter) -> ResultVal
///
/// [spec]: https://console.spec.whatwg.org/#error
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/API/console/error
pub fn error(this: &mut Value, args: &[Value], _: &mut Interpreter) -> ResultValue {
this.with_internal_state_ref(|state| logger(LogMessage::Error(formatter(&args[..])), state));
pub fn error(this: &mut Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue {
this.with_internal_state_ref::<_, Result<(), Value>, _>(|state| {
logger(LogMessage::Error(formatter(args, ctx)?), state);
Ok(())
})?;
Ok(Value::undefined())
}
@ -221,8 +228,11 @@ pub fn error(this: &mut Value, args: &[Value], _: &mut Interpreter) -> ResultVal
///
/// [spec]: https://console.spec.whatwg.org/#info
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/API/console/info
pub fn info(this: &mut Value, args: &[Value], _: &mut Interpreter) -> ResultValue {
this.with_internal_state_ref(|state| logger(LogMessage::Info(formatter(&args[..])), state));
pub fn info(this: &mut Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue {
this.with_internal_state_ref::<_, Result<(), Value>, _>(|state| {
logger(LogMessage::Info(formatter(args, ctx)?), state);
Ok(())
})?;
Ok(Value::undefined())
}
@ -236,8 +246,11 @@ pub fn info(this: &mut Value, args: &[Value], _: &mut Interpreter) -> ResultValu
///
/// [spec]: https://console.spec.whatwg.org/#log
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/API/console/log
pub fn log(this: &mut Value, args: &[Value], _: &mut Interpreter) -> ResultValue {
this.with_internal_state_ref(|state| logger(LogMessage::Log(formatter(&args[..])), state));
pub fn log(this: &mut Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue {
this.with_internal_state_ref::<_, Result<(), Value>, _>(|state| {
logger(LogMessage::Log(formatter(args, ctx)?), state);
Ok(())
})?;
Ok(Value::undefined())
}
@ -251,9 +264,12 @@ pub fn log(this: &mut Value, args: &[Value], _: &mut Interpreter) -> ResultValue
///
/// [spec]: https://console.spec.whatwg.org/#trace
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/API/console/trace
pub fn trace(this: &mut Value, args: &[Value], _: &mut Interpreter) -> ResultValue {
pub fn trace(this: &mut Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue {
if !args.is_empty() {
this.with_internal_state_ref(|state| logger(LogMessage::Log(formatter(&args[..])), state));
this.with_internal_state_ref::<_, Result<(), Value>, _>(|state| {
logger(LogMessage::Log(formatter(args, ctx)?), state);
Ok(())
})?;
/* TODO: get and print stack trace */
this.with_internal_state_ref(|state| {
@ -277,8 +293,11 @@ pub fn trace(this: &mut Value, args: &[Value], _: &mut Interpreter) -> ResultVal
///
/// [spec]: https://console.spec.whatwg.org/#warn
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/API/console/warn
pub fn warn(this: &mut Value, args: &[Value], _: &mut Interpreter) -> ResultValue {
this.with_internal_state_ref(|state| logger(LogMessage::Warn(formatter(&args[..])), state));
pub fn warn(this: &mut Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue {
this.with_internal_state_ref::<_, Result<(), Value>, _>(|state| {
logger(LogMessage::Warn(formatter(args, ctx)?), state);
Ok(())
})?;
Ok(Value::undefined())
}
@ -292,8 +311,11 @@ pub fn warn(this: &mut Value, args: &[Value], _: &mut Interpreter) -> ResultValu
///
/// [spec]: https://console.spec.whatwg.org/#count
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/API/console/count
pub fn count(this: &mut Value, args: &[Value], _: &mut Interpreter) -> ResultValue {
let label = get_arg_at_index::<String>(args, 0).unwrap_or_else(|| "default".to_string());
pub fn count(this: &mut Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue {
let label = match args.get(0) {
Some(value) => ctx.to_string(value)?,
None => "default".to_owned(),
};
this.with_internal_state_mut(|state: &mut ConsoleState| {
let msg = format!("count {}:", &label);
@ -316,8 +338,11 @@ pub fn count(this: &mut Value, args: &[Value], _: &mut Interpreter) -> ResultVal
///
/// [spec]: https://console.spec.whatwg.org/#countreset
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/API/countReset
pub fn count_reset(this: &mut Value, args: &[Value], _: &mut Interpreter) -> ResultValue {
let label = get_arg_at_index::<String>(args, 0).unwrap_or_else(|| "default".to_string());
pub fn count_reset(this: &mut Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue {
let label = match args.get(0) {
Some(value) => ctx.to_string(value)?,
None => "default".to_owned(),
};
this.with_internal_state_mut(|state: &mut ConsoleState| {
state.count_map.remove(&label);
@ -346,8 +371,11 @@ fn system_time_in_ms() -> u128 {
///
/// [spec]: https://console.spec.whatwg.org/#time
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/API/console/time
pub fn time(this: &mut Value, args: &[Value], _: &mut Interpreter) -> ResultValue {
let label = get_arg_at_index::<String>(args, 0).unwrap_or_else(|| "default".to_string());
pub fn time(this: &mut Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue {
let label = match args.get(0) {
Some(value) => ctx.to_string(value)?,
None => "default".to_owned(),
};
this.with_internal_state_mut(|state: &mut ConsoleState| {
if state.timer_map.get(&label).is_some() {
@ -374,8 +402,11 @@ pub fn time(this: &mut Value, args: &[Value], _: &mut Interpreter) -> ResultValu
///
/// [spec]: https://console.spec.whatwg.org/#timelog
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/API/console/timeLog
pub fn time_log(this: &mut Value, args: &[Value], _: &mut Interpreter) -> ResultValue {
let label = get_arg_at_index::<String>(args, 0).unwrap_or_else(|| "default".to_string());
pub fn time_log(this: &mut Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue {
let label = match args.get(0) {
Some(value) => ctx.to_string(value)?,
None => "default".to_owned(),
};
this.with_internal_state_mut(|state: &mut ConsoleState| {
if let Some(t) = state.timer_map.get(&label) {
@ -406,8 +437,11 @@ pub fn time_log(this: &mut Value, args: &[Value], _: &mut Interpreter) -> Result
///
/// [spec]: https://console.spec.whatwg.org/#timeend
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/API/console/timeEnd
pub fn time_end(this: &mut Value, args: &[Value], _: &mut Interpreter) -> ResultValue {
let label = get_arg_at_index::<String>(args, 0).unwrap_or_else(|| "default".to_string());
pub fn time_end(this: &mut Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue {
let label = match args.get(0) {
Some(value) => ctx.to_string(value)?,
None => "default".to_owned(),
};
this.with_internal_state_mut(|state: &mut ConsoleState| {
if let Some(t) = state.timer_map.remove(&label) {
@ -437,8 +471,8 @@ pub fn time_end(this: &mut Value, args: &[Value], _: &mut Interpreter) -> Result
///
/// [spec]: https://console.spec.whatwg.org/#group
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/API/console/group
pub fn group(this: &mut Value, args: &[Value], _: &mut Interpreter) -> ResultValue {
let group_label = formatter(args);
pub fn group(this: &mut Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue {
let group_label = formatter(args, ctx)?;
this.with_internal_state_mut(|state: &mut ConsoleState| {
logger(LogMessage::Info(format!("group: {}", &group_label)), state);

51
boa/src/builtins/console/tests.rs

@ -1,61 +1,82 @@
use crate::builtins::{console::formatter, value::Value};
use crate::{
builtins::{console::formatter, value::Value},
exec::Interpreter,
realm::Realm,
};
#[test]
fn formatter_no_args_is_empty_string() {
assert_eq!(formatter(&[]), "")
let realm = Realm::create();
let mut engine = Interpreter::new(realm);
assert_eq!(formatter(&[], &mut engine).unwrap(), "");
}
#[test]
fn formatter_empty_format_string_is_empty_string() {
let realm = Realm::create();
let mut engine = Interpreter::new(realm);
let val = Value::string("".to_string());
let res = formatter(&[val]);
assert_eq!(res, "");
assert_eq!(formatter(&[val], &mut engine).unwrap(), "");
}
#[test]
fn formatter_format_without_args_renders_verbatim() {
let val = [Value::string("%d %s %% %f".to_string())];
let res = formatter(&val);
let realm = Realm::create();
let mut engine = Interpreter::new(realm);
let val = [Value::string("%d %s %% %f")];
let res = formatter(&val, &mut engine).unwrap();
assert_eq!(res, "%d %s %% %f");
}
#[test]
fn formatter_empty_format_string_concatenates_rest_of_args() {
let realm = Realm::create();
let mut engine = Interpreter::new(realm);
let val = [
Value::string("".to_string()),
Value::string("to powinno zostać".to_string()),
Value::string("połączone".to_string()),
Value::string(""),
Value::string("to powinno zostać"),
Value::string("połączone"),
];
let res = formatter(&val);
let res = formatter(&val, &mut engine).unwrap();
assert_eq!(res, " to powinno zostać połączone");
}
#[test]
fn formatter_utf_8_checks() {
let realm = Realm::create();
let mut engine = Interpreter::new(realm);
let val = [
Value::string("Są takie chwile %dą %są tu%sów %привет%ź".to_string()),
Value::integer(123),
Value::rational(1.23),
Value::string("ł".to_string()),
];
let res = formatter(&val);
let res = formatter(&val, &mut engine).unwrap();
assert_eq!(res, "Są takie chwile 123ą 1.23ą tułów %привет%ź");
}
#[test]
fn formatter_trailing_format_leader_renders() {
let realm = Realm::create();
let mut engine = Interpreter::new(realm);
let val = [
Value::string("%%%%%".to_string()),
Value::string("|".to_string()),
];
let res = formatter(&val);
assert_eq!(res, "%%% |")
let res = formatter(&val, &mut engine).unwrap();
assert_eq!(res, "%%% |");
}
#[test]
#[allow(clippy::approx_constant)]
fn formatter_float_format_works() {
let realm = Realm::create();
let mut engine = Interpreter::new(realm);
let val = [Value::string("%f".to_string()), Value::rational(3.1415)];
let res = formatter(&val);
assert_eq!(res, "3.141500")
let res = formatter(&val, &mut engine).unwrap();
assert_eq!(res, "3.141500");
}

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

@ -37,13 +37,9 @@ mod tests;
///
/// [spec]: https://tc39.es/ecma262/#sec-json.parse
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/parse
pub fn parse(_: &mut Value, args: &[Value], interpreter: &mut Interpreter) -> ResultValue {
pub fn parse(_: &mut Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue {
match serde_json::from_str::<JSONValue>(
&args
.get(0)
.expect("cannot get argument for JSON.parse")
.clone()
.to_string(),
&ctx.to_string(args.get(0).expect("cannot get argument for JSON.parse"))?,
) {
Ok(json) => {
let j = Value::from(json);
@ -51,7 +47,7 @@ pub fn parse(_: &mut Value, args: &[Value], interpreter: &mut Interpreter) -> Re
Some(reviver) if reviver.is_function() => {
let mut holder = Value::new_object(None);
holder.set_field(Value::from(""), j);
walk(reviver, interpreter, &mut holder, Value::from(""))
walk(reviver, ctx, &mut holder, Value::from(""))
}
_ => Ok(j),
}
@ -66,18 +62,13 @@ pub fn parse(_: &mut Value, args: &[Value], interpreter: &mut Interpreter) -> Re
/// for possible transformation.
///
/// [polyfill]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/parse
fn walk(
reviver: &Value,
interpreter: &mut Interpreter,
holder: &mut Value,
key: Value,
) -> ResultValue {
fn walk(reviver: &Value, ctx: &mut Interpreter, holder: &mut Value, key: Value) -> ResultValue {
let mut value = holder.get_field(key.clone());
let obj = value.as_object().as_deref().cloned();
if let Some(obj) = obj {
for key in obj.properties.keys() {
let v = walk(reviver, interpreter, &mut value, Value::from(key.as_str()));
let v = walk(reviver, ctx, &mut value, Value::from(key.as_str()));
match v {
Ok(v) if !v.is_undefined() => {
value.set_field(Value::from(key.as_str()), v);
@ -89,7 +80,7 @@ fn walk(
}
}
}
interpreter.call(reviver, holder, &[key, value])
ctx.call(reviver, holder, &[key, value])
}
/// `JSON.stringify( value[, replacer[, space]] )`
@ -108,7 +99,7 @@ fn walk(
///
/// [spec]: https://tc39.es/ecma262/#sec-json.stringify
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify
pub fn stringify(_: &mut Value, args: &[Value], interpreter: &mut Interpreter) -> ResultValue {
pub fn stringify(_: &mut Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue {
let object = match args.get(0) {
Some(obj) if obj.is_symbol() || obj.is_function() => return Ok(Value::undefined()),
None => return Ok(Value::undefined()),
@ -135,7 +126,7 @@ pub fn stringify(_: &mut Value, args: &[Value], interpreter: &mut Interpreter) -
let mut this_arg = object.clone();
object_to_return.set_property(
key.to_owned(),
Property::default().value(interpreter.call(
Property::default().value(ctx.call(
replacer,
&mut this_arg,
&[Value::string(key), val.clone()],
@ -152,12 +143,12 @@ pub fn stringify(_: &mut Value, args: &[Value], interpreter: &mut Interpreter) -
if key == "length" {
None
} else {
Some(replacer.get_field(key.to_string()))
Some(replacer.get_field(key.to_owned()))
}
});
for field in fields {
if let Some(value) = object
.get_property(&field.to_string())
.get_property(&ctx.to_string(&field)?)
.map(|prop| prop.value.as_ref().map(|v| v.to_json()))
.flatten()
{

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

@ -208,7 +208,8 @@ impl Number {
}
// https://chromium.googlesource.com/v8/v8/+/refs/heads/master/src/numbers/conversions.cc#1230
pub(crate) fn num_to_string(mut value: f64, radix: u8) -> String {
#[allow(clippy::wrong_self_convention)]
pub(crate) fn to_native_string_radix(mut value: f64, radix: u8) -> String {
assert!(radix >= 2);
assert!(radix <= 36);
assert!(value.is_finite());
@ -313,6 +314,22 @@ impl Number {
String::from_utf8_lossy(&buffer[integer_cursor..fraction_cursor]).into()
}
#[allow(clippy::wrong_self_convention)]
pub(crate) fn to_native_string(x: f64) -> String {
if x == -0. {
return "0".to_owned();
} else if x.is_nan() {
return "NaN".to_owned();
} else if x.is_infinite() && x.is_sign_positive() {
return "Infinity".to_owned();
} else if x.is_infinite() && x.is_sign_negative() {
return "-Infinity".to_owned();
}
// FIXME: This is not spec compliant.
format!("{}", x)
}
/// `Number.prototype.toString( [radix] )`
///
/// The `toString()` method returns a string representing the specified Number object.
@ -333,7 +350,15 @@ impl Number {
let x = Self::to_number(this).to_number();
// 2. If radix is undefined, let radixNumber be 10.
// 3. Else, let radixNumber be ? ToInteger(radix).
let radix_number = args.get(0).map_or(10, |arg| arg.to_integer()) as u8;
let radix = args.get(0).map_or(10, |arg| arg.to_integer()) as u8;
// 4. If radixNumber < 2 or radixNumber > 36, throw a RangeError exception.
if radix < 2 || radix > 36 {
return Err(RangeError::run_new(
"radix must be an integer at least 2 and no greater than 36",
ctx,
)?);
}
if x == -0. {
return Ok(Value::from("0"));
@ -345,19 +370,11 @@ impl Number {
return Ok(Value::from("-Infinity"));
}
// 4. If radixNumber < 2 or radixNumber > 36, throw a RangeError exception.
if radix_number < 2 || radix_number > 36 {
return Err(RangeError::run_new(
"radix must be an integer at least 2 and no greater than 36",
ctx,
)?);
}
// 5. If radixNumber = 10, return ! ToString(x).
// This part should use exponential notations for long integer numbers commented tests
if radix_number == 10 {
if radix == 10 {
// return Ok(to_value(format!("{}", Self::to_number(this).to_num())));
return Ok(Value::from(format!("{}", x)));
return Ok(Value::from(Self::to_native_string(x)));
}
// This is a Optimization from the v8 source code to print values that can fit in a single character
@ -369,7 +386,7 @@ impl Number {
// }
// 6. Return the String representation of this Number value using the radix specified by radixNumber.
Ok(Value::from(Self::num_to_string(x, radix_number)))
Ok(Value::from(Self::to_native_string_radix(x, radix)))
}
/// `Number.prototype.toString()`

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

@ -566,9 +566,9 @@ pub fn set_prototype_of(_: &mut Value, args: &[Value], _: &mut Interpreter) -> R
}
/// Define a property in an object
pub fn define_property(_: &mut Value, args: &[Value], _: &mut Interpreter) -> ResultValue {
pub fn define_property(_: &mut Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue {
let obj = args.get(0).expect("Cannot get object");
let prop = String::from(args.get(1).expect("Cannot get object"));
let prop = ctx.to_string(args.get(1).expect("Cannot get object"))?;
let desc = Property::from(args.get(2).expect("Cannot get object"));
obj.set_property(prop, desc);
Ok(Value::undefined())
@ -599,11 +599,11 @@ pub fn to_string(this: &mut Value, _: &[Value], _: &mut Interpreter) -> ResultVa
///
/// [spec]: https://tc39.es/ecma262/#sec-object.prototype.hasownproperty
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/hasOwnProperty
pub fn has_own_property(this: &mut Value, args: &[Value], _: &mut Interpreter) -> ResultValue {
pub fn has_own_property(this: &mut Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue {
let prop = if args.is_empty() {
None
} else {
Some(String::from(args.get(0).expect("Cannot get object")))
Some(ctx.to_string(args.get(0).expect("Cannot get object"))?)
};
Ok(Value::from(
prop.is_some()

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

@ -64,7 +64,7 @@ impl RegExp {
pub(crate) fn make_regexp(
this: &mut Value,
args: &[Value],
_: &mut Interpreter,
ctx: &mut Interpreter,
) -> ResultValue {
if args.is_empty() {
return Err(Value::undefined());
@ -82,10 +82,10 @@ impl RegExp {
if slots.get("RegExpMatcher").is_some() {
// first argument is another `RegExp` object, so copy its pattern and flags
if let Some(body) = slots.get("OriginalSource") {
regex_body = String::from(body);
regex_body = ctx.to_string(body)?;
}
if let Some(flags) = slots.get("OriginalFlags") {
regex_flags = String::from(flags);
regex_flags = ctx.to_string(flags)?;
}
}
}
@ -295,8 +295,8 @@ 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: &mut Value, args: &[Value], _: &mut Interpreter) -> ResultValue {
let arg_str = String::from(args.get(0).expect("could not get argument"));
pub(crate) fn test(this: &mut 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 result = this.with_internal_state_ref(|regex: &RegExp| {
let result = if let Some(m) = regex.matcher.find_at(arg_str.as_str(), last_index) {
@ -328,8 +328,8 @@ 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: &mut Value, args: &[Value], _: &mut Interpreter) -> ResultValue {
let arg_str = String::from(args.get(0).expect("could not get argument"));
pub(crate) fn exec(this: &mut 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 result = this.with_internal_state_ref(|regex: &RegExp| {
let mut locations = regex.matcher.capture_locations();
@ -407,8 +407,8 @@ impl RegExp {
/// [spec]: https://tc39.es/ecma262/#sec-regexp.prototype.tostring
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp/toString
#[allow(clippy::wrong_self_convention)]
pub(crate) fn to_string(this: &mut Value, _: &[Value], _: &mut Interpreter) -> ResultValue {
let body = String::from(&this.get_internal_slot("OriginalSource"));
pub(crate) fn to_string(this: &mut Value, _: &[Value], ctx: &mut Interpreter) -> ResultValue {
let body = ctx.to_string(&this.get_internal_slot("OriginalSource"))?;
let flags = this.with_internal_state_ref(|regex: &RegExp| regex.flags.clone());
Ok(Value::from(format!("/{}/{}", body, flags)))
}

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

@ -70,8 +70,11 @@ impl String {
#[allow(clippy::wrong_self_convention)]
pub(crate) fn to_string(this: &mut Value, _: &[Value], _: &mut Interpreter) -> ResultValue {
// Get String from String Object and send it back as a new value
let primitive_val = this.get_internal_slot("StringData");
Ok(Value::from(format!("{}", primitive_val)))
match this.get_internal_slot("StringData").data() {
ValueData::String(ref string) => Ok(Value::from(string.clone())),
// Throw expection here:
_ => panic!("TypeError: this is not a string"),
}
}
/// `String.prototype.charAt( index )`
@ -93,7 +96,7 @@ impl String {
pub(crate) fn char_at(this: &mut Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue {
// First we get it the actual string a private field stored on the object only the engine has access to.
// Then we convert it into a Rust String by wrapping it in from_value
let primitive_val = ctx.value_to_rust_string(this);
let primitive_val = ctx.to_string(this)?;
let pos = i32::from(
args.get(0)
.expect("failed to get argument for String method"),
@ -139,7 +142,7 @@ impl String {
) -> ResultValue {
// First we get it the actual string a private field stored on the object only the engine has access to.
// Then we convert it into a Rust String by wrapping it in from_value
let primitive_val = ctx.value_to_rust_string(this);
let primitive_val = ctx.to_string(this)?;
// Calling .len() on a string would give the wrong result, as they are bytes not the number of unicode code points
// Note that this is an O(N) operation (because UTF-8 is complex) while getting the number of bytes is an O(1) operation.
@ -179,7 +182,7 @@ impl String {
pub(crate) fn concat(this: &mut Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue {
// First we get it the actual string a private field stored on the object only the engine has access to.
// Then we convert it into a Rust String by wrapping it in from_value
let mut new_str = ctx.value_to_rust_string(this);
let mut new_str = ctx.to_string(this)?;
for arg in args {
let concat_str = arg.to_string();
@ -203,7 +206,7 @@ impl String {
pub(crate) fn repeat(this: &mut Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue {
// First we get it the actual string a private field stored on the object only the engine has access to.
// Then we convert it into a Rust String by wrapping it in from_value
let primitive_val = ctx.value_to_rust_string(this);
let primitive_val = ctx.to_string(this)?;
let repeat_times = usize::from(
args.get(0)
@ -226,7 +229,7 @@ impl String {
pub(crate) fn slice(this: &mut Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue {
// First we get it the actual string a private field stored on the object only the engine has access to.
// Then we convert it into a Rust String by wrapping it in from_value
let primitive_val = ctx.value_to_rust_string(this);
let primitive_val = ctx.to_string(this)?;
let start = i32::from(
args.get(0)
@ -277,13 +280,13 @@ impl String {
) -> ResultValue {
// First we get it the actual string a private field stored on the object only the engine has access to.
// Then we convert it into a Rust String by wrapping it in from_value
let primitive_val = ctx.value_to_rust_string(this);
let primitive_val = ctx.to_string(this)?;
// TODO: Should throw TypeError if pattern is regular expression
let search_string = StdString::from(
let search_string = ctx.to_string(
args.get(0)
.expect("failed to get argument for String method"),
);
)?;
let length = primitive_val.chars().count() as i32;
let search_length = search_string.chars().count() as i32;
@ -324,13 +327,13 @@ impl String {
) -> ResultValue {
// First we get it the actual string a private field stored on the object only the engine has access to.
// Then we convert it into a Rust String by wrapping it in from_value
let primitive_val = ctx.value_to_rust_string(this);
let primitive_val = ctx.to_string(this)?;
// TODO: Should throw TypeError if search_string is regular expression
let search_string = StdString::from(
let search_string = ctx.to_string(
args.get(0)
.expect("failed to get argument for String method"),
);
)?;
let length = primitive_val.chars().count() as i32;
let search_length = search_string.chars().count() as i32;
@ -368,13 +371,13 @@ impl String {
pub(crate) fn includes(this: &mut Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue {
// First we get it the actual string a private field stored on the object only the engine has access to.
// Then we convert it into a Rust String by wrapping it in from_value
let primitive_val = ctx.value_to_rust_string(this);
let primitive_val = ctx.to_string(this)?;
// TODO: Should throw TypeError if search_string is regular expression
let search_string = StdString::from(
let search_string = ctx.to_string(
args.get(0)
.expect("failed to get argument for String method"),
);
)?;
let length = primitive_val.chars().count() as i32;
@ -428,7 +431,7 @@ impl String {
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/replace
pub(crate) fn replace(this: &mut Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue {
// TODO: Support Symbol replacer
let primitive_val = ctx.value_to_rust_string(this);
let primitive_val = ctx.to_string(this)?;
if args.is_empty() {
return Ok(Value::from(primitive_val));
}
@ -501,7 +504,7 @@ impl String {
let result = ctx.call(&replace_object, this, &results).unwrap();
ctx.value_to_rust_string(&result)
ctx.to_string(&result)?
}
_ => "undefined".to_string(),
}
@ -531,13 +534,13 @@ impl String {
pub(crate) fn index_of(this: &mut Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue {
// First we get it the actual string a private field stored on the object only the engine has access to.
// Then we convert it into a Rust String by wrapping it in from_value
let primitive_val = ctx.value_to_rust_string(this);
let primitive_val = ctx.to_string(this)?;
// TODO: Should throw TypeError if search_string is regular expression
let search_string = StdString::from(
let search_string = ctx.to_string(
args.get(0)
.expect("failed to get argument for String method"),
);
)?;
let length = primitive_val.chars().count() as i32;
@ -584,13 +587,13 @@ impl String {
) -> ResultValue {
// First we get it the actual string a private field stored on the object only the engine has access to.
// Then we convert it into a Rust String by wrapping it in from_value
let primitive_val = ctx.value_to_rust_string(this);
let primitive_val = ctx.to_string(this)?;
// TODO: Should throw TypeError if search_string is regular expression
let search_string = StdString::from(
let search_string = ctx.to_string(
args.get(0)
.expect("failed to get argument for String method"),
);
)?;
let length = primitive_val.chars().count() as i32;
@ -633,7 +636,7 @@ impl String {
pub(crate) fn r#match(this: &mut Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue {
let mut re =
RegExp::make_regexp(&mut Value::from(Object::default()), &[args[0].clone()], ctx)?;
RegExp::r#match(&mut re, ctx.value_to_rust_string(this), ctx)
RegExp::r#match(&mut re, ctx.to_string(this)?, ctx)
}
/// Abstract method `StringPad`.
@ -690,7 +693,7 @@ 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: &mut Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue {
let primitive_val = ctx.value_to_rust_string(this);
let primitive_val = ctx.to_string(this)?;
if args.is_empty() {
return Err(Value::from("padEnd requires maxLength argument"));
}
@ -699,11 +702,10 @@ impl String {
.expect("failed to get argument for String method"),
);
let fill_string = match args.len() {
1 => None,
_ => Some(StdString::from(
args.get(1).expect("Could not get argument"),
)),
let fill_string = if args.len() != 1 {
Some(ctx.to_string(args.get(1).expect("Could not get argument"))?)
} else {
None
};
Self::string_pad(primitive_val, max_length, fill_string, false)
@ -726,7 +728,7 @@ impl String {
args: &[Value],
ctx: &mut Interpreter,
) -> ResultValue {
let primitive_val = ctx.value_to_rust_string(this);
let primitive_val = ctx.to_string(this)?;
if args.is_empty() {
return Err(Value::from("padStart requires maxLength argument"));
}
@ -737,9 +739,7 @@ impl String {
let fill_string = match args.len() {
1 => None,
_ => Some(StdString::from(
args.get(1).expect("Could not get argument"),
)),
_ => Some(ctx.to_string(args.get(1).expect("Could not get argument"))?),
};
Self::string_pad(primitive_val, max_length, fill_string, true)
@ -777,7 +777,7 @@ impl String {
/// [spec]: https://tc39.es/ecma262/#sec-string.prototype.trim
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/trim
pub(crate) fn trim(this: &mut Value, _: &[Value], ctx: &mut Interpreter) -> ResultValue {
let this_str = ctx.value_to_rust_string(this);
let this_str = ctx.to_string(this)?;
Ok(Value::from(
this_str.trim_matches(Self::is_trimmable_whitespace),
))
@ -796,7 +796,7 @@ impl String {
/// [spec]: https://tc39.es/ecma262/#sec-string.prototype.trimstart
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/trimStart
pub(crate) fn trim_start(this: &mut Value, _: &[Value], ctx: &mut Interpreter) -> ResultValue {
let this_str = ctx.value_to_rust_string(this);
let this_str = ctx.to_string(this)?;
Ok(Value::from(
this_str.trim_start_matches(Self::is_trimmable_whitespace),
))
@ -815,7 +815,7 @@ impl String {
/// [spec]: https://tc39.es/ecma262/#sec-string.prototype.trimend
/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/trimEnd
pub(crate) fn trim_end(this: &mut Value, _: &[Value], ctx: &mut Interpreter) -> ResultValue {
let this_str = ctx.value_to_rust_string(this);
let this_str = ctx.to_string(this)?;
Ok(Value::from(
this_str.trim_end_matches(Self::is_trimmable_whitespace),
))
@ -839,7 +839,7 @@ impl String {
) -> ResultValue {
// First we get it the actual string a private field stored on the object only the engine has access to.
// Then we convert it into a Rust String by wrapping it in from_value
let this_str = ctx.value_to_rust_string(this);
let this_str = ctx.to_string(this)?;
// The Rust String is mapped to uppercase using the builtin .to_lowercase().
// There might be corner cases where it does not behave exactly like Javascript expects
Ok(Value::from(this_str.to_lowercase()))
@ -865,7 +865,7 @@ impl String {
) -> ResultValue {
// First we get it the actual string a private field stored on the object only the engine has access to.
// Then we convert it into a Rust String by wrapping it in from_value
let this_str = ctx.value_to_rust_string(this);
let this_str = ctx.to_string(this)?;
// The Rust String is mapped to uppercase using the builtin .to_uppercase().
// There might be corner cases where it does not behave exactly like Javascript expects
Ok(Value::from(this_str.to_uppercase()))
@ -888,7 +888,7 @@ impl String {
) -> ResultValue {
// First we get it the actual string a private field stored on the object only the engine has access to.
// Then we convert it into a Rust String by wrapping it in from_value
let primitive_val = ctx.value_to_rust_string(this);
let primitive_val = ctx.to_string(this)?;
// If no args are specified, start is 'undefined', defaults to 0
let start = if args.is_empty() {
0
@ -936,7 +936,7 @@ impl String {
pub(crate) fn substr(this: &mut Value, args: &[Value], ctx: &mut Interpreter) -> ResultValue {
// First we get it the actual string a private field stored on the object only the engine has access to.
// Then we convert it into a Rust String by wrapping it in from_value
let primitive_val = ctx.value_to_rust_string(this);
let primitive_val = ctx.to_string(this)?;
// If no args are specified, start is 'undefined', defaults to 0
let mut start = if args.is_empty() {
0
@ -1016,7 +1016,7 @@ impl String {
if arg.is_null() {
RegExp::make_regexp(
&mut Value::from(Object::default()),
&[Value::from(ctx.value_to_rust_string(arg)), Value::from("g")],
&[Value::from(ctx.to_string(arg)?), Value::from("g")],
ctx,
)
} else if arg.is_undefined() {
@ -1036,7 +1036,7 @@ impl String {
),
}?;
RegExp::match_all(&mut re, ctx.value_to_rust_string(this))
RegExp::match_all(&mut re, ctx.to_string(this)?)
}
/// Create a new `String` object.

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

@ -19,12 +19,6 @@ impl From<Box<str>> for Value {
}
}
impl From<&Value> for String {
fn from(value: &Value) -> Self {
value.to_string()
}
}
impl From<&str> for Value {
fn from(value: &str) -> Value {
Value::string(value)

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

@ -0,0 +1,222 @@
use super::*;
impl Display for Value {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
Display::fmt(&self.data(), f)
}
}
/// A helper macro for printing objects
/// Can be used to print both properties and internal slots
/// All of the overloads take:
/// - The object to be printed
/// - The function with which to print
/// - The indentation for the current level (for nested objects)
/// - A HashSet with the addresses of the already printed objects for the current branch
/// (used to avoid infinite loops when there are cyclic deps)
macro_rules! print_obj_value {
(all of $obj:expr, $display_fn:ident, $indent:expr, $encounters:expr) => {
{
let mut internals = print_obj_value!(internals of $obj, $display_fn, $indent, $encounters);
let mut props = print_obj_value!(props of $obj, $display_fn, $indent, $encounters, true);
props.reserve(internals.len());
props.append(&mut internals);
props
}
};
(internals of $obj:expr, $display_fn:ident, $indent:expr, $encounters:expr) => {
print_obj_value!(impl internal_slots, $obj, |(key, val)| {
format!(
"{:>width$}: {}",
key,
$display_fn(&val, $encounters, $indent.wrapping_add(4), true),
width = $indent,
)
})
};
(props of $obj:expr, $display_fn:ident, $indent:expr, $encounters:expr, $print_internals:expr) => {
print_obj_value!(impl properties, $obj, |(key, val)| {
let v = &val
.value
.as_ref()
.expect("Could not get the property's value");
format!(
"{:>width$}: {}",
key,
$display_fn(v, $encounters, $indent.wrapping_add(4), $print_internals),
width = $indent,
)
})
};
// A private overload of the macro
// DO NOT use directly
(impl $field:ident, $v:expr, $f:expr) => {
$v
.borrow()
.$field
.iter()
.map($f)
.collect::<Vec<String>>()
};
}
pub(crate) fn log_string_from(x: &ValueData, print_internals: bool) -> String {
match x {
// We don't want to print private (compiler) or prototype properties
ValueData::Object(ref v) => {
// Can use the private "type" field of an Object to match on
// which type of Object it represents for special printing
match v.borrow().kind {
ObjectKind::String => match v
.borrow()
.internal_slots
.get("StringData")
.expect("Cannot get primitive value from String")
.data()
{
ValueData::String(ref string) => format!("\"{}\"", string),
_ => unreachable!("[[StringData]] should always contain String"),
},
ObjectKind::Boolean => {
let bool_data = v.borrow().get_internal_slot("BooleanData").to_string();
format!("Boolean {{ {} }}", bool_data)
}
ObjectKind::Array => {
let len = i32::from(
&v.borrow()
.properties
.get("length")
.unwrap()
.value
.clone()
.expect("Could not borrow value"),
);
if len == 0 {
return String::from("[]");
}
let arr = (0..len)
.map(|i| {
// Introduce recursive call to stringify any objects
// which are part of the Array
log_string_from(
&v.borrow()
.properties
.get(&i.to_string())
.unwrap()
.value
.clone()
.expect("Could not borrow value"),
print_internals,
)
})
.collect::<Vec<String>>()
.join(", ");
format!("[ {} ]", arr)
}
_ => display_obj(&x, print_internals),
}
}
ValueData::Symbol(ref sym) => {
let desc: Value = sym.borrow().get_internal_slot("Description");
match *desc {
ValueData::String(ref st) => format!("Symbol(\"{}\")", st.to_string()),
_ => String::from("Symbol()"),
}
}
_ => format!("{}", x),
}
}
/// A helper function for specifically printing object values
pub(crate) fn display_obj(v: &ValueData, print_internals: bool) -> String {
// A simple helper for getting the address of a value
// TODO: Find a more general place for this, as it can be used in other situations as well
fn address_of<T>(t: &T) -> usize {
let my_ptr: *const T = t;
my_ptr as usize
}
// We keep track of which objects we have encountered by keeping their
// in-memory address in this set
let mut encounters = HashSet::new();
fn display_obj_internal(
data: &ValueData,
encounters: &mut HashSet<usize>,
indent: usize,
print_internals: bool,
) -> String {
if let ValueData::Object(ref v) = *data {
// The in-memory address of the current object
let addr = address_of(v.borrow().deref());
// We need not continue if this object has already been
// printed up the current chain
if encounters.contains(&addr) {
return String::from("[Cycle]");
}
// Mark the current object as encountered
encounters.insert(addr);
let result = if print_internals {
print_obj_value!(all of v, display_obj_internal, indent, encounters).join(",\n")
} else {
print_obj_value!(props of v, display_obj_internal, indent, encounters, print_internals)
.join(",\n")
};
// If the current object is referenced in a different branch,
// it will not cause an infinte printing loop, so it is safe to be printed again
encounters.remove(&addr);
let closing_indent = String::from_utf8(vec![b' '; indent.wrapping_sub(4)])
.expect("Could not create the closing brace's indentation string");
format!("{{\n{}\n{}}}", result, closing_indent)
} else {
// Every other type of data is printed as is
format!("{}", data)
}
}
display_obj_internal(v, &mut encounters, 4, print_internals)
}
impl Display for ValueData {
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 v) => match *v.borrow().get_internal_slot("Description") {
// If a description exists use it
Self::String(ref v) => write!(f, "{}", format!("Symbol({})", v)),
_ => write!(f, "Symbol()"),
},
Self::String(ref v) => write!(f, "{}", v),
Self::Rational(v) => write!(
f,
"{}",
match v {
_ if v.is_nan() => "NaN".to_string(),
_ if v.is_infinite() && v.is_sign_negative() => "-Infinity".to_string(),
_ if v.is_infinite() => "Infinity".to_string(),
_ => v.to_string(),
}
),
Self::Object(_) => write!(f, "{}", log_string_from(self, true)),
Self::Integer(v) => write!(f, "{}", v),
Self::BigInt(ref num) => write!(f, "{}n", num),
}
}
}

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

@ -27,8 +27,10 @@ use std::{
};
pub mod conversions;
pub mod display;
pub mod operations;
pub use conversions::*;
pub(crate) use display::display_obj;
pub use operations::*;
/// The result of a Javascript expression is represented like this so it can succeed (`Ok`) or fail (`Err`)
@ -153,12 +155,6 @@ impl Deref for Value {
}
}
impl Display for Value {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
Display::fmt(&self.0, f)
}
}
/// A Javascript value
#[derive(Trace, Finalize, Debug, Clone)]
pub enum ValueData {
@ -794,217 +790,3 @@ impl Default for ValueData {
Self::Undefined
}
}
/// A helper macro for printing objects
/// Can be used to print both properties and internal slots
/// All of the overloads take:
/// - The object to be printed
/// - The function with which to print
/// - The indentation for the current level (for nested objects)
/// - A HashSet with the addresses of the already printed objects for the current branch
/// (used to avoid infinite loops when there are cyclic deps)
macro_rules! print_obj_value {
(all of $obj:expr, $display_fn:ident, $indent:expr, $encounters:expr) => {
{
let mut internals = print_obj_value!(internals of $obj, $display_fn, $indent, $encounters);
let mut props = print_obj_value!(props of $obj, $display_fn, $indent, $encounters, true);
props.reserve(internals.len());
props.append(&mut internals);
props
}
};
(internals of $obj:expr, $display_fn:ident, $indent:expr, $encounters:expr) => {
print_obj_value!(impl internal_slots, $obj, |(key, val)| {
format!(
"{}{}: {}",
String::from_utf8(vec![b' '; $indent])
.expect("Could not create indentation string"),
key,
$display_fn(&val, $encounters, $indent.wrapping_add(4), true)
)
})
};
(props of $obj:expr, $display_fn:ident, $indent:expr, $encounters:expr, $print_internals:expr) => {
print_obj_value!(impl properties, $obj, |(key, val)| {
let v = &val
.value
.as_ref()
.expect("Could not get the property's value");
format!(
"{}{}: {}",
String::from_utf8(vec![b' '; $indent])
.expect("Could not create indentation string"),
key,
$display_fn(v, $encounters, $indent.wrapping_add(4), $print_internals)
)
})
};
// A private overload of the macro
// DO NOT use directly
(impl $field:ident, $v:expr, $f:expr) => {
$v
.borrow()
.$field
.iter()
.map($f)
.collect::<Vec<String>>()
};
}
pub(crate) fn log_string_from(x: &ValueData, print_internals: bool) -> String {
match x {
// We don't want to print private (compiler) or prototype properties
ValueData::Object(ref v) => {
// Can use the private "type" field of an Object to match on
// which type of Object it represents for special printing
match v.borrow().kind {
ObjectKind::String => String::from(
v.borrow()
.internal_slots
.get("StringData")
.expect("Cannot get primitive value from String"),
),
ObjectKind::Boolean => {
let bool_data = v.borrow().get_internal_slot("BooleanData").to_string();
format!("Boolean {{ {} }}", bool_data)
}
ObjectKind::Array => {
let len = i32::from(
&v.borrow()
.properties
.get("length")
.unwrap()
.value
.clone()
.expect("Could not borrow value"),
);
if len == 0 {
return String::from("[]");
}
let arr = (0..len)
.map(|i| {
// Introduce recursive call to stringify any objects
// which are part of the Array
log_string_from(
&v.borrow()
.properties
.get(&i.to_string())
.unwrap()
.value
.clone()
.expect("Could not borrow value"),
print_internals,
)
})
.collect::<Vec<String>>()
.join(", ");
format!("[ {} ]", arr)
}
_ => display_obj(&x, print_internals),
}
}
ValueData::Symbol(ref sym) => {
let desc: Value = sym.borrow().get_internal_slot("Description");
match *desc {
ValueData::String(ref st) => format!("Symbol(\"{}\")", st.to_string()),
_ => String::from("Symbol()"),
}
}
_ => format!("{}", x),
}
}
/// A helper function for specifically printing object values
pub(crate) fn display_obj(v: &ValueData, print_internals: bool) -> String {
// A simple helper for getting the address of a value
// TODO: Find a more general place for this, as it can be used in other situations as well
fn address_of<T>(t: &T) -> usize {
let my_ptr: *const T = t;
my_ptr as usize
}
// We keep track of which objects we have encountered by keeping their
// in-memory address in this set
let mut encounters = HashSet::new();
fn display_obj_internal(
data: &ValueData,
encounters: &mut HashSet<usize>,
indent: usize,
print_internals: bool,
) -> String {
if let ValueData::Object(ref v) = *data {
// The in-memory address of the current object
let addr = address_of(v.borrow().deref());
// We need not continue if this object has already been
// printed up the current chain
if encounters.contains(&addr) {
return String::from("[Cycle]");
}
// Mark the current object as encountered
encounters.insert(addr);
let result = if print_internals {
print_obj_value!(all of v, display_obj_internal, indent, encounters).join(",\n")
} else {
print_obj_value!(props of v, display_obj_internal, indent, encounters, print_internals)
.join(",\n")
};
// If the current object is referenced in a different branch,
// it will not cause an infinte printing loop, so it is safe to be printed again
encounters.remove(&addr);
let closing_indent = String::from_utf8(vec![b' '; indent.wrapping_sub(4)])
.expect("Could not create the closing brace's indentation string");
format!("{{\n{}\n{}}}", result, closing_indent)
} else {
// Every other type of data is printed as is
format!("{}", data)
}
}
display_obj_internal(v, &mut encounters, 4, print_internals)
}
impl Display for ValueData {
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 v) => match *v.borrow().get_internal_slot("Description") {
// If a description exists use it
Self::String(ref v) => write!(f, "{}", format!("Symbol({})", v)),
_ => write!(f, "Symbol()"),
},
Self::String(ref v) => write!(f, "{}", v),
Self::Rational(v) => write!(
f,
"{}",
match v {
_ if v.is_nan() => "NaN".to_string(),
_ if v.is_infinite() && v.is_sign_negative() => "-Infinity".to_string(),
_ if v.is_infinite() => "Infinity".to_string(),
_ => v.to_string(),
}
),
Self::Object(_) => write!(f, "{}", log_string_from(self, true)),
Self::Integer(v) => write!(f, "{}", v),
Self::BigInt(ref num) => write!(f, "{}n", num),
}
}
}

2
boa/src/exec/expression/mod.rs

@ -24,7 +24,7 @@ impl Executable for Call {
Node::GetField(ref obj, ref field) => {
let obj = obj.run(interpreter)?;
let field = field.run(interpreter)?;
(obj.clone(), obj.get_field(field.to_string()))
(obj.clone(), obj.get_field(field))
}
_ => (
interpreter.realm().global_obj.clone(),

66
boa/src/exec/mod.rs

@ -20,6 +20,7 @@ use crate::{
},
property::Property,
value::{ResultValue, Value, ValueData},
BigInt, Number,
},
realm::Realm,
syntax::ast::{
@ -57,6 +58,11 @@ impl Interpreter {
&self.realm
}
/// Retrieves the `Realm` of this executor as a mutable reference.
pub(crate) fn realm_mut(&mut self) -> &mut Realm {
&mut self.realm
}
/// Utility to create a function Value for Function Declarations, Arrow Functions or Function Expressions
pub(crate) fn create_function<P, B>(
&mut self,
@ -107,11 +113,6 @@ impl Interpreter {
val
}
/// Retrieves the `Realm` of this executor as a mutable reference.
pub(crate) fn realm_mut(&mut self) -> &mut Realm {
&mut self.realm
}
/// <https://tc39.es/ecma262/#sec-call>
pub(crate) fn call(
&mut self,
@ -130,18 +131,21 @@ impl Interpreter {
}
/// Converts a value into a rust heap allocated string.
pub(crate) fn value_to_rust_string(&mut self, value: &Value) -> String {
match *value.deref().borrow() {
ValueData::Null => String::from("null"),
ValueData::Boolean(ref boolean) => boolean.to_string(),
ValueData::Rational(ref num) => num.to_string(),
ValueData::Integer(ref num) => num.to_string(),
ValueData::String(ref string) => string.clone(),
#[allow(clippy::wrong_self_convention)]
pub fn to_string(&mut self, value: &Value) -> Result<String, Value> {
match value.data() {
ValueData::Null => Ok("null".to_owned()),
ValueData::Undefined => Ok("undefined".to_owned()),
ValueData::Boolean(boolean) => Ok(boolean.to_string()),
ValueData::Rational(rational) => Ok(Number::to_native_string(*rational)),
ValueData::Integer(integer) => Ok(integer.to_string()),
ValueData::String(string) => Ok(string.clone()),
ValueData::Symbol(_) => panic!("TypeError exception."),
ValueData::BigInt(ref bigint) => Ok(BigInt::to_native_string(bigint)),
ValueData::Object(_) => {
let prim_value = self.to_primitive(&mut (value.clone()), Some("string"));
self.to_string(&prim_value).to_string()
let primitive = self.to_primitive(&mut value.clone(), Some("string"));
self.to_string(&primitive)
}
_ => String::from("undefined"),
}
}
@ -227,36 +231,16 @@ impl Interpreter {
}
}
/// Converts a value into a `String`.
///
/// https://tc39.es/ecma262/#sec-tostring
#[allow(clippy::wrong_self_convention)]
pub(crate) fn to_string(&mut self, value: &Value) -> Value {
match *value.deref().borrow() {
ValueData::Undefined => Value::from("undefined"),
ValueData::Null => Value::from("null"),
ValueData::Boolean(ref boolean) => Value::from(boolean.to_string()),
ValueData::Rational(ref num) => Value::from(num.to_string()),
ValueData::Integer(ref num) => Value::from(num.to_string()),
ValueData::String(ref string) => Value::from(string.clone()),
ValueData::BigInt(ref bigint) => Value::from(bigint.to_string()),
ValueData::Object(_) => {
let prim_value = self.to_primitive(&mut (value.clone()), Some("string"));
self.to_string(&prim_value)
}
_ => Value::from("function(){...}"),
}
}
/// 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: &mut Value) -> Value {
pub(crate) fn to_property_key(&mut self, value: &mut Value) -> ResultValue {
let key = self.to_primitive(value, Some("string"));
if key.is_symbol() {
key
Ok(key)
} else {
self.to_string(&key)
self.to_string(&key).map(Value::from)
}
}
@ -343,9 +327,9 @@ impl Interpreter {
ValueData::Object(_) => {
let prim_value = self.to_primitive(&mut (value.clone()), Some("number"));
self.to_string(&prim_value)
.to_string()
.expect("cannot convert value to string")
.parse::<f64>()
.expect("cannot parse valur to x64")
.expect("cannot parse value to f64")
}
_ => {
// TODO: Make undefined?

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

@ -89,7 +89,7 @@ impl Executable for BinOp {
if !v_b.is_object() {
panic!("TypeError: {} is not an Object.", v_b);
}
let key = interpreter.to_property_key(&mut v_a);
let key = interpreter.to_property_key(&mut v_a)?;
interpreter.has_property(&mut v_b, &key)
}
}))

Loading…
Cancel
Save