use std::borrow::Cow;
use crate::{
builtins::promise::PromiseState, object::ObjectKind, property::PropertyDescriptor,
string::utf16, JsError, JsString,
};
use super::{fmt, Display, HashSet, JsValue};
#[derive(Debug, Clone, Copy)]
pub struct ValueDisplay<'value> {
pub(super) value: &'value JsValue,
pub(super) internals: bool,
}
impl ValueDisplay<'_> {
#[inline]
#[must_use]
pub const fn internals(mut self, yes: bool) -> Self {
self.internals = yes;
self
}
}
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) => {
{
let object = $obj.borrow();
if let Some(object) = object.prototype() {
vec![format!(
"{:>width$}: {}",
"__proto__",
$display_fn(&object.clone().into(), $encounters, $indent.wrapping_add(4), true),
width = $indent,
)]
} else {
vec![format!(
"{:>width$}: {}",
"__proto__",
JsValue::Null.display(),
width = $indent,
)]
}
}
};
(props of $obj:expr, $display_fn:ident, $indent:expr, $encounters:expr, $print_internals:expr) => {
{let mut keys: Vec<_> = $obj.borrow().properties().index_property_keys().map(crate::property::PropertyKey::Index).collect();
keys.extend($obj.borrow().properties().shape.keys());
let mut result = Vec::default();
for key in keys {
let val = $obj.borrow().properties().get(&key).expect("There should be a value");
if val.is_data_descriptor() {
let v = &val.expect_value();
result.push(format!(
"{:>width$}: {}",
key,
$display_fn(v, $encounters, $indent.wrapping_add(4), $print_internals),
width = $indent,
));
} else {
let display = match (val.set().is_some(), val.get().is_some()) {
(true, true) => "Getter & Setter",
(true, false) => "Setter",
(false, true) => "Getter",
_ => "No Getter/Setter"
};
result.push(format!("{:>width$}: {}", key, display, width = $indent));
}
}
result}
};
}
pub(crate) fn log_string_from(x: &JsValue, print_internals: bool, print_children: bool) -> String {
match x {
JsValue::Object(ref v) => {
match v.borrow().kind() {
ObjectKind::String(ref string) => {
format!("String {{ \"{}\" }}", string.to_std_string_escaped())
}
ObjectKind::Boolean(boolean) => format!("Boolean {{ {boolean} }}"),
ObjectKind::Number(rational) => {
if rational.is_sign_negative() && *rational == 0.0 {
"Number { -0 }".to_string()
} else {
let mut buffer = ryu_js::Buffer::new();
format!("Number {{ {} }}", buffer.format(*rational))
}
}
ObjectKind::Array => {
let len = v
.borrow()
.properties()
.get(&utf16!("length").into())
.expect("array object must have 'length' property")
.expect_value()
.as_number()
.map(|n| n as i32)
.unwrap_or_default();
if print_children {
if len == 0 {
return String::from("[]");
}
let arr = (0..len)
.map(|i| {
if let Some(value) = v
.borrow()
.properties()
.get(&i.into())
.and_then(|x| x.value().cloned())
{
log_string_from(&value, print_internals, false)
} else {
String::from("<empty>")
}
})
.collect::<Vec<String>>()
.join(", ");
format!("[ {arr} ]")
} else {
format!("Array({len})")
}
}
ObjectKind::Map(ref map) => {
let size = map.len();
if size == 0 {
return String::from("Map(0)");
}
if print_children {
let mappings = map
.iter()
.map(|(key, value)| {
let key = log_string_from(key, print_internals, false);
let value = log_string_from(value, print_internals, false);
format!("{key} → {value}")
})
.collect::<Vec<String>>()
.join(", ");
format!("Map {{ {mappings} }}")
} else {
format!("Map({size})")
}
}
ObjectKind::Set(ref set) => {
let size = set.len();
if size == 0 {
return String::from("Set(0)");
}
if print_children {
let entries = set
.iter()
.map(|value| log_string_from(value, print_internals, false))
.collect::<Vec<String>>()
.join(", ");
format!("Set {{ {entries} }}")
} else {
format!("Set({size})")
}
}
ObjectKind::Error(_) => {
let name: Cow<'static, str> = v
.get_property(&utf16!("name").into())
.as_ref()
.and_then(PropertyDescriptor::value)
.map_or_else(
|| "<error>".into(),
|v| {
v.as_string()
.map_or_else(
|| v.display().to_string(),
JsString::to_std_string_escaped,
)
.into()
},
);
let message = v
.get_property(&utf16!("message").into())
.as_ref()
.and_then(PropertyDescriptor::value)
.map(|v| {
v.as_string().map_or_else(
|| v.display().to_string(),
JsString::to_std_string_escaped,
)
})
.unwrap_or_default();
if name.is_empty() {
message
} else if message.is_empty() {
name.to_string()
} else {
format!("{name}: {message}")
}
}
ObjectKind::Promise(ref promise) => {
format!(
"Promise {{ {} }}",
match promise.state() {
PromiseState::Pending => Cow::Borrowed("<pending>"),
PromiseState::Fulfilled(val) => Cow::Owned(val.display().to_string()),
PromiseState::Rejected(reason) => Cow::Owned(format!(
"<rejected> {}",
JsError::from_opaque(reason.clone())
)),
}
)
}
_ => x.display_obj(print_internals),
}
}
_ => x.display().to_string(),
}
}
impl JsValue {
pub fn display_obj(&self, print_internals: bool) -> String {
fn address_of<T>(t: &T) -> usize {
let my_ptr: *const T = t;
my_ptr as usize
}
fn display_obj_internal(
data: &JsValue,
encounters: &mut HashSet<usize>,
indent: usize,
print_internals: bool,
) -> String {
if let JsValue::Object(ref v) = *data {
let addr = address_of(v.as_ref());
if encounters.contains(&addr) {
return String::from("[Cycle]");
}
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")
};
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{result}\n{closing_indent}}}")
} else {
data.display().to_string()
}
}
let mut encounters = HashSet::new();
display_obj_internal(self, &mut encounters, 4, print_internals)
}
}
impl Display for ValueDisplay<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self.value {
JsValue::Null => write!(f, "null"),
JsValue::Undefined => write!(f, "undefined"),
JsValue::Boolean(v) => write!(f, "{v}"),
JsValue::Symbol(ref symbol) => {
write!(f, "{}", symbol.descriptive_string().to_std_string_escaped())
}
JsValue::String(ref v) => write!(f, "\"{}\"", v.to_std_string_escaped()),
JsValue::Rational(v) => format_rational(*v, f),
JsValue::Object(_) => {
write!(f, "{}", log_string_from(self.value, self.internals, true))
}
JsValue::Integer(v) => write!(f, "{v}"),
JsValue::BigInt(ref num) => write!(f, "{num}n"),
}
}
}
fn format_rational(v: f64, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if v.is_sign_negative() && v == 0.0 {
f.write_str("-0")
} else {
let mut buffer = ryu_js::Buffer::new();
write!(f, "{}", buffer.format(v))
}
}