boa/value/
display.rs

1use crate::object::ObjectKind;
2
3use super::*;
4
5/// This object is used for displaying a `Value`.
6#[derive(Debug, Clone, Copy)]
7pub struct ValueDisplay<'value> {
8    pub(super) value: &'value JsValue,
9}
10
11/// A helper macro for printing objects
12/// Can be used to print both properties and internal slots
13/// All of the overloads take:
14/// - The object to be printed
15/// - The function with which to print
16/// - The indentation for the current level (for nested objects)
17/// - A HashSet with the addresses of the already printed objects for the current branch
18///      (used to avoid infinite loops when there are cyclic deps)
19macro_rules! print_obj_value {
20    (all of $obj:expr, $display_fn:ident, $indent:expr, $encounters:expr) => {
21        {
22            let mut internals = print_obj_value!(internals of $obj, $display_fn, $indent, $encounters);
23            let mut props = print_obj_value!(props of $obj, $display_fn, $indent, $encounters, true);
24
25            props.reserve(internals.len());
26            props.append(&mut internals);
27
28            props
29        }
30    };
31    (internals of $obj:expr, $display_fn:ident, $indent:expr, $encounters:expr) => {
32        {
33            let object = $obj.borrow();
34            if object.prototype_instance().is_object() {
35                vec![format!(
36                    "{:>width$}: {}",
37                    "__proto__",
38                    $display_fn(object.prototype_instance(), $encounters, $indent.wrapping_add(4), true),
39                    width = $indent,
40                )]
41            } else {
42                vec![format!(
43                    "{:>width$}: {}",
44                    "__proto__",
45                    object.prototype_instance().display(),
46                    width = $indent,
47                )]
48            }
49        }
50    };
51    (props of $obj:expr, $display_fn:ident, $indent:expr, $encounters:expr, $print_internals:expr) => {
52        print_obj_value!(impl $obj, |(key, val)| {
53            if val.is_data_descriptor() {
54                let v = &val.expect_value();
55                format!(
56                    "{:>width$}: {}",
57                    key,
58                    $display_fn(v, $encounters, $indent.wrapping_add(4), $print_internals),
59                    width = $indent,
60                )
61            } else {
62               let display = match (val.set().is_some(), val.get().is_some()) {
63                    (true, true) => "Getter & Setter",
64                    (true, false) => "Setter",
65                    (false, true) => "Getter",
66                    _ => "No Getter/Setter"
67                };
68               format!("{:>width$}: {}", key, display, width = $indent)
69            }
70        })
71    };
72
73    // A private overload of the macro
74    // DO NOT use directly
75    (impl $v:expr, $f:expr) => {
76        $v
77            .borrow()
78            .properties()
79            .iter()
80            .map($f)
81            .collect::<Vec<String>>()
82    };
83}
84
85pub(crate) fn log_string_from(x: &JsValue, print_internals: bool, print_children: bool) -> String {
86    match x {
87        // We don't want to print private (compiler) or prototype properties
88        JsValue::Object(ref v) => {
89            // Can use the private "type" field of an Object to match on
90            // which type of Object it represents for special printing
91            match v.borrow().kind() {
92                ObjectKind::String(ref string) => format!("String {{ \"{}\" }}", string),
93                ObjectKind::Boolean(boolean) => format!("Boolean {{ {} }}", boolean),
94                ObjectKind::Number(rational) => {
95                    if rational.is_sign_negative() && *rational == 0.0 {
96                        "Number { -0 }".to_string()
97                    } else {
98                        let mut buffer = ryu_js::Buffer::new();
99                        format!("Number {{ {} }}", buffer.format(*rational))
100                    }
101                }
102                ObjectKind::Array => {
103                    let len = v
104                        .borrow()
105                        .properties()
106                        .get(&PropertyKey::from("length"))
107                        // TODO: do this in a better way `unwrap`
108                        .unwrap()
109                        // FIXME: handle accessor descriptors
110                        .expect_value()
111                        .as_number()
112                        .map(|n| n as i32)
113                        .unwrap_or_default();
114
115                    if print_children {
116                        if len == 0 {
117                            return String::from("[]");
118                        }
119
120                        let arr = (0..len)
121                            .map(|i| {
122                                // Introduce recursive call to stringify any objects
123                                // which are part of the Array
124                                log_string_from(
125                                    v.borrow()
126                                        .properties()
127                                        .get(&i.into())
128                                        // FIXME: handle accessor descriptors
129                                        .and_then(|p| p.value())
130                                        .unwrap_or(&JsValue::Undefined),
131                                    print_internals,
132                                    false,
133                                )
134                            })
135                            .collect::<Vec<String>>()
136                            .join(", ");
137
138                        format!("[ {} ]", arr)
139                    } else {
140                        format!("Array({})", len)
141                    }
142                }
143                ObjectKind::Map(ref map) => {
144                    let size = map.len();
145                    if size == 0 {
146                        return String::from("Map(0)");
147                    }
148
149                    if print_children {
150                        let mappings = map
151                            .iter()
152                            .map(|(key, value)| {
153                                let key = log_string_from(key, print_internals, false);
154                                let value = log_string_from(value, print_internals, false);
155                                format!("{} → {}", key, value)
156                            })
157                            .collect::<Vec<String>>()
158                            .join(", ");
159                        format!("Map {{ {} }}", mappings)
160                    } else {
161                        format!("Map({})", size)
162                    }
163                }
164                ObjectKind::Set(ref set) => {
165                    let size = set.size();
166
167                    if size == 0 {
168                        return String::from("Set(0)");
169                    }
170
171                    if print_children {
172                        let entries = set
173                            .iter()
174                            .map(|value| log_string_from(value, print_internals, false))
175                            .collect::<Vec<String>>()
176                            .join(", ");
177                        format!("Set {{ {} }}", entries)
178                    } else {
179                        format!("Set({})", size)
180                    }
181                }
182                _ => display_obj(x, print_internals),
183            }
184        }
185        JsValue::Symbol(ref symbol) => symbol.to_string(),
186        _ => format!("{}", x.display()),
187    }
188}
189
190/// A helper function for specifically printing object values
191pub(crate) fn display_obj(v: &JsValue, print_internals: bool) -> String {
192    // A simple helper for getting the address of a value
193    // TODO: Find a more general place for this, as it can be used in other situations as well
194    fn address_of<T>(t: &T) -> usize {
195        let my_ptr: *const T = t;
196        my_ptr as usize
197    }
198
199    // We keep track of which objects we have encountered by keeping their
200    // in-memory address in this set
201    let mut encounters = HashSet::new();
202
203    if let JsValue::Object(object) = v {
204        if object.borrow().is_error() {
205            let name = v
206                .get_property("name")
207                .as_ref()
208                .and_then(|d| d.value())
209                .unwrap_or(&JsValue::Undefined)
210                .display()
211                .to_string();
212            let message = v
213                .get_property("message")
214                .as_ref()
215                .and_then(|d| d.value())
216                .unwrap_or(&JsValue::Undefined)
217                .display()
218                .to_string();
219            return format!("{}: {}", name, message);
220        }
221    }
222
223    fn display_obj_internal(
224        data: &JsValue,
225        encounters: &mut HashSet<usize>,
226        indent: usize,
227        print_internals: bool,
228    ) -> String {
229        if let JsValue::Object(ref v) = *data {
230            // The in-memory address of the current object
231            let addr = address_of(v.as_ref());
232
233            // We need not continue if this object has already been
234            // printed up the current chain
235            if encounters.contains(&addr) {
236                return String::from("[Cycle]");
237            }
238
239            // Mark the current object as encountered
240            encounters.insert(addr);
241
242            let result = if print_internals {
243                print_obj_value!(all of v, display_obj_internal, indent, encounters).join(",\n")
244            } else {
245                print_obj_value!(props of v, display_obj_internal, indent, encounters, print_internals)
246                        .join(",\n")
247            };
248
249            // If the current object is referenced in a different branch,
250            // it will not cause an infinte printing loop, so it is safe to be printed again
251            encounters.remove(&addr);
252
253            let closing_indent = String::from_utf8(vec![b' '; indent.wrapping_sub(4)])
254                .expect("Could not create the closing brace's indentation string");
255
256            format!("{{\n{}\n{}}}", result, closing_indent)
257        } else {
258            // Every other type of data is printed with the display method
259            format!("{}", data.display())
260        }
261    }
262
263    display_obj_internal(v, &mut encounters, 4, print_internals)
264}
265
266impl Display for ValueDisplay<'_> {
267    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
268        match self.value {
269            JsValue::Null => write!(f, "null"),
270            JsValue::Undefined => write!(f, "undefined"),
271            JsValue::Boolean(v) => write!(f, "{}", v),
272            JsValue::Symbol(ref symbol) => match symbol.description() {
273                Some(description) => write!(f, "Symbol({})", description),
274                None => write!(f, "Symbol()"),
275            },
276            JsValue::String(ref v) => write!(f, "\"{}\"", v),
277            JsValue::Rational(v) => format_rational(*v, f),
278            JsValue::Object(_) => write!(f, "{}", log_string_from(self.value, true, true)),
279            JsValue::Integer(v) => write!(f, "{}", v),
280            JsValue::BigInt(ref num) => write!(f, "{}n", num),
281        }
282    }
283}
284
285/// This is different from the ECMAScript compliant number to string, in the printing of `-0`.
286///
287/// This function prints `-0` as `-0` instead of pasitive `0` as the specification says.
288/// This is done to make it easer for the user of the REPL to identify what is a `-0` vs `0`,
289/// since the REPL is not bound to the ECMAScript specification we can do this.
290fn format_rational(v: f64, f: &mut fmt::Formatter<'_>) -> fmt::Result {
291    if v.is_sign_negative() && v == 0.0 {
292        f.write_str("-0")
293    } else {
294        let mut buffer = ryu_js::Buffer::new();
295        write!(f, "{}", buffer.format(v))
296    }
297}