1use super::Cx;
7use crate::heap::atom::{KeyKind, PropertyKey};
8use crate::heap::object::{InternalData, InternalTag, NONE, ObjectKind, flags};
9use crate::runtime::{Names, Runtime};
10use crate::shape::PropertyFlags;
11use crate::value::{Kind, ObjectId, StringId, Value};
12
13const MAX_DEPTH: usize = 2;
14const MAX_STRING_LENGTH: usize = 1_000;
15const MAX_ITEM_COUNT: usize = 100;
16
17#[derive(Clone, Copy, PartialEq, Eq)]
18enum Comma {
19 Empty,
21 Between,
23 Suffix,
25}
26
27enum Body {
28 Ordinary,
29 Array {
33 dense: Vec<Value>,
34 length: u32,
35 sparse: bool,
36 },
37 TypedArray,
38 Function,
39 Map(Vec<Option<(Value, Value)>>, usize),
41 Set(Vec<Option<Value>>, usize),
42 RegExp,
43 Date(f64),
44 Error,
45}
46
47enum Slot {
48 Data(Value),
49 Accessor { get: bool, set: bool },
50}
51
52struct Printer<'a, 'c> {
53 cx: &'a mut Cx<'c>,
54 out: Out,
55 stack: Vec<ObjectId>,
56}
57
58struct Out(Vec<u8>);
61
62impl Out {
63 fn push_str(&mut self, text: &str) {
64 self.0.extend_from_slice(text.as_bytes());
65 }
66
67 fn push(&mut self, character: char) {
68 let mut buffer = [0; 4];
69 self.push_str(character.encode_utf8(&mut buffer));
70 }
71
72 fn extend(&mut self, characters: impl Iterator<Item = char>) {
73 for character in characters {
74 self.push(character);
75 }
76 }
77}
78
79pub(crate) fn wtf8(units: impl Iterator<Item = u16>, out: &mut Vec<u8>) {
82 for point in char::decode_utf16(units) {
83 match point {
84 Ok(point) => {
85 let mut buffer = [0; 4];
86 out.extend_from_slice(point.encode_utf8(&mut buffer).as_bytes());
87 }
88 Err(error) => {
89 let unit = error.unpaired_surrogate();
90 out.extend_from_slice(&[
91 0xE0 | (unit >> 12) as u8,
92 0x80 | ((unit >> 6) & 0x3F) as u8,
93 0x80 | (unit & 0x3F) as u8,
94 ]);
95 }
96 }
97 }
98}
99
100impl Runtime {
101 pub fn print_value(&mut self, value: Value) -> String {
106 self.native_cx().print_value(value)
107 }
108
109 pub fn print_value_bytes(&mut self, value: Value) -> Vec<u8> {
111 self.native_cx().print_value_bytes(value)
112 }
113}
114
115impl crate::NativeCx<'_> {
116 pub fn print_value(&mut self, value: Value) -> String {
118 String::from_utf8_lossy(&print_value(&mut self.cx(), value)).into_owned()
119 }
120
121 pub fn print_value_bytes(&mut self, value: Value) -> Vec<u8> {
123 print_value(&mut self.cx(), value)
124 }
125}
126
127pub(crate) fn print_value(cx: &mut Cx<'_>, value: Value) -> Vec<u8> {
129 let mut printer = Printer {
130 cx,
131 out: Out(Vec::new()),
132 stack: Vec::with_capacity(MAX_DEPTH),
133 };
134 printer.value(value);
135 printer.out.0
136}
137
138impl Printer<'_, '_> {
139 fn value(&mut self, value: Value) {
140 match value.kind() {
141 Kind::Undefined => self.out.push_str("undefined"),
142 Kind::Null => self.out.push_str("null"),
143 Kind::Bool => self.out.push_str(if value.as_bool() == Some(true) {
144 "true"
145 } else {
146 "false"
147 }),
148 Kind::Int32 | Kind::Number => {
149 let number = value.as_f64().unwrap_or(f64::NAN);
150 if number == 0.0 && number.is_sign_negative() {
151 self.out.push_str("-0");
152 } else {
153 self.out.push_str(&super::number::to_string(number));
154 }
155 }
156 Kind::BigInt => {
157 let text = super::ops::big(self.cx.heap, value).to_string_radix10();
158 self.out.push_str(&text);
159 self.out.push('n');
160 }
161 Kind::String => self.string(value.as_string().expect("a string")),
162 Kind::Symbol => {
163 self.out.push_str("Symbol(");
164 let record = self.cx.heap.symbol(value.as_symbol().expect("a symbol"));
165 let units = if record.description == NONE {
166 Vec::new()
167 } else {
168 self.units(StringId(record.description))
169 };
170 self.atom_units(&units);
171 self.out.push(')');
172 }
173 Kind::Object => self.object(value.as_object().expect("an object")),
174 _ => self.out.push_str("uninitialized"),
175 }
176 }
177
178 fn units(&self, id: StringId) -> Vec<u16> {
179 self.cx.heap.string(id).repr().units().collect()
180 }
181
182 fn leaves(&self, id: StringId) -> Vec<Vec<u16>> {
186 let heap = &*self.cx.heap;
187 let mut leaves = Vec::new();
188 let mut stack = vec![id];
189 while let Some(id) = stack.pop() {
190 let record = heap.strings.get(id.0);
191 match (record.flat(), heap.ropes.get(&id.0)) {
192 (Some(flat), _) => leaves.push(flat.units().collect()),
193 (None, Some(&(left, right))) => {
194 stack.push(right);
195 stack.push(left);
196 }
197 (None, None) => {}
198 }
199 }
200 leaves
201 }
202
203 fn string(&mut self, id: StringId) {
204 let leaves = self.leaves(id);
205 let total: usize = leaves.iter().map(Vec::len).sum();
206 self.out.push('"');
207 let mut position = 0;
208 for leaf in &leaves {
209 if position < MAX_STRING_LENGTH {
210 let shown = leaf.len().min(MAX_STRING_LENGTH - position);
211 self.escaped(&leaf[..shown], '"');
212 }
213 position += leaf.len();
214 }
215 self.out.push('"');
216 if total > MAX_STRING_LENGTH {
217 let remaining = total - MAX_STRING_LENGTH;
218 self.out
219 .push_str(&format!("... {remaining} more character"));
220 if remaining > 1 {
221 self.out.push('s');
222 }
223 }
224 }
225
226 fn escaped(&mut self, units: &[u16], separator: char) {
227 let mut index = 0;
228 while index < units.len() {
229 let unit = units[index];
230 index += 1;
231 match unit {
232 0x09 => self.out.push_str("\\t"),
233 0x0d => self.out.push_str("\\r"),
234 0x0a => self.out.push_str("\\n"),
235 0x08 => self.out.push_str("\\b"),
236 0x0c => self.out.push_str("\\f"),
237 0x5c => self.out.push_str("\\\\"),
238 _ if unit == separator as u16 => {
239 self.out.push('\\');
240 self.out.push(separator);
241 }
242 0x20..=0x7e => self.out.push(unit as u8 as char),
243 0x00..=0x1f | 0x7f..=0x9f => self.unicode_escape(unit),
244 0xd800..=0xdbff => match units.get(index) {
245 Some(&low) if (0xdc00..=0xdfff).contains(&low) => {
246 index += 1;
247 let code = 0x1_0000
248 + ((u32::from(unit) - 0xd800) << 10)
249 + (u32::from(low) - 0xdc00);
250 self.out.push(char::from_u32(code).unwrap_or('\u{fffd}'));
251 }
252 _ => self.unicode_escape(unit),
253 },
254 0xdc00..=0xdfff => self.unicode_escape(unit),
255 _ => self
256 .out
257 .push(char::from_u32(u32::from(unit)).unwrap_or('\u{fffd}')),
258 }
259 }
260 }
261
262 fn unicode_escape(&mut self, unit: u16) {
263 self.out.push_str(&format!("\\u{unit:04x}"));
264 }
265
266 fn atom_units(&mut self, units: &[u16]) {
269 let identifier = !units.is_empty()
270 && units.iter().enumerate().all(|(index, &unit)| {
271 matches!(unit, 0x61..=0x7a | 0x41..=0x5a | 0x5f | 0x24)
272 || (index > 0 && (0x30..=0x39).contains(&unit))
273 });
274 if identifier {
275 self.out
276 .extend(units.iter().map(|&unit| unit as u8 as char));
277 } else {
278 self.out.push('"');
279 self.escaped(units, '"');
280 self.out.push('"');
281 }
282 }
283
284 fn key(&mut self, key: PropertyKey) {
285 match key.kind() {
286 KeyKind::Index(index) => self.out.push_str(&index.to_string()),
287 KeyKind::String(id) => {
288 let units = self.units(id);
289 match self.cx.heap.string(id).array_index {
290 NONE => self.atom_units(&units),
291 index => self.out.push_str(&index.to_string()),
292 }
293 }
294 KeyKind::Symbol(id) => {
295 let record = self.cx.heap.symbol(id);
296 let units = if record.description == NONE {
297 Vec::new()
298 } else {
299 self.units(StringId(record.description))
300 };
301 self.atom_units(&units);
302 }
303 }
304 }
305
306 fn class_name(&self, object: ObjectId) -> &'static str {
307 let heap = &self.cx.heap;
308 match heap.object_kind(object) {
309 ObjectKind::Array => "Array",
310 ObjectKind::Function | ObjectKind::BoundFunction => self.function_class(object),
311 ObjectKind::Arguments => "Arguments",
312 ObjectKind::RegExp => "RegExp",
313 ObjectKind::ForInIterator => "ForInIterator",
314 ObjectKind::StringWrapper => "String",
315 ObjectKind::NumberWrapper => "Number",
316 ObjectKind::BooleanWrapper => "Boolean",
317 ObjectKind::SymbolWrapper => "Symbol",
318 ObjectKind::BigIntWrapper => "BigInt",
319 ObjectKind::Date => "Date",
320 ObjectKind::TypedArray => super::typed::view(heap, object).0.kind.name(),
321 ObjectKind::ModuleNamespace => "Module",
322 ObjectKind::Builtin => match heap.internal_tag(object) {
323 InternalTag::ArrayIterator => "Array Iterator",
324 InternalTag::StringIterator => "String Iterator",
325 InternalTag::MapIterator => "Map Iterator",
326 InternalTag::SetIterator => "Set Iterator",
327 InternalTag::RegExpStringIterator => "RegExp String Iterator",
328 InternalTag::IteratorHelper => "Iterator Helper",
329 InternalTag::Iterator => "Iterator",
330 InternalTag::WrapForValidIterator => "Iterator Wrap",
331 InternalTag::AsyncFromSyncIterator => "",
332 InternalTag::Promise => "Promise",
333 InternalTag::Map => "Map",
334 InternalTag::Set => "Set",
335 InternalTag::WeakMap => "WeakMap",
336 InternalTag::WeakSet => "WeakSet",
337 InternalTag::WeakRef => "WeakRef",
338 InternalTag::FinalizationRegistry => "FinalizationRegistry",
339 InternalTag::Generator => "Generator",
340 InternalTag::AsyncGenerator => "AsyncGenerator",
341 InternalTag::AsyncFunction => "AsyncFunction",
342 InternalTag::ArrayBuffer => "ArrayBuffer",
343 InternalTag::SharedArrayBuffer => "SharedArrayBuffer",
344 InternalTag::DataView => "DataView",
345 _ => "Object",
346 },
347 _ if heap.header(object).has(flags::ERROR) => "Error",
348 _ => "Object",
349 }
350 }
351
352 fn function_class(&self, object: ObjectId) -> &'static str {
353 let heap = &self.cx.heap;
354 if heap.object_kind(object) != ObjectKind::Function {
355 return "Function";
356 }
357 let Some(bytecode) = heap.function_bytecode(object) else {
358 let native = heap.function_record(object).native;
360 return if native == crate::builtins::Native::PromiseResolveFunction.index() {
361 "PromiseResolveFunction"
362 } else if native == crate::builtins::Native::PromiseRejectFunction.index() {
363 "PromiseRejectFunction"
364 } else {
365 "Function"
366 };
367 };
368 let unit = self.cx.unit(heap.bytecode_unit(bytecode));
369 use crate::bytecode::flags as unit_flags;
370 match (unit.has(unit_flags::ASYNC), unit.has(unit_flags::GENERATOR)) {
371 (false, false) => "Function",
372 (false, true) => "GeneratorFunction",
373 (true, false) => "AsyncFunction",
374 (true, true) => "AsyncGeneratorFunction",
375 }
376 }
377
378 fn object(&mut self, object: ObjectId) {
379 if let Some(index) = self.stack.iter().position(|&seen| seen == object) {
380 self.out.push_str(&format!("[circular {index}]"));
381 return;
382 }
383 if self.stack.len() >= MAX_DEPTH {
384 let name = self.class_name(object);
385 self.out.push('[');
386 let units: Vec<u16> = name.encode_utf16().collect();
387 self.atom_units(&units);
388 self.out.push(']');
389 return;
390 }
391 let body = self.body(object);
392 let properties = self.properties(object, &body);
393 self.stack.push(object);
394 self.print_object(object, body, properties);
395 self.stack.pop();
396 }
397
398 fn body(&mut self, object: ObjectId) -> Body {
399 let heap = &self.cx.heap;
400 if heap.header(object).has(flags::ERROR) {
401 return Body::Error;
402 }
403 match heap.object_kind(object) {
404 ObjectKind::Array => {
405 let length = heap.array_length(object);
406 if heap.header(object).has(flags::SPARSE) || heap.holey.contains(&object.0) {
407 return Body::Array {
408 dense: Vec::new(),
409 length,
410 sparse: true,
411 };
412 }
413 let mut dense = Vec::new();
414 let mut index = 0;
415 while index < length {
416 let value = heap.get_element(object, index);
417 if value.is_empty() {
418 break;
419 }
420 dense.push(value);
421 index += 1;
422 }
423 if (index..length).any(|later| !heap.get_element(object, later).is_empty()) {
426 return Body::Array {
427 dense: Vec::new(),
428 length,
429 sparse: true,
430 };
431 }
432 Body::Array {
433 dense,
434 length,
435 sparse: false,
436 }
437 }
438 ObjectKind::TypedArray => Body::TypedArray,
439 ObjectKind::Function | ObjectKind::BoundFunction => Body::Function,
440 ObjectKind::RegExp => Body::RegExp,
441 ObjectKind::Date => {
442 Body::Date(heap.internal_slot(object, 0).as_f64().unwrap_or(f64::NAN))
443 }
444 ObjectKind::Builtin => match heap.internal_tag(object) {
445 InternalTag::Map | InternalTag::Set => {
446 let map = heap.internal_tag(object) == InternalTag::Map;
447 let internal = heap.internal(object);
448 let InternalData::Table(table) = &internal.data else {
449 return Body::Ordinary;
450 };
451 let width = table.width();
452 let size = table.len();
453 let mut entries = Vec::new();
454 let mut live = 0;
455 for (index, entry) in internal.slots.chunks(width).enumerate() {
456 if live >= MAX_ITEM_COUNT {
457 break;
458 }
459 if entry[0].is_empty() {
460 if table.is_zombie(index) {
461 entries.push(None);
462 }
463 continue;
464 }
465 live += 1;
466 entries.push(Some((
467 entry[0],
468 if map { entry[1] } else { Value::UNDEFINED },
469 )));
470 }
471 if map {
472 Body::Map(entries, size)
473 } else {
474 Body::Set(
475 entries
476 .into_iter()
477 .map(|entry| entry.map(|(key, _)| key))
478 .collect(),
479 size,
480 )
481 }
482 }
483 _ => Body::Ordinary,
484 },
485 _ => Body::Ordinary,
486 }
487 }
488
489 fn properties(&self, object: ObjectId, body: &Body) -> (Vec<(PropertyKey, Slot)>, usize) {
493 let heap = &self.cx.heap;
494 let mut properties = Vec::new();
495 let mut count = 0;
496 let elements_are_properties =
497 !matches!(body, Body::Array { sparse: false, .. } | Body::TypedArray)
498 && heap.object_kind(object).has_elements()
499 && heap.object_kind(object) != ObjectKind::Arguments;
500 if elements_are_properties {
501 let header = heap.header(object);
502 let length = header.elements().len;
503 let limit = if header.kind == ObjectKind::Array {
504 length.min(header.payload)
505 } else {
506 length
507 };
508 for index in 0..limit {
509 let value = heap.get_element(object, index);
510 if value.is_empty() {
511 continue;
512 }
513 count += 1;
514 if properties.len() < MAX_ITEM_COUNT
515 && let Some(key) = PropertyKey::index(index)
516 {
517 properties.push((key, Slot::Data(value)));
518 }
519 }
520 }
521 for (key, flags, slot) in heap.shape_entries(heap.shape_of(object)) {
522 if !flags.has(PropertyFlags::ENUMERABLE) {
523 continue;
524 }
525 if let KeyKind::Symbol(id) = key.kind()
526 && heap.symbol(id).private
527 {
528 continue;
529 }
530 count += 1;
531 if properties.len() >= MAX_ITEM_COUNT {
532 continue;
533 }
534 let value = if flags.has(PropertyFlags::ACCESSOR) {
535 Slot::Accessor {
536 get: !heap.get_slot(object, slot).is_undefined(),
537 set: !heap.get_slot(object, slot + 1).is_undefined(),
538 }
539 } else {
540 Slot::Data(heap.get_slot(object, slot))
541 };
542 properties.push((key, value));
543 }
544 if heap.object_kind(object) == ObjectKind::Arguments
545 && heap.header(object).has(crate::heap::object::flags::SPARSE)
546 {
547 self.slow_arguments(object, &mut properties, &mut count);
548 }
549 (properties, count)
550 }
551
552 fn slow_arguments(
556 &self,
557 object: ObjectId,
558 properties: &mut Vec<(PropertyKey, Slot)>,
559 count: &mut usize,
560 ) {
561 let heap = &self.cx.heap;
562 for index in 0..crate::vm::arguments::mapped_count(heap, object) {
563 let (Some(cell), Some(key)) = (
564 crate::vm::arguments::mapped_cell(heap, object, index),
565 PropertyKey::index(index),
566 ) else {
567 continue;
568 };
569 if heap.lookup_own(object, key).is_none() {
570 *count += 1;
571 if properties.len() < MAX_ITEM_COUNT {
572 properties.push((key, Slot::Data(heap.scope_get(cell, 0, 0))));
573 }
574 }
575 }
576 let rank = |key: &PropertyKey| match key.kind() {
577 KeyKind::Index(index) => u64::from(index),
578 _ => u64::MAX,
579 };
580 properties.sort_by_key(|(key, _)| rank(key));
581 }
582
583 fn comma(&mut self, state: &mut Comma) {
584 match *state {
585 Comma::Empty => {}
586 Comma::Between => self.out.push_str(", "),
587 Comma::Suffix => self.out.push_str(" { "),
588 }
589 *state = Comma::Between;
590 }
591
592 fn more_items(&mut self, state: &mut Comma, count: usize) {
593 self.comma(state);
594 self.out.push_str(&format!("... {count} more item"));
595 if count > 1 {
596 self.out.push('s');
597 }
598 }
599
600 fn print_object(
601 &mut self,
602 object: ObjectId,
603 body: Body,
604 (properties, count): (Vec<(PropertyKey, Slot)>, usize),
605 ) {
606 let mut state = Comma::Empty;
607 let mut is_array = false;
608 match body {
609 Body::Array {
610 dense,
611 length,
612 sparse,
613 } => {
614 is_array = true;
615 self.out.push_str("[ ");
616 for &value in dense.iter().take(MAX_ITEM_COUNT) {
617 self.comma(&mut state);
618 self.value(value);
619 }
620 if dense.len() > MAX_ITEM_COUNT {
621 self.more_items(&mut state, dense.len() - MAX_ITEM_COUNT);
622 }
623 if !sparse && (dense.len() as u32) < length {
624 let empty = length as usize - dense.len();
625 self.comma(&mut state);
626 self.out.push_str(&format!("<{empty} empty item"));
627 if empty > 1 {
628 self.out.push('s');
629 }
630 self.out.push('>');
631 }
632 }
633 Body::TypedArray => {
634 is_array = true;
635 let name = self.class_name(object);
636 let length = super::typed::length(self.cx.heap, object).unwrap_or(0);
637 self.atom_units(&name.encode_utf16().collect::<Vec<_>>());
638 self.out.push_str(&format!("({length}) [ "));
639 let shown = length.min(MAX_ITEM_COUNT);
640 for index in 0..shown {
641 self.comma(&mut state);
642 match super::typed::get_element(self.cx, object, index as f64) {
643 Ok(Some(value)) if value.kind() == Kind::BigInt => {
646 let text = super::ops::big(self.cx.heap, value).to_string_radix10();
647 self.out.push_str(&text);
648 }
649 Ok(Some(value)) => self.value(value),
650 _ => self.out.push_str("undefined"),
651 }
652 }
653 if shown < length {
654 self.more_items(&mut state, length - shown);
655 }
656 }
657 Body::Function => {
658 self.out.push_str("[Function ");
659 match self.raw_string(object, self.cx.realm.names.name) {
660 Some(name) if !name.is_empty() => self.c_string(&name),
661 _ => self.out.push_str("(anonymous)"),
662 }
663 self.out.push(']');
664 state = Comma::Suffix;
665 }
666 Body::Map(entries, size) => {
667 self.out.push_str(&format!("Map({size}) {{ "));
668 let mut live = 0;
669 for &entry in &entries {
670 self.comma(&mut state);
671 if let Some((key, value)) = entry {
672 self.value(key);
673 self.out.push_str(" => ");
674 self.value(value);
675 live += 1;
676 }
677 }
678 if live < size {
679 self.more_items(&mut state, size - live);
680 }
681 }
682 Body::Set(entries, size) => {
683 self.out.push_str(&format!("Set({size}) {{ "));
684 let mut live = 0;
685 for &entry in &entries {
686 self.comma(&mut state);
687 if let Some(key) = entry {
688 self.value(key);
689 live += 1;
690 }
691 }
692 if live < size {
693 self.more_items(&mut state, size - live);
694 }
695 }
696 Body::RegExp => {
697 self.regexp(object);
698 state = Comma::Suffix;
699 }
700 Body::Date(time) => match crate::builtins::date_iso_string(self.cx, time) {
701 Some(text) => {
702 self.out.push_str(&text);
703 state = Comma::Suffix;
704 }
705 None => self.out.push_str("Date { "),
706 },
707 Body::Error => {
708 self.error(object);
709 state = Comma::Suffix;
710 }
711 Body::Ordinary => {
712 let name = self.class_name(object);
713 let plain =
714 self.cx.heap.object_kind(object) == ObjectKind::Ordinary && name == "Object";
715 if !plain {
716 self.atom_units(&name.encode_utf16().collect::<Vec<_>>());
717 self.out.push(' ');
718 }
719 self.out.push_str("{ ");
720 }
721 }
722 let shown = properties.len();
723 for (key, slot) in properties {
724 self.comma(&mut state);
725 self.key(key);
726 self.out.push_str(": ");
727 match slot {
728 Slot::Data(value) => self.value(value),
729 Slot::Accessor { get, set } => self.out.push_str(match (get, set) {
730 (true, true) => "[Getter/Setter]",
731 (false, true) => "[Setter]",
732 _ => "[Getter]",
733 }),
734 }
735 }
736 if shown < count {
737 self.more_items(&mut state, count - shown);
738 }
739 if is_array {
740 self.out.push_str(" ]");
741 } else if state != Comma::Suffix {
742 self.out.push_str(" }");
743 }
744 }
745
746 fn raw_string(&self, object: ObjectId, name: StringId) -> Option<Vec<u16>> {
749 let heap = &self.cx.heap;
750 let key = Names::key(name);
751 let read = |object: ObjectId| -> Option<Option<Vec<u16>>> {
752 let (slot, flags) = heap.lookup_own(object, key)?;
753 if flags.has(PropertyFlags::ACCESSOR) {
754 return Some(None);
755 }
756 Some(
759 heap.get_slot(object, slot)
760 .as_string()
761 .filter(|id| heap.strings.get(id.0).flat().is_some())
762 .map(|id| heap.string(id).repr().units().collect()),
763 )
764 };
765 match read(object) {
766 Some(found) => found,
767 None => heap.prototype_of(object).and_then(read).flatten(),
768 }
769 }
770
771 fn c_string(&mut self, units: &[u16]) {
774 let end = units
775 .iter()
776 .position(|&unit| unit == 0)
777 .unwrap_or(units.len());
778 wtf8(units[..end].iter().copied(), &mut self.out.0);
779 }
780
781 fn error(&mut self, object: ObjectId) {
782 let names = &self.cx.realm.names;
783 let (name, message, stack) = (names.name, names.message, names.stack);
784 match self.raw_string(object, name) {
785 Some(name) => self.c_string(&name),
786 None => self.out.push_str("Error"),
787 }
788 if let Some(message) = self.raw_string(object, message)
789 && message.first() != Some(&0)
790 && !message.is_empty()
791 {
792 self.out.push_str(": ");
793 self.c_string(&message);
794 }
795 if let Some(mut units) = self.raw_string(object, stack) {
796 if let Some(nul) = units.iter().position(|&unit| unit == 0) {
797 units.truncate(nul);
798 }
799 if units.last() == Some(&0x0a) {
800 units.pop();
801 }
802 self.out.push('\n');
803 wtf8(units.into_iter(), &mut self.out.0);
804 }
805 }
806
807 fn regexp(&mut self, object: ObjectId) {
808 let index = self.cx.heap.header(object).payload;
809 let record = self.cx.heap.regexps.get(index);
810 let pattern = self.units(record.source);
811 let flags = self.units(record.flags);
812 self.out.push('/');
813 if pattern.is_empty() {
814 self.out.push_str("(?:)");
815 } else {
816 let mut in_class = false;
817 let mut index = 0;
818 while index < pattern.len() {
819 let mut first = pattern[index];
820 index += 1;
821 let mut second = None;
822 match first {
823 0x5c => {
824 if let Some(&next) = pattern.get(index) {
825 second = Some(next);
826 index += 1;
827 }
828 }
829 0x5d => in_class = false,
830 0x5b if !in_class => {
831 if pattern.get(index) == Some(&0x5d) {
832 second = Some(0x5d);
833 index += 1;
834 }
835 in_class = true;
836 }
837 0x0a => {
838 first = 0x5c;
839 second = Some(u16::from(b'n'));
840 }
841 0x0d => {
842 first = 0x5c;
843 second = Some(u16::from(b'r'));
844 }
845 0x2f if !in_class => {
846 first = 0x5c;
847 second = Some(0x2f);
848 }
849 _ => {}
850 }
851 self.out.push(first as u8 as char);
853 if let Some(second) = second {
854 self.out.push(second as u8 as char);
855 }
856 }
857 }
858 self.out.push('/');
859 for flag in "gimsuyd".chars() {
863 if flags.contains(&(flag as u16)) {
864 self.out.push(flag);
865 }
866 }
867 if self.cx.heap.regexps.program(index).has_named_captures() {
868 self.out.push('v');
869 }
870 }
871}